Compare commits
	
		
			355 Commits
		
	
	
		
			38d746b310
			...
			v0.2025.10
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 4052e3235f | |||
| 13302ad1c7 | |||
| 0f8687e473 | |||
| 9399ccd684 | |||
| b3604039fa | |||
| 732089da2c | |||
| b3e7e4b196 | |||
| 09a0cfd349 | |||
| 5bf7346321 | |||
| dd558c57e0 | |||
| 5647196924 | |||
| 49b1834bc6 | |||
| d111647ea8 | |||
| d7580dab9b | |||
| 28db8a8d5f | |||
| e64d5617e7 | |||
| acd114650a | |||
| 7a47ffaa61 | |||
| 42f7f66f35 | |||
| b2b4ffeeae | |||
| 26de1f7daa | |||
| 07605933dc | |||
| 4bdc7ec616 | |||
| 8ca64550e5 | |||
| 25dbac804c | |||
| 6ab3fd168b | |||
| 94858e2371 | |||
| 6d13502e94 | |||
| 77001e595c | |||
| 6fad20ffa3 | |||
| 00fb6c9839 | |||
| 97fcf72d63 | |||
| 5d8d02515d | |||
| 859fe1feb0 | |||
| 8f61d83f41 | |||
| 6423b3e479 | |||
| 2bc8cec8a2 | |||
| b49a6cd685 | |||
| 2885380f40 | |||
| 2ec3b6a249 | |||
| 3ef795452d | |||
| 479d87c8b8 | |||
| a56077dcc7 | |||
| d3f4587c3b | |||
| 623705b7a1 | |||
| 8f87f4751d | |||
| 2ac6dfde9d | |||
| 81ade7a400 | |||
| 63f7ff9f27 | |||
| 8a0fa17a79 | |||
| 0ead5ed967 | |||
| 53261a6fbc | |||
| c60ff86a4d | |||
| 83a0b017c5 | |||
| 3746622a11 | |||
| ccd50cf59f | |||
| 93680eb43d | |||
| 3ae4b7086a | |||
| 446b1f8600 | |||
| 00fd208a2c | |||
| e574d03716 | |||
| c1f3116c9d | |||
| 3aec7e6c14 | |||
| 9f0020dec8 | |||
| 6e78ad9729 | |||
| 44d84a9b2a | |||
| ac7809415c | |||
| 675cecaa20 | |||
| 5d179cc088 | |||
| d905618590 | |||
| 3fd9bc0b18 | |||
| 39abee7f73 | |||
| b770619111 | |||
| 1c44857da4 | |||
| bca4440867 | |||
| 4855543961 | |||
| cb3d6a98b9 | |||
| ada67a13d3 | |||
| f4c928f26e | |||
| 91fd515d39 | |||
| be6e841d3d | |||
| af6afa6903 | |||
| 6ab5d2a28d | |||
| 4be033f288 | |||
| c550f92003 | |||
| ed836b3ee0 | |||
| ac7a43abf4 | |||
| 49f19fce91 | |||
| b2197eb8e9 | |||
| 5fbc2cae1c | |||
| 730abb49ce | |||
| edccab054a | |||
| e8210c6fdd | |||
| 55d69d7c13 | |||
| 50fb18d4ff | |||
| 77b1ea1fc8 | |||
| 61501a9b64 | |||
| c1507adac5 | |||
| 45fb9eda1c | |||
| 982a61f4bf | |||
| 18e5b41663 | |||
| 910c39cbd0 | |||
| 9952dfd49d | |||
| 00f75d5382 | |||
| d78828554b | |||
| b84b561109 | |||
| a618815500 | |||
| e1f3dc6ae4 | |||
| f378db6c6f | |||
| 7cec0f7d61 | |||
| f902d0374c | |||
| b5f0a0c4f7 | |||
| 00623cea09 | |||
| ed4f1d6f2c | |||
| 73f4a3407f | |||
| 6f11318e84 | |||
| e88ee91f0e | |||
| 3f8daf257c | |||
| dc387acadc | |||
| 68aa41ab96 | |||
| 85b23437b3 | |||
| c59fba817d | |||
| c3415ab75c | |||
| f1d0151d71 | |||
| 3c5c1756d1 | |||
| 6a6b65d1b3 | |||
| 81bd54dbe6 | |||
| 6a1bb0d3bc | |||
| 705e8b553f | |||
| e4729b22f2 | |||
| 662112551a | |||
| 38fe88aab8 | |||
| 578c51faa0 | |||
| a3ccc73b81 | |||
| 7312f4d43a | |||
| 8b546c7e02 | |||
| c0b6ff2e64 | |||
| 638b7cc1e5 | |||
| 05e54e1be0 | |||
| 4c3299ead0 | |||
| 1ef56b35ad | |||
| 061e79c295 | |||
| 5edfe732b1 | |||
| a8f9b67f71 | |||
| de7fbf1eb7 | |||
| a51a3d7e43 | |||
| 433b3b1003 | |||
| 6703c5b584 | |||
| 5f729efabe | |||
| b2085b3f28 | |||
| 2f893494b0 | |||
| e26af21f63 | |||
| 7e1d738f8d | |||
| 199448e11e | |||
| fdaabab807 | |||
| ca4560c5c9 | |||
| 2478f3064d | |||
| e9b8b43e7c | |||
| 951155f1b6 | |||
| 1b678175ef | |||
| 8eb1f40eec | |||
| 235887b3bf | |||
| 0b3d66dd48 | |||
| beb9ef3754 | |||
| 9f6a480736 | |||
| b3bac2927d | |||
| ef389f2ba2 | |||
| ef21dc6ae8 | |||
| 6e55b6b49e | |||
| db115ef1bd | |||
| 678838dbd5 | |||
| 586f87625d | |||
| 1542370f9b | |||
| 1f7d5968c7 | |||
| 39e51f7790 | |||
| 052663efbe | |||
| 8f84ff2611 | |||
| 37e1c5d97b | |||
| cef526bcf3 | |||
| 6af36cafa9 | |||
| fca859d93d | |||
| 2178300d8d | |||
| 636bdcce6b | |||
| 94b7703ca9 | |||
| a391dd1316 | |||
| b6ba5211b7 | |||
| 8e8e130045 | |||
| 1f40bc1a0f | |||
| 5437212222 | |||
| a8ab845cd2 | |||
| 8cee6dc98b | |||
| 70c2b73414 | |||
| 98013c4422 | |||
| e9e22b762d | |||
| 620db19936 | |||
| 94a79dd62c | |||
| b56c3efde0 | |||
| 066827f8f1 | |||
| c3b65d9cd8 | |||
| a15b916b06 | |||
| 31d0a5c233 | |||
| 140179e80a | |||
| 53cba2d7e4 | |||
| e54312d3b8 | |||
| cadc27b7b5 | |||
| 388b829ec1 | |||
| 67861f0f33 | |||
| a1f1eb34d5 | |||
| 2a6789063e | |||
| cbf1273a55 | |||
| 8143a23ced | |||
| 3c17810747 | |||
| bea7a2e9ed | |||
| 2f0a2ac6b0 | |||
| 18908b6b56 | |||
| b135a210cc | |||
| 3a2a829940 | |||
| e56dd2dd2d | |||
| 3f41a48bc7 | |||
| 65ed53281a | |||
| 1121557a2e | |||
| d4a7b86ee7 | |||
| 626c18b04e | |||
| bfa97ed7c7 | |||
| deae4d5367 | |||
| 899605c860 | |||
| dc9a279991 | |||
| 2a53892581 | |||
| 6bef0eb764 | |||
| 462b40640c | |||
| 72e1b2025c | |||
| fc7c4b1257 | |||
| 6c22c59056 | |||
| 94c2b1184f | |||
| 45231d703d | |||
| 7882fcbe8f | |||
| 3bbc8c4d35 | |||
| 8ae10dc80b | |||
| 9b11c2c629 | |||
| e2a231fb4a | |||
| 8a9502d1f2 | |||
| 534438df63 | |||
| 45a4feec96 | |||
| aa7a32395e | |||
| ab9f57f044 | |||
| 4040d6aa08 | |||
| 1c96f5c35e | |||
| 4d3e42812d | |||
| f7b3711d4f | |||
| 2408e076ff | |||
| 6f71ffb477 | |||
| 214433f36a | |||
| 309b22732e | |||
| 6fe7687b2a | |||
| a8cbf757ff | |||
| 4a4bedfe2b | |||
| 051291f725 | |||
| d2b338095f | |||
| 899827a8f2 | |||
| 5fcbe3d6a9 | |||
| a0a40e6cb2 | |||
| bb1190e3f8 | |||
| 0a3baed1da | |||
| 4931c489ed | |||
| 996f9abaa2 | |||
| 08c097e176 | |||
| daa861a98b | |||
| a25d08fd76 | |||
| 392d31cc53 | |||
| 92926fa8df | |||
| 61ae9ae465 | |||
| 89622697d5 | |||
| 17694f5646 | |||
| 5a1303149f | |||
| 8a0e190a86 | |||
| 0d7dfd8c9e | |||
| f979ff7050 | |||
| e3fcdea362 | |||
| 476fec2757 | |||
| 53c215399b | |||
| 2c330802da | |||
| 851d7046ea | |||
| c0019d7246 | |||
| 7688e4d3a8 | |||
| ef58749ce3 | |||
| 35941a7ddc | |||
| 1f2664e5a8 | |||
| 35656a5c34 | |||
| 799f22e989 | |||
| e226a37251 | |||
| 8e3bc9d700 | |||
| 58c3e6c2ab | |||
| 0dc148bfea | |||
| 3eff1b08a9 | |||
| 02d789471f | |||
| d367d47c4d | |||
| c93b8fc045 | |||
| eb9377e21d | |||
| a1764eee42 | |||
| 86ef74e20d | |||
| 4de53b9926 | |||
| 99a195a3fd | |||
| f1ced31f69 | |||
| b3cedf2baa | |||
| 3bf19fabda | |||
| cf81ebe8ad | |||
| 278b5566a1 | |||
| e8c1390f09 | |||
| 3c04abda45 | |||
| 2597f99ccf | |||
| 9d3a07c1cf | |||
| bdfd8925b5 | |||
| 1a4d1985f4 | |||
| 6273f3ea53 | |||
| 5bdc6fa471 | |||
| 3ba41291db | |||
| 0867811952 | |||
| 8d961cd805 | |||
| 97cea7b40b | |||
| 4106834db8 | |||
| a4a8f7cab2 | |||
| 9e209ee800 | |||
| ddfa84f040 | |||
| 6b3a6ec7c1 | |||
| 4d037c02bf | |||
| deaeab10d8 | |||
| 2a5375b1e7 | |||
| e7a03e3283 | |||
| efb3a12dcc | |||
| 3830d695d7 | |||
| f36620927b | |||
| 5423cbd628 | |||
| abde709e54 | |||
| 27f2d319ab | |||
| 66234b14bc | |||
| 6a9167e565 | |||
| 3c60f8ca06 | |||
| c26bf5c112 | |||
| 41cbde934a | |||
| 946941d95e | |||
| 50f0104239 | |||
| 40fa7edadf | |||
| d6926569c6 | |||
| a8bba324ca | |||
| 5bba5776b3 | |||
| 8104f6f228 | |||
| 3f4738e593 | |||
| 1516e17f5d | |||
| 676d2702b7 | |||
| 5d39548964 | |||
| 67d458bd38 | |||
| d9684c7d62 | |||
| 5a818d2119 | |||
| 6f96d4ce65 | |||
| f72395756a | 
| @@ -48,7 +48,7 @@ jobs: | ||||
|       - name: Build documentation | ||||
|         run: | | ||||
|           mkdir -p out/html/ ~/.ssh/ | ||||
|           make docs | ||||
|           make -j`nproc` docs | ||||
|           echo 'pildefriends ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIKD3Kde5vDO0TrMBDK0IGGeNGe/XinWAZkSQ/rXxwUjt' >> ~/.ssh/known_hosts | ||||
|           rsync -avP --delete -e "ssh -i /opt/keys/ssh.ed25519" out/html/ tfdocs@pildefriends:docs/html/ | ||||
|       - name: Setup JDK | ||||
| @@ -59,11 +59,11 @@ jobs: | ||||
|       - name: Setup Android SDK | ||||
|         uses: android-actions/setup-android@v3 | ||||
|         with: | ||||
|           packages: 'tools platform-tools build-tools;34.0.0 platforms;android-34 ndk;26.3.11579264' | ||||
|           packages: 'tools platform-tools build-tools;35.0.0 platforms;android-35 ndk;27.2.12479018' | ||||
|       - name: Docker build | ||||
|         run: DOCKER_BUILDKIT=1 docker build . | ||||
|       - name: Build | ||||
|         run: ANDROID_SDK=$HOME/.android/sdk make -j`nproc` all dist docs | ||||
|         run: ANDROID_SDK=$HOME/.android/sdk make -j`nproc` all dist | ||||
|       - name: Upload artifacts | ||||
|         uses: actions/upload-artifact@v3 | ||||
|         with: | ||||
|   | ||||
							
								
								
									
										4
									
								
								.gitmodules
									
									
									
									
										vendored
									
									
								
							
							
						
						| @@ -19,10 +19,6 @@ | ||||
| [submodule "deps/picohttpparser"] | ||||
| 	path = deps/picohttpparser | ||||
| 	url = https://github.com/h2o/picohttpparser.git | ||||
| [submodule "deps/openssl_src"] | ||||
| 	path = deps/openssl_src | ||||
| 	url = https://github.com/openssl/openssl.git | ||||
| 	shallow = true | ||||
| [submodule "deps/c-ares"] | ||||
| 	path = deps/c-ares | ||||
| 	url = https://github.com/c-ares/c-ares.git | ||||
|   | ||||
| @@ -4,7 +4,6 @@ RUN apt-get update && \ | ||||
| 	apt-get install -y --no-install-recommends \ | ||||
| 		gcc \ | ||||
| 		libc6-dev \ | ||||
| 		perl \ | ||||
| 		make | ||||
|  | ||||
| COPY . /app | ||||
|   | ||||
							
								
								
									
										366
									
								
								Doxyfile
									
									
									
									
									
								
							
							
						
						| @@ -1,4 +1,4 @@ | ||||
| # Doxyfile 1.9.8 | ||||
| # Doxyfile 1.9.4 | ||||
|  | ||||
| # This file describes the settings to be used by the documentation system | ||||
| # doxygen (www.doxygen.org) for a project. | ||||
| @@ -19,8 +19,7 @@ | ||||
| # configuration file: | ||||
| # doxygen -x [configFile] | ||||
| # Use doxygen to compare the used configuration file with the template | ||||
| # configuration file without replacing the environment variables or CMake type | ||||
| # replacement variables: | ||||
| # configuration file without replacing the environment variables: | ||||
| # doxygen -x_noenv [configFile] | ||||
|  | ||||
| #--------------------------------------------------------------------------- | ||||
| @@ -86,7 +85,7 @@ CREATE_SUBDIRS         = NO | ||||
| # level increment doubles the number of directories, resulting in 4096 | ||||
| # directories at level 8 which is the default and also the maximum value. The | ||||
| # sub-directories are organized in 2 levels, the first level always has a fixed | ||||
| # number of 16 directories. | ||||
| # numer of 16 directories. | ||||
| # Minimum value: 0, maximum value: 8, default value: 8. | ||||
| # This tag requires that the tag CREATE_SUBDIRS is set to YES. | ||||
|  | ||||
| @@ -342,7 +341,7 @@ OPTIMIZE_OUTPUT_SLICE  = NO | ||||
| # | ||||
| # Note see also the list of default file extension mappings. | ||||
|  | ||||
| EXTENSION_MAPPING      = | ||||
| EXTENSION_MAPPING      = js=javascript | ||||
|  | ||||
| # If the MARKDOWN_SUPPORT tag is enabled then doxygen pre-processes all comments | ||||
| # according to the Markdown format, which allows for more readable | ||||
| @@ -363,17 +362,6 @@ MARKDOWN_SUPPORT       = YES | ||||
|  | ||||
| TOC_INCLUDE_HEADINGS   = 5 | ||||
|  | ||||
| # The MARKDOWN_ID_STYLE tag can be used to specify the algorithm used to | ||||
| # generate identifiers for the Markdown headings. Note: Every identifier is | ||||
| # unique. | ||||
| # Possible values are: DOXYGEN use a fixed 'autotoc_md' string followed by a | ||||
| # sequence number starting at 0 and GITHUB use the lower case version of title | ||||
| # with any whitespace replaced by '-' and punctuation characters removed. | ||||
| # The default value is: DOXYGEN. | ||||
| # This tag requires that the tag MARKDOWN_SUPPORT is set to YES. | ||||
|  | ||||
| MARKDOWN_ID_STYLE      = DOXYGEN | ||||
|  | ||||
| # When enabled doxygen tries to link words that correspond to documented | ||||
| # classes, or namespaces to their corresponding documentation. Such a link can | ||||
| # be prevented in individual cases by putting a % sign in front of the word or | ||||
| @@ -498,14 +486,6 @@ LOOKUP_CACHE_SIZE      = 0 | ||||
|  | ||||
| NUM_PROC_THREADS       = 1 | ||||
|  | ||||
| # If the TIMESTAMP tag is set different from NO then each generated page will | ||||
| # contain the date or date and time when the page was generated. Setting this to | ||||
| # NO can help when comparing the output of multiple runs. | ||||
| # Possible values are: YES, NO, DATETIME and DATE. | ||||
| # The default value is: NO. | ||||
|  | ||||
| TIMESTAMP              = NO | ||||
|  | ||||
| #--------------------------------------------------------------------------- | ||||
| # Build related configuration options | ||||
| #--------------------------------------------------------------------------- | ||||
| @@ -587,8 +567,7 @@ HIDE_UNDOC_MEMBERS     = NO | ||||
| # If the HIDE_UNDOC_CLASSES tag is set to YES, doxygen will hide all | ||||
| # undocumented classes that are normally visible in the class hierarchy. If set | ||||
| # to NO, these classes will be included in the various overviews. This option | ||||
| # will also hide undocumented C++ concepts if enabled. This option has no effect | ||||
| # if EXTRACT_ALL is enabled. | ||||
| # has no effect if EXTRACT_ALL is enabled. | ||||
| # The default value is: NO. | ||||
|  | ||||
| HIDE_UNDOC_CLASSES     = NO | ||||
| @@ -626,8 +605,7 @@ INTERNAL_DOCS          = NO | ||||
| # Windows (including Cygwin) and MacOS, users should typically set this option | ||||
| # to NO, whereas on Linux or other Unix flavors it should typically be set to | ||||
| # YES. | ||||
| # Possible values are: SYSTEM, NO and YES. | ||||
| # The default value is: SYSTEM. | ||||
| # The default value is: system dependent. | ||||
|  | ||||
| CASE_SENSE_NAMES       = YES | ||||
|  | ||||
| @@ -879,26 +857,11 @@ WARN_IF_INCOMPLETE_DOC = YES | ||||
|  | ||||
| WARN_NO_PARAMDOC       = NO | ||||
|  | ||||
| # If WARN_IF_UNDOC_ENUM_VAL option is set to YES, doxygen will warn about | ||||
| # undocumented enumeration values. If set to NO, doxygen will accept | ||||
| # undocumented enumeration values. If EXTRACT_ALL is set to YES then this flag | ||||
| # will automatically be disabled. | ||||
| # The default value is: NO. | ||||
|  | ||||
| WARN_IF_UNDOC_ENUM_VAL = NO | ||||
|  | ||||
| # If the WARN_AS_ERROR tag is set to YES then doxygen will immediately stop when | ||||
| # a warning is encountered. If the WARN_AS_ERROR tag is set to FAIL_ON_WARNINGS | ||||
| # then doxygen will continue running as if WARN_AS_ERROR tag is set to NO, but | ||||
| # at the end of the doxygen process doxygen will return with a non-zero status. | ||||
| # If the WARN_AS_ERROR tag is set to FAIL_ON_WARNINGS_PRINT then doxygen behaves | ||||
| # like FAIL_ON_WARNINGS but in case no WARN_LOGFILE is defined doxygen will not | ||||
| # write the warning messages in between other messages but write them at the end | ||||
| # of a run, in case a WARN_LOGFILE is defined the warning messages will be | ||||
| # besides being in the defined file also be shown at the end of a run, unless | ||||
| # the WARN_LOGFILE is defined as - i.e. standard output (stdout) in that case | ||||
| # the behavior will remain as with the setting FAIL_ON_WARNINGS. | ||||
| # Possible values are: NO, YES, FAIL_ON_WARNINGS and FAIL_ON_WARNINGS_PRINT. | ||||
| # Possible values are: NO, YES and FAIL_ON_WARNINGS. | ||||
| # The default value is: NO. | ||||
|  | ||||
| WARN_AS_ERROR          = NO | ||||
| @@ -944,6 +907,10 @@ WARN_LOGFILE           = | ||||
| # Note: If this tag is empty the current directory is searched. | ||||
|  | ||||
| INPUT                  = README.md \ | ||||
|                          core/app.js \ | ||||
|                          core/client.js \ | ||||
|                          core/core.js \ | ||||
|                          core/tfrpc.js \ | ||||
|                          docs/ \ | ||||
|                          src/ | ||||
|  | ||||
| @@ -952,21 +919,10 @@ INPUT                  = README.md \ | ||||
| # libiconv (or the iconv built into libc) for the transcoding. See the libiconv | ||||
| # documentation (see: | ||||
| # https://www.gnu.org/software/libiconv/) for the list of possible encodings. | ||||
| # See also: INPUT_FILE_ENCODING | ||||
| # The default value is: UTF-8. | ||||
|  | ||||
| INPUT_ENCODING         = UTF-8 | ||||
|  | ||||
| # This tag can be used to specify the character encoding of the source files | ||||
| # that doxygen parses The INPUT_FILE_ENCODING tag can be used to specify | ||||
| # character encoding on a per file pattern basis. Doxygen will compare the file | ||||
| # name with each pattern and apply the encoding instead of the default | ||||
| # INPUT_ENCODING) if there is a match. The character encodings are a list of the | ||||
| # form: pattern=encoding (like *.php=ISO-8859-1). See cfg_input_encoding | ||||
| # "INPUT_ENCODING" for further information on supported encodings. | ||||
|  | ||||
| INPUT_FILE_ENCODING    = | ||||
|  | ||||
| # If the value of the INPUT tag contains directories, you can use the | ||||
| # FILE_PATTERNS tag to specify one or more wildcard patterns (like *.cpp and | ||||
| # *.h) to filter out the source-files in the directories. | ||||
| @@ -978,14 +934,15 @@ INPUT_FILE_ENCODING    = | ||||
| # Note the list of default checked file patterns might differ from the list of | ||||
| # default file extension mappings. | ||||
| # | ||||
| # If left blank the following patterns are tested:*.c, *.cc, *.cxx, *.cxxm, | ||||
| # *.cpp, *.cppm, *.c++, *.c++m, *.java, *.ii, *.ixx, *.ipp, *.i++, *.inl, *.idl, | ||||
| # *.ddl, *.odl, *.h, *.hh, *.hxx, *.hpp, *.h++, *.ixx, *.l, *.cs, *.d, *.php, | ||||
| # *.php4, *.php5, *.phtml, *.inc, *.m, *.markdown, *.md, *.mm, *.dox (to be | ||||
| # provided as doxygen C comment), *.py, *.pyw, *.f90, *.f95, *.f03, *.f08, | ||||
| # *.f18, *.f, *.for, *.vhd, *.vhdl, *.ucf, *.qsf and *.ice. | ||||
| # If left blank the following patterns are tested:*.c, *.cc, *.cxx, *.cpp, | ||||
| # *.c++, *.java, *.ii, *.ixx, *.ipp, *.i++, *.inl, *.idl, *.ddl, *.odl, *.h, | ||||
| # *.hh, *.hxx, *.hpp, *.h++, *.l, *.cs, *.d, *.php, *.php4, *.php5, *.phtml, | ||||
| # *.inc, *.m, *.markdown, *.md, *.mm, *.dox (to be provided as doxygen C | ||||
| # comment), *.py, *.pyw, *.f90, *.f95, *.f03, *.f08, *.f18, *.f, *.for, *.vhd, | ||||
| # *.vhdl, *.ucf, *.qsf and *.ice. | ||||
|  | ||||
| FILE_PATTERNS          = *.h \ | ||||
|                          *.js \ | ||||
|                          *.md | ||||
|  | ||||
| # The RECURSIVE tag can be used to specify whether or not subdirectories should | ||||
| @@ -1024,6 +981,9 @@ EXCLUDE_PATTERNS       = | ||||
| # output. The symbol name can be a fully qualified name, a word, or if the | ||||
| # wildcard * is used, a substring. Examples: ANamespace, AClass, | ||||
| # ANamespace::AClass, ANamespace::*Test | ||||
| # | ||||
| # Note that the wildcards are matched against the file with absolute path, so to | ||||
| # exclude all test directories use the pattern */test/* | ||||
|  | ||||
| EXCLUDE_SYMBOLS        = | ||||
|  | ||||
| @@ -1051,7 +1011,7 @@ EXAMPLE_RECURSIVE      = NO | ||||
| # that contain images that are to be included in the documentation (see the | ||||
| # \image command). | ||||
|  | ||||
| IMAGE_PATH             = | ||||
| IMAGE_PATH             = docs/images/ | ||||
|  | ||||
| # The INPUT_FILTER tag can be used to specify a program that doxygen should | ||||
| # invoke to filter for each input file. Doxygen will invoke the filter program | ||||
| @@ -1068,11 +1028,6 @@ IMAGE_PATH             = | ||||
| # code is scanned, but not when the output code is generated. If lines are added | ||||
| # or removed, the anchors will not be placed correctly. | ||||
| # | ||||
| # Note that doxygen will use the data processed and written to standard output | ||||
| # for further processing, therefore nothing else, like debug statements or used | ||||
| # commands (so in case of a Windows batch file always use @echo OFF), should be | ||||
| # written to standard output. | ||||
| # | ||||
| # Note that for custom extensions or not directly supported extensions you also | ||||
| # need to set EXTENSION_MAPPING for the extension otherwise the files are not | ||||
| # properly processed by doxygen. | ||||
| @@ -1114,15 +1069,6 @@ FILTER_SOURCE_PATTERNS = | ||||
|  | ||||
| USE_MDFILE_AS_MAINPAGE = README.md | ||||
|  | ||||
| # The Fortran standard specifies that for fixed formatted Fortran code all | ||||
| # characters from position 72 are to be considered as comment. A common | ||||
| # extension is to allow longer lines before the automatic comment starts. The | ||||
| # setting FORTRAN_COMMENT_AFTER will also make it possible that longer lines can | ||||
| # be processed before the automatic comment starts. | ||||
| # Minimum value: 7, maximum value: 10000, default value: 72. | ||||
|  | ||||
| FORTRAN_COMMENT_AFTER  = 72 | ||||
|  | ||||
| #--------------------------------------------------------------------------- | ||||
| # Configuration options related to source browsing | ||||
| #--------------------------------------------------------------------------- | ||||
| @@ -1260,11 +1206,10 @@ CLANG_DATABASE_PATH    = | ||||
|  | ||||
| ALPHABETICAL_INDEX     = YES | ||||
|  | ||||
| # The IGNORE_PREFIX tag can be used to specify a prefix (or a list of prefixes) | ||||
| # that should be ignored while generating the index headers. The IGNORE_PREFIX | ||||
| # tag works for classes, function and member names. The entity will be placed in | ||||
| # the alphabetical list under the first letter of the entity name that remains | ||||
| # after removing the prefix. | ||||
| # In case all classes in a project start with a common prefix, all classes will | ||||
| # be put under the same header in the alphabetical index. The IGNORE_PREFIX tag | ||||
| # can be used to specify a prefix (or a list of prefixes) that should be ignored | ||||
| # while generating the index headers. | ||||
| # This tag requires that the tag ALPHABETICAL_INDEX is set to YES. | ||||
|  | ||||
| IGNORE_PREFIX          = | ||||
| @@ -1343,12 +1288,7 @@ HTML_STYLESHEET        = | ||||
| # Doxygen will copy the style sheet files to the output directory. | ||||
| # Note: The order of the extra style sheet files is of importance (e.g. the last | ||||
| # style sheet in the list overrules the setting of the previous ones in the | ||||
| # list). | ||||
| # Note: Since the styling of scrollbars can currently not be overruled in | ||||
| # Webkit/Chromium, the styling will be left out of the default doxygen.css if | ||||
| # one or more extra stylesheets have been specified. So if scrollbar | ||||
| # customization is desired it has to be added explicitly. For an example see the | ||||
| # documentation. | ||||
| # list). For an example see the documentation. | ||||
| # This tag requires that the tag GENERATE_HTML is set to YES. | ||||
|  | ||||
| HTML_EXTRA_STYLESHEET  = | ||||
| @@ -1363,19 +1303,6 @@ HTML_EXTRA_STYLESHEET  = | ||||
|  | ||||
| HTML_EXTRA_FILES       = | ||||
|  | ||||
| # The HTML_COLORSTYLE tag can be used to specify if the generated HTML output | ||||
| # should be rendered with a dark or light theme. | ||||
| # Possible values are: LIGHT always generate light mode output, DARK always | ||||
| # generate dark mode output, AUTO_LIGHT automatically set the mode according to | ||||
| # the user preference, use light mode if no preference is set (the default), | ||||
| # AUTO_DARK automatically set the mode according to the user preference, use | ||||
| # dark mode if no preference is set and TOGGLE allow to user to switch between | ||||
| # light and dark mode via a button. | ||||
| # The default value is: AUTO_LIGHT. | ||||
| # This tag requires that the tag GENERATE_HTML is set to YES. | ||||
|  | ||||
| HTML_COLORSTYLE        = AUTO_LIGHT | ||||
|  | ||||
| # The HTML_COLORSTYLE_HUE tag controls the color of the HTML output. Doxygen | ||||
| # will adjust the colors in the style sheet and background images according to | ||||
| # this color. Hue is specified as an angle on a color-wheel, see | ||||
| @@ -1406,6 +1333,15 @@ HTML_COLORSTYLE_SAT    = 100 | ||||
|  | ||||
| HTML_COLORSTYLE_GAMMA  = 80 | ||||
|  | ||||
| # If the HTML_TIMESTAMP tag is set to YES then the footer of each generated HTML | ||||
| # page will contain the date and time when the page was generated. Setting this | ||||
| # to YES can help to show when doxygen was last run and thus if the | ||||
| # documentation is up to date. | ||||
| # The default value is: NO. | ||||
| # This tag requires that the tag GENERATE_HTML is set to YES. | ||||
|  | ||||
| #HTML_TIMESTAMP         = NO | ||||
|  | ||||
| # If the HTML_DYNAMIC_MENUS tag is set to YES then the generated HTML | ||||
| # documentation will contain a main index with vertical navigation menus that | ||||
| # are dynamically created via JavaScript. If disabled, the navigation index will | ||||
| @@ -1425,13 +1361,6 @@ HTML_DYNAMIC_MENUS     = YES | ||||
|  | ||||
| HTML_DYNAMIC_SECTIONS  = NO | ||||
|  | ||||
| # If the HTML_CODE_FOLDING tag is set to YES then classes and functions can be | ||||
| # dynamically folded and expanded in the generated HTML source code. | ||||
| # The default value is: YES. | ||||
| # This tag requires that the tag GENERATE_HTML is set to YES. | ||||
|  | ||||
| HTML_CODE_FOLDING      = YES | ||||
|  | ||||
| # With HTML_INDEX_NUM_ENTRIES one can control the preferred number of entries | ||||
| # shown in the various tree structured indices initially; the user can expand | ||||
| # and collapse entries dynamically later on. Doxygen will expand the tree to | ||||
| @@ -1562,16 +1491,6 @@ BINARY_TOC             = NO | ||||
|  | ||||
| TOC_EXPAND             = NO | ||||
|  | ||||
| # The SITEMAP_URL tag is used to specify the full URL of the place where the | ||||
| # generated documentation will be placed on the server by the user during the | ||||
| # deployment of the documentation. The generated sitemap is called sitemap.xml | ||||
| # and placed on the directory specified by HTML_OUTPUT. In case no SITEMAP_URL | ||||
| # is specified no sitemap is generated. For information about the sitemap | ||||
| # protocol see https://www.sitemaps.org | ||||
| # This tag requires that the tag GENERATE_HTML is set to YES. | ||||
|  | ||||
| SITEMAP_URL            = | ||||
|  | ||||
| # If the GENERATE_QHP tag is set to YES and both QHP_NAMESPACE and | ||||
| # QHP_VIRTUAL_FOLDER are set, an additional index file will be generated that | ||||
| # can be used as input for Qt's qhelpgenerator to generate a Qt Compressed Help | ||||
| @@ -1747,6 +1666,17 @@ HTML_FORMULA_FORMAT    = png | ||||
|  | ||||
| FORMULA_FONTSIZE       = 10 | ||||
|  | ||||
| # Use the FORMULA_TRANSPARENT tag to determine whether or not the images | ||||
| # generated for formulas are transparent PNGs. Transparent PNGs are not | ||||
| # supported properly for IE 6.0, but are supported on all modern browsers. | ||||
| # | ||||
| # Note that when changing this option you need to delete any form_*.png files in | ||||
| # the HTML output directory before the changes have effect. | ||||
| # The default value is: YES. | ||||
| # This tag requires that the tag GENERATE_HTML is set to YES. | ||||
|  | ||||
| #FORMULA_TRANSPARENT    = YES | ||||
|  | ||||
| # The FORMULA_MACROFILE can contain LaTeX \newcommand and \renewcommand commands | ||||
| # to create new LaTeX commands to be used in formulas as building blocks. See | ||||
| # the section "Including formulas" for details. | ||||
| @@ -1808,8 +1738,8 @@ MATHJAX_RELPATH        = https://cdn.jsdelivr.net/npm/mathjax@2 | ||||
|  | ||||
| # The MATHJAX_EXTENSIONS tag can be used to specify one or more MathJax | ||||
| # extension names that should be enabled during MathJax rendering. For example | ||||
| # for MathJax version 2 (see | ||||
| # https://docs.mathjax.org/en/v2.7-latest/tex.html#tex-and-latex-extensions): | ||||
| # for MathJax version 2 (see https://docs.mathjax.org/en/v2.7-latest/tex.html | ||||
| # #tex-and-latex-extensions): | ||||
| # MATHJAX_EXTENSIONS = TeX/AMSmath TeX/AMSsymbols | ||||
| # For example for MathJax version 3 (see | ||||
| # http://docs.mathjax.org/en/latest/input/tex/extensions/index.html): | ||||
| @@ -2060,16 +1990,9 @@ PDF_HYPERLINKS         = YES | ||||
|  | ||||
| USE_PDFLATEX           = YES | ||||
|  | ||||
| # The LATEX_BATCHMODE tag signals the behavior of LaTeX in case of an error. | ||||
| # Possible values are: NO same as ERROR_STOP, YES same as BATCH, BATCH In batch | ||||
| # mode nothing is printed on the terminal, errors are scrolled as if <return> is | ||||
| # hit at every error; missing files that TeX tries to input or request from | ||||
| # keyboard input (\read on a not open input stream) cause the job to abort, | ||||
| # NON_STOP In nonstop mode the diagnostic message will appear on the terminal, | ||||
| # but there is no possibility of user interaction just like in batch mode, | ||||
| # SCROLL In scroll mode, TeX will stop only for missing files to input or if | ||||
| # keyboard input is necessary and ERROR_STOP In errorstop mode, TeX will stop at | ||||
| # each error, asking for user intervention. | ||||
| # If the LATEX_BATCHMODE tag is set to YES, doxygen will add the \batchmode | ||||
| # command to the generated LaTeX files. This will instruct LaTeX to keep running | ||||
| # if errors occur, instead of asking the user for help. | ||||
| # The default value is: NO. | ||||
| # This tag requires that the tag GENERATE_LATEX is set to YES. | ||||
|  | ||||
| @@ -2090,6 +2013,14 @@ LATEX_HIDE_INDICES     = NO | ||||
|  | ||||
| LATEX_BIB_STYLE        = plain | ||||
|  | ||||
| # If the LATEX_TIMESTAMP tag is set to YES then the footer of each generated | ||||
| # page will contain the date and time when the page was generated. Setting this | ||||
| # to NO can help when comparing the output of multiple runs. | ||||
| # The default value is: NO. | ||||
| # This tag requires that the tag GENERATE_LATEX is set to YES. | ||||
|  | ||||
| #LATEX_TIMESTAMP        = NO | ||||
|  | ||||
| # The LATEX_EMOJI_DIRECTORY tag is used to specify the (relative or absolute) | ||||
| # path from which the emoji images will be read. If a relative path is entered, | ||||
| # it will be relative to the LATEX_OUTPUT directory. If left blank the | ||||
| @@ -2255,39 +2186,13 @@ DOCBOOK_OUTPUT         = docbook | ||||
| #--------------------------------------------------------------------------- | ||||
|  | ||||
| # If the GENERATE_AUTOGEN_DEF tag is set to YES, doxygen will generate an | ||||
| # AutoGen Definitions (see https://autogen.sourceforge.net/) file that captures | ||||
| # AutoGen Definitions (see http://autogen.sourceforge.net/) file that captures | ||||
| # the structure of the code including all documentation. Note that this feature | ||||
| # is still experimental and incomplete at the moment. | ||||
| # The default value is: NO. | ||||
|  | ||||
| GENERATE_AUTOGEN_DEF   = NO | ||||
|  | ||||
| #--------------------------------------------------------------------------- | ||||
| # Configuration options related to Sqlite3 output | ||||
| #--------------------------------------------------------------------------- | ||||
|  | ||||
| # If the GENERATE_SQLITE3 tag is set to YES doxygen will generate a Sqlite3 | ||||
| # database with symbols found by doxygen stored in tables. | ||||
| # The default value is: NO. | ||||
|  | ||||
| #GENERATE_SQLITE3       = NO | ||||
|  | ||||
| # The SQLITE3_OUTPUT tag is used to specify where the Sqlite3 database will be | ||||
| # put. If a relative path is entered the value of OUTPUT_DIRECTORY will be put | ||||
| # in front of it. | ||||
| # The default directory is: sqlite3. | ||||
| # This tag requires that the tag GENERATE_SQLITE3 is set to YES. | ||||
|  | ||||
| #SQLITE3_OUTPUT         = sqlite3 | ||||
|  | ||||
| # The SQLITE3_OVERWRITE_DB tag is set to YES, the existing doxygen_sqlite3.db | ||||
| # database file will be recreated with each doxygen run. If set to NO, doxygen | ||||
| # will warn if an a database file is already found and not modify it. | ||||
| # The default value is: YES. | ||||
| # This tag requires that the tag GENERATE_SQLITE3 is set to YES. | ||||
|  | ||||
| #SQLITE3_RECREATE_DB    = YES | ||||
|  | ||||
| #--------------------------------------------------------------------------- | ||||
| # Configuration options related to the Perl module output | ||||
| #--------------------------------------------------------------------------- | ||||
| @@ -2430,15 +2335,15 @@ TAGFILES               = | ||||
|  | ||||
| GENERATE_TAGFILE       = | ||||
|  | ||||
| # If the ALLEXTERNALS tag is set to YES, all external classes and namespaces | ||||
| # will be listed in the class and namespace index. If set to NO, only the | ||||
| # inherited external classes will be listed. | ||||
| # If the ALLEXTERNALS tag is set to YES, all external class will be listed in | ||||
| # the class index. If set to NO, only the inherited external classes will be | ||||
| # listed. | ||||
| # The default value is: NO. | ||||
|  | ||||
| ALLEXTERNALS           = NO | ||||
|  | ||||
| # If the EXTERNAL_GROUPS tag is set to YES, all external groups will be listed | ||||
| # in the topic index. If set to NO, only the current project's groups will be | ||||
| # in the modules index. If set to NO, only the current project's groups will be | ||||
| # listed. | ||||
| # The default value is: YES. | ||||
|  | ||||
| @@ -2452,9 +2357,16 @@ EXTERNAL_GROUPS        = YES | ||||
| EXTERNAL_PAGES         = YES | ||||
|  | ||||
| #--------------------------------------------------------------------------- | ||||
| # Configuration options related to diagram generator tools | ||||
| # Configuration options related to the dot tool | ||||
| #--------------------------------------------------------------------------- | ||||
|  | ||||
| # You can include diagrams made with dia in doxygen documentation. Doxygen will | ||||
| # then run dia to produce the diagram and insert it in the documentation. The | ||||
| # DIA_PATH tag allows you to specify the directory where the dia binary resides. | ||||
| # If left empty dia is assumed to be found in the default search path. | ||||
|  | ||||
| DIA_PATH               = | ||||
|  | ||||
| # If set to YES the inheritance and collaboration graphs will hide inheritance | ||||
| # and usage relations if the target is undocumented or is not a class. | ||||
| # The default value is: YES. | ||||
| @@ -2463,10 +2375,10 @@ HIDE_UNDOC_RELATIONS   = YES | ||||
|  | ||||
| # If you set the HAVE_DOT tag to YES then doxygen will assume the dot tool is | ||||
| # available from the path. This tool is part of Graphviz (see: | ||||
| # https://www.graphviz.org/), a graph visualization toolkit from AT&T and Lucent | ||||
| # http://www.graphviz.org/), a graph visualization toolkit from AT&T and Lucent | ||||
| # Bell Labs. The other options in this section have no effect if this option is | ||||
| # set to NO | ||||
| # The default value is: YES. | ||||
| # The default value is: NO. | ||||
|  | ||||
| HAVE_DOT               = YES | ||||
|  | ||||
| @@ -2480,51 +2392,37 @@ HAVE_DOT               = YES | ||||
|  | ||||
| DOT_NUM_THREADS        = 0 | ||||
|  | ||||
| # DOT_COMMON_ATTR is common attributes for nodes, edges and labels of | ||||
| # subgraphs. When you want a differently looking font in the dot files that | ||||
| # doxygen generates you can specify fontname, fontcolor and fontsize attributes. | ||||
| # For details please see <a href=https://graphviz.org/doc/info/attrs.html>Node, | ||||
| # Edge and Graph Attributes specification</a> You need to make sure dot is able | ||||
| # to find the font, which can be done by putting it in a standard location or by | ||||
| # setting the DOTFONTPATH environment variable or by setting DOT_FONTPATH to the | ||||
| # directory containing the font. Default graphviz fontsize is 14. | ||||
| # The default value is: fontname=Helvetica,fontsize=10. | ||||
| # When you want a differently looking font in the dot files that doxygen | ||||
| # generates you can specify the font name using DOT_FONTNAME. You need to make | ||||
| # sure dot is able to find the font, which can be done by putting it in a | ||||
| # standard location or by setting the DOTFONTPATH environment variable or by | ||||
| # setting DOT_FONTPATH to the directory containing the font. | ||||
| # The default value is: Helvetica. | ||||
| # This tag requires that the tag HAVE_DOT is set to YES. | ||||
|  | ||||
| DOT_COMMON_ATTR        = "fontname=Helvetica,fontsize=10" | ||||
| #DOT_FONTNAME           = Helvetica | ||||
|  | ||||
| # DOT_EDGE_ATTR is concatenated with DOT_COMMON_ATTR. For elegant style you can | ||||
| # add 'arrowhead=open, arrowtail=open, arrowsize=0.5'. <a | ||||
| # href=https://graphviz.org/doc/info/arrows.html>Complete documentation about | ||||
| # arrows shapes.</a> | ||||
| # The default value is: labelfontname=Helvetica,labelfontsize=10. | ||||
| # The DOT_FONTSIZE tag can be used to set the size (in points) of the font of | ||||
| # dot graphs. | ||||
| # Minimum value: 4, maximum value: 24, default value: 10. | ||||
| # This tag requires that the tag HAVE_DOT is set to YES. | ||||
|  | ||||
| DOT_EDGE_ATTR          = "labelfontname=Helvetica,labelfontsize=10" | ||||
| #DOT_FONTSIZE           = 10 | ||||
|  | ||||
| # DOT_NODE_ATTR is concatenated with DOT_COMMON_ATTR. For view without boxes | ||||
| # around nodes set 'shape=plain' or 'shape=plaintext' <a | ||||
| # href=https://www.graphviz.org/doc/info/shapes.html>Shapes specification</a> | ||||
| # The default value is: shape=box,height=0.2,width=0.4. | ||||
| # By default doxygen will tell dot to use the default font as specified with | ||||
| # DOT_FONTNAME. If you specify a different font using DOT_FONTNAME you can set | ||||
| # the path where dot can find it using this tag. | ||||
| # This tag requires that the tag HAVE_DOT is set to YES. | ||||
|  | ||||
| DOT_NODE_ATTR          = "shape=box,height=0.2,width=0.4" | ||||
| #DOT_FONTPATH           = | ||||
|  | ||||
| # You can set the path where dot can find font specified with fontname in | ||||
| # DOT_COMMON_ATTR and others dot attributes. | ||||
| # This tag requires that the tag HAVE_DOT is set to YES. | ||||
|  | ||||
| DOT_FONTPATH           = | ||||
|  | ||||
| # If the CLASS_GRAPH tag is set to YES or GRAPH or BUILTIN then doxygen will | ||||
| # generate a graph for each documented class showing the direct and indirect | ||||
| # inheritance relations. In case the CLASS_GRAPH tag is set to YES or GRAPH and | ||||
| # HAVE_DOT is enabled as well, then dot will be used to draw the graph. In case | ||||
| # the CLASS_GRAPH tag is set to YES and HAVE_DOT is disabled or if the | ||||
| # CLASS_GRAPH tag is set to BUILTIN, then the built-in generator will be used. | ||||
| # If the CLASS_GRAPH tag is set to TEXT the direct and indirect inheritance | ||||
| # relations will be shown as texts / links. | ||||
| # Possible values are: NO, YES, TEXT, GRAPH and BUILTIN. | ||||
| # If the CLASS_GRAPH tag is set to YES (or GRAPH) then doxygen will generate a | ||||
| # graph for each documented class showing the direct and indirect inheritance | ||||
| # relations. In case HAVE_DOT is set as well dot will be used to draw the graph, | ||||
| # otherwise the built-in generator will be used. If the CLASS_GRAPH tag is set | ||||
| # to TEXT the direct and indirect inheritance relations will be shown as texts / | ||||
| # links. | ||||
| # Possible values are: NO, YES, TEXT and GRAPH. | ||||
| # The default value is: YES. | ||||
|  | ||||
| CLASS_GRAPH            = YES | ||||
| @@ -2532,21 +2430,15 @@ CLASS_GRAPH            = YES | ||||
| # If the COLLABORATION_GRAPH tag is set to YES then doxygen will generate a | ||||
| # graph for each documented class showing the direct and indirect implementation | ||||
| # dependencies (inheritance, containment, and class references variables) of the | ||||
| # class with other documented classes. Explicit enabling a collaboration graph, | ||||
| # when COLLABORATION_GRAPH is set to NO, can be accomplished by means of the | ||||
| # command \collaborationgraph. Disabling a collaboration graph can be | ||||
| # accomplished by means of the command \hidecollaborationgraph. | ||||
| # class with other documented classes. | ||||
| # The default value is: YES. | ||||
| # This tag requires that the tag HAVE_DOT is set to YES. | ||||
|  | ||||
| COLLABORATION_GRAPH    = YES | ||||
|  | ||||
| # If the GROUP_GRAPHS tag is set to YES then doxygen will generate a graph for | ||||
| # groups, showing the direct groups dependencies. Explicit enabling a group | ||||
| # dependency graph, when GROUP_GRAPHS is set to NO, can be accomplished by means | ||||
| # of the command \groupgraph. Disabling a directory graph can be accomplished by | ||||
| # means of the command \hidegroupgraph. See also the chapter Grouping in the | ||||
| # manual. | ||||
| # groups, showing the direct groups dependencies. See also the chapter Grouping | ||||
| # in the manual. | ||||
| # The default value is: YES. | ||||
| # This tag requires that the tag HAVE_DOT is set to YES. | ||||
|  | ||||
| @@ -2606,9 +2498,7 @@ TEMPLATE_RELATIONS     = NO | ||||
| # If the INCLUDE_GRAPH, ENABLE_PREPROCESSING and SEARCH_INCLUDES tags are set to | ||||
| # YES then doxygen will generate a graph for each documented file showing the | ||||
| # direct and indirect include dependencies of the file with other documented | ||||
| # files. Explicit enabling an include graph, when INCLUDE_GRAPH is is set to NO, | ||||
| # can be accomplished by means of the command \includegraph. Disabling an | ||||
| # include graph can be accomplished by means of the command \hideincludegraph. | ||||
| # files. | ||||
| # The default value is: YES. | ||||
| # This tag requires that the tag HAVE_DOT is set to YES. | ||||
|  | ||||
| @@ -2617,10 +2507,7 @@ INCLUDE_GRAPH          = YES | ||||
| # If the INCLUDED_BY_GRAPH, ENABLE_PREPROCESSING and SEARCH_INCLUDES tags are | ||||
| # set to YES then doxygen will generate a graph for each documented file showing | ||||
| # the direct and indirect include dependencies of the file with other documented | ||||
| # files. Explicit enabling an included by graph, when INCLUDED_BY_GRAPH is set | ||||
| # to NO, can be accomplished by means of the command \includedbygraph. Disabling | ||||
| # an included by graph can be accomplished by means of the command | ||||
| # \hideincludedbygraph. | ||||
| # files. | ||||
| # The default value is: YES. | ||||
| # This tag requires that the tag HAVE_DOT is set to YES. | ||||
|  | ||||
| @@ -2660,10 +2547,7 @@ GRAPHICAL_HIERARCHY    = YES | ||||
| # If the DIRECTORY_GRAPH tag is set to YES then doxygen will show the | ||||
| # dependencies a directory has on other directories in a graphical way. The | ||||
| # dependency relations are determined by the #include relations between the | ||||
| # files in the directories. Explicit enabling a directory graph, when | ||||
| # DIRECTORY_GRAPH is set to NO, can be accomplished by means of the command | ||||
| # \directorygraph. Disabling a directory graph can be accomplished by means of | ||||
| # the command \hidedirectorygraph. | ||||
| # files in the directories. | ||||
| # The default value is: YES. | ||||
| # This tag requires that the tag HAVE_DOT is set to YES. | ||||
|  | ||||
| @@ -2679,13 +2563,12 @@ DIR_GRAPH_MAX_DEPTH    = 1 | ||||
| # The DOT_IMAGE_FORMAT tag can be used to set the image format of the images | ||||
| # generated by dot. For an explanation of the image formats see the section | ||||
| # output formats in the documentation of the dot tool (Graphviz (see: | ||||
| # https://www.graphviz.org/)). | ||||
| # http://www.graphviz.org/)). | ||||
| # Note: If you choose svg you need to set HTML_FILE_EXTENSION to xhtml in order | ||||
| # to make the SVG files visible in IE 9+ (other browsers do not have this | ||||
| # requirement). | ||||
| # Possible values are: png, jpg, jpg:cairo, jpg:cairo:gd, jpg:gd, jpg:gd:gd, | ||||
| # gif, gif:cairo, gif:cairo:gd, gif:gd, gif:gd:gd, svg, png:gd, png:gd:gd, | ||||
| # png:cairo, png:cairo:gd, png:cairo:cairo, png:cairo:gdiplus, png:gdiplus and | ||||
| # Possible values are: png, jpg, gif, svg, png:gd, png:gd:gd, png:cairo, | ||||
| # png:cairo:gd, png:cairo:cairo, png:cairo:gdiplus, png:gdiplus and | ||||
| # png:gdiplus:gdiplus. | ||||
| # The default value is: png. | ||||
| # This tag requires that the tag HAVE_DOT is set to YES. | ||||
| @@ -2717,12 +2600,11 @@ DOT_PATH               = | ||||
|  | ||||
| DOTFILE_DIRS           = | ||||
|  | ||||
| # You can include diagrams made with dia in doxygen documentation. Doxygen will | ||||
| # then run dia to produce the diagram and insert it in the documentation. The | ||||
| # DIA_PATH tag allows you to specify the directory where the dia binary resides. | ||||
| # If left empty dia is assumed to be found in the default search path. | ||||
| # The MSCFILE_DIRS tag can be used to specify one or more directories that | ||||
| # contain msc files that are included in the documentation (see the \mscfile | ||||
| # command). | ||||
|  | ||||
| DIA_PATH               = | ||||
| MSCFILE_DIRS           = | ||||
|  | ||||
| # The DIAFILE_DIRS tag can be used to specify one or more directories that | ||||
| # contain dia files that are included in the documentation (see the \diafile | ||||
| @@ -2772,6 +2654,18 @@ DOT_GRAPH_MAX_NODES    = 50 | ||||
|  | ||||
| MAX_DOT_GRAPH_DEPTH    = 0 | ||||
|  | ||||
| # Set the DOT_TRANSPARENT tag to YES to generate images with a transparent | ||||
| # background. This is disabled by default, because dot on Windows does not seem | ||||
| # to support this out of the box. | ||||
| # | ||||
| # Warning: Depending on the platform used, enabling this option may lead to | ||||
| # badly anti-aliased labels on the edges of a graph (i.e. they become hard to | ||||
| # read). | ||||
| # The default value is: NO. | ||||
| # This tag requires that the tag HAVE_DOT is set to YES. | ||||
|  | ||||
| #DOT_TRANSPARENT        = NO | ||||
|  | ||||
| # Set the DOT_MULTI_TARGETS tag to YES to allow dot to generate multiple output | ||||
| # files in one run (i.e. multiple -o and -T options on the command line). This | ||||
| # makes dot run faster, but since only newer versions of dot (>1.8.10) support | ||||
| @@ -2799,19 +2693,3 @@ GENERATE_LEGEND        = YES | ||||
| # The default value is: YES. | ||||
|  | ||||
| DOT_CLEANUP            = YES | ||||
|  | ||||
| # You can define message sequence charts within doxygen comments using the \msc | ||||
| # command. If the MSCGEN_TOOL tag is left empty (the default), then doxygen will | ||||
| # use a built-in version of mscgen tool to produce the charts. Alternatively, | ||||
| # the MSCGEN_TOOL tag can also specify the name an external tool. For instance, | ||||
| # specifying prog as the value, doxygen will call the tool as prog -T | ||||
| # <outfile_format> -o <outputfile> <inputfile>. The external tool should support | ||||
| # output file formats "png", "eps", "svg", and "ismap". | ||||
|  | ||||
| MSCGEN_TOOL            = | ||||
|  | ||||
| # The MSCFILE_DIRS tag can be used to specify one or more directories that | ||||
| # contain msc files that are included in the documentation (see the \mscfile | ||||
| # command). | ||||
|  | ||||
| MSCFILE_DIRS           = | ||||
|   | ||||
							
								
								
									
										212
									
								
								GNUmakefile
									
									
									
									
									
								
							
							
						
						| @@ -16,14 +16,14 @@ MAKEFLAGS += --no-builtin-rules | ||||
| ## LD := Linker. | ||||
| ## ANDROID_SDK := Path to the Android SDK. | ||||
|  | ||||
| VERSION_CODE := 35 | ||||
| VERSION_CODE_IOS := 12 | ||||
| VERSION_NUMBER := 0.0.30-wip | ||||
| VERSION_CODE := 44 | ||||
| VERSION_CODE_IOS := 18 | ||||
| VERSION_NUMBER := 0.2025.10 | ||||
| VERSION_NAME := This program kills fascists. | ||||
|  | ||||
| IPHONEOS_VERSION_MIN=14.0 | ||||
|  | ||||
| SQLITE_URL := https://www.sqlite.org/2025/sqlite-amalgamation-3490100.zip | ||||
| SQLITE_URL := https://www.sqlite.org/2025/sqlite-amalgamation-3500400.zip | ||||
| BUNDLETOOL_URL := https://github.com/google/bundletool/releases/download/1.17.0/bundletool-all-1.17.0.jar | ||||
| APPIMAGETOOL_URL := https://github.com/AppImage/AppImageKit/releases/download/continuous/appimagetool-x86_64.AppImage | ||||
| APPIMAGETOOL_MD5 := e989fadfc4d685fd3d6aeeb9b525d74d  out/appimagetool | ||||
| @@ -38,7 +38,6 @@ BUNDLETOOL = out/bundletool.jar | ||||
|  | ||||
| HAVE_WIN := | ||||
| HAVE_CROSS_AARCH64 := | ||||
| USE_SYSTEM_SSL := | ||||
|  | ||||
| export SOURCE_DATE_EPOCH=1 | ||||
| export TZ=UTC | ||||
| @@ -65,7 +64,6 @@ LDFLAGS += \ | ||||
| 	-lbsd \ | ||||
| 	-lnetwork \ | ||||
| 	-Wno-stringop-overflow | ||||
| USE_SYSTEM_SSL := 1 | ||||
| HAVE_ANDROID = 0 | ||||
| HAVE_LINUX_IOS = 0 | ||||
| HAVE_LINUX_MACOS = 0 | ||||
| @@ -80,13 +78,12 @@ LDFLAGS += \ | ||||
| HAVE_ANDROID := | ||||
| HAVE_LINUX_IOS := | ||||
| HAVE_LINUX_MACOS := | ||||
| USE_SYSTEM_SSL := 1 | ||||
| else | ||||
| $(error Unexpected host platform $(UNAME_S).) | ||||
| endif | ||||
|  | ||||
| # Everything is set above. | ||||
| $(info Building Tilde Friends $(VERSION_NUMBER) android=$(if $(HAVE_ANDROID),1,0) win=$(if $(HAVE_WIN),1,0) cross_aarch64=$(if $(HAVE_CROSS_AARCH64),1,0) cross_ios=$(if $(HAVE_LINUX_IOS),1,0) cross_macos=$(if $(HAVE_LINUX_MACOS),1,0) system_ssl=$(if $(USE_SYSTEM_SSL),1,0)) | ||||
| $(info Building Tilde Friends $(VERSION_NUMBER) android=$(if $(HAVE_ANDROID),1,0) win=$(if $(HAVE_WIN),1,0) cross_aarch64=$(if $(HAVE_CROSS_AARCH64),1,0) cross_ios=$(if $(HAVE_LINUX_IOS),1,0) cross_macos=$(if $(HAVE_LINUX_MACOS),1,0)) | ||||
|  | ||||
| CFLAGS += \ | ||||
| 	-std=gnu11 \ | ||||
| @@ -106,10 +103,10 @@ LDFLAGS += \ | ||||
| 	-Wno-aggressive-loop-optimizations | ||||
|  | ||||
| ANDROID_MIN_SDK_VERSION := 24 | ||||
| ANDROID_TARGET_SDK_VERSION := 34 | ||||
| ANDROID_BUILD_TOOLS := $(ANDROID_SDK)/build-tools/34.0.0 | ||||
| ANDROID_TARGET_SDK_VERSION := 35 | ||||
| ANDROID_BUILD_TOOLS := $(ANDROID_SDK)/build-tools/35.0.0 | ||||
| ANDROID_PLATFORM := $(ANDROID_SDK)/platforms/android-$(ANDROID_TARGET_SDK_VERSION) | ||||
| ANDROID_NDK ?= $(ANDROID_SDK)/ndk/26.3.11579264 | ||||
| ANDROID_NDK ?= $(ANDROID_SDK)/ndk/27.2.12479018 | ||||
|  | ||||
| ANDROID_ARMV7A_TARGETS := \ | ||||
| 	out/androiddebug-armv7a/tildefriends \ | ||||
| @@ -253,7 +250,10 @@ $(ANDROID_TARGETS): CFLAGS += \ | ||||
| 	-fno-asynchronous-unwind-tables \ | ||||
| 	-funwind-tables \ | ||||
| 	-Wno-unknown-warning-option | ||||
| $(ANDROID_TARGETS): LDFLAGS += --sysroot $(ANDROID_NDK)/toolchains/llvm/prebuilt/linux-x86_64/sysroot -fPIC | ||||
| $(ANDROID_TARGETS): LDFLAGS += \ | ||||
| 	--sysroot $(ANDROID_NDK)/toolchains/llvm/prebuilt/linux-x86_64/sysroot \ | ||||
| 	-Wl,-z,max-page-size=16384 \ | ||||
| 	-fPIC | ||||
| $(DEBUG_TARGETS): CFLAGS += -DDEBUG -Og | ||||
| $(DEBUG_TARGETS): LDFLAGS += -Og | ||||
| $(RELEASE_TARGETS): CFLAGS += \ | ||||
| @@ -267,16 +267,12 @@ $(WINDOWS_TARGETS): AS = $(CC) | ||||
| $(WINDOWS_TARGETS): CFLAGS += \ | ||||
| 	-D_WIN32_WINNT=0x0A00 \ | ||||
| 	-DWINVER=0x0A00 \ | ||||
| 	-DNTDDI_VERSION=NTDDI_WIN10 \ | ||||
| 	-Iout/openssl/$(UNAME_S)/mingw64/usr/local/include | ||||
| 	-DNTDDI_VERSION=NTDDI_WIN10 | ||||
| $(WINDOWS_TARGETS): LDFLAGS += \ | ||||
| 	-static \ | ||||
| 	-lm \ | ||||
| 	-Lout/openssl/$(UNAME_S)/mingw64/usr/local/lib | ||||
| 	-lm | ||||
| $(AARCH64_TARGETS): CC = aarch64-linux-gnu-gcc | ||||
| $(AARCH64_TARGETS): AS = $(CC) | ||||
| $(AARCH64_TARGETS): CFLAGS += -Iout/openssl/Linux/aarch64/usr/local/include | ||||
| $(AARCH64_TARGETS): LDFLAGS += -Lout/openssl/Linux/aarch64/usr/local/lib | ||||
| ifeq ($(UNAME_S),Darwin) | ||||
| $(HOST_TARGETS): CC = xcrun clang | ||||
| $(IOS_TARGETS): IOS_SYSROOT := $(shell xcrun --sdk iphoneos --show-sdk-path) | ||||
| @@ -301,39 +297,12 @@ $(ANDROID_TARGETS): AS = $(CC) | ||||
| $(ANDROID_TARGETS): CFLAGS += \ | ||||
| 	-target $(ANDROID_NDK_TARGET_TRIPLE)$(ANDROID_MIN_SDK_VERSION) \ | ||||
| 	-Wno-unknown-warning-option | ||||
| $(ANDROID_ARMV7A_TARGETS): CFLAGS += -Iout/openssl/android/armeabi-v7a/usr/local/include | ||||
| $(ANDROID_ARMV7A_TARGETS): LDFLAGS += -Lout/openssl/android/armeabi-v7a/usr/local/lib | ||||
| $(ANDROID_ARM64_TARGETS): CFLAGS += -Iout/openssl/android/arm64-v8a/usr/local/include | ||||
| $(ANDROID_ARM64_TARGETS): LDFLAGS += -Lout/openssl/android/arm64-v8a/usr/local/lib | ||||
| $(ANDROID_X86_TARGETS): CFLAGS += -Iout/openssl/android/x86/usr/local/include | ||||
| $(ANDROID_X86_TARGETS): CFLAGS += -Wno-atomic-alignment | ||||
| $(ANDROID_X86_TARGETS): LDFLAGS += -Lout/openssl/android/x86/usr/local/lib | ||||
| $(ANDROID_X86_64_TARGETS): CFLAGS += -Iout/openssl/android/x86_64/usr/local/include | ||||
| $(ANDROID_X86_64_TARGETS): LDFLAGS += -Lout/openssl/android/x86_64/usr/local/lib | ||||
| $(NONMACOS_TARGETS): CFLAGS += -Wno-cast-function-type | ||||
| $(MACOS_TARGETS): LDFLAGS += -Wl,-dead_strip | ||||
| $(NONMACOS_TARGETS): LDFLAGS += -Wl,--gc-sections -Wl,--as-needed | ||||
| $(IOS_TARGETS): CFLAGS += -miphoneos-version-min=$(IPHONEOS_VERSION_MIN) | ||||
| $(IOS_TARGETS): LDFLAGS += -miphoneos-version-min=$(IPHONEOS_VERSION_MIN) | ||||
| ifeq ($(UNAME_S),Darwin) | ||||
| $(IOS_TARGETS): CFLAGS += -Iout/openssl/ios/ios64-xcrun/usr/local/include | ||||
| $(IOS_TARGETS): LDFLAGS += -Lout/openssl/ios/ios64-xcrun/usr/local/lib | ||||
| else | ||||
| $(IOS_TARGETS): CFLAGS += -Iout/openssl/$(UNAME_S)/ios64-cross/usr/local/include | ||||
| $(IOS_TARGETS): LDFLAGS += -Lout/openssl/$(UNAME_S)/ios64-cross/usr/local/lib | ||||
| $(filter $(BUILD_DIR)/macosdebug-x86_64/%,$(ALL_TARGETS)): CFLAGS += -Iout/openssl/$(UNAME_S)/macos-x86_64/usr/local/include | ||||
| $(filter $(BUILD_DIR)/macosdebug-arm/%,$(ALL_TARGETS)): CFLAGS += -Iout/openssl/$(UNAME_S)/macos-arm/usr/local/include | ||||
| $(filter $(BUILD_DIR)/macosdebug-x86_64/%,$(ALL_TARGETS)): LDFLAGS += -Lout/openssl/$(UNAME_S)/macos-x86_64/usr/local/lib | ||||
| $(filter $(BUILD_DIR)/macosdebug-arm/%,$(ALL_TARGETS)): LDFLAGS += -Lout/openssl/$(UNAME_S)/macos-arm/usr/local/lib | ||||
| $(filter $(BUILD_DIR)/macosrelease-x86_64/%,$(ALL_TARGETS)): CFLAGS += -Iout/openssl/$(UNAME_S)/macos-x86_64/usr/local/include | ||||
| $(filter $(BUILD_DIR)/macosrelease-arm/%,$(ALL_TARGETS)): CFLAGS += -Iout/openssl/$(UNAME_S)/macos-arm/usr/local/include | ||||
| $(filter $(BUILD_DIR)/macosrelease-x86_64/%,$(ALL_TARGETS)): LDFLAGS += -Lout/openssl/$(UNAME_S)/macos-x86_64/usr/local/lib | ||||
| $(filter $(BUILD_DIR)/macosrelease-arm/%,$(ALL_TARGETS)): LDFLAGS += -Lout/openssl/$(UNAME_S)/macos-arm/usr/local/lib | ||||
| endif | ||||
| $(IOSSIM_TARGETS): CFLAGS += -Iout/openssl/ios/iossimulator-xcrun/usr/local/include | ||||
| $(IOSSIM_TARGETS): LDFLAGS += -Lout/openssl/ios/iossimulator-xcrun/usr/local/lib | ||||
| $(HOST_TARGETS): CFLAGS += -Iout/openssl/$(UNAME_S)/$(UNAME_M)/usr/local/include | ||||
| $(HOST_TARGETS): LDFLAGS += -Lout/openssl/$(UNAME_S)/$(UNAME_M)/usr/local/lib | ||||
|  | ||||
| ifeq ($(UNAME_M),x86_64) | ||||
| ifeq ($(UNAME_S),Linux) | ||||
| @@ -614,15 +583,16 @@ $(UV_OBJS): CFLAGS += \ | ||||
| 	-Ideps/libuv/include \ | ||||
| 	-Ideps/libuv/src \ | ||||
| 	-Wno-dangling-pointer \ | ||||
| 	-Wno-format-truncation \ | ||||
| 	-Wno-incompatible-pointer-types \ | ||||
| 	-Wno-maybe-uninitialized \ | ||||
| 	-Wno-nonnull \ | ||||
| 	-Wno-sign-compare \ | ||||
| 	-Wno-unknown-attributes \ | ||||
| 	-Wno-unused-but-set-parameter \ | ||||
| 	-Wno-unused-but-set-variable \ | ||||
| 	-Wno-unused-result \ | ||||
| 	-Wno-unused-variable \ | ||||
| 	-Wno-nonnull | ||||
| 	-Wno-unused-variable | ||||
| $(filter out/win%,$(UV_OBJS)): \ | ||||
| 	CFLAGS += \ | ||||
| 		-Wno-cast-function-type \ | ||||
| @@ -649,6 +619,7 @@ SODIUM_SOURCES := \ | ||||
| 	deps/libsodium/src/libsodium/crypto_core/hsalsa20/ref2/core_hsalsa20_ref2.c \ | ||||
| 	deps/libsodium/src/libsodium/crypto_core/salsa/ref/core_salsa_ref.c \ | ||||
| 	deps/libsodium/src/libsodium/crypto_core/softaes/softaes.c \ | ||||
| 	deps/libsodium/src/libsodium/crypto_generichash/crypto_generichash.c \ | ||||
| 	deps/libsodium/src/libsodium/crypto_generichash/blake2b/ref/blake2b-compress-ref.c \ | ||||
| 	deps/libsodium/src/libsodium/crypto_generichash/blake2b/ref/blake2b-ref.c \ | ||||
| 	deps/libsodium/src/libsodium/crypto_generichash/blake2b/ref/generichash_blake2b.c \ | ||||
| @@ -718,12 +689,12 @@ $(SQLITE_OBJS): CFLAGS += \ | ||||
| 	-DSQLITE_MAX_COMPOUND_SELECT=300 \ | ||||
| 	-DSQLITE_MAX_EXPR_DEPTH=40 \ | ||||
| 	-DSQLITE_MAX_FUNCTION_ARG=8 \ | ||||
| 	-DSQLITE_MAX_LENGTH=5242880 \ | ||||
| 	-DSQLITE_MAX_LENGTH=10485760 \ | ||||
| 	-DSQLITE_MAX_LIKE_PATTERN_LENGTH=50 \ | ||||
| 	-DSQLITE_MAX_SQL_LENGTH=100000 \ | ||||
| 	-DSQLITE_MAX_TRIGGER_DEPTH=10 \ | ||||
| 	-DSQLITE_MAX_VARIABLE_NUMBER=100 \ | ||||
| 	-DSQLITE_MAX_VDBE_OP=25000 \ | ||||
| 	-DSQLITE_MAX_VDBE_OP=50000 \ | ||||
| 	-DSQLITE_OMIT_DEPRECATED \ | ||||
| 	-DSQLITE_OMIT_DESERIALIZE \ | ||||
| 	-DSQLITE_OMIT_LOAD_EXTENSION \ | ||||
| @@ -742,7 +713,7 @@ $(SQLITE_OBJS): CFLAGS += \ | ||||
|  | ||||
| QUICKJS_SOURCES := \ | ||||
| 	deps/quickjs/cutils.c \ | ||||
| 	deps/quickjs/libbf.c \ | ||||
| 	deps/quickjs/dtoa.c \ | ||||
| 	deps/quickjs/libregexp.c \ | ||||
| 	deps/quickjs/libunicode.c \ | ||||
| 	deps/quickjs/quickjs.c | ||||
| @@ -819,9 +790,6 @@ $(MINIUNZIP_OBJS): CFLAGS += \ | ||||
| LDFLAGS += \ | ||||
| 	-pthread \ | ||||
| 	-lm | ||||
| $(HOST_TARGETS) $(IOS_TARGETS) $(IOSSIM_TARGETS) $(AARCH64_TARGETS) $(filter-out $(HOST_TARGETS),$(MACOS_TARGETS)): LDFLAGS += \ | ||||
| 	-lssl \ | ||||
| 	-lcrypto | ||||
| ifneq ($(UNAME_S),Haiku) | ||||
| ifneq ($(UNAME_S),OpenBSD) | ||||
| $(HOST_TARGETS) $(IOS_TARGETS) $(IOSSIM_TARGETS): LDFLAGS += \ | ||||
| @@ -829,8 +797,6 @@ $(HOST_TARGETS) $(IOS_TARGETS) $(IOSSIM_TARGETS): LDFLAGS += \ | ||||
| endif | ||||
| endif | ||||
| $(WINDOWS_TARGETS): LDFLAGS += \ | ||||
| 	-lssl \ | ||||
| 	-lcrypto \ | ||||
| 	-lcrypt32 \ | ||||
| 	-ldbghelp \ | ||||
| 	-liphlpapi \ | ||||
| @@ -843,9 +809,7 @@ $(WINDOWS_TARGETS): LDFLAGS += \ | ||||
| $(ANDROID_TARGETS): LDFLAGS += \ | ||||
| 	-target $(ANDROID_NDK_TARGET_TRIPLE)$(ANDROID_MIN_SDK_VERSION) \ | ||||
| 	-ldl \ | ||||
| 	-llog \ | ||||
| 	-lssl \ | ||||
| 	-lcrypto | ||||
| 	-llog | ||||
| $(MACOS_TARGETS) $(IOS_TARGETS) $(IOSSIM_TARGETS): CFLAGS += \ | ||||
| 	-Wno-unknown-warning-option | ||||
| $(IOS_TARGETS) $(IOSSIM_TARGETS): LDFLAGS += \ | ||||
| @@ -1015,7 +979,7 @@ $(BUNDLETOOL): | ||||
| 	@curl -q -L --create-dirs -o $@ $(BUNDLETOOL_URL) | ||||
|  | ||||
| out/TildeFriends.aab: out/apk/classes.dex $(filter-out %debug%, $(ANDROID_TARGETS)) $(RAW_FILES) out/apk/res.apk src/android/AndroidManifest.xml $(BUNDLETOOL) | ||||
| 	@rm -rf out/aab/staging/ | ||||
| 	@rm -rf out/aab/staging/ out/aab/base.zip | ||||
| 	@mkdir -p out/aab/staging | ||||
| 	@$(ANDROID_BUILD_TOOLS)/aapt2 link --proto-format -o out/aab/temporary.apk \ | ||||
| 		-I $(ANDROID_PLATFORM)/android.jar \ | ||||
| @@ -1035,14 +999,11 @@ out/TildeFriends.aab: out/apk/classes.dex $(filter-out %debug%, $(ANDROID_TARGET | ||||
| 	@cp out/apk/classes.dex out/aab/staging/dex/ | ||||
| 	@rm -fv out/base.zip | ||||
| 	@mkdir -p out/aab/staging/lib/arm64-v8a out/aab/staging/lib/armeabi-v7a out/aab/staging/lib/x86_64 out/aab/staging/lib/x86 | ||||
| 	@cp out/androidrelease/tildefriends out/aab/staging/lib/arm64-v8a/libtildefriends.so | ||||
| 	@cp out/androidrelease-armv7a/tildefriends out/aab/staging/lib/armeabi-v7a/libtildefriends.so | ||||
| 	@cp out/androidrelease-x86_64/tildefriends out/aab/staging/lib/x86_64/libtildefriends.so | ||||
| 	@cp out/androidrelease-x86/tildefriends out/aab/staging/lib/x86/libtildefriends.so | ||||
| 	@$(ANDROID_NDK)/toolchains/llvm/prebuilt/linux-x86_64/bin/llvm-strip out/aab/staging/lib/arm64-v8a/libtildefriends.so | ||||
| 	@$(ANDROID_NDK)/toolchains/llvm/prebuilt/linux-x86_64/bin/llvm-strip out/aab/staging/lib/armeabi-v7a/libtildefriends.so | ||||
| 	@$(ANDROID_NDK)/toolchains/llvm/prebuilt/linux-x86_64/bin/llvm-strip out/aab/staging/lib/x86_64/libtildefriends.so | ||||
| 	@$(ANDROID_NDK)/toolchains/llvm/prebuilt/linux-x86_64/bin/llvm-strip out/aab/staging/lib/x86/libtildefriends.so | ||||
| 	@mkdir -p out/aab/staging/BUNDLE-METADATA/com.android.tools.build.debugsymbols/arm64-v8a out/aab/staging/BUNDLE-METADATA/com.android.tools.build.debugsymbols/armeabi-v7a out/aab/staging/BUNDLE-METADATA/com.android.tools.build.debugsymbols/x86_64 out/aab/staging/BUNDLE-METADATA/com.android.tools.build.debugsymbols/x86 | ||||
| 	@$(ANDROID_NDK)/toolchains/llvm/prebuilt/linux-x86_64/bin/llvm-strip out/androidrelease/tildefriends -o out/aab/staging/lib/arm64-v8a/libtildefriends.so | ||||
| 	@$(ANDROID_NDK)/toolchains/llvm/prebuilt/linux-x86_64/bin/llvm-strip out/androidrelease-armv7a/tildefriends -o out/aab/staging/lib/armeabi-v7a/libtildefriends.so | ||||
| 	@$(ANDROID_NDK)/toolchains/llvm/prebuilt/linux-x86_64/bin/llvm-strip out/androidrelease-x86_64/tildefriends -o out/aab/staging/lib/x86_64/libtildefriends.so | ||||
| 	@$(ANDROID_NDK)/toolchains/llvm/prebuilt/linux-x86_64/bin/llvm-strip out/androidrelease-x86/tildefriends -o out/aab/staging/lib/x86/libtildefriends.so | ||||
| 	@cp -r apps/ out/aab/staging/root/ | ||||
| 	@rm -rf out/aab/staging/root/apps/welcome* | ||||
| 	@cp -r core/ out/aab/staging/root/ | ||||
| @@ -1051,7 +1012,12 @@ out/TildeFriends.aab: out/apk/classes.dex $(filter-out %debug%, $(ANDROID_TARGET | ||||
| 	@cp -r deps/codemirror/ out/aab/staging/root/deps/ | ||||
| 	@cd out/aab/staging/; zip -r ../base.zip *; cd ../../../ | ||||
| 	@java -jar $(BUNDLETOOL) build-bundle --overwrite --config=src/android/BundleConfig.json --modules=out/aab/base.zip --output=$@ | ||||
| 	@$(ANDROID_BUILD_TOOLS)/apksigner sign -ks .keys/android.jks --ks-key-alias androidKey -ks-pass pass:android --min-sdk-version=$(ANDROID_MIN_SDK_VERSION) $@ | ||||
| 	@$(ANDROID_NDK)/toolchains/llvm/prebuilt/linux-x86_64/bin/llvm-strip --only-keep-debug out/androidrelease/tildefriends -o out/aab/staging/BUNDLE-METADATA/com.android.tools.build.debugsymbols/arm64-v8a/libtildefriends.so.sym | ||||
| 	@$(ANDROID_NDK)/toolchains/llvm/prebuilt/linux-x86_64/bin/llvm-strip --only-keep-debug out/androidrelease-armv7a/tildefriends -o out/aab/staging/BUNDLE-METADATA/com.android.tools.build.debugsymbols/armeabi-v7a/libtildefriends.so.sym | ||||
| 	@$(ANDROID_NDK)/toolchains/llvm/prebuilt/linux-x86_64/bin/llvm-strip --only-keep-debug out/androidrelease-x86_64/tildefriends -o out/aab/staging/BUNDLE-METADATA/com.android.tools.build.debugsymbols/x86_64/libtildefriends.so.sym | ||||
| 	@$(ANDROID_NDK)/toolchains/llvm/prebuilt/linux-x86_64/bin/llvm-strip --only-keep-debug out/androidrelease-x86/tildefriends -o out/aab/staging/BUNDLE-METADATA/com.android.tools.build.debugsymbols/x86/libtildefriends.so.sym | ||||
| 	@cd out/aab/staging; zip -u ../../../$@ BUNDLE-METADATA/com.android.tools.build.debugsymbols/arm64-v8a/libtildefriends.so.sym BUNDLE-METADATA/com.android.tools.build.debugsymbols/armeabi-v7a/libtildefriends.so.sym BUNDLE-METADATA/com.android.tools.build.debugsymbols/x86_64/libtildefriends.so.sym BUNDLE-METADATA/com.android.tools.build.debugsymbols/x86/libtildefriends.so.sym; cd ../../../ | ||||
| 	@$(ANDROID_BUILD_TOOLS)/apksigner sign -ks .keys/android.jks --ks-key-alias androidKey -ks-pass pass:android --min-sdk-version=$(ANDROID_MIN_SDK_VERSION) --alignment-preserved $@ | ||||
|  | ||||
| aab: out/TildeFriends.aab ## Build an Android App Bundle. | ||||
| .PHONY: aab | ||||
| @@ -1113,12 +1079,12 @@ out/apk/TildeFriends-%.fdroid.unsigned.apk: | ||||
|  | ||||
| out/%.apk: out/apk/%.unsigned.apk | ||||
| 	@echo "[apksigner] $(notdir $@)" | ||||
| 	@$(ANDROID_BUILD_TOOLS)/apksigner sign --ks .keys/android.jks --ks-key-alias androidKey --ks-pass pass:android --key-pass pass:android --min-sdk-version $(ANDROID_MIN_SDK_VERSION) --out $@ $< | ||||
| 	@$(ANDROID_BUILD_TOOLS)/apksigner sign --ks .keys/android.jks --ks-key-alias androidKey --ks-pass pass:android --key-pass pass:android --min-sdk-version $(ANDROID_MIN_SDK_VERSION) --out $@ --alignment-preserved $< | ||||
|  | ||||
| out/%.zopfli.apk: out/%.apk | ||||
| 	@echo "[zopfli] $(notdir $@)" | ||||
| 	$(ANDROID_BUILD_TOOLS)/zipalign -f -z 4 $< $@.zopfli | ||||
| 	@$(ANDROID_BUILD_TOOLS)/apksigner sign --ks .keys/android.jks --ks-key-alias androidKey --ks-pass pass:android --key-pass pass:android --min-sdk-version $(ANDROID_MIN_SDK_VERSION) --out $@ $@.zopfli | ||||
| 	@$(ANDROID_BUILD_TOOLS)/apksigner sign --ks .keys/android.jks --ks-key-alias androidKey --ks-pass pass:android --key-pass pass:android --min-sdk-version $(ANDROID_MIN_SDK_VERSION) --out $@ --alignment-preserved $@.zopfli | ||||
|  | ||||
| release-apk: out/TildeFriends-arm-release.zopfli.apk out/TildeFriends-x86-release.zopfli.apk ## Build an Android release APK. | ||||
| .PHONY: release-apk | ||||
| @@ -1136,6 +1102,11 @@ releaseapkgo: out/TildeFriends-arm-release.apk ## Build, install, and run a rele | ||||
| 	@adb shell am start com.unprompted.tildefriends/.TildeFriendsActivity | ||||
| .PHONY: releaseapkgo | ||||
|  | ||||
| x86releaseapkgo: out/TildeFriends-x86-release.apk ## Build, install, and run an x86 release Android APK. | ||||
| 	@adb install -r $< | ||||
| 	@adb shell am start com.unprompted.tildefriends/.TildeFriendsActivity | ||||
| .PHONY: x86releaseapkgo | ||||
|  | ||||
| apklog: ## Display Android log output. | ||||
| 	@adb logcat *:S tildefriends | ||||
| .PHONY: apklog | ||||
| @@ -1181,6 +1152,7 @@ out/tildefriends-%.ipa: out/tildefriends-ios%.app/tildefriends | ||||
| 	@echo "[ipa] $@" | ||||
| 	@rm -rf $@.tmp $@ | ||||
| 	@mkdir -p $@.tmp/Payload/tildefriends.app/ | ||||
| 	@cp src/ios/tildefriends512.png $@.tmp/iTunesArtwork | ||||
| 	@cp -R $(dir $<)/* $@.tmp/Payload/tildefriends.app/ | ||||
| 	@cd $@.tmp/ && zip -u ../../$@ -q -9 -r ./ | ||||
| 	@rm -rf $@.tmp/ | ||||
| @@ -1212,94 +1184,6 @@ iossimdebuggo: out/tildefriends-iossimdebug.app/tildefriends ## Build, install, | ||||
| 	xcrun simctl launch booted com.unprompted.tildefriends | ||||
| .PHONY: iossimdebuggo | ||||
|  | ||||
| ANDROID_DEPS := out/openssl/android/arm64-v8a/usr/local/lib/libssl.a | ||||
| $(ANDROID_DEPS): | ||||
| 	+@export ANDROID_NDK_ROOT=$(ANDROID_NDK) | ||||
| 	+@export BUILD_PLATFORM=android | ||||
| 	+@export TOOLCHAIN=$(ANDROID_NDK)/toolchains/llvm/prebuilt/linux-x86_64 | ||||
| 	+@PATH="$$TOOLCHAIN/x86_64-linux-android/bin:$$TOOLCHAIN/bin:$$PATH" BUILD_TARGET=x86_64 SSL_TARGET=android-x86_64 OPTIONS="-D__ANDROID_API__=$(ANDROID_MIN_SDK_VERSION) -Wno-macro-redefined" tools/ssl-local | ||||
| 	+@PATH="$$TOOLCHAIN/i686-linux-android/bin:$$TOOLCHAIN/bin:$$PATH" BUILD_TARGET=x86 SSL_TARGET=android-x86 OPTIONS="-D__ANDROID_API__=$(ANDROID_MIN_SDK_VERSION) -Wno-macro-redefined" tools/ssl-local | ||||
| 	+@PATH="$$TOOLCHAIN/arm-linux-androideabi/bin:$$TOOLCHAIN/bin:$$PATH" BUILD_TARGET=armeabi-v7a SSL_TARGET=android-arm OPTIONS="--target=armv7a-linux-androideabi -Wl,--fix-cortex-a8 -D__ANDROID_API__=$(ANDROID_MIN_SDK_VERSION) -Wno-macro-redefined" tools/ssl-local | ||||
| 	+@PATH="$$TOOLCHAIN/aarch64-linux-android/bin:$$TOOLCHAIN/bin:$$PATH" BUILD_TARGET=arm64-v8a SSL_TARGET=android-arm64 OPTIONS="-D__ANDROID_API__=$(ANDROID_MIN_SDK_VERSION) -Wno-macro-redefined" tools/ssl-local | ||||
| $(filter $(BUILD_DIR)/android%,$(APP_OBJS)): | $(ANDROID_DEPS) | ||||
|  | ||||
| ifeq ($(UNAME_S),Linux) | ||||
| ifneq ($(USE_SYSTEM_SSL),1) | ||||
| LOCAL_DEPS := out/openssl/$(UNAME_S)/$(UNAME_M)/usr/local/lib/libssl.a | ||||
| $(LOCAL_DEPS): | ||||
| 	+@tools/ssl-local | ||||
| $(filter $(BUILD_DIR)/debug/%,$(APP_OBJS)) $(filter $(BUILD_DIR)/release/%,$(APP_OBJS)): | $(LOCAL_DEPS) | ||||
| endif | ||||
|  | ||||
| ifeq ($(HAVE_CROSS_AARCH64),1) | ||||
| LOCAL_DEPS := out/openssl/$(UNAME_S)/aarch64/usr/local/lib/libssl.a | ||||
| $(LOCAL_DEPS): | ||||
| 	+@OPTIONS="--cross-compile-prefix=aarch64-linux-gnu-" BUILD_TARGET=aarch64 SSL_TARGET=linux-aarch64 tools/ssl-local | ||||
| $(filter $(BUILD_DIR)/armdebug/%,$(APP_OBJS)) $(filter $(BUILD_DIR)/armrelease/%,$(APP_OBJS)): | $(LOCAL_DEPS) | ||||
| endif | ||||
|  | ||||
| ifeq ($(HAVE_LINUX_IOS),1) | ||||
| LOCAL_DEPS := out/openssl/$(UNAME_S)/ios64-cross/usr/local/lib/libssl.a | ||||
| $(LOCAL_DEPS): | ||||
| 	+@PATH=deps/ios_toolchain/target/bin:$$PATH \ | ||||
| 		BUILD_TARGET=ios64-cross \ | ||||
| 		SSL_TARGET=ios64-cross \ | ||||
| 		CROSS_COMPILE=../../deps/ios_toolchain/target/bin/arm-apple-darwin11- \ | ||||
| 		CROSS_TOP=../../deps/ios_toolchain/target \ | ||||
| 		CROSS_SDK=iPhoneOS18.2.sdk \ | ||||
| 		CC=clang \ | ||||
| 		OPTIONS=-miphoneos-version-min=$(IPHONEOS_VERSION_MIN) \ | ||||
| 		tools/ssl-local | ||||
| $(filter $(BUILD_DIR)/ios%,$(APP_OBJS)): | $(LOCAL_DEPS) | ||||
| endif | ||||
|  | ||||
| ifeq ($(HAVE_LINUX_MACOS),1) | ||||
| LOCAL_DEPS := out/openssl/$(UNAME_S)/macos-arm/usr/local/lib/libssl.a | ||||
| $(LOCAL_DEPS): | ||||
| 	+@PATH=../../deps/macos_toolchain/bin:$$PATH \ | ||||
| 		BUILD_TARGET=macos-arm \ | ||||
| 		SSL_TARGET=darwin64-arm64 \ | ||||
| 		CC=../../deps/macos_toolchain/bin/oa64-clang \ | ||||
| 		RANLIB=../../deps/macos_toolchain/bin/x86_64-apple-darwin24-ranlib \ | ||||
| 		AR=../../deps/macos_toolchain/bin/arm64-apple-darwin24-ar \ | ||||
| 		tools/ssl-local | ||||
| $(filter $(BUILD_DIR)/macosrelease-arm/% $(BUILD_DIR)/macosdebug-arm/%,$(APP_OBJS)): | $(LOCAL_DEPS) | ||||
|  | ||||
| LOCAL_DEPS := out/openssl/$(UNAME_S)/macos-x86_64/usr/local/lib/libssl.a | ||||
| $(LOCAL_DEPS): | ||||
| 	+@PATH=../../deps/macos_toolchain/bin:$$PATH \ | ||||
| 		BUILD_TARGET=macos-x86_64 \ | ||||
| 		SSL_TARGET=darwin64-x86_64 \ | ||||
| 		CC=../../deps/macos_toolchain/bin/o64-clang \ | ||||
| 		RANLIB=../../deps/macos_toolchain/bin/x86_64-apple-darwin24-ranlib \ | ||||
| 		AR=../../deps/macos_toolchain/bin/x86_64-apple-darwin24-ar \ | ||||
| 		tools/ssl-local | ||||
| $(filter $(BUILD_DIR)/macosrelease-x86_64/% $(BUILD_DIR)/macosdebug-x86_64/%,$(APP_OBJS)): | $(LOCAL_DEPS) | ||||
| endif | ||||
| endif | ||||
|  | ||||
| ifeq ($(UNAME_S),Darwin) | ||||
| LOCAL_DEPS := out/openssl/$(UNAME_S)/$(UNAME_M)/usr/local/lib/libssl.a | ||||
| $(LOCAL_DEPS): | ||||
| 	+@tools/ssl-local | ||||
| $(filter $(BUILD_DIR)/debug/%,$(APP_OBJS)) $(filter $(BUILD_DIR)/release/%,$(APP_OBJS)): | $(LOCAL_DEPS) | ||||
| endif | ||||
|  | ||||
| ifeq ($(HAVE_WIN),1) | ||||
| WINDOWS_DEPS := out/openssl/$(UNAME_S)/mingw64/usr/local/lib/libssl.a | ||||
| $(WINDOWS_DEPS): | ||||
| 	+@BUILD_TARGET=mingw64 SSL_TARGET=mingw64 OPTIONS="--cross-compile-prefix=x86_64-w64-mingw32-" tools/ssl-local | ||||
| $(filter $(BUILD_DIR)/win%,$(APP_OBJS)): | $(WINDOWS_DEPS) | ||||
| endif | ||||
|  | ||||
| ifeq ($(UNAME_S),Darwin) | ||||
| IOS_DEPS := out/openssl/ios/ios64-xcrun/usr/local/lib/libssl.a | ||||
| $(IOS_DEPS): | ||||
| 	+@BUILD_PLATFORM=ios BUILD_TARGET=ios64-xcrun SSL_TARGET=ios64-xcrun OPTIONS="-fPIC -Wno-macro-redefined -miphoneos-version-min=$(IPHONEOS_VERSION_MIN)" tools/ssl-local | ||||
| 	+@BUILD_PLATFORM=ios BUILD_TARGET=iossimulator-xcrun SSL_TARGET=iossimulator-xcrun OPTIONS="-fPIC -Wno-macro-redefined" tools/ssl-local | ||||
| $(filter $(BUILD_DIR)/ios%,$(APP_OBJS)): | $(IOS_DEPS) | ||||
| endif | ||||
|  | ||||
| out/macos%/tildefriends: out/macos%-arm/tildefriends out/macos%-x86_64/tildefriends | ||||
| 	@echo [lipo] $@ | ||||
| 	@mkdir -p $(@D) | ||||
| @@ -1480,6 +1364,18 @@ help: ## Display this help message. | ||||
| .PHONY: help | ||||
| .DEFAULT_GOAL := help | ||||
|  | ||||
| docs: debug | ||||
| docs: ## Build HTML docs. | ||||
| 	@echo '# CLI Usage\n' > docs/usage.md | ||||
| 	@echo "## tildefriends -h" >> docs/usage.md | ||||
| 	@echo '\n```' >> docs/usage.md | ||||
| 	@out/debug/tildefriends -h >> docs/usage.md | ||||
| 	@echo '```' >> docs/usage.md | ||||
| 	@for command in $$(out/debug/tildefriends -h | grep -Po '[A-Za-z_]*(?= - )'); do | ||||
| 	@  echo "\n## tildefriends $$command -h" >> docs/usage.md | ||||
| 	@  echo '\n```' >> docs/usage.md | ||||
| 	@  out/debug/tildefriends $$command -h >> docs/usage.md | ||||
| 	@  echo '```' >> docs/usage.md | ||||
| 	@done | ||||
| 	@doxygen | ||||
| .PHONY: docs | ||||
|   | ||||
| @@ -38,8 +38,6 @@ dependencies in the right places. | ||||
|  | ||||
| ### Requirements | ||||
|  | ||||
| System OpenSSL libraries are assumed to be available on Haiku and OpenBSD. | ||||
|  | ||||
| On MacOS, Xcode's command-line tools are expected to be available. | ||||
|  | ||||
| ### Build Commands | ||||
|   | ||||
| @@ -1,4 +1,4 @@ | ||||
| /* W3.CSS 5.01 March 14 2025 by Jan Egil and Borge Refsnes */ | ||||
| /* W3.CSS 5.02 March 31 2025 by Jan Egil and Borge Refsnes */ | ||||
| html{box-sizing:border-box}*,*:before,*:after{box-sizing:inherit} | ||||
| /* Extract from normalize.css by Nicolas Gallagher and Jonathan Neal git.io/normalize */ | ||||
| html{-ms-text-size-adjust:100%;-webkit-text-size-adjust:100%}body{margin:0} | ||||
| @@ -108,10 +108,8 @@ hr{border:0;border-top:1px solid #eee;margin:20px 0} | ||||
| .w3-round-small{border-radius:2px}.w3-round,.w3-round-medium{border-radius:4px}.w3-round-large{border-radius:8px}.w3-round-xlarge{border-radius:16px}.w3-round-xxlarge{border-radius:32px} | ||||
| .w3-row-padding,.w3-row-padding>.w3-half,.w3-row-padding>.w3-third,.w3-row-padding>.w3-twothird,.w3-row-padding>.w3-threequarter,.w3-row-padding>.w3-quarter,.w3-row-padding>.w3-col{padding:0 8px} | ||||
| .w3-container,.w3-panel{padding:0.01em 16px}.w3-panel{margin-top:16px;margin-bottom:16px} | ||||
|  | ||||
| .w3-grid{display:grid}.w3-grid-padding{display:grid;gap:16px}.w3-flex{display:flex} | ||||
| .w3-text-center{text-align:center}.w3-text-bold,.w3-bold{font-weight:bold}.w3-text-italic,.w3-italic{font-style:italic} | ||||
|  | ||||
| .w3-code,.w3-codespan{font-family:Consolas,"courier new";font-size:16px} | ||||
| .w3-code{width:auto;background-color:#fff;padding:8px 12px;border-left:4px solid #4CAF50;word-wrap:break-word} | ||||
| .w3-codespan{color:crimson;background-color:#f1f1f1;padding-left:4px;padding-right:4px;font-size:110%} | ||||
| @@ -152,10 +150,11 @@ hr{border:0;border-top:1px solid #eee;margin:20px 0} | ||||
| .w3-button:hover{color:#000!important;background-color:#ccc!important} | ||||
| .w3-transparent,.w3-hover-none:hover{background-color:transparent!important} | ||||
| .w3-hover-none:hover{box-shadow:none!important} | ||||
| .w3-rtl{direction:rtl}.w3-ltr{direction:ltr} | ||||
| /* Colors */ | ||||
| .w3-amber,.w3-hover-amber:hover,.w3-warning{color:#000!important;background-color:#ffc107!important} | ||||
| .w3-amber,.w3-hover-amber:hover{color:#000!important;background-color:#ffc107!important} | ||||
| .w3-aqua,.w3-hover-aqua:hover{color:#000!important;background-color:#00ffff!important} | ||||
| .w3-blue,.w3-hover-blue:hover,.w3-info,.w3-primary{color:#fff!important;background-color:#2196F3!important} | ||||
| .w3-blue,.w3-hover-blue:hover{color:#fff!important;background-color:#2196F3!important} | ||||
| .w3-light-blue,.w3-hover-light-blue:hover{color:#000!important;background-color:#87CEEB!important} | ||||
| .w3-brown,.w3-hover-brown:hover{color:#fff!important;background-color:#795548!important} | ||||
| .w3-cyan,.w3-hover-cyan:hover{color:#000!important;background-color:#00bcd4!important} | ||||
| @@ -170,24 +169,28 @@ hr{border:0;border-top:1px solid #eee;margin:20px 0} | ||||
| .w3-pink,.w3-hover-pink:hover{color:#fff!important;background-color:#e91e63!important} | ||||
| .w3-purple,.w3-hover-purple:hover{color:#fff!important;background-color:#9c27b0!important} | ||||
| .w3-deep-purple,.w3-hover-deep-purple:hover{color:#fff!important;background-color:#673ab7!important} | ||||
| .w3-red,.w3-hover-red:hover,.w3-danger{color:#fff!important;background-color:#f44336!important} | ||||
| .w3-red,.w3-hover-red:hover{color:#fff!important;background-color:#f44336!important} | ||||
| .w3-sand,.w3-hover-sand:hover{color:#000!important;background-color:#fdf5e6!important} | ||||
| .w3-teal,.w3-hover-teal:hover{color:#fff!important;background-color:#009688!important} | ||||
| .w3-yellow,.w3-hover-yellow:hover,.w3-note{color:#000!important;background-color:#ffeb3b!important} | ||||
| .w3-yellow,.w3-hover-yellow:hover{color:#000!important;background-color:#ffeb3b!important} | ||||
| .w3-white,.w3-hover-white:hover{color:#000!important;background-color:#fff!important} | ||||
| .w3-black,.w3-hover-black:hover{color:#fff!important;background-color:#000!important} | ||||
|  | ||||
| .w3-grey,.w3-hover-grey:hover,.w3-gray,.w3-hover-gray:hover,.w3-secondary{color:#000!important;background-color:#9e9e9e!important} | ||||
| .w3-grey,.w3-hover-grey:hover,.w3-gray,.w3-hover-gray:hover{color:#000!important;background-color:#9e9e9e!important} | ||||
| .w3-light-grey,.w3-hover-light-grey:hover,.w3-light-gray,.w3-hover-light-gray:hover{color:#000!important;background-color:#f1f1f1!important} | ||||
| .w3-dark-grey,.w3-hover-dark-grey:hover,.w3-dark-gray,.w3-hover-dark-gray:hover{color:#fff!important;background-color:#616161!important} | ||||
|  | ||||
| .w3-asphalt,.w3-hover-asphalt:hover{color:#fff!important;background-color:#343a40!important}.w3-crimson,.w3-hover-crimson:hover{color:#fff!important;background-color:#a20025!important} | ||||
| .w3-asphalt,.w3-hover-asphalt:hover{color:#fff!important;background-color:#343a40!important} | ||||
| .w3-crimson,.w3-hover-crimson:hover{color:#fff!important;background-color:#a20025!important} | ||||
| .w3-cobalt,w3-hover-cobalt:hover{color:#fff!important;background-color:#0050ef!important} | ||||
| .w3-emerald,.w3-hover-emerald:hover,.w3-success{color:#fff!important;background-color:#008a00!important} | ||||
| .w3-emerald,.w3-hover-emerald:hover{color:#fff!important;background-color:#008a00!important} | ||||
| .w3-olive,.w3-hover-olive:hover{color:#fff!important;background-color:#6d8764!important} | ||||
| .w3-paper,.w3-hover-paper:hover{color:#000!important;background-color:#f8f9fa!important}.w3-sienna,.w3-hover-sienna:hover{color:#fff!important;background-color:#a0522d!important} | ||||
| .w3-paper,.w3-hover-paper:hover{color:#000!important;background-color:#f8f9fa!important} | ||||
| .w3-sienna,.w3-hover-sienna:hover{color:#fff!important;background-color:#a0522d!important} | ||||
| .w3-taupe,.w3-hover-taupe:hover{color:#fff!important;background-color:#87794e!important} | ||||
|  | ||||
| .w3-danger{color:#fff!important;background-color:#dd0000!important} | ||||
| .w3-note{color:#000!important;background-color:#fff599!important} | ||||
| .w3-info{color:#fff!important;background-color:#0a6fc2!important} | ||||
| .w3-warning{color:#000!important;background-color:#ffb305!important} | ||||
| .w3-success{color:#fff!important;background-color:#008a00!important} | ||||
| .w3-pale-red,.w3-hover-pale-red:hover{color:#000!important;background-color:#ffdddd!important} | ||||
| .w3-pale-green,.w3-hover-pale-green:hover{color:#000!important;background-color:#ddffdd!important} | ||||
| .w3-pale-yellow,.w3-hover-pale-yellow:hover{color:#000!important;background-color:#ffffcc!important} | ||||
|   | ||||
| @@ -1,5 +1,5 @@ | ||||
| { | ||||
| 	"type": "tildefriends-app", | ||||
| 	"emoji": "📜", | ||||
| 	"previous": "&BEf0nraBdHk/+PWqx6tOSu5rheWVaxaL7orAOz3285M=.sha256" | ||||
| 	"previous": "&sJqeyYjHys6Z8IqqtZ2ij2ZC1E2xieu/FU/u2hE+O1U=.sha256" | ||||
| } | ||||
|   | ||||
| @@ -55,6 +55,9 @@ app.setDocument(`<head> | ||||
| </head> | ||||
| <body style="color:#fff"> | ||||
| 	${markdown(docs.docs.global)} | ||||
| 	<!-- | ||||
| 	${Object.keys(docs.docs).filter(x => [...treeify('', globalThis)].indexOf(x) == -1).map(x => `<p>STALE: ${x}</p>`).join('')} | ||||
| 	--> | ||||
| 	${[...treeify('', globalThis)].map(x => document(x)).join('\n')} | ||||
| 	<a id="Database"></a> | ||||
| 	${markdown(docs.docs.database)} | ||||
|   | ||||
| @@ -195,51 +195,6 @@ Call a function after some delay. | ||||
|  * *Number* **timeout** Number of milliseconds to wait before calling the callback function. | ||||
| `; | ||||
|  | ||||
| docs['parseHttpRequest()'] = ` | ||||
| Parses an HTTP request. | ||||
| ### Parameters | ||||
|  * *Uint8Array* **request** The request data.  Maybe be partial or contain extra data.  The return value will | ||||
|     indicate when and where it is complete. | ||||
|  * *Number* **last_length** The length of the data passed on a previous attempt for the same request, or 0 initially. | ||||
| ### Returns | ||||
|  * *Integer* **-2** if the request is incomplete. | ||||
|  * *Integer* **-1** if the request could not be parsed. | ||||
|  * *Object* An object with **bytes_parsed**, **minor_version**, **path**, and **headers** fields on successful parse. | ||||
| `; | ||||
|  | ||||
| docs['parseHttpResponse()'] = ` | ||||
| Parses an HTTP response. | ||||
| ### Parameters | ||||
|  * *Uint8Array* **response** The response data.  Maybe be partial or contain extra data.  The return value will | ||||
|     indicate when and where it is complete. | ||||
|  * *Number* **last_length** The length of the data passed on a previous attempt for the same response, or 0 initially. | ||||
| ### Returns | ||||
|  * *Integer* **-2** if the response is incomplete. | ||||
|  * *Integer* **-1** if the response could not be parsed. | ||||
|  * *Object* An object with **bytes_parsed**, **minor_version**, **status**, **message**, and **headers** fields on successful parse. | ||||
| `; | ||||
|  | ||||
| docs['sha1Digest()'] = ` | ||||
| Calculates a SHA1 digest. | ||||
|  | ||||
| Completes synchronously. | ||||
| ### Parameters | ||||
|  * *String* **value** The value for which to calculate the digest. | ||||
| ### Returns | ||||
| *String* The SHA1 digest of UTF-8 encoded \`value\`. | ||||
| `; | ||||
|  | ||||
| docs['maskBytes()'] = ` | ||||
| Masks bytes for WebSocket communication. | ||||
|  | ||||
| Completes synchronously. | ||||
| ### Parameters | ||||
|  * *Uint8Array* **bytes** The byte array of data to mask. | ||||
|  * *Uint32* **mask** The mask to apply. | ||||
| ### Returns | ||||
| *Uint32Array* The masked bytes. | ||||
| `; | ||||
|  | ||||
| docs['exit()'] = ` | ||||
| Exits the app.  But why would you want to do that? | ||||
|  | ||||
|   | ||||
| @@ -1,4 +1,4 @@ | ||||
| /* W3.CSS 5.01 March 14 2025 by Jan Egil and Borge Refsnes */ | ||||
| /* W3.CSS 5.02 March 31 2025 by Jan Egil and Borge Refsnes */ | ||||
| html{box-sizing:border-box}*,*:before,*:after{box-sizing:inherit} | ||||
| /* Extract from normalize.css by Nicolas Gallagher and Jonathan Neal git.io/normalize */ | ||||
| html{-ms-text-size-adjust:100%;-webkit-text-size-adjust:100%}body{margin:0} | ||||
| @@ -108,10 +108,8 @@ hr{border:0;border-top:1px solid #eee;margin:20px 0} | ||||
| .w3-round-small{border-radius:2px}.w3-round,.w3-round-medium{border-radius:4px}.w3-round-large{border-radius:8px}.w3-round-xlarge{border-radius:16px}.w3-round-xxlarge{border-radius:32px} | ||||
| .w3-row-padding,.w3-row-padding>.w3-half,.w3-row-padding>.w3-third,.w3-row-padding>.w3-twothird,.w3-row-padding>.w3-threequarter,.w3-row-padding>.w3-quarter,.w3-row-padding>.w3-col{padding:0 8px} | ||||
| .w3-container,.w3-panel{padding:0.01em 16px}.w3-panel{margin-top:16px;margin-bottom:16px} | ||||
|  | ||||
| .w3-grid{display:grid}.w3-grid-padding{display:grid;gap:16px}.w3-flex{display:flex} | ||||
| .w3-text-center{text-align:center}.w3-text-bold,.w3-bold{font-weight:bold}.w3-text-italic,.w3-italic{font-style:italic} | ||||
|  | ||||
| .w3-code,.w3-codespan{font-family:Consolas,"courier new";font-size:16px} | ||||
| .w3-code{width:auto;background-color:#fff;padding:8px 12px;border-left:4px solid #4CAF50;word-wrap:break-word} | ||||
| .w3-codespan{color:crimson;background-color:#f1f1f1;padding-left:4px;padding-right:4px;font-size:110%} | ||||
| @@ -152,10 +150,11 @@ hr{border:0;border-top:1px solid #eee;margin:20px 0} | ||||
| .w3-button:hover{color:#000!important;background-color:#ccc!important} | ||||
| .w3-transparent,.w3-hover-none:hover{background-color:transparent!important} | ||||
| .w3-hover-none:hover{box-shadow:none!important} | ||||
| .w3-rtl{direction:rtl}.w3-ltr{direction:ltr} | ||||
| /* Colors */ | ||||
| .w3-amber,.w3-hover-amber:hover,.w3-warning{color:#000!important;background-color:#ffc107!important} | ||||
| .w3-amber,.w3-hover-amber:hover{color:#000!important;background-color:#ffc107!important} | ||||
| .w3-aqua,.w3-hover-aqua:hover{color:#000!important;background-color:#00ffff!important} | ||||
| .w3-blue,.w3-hover-blue:hover,.w3-info,.w3-primary{color:#fff!important;background-color:#2196F3!important} | ||||
| .w3-blue,.w3-hover-blue:hover{color:#fff!important;background-color:#2196F3!important} | ||||
| .w3-light-blue,.w3-hover-light-blue:hover{color:#000!important;background-color:#87CEEB!important} | ||||
| .w3-brown,.w3-hover-brown:hover{color:#fff!important;background-color:#795548!important} | ||||
| .w3-cyan,.w3-hover-cyan:hover{color:#000!important;background-color:#00bcd4!important} | ||||
| @@ -170,24 +169,28 @@ hr{border:0;border-top:1px solid #eee;margin:20px 0} | ||||
| .w3-pink,.w3-hover-pink:hover{color:#fff!important;background-color:#e91e63!important} | ||||
| .w3-purple,.w3-hover-purple:hover{color:#fff!important;background-color:#9c27b0!important} | ||||
| .w3-deep-purple,.w3-hover-deep-purple:hover{color:#fff!important;background-color:#673ab7!important} | ||||
| .w3-red,.w3-hover-red:hover,.w3-danger{color:#fff!important;background-color:#f44336!important} | ||||
| .w3-red,.w3-hover-red:hover{color:#fff!important;background-color:#f44336!important} | ||||
| .w3-sand,.w3-hover-sand:hover{color:#000!important;background-color:#fdf5e6!important} | ||||
| .w3-teal,.w3-hover-teal:hover{color:#fff!important;background-color:#009688!important} | ||||
| .w3-yellow,.w3-hover-yellow:hover,.w3-note{color:#000!important;background-color:#ffeb3b!important} | ||||
| .w3-yellow,.w3-hover-yellow:hover{color:#000!important;background-color:#ffeb3b!important} | ||||
| .w3-white,.w3-hover-white:hover{color:#000!important;background-color:#fff!important} | ||||
| .w3-black,.w3-hover-black:hover{color:#fff!important;background-color:#000!important} | ||||
|  | ||||
| .w3-grey,.w3-hover-grey:hover,.w3-gray,.w3-hover-gray:hover,.w3-secondary{color:#000!important;background-color:#9e9e9e!important} | ||||
| .w3-grey,.w3-hover-grey:hover,.w3-gray,.w3-hover-gray:hover{color:#000!important;background-color:#9e9e9e!important} | ||||
| .w3-light-grey,.w3-hover-light-grey:hover,.w3-light-gray,.w3-hover-light-gray:hover{color:#000!important;background-color:#f1f1f1!important} | ||||
| .w3-dark-grey,.w3-hover-dark-grey:hover,.w3-dark-gray,.w3-hover-dark-gray:hover{color:#fff!important;background-color:#616161!important} | ||||
|  | ||||
| .w3-asphalt,.w3-hover-asphalt:hover{color:#fff!important;background-color:#343a40!important}.w3-crimson,.w3-hover-crimson:hover{color:#fff!important;background-color:#a20025!important} | ||||
| .w3-asphalt,.w3-hover-asphalt:hover{color:#fff!important;background-color:#343a40!important} | ||||
| .w3-crimson,.w3-hover-crimson:hover{color:#fff!important;background-color:#a20025!important} | ||||
| .w3-cobalt,w3-hover-cobalt:hover{color:#fff!important;background-color:#0050ef!important} | ||||
| .w3-emerald,.w3-hover-emerald:hover,.w3-success{color:#fff!important;background-color:#008a00!important} | ||||
| .w3-emerald,.w3-hover-emerald:hover{color:#fff!important;background-color:#008a00!important} | ||||
| .w3-olive,.w3-hover-olive:hover{color:#fff!important;background-color:#6d8764!important} | ||||
| .w3-paper,.w3-hover-paper:hover{color:#000!important;background-color:#f8f9fa!important}.w3-sienna,.w3-hover-sienna:hover{color:#fff!important;background-color:#a0522d!important} | ||||
| .w3-paper,.w3-hover-paper:hover{color:#000!important;background-color:#f8f9fa!important} | ||||
| .w3-sienna,.w3-hover-sienna:hover{color:#fff!important;background-color:#a0522d!important} | ||||
| .w3-taupe,.w3-hover-taupe:hover{color:#fff!important;background-color:#87794e!important} | ||||
|  | ||||
| .w3-danger{color:#fff!important;background-color:#dd0000!important} | ||||
| .w3-note{color:#000!important;background-color:#fff599!important} | ||||
| .w3-info{color:#fff!important;background-color:#0a6fc2!important} | ||||
| .w3-warning{color:#000!important;background-color:#ffb305!important} | ||||
| .w3-success{color:#fff!important;background-color:#008a00!important} | ||||
| .w3-pale-red,.w3-hover-pale-red:hover{color:#000!important;background-color:#ffdddd!important} | ||||
| .w3-pale-green,.w3-hover-pale-green:hover{color:#000!important;background-color:#ddffdd!important} | ||||
| .w3-pale-yellow,.w3-hover-pale-yellow:hover{color:#000!important;background-color:#ffffcc!important} | ||||
|   | ||||
							
								
								
									
										42
									
								
								apps/blog/lit-all.min.js
									
									
									
									
										vendored
									
									
								
							
							
						
						| @@ -1,4 +1,4 @@ | ||||
| /* W3.CSS 5.01 March 14 2025 by Jan Egil and Borge Refsnes */ | ||||
| /* W3.CSS 5.02 March 31 2025 by Jan Egil and Borge Refsnes */ | ||||
| html{box-sizing:border-box}*,*:before,*:after{box-sizing:inherit} | ||||
| /* Extract from normalize.css by Nicolas Gallagher and Jonathan Neal git.io/normalize */ | ||||
| html{-ms-text-size-adjust:100%;-webkit-text-size-adjust:100%}body{margin:0} | ||||
| @@ -108,10 +108,8 @@ hr{border:0;border-top:1px solid #eee;margin:20px 0} | ||||
| .w3-round-small{border-radius:2px}.w3-round,.w3-round-medium{border-radius:4px}.w3-round-large{border-radius:8px}.w3-round-xlarge{border-radius:16px}.w3-round-xxlarge{border-radius:32px} | ||||
| .w3-row-padding,.w3-row-padding>.w3-half,.w3-row-padding>.w3-third,.w3-row-padding>.w3-twothird,.w3-row-padding>.w3-threequarter,.w3-row-padding>.w3-quarter,.w3-row-padding>.w3-col{padding:0 8px} | ||||
| .w3-container,.w3-panel{padding:0.01em 16px}.w3-panel{margin-top:16px;margin-bottom:16px} | ||||
|  | ||||
| .w3-grid{display:grid}.w3-grid-padding{display:grid;gap:16px}.w3-flex{display:flex} | ||||
| .w3-text-center{text-align:center}.w3-text-bold,.w3-bold{font-weight:bold}.w3-text-italic,.w3-italic{font-style:italic} | ||||
|  | ||||
| .w3-code,.w3-codespan{font-family:Consolas,"courier new";font-size:16px} | ||||
| .w3-code{width:auto;background-color:#fff;padding:8px 12px;border-left:4px solid #4CAF50;word-wrap:break-word} | ||||
| .w3-codespan{color:crimson;background-color:#f1f1f1;padding-left:4px;padding-right:4px;font-size:110%} | ||||
| @@ -152,10 +150,11 @@ hr{border:0;border-top:1px solid #eee;margin:20px 0} | ||||
| .w3-button:hover{color:#000!important;background-color:#ccc!important} | ||||
| .w3-transparent,.w3-hover-none:hover{background-color:transparent!important} | ||||
| .w3-hover-none:hover{box-shadow:none!important} | ||||
| .w3-rtl{direction:rtl}.w3-ltr{direction:ltr} | ||||
| /* Colors */ | ||||
| .w3-amber,.w3-hover-amber:hover,.w3-warning{color:#000!important;background-color:#ffc107!important} | ||||
| .w3-amber,.w3-hover-amber:hover{color:#000!important;background-color:#ffc107!important} | ||||
| .w3-aqua,.w3-hover-aqua:hover{color:#000!important;background-color:#00ffff!important} | ||||
| .w3-blue,.w3-hover-blue:hover,.w3-info,.w3-primary{color:#fff!important;background-color:#2196F3!important} | ||||
| .w3-blue,.w3-hover-blue:hover{color:#fff!important;background-color:#2196F3!important} | ||||
| .w3-light-blue,.w3-hover-light-blue:hover{color:#000!important;background-color:#87CEEB!important} | ||||
| .w3-brown,.w3-hover-brown:hover{color:#fff!important;background-color:#795548!important} | ||||
| .w3-cyan,.w3-hover-cyan:hover{color:#000!important;background-color:#00bcd4!important} | ||||
| @@ -170,24 +169,28 @@ hr{border:0;border-top:1px solid #eee;margin:20px 0} | ||||
| .w3-pink,.w3-hover-pink:hover{color:#fff!important;background-color:#e91e63!important} | ||||
| .w3-purple,.w3-hover-purple:hover{color:#fff!important;background-color:#9c27b0!important} | ||||
| .w3-deep-purple,.w3-hover-deep-purple:hover{color:#fff!important;background-color:#673ab7!important} | ||||
| .w3-red,.w3-hover-red:hover,.w3-danger{color:#fff!important;background-color:#f44336!important} | ||||
| .w3-red,.w3-hover-red:hover{color:#fff!important;background-color:#f44336!important} | ||||
| .w3-sand,.w3-hover-sand:hover{color:#000!important;background-color:#fdf5e6!important} | ||||
| .w3-teal,.w3-hover-teal:hover{color:#fff!important;background-color:#009688!important} | ||||
| .w3-yellow,.w3-hover-yellow:hover,.w3-note{color:#000!important;background-color:#ffeb3b!important} | ||||
| .w3-yellow,.w3-hover-yellow:hover{color:#000!important;background-color:#ffeb3b!important} | ||||
| .w3-white,.w3-hover-white:hover{color:#000!important;background-color:#fff!important} | ||||
| .w3-black,.w3-hover-black:hover{color:#fff!important;background-color:#000!important} | ||||
|  | ||||
| .w3-grey,.w3-hover-grey:hover,.w3-gray,.w3-hover-gray:hover,.w3-secondary{color:#000!important;background-color:#9e9e9e!important} | ||||
| .w3-grey,.w3-hover-grey:hover,.w3-gray,.w3-hover-gray:hover{color:#000!important;background-color:#9e9e9e!important} | ||||
| .w3-light-grey,.w3-hover-light-grey:hover,.w3-light-gray,.w3-hover-light-gray:hover{color:#000!important;background-color:#f1f1f1!important} | ||||
| .w3-dark-grey,.w3-hover-dark-grey:hover,.w3-dark-gray,.w3-hover-dark-gray:hover{color:#fff!important;background-color:#616161!important} | ||||
|  | ||||
| .w3-asphalt,.w3-hover-asphalt:hover{color:#fff!important;background-color:#343a40!important}.w3-crimson,.w3-hover-crimson:hover{color:#fff!important;background-color:#a20025!important} | ||||
| .w3-asphalt,.w3-hover-asphalt:hover{color:#fff!important;background-color:#343a40!important} | ||||
| .w3-crimson,.w3-hover-crimson:hover{color:#fff!important;background-color:#a20025!important} | ||||
| .w3-cobalt,w3-hover-cobalt:hover{color:#fff!important;background-color:#0050ef!important} | ||||
| .w3-emerald,.w3-hover-emerald:hover,.w3-success{color:#fff!important;background-color:#008a00!important} | ||||
| .w3-emerald,.w3-hover-emerald:hover{color:#fff!important;background-color:#008a00!important} | ||||
| .w3-olive,.w3-hover-olive:hover{color:#fff!important;background-color:#6d8764!important} | ||||
| .w3-paper,.w3-hover-paper:hover{color:#000!important;background-color:#f8f9fa!important}.w3-sienna,.w3-hover-sienna:hover{color:#fff!important;background-color:#a0522d!important} | ||||
| .w3-paper,.w3-hover-paper:hover{color:#000!important;background-color:#f8f9fa!important} | ||||
| .w3-sienna,.w3-hover-sienna:hover{color:#fff!important;background-color:#a0522d!important} | ||||
| .w3-taupe,.w3-hover-taupe:hover{color:#fff!important;background-color:#87794e!important} | ||||
|  | ||||
| .w3-danger{color:#fff!important;background-color:#dd0000!important} | ||||
| .w3-note{color:#000!important;background-color:#fff599!important} | ||||
| .w3-info{color:#fff!important;background-color:#0a6fc2!important} | ||||
| .w3-warning{color:#000!important;background-color:#ffb305!important} | ||||
| .w3-success{color:#fff!important;background-color:#008a00!important} | ||||
| .w3-pale-red,.w3-hover-pale-red:hover{color:#000!important;background-color:#ffdddd!important} | ||||
| .w3-pale-green,.w3-hover-pale-green:hover{color:#000!important;background-color:#ddffdd!important} | ||||
| .w3-pale-yellow,.w3-hover-pale-yellow:hover{color:#000!important;background-color:#ffffcc!important} | ||||
|   | ||||
							
								
								
									
										5
									
								
								apps/intro.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,5 @@ | ||||
| { | ||||
| 	"type": "tildefriends-app", | ||||
| 	"emoji": "💡", | ||||
| 	"previous": "&eN6DNPpQUNhGvxneLuLPgsOXR6qyFZ7u+MAz0b4fa7k=.sha256" | ||||
| } | ||||
							
								
								
									
										16
									
								
								apps/intro/app.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,16 @@ | ||||
| import * as tfrpc from '/tfrpc.js'; | ||||
|  | ||||
| async function main() { | ||||
| 	await app.setDocument(utf8Decode(getFile('index.html'))); | ||||
| } | ||||
|  | ||||
| tfrpc.register(async function complete() { | ||||
| 	if ( | ||||
| 		core.user?.credentials?.permissions?.administration && | ||||
| 		(await core.globalSettingsGet('index')) == '/~core/intro/' | ||||
| 	) { | ||||
| 		return await core.globalSettingsSet('index', '/~core/ssb/'); | ||||
| 	} | ||||
| }); | ||||
|  | ||||
| main(); | ||||
							
								
								
									
										286
									
								
								apps/intro/index.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,286 @@ | ||||
| <!doctype html> | ||||
| <html style="height: 100%; margin: 0; padding: 0; box-sizing: border-box"> | ||||
| 	<head> | ||||
| 		<meta name="viewport" content="width=device-width, initial-scale=1" /> | ||||
| 		<link rel="stylesheet" type="text/css" href="w3.css" /> | ||||
| 		<style> | ||||
| 			.slide { | ||||
| 				display: none; | ||||
| 				margin-left: auto; | ||||
| 				margin-right: auto; | ||||
| 			} | ||||
| 			.dot { | ||||
| 				width: 1em; | ||||
| 				height: 1em; | ||||
| 				cursor: pointer; | ||||
| 			} | ||||
| 			.w3-left, | ||||
| 			.w3-right { | ||||
| 				cursor: pointer; | ||||
| 			} | ||||
| 		</style> | ||||
| 	</head> | ||||
| 	<body | ||||
| 		style=" | ||||
| 			width: 100%; | ||||
| 			height: 100%; | ||||
| 			max-width: 100%; | ||||
| 			max-height: 100%; | ||||
| 			margin: 0; | ||||
| 			padding: 0; | ||||
| 			flex-direction: column; | ||||
| 			align-items: center; | ||||
| 		" | ||||
| 		class="w3-flex w3-dark-gray w3-center" | ||||
| 	> | ||||
| 		<div | ||||
| 			style=" | ||||
| 				flex: 1 1 auto; | ||||
| 				overflow: auto; | ||||
| 				contain: content; | ||||
| 				padding-top: 16px; | ||||
| 				padding-bottom: 16px; | ||||
| 			" | ||||
| 		> | ||||
| 			<div class="slide"> | ||||
| 				<div | ||||
| 					class="w3-content w3-xlarge w3-card-4 w3-blue w3-panel w3-padding-32 w3-round-xlarge" | ||||
| 					style="margin: 32px" | ||||
| 				> | ||||
| 					<div> | ||||
| 						<div>Welcome to</div> | ||||
| 						<div>~😎 Tilde Friends.</div> | ||||
| 					</div> | ||||
| 					<footer> | ||||
| 						<button class="w3-button w3-yellow proceed">Next</button> | ||||
| 					</footer> | ||||
| 				</div> | ||||
| 			</div> | ||||
| 			<div class="slide w3-card-4 w3-gray" style="width: 90%"> | ||||
| 				<header class="w3-container w3-blue w3-xlarge"> | ||||
| 					<h1>This brief tutorial will introduce:</h1> | ||||
| 				</header> | ||||
| 				<ul class="w3-large w3-left-align"> | ||||
| 					<li><b>Secure Scuttlebutt</b>, a decentralized social network.</li> | ||||
| 					<li> | ||||
| 						<b>Tilde Friends</b>, the application platform that you are using | ||||
| 						right now. | ||||
| 					</li> | ||||
| 					<li> | ||||
| 						<b>How to get started</b> if you want to get gossiping right away. | ||||
| 					</li> | ||||
| 				</ul> | ||||
| 				<footer class="w3-center w3-xlarge w3-padding"> | ||||
| 					<button class="w3-button w3-yellow proceed">Onward</button> | ||||
| 				</footer> | ||||
| 			</div> | ||||
| 			<div class="slide w3-gray" style="width: 90%"> | ||||
| 				<div class="w3-card-4 w3-xlarge"> | ||||
| 					<header class="w3-container w3-blue"> | ||||
| 						<h1>💻Secure Scuttlebutt in a Nutshell🦀</h1> | ||||
| 					</header> | ||||
| 					<div class="w3-container w3-large w3-left-align"> | ||||
| 						<p> | ||||
| 							Secure Scuttlebutt is a social network whose technical operation | ||||
| 							attempts to mirror human social interaction. | ||||
| 						</p> | ||||
| 						<ul> | ||||
| 							<li> | ||||
| 								You can create your own account and post to your own feed on | ||||
| 								your own device. This is all <b>local</b> with no external | ||||
| 								communication. This puts you fully in control of your own words | ||||
| 								and actions. | ||||
| 							</li> | ||||
| 							<li> | ||||
| 								Before you can interact with others, you need to | ||||
| 								<b>connect over the network</b>, either directly to your friends | ||||
| 								(i.e., peer-to-peer between your phones on coffee shop Wi-Fi) or | ||||
| 								to 🚪<i>rooms</i> and 🍻<i>pubs</i> (hint: search the web for | ||||
| 								<i>#ssbroom</i>). | ||||
| 							</li> | ||||
| 							<li> | ||||
| 								Who you choose to <b>follow</b> determines what you see, with | ||||
| 								most people choosing to see messages from friends and friends of | ||||
| 								those friends. If you encounter content you'd rather not see, | ||||
| 								<b>block</b> the offending account to improve the experience for | ||||
| 								you and your followers. | ||||
| 							</li> | ||||
| 							<li> | ||||
| 								Your feed is an <b>immutable</b> log of your activity. Post with | ||||
| 								care, because like your words in real life, posts can't be taken | ||||
| 								back. | ||||
| 							</li> | ||||
| 						</ul> | ||||
| 					</div> | ||||
| 					<footer class="w3-center w3-xlarge w3-padding"> | ||||
| 						<a | ||||
| 							class="w3-button w3-light-gray" | ||||
| 							href="https://scuttlebutt.nz/" | ||||
| 							target="_blank" | ||||
| 							>See scuttlebutt.nz</a | ||||
| 						> | ||||
| 						<button class="w3-button w3-yellow proceed">Got It</button> | ||||
| 					</footer> | ||||
| 				</div> | ||||
| 			</div> | ||||
| 			<div class="slide w3-gray" style="width: 90%"> | ||||
| 				<div class="w3-card-4 w3-xlarge"> | ||||
| 					<header class="w3-container w3-blue w3-center"> | ||||
| 						<h1>~😎 Let's Talk Tilde Friends ~😎</h1> | ||||
| 					</header> | ||||
| 					<div class="w3-container w3-large w3-left-align"> | ||||
| 						<p> | ||||
| 							Tilde Friends is an application platform that is an application of | ||||
| 							its own. | ||||
| 						</p> | ||||
| 						<ul> | ||||
| 							<li> | ||||
| 								This intro is a Tilde Friends app. You can click <b>edit</b> at | ||||
| 								the top to look under the hood and make changes. | ||||
| 							</li> | ||||
| 							<li> | ||||
| 								It is already possible to make and share new applications using | ||||
| 								only Tilde Friends and Secure Scuttlebutt without having to set | ||||
| 								up development environments, configure web servers, register | ||||
| 								domain names, or pay for hosting services. | ||||
| 							</li> | ||||
| 							<li> | ||||
| 								But it's also set up so that you can't just break an app that | ||||
| 								everybody is using or do malicious things with personal content. | ||||
| 								There are <b>protections</b> in place like an operating system. | ||||
| 								The intent is also for it to be <b>safe</b> to run strange apps | ||||
| 								without worrying about adverse effects. | ||||
| 							</li> | ||||
| 							<li> | ||||
| 								But this is all a big 🚧work in progress🚧 and | ||||
| 								<b>experiment</b>. Let's see where it takes us. | ||||
| 							</li> | ||||
| 						</ul> | ||||
| 					</div> | ||||
| 					<footer class="w3-center w3-xlarge w3-padding"> | ||||
| 						<button class="w3-button w3-yellow proceed">Okay</button> | ||||
| 					</footer> | ||||
| 				</div> | ||||
| 			</div> | ||||
| 			<div class="slide w3-gray" style="width: 90%"> | ||||
| 				<div class="w3-card-4 w3-xlarge"> | ||||
| 					<header class="w3-container w3-blue w3-center"> | ||||
| 						<h1>🦀Let's Get this Tilde Friends Party Started🎉</h1> | ||||
| 					</header> | ||||
| 					<div class="w3-container w3-large w3-left-align"> | ||||
| 						<p>The button below will take you to the Secure Scuttlebutt app.</p> | ||||
| 						<ul> | ||||
| 							<li> | ||||
| 								Remember: | ||||
| 								<ol> | ||||
| 									<li>You are in charge. This is all on your device.</li> | ||||
| 									<li> | ||||
| 										Make network connections to exchange messages with others. | ||||
| 									</li> | ||||
| 									<li> | ||||
| 										Follow more accounts to see more content, and block those | ||||
| 										posting content you'd rather not see. | ||||
| 									</li> | ||||
| 									<li> | ||||
| 										Be respectful, and consider the consequences of what you | ||||
| 										post. | ||||
| 									</li> | ||||
| 									<li> | ||||
| 										This is all under active development. Exercise patience, and | ||||
| 										report issues encountered. | ||||
| 									</li> | ||||
| 								</ol> | ||||
| 							</li> | ||||
| 							<li> | ||||
| 								To see this tutorial again later, select <b>apps</b> -> | ||||
| 								<b>Core Apps</b> -> <b>intro</b>. | ||||
| 							</li> | ||||
| 						</ul> | ||||
| 					</div> | ||||
| 					<footer class="w3-center w3-xlarge w3-padding"> | ||||
| 						<button class="w3-button w3-yellow" id="complete">Let's Go!</button> | ||||
| 					</footer> | ||||
| 				</div> | ||||
| 			</div> | ||||
| 		</div> | ||||
| 		<div | ||||
| 			class="w3-text-white w3-xlarge w3-center w3-flex" | ||||
| 			style=" | ||||
| 				width: 100%; | ||||
| 				flex: 0 1; | ||||
| 				flex-direction: row; | ||||
| 				align-items: center; | ||||
| 				gap: 8px; | ||||
| 			" | ||||
| 		> | ||||
| 			<div class="w3-jumbo" id="left" style="flex: 1 0; cursor: pointer"> | ||||
| 				❮ | ||||
| 			</div> | ||||
| 			<span class="w3-badge dot w3-border w3-hover-yellow"></span> | ||||
| 			<span class="w3-badge dot w3-border w3-hover-yellow"></span> | ||||
| 			<span class="w3-badge dot w3-border w3-hover-yellow"></span> | ||||
| 			<span class="w3-badge dot w3-border w3-hover-yellow"></span> | ||||
| 			<span class="w3-badge dot w3-border w3-hover-yellow"></span> | ||||
| 			<div class="w3-jumbo" style="flex: 1 0; cursor: pointer" id="right"> | ||||
| 				❯ | ||||
| 			</div> | ||||
| 		</div> | ||||
| 		<script type="module"> | ||||
| 			import * as tfrpc from '/static/tfrpc.js'; | ||||
|  | ||||
| 			let index = 0; | ||||
| 			function set(i) { | ||||
| 				show(i - index); | ||||
| 			} | ||||
| 			function show(delta) { | ||||
| 				let slides = [...document.getElementsByClassName('slide')]; | ||||
| 				let dots = [...document.getElementsByClassName('dot')]; | ||||
| 				index = (index + delta + slides.length) % slides.length; | ||||
| 				for (let slide of slides) { | ||||
| 					slide.style.display = | ||||
| 						slides.indexOf(slide) == index ? 'block' : 'none'; | ||||
| 				} | ||||
| 				for (let dot of dots) { | ||||
| 					if (dots.indexOf(dot) == index) { | ||||
| 						dot.classList.add('w3-white'); | ||||
| 					} else { | ||||
| 						dot.classList.remove('w3-white'); | ||||
| 					} | ||||
| 				} | ||||
| 				document.getElementById('left').style.visibility = | ||||
| 					index == 0 ? 'hidden' : 'visible'; | ||||
| 				document.getElementById('right').style.visibility = | ||||
| 					index == slides.length - 1 ? 'hidden' : 'visible'; | ||||
| 			} | ||||
|  | ||||
| 			let dots = [...document.getElementsByClassName('dot')]; | ||||
| 			for (let dot of dots) { | ||||
| 				dot.onclick = () => set(dots.indexOf(dot)); | ||||
| 			} | ||||
| 			for (let button of document.getElementsByClassName('proceed')) { | ||||
| 				button.onclick = () => show(1); | ||||
| 			} | ||||
| 			document.getElementById('left').onclick = () => show(-1); | ||||
| 			document.getElementById('right').onclick = () => show(1); | ||||
| 			document.getElementById('complete').onclick = function () { | ||||
| 				console.log('completing'); | ||||
| 				tfrpc.rpc.complete().finally(function () { | ||||
| 					console.log('completed'); | ||||
| 					let a = document.createElement('a'); | ||||
| 					a.href = '/~core/ssb/'; | ||||
| 					a.target = '_top'; | ||||
| 					document.body.appendChild(a); | ||||
| 					a.click(); | ||||
| 				}); | ||||
| 			}; | ||||
| 			window.addEventListener('keyup', function (event) { | ||||
| 				if (event.key == 'ArrowLeft') { | ||||
| 					show(-1); | ||||
| 				} else if (event.key == 'ArrowRight') { | ||||
| 					show(1); | ||||
| 				} | ||||
| 			}); | ||||
| 			show(0); | ||||
| 		</script> | ||||
| 	</body> | ||||
| </html> | ||||
							
								
								
									
										251
									
								
								apps/intro/w3.css
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,251 @@ | ||||
| /* W3.CSS 5.02 March 31 2025 by Jan Egil and Borge Refsnes */ | ||||
| html{box-sizing:border-box}*,*:before,*:after{box-sizing:inherit} | ||||
| /* Extract from normalize.css by Nicolas Gallagher and Jonathan Neal git.io/normalize */ | ||||
| html{-ms-text-size-adjust:100%;-webkit-text-size-adjust:100%}body{margin:0} | ||||
| article,aside,details,figcaption,figure,footer,header,main,menu,nav,section{display:block}summary{display:list-item} | ||||
| audio,canvas,progress,video{display:inline-block}progress{vertical-align:baseline} | ||||
| audio:not([controls]){display:none;height:0}[hidden],template{display:none} | ||||
| a{background-color:transparent}a:active,a:hover{outline-width:0} | ||||
| abbr[title]{border-bottom:none;text-decoration:underline;text-decoration:underline dotted} | ||||
| b,strong{font-weight:bolder}dfn{font-style:italic}mark{background:#ff0;color:#000} | ||||
| small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline} | ||||
| sub{bottom:-0.25em}sup{top:-0.5em}figure{margin:1em 40px}img{border-style:none} | ||||
| code,kbd,pre,samp{font-family:monospace,monospace;font-size:1em}hr{box-sizing:content-box;height:0;overflow:visible} | ||||
| button,input,select,textarea,optgroup{font:inherit;margin:0}optgroup{font-weight:bold} | ||||
| button,input{overflow:visible}button,select{text-transform:none} | ||||
| button,[type=button],[type=reset],[type=submit]{-webkit-appearance:button} | ||||
| button::-moz-focus-inner,[type=button]::-moz-focus-inner,[type=reset]::-moz-focus-inner,[type=submit]::-moz-focus-inner{border-style:none;padding:0} | ||||
| button:-moz-focusring,[type=button]:-moz-focusring,[type=reset]:-moz-focusring,[type=submit]:-moz-focusring{outline:1px dotted ButtonText} | ||||
| fieldset{border:1px solid #c0c0c0;margin:0 2px;padding:.35em .625em .75em} | ||||
| legend{color:inherit;display:table;max-width:100%;padding:0;white-space:normal}textarea{overflow:auto} | ||||
| [type=checkbox],[type=radio]{padding:0} | ||||
| [type=number]::-webkit-inner-spin-button,[type=number]::-webkit-outer-spin-button{height:auto} | ||||
| [type=search]{-webkit-appearance:textfield;outline-offset:-2px} | ||||
| [type=search]::-webkit-search-decoration{-webkit-appearance:none} | ||||
| ::-webkit-file-upload-button{-webkit-appearance:button;font:inherit} | ||||
| /* End extract */ | ||||
| html,body{font-family:Verdana,sans-serif;font-size:15px;line-height:1.5}html{overflow-x:hidden} | ||||
| h1{font-size:36px}h2{font-size:30px}h3{font-size:24px}h4{font-size:20px}h5{font-size:18px}h6{font-size:16px} | ||||
| .w3-serif{font-family:serif}.w3-sans-serif{font-family:sans-serif}.w3-cursive{font-family:cursive}.w3-monospace{font-family:monospace} | ||||
| h1,h2,h3,h4,h5,h6{font-family:"Segoe UI",Arial,sans-serif;font-weight:400;margin:10px 0}.w3-wide{letter-spacing:4px} | ||||
| hr{border:0;border-top:1px solid #eee;margin:20px 0} | ||||
| .w3-image{max-width:100%;height:auto}img{vertical-align:middle}a{color:inherit} | ||||
| .w3-table,.w3-table-all{border-collapse:collapse;border-spacing:0;width:100%;display:table}.w3-table-all{border:1px solid #ccc} | ||||
| .w3-bordered tr,.w3-table-all tr{border-bottom:1px solid #ddd}.w3-striped tbody tr:nth-child(even){background-color:#f1f1f1} | ||||
| .w3-table-all tr:nth-child(odd){background-color:#fff}.w3-table-all tr:nth-child(even){background-color:#f1f1f1} | ||||
| .w3-hoverable tbody tr:hover,.w3-ul.w3-hoverable li:hover{background-color:#ccc}.w3-centered tr th,.w3-centered tr td{text-align:center} | ||||
| .w3-table td,.w3-table th,.w3-table-all td,.w3-table-all th{padding:8px 8px;display:table-cell;text-align:left;vertical-align:top} | ||||
| .w3-table th:first-child,.w3-table td:first-child,.w3-table-all th:first-child,.w3-table-all td:first-child{padding-left:16px} | ||||
| .w3-btn,.w3-button{border:none;display:inline-block;padding:8px 16px;vertical-align:middle;overflow:hidden;text-decoration:none;color:inherit;background-color:inherit;text-align:center;cursor:pointer;white-space:nowrap} | ||||
| .w3-btn:hover{box-shadow:0 8px 16px 0 rgba(0,0,0,0.2),0 6px 20px 0 rgba(0,0,0,0.19)} | ||||
| .w3-btn,.w3-button{-webkit-touch-callout:none;-webkit-user-select:none;-khtml-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}    | ||||
| .w3-disabled,.w3-btn:disabled,.w3-button:disabled{cursor:not-allowed;opacity:0.3}.w3-disabled *,:disabled *{pointer-events:none} | ||||
| .w3-btn.w3-disabled:hover,.w3-btn:disabled:hover{box-shadow:none} | ||||
| .w3-badge,.w3-tag{background-color:#000;color:#fff;display:inline-block;padding-left:8px;padding-right:8px;text-align:center}.w3-badge{border-radius:50%} | ||||
| .w3-ul{list-style-type:none;padding:0;margin:0}.w3-ul li{padding:8px 16px;border-bottom:1px solid #ddd}.w3-ul li:last-child{border-bottom:none} | ||||
| .w3-tooltip,.w3-display-container{position:relative}.w3-tooltip .w3-text{display:none}.w3-tooltip:hover .w3-text{display:inline-block} | ||||
| .w3-ripple:active{opacity:0.5}.w3-ripple{transition:opacity 0s} | ||||
| .w3-input{padding:8px;display:block;border:none;border-bottom:1px solid #ccc;width:100%} | ||||
| .w3-select{padding:9px 0;width:100%;border:none;border-bottom:1px solid #ccc} | ||||
| .w3-dropdown-click,.w3-dropdown-hover{position:relative;display:inline-block;cursor:pointer} | ||||
| .w3-dropdown-hover:hover .w3-dropdown-content{display:block} | ||||
| .w3-dropdown-hover:first-child,.w3-dropdown-click:hover{background-color:#ccc;color:#000} | ||||
| .w3-dropdown-hover:hover > .w3-button:first-child,.w3-dropdown-click:hover > .w3-button:first-child{background-color:#ccc;color:#000} | ||||
| .w3-dropdown-content{cursor:auto;color:#000;background-color:#fff;display:none;position:absolute;min-width:160px;margin:0;padding:0;z-index:1} | ||||
| .w3-check,.w3-radio{width:24px;height:24px;position:relative;top:6px} | ||||
| .w3-sidebar{height:100%;width:200px;background-color:#fff;position:fixed!important;z-index:1;overflow:auto} | ||||
| .w3-bar-block .w3-dropdown-hover,.w3-bar-block .w3-dropdown-click{width:100%} | ||||
| .w3-bar-block .w3-dropdown-hover .w3-dropdown-content,.w3-bar-block .w3-dropdown-click .w3-dropdown-content{min-width:100%} | ||||
| .w3-bar-block .w3-dropdown-hover .w3-button,.w3-bar-block .w3-dropdown-click .w3-button{width:100%;text-align:left;padding:8px 16px} | ||||
| .w3-main,#main{transition:margin-left .4s} | ||||
| .w3-modal{z-index:3;display:none;padding-top:100px;position:fixed;left:0;top:0;width:100%;height:100%;overflow:auto;background-color:rgb(0,0,0);background-color:rgba(0,0,0,0.4)} | ||||
| .w3-modal-content{margin:auto;background-color:#fff;position:relative;padding:0;outline:0;width:600px} | ||||
| .w3-bar{width:100%;overflow:hidden}.w3-center .w3-bar{display:inline-block;width:auto} | ||||
| .w3-bar .w3-bar-item{padding:8px 16px;float:left;width:auto;border:none;display:block;outline:0} | ||||
| .w3-bar .w3-dropdown-hover,.w3-bar .w3-dropdown-click{position:static;float:left} | ||||
| .w3-bar .w3-button{white-space:normal} | ||||
| .w3-bar-block .w3-bar-item{width:100%;display:block;padding:8px 16px;text-align:left;border:none;white-space:normal;float:none;outline:0} | ||||
| .w3-bar-block.w3-center .w3-bar-item{text-align:center}.w3-block{display:block;width:100%} | ||||
| .w3-responsive{display:block;overflow-x:auto} | ||||
| .w3-container:after,.w3-container:before,.w3-panel:after,.w3-panel:before,.w3-row:after,.w3-row:before,.w3-row-padding:after,.w3-row-padding:before, | ||||
| .w3-cell-row:before,.w3-cell-row:after,.w3-clear:after,.w3-clear:before,.w3-bar:before,.w3-bar:after{content:"";display:table;clear:both} | ||||
| .w3-col,.w3-half,.w3-third,.w3-twothird,.w3-threequarter,.w3-quarter{float:left;width:100%} | ||||
| .w3-col.s1{width:8.33333%}.w3-col.s2{width:16.66666%}.w3-col.s3{width:24.99999%}.w3-col.s4{width:33.33333%} | ||||
| .w3-col.s5{width:41.66666%}.w3-col.s6{width:49.99999%}.w3-col.s7{width:58.33333%}.w3-col.s8{width:66.66666%} | ||||
| .w3-col.s9{width:74.99999%}.w3-col.s10{width:83.33333%}.w3-col.s11{width:91.66666%}.w3-col.s12{width:99.99999%} | ||||
| @media (min-width:601px){.w3-col.m1{width:8.33333%}.w3-col.m2{width:16.66666%}.w3-col.m3,.w3-quarter{width:24.99999%}.w3-col.m4,.w3-third{width:33.33333%} | ||||
| .w3-col.m5{width:41.66666%}.w3-col.m6,.w3-half{width:49.99999%}.w3-col.m7{width:58.33333%}.w3-col.m8,.w3-twothird{width:66.66666%} | ||||
| .w3-col.m9,.w3-threequarter{width:74.99999%}.w3-col.m10{width:83.33333%}.w3-col.m11{width:91.66666%}.w3-col.m12{width:99.99999%}} | ||||
| @media (min-width:993px){.w3-col.l1{width:8.33333%}.w3-col.l2{width:16.66666%}.w3-col.l3{width:24.99999%}.w3-col.l4{width:33.33333%} | ||||
| .w3-col.l5{width:41.66666%}.w3-col.l6{width:49.99999%}.w3-col.l7{width:58.33333%}.w3-col.l8{width:66.66666%} | ||||
| .w3-col.l9{width:74.99999%}.w3-col.l10{width:83.33333%}.w3-col.l11{width:91.66666%}.w3-col.l12{width:99.99999%}} | ||||
| .w3-rest{overflow:hidden}.w3-stretch{margin-left:-16px;margin-right:-16px} | ||||
| .w3-content,.w3-auto{margin-left:auto;margin-right:auto}.w3-content{max-width:980px}.w3-auto{max-width:1140px} | ||||
| .w3-cell-row{display:table;width:100%}.w3-cell{display:table-cell} | ||||
| .w3-cell-top{vertical-align:top}.w3-cell-middle{vertical-align:middle}.w3-cell-bottom{vertical-align:bottom} | ||||
| .w3-hide{display:none!important}.w3-show-block,.w3-show{display:block!important}.w3-show-inline-block{display:inline-block!important} | ||||
| @media (max-width:1205px){.w3-auto{max-width:95%}} | ||||
| @media (max-width:600px){.w3-modal-content{margin:0 10px;width:auto!important}.w3-modal{padding-top:30px} | ||||
| .w3-dropdown-hover.w3-mobile .w3-dropdown-content,.w3-dropdown-click.w3-mobile .w3-dropdown-content{position:relative}	 | ||||
| .w3-hide-small{display:none!important}.w3-mobile{display:block;width:100%!important}.w3-bar-item.w3-mobile,.w3-dropdown-hover.w3-mobile,.w3-dropdown-click.w3-mobile{text-align:center} | ||||
| .w3-dropdown-hover.w3-mobile,.w3-dropdown-hover.w3-mobile .w3-btn,.w3-dropdown-hover.w3-mobile .w3-button,.w3-dropdown-click.w3-mobile,.w3-dropdown-click.w3-mobile .w3-btn,.w3-dropdown-click.w3-mobile .w3-button{width:100%}} | ||||
| @media (max-width:768px){.w3-modal-content{width:500px}.w3-modal{padding-top:50px}} | ||||
| @media (min-width:993px){.w3-modal-content{width:900px}.w3-hide-large{display:none!important}.w3-sidebar.w3-collapse{display:block!important}} | ||||
| @media (max-width:992px) and (min-width:601px){.w3-hide-medium{display:none!important}} | ||||
| @media (max-width:992px){.w3-sidebar.w3-collapse{display:none}.w3-main{margin-left:0!important;margin-right:0!important}.w3-auto{max-width:100%}} | ||||
| .w3-top,.w3-bottom{position:fixed;width:100%;z-index:1}.w3-top{top:0}.w3-bottom{bottom:0} | ||||
| .w3-overlay{position:fixed;display:none;width:100%;height:100%;top:0;left:0;right:0;bottom:0;background-color:rgba(0,0,0,0.5);z-index:2} | ||||
| .w3-display-topleft{position:absolute;left:0;top:0}.w3-display-topright{position:absolute;right:0;top:0} | ||||
| .w3-display-bottomleft{position:absolute;left:0;bottom:0}.w3-display-bottomright{position:absolute;right:0;bottom:0} | ||||
| .w3-display-middle{position:absolute;top:50%;left:50%;transform:translate(-50%,-50%);-ms-transform:translate(-50%,-50%)} | ||||
| .w3-display-left{position:absolute;top:50%;left:0%;transform:translate(0%,-50%);-ms-transform:translate(-0%,-50%)} | ||||
| .w3-display-right{position:absolute;top:50%;right:0%;transform:translate(0%,-50%);-ms-transform:translate(0%,-50%)} | ||||
| .w3-display-topmiddle{position:absolute;left:50%;top:0;transform:translate(-50%,0%);-ms-transform:translate(-50%,0%)} | ||||
| .w3-display-bottommiddle{position:absolute;left:50%;bottom:0;transform:translate(-50%,0%);-ms-transform:translate(-50%,0%)} | ||||
| .w3-display-container:hover .w3-display-hover{display:block}.w3-display-container:hover span.w3-display-hover{display:inline-block}.w3-display-hover{display:none} | ||||
| .w3-display-position{position:absolute} | ||||
| .w3-circle{border-radius:50%} | ||||
| .w3-round-small{border-radius:2px}.w3-round,.w3-round-medium{border-radius:4px}.w3-round-large{border-radius:8px}.w3-round-xlarge{border-radius:16px}.w3-round-xxlarge{border-radius:32px} | ||||
| .w3-row-padding,.w3-row-padding>.w3-half,.w3-row-padding>.w3-third,.w3-row-padding>.w3-twothird,.w3-row-padding>.w3-threequarter,.w3-row-padding>.w3-quarter,.w3-row-padding>.w3-col{padding:0 8px} | ||||
| .w3-container,.w3-panel{padding:0.01em 16px}.w3-panel{margin-top:16px;margin-bottom:16px} | ||||
| .w3-grid{display:grid}.w3-grid-padding{display:grid;gap:16px}.w3-flex{display:flex} | ||||
| .w3-text-center{text-align:center}.w3-text-bold,.w3-bold{font-weight:bold}.w3-text-italic,.w3-italic{font-style:italic} | ||||
| .w3-code,.w3-codespan{font-family:Consolas,"courier new";font-size:16px} | ||||
| .w3-code{width:auto;background-color:#fff;padding:8px 12px;border-left:4px solid #4CAF50;word-wrap:break-word} | ||||
| .w3-codespan{color:crimson;background-color:#f1f1f1;padding-left:4px;padding-right:4px;font-size:110%} | ||||
| .w3-card,.w3-card-2{box-shadow:0 2px 5px 0 rgba(0,0,0,0.16),0 2px 10px 0 rgba(0,0,0,0.12)} | ||||
| .w3-card-4,.w3-hover-shadow:hover{box-shadow:0 4px 10px 0 rgba(0,0,0,0.2),0 4px 20px 0 rgba(0,0,0,0.19)} | ||||
| .w3-spin{animation:w3-spin 2s infinite linear}@keyframes w3-spin{0%{transform:rotate(0deg)}100%{transform:rotate(359deg)}} | ||||
| .w3-animate-fading{animation:fading 10s infinite}@keyframes fading{0%{opacity:0}50%{opacity:1}100%{opacity:0}} | ||||
| .w3-animate-opacity{animation:opac 0.8s}@keyframes opac{from{opacity:0} to{opacity:1}} | ||||
| .w3-animate-top{position:relative;animation:animatetop 0.4s}@keyframes animatetop{from{top:-300px;opacity:0} to{top:0;opacity:1}} | ||||
| .w3-animate-left{position:relative;animation:animateleft 0.4s}@keyframes animateleft{from{left:-300px;opacity:0} to{left:0;opacity:1}} | ||||
| .w3-animate-right{position:relative;animation:animateright 0.4s}@keyframes animateright{from{right:-300px;opacity:0} to{right:0;opacity:1}} | ||||
| .w3-animate-bottom{position:relative;animation:animatebottom 0.4s}@keyframes animatebottom{from{bottom:-300px;opacity:0} to{bottom:0;opacity:1}} | ||||
| .w3-animate-zoom {animation:animatezoom 0.6s}@keyframes animatezoom{from{transform:scale(0)} to{transform:scale(1)}} | ||||
| .w3-animate-input{transition:width 0.4s ease-in-out}.w3-animate-input:focus{width:100%!important} | ||||
| .w3-opacity,.w3-hover-opacity:hover{opacity:0.60}.w3-opacity-off,.w3-hover-opacity-off:hover{opacity:1} | ||||
| .w3-opacity-max{opacity:0.25}.w3-opacity-min{opacity:0.75} | ||||
| .w3-greyscale-max,.w3-grayscale-max,.w3-hover-greyscale:hover,.w3-hover-grayscale:hover{filter:grayscale(100%)} | ||||
| .w3-greyscale,.w3-grayscale{filter:grayscale(75%)}.w3-greyscale-min,.w3-grayscale-min{filter:grayscale(50%)} | ||||
| .w3-sepia{filter:sepia(75%)}.w3-sepia-max,.w3-hover-sepia:hover{filter:sepia(100%)}.w3-sepia-min{filter:sepia(50%)} | ||||
| .w3-tiny{font-size:10px!important}.w3-small{font-size:12px!important}.w3-medium{font-size:15px!important}.w3-large{font-size:18px!important} | ||||
| .w3-xlarge{font-size:24px!important}.w3-xxlarge{font-size:36px!important}.w3-xxxlarge{font-size:48px!important}.w3-jumbo{font-size:64px!important} | ||||
| .w3-left-align{text-align:left!important}.w3-right-align{text-align:right!important}.w3-justify{text-align:justify!important}.w3-center{text-align:center!important} | ||||
| .w3-border-0{border:0!important}.w3-border{border:1px solid #ccc!important} | ||||
| .w3-border-top{border-top:1px solid #ccc!important}.w3-border-bottom{border-bottom:1px solid #ccc!important} | ||||
| .w3-border-left{border-left:1px solid #ccc!important}.w3-border-right{border-right:1px solid #ccc!important} | ||||
| .w3-topbar{border-top:6px solid #ccc!important}.w3-bottombar{border-bottom:6px solid #ccc!important} | ||||
| .w3-leftbar{border-left:6px solid #ccc!important}.w3-rightbar{border-right:6px solid #ccc!important} | ||||
| .w3-section,.w3-code{margin-top:16px!important;margin-bottom:16px!important} | ||||
| .w3-margin{margin:16px!important}.w3-margin-top{margin-top:16px!important}.w3-margin-bottom{margin-bottom:16px!important} | ||||
| .w3-margin-left{margin-left:16px!important}.w3-margin-right{margin-right:16px!important} | ||||
| .w3-padding-small{padding:4px 8px!important}.w3-padding{padding:8px 16px!important}.w3-padding-large{padding:12px 24px!important} | ||||
| .w3-padding-16{padding-top:16px!important;padding-bottom:16px!important}.w3-padding-24{padding-top:24px!important;padding-bottom:24px!important} | ||||
| .w3-padding-32{padding-top:32px!important;padding-bottom:32px!important}.w3-padding-48{padding-top:48px!important;padding-bottom:48px!important} | ||||
| .w3-padding-64{padding-top:64px!important;padding-bottom:64px!important} | ||||
| .w3-padding-top-64{padding-top:64px!important}.w3-padding-top-48{padding-top:48px!important} | ||||
| .w3-padding-top-32{padding-top:32px!important}.w3-padding-top-24{padding-top:24px!important} | ||||
| .w3-left{float:left!important}.w3-right{float:right!important} | ||||
| .w3-button:hover{color:#000!important;background-color:#ccc!important} | ||||
| .w3-transparent,.w3-hover-none:hover{background-color:transparent!important} | ||||
| .w3-hover-none:hover{box-shadow:none!important} | ||||
| .w3-rtl{direction:rtl}.w3-ltr{direction:ltr} | ||||
| /* Colors */ | ||||
| .w3-amber,.w3-hover-amber:hover{color:#000!important;background-color:#ffc107!important} | ||||
| .w3-aqua,.w3-hover-aqua:hover{color:#000!important;background-color:#00ffff!important} | ||||
| .w3-blue,.w3-hover-blue:hover{color:#fff!important;background-color:#2196F3!important} | ||||
| .w3-light-blue,.w3-hover-light-blue:hover{color:#000!important;background-color:#87CEEB!important} | ||||
| .w3-brown,.w3-hover-brown:hover{color:#fff!important;background-color:#795548!important} | ||||
| .w3-cyan,.w3-hover-cyan:hover{color:#000!important;background-color:#00bcd4!important} | ||||
| .w3-blue-grey,.w3-hover-blue-grey:hover,.w3-blue-gray,.w3-hover-blue-gray:hover{color:#fff!important;background-color:#607d8b!important} | ||||
| .w3-green,.w3-hover-green:hover{color:#fff!important;background-color:#4CAF50!important} | ||||
| .w3-light-green,.w3-hover-light-green:hover{color:#000!important;background-color:#8bc34a!important} | ||||
| .w3-indigo,.w3-hover-indigo:hover{color:#fff!important;background-color:#3f51b5!important} | ||||
| .w3-khaki,.w3-hover-khaki:hover{color:#000!important;background-color:#f0e68c!important} | ||||
| .w3-lime,.w3-hover-lime:hover{color:#000!important;background-color:#cddc39!important} | ||||
| .w3-orange,.w3-hover-orange:hover{color:#000!important;background-color:#ff9800!important} | ||||
| .w3-deep-orange,.w3-hover-deep-orange:hover{color:#fff!important;background-color:#ff5722!important} | ||||
| .w3-pink,.w3-hover-pink:hover{color:#fff!important;background-color:#e91e63!important} | ||||
| .w3-purple,.w3-hover-purple:hover{color:#fff!important;background-color:#9c27b0!important} | ||||
| .w3-deep-purple,.w3-hover-deep-purple:hover{color:#fff!important;background-color:#673ab7!important} | ||||
| .w3-red,.w3-hover-red:hover{color:#fff!important;background-color:#f44336!important} | ||||
| .w3-sand,.w3-hover-sand:hover{color:#000!important;background-color:#fdf5e6!important} | ||||
| .w3-teal,.w3-hover-teal:hover{color:#fff!important;background-color:#009688!important} | ||||
| .w3-yellow,.w3-hover-yellow:hover{color:#000!important;background-color:#ffeb3b!important} | ||||
| .w3-white,.w3-hover-white:hover{color:#000!important;background-color:#fff!important} | ||||
| .w3-black,.w3-hover-black:hover{color:#fff!important;background-color:#000!important} | ||||
| .w3-grey,.w3-hover-grey:hover,.w3-gray,.w3-hover-gray:hover{color:#000!important;background-color:#9e9e9e!important} | ||||
| .w3-light-grey,.w3-hover-light-grey:hover,.w3-light-gray,.w3-hover-light-gray:hover{color:#000!important;background-color:#f1f1f1!important} | ||||
| .w3-dark-grey,.w3-hover-dark-grey:hover,.w3-dark-gray,.w3-hover-dark-gray:hover{color:#fff!important;background-color:#616161!important} | ||||
| .w3-asphalt,.w3-hover-asphalt:hover{color:#fff!important;background-color:#343a40!important} | ||||
| .w3-crimson,.w3-hover-crimson:hover{color:#fff!important;background-color:#a20025!important} | ||||
| .w3-cobalt,w3-hover-cobalt:hover{color:#fff!important;background-color:#0050ef!important} | ||||
| .w3-emerald,.w3-hover-emerald:hover{color:#fff!important;background-color:#008a00!important} | ||||
| .w3-olive,.w3-hover-olive:hover{color:#fff!important;background-color:#6d8764!important} | ||||
| .w3-paper,.w3-hover-paper:hover{color:#000!important;background-color:#f8f9fa!important} | ||||
| .w3-sienna,.w3-hover-sienna:hover{color:#fff!important;background-color:#a0522d!important} | ||||
| .w3-taupe,.w3-hover-taupe:hover{color:#fff!important;background-color:#87794e!important} | ||||
| .w3-danger{color:#fff!important;background-color:#dd0000!important} | ||||
| .w3-note{color:#000!important;background-color:#fff599!important} | ||||
| .w3-info{color:#fff!important;background-color:#0a6fc2!important} | ||||
| .w3-warning{color:#000!important;background-color:#ffb305!important} | ||||
| .w3-success{color:#fff!important;background-color:#008a00!important} | ||||
| .w3-pale-red,.w3-hover-pale-red:hover{color:#000!important;background-color:#ffdddd!important} | ||||
| .w3-pale-green,.w3-hover-pale-green:hover{color:#000!important;background-color:#ddffdd!important} | ||||
| .w3-pale-yellow,.w3-hover-pale-yellow:hover{color:#000!important;background-color:#ffffcc!important} | ||||
| .w3-pale-blue,.w3-hover-pale-blue:hover{color:#000!important;background-color:#ddffff!important} | ||||
| .w3-text-amber,.w3-hover-text-amber:hover{color:#ffc107!important} | ||||
| .w3-text-aqua,.w3-hover-text-aqua:hover{color:#00ffff!important} | ||||
| .w3-text-blue,.w3-hover-text-blue:hover{color:#2196F3!important} | ||||
| .w3-text-light-blue,.w3-hover-text-light-blue:hover{color:#87CEEB!important} | ||||
| .w3-text-brown,.w3-hover-text-brown:hover{color:#795548!important} | ||||
| .w3-text-cyan,.w3-hover-text-cyan:hover{color:#00bcd4!important} | ||||
| .w3-text-blue-grey,.w3-hover-text-blue-grey:hover,.w3-text-blue-gray,.w3-hover-text-blue-gray:hover{color:#607d8b!important} | ||||
| .w3-text-green,.w3-hover-text-green:hover{color:#4CAF50!important} | ||||
| .w3-text-light-green,.w3-hover-text-light-green:hover{color:#8bc34a!important} | ||||
| .w3-text-indigo,.w3-hover-text-indigo:hover{color:#3f51b5!important} | ||||
| .w3-text-khaki,.w3-hover-text-khaki:hover{color:#b4aa50!important} | ||||
| .w3-text-lime,.w3-hover-text-lime:hover{color:#cddc39!important} | ||||
| .w3-text-orange,.w3-hover-text-orange:hover{color:#ff9800!important} | ||||
| .w3-text-deep-orange,.w3-hover-text-deep-orange:hover{color:#ff5722!important} | ||||
| .w3-text-pink,.w3-hover-text-pink:hover{color:#e91e63!important} | ||||
| .w3-text-purple,.w3-hover-text-purple:hover{color:#9c27b0!important} | ||||
| .w3-text-deep-purple,.w3-hover-text-deep-purple:hover{color:#673ab7!important} | ||||
| .w3-text-red,.w3-hover-text-red:hover{color:#f44336!important} | ||||
| .w3-text-sand,.w3-hover-text-sand:hover{color:#fdf5e6!important} | ||||
| .w3-text-teal,.w3-hover-text-teal:hover{color:#009688!important} | ||||
| .w3-text-yellow,.w3-hover-text-yellow:hover{color:#d2be0e!important} | ||||
| .w3-text-white,.w3-hover-text-white:hover{color:#fff!important} | ||||
| .w3-text-black,.w3-hover-text-black:hover{color:#000!important} | ||||
| .w3-text-grey,.w3-hover-text-grey:hover,.w3-text-gray,.w3-hover-text-gray:hover{color:#757575!important} | ||||
| .w3-text-light-grey,.w3-hover-text-light-grey:hover,.w3-text-light-gray,.w3-hover-text-light-gray:hover{color:#f1f1f1!important} | ||||
| .w3-text-dark-grey,.w3-hover-text-dark-grey:hover,.w3-text-dark-gray,.w3-hover-text-dark-gray:hover{color:#3a3a3a!important} | ||||
| .w3-border-amber,.w3-hover-border-amber:hover{border-color:#ffc107!important} | ||||
| .w3-border-aqua,.w3-hover-border-aqua:hover{border-color:#00ffff!important} | ||||
| .w3-border-blue,.w3-hover-border-blue:hover{border-color:#2196F3!important} | ||||
| .w3-border-light-blue,.w3-hover-border-light-blue:hover{border-color:#87CEEB!important} | ||||
| .w3-border-brown,.w3-hover-border-brown:hover{border-color:#795548!important} | ||||
| .w3-border-cyan,.w3-hover-border-cyan:hover{border-color:#00bcd4!important} | ||||
| .w3-border-blue-grey,.w3-hover-border-blue-grey:hover,.w3-border-blue-gray,.w3-hover-border-blue-gray:hover{border-color:#607d8b!important} | ||||
| .w3-border-green,.w3-hover-border-green:hover{border-color:#4CAF50!important} | ||||
| .w3-border-light-green,.w3-hover-border-light-green:hover{border-color:#8bc34a!important} | ||||
| .w3-border-indigo,.w3-hover-border-indigo:hover{border-color:#3f51b5!important} | ||||
| .w3-border-khaki,.w3-hover-border-khaki:hover{border-color:#f0e68c!important} | ||||
| .w3-border-lime,.w3-hover-border-lime:hover{border-color:#cddc39!important} | ||||
| .w3-border-orange,.w3-hover-border-orange:hover{border-color:#ff9800!important} | ||||
| .w3-border-deep-orange,.w3-hover-border-deep-orange:hover{border-color:#ff5722!important} | ||||
| .w3-border-pink,.w3-hover-border-pink:hover{border-color:#e91e63!important} | ||||
| .w3-border-purple,.w3-hover-border-purple:hover{border-color:#9c27b0!important} | ||||
| .w3-border-deep-purple,.w3-hover-border-deep-purple:hover{border-color:#673ab7!important} | ||||
| .w3-border-red,.w3-hover-border-red:hover{border-color:#f44336!important} | ||||
| .w3-border-sand,.w3-hover-border-sand:hover{border-color:#fdf5e6!important} | ||||
| .w3-border-teal,.w3-hover-border-teal:hover{border-color:#009688!important} | ||||
| .w3-border-yellow,.w3-hover-border-yellow:hover{border-color:#ffeb3b!important} | ||||
| .w3-border-white,.w3-hover-border-white:hover{border-color:#fff!important} | ||||
| .w3-border-black,.w3-hover-border-black:hover{border-color:#000!important} | ||||
| .w3-border-grey,.w3-hover-border-grey:hover,.w3-border-gray,.w3-hover-border-gray:hover{border-color:#9e9e9e!important} | ||||
| .w3-border-light-grey,.w3-hover-border-light-grey:hover,.w3-border-light-gray,.w3-hover-border-light-gray:hover{border-color:#f1f1f1!important} | ||||
| .w3-border-dark-grey,.w3-hover-border-dark-grey:hover,.w3-border-dark-gray,.w3-hover-border-dark-gray:hover{border-color:#616161!important} | ||||
| .w3-border-pale-red,.w3-hover-border-pale-red:hover{border-color:#ffe7e7!important}.w3-border-pale-green,.w3-hover-border-pale-green:hover{border-color:#e7ffe7!important} | ||||
| .w3-border-pale-yellow,.w3-hover-border-pale-yellow:hover{border-color:#ffffcc!important}.w3-border-pale-blue,.w3-hover-border-pale-blue:hover{border-color:#e7ffff!important} | ||||
							
								
								
									
										42
									
								
								apps/issues/lit-all.min.js
									
									
									
									
										vendored
									
									
								
							
							
						
						
							
								
								
									
										42
									
								
								apps/journal/lit-all.min.js
									
									
									
									
										vendored
									
									
								
							
							
						
						| @@ -1,5 +1,5 @@ | ||||
| { | ||||
| 	"type": "tildefriends-app", | ||||
| 	"emoji": "🚪", | ||||
| 	"previous": "&HXCdDG8gGYXElTyEFbg85jqa6lDXNL2ENPIA9UoJNbI=.sha256" | ||||
| 	"previous": "&DJwkqNfYWtW9yBtJQMseEXm7l04Enpi+yAxZulLq9Vk=.sha256" | ||||
| } | ||||
|   | ||||
| @@ -2,8 +2,8 @@ async function main() { | ||||
| 	print(core.url); | ||||
| 	let host = core.url.match(/.*?\/\/([^:/]*)/)[1]; | ||||
| 	let port = await ssb.port(); | ||||
| 	let id = (await ssb.getServerIdentity()).substring(1); | ||||
| 	let room = `net:${host}:${port}~shs:${id}:SSB+Room+SK3TLYC2T86EHQCUHBUHASCASE18JBV24=`; | ||||
| 	let id = (await ssb.getServerIdentity()).substring(1).split('.')[0]; | ||||
| 	let room = `net:${host}:${port}~shs:${id}:SSB+Room+PSK3TLYC2T86EHQCUHBUHASCASE18JBV24=`; | ||||
| 	await app.setDocument(` | ||||
| 		<body style="color: #fff"> | ||||
| 			<h1>Server</h1> | ||||
|   | ||||
							
								
								
									
										42
									
								
								apps/sneaker/lit-all.min.js
									
									
									
									
										vendored
									
									
								
							
							
						
						| @@ -1,5 +1,5 @@ | ||||
| { | ||||
| 	"type": "tildefriends-app", | ||||
| 	"emoji": "🦀", | ||||
| 	"previous": "&YZCzXrfB6j+y0sXF4KspAibwjLsSCaMoB5rdO3mQl+Q=.sha256" | ||||
| 	"previous": "&Gic1e3jOZ7z5131jSCclbFXRpjyu8JlWJrjE7Fvn5dc=.sha256" | ||||
| } | ||||
|   | ||||
| @@ -76,6 +76,9 @@ tfrpc.register(function setHash(hash) { | ||||
| core.register('onMessage', async function (id) { | ||||
| 	await tfrpc.rpc.notifyNewMessage(id); | ||||
| }); | ||||
| core.register('onBlob', async function (id) { | ||||
| 	await tfrpc.rpc.notifyNewBlob(id); | ||||
| }); | ||||
| tfrpc.register(async function store_blob(blob) { | ||||
| 	if (Array.isArray(blob)) { | ||||
| 		blob = Uint8Array.from(blob); | ||||
| @@ -106,6 +109,15 @@ tfrpc.register(async function sync() { | ||||
| tfrpc.register(async function url() { | ||||
| 	return core.url; | ||||
| }); | ||||
| tfrpc.register(async function globalSettingsGet(key) { | ||||
| 	return core.globalSettingsGet(key); | ||||
| }); | ||||
| tfrpc.register(async function globalSettingsSet(key, value) { | ||||
| 	return core.globalSettingsSet(key, value); | ||||
| }); | ||||
| tfrpc.register(function isAdministrator() { | ||||
| 	return core.user?.credentials?.permissions?.administration; | ||||
| }); | ||||
|  | ||||
| core.register('onBroadcastsChanged', async function () { | ||||
| 	await tfrpc.rpc.set('broadcasts', await ssb.getBroadcasts()); | ||||
|   | ||||
| @@ -1,6 +1,6 @@ | ||||
| import * as tfrpc from '/static/tfrpc.js'; | ||||
| import {html, render} from './lit-all.min.js'; | ||||
| import {styles} from './tf-styles.js'; | ||||
| import {styles, generate_theme} from './tf-styles.js'; | ||||
|  | ||||
| let g_emojis; | ||||
|  | ||||
| @@ -14,23 +14,8 @@ function get_emojis() { | ||||
| 	}); | ||||
| } | ||||
|  | ||||
| async function get_recent(author) { | ||||
| 	let recent = await tfrpc.rpc.query( | ||||
| 		` | ||||
| 		SELECT DISTINCT content ->> '$.vote.expression' AS value | ||||
| 		FROM messages | ||||
| 		WHERE author = ? AND | ||||
| 		content ->> '$.type' = 'vote' | ||||
| 		ORDER BY timestamp DESC LIMIT 10 | ||||
| 	`, | ||||
| 		[author] | ||||
| 	); | ||||
| 	return recent.map((x) => x.value); | ||||
| } | ||||
|  | ||||
| export async function picker(callback, anchor, author) { | ||||
| export async function picker(callback, anchor, author, recent) { | ||||
| 	let json = await get_emojis(); | ||||
| 	let recent = await get_recent(author); | ||||
|  | ||||
| 	let div = document.createElement('div'); | ||||
| 	div.id = 'emoji_picker'; | ||||
| @@ -155,6 +140,9 @@ export async function picker(callback, anchor, author) { | ||||
| 		<style> | ||||
| 			${styles} | ||||
| 		</style> | ||||
| 		<style> | ||||
| 			${generate_theme()} | ||||
| 		</style> | ||||
| 		<div | ||||
| 			class="w3-modal" | ||||
| 			style="display: block; box-sizing: border-box; z-index: 10" | ||||
|   | ||||
							
								
								
									
										42
									
								
								apps/ssb/lit-all.min.js
									
									
									
									
										vendored
									
									
								
							
							
						
						| @@ -12,12 +12,14 @@ import * as tf_tab_news from './tf-tab-news.js'; | ||||
| import * as tf_tab_news_feed from './tf-tab-news-feed.js'; | ||||
| import * as tf_tab_search from './tf-tab-search.js'; | ||||
| import * as tf_tab_connections from './tf-tab-connections.js'; | ||||
| import * as tf_tab_query from './tf-tab-query.js'; | ||||
| import * as tf_tag from './tf-tag.js'; | ||||
| import * as tf_styles from './tf-styles.js'; | ||||
|  | ||||
| window.addEventListener('load', function () { | ||||
| 	let style = document.createElement('style'); | ||||
| 	style.innerText = tf_styles.styles; | ||||
| 	Promise.resolve(tf_styles.generate_theme()).then(function (x) { | ||||
| 		style.innerText += x; | ||||
| 	}); | ||||
| 	document.body.appendChild(style); | ||||
| }); | ||||
|   | ||||
| @@ -1,6 +1,6 @@ | ||||
| import {LitElement, html, css, guard, until} from './lit-all.min.js'; | ||||
| import * as tfrpc from '/static/tfrpc.js'; | ||||
| import {styles} from './tf-styles.js'; | ||||
| import {styles, generate_theme} from './tf-styles.js'; | ||||
|  | ||||
| class TfElement extends LitElement { | ||||
| 	static get properties() { | ||||
| @@ -11,6 +11,7 @@ class TfElement extends LitElement { | ||||
| 			broadcasts: {type: Array}, | ||||
| 			connections: {type: Array}, | ||||
| 			loading: {type: Boolean}, | ||||
| 			loading_about: {type: Number}, | ||||
| 			loaded: {type: Boolean}, | ||||
| 			following: {type: Array}, | ||||
| 			users: {type: Object}, | ||||
| @@ -20,7 +21,13 @@ class TfElement extends LitElement { | ||||
| 			channels_latest: {type: Object}, | ||||
| 			guest: {type: Boolean}, | ||||
| 			url: {type: String}, | ||||
| 			private_closed: {type: Object}, | ||||
| 			private_messages: {type: Array}, | ||||
| 			grouped_private_messages: {type: Object}, | ||||
| 			recent_reactions: {type: Array}, | ||||
| 			is_administrator: {type: Boolean}, | ||||
| 			stay_connected: {type: Boolean}, | ||||
| 			progress: {type: Number}, | ||||
| 		}; | ||||
| 	} | ||||
|  | ||||
| @@ -36,11 +43,14 @@ class TfElement extends LitElement { | ||||
| 		this.following = []; | ||||
| 		this.users = {}; | ||||
| 		this.loaded = false; | ||||
| 		this.loading_about = 0; | ||||
| 		this.channels = []; | ||||
| 		this.channels_unread = {}; | ||||
| 		this.channels_latest = {}; | ||||
| 		this.loading_latest = 0; | ||||
| 		this.loading_latest_scheduled = 0; | ||||
| 		this.recent_reactions = []; | ||||
| 		this.private_closed = {}; | ||||
| 		tfrpc.rpc.getBroadcasts().then((b) => { | ||||
| 			self.broadcasts = b || []; | ||||
| 		}); | ||||
| @@ -50,10 +60,22 @@ class TfElement extends LitElement { | ||||
| 		tfrpc.rpc.getHash().then((hash) => self.set_hash(hash)); | ||||
| 		tfrpc.register(function hashChanged(hash) { | ||||
| 			self.set_hash(hash); | ||||
| 			self.reset_progress(); | ||||
| 		}); | ||||
| 		tfrpc.register(async function notifyNewMessage(id) { | ||||
| 			await self.fetch_new_message(id); | ||||
| 		}); | ||||
| 		tfrpc.register(async function notifyNewBlob(id) { | ||||
| 			window.dispatchEvent( | ||||
| 				new CustomEvent('blob-stored', { | ||||
| 					bubbles: true, | ||||
| 					composed: true, | ||||
| 					detail: { | ||||
| 						id: id, | ||||
| 					}, | ||||
| 				}) | ||||
| 			); | ||||
| 		}); | ||||
| 		tfrpc.register(function set(name, value) { | ||||
| 			if (name === 'broadcasts') { | ||||
| 				self.broadcasts = value; | ||||
| @@ -69,13 +91,30 @@ class TfElement extends LitElement { | ||||
| 	async initial_load() { | ||||
| 		let whoami = await tfrpc.rpc.getActiveIdentity(); | ||||
| 		let ids = (await tfrpc.rpc.getIdentities()) || []; | ||||
| 		this.is_administrator = await tfrpc.rpc.isAdministrator(); | ||||
| 		this.stay_connected = | ||||
| 			this.is_administrator && | ||||
| 			(await tfrpc.rpc.globalSettingsGet('stay_connected')); | ||||
| 		this.url = await tfrpc.rpc.url(); | ||||
| 		this.whoami = whoami ?? (ids.length ? ids[0] : undefined); | ||||
| 		this.guest = !this.whoami?.length; | ||||
| 		this.ids = ids; | ||||
| 		let private_closed = | ||||
| 			(await tfrpc.rpc.databaseGet('private_closed')) ?? '{}'; | ||||
| 		this.private_closed = JSON.parse(private_closed); | ||||
| 		await this.load_channels(); | ||||
| 	} | ||||
|  | ||||
| 	async close_private_chat(event) { | ||||
| 		let update = {}; | ||||
| 		update[event.detail.key] = true; | ||||
| 		this.private_closed = Object.assign(update, this.private_closed); | ||||
| 		await tfrpc.rpc.databaseSet( | ||||
| 			'private_closed', | ||||
| 			JSON.stringify(this.private_closed) | ||||
| 		); | ||||
| 	} | ||||
|  | ||||
| 	async load_channels() { | ||||
| 		let channels = await tfrpc.rpc.query( | ||||
| 			` | ||||
| @@ -123,8 +162,34 @@ class TfElement extends LitElement { | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	visible_private() { | ||||
| 		if (!this.grouped_private_messages || !this.private_closed) { | ||||
| 			return []; | ||||
| 		} | ||||
| 		let self = this; | ||||
| 		return Object.fromEntries( | ||||
| 			Object.entries(this.grouped_private_messages).filter(([key, value]) => { | ||||
| 				let channel = '🔐' + [...new Set(JSON.parse(key))].sort().join(','); | ||||
| 				let grouped_latest = Math.max(...value.map((x) => x.rowid)); | ||||
| 				return ( | ||||
| 					!self.private_closed[key] || | ||||
| 					self.channels_unread[channel] === undefined || | ||||
| 					grouped_latest > self.channels_unread[channel] | ||||
| 				); | ||||
| 			}) | ||||
| 		); | ||||
| 	} | ||||
|  | ||||
| 	next_channel(delta) { | ||||
| 		let channel_names = ['', '@', '🔐', ...this.channels.map((x) => '#' + x)]; | ||||
| 		let channel_names = [ | ||||
| 			'', | ||||
| 			'@', | ||||
| 			'👍', | ||||
| 			...Object.keys(this.visible_private()) | ||||
| 				.sort() | ||||
| 				.map((x) => '🔐' + JSON.parse(x).join(',')), | ||||
| 			...this.channels.map((x) => '#' + x), | ||||
| 		]; | ||||
| 		let index = channel_names.indexOf(this.hash.substring(1)); | ||||
| 		index = index != -1 ? index + delta : 0; | ||||
| 		tfrpc.rpc.setHash( | ||||
| @@ -141,16 +206,15 @@ class TfElement extends LitElement { | ||||
| 			this.tab = 'search'; | ||||
| 		} else if (this.hash === '#connections') { | ||||
| 			this.tab = 'connections'; | ||||
| 		} else if (this.hash.startsWith('#sql=')) { | ||||
| 			this.tab = 'query'; | ||||
| 		} else { | ||||
| 			this.tab = 'news'; | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	async fetch_about(following, users) { | ||||
| 		this.loading_about++; | ||||
| 		let ids = Object.keys(following).sort(); | ||||
| 		const k_cache_version = 1; | ||||
| 		const k_cache_version = 3; | ||||
| 		let cache = await tfrpc.rpc.databaseGet('about'); | ||||
| 		let original_cache = cache; | ||||
| 		cache = cache ? JSON.parse(cache) : {}; | ||||
| @@ -158,81 +222,86 @@ class TfElement extends LitElement { | ||||
| 			cache = { | ||||
| 				version: k_cache_version, | ||||
| 				about: {}, | ||||
| 				last_row_id: 0, | ||||
| 			}; | ||||
| 		} | ||||
| 		let max_row_id = ( | ||||
| 			await tfrpc.rpc.query( | ||||
| 				` | ||||
| 					SELECT MAX(rowid) AS max_row_id FROM messages | ||||
| 				`, | ||||
| 				[] | ||||
| 			) | ||||
| 		)[0].max_row_id; | ||||
|  | ||||
| 		let ids_out_of_date = ids.filter( | ||||
| 			(x) => | ||||
| 				(users[x]?.seq && !cache.about[x]?.seq) || | ||||
| 				(users[x]?.seq && users[x]?.seq > cache.about[x].seq) | ||||
| 		); | ||||
|  | ||||
| 		for (let id of Object.keys(cache.about)) { | ||||
| 			if (ids.indexOf(id) == -1) { | ||||
| 				delete cache.about[id]; | ||||
| 			} else { | ||||
| 				users[id] = Object.assign(cache.about[id], users[id] || {}); | ||||
| 			} | ||||
| 		} | ||||
|  | ||||
| 		let abouts = await tfrpc.rpc.query( | ||||
| 			` | ||||
| 				SELECT | ||||
| 					messages.author, json(messages.content) AS content, messages.sequence | ||||
| 				FROM | ||||
| 					messages, | ||||
| 					json_each(?1) AS following | ||||
| 				WHERE | ||||
| 					messages.author = following.value AND | ||||
| 					messages.content ->> 'type' = 'about' AND | ||||
| 					messages.rowid > ?3 AND | ||||
| 					messages.rowid <= ?4 | ||||
| 				UNION | ||||
| 				SELECT | ||||
| 					messages.author, json(messages.content) AS content, messages.sequence | ||||
| 				FROM | ||||
| 					messages, | ||||
| 					json_each(?2) AS following | ||||
| 				WHERE | ||||
| 					messages.author = following.value AND | ||||
| 					messages.content ->> 'type' = 'about' AND | ||||
| 					messages.rowid <= ?4 | ||||
| 				ORDER BY messages.author, messages.sequence | ||||
| 			`, | ||||
| 			[ | ||||
| 				JSON.stringify(ids.filter((id) => cache.about[id])), | ||||
| 				JSON.stringify(ids.filter((id) => !cache.about[id])), | ||||
| 				cache.last_row_id, | ||||
| 				max_row_id, | ||||
| 			] | ||||
| 		console.log( | ||||
| 			'loading about for', | ||||
| 			ids.length, | ||||
| 			'accounts', | ||||
| 			ids_out_of_date.length, | ||||
| 			'out of date' | ||||
| 		); | ||||
| 		for (let about of abouts) { | ||||
| 			let content = JSON.parse(about.content); | ||||
| 			if (content.about === about.author) { | ||||
| 				delete content.type; | ||||
| 				delete content.about; | ||||
| 				cache.about[about.author] = Object.assign( | ||||
| 					cache.about[about.author] || {}, | ||||
| 					content | ||||
| 		if (ids_out_of_date.length) { | ||||
| 			try { | ||||
| 				let rows = await tfrpc.rpc.query( | ||||
| 					` | ||||
| 						SELECT all_abouts.author, json(json_group_object(all_abouts.key, all_abouts.value)) AS about | ||||
| 						FROM ( | ||||
| 							SELECT | ||||
| 								messages.author, | ||||
| 								fields.key, | ||||
| 								RANK() OVER (PARTITION BY messages.author, fields.key ORDER BY messages.sequence DESC) AS rank, | ||||
| 								fields.value | ||||
| 							FROM messages JOIN json_each(messages.content) AS fields | ||||
| 							WHERE | ||||
| 								messages.content ->> '$.type' = 'about' AND | ||||
| 								messages.content ->> '$.about' = messages.author AND | ||||
| 								NOT fields.key IN ('about', 'type')) all_abouts | ||||
| 						JOIN json_each(?) AS following ON all_abouts.author = following.value | ||||
| 						WHERE rank = 1 | ||||
| 						GROUP BY all_abouts.author | ||||
| 					`, | ||||
| 					[JSON.stringify(ids_out_of_date)] | ||||
| 				); | ||||
| 				users = users || {}; | ||||
| 				for (let row of rows) { | ||||
| 					users[row.author] = Object.assign( | ||||
| 						users[row.author] || {}, | ||||
| 						JSON.parse(row.about) | ||||
| 					); | ||||
| 					cache.about[row.author] = Object.assign( | ||||
| 						{seq: users[row.author].seq}, | ||||
| 						JSON.parse(row.about) | ||||
| 					); | ||||
| 				} | ||||
| 			} catch (e) { | ||||
| 				console.log(e); | ||||
| 			} | ||||
| 		} | ||||
| 		cache.last_row_id = max_row_id; | ||||
|  | ||||
| 		for (let id of ids_out_of_date) { | ||||
| 			if (!cache.about[id]?.seq) { | ||||
| 				cache.about[id] = Object.assign(cache.about[id] ?? {}, { | ||||
| 					seq: users[id]?.seq ?? 0, | ||||
| 				}); | ||||
| 			} | ||||
| 		} | ||||
|  | ||||
| 		this.loading_about--; | ||||
|  | ||||
| 		let new_cache = JSON.stringify(cache); | ||||
| 		if (new_cache !== original_cache) { | ||||
| 		if (new_cache != original_cache) { | ||||
| 			let start_time = new Date(); | ||||
| 			tfrpc.rpc.databaseSet('about', new_cache).then(function () { | ||||
| 				console.log('saving about took', (new Date() - start_time) / 1000); | ||||
| 			}); | ||||
| 		} | ||||
| 		users = users || {}; | ||||
| 		for (let id of Object.keys(cache.about)) { | ||||
| 			users[id] = Object.assign( | ||||
| 				{follow_depth: following[id]?.d}, | ||||
| 				users[id] || {}, | ||||
| 				cache.about[id] | ||||
| 			); | ||||
| 		} | ||||
|  | ||||
| 		return Object.assign({}, users); | ||||
| 	} | ||||
|  | ||||
| @@ -292,11 +361,7 @@ class TfElement extends LitElement { | ||||
| 				ranges.push([i, Math.min(i + k_chunk_size, latest), true]); | ||||
| 			} | ||||
| 			for (let i = cache.range[0]; i >= 0; i -= k_chunk_size) { | ||||
| 				ranges.push([ | ||||
| 					Math.max(i - k_chunk_size, 0), | ||||
| 					Math.min(cache.range[0], i + k_chunk_size), | ||||
| 					false, | ||||
| 				]); | ||||
| 				ranges.push([Math.max(i - k_chunk_size, 0), i, false]); | ||||
| 			} | ||||
| 		} else { | ||||
| 			for (let i = 0; i < latest; i += k_chunk_size) { | ||||
| @@ -312,7 +377,7 @@ class TfElement extends LitElement { | ||||
| 							messages.rowid > ?1 AND | ||||
| 							messages.rowid <= ?2 AND | ||||
| 							json(messages.content) LIKE '"%' | ||||
| 						ORDER BY sequence DESC | ||||
| 						ORDER BY messages.rowid DESC | ||||
| 					`, | ||||
| 				[range[0], range[1]] | ||||
| 			); | ||||
| @@ -338,51 +403,125 @@ class TfElement extends LitElement { | ||||
| 		return [cache.latest, cache.messages]; | ||||
| 	} | ||||
|  | ||||
| 	async query_timed(sql, args) { | ||||
| 		let start = new Date(); | ||||
| 		let result = await tfrpc.rpc.query(sql, args); | ||||
| 		let end = new Date(); | ||||
| 		console.log((end - start) / 1000, sql.replaceAll(/\s+/g, ' ').trim()); | ||||
| 		return result; | ||||
| 	} | ||||
|  | ||||
| 	async group_private_messages(messages) { | ||||
| 		let groups = {}; | ||||
| 		let result = await this.decrypt( | ||||
| 			await tfrpc.rpc.query( | ||||
| 				` | ||||
| 				SELECT messages.rowid, messages.id, author, timestamp, json(content) AS content | ||||
| 				FROM messages | ||||
| 				JOIN json_each(?) AS ids | ||||
| 				WHERE messages.id = ids.value | ||||
| 				ORDER BY timestamp DESC | ||||
| 			`, | ||||
| 				[JSON.stringify(messages)] | ||||
| 			) | ||||
| 		); | ||||
| 		for (let message of result) { | ||||
| 			let key = JSON.stringify( | ||||
| 				[ | ||||
| 					...new Set( | ||||
| 						message?.decrypted?.recps?.filter((x) => x != this.whoami) | ||||
| 					), | ||||
| 				].sort() ?? [] | ||||
| 			); | ||||
| 			if (!groups[key]) { | ||||
| 				groups[key] = []; | ||||
| 			} | ||||
| 			groups[key].push(message); | ||||
| 		} | ||||
| 		return groups; | ||||
| 	} | ||||
|  | ||||
| 	async load_channels_latest(following) { | ||||
| 		let start_time = new Date(); | ||||
| 		let latest_private = this.get_latest_private(following); | ||||
| 		let channels = await tfrpc.rpc.query( | ||||
| 			` | ||||
| 			SELECT channels.value AS channel, MAX(messages.rowid) AS rowid FROM messages | ||||
| 			JOIN json_each(?1) AS channels ON messages.content ->> 'channel' = channels.value | ||||
| 			JOIN json_each(?2) AS following ON messages.author = following.value | ||||
| 			WHERE | ||||
| 				messages.content ->> 'type' = 'post' AND | ||||
| 				messages.content ->> 'root' IS NULL AND | ||||
| 				messages.author != ?4 | ||||
| 			GROUP by channel | ||||
| 			UNION | ||||
| 			SELECT '' AS channel, MAX(messages.rowid) AS rowid FROM messages | ||||
| 			JOIN json_each(?2) AS following ON messages.author = following.value | ||||
| 			WHERE | ||||
| 				messages.content ->> 'type' = 'post' AND | ||||
| 				messages.content ->> 'root' IS NULL AND | ||||
| 				messages.author != ?4 | ||||
| 			UNION | ||||
| 			SELECT '@' AS channel, MAX(messages.rowid) AS rowid FROM messages_fts(?3) | ||||
| 			JOIN messages ON messages.rowid = messages_fts.rowid | ||||
| 			JOIN json_each(?2) AS following ON messages.author = following.value | ||||
| 			WHERE messages.author != ?4 | ||||
| 		`, | ||||
| 			[ | ||||
| 				JSON.stringify(this.channels), | ||||
| 				JSON.stringify(following), | ||||
| 				'"' + this.whoami.replace('"', '""') + '"', | ||||
| 				this.whoami, | ||||
| 			] | ||||
| 		); | ||||
| 		this.channels_latest = Object.fromEntries( | ||||
| 			channels.map((x) => [x.channel, x.rowid]) | ||||
| 		); | ||||
| 		const k_args = [ | ||||
| 			JSON.stringify(this.channels), | ||||
| 			JSON.stringify(following), | ||||
| 			'"' + this.whoami.replace('"', '""') + '"', | ||||
| 			this.whoami, | ||||
| 		]; | ||||
| 		let channels = ( | ||||
| 			await Promise.all([ | ||||
| 				this.query_timed( | ||||
| 					` | ||||
| 					SELECT channels.value AS channel, MAX(messages.rowid) AS rowid FROM messages | ||||
| 					JOIN json_each(?1) AS channels ON messages.content ->> 'channel' = channels.value | ||||
| 					JOIN json_each(?2) AS following ON messages.author = following.value | ||||
| 					WHERE | ||||
| 						messages.content ->> 'type' = 'post' AND | ||||
| 						messages.content ->> 'root' IS NULL AND | ||||
| 						messages.author != ?4 | ||||
| 					GROUP by channel | ||||
| 				`, | ||||
| 					k_args | ||||
| 				), | ||||
| 				this.query_timed( | ||||
| 					` | ||||
| 					SELECT channels.value AS channel, MAX(messages.rowid) AS rowid FROM messages | ||||
| 					JOIN messages_refs ON messages.id = messages_refs.message | ||||
| 					JOIN json_each(?1) AS channels ON messages_refs.ref = '#' || channels.value | ||||
| 					JOIN json_each(?2) AS following ON messages.author = following.value | ||||
| 					WHERE | ||||
| 						messages.content ->> 'type' = 'post' AND | ||||
| 						messages.content ->> 'root' IS NULL AND | ||||
| 						messages.author != ?4 | ||||
| 					GROUP by channel | ||||
| 				`, | ||||
| 					k_args | ||||
| 				), | ||||
| 				this.query_timed( | ||||
| 					` | ||||
| 					SELECT '' AS channel, MAX(messages.rowid) AS rowid FROM messages | ||||
| 					JOIN json_each(?2) AS following ON messages.author = following.value | ||||
| 					WHERE | ||||
| 						messages.content ->> 'type' = 'post' AND | ||||
| 						messages.content ->> 'root' IS NULL AND | ||||
| 						messages.author != ?4 | ||||
| 				`, | ||||
| 					k_args | ||||
| 				), | ||||
| 				this.query_timed( | ||||
| 					` | ||||
| 					SELECT '@' AS channel, MAX(messages.rowid) AS rowid FROM messages_fts(?3) | ||||
| 					JOIN messages ON messages.rowid = messages_fts.rowid | ||||
| 					JOIN json_each(?2) AS following ON messages.author = following.value | ||||
| 					WHERE messages.author != ?4 | ||||
| 				`, | ||||
| 					k_args | ||||
| 				), | ||||
| 			]) | ||||
| 		).flat(); | ||||
| 		let latest = {}; | ||||
| 		for (let row of channels) { | ||||
| 			if (!latest[row.channel]) { | ||||
| 				latest[row.channel] = row.rowid; | ||||
| 			} else { | ||||
| 				latest[row.channel] = Math.max(row.rowid, latest[row.channel]); | ||||
| 			} | ||||
| 		} | ||||
| 		this.channels_latest = latest; | ||||
| 		console.log('channels took', (new Date() - start_time) / 1000.0); | ||||
| 		let self = this; | ||||
| 		start_time = new Date(); | ||||
| 		latest_private.then(function (latest) { | ||||
| 		latest_private.then(async function (latest) { | ||||
| 			self.channels_latest = Object.assign({}, self.channels_latest, { | ||||
| 				'🔐': latest[0], | ||||
| 			}); | ||||
| 			console.log('private took', (new Date() - start_time) / 1000.0); | ||||
| 			self.private_messages = latest[1]; | ||||
| 			self.grouped_private_messages = await self.group_private_messages( | ||||
| 				latest[1] | ||||
| 			); | ||||
| 		}); | ||||
| 	} | ||||
|  | ||||
| @@ -391,7 +530,28 @@ class TfElement extends LitElement { | ||||
| 		this.schedule_load_latest(); | ||||
| 	} | ||||
|  | ||||
| 	reset_progress() { | ||||
| 		if (this.progress === undefined) { | ||||
| 			this._progress_start = new Date(); | ||||
| 			requestAnimationFrame(this.update_progress.bind(this)); | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	update_progress() { | ||||
| 		if ( | ||||
| 			!this.loading_latest && | ||||
| 			!this.loading_latest_scheduled && | ||||
| 			!this.shadowRoot.getElementById('tf-tab-news')?.is_loading() | ||||
| 		) { | ||||
| 			this.progress = undefined; | ||||
| 			return; | ||||
| 		} | ||||
| 		this.progress = (new Date() - this._progress_start).valueOf(); | ||||
| 		requestAnimationFrame(this.update_progress.bind(this)); | ||||
| 	} | ||||
|  | ||||
| 	schedule_load_latest() { | ||||
| 		this.reset_progress(); | ||||
| 		if (!this.loading_latest) { | ||||
| 			this.shadowRoot.getElementById('tf-tab-news')?.load_latest(); | ||||
| 			this.load(); | ||||
| @@ -411,43 +571,59 @@ class TfElement extends LitElement { | ||||
| 			[JSON.stringify(Object.keys(users))] | ||||
| 		); | ||||
| 		for (let row of info) { | ||||
| 			users[row.author].seq = row.max_seq; | ||||
| 			users[row.author].ts = row.max_ts; | ||||
| 			users[row.author] = Object.assign(users[row.author], { | ||||
| 				seq: row.max_sequence, | ||||
| 				ts: row.max_ts, | ||||
| 			}); | ||||
| 		} | ||||
| 		return users; | ||||
| 	} | ||||
|  | ||||
| 	async load_recent_reactions() { | ||||
| 		this.recent_reactions = ( | ||||
| 			await tfrpc.rpc.query( | ||||
| 				` | ||||
| 			SELECT DISTINCT content ->> '$.vote.expression' AS value | ||||
| 			FROM messages | ||||
| 			WHERE author = ? AND | ||||
| 			content ->> '$.type' = 'vote' | ||||
| 			ORDER BY timestamp DESC LIMIT 10 | ||||
| 		`, | ||||
| 				[this.whoami] | ||||
| 			) | ||||
| 		).map((x) => x.value); | ||||
| 	} | ||||
|  | ||||
| 	async load() { | ||||
| 		this.loading_latest = true; | ||||
| 		this.reset_progress(); | ||||
| 		try { | ||||
| 			let start_time = new Date(); | ||||
| 			let whoami = this.whoami; | ||||
| 			let following = await tfrpc.rpc.following([whoami], 2); | ||||
| 			let old_users = this.users ?? {}; | ||||
| 			let users = {}; | ||||
| 			let by_count = []; | ||||
| 			for (let [id, v] of Object.entries(following)) { | ||||
| 				users[id] = { | ||||
| 					following: v.of, | ||||
| 					blocking: v.ob, | ||||
| 					followed: v.if, | ||||
| 					blocked: v.ib, | ||||
| 				}; | ||||
| 				users[id] = Object.assign( | ||||
| 					{ | ||||
| 						following: v.of, | ||||
| 						blocking: v.ob, | ||||
| 						followed: v.if, | ||||
| 						blocked: v.ib, | ||||
| 						follow_depth: following[id]?.d, | ||||
| 					}, | ||||
| 					old_users[id] | ||||
| 				); | ||||
| 				by_count.push({count: v.of, id: id}); | ||||
| 			} | ||||
| 			let reactions = this.load_recent_reactions(); | ||||
| 			this.load_channels_latest(Object.keys(following)); | ||||
| 			this.channels_unread = JSON.parse( | ||||
| 				(await tfrpc.rpc.databaseGet('unread')) ?? '{}' | ||||
| 			); | ||||
| 			this.following = Object.keys(following); | ||||
| 			let about_start_time = new Date(); | ||||
| 			users = await this.fetch_about(following, users); | ||||
| 			console.log( | ||||
| 				'about took', | ||||
| 				(new Date() - about_start_time) / 1000.0, | ||||
| 				'seconds for', | ||||
| 				Object.keys(users).length, | ||||
| 				'users' | ||||
| 			); | ||||
| 			start_time = new Date(); | ||||
| 			users = await this.fetch_user_info(users); | ||||
| 			console.log( | ||||
| @@ -456,9 +632,22 @@ class TfElement extends LitElement { | ||||
| 				'seconds' | ||||
| 			); | ||||
| 			this.users = users; | ||||
|  | ||||
| 			let self = this; | ||||
| 			this.fetch_about(following, users).then(function (result) { | ||||
| 				self.users = result; | ||||
| 				console.log( | ||||
| 					'about took', | ||||
| 					(new Date() - about_start_time) / 1000.0, | ||||
| 					'seconds for', | ||||
| 					Object.keys(users).length, | ||||
| 					'users' | ||||
| 				); | ||||
| 			}); | ||||
| 			console.log( | ||||
| 				`load finished ${whoami} => ${this.whoami} in ${(new Date() - start_time) / 1000}` | ||||
| 			); | ||||
| 			await reactions; | ||||
| 			this.whoami = whoami; | ||||
| 			this.loaded = whoami; | ||||
| 		} finally { | ||||
| @@ -509,13 +698,22 @@ class TfElement extends LitElement { | ||||
| 					whoami=${this.whoami} | ||||
| 					.users=${this.users} | ||||
| 					hash=${this.hash} | ||||
| 					?loading=${this.loading} | ||||
| 					?loading=${this.loading || this.loading_about != 0} | ||||
| 					.channels=${this.channels} | ||||
| 					.channels_latest=${this.channels_latest} | ||||
| 					.channels_unread=${this.channels_unread} | ||||
| 					@channelsetunread=${this.channel_set_unread} | ||||
| 					@refresh=${this.refresh} | ||||
| 					@toggle_stay_connected=${this.toggle_stay_connected} | ||||
| 					@loadmessages=${this.reset_progress} | ||||
| 					@closeprivatechat=${this.close_private_chat} | ||||
| 					.connections=${this.connections} | ||||
| 					.private_messages=${this.private_messages} | ||||
| 					.visible_private_messages=${this.visible_private()} | ||||
| 					.grouped_private_messages=${this.grouped_private_messages} | ||||
| 					.recent_reactions=${this.recent_reactions} | ||||
| 					?is_administrator=${this.is_administrator} | ||||
| 					?stay_connected=${this.stay_connected} | ||||
| 				></tf-tab-news> | ||||
| 			`; | ||||
| 		} else if (this.tab === 'connections') { | ||||
| @@ -537,28 +735,16 @@ class TfElement extends LitElement { | ||||
| 						: null} | ||||
| 				></tf-tab-search> | ||||
| 			`; | ||||
| 		} else if (this.tab === 'query') { | ||||
| 			return html` | ||||
| 				<tf-tab-query | ||||
| 					.following=${this.following} | ||||
| 					whoami=${this.whoami} | ||||
| 					.users=${this.users} | ||||
| 					query=${this.hash?.startsWith('#sql=') | ||||
| 						? decodeURIComponent(this.hash.substring(5)) | ||||
| 						: null} | ||||
| 				></tf-tab-query> | ||||
| 			`; | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	async set_tab(tab) { | ||||
| 		this.tab = tab; | ||||
| 		if (tab === 'news') { | ||||
| 			this.schedule_load_latest(); | ||||
| 			await tfrpc.rpc.setHash('#'); | ||||
| 		} else if (tab === 'connections') { | ||||
| 			await tfrpc.rpc.setHash('#connections'); | ||||
| 		} else if (tab === 'query') { | ||||
| 			await tfrpc.rpc.setHash('#sql='); | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| @@ -566,6 +752,29 @@ class TfElement extends LitElement { | ||||
| 		tfrpc.rpc.sync(); | ||||
| 	} | ||||
|  | ||||
| 	async toggle_stay_connected() { | ||||
| 		let stay_connected = await tfrpc.rpc.globalSettingsGet('stay_connected'); | ||||
| 		let new_stay_connected = !this.stay_connected; | ||||
| 		try { | ||||
| 			if (new_stay_connected != stay_connected) { | ||||
| 				await tfrpc.rpc.globalSettingsSet('stay_connected', new_stay_connected); | ||||
| 			} | ||||
| 		} finally { | ||||
| 			this.stay_connected = await tfrpc.rpc.globalSettingsGet('stay_connected'); | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	async pick_color() { | ||||
| 		let input = document.createElement('input'); | ||||
| 		input.type = 'color'; | ||||
| 		input.value = (await tfrpc.rpc.localStorageGet('color')) ?? '#ff0000'; | ||||
| 		input.addEventListener('change', async function () { | ||||
| 			await tfrpc.rpc.localStorageSet('color', input.value); | ||||
| 			window.location.reload(); | ||||
| 		}); | ||||
| 		input.click(); | ||||
| 	} | ||||
|  | ||||
| 	render() { | ||||
| 		let self = this; | ||||
|  | ||||
| @@ -580,7 +789,6 @@ class TfElement extends LitElement { | ||||
| 			'📰': 'news', | ||||
| 			'📡': 'connections', | ||||
| 			'🔍': 'search', | ||||
| 			'👩💻': 'query', | ||||
| 		}; | ||||
|  | ||||
| 		let tabs = html` | ||||
| @@ -588,14 +796,25 @@ class TfElement extends LitElement { | ||||
| 				class="w3-bar w3-theme-l1" | ||||
| 				style="position: static; top: 0; z-index: 10" | ||||
| 			> | ||||
| 				<button | ||||
| 					class=${'w3-bar-item w3-button w3-circle w3-ripple' + | ||||
| 					(this.connections?.some((x) => x.flags.one_shot) ? ' w3-spin' : '')} | ||||
| 					style="width: 1.5em; height: 1.5em; padding: 8px" | ||||
| 					@click=${this.refresh} | ||||
| 				> | ||||
| 					↻ | ||||
| 				</button> | ||||
| 				${this.is_administrator | ||||
| 					? html` | ||||
| 							<button | ||||
| 								class=${'w3-bar-item w3-button w3-circle w3-ripple' + | ||||
| 								(this.connections?.some((x) => x.flags.one_shot) | ||||
| 									? ' w3-spin' | ||||
| 									: '')} | ||||
| 								@click=${this.refresh} | ||||
| 							> | ||||
| 								↻ | ||||
| 							</button> | ||||
| 							<button | ||||
| 								class="w3-bar-item w3-button w3-ripple" | ||||
| 								@click=${this.toggle_stay_connected} | ||||
| 							> | ||||
| 								${this.stay_connected ? '🔗' : '⛓️💥'} | ||||
| 							</button> | ||||
| 						` | ||||
| 					: undefined} | ||||
| 				${Object.entries(k_tabs).map( | ||||
| 					([k, v]) => html` | ||||
| 						<button | ||||
| @@ -612,6 +831,12 @@ class TfElement extends LitElement { | ||||
| 						</button> | ||||
| 					` | ||||
| 				)} | ||||
| 				<button | ||||
| 					class="w3-bar-item w3-button w3-right" | ||||
| 					@click=${this.pick_color} | ||||
| 				> | ||||
| 					🎨<span class="w3-hide-small">Color</span> | ||||
| 				</button> | ||||
| 			</div> | ||||
| 		`; | ||||
| 		let contents = this.guest | ||||
| @@ -635,11 +860,26 @@ class TfElement extends LitElement { | ||||
| 						Loading... | ||||
| 					</div>` | ||||
| 				: this.render_tab(); | ||||
| 		let progress = | ||||
| 			this.progress !== undefined | ||||
| 				? html` | ||||
| 						<div style="position: absolute; width: 100%" id="progress"> | ||||
| 							<div | ||||
| 								class="w3-theme-l3" | ||||
| 								style=${`height: 4px; position: absolute; right: ${Math.cos(this.progress / 250) > 0 ? 'auto' : '0'}; width: ${50 * Math.sin(this.progress / 250) + 50}%`} | ||||
| 							></div> | ||||
| 						</div> | ||||
| 					` | ||||
| 				: undefined; | ||||
| 		return html` | ||||
| 			<style> | ||||
| 				${generate_theme()} | ||||
| 			</style> | ||||
| 			<div | ||||
| 				style="width: 100vw; min-height: 100vh; height: 100vh; display: flex; flex-direction: column" | ||||
| 				class="w3-theme-dark" | ||||
| 			> | ||||
| 				${progress} | ||||
| 				<div style="flex: 0 0">${tabs}</div> | ||||
| 				<div style="flex: 1 1; overflow: auto; contain: layout"> | ||||
| 					${contents} | ||||
|   | ||||
| @@ -1,7 +1,7 @@ | ||||
| import {LitElement, html, unsafeHTML, live} from './lit-all.min.js'; | ||||
| import * as tfutils from './tf-utils.js'; | ||||
| import * as tfrpc from '/static/tfrpc.js'; | ||||
| import {styles} from './tf-styles.js'; | ||||
| import {styles, generate_theme} from './tf-styles.js'; | ||||
| import Tribute from './tribute.esm.js'; | ||||
|  | ||||
| class TfComposeElement extends LitElement { | ||||
| @@ -16,6 +16,7 @@ class TfComposeElement extends LitElement { | ||||
| 			author: {type: String}, | ||||
| 			channel: {type: String}, | ||||
| 			new_thread: {type: Boolean}, | ||||
| 			recipients: {type: Array}, | ||||
| 		}; | ||||
| 	} | ||||
|  | ||||
| @@ -91,7 +92,9 @@ class TfComposeElement extends LitElement { | ||||
| 				bubbles: true, | ||||
| 				composed: true, | ||||
| 				detail: { | ||||
| 					id: this.branch, | ||||
| 					id: | ||||
| 						this.branch ?? | ||||
| 						(this.recipients ? this.recipients.join(',') : undefined), | ||||
| 					draft: draft, | ||||
| 				}, | ||||
| 			}) | ||||
| @@ -291,7 +294,7 @@ class TfComposeElement extends LitElement { | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	firstUpdated() { | ||||
| 	get_values() { | ||||
| 		let values = Object.entries(this.users).map((x) => ({ | ||||
| 			key: x[1].name ?? x[0], | ||||
| 			value: x[0], | ||||
| @@ -307,11 +310,15 @@ class TfComposeElement extends LitElement { | ||||
| 				values | ||||
| 			); | ||||
| 		} | ||||
| 		return values; | ||||
| 	} | ||||
|  | ||||
| 	firstUpdated() { | ||||
| 		let tribute = new Tribute({ | ||||
| 			iframe: this.shadowRoot, | ||||
| 			collection: [ | ||||
| 				{ | ||||
| 					values: values, | ||||
| 					values: this.get_values(), | ||||
| 					selectTemplate: function (item) { | ||||
| 						return item | ||||
| 							? `[@${item.original.key}](${item.original.value})` | ||||
| @@ -330,6 +337,7 @@ class TfComposeElement extends LitElement { | ||||
| 			], | ||||
| 		}); | ||||
| 		tribute.attach(this.renderRoot.getElementById('edit')); | ||||
| 		this._tribute = tribute; | ||||
| 	} | ||||
|  | ||||
| 	updated() { | ||||
| @@ -340,6 +348,7 @@ class TfComposeElement extends LitElement { | ||||
| 			preview.innerHTML = this.process_text(edit.innerText); | ||||
| 			this.last_updated_text = edit.innerText; | ||||
| 		} | ||||
| 		this._tribute.collection[0].values = this.get_values(); | ||||
| 		let encrypt = this.renderRoot.getElementById('encrypt_to'); | ||||
| 		if (encrypt) { | ||||
| 			let tribute = new Tribute({ | ||||
| @@ -359,7 +368,7 @@ class TfComposeElement extends LitElement { | ||||
| 	remove_mention(id) { | ||||
| 		let draft = this.get_draft(); | ||||
| 		delete draft.mentions[id]; | ||||
| 		setTimeout(() => this.notify(), 0); | ||||
| 		setTimeout(() => this.notify(draft), 0); | ||||
| 	} | ||||
|  | ||||
| 	render_mention(mention) { | ||||
| @@ -446,12 +455,15 @@ class TfComposeElement extends LitElement { | ||||
| 			self.apps = await tfrpc.rpc.apps(); | ||||
| 		} | ||||
| 		if (!this.apps) { | ||||
| 			return html`<button class="w3-button w3-theme-d1" @click=${attach_app}> | ||||
| 			return html`<button | ||||
| 				class="w3-button w3-bar-item w3-theme-d1" | ||||
| 				@click=${attach_app} | ||||
| 			> | ||||
| 				Attach App | ||||
| 			</button>`; | ||||
| 		} else { | ||||
| 			return html`<button | ||||
| 				class="w3-button w3-theme-d1" | ||||
| 				class="w3-button w3-bar-item w3-theme-d1" | ||||
| 				@click=${() => (this.apps = null)} | ||||
| 			> | ||||
| 				Discard App | ||||
| @@ -472,18 +484,9 @@ class TfComposeElement extends LitElement { | ||||
| 		if (draft.content_warning !== undefined) { | ||||
| 			return html` | ||||
| 				<div class="w3-container w3-padding"> | ||||
| 					<p> | ||||
| 						<input type="checkbox" class="w3-check w3-theme-d1" id="cw" @change=${() => self.set_content_warning(undefined)} checked="checked"></input> | ||||
| 						<label for="cw">CW</label> | ||||
| 					</p> | ||||
| 					<input type="text" class="w3-input w3-border w3-theme-d1" id="content_warning" placeholder="Enter a content warning here." @input=${self.input} value=${draft.content_warning}></input> | ||||
| 				</div> | ||||
| 			`; | ||||
| 		} else { | ||||
| 			return html` | ||||
| 				<input type="checkbox" class="w3-check w3-theme-d1" id="cw" @change=${() => self.set_content_warning('')}></input> | ||||
| 				<label for="cw">CW</label> | ||||
| 			`; | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| @@ -502,7 +505,17 @@ class TfComposeElement extends LitElement { | ||||
| 	} | ||||
|  | ||||
| 	get_draft() { | ||||
| 		return this.drafts[this.branch || ''] || {}; | ||||
| 		let key = | ||||
| 			this.branch || | ||||
| 			(this.recipients ? this.recipients.join(',') : undefined) || | ||||
| 			''; | ||||
| 		let draft = this.drafts[key] || {}; | ||||
| 		if (this.recipients && !draft.encrypt_to?.length) { | ||||
| 			draft.encrypt_to = [ | ||||
| 				...new Set(this.recipients).union(new Set(draft.encrypt_to ?? [])), | ||||
| 			]; | ||||
| 		} | ||||
| 		return draft; | ||||
| 	} | ||||
|  | ||||
| 	update_encrypt(event) { | ||||
| @@ -524,7 +537,7 @@ class TfComposeElement extends LitElement { | ||||
| 		return html` | ||||
| 			<div style="display: flex; flex-direction: row; width: 100%"> | ||||
| 				<label for="encrypt_to">🔐 To:</label> | ||||
| 				<input type="text" id="encrypt_to" style="display: flex; flex: 1 1" @input=${this.update_encrypt}></input> | ||||
| 				<input type="text" id="encrypt_to" class="w3-input w3-theme-d1 w3-border" style="display: flex; flex: 1 1" @input=${this.update_encrypt}></input> | ||||
| 				<button class="w3-button w3-theme-d1" @click=${() => this.set_encrypt(undefined)}>🚮</button> | ||||
| 			</div> | ||||
| 			<ul> | ||||
| @@ -546,6 +559,31 @@ class TfComposeElement extends LitElement { | ||||
| 		this.requestUpdate(); | ||||
| 	} | ||||
|  | ||||
| 	toggle_menu(event) { | ||||
| 		event.srcElement.parentNode | ||||
| 			.querySelector('.w3-dropdown-content') | ||||
| 			.classList.toggle('w3-show'); | ||||
| 	} | ||||
|  | ||||
| 	connectedCallback() { | ||||
| 		super.connectedCallback(); | ||||
| 		this._click_callback = this.document_click.bind(this); | ||||
| 		document.body.addEventListener('mouseup', this._click_callback); | ||||
| 	} | ||||
|  | ||||
| 	disconnectedCallback() { | ||||
| 		super.disconnectedCallback(); | ||||
| 		document.body.removeEventListener('mouseup', this._click_callback); | ||||
| 	} | ||||
|  | ||||
| 	document_click(event) { | ||||
| 		let content = this.renderRoot.querySelector('.w3-dropdown-content'); | ||||
| 		let target = event.target; | ||||
| 		if (content && !content.contains(target)) { | ||||
| 			content.classList.remove('w3-show'); | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	render() { | ||||
| 		let self = this; | ||||
| 		let draft = self.get_draft(); | ||||
| @@ -559,12 +597,15 @@ class TfComposeElement extends LitElement { | ||||
| 			draft.encrypt_to !== undefined | ||||
| 				? undefined | ||||
| 				: html`<button | ||||
| 						class="w3-button w3-theme-d1" | ||||
| 						class="w3-button w3-bar-item w3-theme-d1" | ||||
| 						@click=${() => this.set_encrypt([])} | ||||
| 					> | ||||
| 						🔐 | ||||
| 						🔐 Encrypt | ||||
| 					</button>`; | ||||
| 		let result = html` | ||||
| 			<style> | ||||
| 				${generate_theme()} | ||||
| 			</style> | ||||
| 			<style> | ||||
| 				.w3-input:empty::before { | ||||
| 					content: attr(placeholder); | ||||
| @@ -583,11 +624,11 @@ class TfComposeElement extends LitElement { | ||||
| 						: undefined} | ||||
| 					${this.render_encrypt()} | ||||
| 				</header> | ||||
| 				<div class="w3-container w3-padding-small"> | ||||
| 				<div class="w3-container" style="padding: 0 0 16px 0"> | ||||
| 					<div class="w3-half"> | ||||
| 						<span | ||||
| 							class="w3-input w3-theme-d1 w3-border" | ||||
| 							style="resize: vertical; width: 100%; overflow: hidden; white-space: pre-wrap" | ||||
| 							style="resize: vertical; width: 100%; white-space: pre-wrap" | ||||
| 							placeholder="Write a post here." | ||||
| 							id="edit" | ||||
| 							@input=${this.input} | ||||
| @@ -604,7 +645,7 @@ class TfComposeElement extends LitElement { | ||||
| 				${Object.values(draft.mentions || {}).map((x) => | ||||
| 					self.render_mention(x) | ||||
| 				)} | ||||
| 				<footer class="w3-container"> | ||||
| 				<footer> | ||||
| 					${this.render_attach_app()} ${this.render_content_warning()} | ||||
| 					${this.render_new_thread()} | ||||
| 					<button | ||||
| @@ -614,13 +655,43 @@ class TfComposeElement extends LitElement { | ||||
| 					> | ||||
| 						Submit | ||||
| 					</button> | ||||
| 					<button class="w3-button w3-theme-d1" @click=${this.attach}> | ||||
| 						Attach | ||||
| 					</button> | ||||
| 					${this.render_attach_app_button()} ${encrypt} | ||||
| 					<button class="w3-button w3-theme-d1" @click=${this.discard}> | ||||
| 						Discard | ||||
| 					</button> | ||||
| 					<div class="w3-dropdown-click"> | ||||
| 						<button class="w3-button w3-theme-d1" @click=${this.toggle_menu}> | ||||
| 							⚙️ | ||||
| 						</button> | ||||
| 						<div class="w3-dropdown-content w3-bar-block"> | ||||
| 							${this.get_draft().content_warning === undefined | ||||
| 								? html` | ||||
| 										<button | ||||
| 											class="w3-button w3-bar-item w3-theme-d1" | ||||
| 											@click=${() => self.set_content_warning('')} | ||||
| 										> | ||||
| 											Add Content Warning | ||||
| 										</button> | ||||
| 									` | ||||
| 								: html` | ||||
| 										<button | ||||
| 											class="w3-button w3-bar-item w3-theme-d1" | ||||
| 											@click=${() => self.set_content_warning(undefined)} | ||||
| 										> | ||||
| 											Remove Content Warning | ||||
| 										</button> | ||||
| 									`} | ||||
| 							<button | ||||
| 								class="w3-button w3-bar-item w3-theme-d1" | ||||
| 								@click=${this.attach} | ||||
| 							> | ||||
| 								Attach | ||||
| 							</button> | ||||
| 							${this.render_attach_app_button()} ${encrypt} | ||||
| 							<button | ||||
| 								class="w3-button w3-bar-item w3-theme-d1" | ||||
| 								@click=${this.discard} | ||||
| 							> | ||||
| 								Discard | ||||
| 							</button> | ||||
| 						</div> | ||||
| 					</div> | ||||
| 				</footer> | ||||
| 			</div> | ||||
| 		`; | ||||
|   | ||||
| @@ -1,8 +1,17 @@ | ||||
| import {LitElement, html, repeat, render, unsafeHTML} from './lit-all.min.js'; | ||||
| import { | ||||
| 	LitElement, | ||||
| 	css, | ||||
| 	html, | ||||
| 	repeat, | ||||
| 	render, | ||||
| 	unsafeCSS, | ||||
| 	unsafeHTML, | ||||
| 	until, | ||||
| } from './lit-all.min.js'; | ||||
| import * as tfrpc from '/static/tfrpc.js'; | ||||
| import * as tfutils from './tf-utils.js'; | ||||
| import * as emojis from './emojis.js'; | ||||
| import {styles} from './tf-styles.js'; | ||||
| import {styles, generate_theme} from './tf-styles.js'; | ||||
|  | ||||
| class TfMessageElement extends LitElement { | ||||
| 	static get properties() { | ||||
| @@ -16,6 +25,8 @@ class TfMessageElement extends LitElement { | ||||
| 			expanded: {type: Object}, | ||||
| 			channel: {type: String}, | ||||
| 			channel_unread: {type: Number}, | ||||
| 			recent_reactions: {type: Array}, | ||||
| 			depth: {type: Number}, | ||||
| 		}; | ||||
| 	} | ||||
|  | ||||
| @@ -31,16 +42,21 @@ class TfMessageElement extends LitElement { | ||||
| 		this.format = 'message'; | ||||
| 		this.expanded = {}; | ||||
| 		this.channel_unread = -1; | ||||
| 		this.recent_reactions = []; | ||||
| 		this.depth = 0; | ||||
| 	} | ||||
|  | ||||
| 	connectedCallback() { | ||||
| 		super.connectedCallback(); | ||||
| 		this._click_callback = this.document_click.bind(this); | ||||
| 		this._blob_stored = this.blob_stored.bind(this); | ||||
| 		document.body.addEventListener('mouseup', this._click_callback); | ||||
| 		window.addEventListener('blob-stored', this._blob_stored); | ||||
| 	} | ||||
|  | ||||
| 	disconnectedCallback() { | ||||
| 		super.disconnectedCallback(); | ||||
| 		window.removeEventListener('blob-stored', this._blob_stored); | ||||
| 		document.body.removeEventListener('mouseup', this._click_callback); | ||||
| 	} | ||||
|  | ||||
| @@ -52,6 +68,16 @@ class TfMessageElement extends LitElement { | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	blob_stored(event) { | ||||
| 		let search = `/${event.detail.id}/view`; | ||||
| 		for (let img of this.shadowRoot.querySelectorAll('img')) { | ||||
| 			if (img.src.indexOf(search) != -1) { | ||||
| 				let src = img.src.split('?')[0]; | ||||
| 				img.src = `${src}?${new Date().valueOf()}`; | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	show_reply() { | ||||
| 		let event = new CustomEvent('tf-draft', { | ||||
| 			bubbles: true, | ||||
| @@ -84,20 +110,27 @@ class TfMessageElement extends LitElement { | ||||
|  | ||||
| 	render_votes() { | ||||
| 		function normalize_expression(expression) { | ||||
| 			if (expression === 'Like' || !expression) { | ||||
| 				return '👍'; | ||||
| 			} else if (expression === 'Unlike') { | ||||
| 			if ( | ||||
| 				expression === 'Unlike' || | ||||
| 				expression === 'unlike' || | ||||
| 				expression == 'undig' | ||||
| 			) { | ||||
| 				return '👎'; | ||||
| 			} else if (expression === 'heart') { | ||||
| 				return '❤️'; | ||||
| 			} else if ( | ||||
| 				(expression ?? '').split('').every((x) => x.charCodeAt(0) < 256) | ||||
| 			) { | ||||
| 				return '👍'; | ||||
| 			} else { | ||||
| 				return expression; | ||||
| 			} | ||||
| 		} | ||||
| 		if (this.message?.votes?.length) { | ||||
| 			return html` <div class="w3-container"> | ||||
| 			return html` <footer class="w3-container"> | ||||
| 				<div | ||||
| 					class="w3-button w3-bar w3-padding-small" | ||||
| 					class="w3-button w3-bar" | ||||
| 					style="padding: 0" | ||||
| 					@click=${this.show_reactions} | ||||
| 				> | ||||
| 					${(this.message.votes || []).map( | ||||
| @@ -112,7 +145,7 @@ class TfMessageElement extends LitElement { | ||||
| 						` | ||||
| 					)} | ||||
| 				</div> | ||||
| 			</div>`; | ||||
| 			</footer>`; | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| @@ -155,7 +188,12 @@ class TfMessageElement extends LitElement { | ||||
| 	} | ||||
|  | ||||
| 	react(event) { | ||||
| 		emojis.picker((x) => this.vote(x), null, this.whoami); | ||||
| 		emojis.picker( | ||||
| 			(x) => this.vote(x), | ||||
| 			null, | ||||
| 			this.whoami, | ||||
| 			this.recent_reactions | ||||
| 		); | ||||
| 	} | ||||
|  | ||||
| 	show_image(link) { | ||||
| @@ -170,12 +208,12 @@ class TfMessageElement extends LitElement { | ||||
| 		div.style.display = 'grid'; | ||||
| 		let img = document.createElement('img'); | ||||
| 		img.src = link; | ||||
| 		img.style.maxWidth = '100%'; | ||||
| 		img.style.maxHeight = '100%'; | ||||
| 		img.style.maxWidth = '100vw'; | ||||
| 		img.style.maxHeight = '100vh'; | ||||
| 		img.style.display = 'block'; | ||||
| 		img.style.margin = 'auto'; | ||||
| 		img.style.objectFit = 'contain'; | ||||
| 		img.style.width = '100%'; | ||||
| 		img.style.width = '100vw'; | ||||
| 		div.appendChild(img); | ||||
| 		function image_close(event) { | ||||
| 			document.body.removeChild(div); | ||||
| @@ -289,53 +327,76 @@ class TfMessageElement extends LitElement { | ||||
| 		return total; | ||||
| 	} | ||||
|  | ||||
| 	expanded_key() { | ||||
| 		return ( | ||||
| 			this.message?.id || this.message?.messages?.map((x) => x.id).join(':') | ||||
| 		); | ||||
| 	} | ||||
|  | ||||
| 	set_expanded(expanded, tag) { | ||||
| 		let key = this.expanded_key(); | ||||
| 		this.dispatchEvent( | ||||
| 			new CustomEvent('tf-expand', { | ||||
| 				bubbles: true, | ||||
| 				composed: true, | ||||
| 				detail: {id: (this.message.id || '') + (tag || ''), expanded: expanded}, | ||||
| 				detail: {id: key + (tag || ''), expanded: expanded}, | ||||
| 			}) | ||||
| 		); | ||||
| 	} | ||||
|  | ||||
| 	toggle_expanded(tag) { | ||||
| 		this.set_expanded( | ||||
| 			!this.expanded[(this.message.id || '') + (tag || '')], | ||||
| 			tag | ||||
| 		); | ||||
| 		let key = this.expanded_key(); | ||||
| 		this.set_expanded(!this.expanded[key + (tag || '')], tag); | ||||
| 	} | ||||
|  | ||||
| 	is_expanded(tag) { | ||||
| 		let key = this.expanded_key(); | ||||
| 		return this.expanded[key + (tag || '')]; | ||||
| 	} | ||||
|  | ||||
| 	render_children() { | ||||
| 		let self = this; | ||||
| 		if (this.message.child_messages?.length) { | ||||
| 			if (!this.expanded[this.message.id]) { | ||||
| 				return html`<button | ||||
| 					class="w3-button w3-theme-d1" | ||||
| 					@click=${() => self.set_expanded(true)} | ||||
| 				> | ||||
| 					+ ${this.total_child_messages(this.message) + ' More'} | ||||
| 				</button>`; | ||||
| 			} else { | ||||
| 				return html`<button | ||||
| 						class="w3-button w3-theme-d1" | ||||
| 						@click=${() => self.set_expanded(false)} | ||||
| 			if (!this.expanded[this.expanded_key()]) { | ||||
| 				return html` | ||||
| 					<button | ||||
| 						class="w3-button w3-theme-d1 w3-block w3-bar" | ||||
| 						style="box-sizing: border-box" | ||||
| 						@click=${() => self.set_expanded(true)} | ||||
| 					> | ||||
| 						Collapse</button | ||||
| 					>${repeat( | ||||
| 						+ ${this.total_child_messages(this.message) + ' More'} | ||||
| 					</button> | ||||
| 				`; | ||||
| 			} else { | ||||
| 				return html` <ul class="w3-container w3-margin-bottom w3-ul w3-card-4"> | ||||
| 					${repeat( | ||||
| 						this.message.child_messages || [], | ||||
| 						(x) => x.id, | ||||
| 						(x) => | ||||
| 							html`<tf-message | ||||
| 								.message=${x} | ||||
| 								whoami=${this.whoami} | ||||
| 								.users=${this.users} | ||||
| 								.drafts=${this.drafts} | ||||
| 								.expanded=${this.expanded} | ||||
| 								channel=${this.channel} | ||||
| 								channel_unread=${this.channel_unread} | ||||
| 							></tf-message>` | ||||
| 					)}`; | ||||
| 							html`<li style="padding: 0"> | ||||
| 								<tf-message | ||||
| 									.message=${x} | ||||
| 									whoami=${this.whoami} | ||||
| 									.users=${this.users} | ||||
| 									.drafts=${this.drafts} | ||||
| 									.expanded=${this.expanded} | ||||
| 									channel=${this.channel} | ||||
| 									channel_unread=${this.channel_unread} | ||||
| 									.recent_reactions=${this.recent_reactions} | ||||
| 									depth=${this.depth + 1} | ||||
| 								></tf-message> | ||||
| 							</li>` | ||||
| 					)} | ||||
| 					<li style="padding: 0" class="w3-margin-bottom"> | ||||
| 						<button | ||||
| 							class="w3-button w3-theme-d1 w3-block w3-bar" | ||||
| 							style="box-sizing: border-box" | ||||
| 							@click=${() => self.set_expanded(false)} | ||||
| 						> | ||||
| 							Collapse | ||||
| 						</button> | ||||
| 					</li> | ||||
| 				</ul>`; | ||||
| 			} | ||||
| 		} else { | ||||
| 			return undefined; | ||||
| @@ -377,7 +438,7 @@ class TfMessageElement extends LitElement { | ||||
| 	class_background() { | ||||
| 		return this.message?.decrypted | ||||
| 			? 'w3-pale-red' | ||||
| 			: this.message?.rowid >= this.channel_unread | ||||
| 			: this.allow_unread() && this.message?.rowid >= this.channel_unread | ||||
| 				? 'w3-theme-d2' | ||||
| 				: 'w3-theme-d4'; | ||||
| 	} | ||||
| @@ -431,12 +492,17 @@ class TfMessageElement extends LitElement { | ||||
| 					> | ||||
| 						Copy ID | ||||
| 					</button> | ||||
| 					${this.drafts[this.message?.id] === undefined ? html` | ||||
| 						<button class="w3-button w3-bar-item" @click=${this.show_reply}> | ||||
| 							⮢ Reply | ||||
| 						</button> | ||||
| 					` : undefined} | ||||
| 					<button class="w3-button w3-bar-item w3-border-bottom" @click=${this.react}> | ||||
| 					${this.drafts[this.message?.id] === undefined | ||||
| 						? html` | ||||
| 								<button class="w3-button w3-bar-item" @click=${this.show_reply}> | ||||
| 									↩️ Reply | ||||
| 								</button> | ||||
| 							` | ||||
| 						: undefined} | ||||
| 					<button | ||||
| 						class="w3-button w3-bar-item w3-border-bottom" | ||||
| 						@click=${this.react} | ||||
| 					> | ||||
| 						👍 React | ||||
| 					</button> | ||||
| 					${formats.map( | ||||
| @@ -464,7 +530,10 @@ class TfMessageElement extends LitElement { | ||||
| 		return html` | ||||
| 			<header class="w3-bar"> | ||||
| 				<span class="w3-bar-item"> | ||||
| 					<tf-user id=${this.message.author} .users=${this.users}></tf-user> | ||||
| 					${this.render_unread_icon()}<tf-user | ||||
| 						id=${this.message.author} | ||||
| 						.users=${this.users} | ||||
| 					></tf-user> | ||||
| 				</span> | ||||
| 				${is_encrypted} ${this.render_menu()} | ||||
| 				<div class="w3-bar-item w3-right" style="text-wrap: nowrap"> | ||||
| @@ -491,8 +560,11 @@ class TfMessageElement extends LitElement { | ||||
| 				} | ||||
| 			</style> | ||||
| 			<div | ||||
| 				class="w3-card-4 ${this.class_background()} w3-border-theme w3-margin-top" | ||||
| 				style="overflow: auto; overflow-wrap: anywhere; display: block; max-width: 100%" | ||||
| 				class="w3-card-4 ${this.class_background()} w3-border-theme ${this | ||||
| 					.depth == 0 | ||||
| 					? 'w3-margin-top' | ||||
| 					: ''}" | ||||
| 				style="overflow-wrap: anywhere; display: block; max-width: 100%" | ||||
| 			> | ||||
| 				${inner} | ||||
| 			</div> | ||||
| @@ -517,6 +589,8 @@ class TfMessageElement extends LitElement { | ||||
| 						.expanded=${self.expanded} | ||||
| 						channel=${self.channel} | ||||
| 						channel_unread=${self.channel_unread} | ||||
| 						.recent_reactions=${self.recent_reactions} | ||||
| 						depth=${self.depth + 1} | ||||
| 					></tf-message> | ||||
| 				` | ||||
| 			)} | ||||
| @@ -528,26 +602,108 @@ class TfMessageElement extends LitElement { | ||||
| 		let reply = | ||||
| 			this.drafts[this.message?.id] !== undefined | ||||
| 				? html` | ||||
| 						<tf-compose | ||||
| 							whoami=${this.whoami} | ||||
| 							.users=${this.users} | ||||
| 							root=${content.root || this.message.id} | ||||
| 							branch=${this.message.id} | ||||
| 							.drafts=${this.drafts} | ||||
| 							@tf-discard=${this.discard_reply} | ||||
| 							author=${this.message.author} | ||||
| 						></tf-compose> | ||||
| 						<div class="w3-section w3-container"> | ||||
| 							<tf-compose | ||||
| 								whoami=${this.whoami} | ||||
| 								.users=${this.users} | ||||
| 								root=${content.root || this.message.id} | ||||
| 								branch=${this.message.id} | ||||
| 								.drafts=${this.drafts} | ||||
| 								@tf-discard=${this.discard_reply} | ||||
| 								author=${this.message.author} | ||||
| 								.recent_reactions=${this.recent_reactions} | ||||
| 							></tf-compose> | ||||
| 						</div> | ||||
| 					` | ||||
| 				: undefined; | ||||
| 		return html` | ||||
| 			<div class="w3-section w3-container"> | ||||
| 				${reply} | ||||
| 				${this.render_children()} | ||||
| 			</div> | ||||
| 			${reply} | ||||
| 			<footer>${this.render_children()}</footer> | ||||
| 		`; | ||||
| 	} | ||||
|  | ||||
| 	render() { | ||||
| 	content_group_by_author() { | ||||
| 		let sorted = this.message.messages | ||||
| 			.map((x) => [ | ||||
| 				x.author, | ||||
| 				x.content.following && x.content.blocking | ||||
| 					? 'is following and blocking' | ||||
| 					: x.content.following | ||||
| 						? 'is following' | ||||
| 						: x.content.blocking | ||||
| 							? 'is blocking' | ||||
| 							: x.content.blocking !== undefined | ||||
| 								? 'is no longer blocking' | ||||
| 								: x.content.following !== undefined | ||||
| 									? 'is no longer following' | ||||
| 									: '', | ||||
| 				x.content.contact, | ||||
| 				x, | ||||
| 			]) | ||||
| 			.sort(); | ||||
| 		let result = []; | ||||
| 		let last; | ||||
| 		let group; | ||||
| 		for (let row of sorted) { | ||||
| 			if (last && last[0] == row[0] && last[1] == row[1]) { | ||||
| 				group.push(row[2]); | ||||
| 			} else { | ||||
| 				if (group) { | ||||
| 					result.push({author: last[0], action: last[1], users: group}); | ||||
| 				} | ||||
| 				last = row; | ||||
| 				group = [row[2]]; | ||||
| 			} | ||||
| 		} | ||||
| 		if (group) { | ||||
| 			result.push({author: last[0], action: last[1], users: group}); | ||||
| 		} | ||||
| 		return result; | ||||
| 	} | ||||
|  | ||||
| 	channel_group_by_author() { | ||||
| 		let sorted = this.message.messages | ||||
| 			.map((x) => [ | ||||
| 				x.author, | ||||
| 				x.content.subscribed ? 'subscribed to' : 'unsubscribed from', | ||||
| 				x.content.channel, | ||||
| 				x, | ||||
| 			]) | ||||
| 			.sort(); | ||||
| 		let result = []; | ||||
| 		let last; | ||||
| 		let group; | ||||
| 		for (let row of sorted) { | ||||
| 			if (last && last[0] == row[0] && last[1] == row[1]) { | ||||
| 				group.push(row[2]); | ||||
| 			} else { | ||||
| 				if (group) { | ||||
| 					result.push({author: last[0], action: last[1], channels: group}); | ||||
| 				} | ||||
| 				last = row; | ||||
| 				group = [row[2]]; | ||||
| 			} | ||||
| 		} | ||||
| 		if (group) { | ||||
| 			result.push({author: last[0], action: last[1], channels: group}); | ||||
| 		} | ||||
| 		return result; | ||||
| 	} | ||||
|  | ||||
| 	allow_unread() { | ||||
| 		return ( | ||||
| 			this.channel == '@' || | ||||
| 			(!this.channel.startsWith('@') && !this.channel.startsWith('%')) | ||||
| 		); | ||||
| 	} | ||||
|  | ||||
| 	render_unread_icon() { | ||||
| 		return this.allow_unread() && this.message?.rowid >= this.channel_unread | ||||
| 			? html`✉️` | ||||
| 			: undefined; | ||||
| 	} | ||||
|  | ||||
| 	_render() { | ||||
| 		let content = this.message?.content; | ||||
| 		if (this.message?.decrypted?.type == 'post') { | ||||
| 			content = this.message.decrypted; | ||||
| @@ -555,29 +711,145 @@ class TfMessageElement extends LitElement { | ||||
| 		let class_background = this.class_background(); | ||||
| 		let self = this; | ||||
| 		if (this.message?.type === 'contact_group') { | ||||
| 			return this.render_frame( | ||||
| 				html` ${this.message.messages.map( | ||||
| 					(x) => | ||||
| 						html`<tf-message | ||||
| 							.message=${x} | ||||
| 							whoami=${this.whoami} | ||||
| 							.users=${this.users} | ||||
| 							.drafts=${this.drafts} | ||||
| 							.expanded=${this.expanded} | ||||
| 							channel=${this.channel} | ||||
| 							channel_unread=${this.channel_unread} | ||||
| 						></tf-message>` | ||||
| 				)}` | ||||
| 			); | ||||
| 			if (this.expanded[this.expanded_key()]) { | ||||
| 				return this.render_frame(html` | ||||
| 					<div class="w3-padding"> | ||||
| 						${this.message.messages.map( | ||||
| 							(x) => | ||||
| 								html`<tf-message | ||||
| 									.message=${x} | ||||
| 									whoami=${this.whoami} | ||||
| 									.users=${this.users} | ||||
| 									.drafts=${this.drafts} | ||||
| 									.expanded=${this.expanded} | ||||
| 									channel=${this.channel} | ||||
| 									channel_unread=${this.channel_unread} | ||||
| 									depth=${this.depth + 1} | ||||
| 								></tf-message>` | ||||
| 						)} | ||||
| 					</div> | ||||
| 					<button | ||||
| 						class="w3-button w3-theme-d1 w3-block w3-bar" | ||||
| 						style="box-sizing: border-box" | ||||
| 						@click=${() => self.set_expanded(false)} | ||||
| 					> | ||||
| 						Collapse | ||||
| 					</button> | ||||
| 				`); | ||||
| 			} else { | ||||
| 				return this.render_frame(html` | ||||
| 					<div class="w3-padding"> | ||||
| 						${this.content_group_by_author().map( | ||||
| 							(x) => html` | ||||
| 								<div> | ||||
| 									<tf-user id=${x.author} .users=${this.users}></tf-user> | ||||
| 									${x.action} | ||||
| 									${x.users.map( | ||||
| 										(y) => html` | ||||
| 											<tf-user | ||||
| 												id=${y} | ||||
| 												.users=${this.users} | ||||
| 												icon_only="true" | ||||
| 											></tf-user> | ||||
| 										` | ||||
| 									)} | ||||
| 								</div> | ||||
| 							` | ||||
| 						)} | ||||
| 					</div> | ||||
| 					<button | ||||
| 						class="w3-button w3-theme-d1 w3-block w3-bar" | ||||
| 						style="box-sizing: border-box" | ||||
| 						@click=${() => self.set_expanded(true)} | ||||
| 					> | ||||
| 						Expand | ||||
| 					</button> | ||||
| 				`); | ||||
| 			} | ||||
| 		} else if (this.message?.type === 'channel_group') { | ||||
| 			if (this.expanded[this.expanded_key()]) { | ||||
| 				return this.render_frame(html` | ||||
| 					<div class="w3-padding"> | ||||
| 						${this.message.messages.map( | ||||
| 							(x) => | ||||
| 								html`<tf-message | ||||
| 									.message=${x} | ||||
| 									whoami=${this.whoami} | ||||
| 									.users=${this.users} | ||||
| 									.drafts=${this.drafts} | ||||
| 									.expanded=${this.expanded} | ||||
| 									channel=${this.channel} | ||||
| 									channel_unread=${this.channel_unread} | ||||
| 									depth=${this.depth + 1} | ||||
| 								></tf-message>` | ||||
| 						)} | ||||
| 					</div> | ||||
| 					<button | ||||
| 						class="w3-button w3-theme-d1 w3-block w3-bar" | ||||
| 						style="box-sizing: border-box" | ||||
| 						@click=${() => self.set_expanded(false)} | ||||
| 					> | ||||
| 						Collapse | ||||
| 					</button> | ||||
| 				`); | ||||
| 			} else { | ||||
| 				return this.render_frame(html` | ||||
| 					<div class="w3-padding"> | ||||
| 						${this.channel_group_by_author().map( | ||||
| 							(x) => html` | ||||
| 								<div> | ||||
| 									<tf-user id=${x.author} .users=${this.users}></tf-user> | ||||
| 									${x.action} | ||||
| 									${x.channels.map( | ||||
| 										(y) => html` <tf-tag tag=${'#' + y}></tf-tag> ` | ||||
| 									)} | ||||
| 								</div> | ||||
| 							` | ||||
| 						)} | ||||
| 					</div> | ||||
| 					<button | ||||
| 						class="w3-button w3-theme-d1 w3-block w3-bar" | ||||
| 						style="box-sizing: border-box" | ||||
| 						@click=${() => self.set_expanded(true)} | ||||
| 					> | ||||
| 						Expand | ||||
| 					</button> | ||||
| 				`); | ||||
| 			} | ||||
| 		} else if (this.message.placeholder) { | ||||
| 			return this.render_frame( | ||||
| 				html`<div class="w3-padding"> | ||||
| 					<p> | ||||
| 						<a target="_top" href=${'#' + encodeURIComponent(this.message.id)} | ||||
| 							>${this.message.id}</a | ||||
| 				html`<div> | ||||
| 					<div class="w3-bar"> | ||||
| 						<a | ||||
| 							class="w3-bar-item w3-panel w3-round-xlarge w3-theme-d1 w3-margin w3-button" | ||||
| 							target="_top" | ||||
| 							href=${'#' + encodeURIComponent(this.message?.id)} | ||||
| 						> | ||||
| 						(placeholder) | ||||
| 					</p> | ||||
| 							This message is not currently available. | ||||
| 						</a> | ||||
| 						<div class="w3-bar-item w3-right"> | ||||
| 							<button class="w3-button w3-theme-d1" @click=${this.toggle_menu}> | ||||
| 								% | ||||
| 							</button> | ||||
| 							<div | ||||
| 								class="w3-dropdown-content w3-bar-block w3-card-4 w3-theme-l1" | ||||
| 								style="right: 48px" | ||||
| 							> | ||||
| 								<a | ||||
| 									target="_top" | ||||
| 									class="w3-button w3-bar-item" | ||||
| 									href=${'#' + encodeURIComponent(this.message?.id)} | ||||
| 									>View Message</a | ||||
| 								> | ||||
| 								<button | ||||
| 									class="w3-button w3-bar-item w3-border-bottom" | ||||
| 									@click=${this.copy_id} | ||||
| 								> | ||||
| 									Copy ID | ||||
| 								</button> | ||||
| 							</div> | ||||
| 						</div> | ||||
| 					</div> | ||||
| 					<div>${this.render_votes()}</div> | ||||
| 					${(this.message.child_messages || []).map( | ||||
| 						(x) => html` | ||||
| @@ -589,6 +861,7 @@ class TfMessageElement extends LitElement { | ||||
| 								.expanded=${this.expanded} | ||||
| 								channel=${this.channel} | ||||
| 								channel_unread=${this.channel_unread} | ||||
| 								depth=${this.depth + 1} | ||||
| 							></tf-message> | ||||
| 						` | ||||
| 					)} | ||||
| @@ -604,7 +877,7 @@ class TfMessageElement extends LitElement { | ||||
| 				} | ||||
| 				if (content.image !== undefined) { | ||||
| 					image = html` | ||||
| 						<div><img src=${'/' + (typeof content.image?.link == 'string' ? content.image.link : content.image) + '/view'} style="width: 256px; height: auto"></img></div> | ||||
| 						<div @click=${this.body_click}><img src=${'/' + (typeof content.image?.link == 'string' ? content.image.link : content.image) + '/view'} style="width: 256px; height: auto"></img></div> | ||||
| 					`; | ||||
| 				} | ||||
| 				if (content.description !== undefined) { | ||||
| @@ -627,25 +900,45 @@ class TfMessageElement extends LitElement { | ||||
| 					</div> | ||||
| 				`); | ||||
| 			} else if (content.type == 'contact') { | ||||
| 				return html` | ||||
| 					<div class="w3-padding"> | ||||
| 						<tf-user id=${this.message.author} .users=${this.users}></tf-user> | ||||
| 						is | ||||
| 						${content.blocking === true | ||||
| 							? 'blocking' | ||||
| 							: content.blocking === false | ||||
| 								? 'no longer blocking' | ||||
| 								: content.following === true | ||||
| 									? 'following' | ||||
| 									: content.following === false | ||||
| 										? 'no longer following' | ||||
| 										: '?'} | ||||
| 						<tf-user | ||||
| 							id=${this.message.content.contact} | ||||
| 							.users=${this.users} | ||||
| 						></tf-user> | ||||
| 					</div> | ||||
| 				`; | ||||
| 				switch (this.format) { | ||||
| 					case 'message': | ||||
| 					default: | ||||
| 						return this.render_frame(html` | ||||
| 							<div class="w3-bar"> | ||||
| 								<div class="w3-bar-item"> | ||||
| 									<tf-user | ||||
| 										id=${this.message.author} | ||||
| 										.users=${this.users} | ||||
| 									></tf-user> | ||||
| 									is | ||||
| 									${content.blocking === true | ||||
| 										? 'blocking' | ||||
| 										: content.blocking === false | ||||
| 											? 'no longer blocking' | ||||
| 											: content.following === true | ||||
| 												? 'following' | ||||
| 												: content.following === false | ||||
| 													? 'no longer following' | ||||
| 													: '?'} | ||||
| 									<tf-user | ||||
| 										id=${this.message.content.contact} | ||||
| 										.users=${this.users} | ||||
| 									></tf-user> | ||||
| 								</div> | ||||
| 								${this.render_menu()} ${this.render_votes()} | ||||
| 								${this.render_actions()} | ||||
| 							</div> | ||||
| 						`); | ||||
| 						break; | ||||
| 					case 'raw': | ||||
| 						return this.render_frame(html` | ||||
| 							${this.render_header()} | ||||
| 							<div class="w3-container">${this.render_raw()}</div> | ||||
| 							${this.render_votes()} ${this.render_actions()} | ||||
| 						</div> | ||||
| 						`); | ||||
| 						break; | ||||
| 				} | ||||
| 			} else if (content.type == 'post') { | ||||
| 				let self = this; | ||||
| 				let body; | ||||
| @@ -668,11 +961,14 @@ class TfMessageElement extends LitElement { | ||||
| 				} | ||||
| 				let content_warning = html` | ||||
| 					<div | ||||
| 						class="w3-panel w3-round-xlarge w3-theme-l4" | ||||
| 						class="w3-panel w3-round-xlarge w3-theme-l4 w3" | ||||
| 						style="cursor: pointer" | ||||
| 						@click=${(x) => this.toggle_expanded(':cw')} | ||||
| 					> | ||||
| 						<p>${content.contentWarning}</p> | ||||
| 						<p class="w3-small"> | ||||
| 							${this.is_expanded(':cw') ? 'Show less' : 'Show more'} | ||||
| 						</p> | ||||
| 					</div> | ||||
| 				`; | ||||
| 				let content_html = html` | ||||
| @@ -801,6 +1097,15 @@ class TfMessageElement extends LitElement { | ||||
| 			return this.render_small_frame(this.render_raw()); | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	render() { | ||||
| 		return html` | ||||
| 			<style> | ||||
| 				${generate_theme()} | ||||
| 			</style> | ||||
| 			${this._render()} | ||||
| 		`; | ||||
| 	} | ||||
| } | ||||
|  | ||||
| customElements.define('tf-message', TfMessageElement); | ||||
|   | ||||
| @@ -1,6 +1,6 @@ | ||||
| import {LitElement, html, unsafeHTML, repeat, until} from './lit-all.min.js'; | ||||
| import * as tfrpc from '/static/tfrpc.js'; | ||||
| import {styles} from './tf-styles.js'; | ||||
| import {styles, generate_theme} from './tf-styles.js'; | ||||
|  | ||||
| class TfNewsElement extends LitElement { | ||||
| 	static get properties() { | ||||
| @@ -13,6 +13,8 @@ class TfNewsElement extends LitElement { | ||||
| 			expanded: {type: Object}, | ||||
| 			channel: {type: String}, | ||||
| 			channel_unread: {type: Number}, | ||||
| 			recent_reactions: {type: Array}, | ||||
| 			hash: {type: String}, | ||||
| 		}; | ||||
| 	} | ||||
|  | ||||
| @@ -28,6 +30,7 @@ class TfNewsElement extends LitElement { | ||||
| 		this.drafts = {}; | ||||
| 		this.expanded = {}; | ||||
| 		this.channel_unread = -1; | ||||
| 		this.recent_reactions = []; | ||||
| 	} | ||||
|  | ||||
| 	process_messages(messages) { | ||||
| @@ -157,46 +160,80 @@ class TfNewsElement extends LitElement { | ||||
| 		return recursive_sort(roots, true); | ||||
| 	} | ||||
|  | ||||
| 	group_following(messages) { | ||||
| 	group_messages(messages) { | ||||
| 		let result = []; | ||||
| 		let group = []; | ||||
| 		let type = undefined; | ||||
| 		for (let message of messages) { | ||||
| 			if (message?.content?.type === 'contact') { | ||||
| 			if ( | ||||
| 				message?.content?.type === 'contact' || | ||||
| 				message?.content?.type === 'channel' | ||||
| 			) { | ||||
| 				if (type && message.content.type !== type) { | ||||
| 					if (group.length == 1) { | ||||
| 						result.push(group[0]); | ||||
| 						group = []; | ||||
| 					} else if (group.length > 1) { | ||||
| 						result.push({ | ||||
| 							rowid: Math.max(...group.map((x) => x.rowid)), | ||||
| 							type: `${type}_group`, | ||||
| 							messages: group, | ||||
| 						}); | ||||
| 						group = []; | ||||
| 					} | ||||
| 				} | ||||
| 				type = message.content.type; | ||||
| 				group.push(message); | ||||
| 			} else { | ||||
| 				if (group.length > 0) { | ||||
| 				if (group.length == 1) { | ||||
| 					result.push(group[0]); | ||||
| 					group = []; | ||||
| 				} else if (group.length > 1) { | ||||
| 					result.push({ | ||||
| 						rowid: Math.max(...group.map((x) => x.rowid)), | ||||
| 						type: 'contact_group', | ||||
| 						type: `${type}_group`, | ||||
| 						messages: group, | ||||
| 					}); | ||||
| 					group = []; | ||||
| 				} | ||||
| 				result.push(message); | ||||
| 				type = undefined; | ||||
| 			} | ||||
| 		} | ||||
| 		if (group.length > 0) { | ||||
| 		if (group.length == 1) { | ||||
| 			result.push(group[0]); | ||||
| 			group = []; | ||||
| 		} else if (group.length > 1) { | ||||
| 			result.push({ | ||||
| 				rowid: Math.max(...group.map((x) => x.rowid)), | ||||
| 				type: 'contact_group', | ||||
| 				type: `${type}_group`, | ||||
| 				messages: group, | ||||
| 			}); | ||||
| 		} | ||||
| 		return result; | ||||
| 	} | ||||
|  | ||||
| 	unread_allowed() { | ||||
| 		return !this.hash?.startsWith('#%') && !this.hash?.startsWith('#@'); | ||||
| 	} | ||||
|  | ||||
| 	load_and_render(messages) { | ||||
| 		let messages_by_id = this.process_messages(messages); | ||||
| 		let final_messages = this.group_following( | ||||
| 		let final_messages = this.group_messages( | ||||
| 			this.finalize_messages(messages_by_id) | ||||
| 		); | ||||
| 		let unread_rowid = -1; | ||||
| 		for (let message of final_messages) { | ||||
| 			if (message.rowid >= this.channel_unread) { | ||||
| 				unread_rowid = message.rowid; | ||||
| 		if (this.unread_allowed()) { | ||||
| 			for (let message of final_messages) { | ||||
| 				if (message.rowid >= this.channel_unread) { | ||||
| 					unread_rowid = message.rowid; | ||||
| 				} | ||||
| 			} | ||||
| 		} | ||||
| 		return html` | ||||
| 			<style> | ||||
| 				${generate_theme()} | ||||
| 			</style> | ||||
| 			<div> | ||||
| 				${repeat( | ||||
| 					final_messages, | ||||
| @@ -211,13 +248,26 @@ class TfNewsElement extends LitElement { | ||||
| 							collapsed="true" | ||||
| 							channel=${this.channel} | ||||
| 							channel_unread=${this.channel_unread} | ||||
| 							.recent_reactions=${this.recent_reactions} | ||||
| 						></tf-message> | ||||
| 						${x.rowid == unread_rowid | ||||
| 							? html`<div style="display: flex; flex-direction: row"> | ||||
| 									<div | ||||
| 										style="border-bottom: 1px solid #f00; flex: 1; align-self: center; height: 1px" | ||||
| 									></div> | ||||
| 									<div style="color: #f00; padding: 8px">unread</div> | ||||
| 									<button | ||||
| 										style="color: #f00; padding: 8px" | ||||
| 										class="w3-button" | ||||
| 										@click=${() => | ||||
| 											this.dispatchEvent( | ||||
| 												new Event('mark_all_read', { | ||||
| 													bubbles: true, | ||||
| 													composed: true, | ||||
| 												}) | ||||
| 											)} | ||||
| 									> | ||||
| 										unread | ||||
| 									</button> | ||||
| 									<div | ||||
| 										style="border-bottom: 1px solid #f00; flex: 1; align-self: center; height: 1px" | ||||
| 									></div> | ||||
|   | ||||
| @@ -1,7 +1,7 @@ | ||||
| import {LitElement, html, unsafeHTML} from './lit-all.min.js'; | ||||
| import {LitElement, html, until, unsafeHTML} from './lit-all.min.js'; | ||||
| import * as tfrpc from '/static/tfrpc.js'; | ||||
| import * as tfutils from './tf-utils.js'; | ||||
| import {styles} from './tf-styles.js'; | ||||
| import {styles, generate_theme} from './tf-styles.js'; | ||||
|  | ||||
| class TfProfileElement extends LitElement { | ||||
| 	static get properties() { | ||||
| @@ -11,8 +11,10 @@ class TfProfileElement extends LitElement { | ||||
| 			id: {type: String}, | ||||
| 			users: {type: Object}, | ||||
| 			size: {type: Number}, | ||||
| 			sequence: {type: Number}, | ||||
| 			following: {type: Boolean}, | ||||
| 			blocking: {type: Boolean}, | ||||
| 			show_followed: {type: Boolean}, | ||||
| 		}; | ||||
| 	} | ||||
|  | ||||
| @@ -26,6 +28,7 @@ class TfProfileElement extends LitElement { | ||||
| 		this.id = null; | ||||
| 		this.users = {}; | ||||
| 		this.size = 0; | ||||
| 		this.sequence = 0; | ||||
| 	} | ||||
|  | ||||
| 	async load() { | ||||
| @@ -164,17 +167,83 @@ class TfProfileElement extends LitElement { | ||||
| 		navigator.clipboard.writeText(this.id); | ||||
| 	} | ||||
|  | ||||
| 	show_image(link) { | ||||
| 		let div = document.createElement('div'); | ||||
| 		div.style.left = 0; | ||||
| 		div.style.top = 0; | ||||
| 		div.style.width = '100%'; | ||||
| 		div.style.height = '100%'; | ||||
| 		div.style.position = 'fixed'; | ||||
| 		div.style.background = '#000'; | ||||
| 		div.style.zIndex = 100; | ||||
| 		div.style.display = 'grid'; | ||||
| 		let img = document.createElement('img'); | ||||
| 		img.src = link; | ||||
| 		img.style.maxWidth = '100vw'; | ||||
| 		img.style.maxHeight = '100vh'; | ||||
| 		img.style.display = 'block'; | ||||
| 		img.style.margin = 'auto'; | ||||
| 		img.style.objectFit = 'contain'; | ||||
| 		img.style.width = '100vw'; | ||||
| 		div.appendChild(img); | ||||
| 		function image_close(event) { | ||||
| 			document.body.removeChild(div); | ||||
| 			window.removeEventListener('keydown', image_close); | ||||
| 		} | ||||
| 		div.onclick = image_close; | ||||
| 		window.addEventListener('keydown', image_close); | ||||
| 		document.body.appendChild(div); | ||||
| 	} | ||||
|  | ||||
| 	body_click(event) { | ||||
| 		if (event.srcElement.tagName == 'IMG') { | ||||
| 			this.show_image(event.srcElement.src); | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	toggle_account_list(event) { | ||||
| 		let content = event.srcElement.nextElementSibling; | ||||
| 		this.show_followed = !this.show_followed; | ||||
| 	} | ||||
|  | ||||
| 	async load_follows() { | ||||
| 		let accounts = await tfrpc.rpc.following([this.id], 1); | ||||
| 		return html` | ||||
| 			<div class="w3-container"> | ||||
| 				<button | ||||
| 					class="w3-button w3-block w3-theme-d1 followed_accounts" | ||||
| 					@click=${this.toggle_account_list} | ||||
| 				> | ||||
| 					${this.show_followed ? 'Hide' : 'Show'} Followed Accounts | ||||
| 					(${Object.keys(accounts).length}) | ||||
| 				</button> | ||||
| 				<div class=${'w3-card' + (this.show_followed ? '' : ' w3-hide')}> | ||||
| 					<ul class="w3-ul w3-theme-d4 w3-border-theme"> | ||||
| 						${Object.keys(accounts).map( | ||||
| 							(x) => html` | ||||
| 								<li class="w3-border-theme"> | ||||
| 									<tf-user id=${x} .users=${this.users}></tf-user> | ||||
| 								</li> | ||||
| 							` | ||||
| 						)} | ||||
| 					</ul> | ||||
| 				</div> | ||||
| 			</div> | ||||
| 		`; | ||||
| 	} | ||||
|  | ||||
| 	render() { | ||||
| 		this.load(); | ||||
| 		let self = this; | ||||
| 		let profile = this.users[this.id] || {}; | ||||
| 		tfrpc.rpc | ||||
| 			.query( | ||||
| 				`SELECT SUM(LENGTH(content)) AS size FROM messages WHERE author = ?`, | ||||
| 				`SELECT SUM(LENGTH(content)) AS size, MAX(sequence) AS sequence FROM messages WHERE author = ?`, | ||||
| 				[this.id] | ||||
| 			) | ||||
| 			.then(function (result) { | ||||
| 				self.size = result[0].size; | ||||
| 				self.sequence = result[0].sequence; | ||||
| 			}); | ||||
| 		let edit; | ||||
| 		let follow; | ||||
| @@ -239,22 +308,28 @@ class TfProfileElement extends LitElement { | ||||
| 				</div> | ||||
| 			</div>` | ||||
| 			: null; | ||||
| 		let image = | ||||
| 			typeof profile.image == 'string' ? profile.image : profile.image?.link; | ||||
| 		let image = profile.image; | ||||
| 		if (typeof image == 'string' && !image.startsWith('&')) { | ||||
| 			try { | ||||
| 				image = JSON.parse(image)?.link; | ||||
| 			} catch {} | ||||
| 		} | ||||
| 		image = this.editing?.image ?? image; | ||||
| 		let description = this.editing?.description ?? profile.description; | ||||
| 		return html`<div class="w3-card-4 w3-container w3-theme-d3" style="box-sizing: border-box"> | ||||
| 		return html` | ||||
| 			<style>${generate_theme()}</style> | ||||
| 			<div class="w3-card-4 w3-container w3-theme-d3" style="box-sizing: border-box"> | ||||
| 			<header class="w3-container"> | ||||
| 				<p><tf-user id=${this.id} .users=${this.users}></tf-user> (${tfutils.human_readable_size(this.size)})</p> | ||||
| 				<p><tf-user id=${this.id} .users=${this.users}></tf-user> (${tfutils.human_readable_size(this.size)} in ${this.sequence} messages)</p> | ||||
| 			</header> | ||||
| 			<div class="w3-container"> | ||||
| 			<div class="w3-container" @click=${this.body_click}> | ||||
| 				<div class="w3-margin-bottom" style="display: flex; flex-direction: row"> | ||||
| 					<input type="text" class="w3-input w3-border w3-theme-d1" style="display: flex 1 1" readonly value=${this.id}></input> | ||||
| 					<button class="w3-button w3-theme-d1 w3-ripple" style="flex: 0 0 auto" @click=${this.copy_id}>Copy</button> | ||||
| 				</div> | ||||
| 				<div style="display: flex; flex-direction: row; gap: 1em"> | ||||
| 					${edit_profile} | ||||
| 					<div style="flex: 1 0 50%"> | ||||
| 					<div style="flex: 1 0 50%; contain: layout; overflow: auto; word-wrap: normal; word-break: normal"> | ||||
| 						${ | ||||
| 							image | ||||
| 								? html`<div><img src=${'/' + image + '/view'} style="width: 256px; height: auto"></img></div>` | ||||
| @@ -273,8 +348,12 @@ class TfProfileElement extends LitElement { | ||||
| 					Blocked by ${profile.blocked} identities. | ||||
| 				</div> | ||||
| 			</div> | ||||
| 			${until(this.load_follows(), html`<p>Loading accounts followed...</p>`)} | ||||
| 			<footer class="w3-container"> | ||||
| 				<p> | ||||
| 					<a class="w3-button w3-theme-d1" href=${'#🔐' + (this.id != this.whoami ? this.id : '')}> | ||||
| 						Open Private Chat | ||||
| 					</a> | ||||
| 					${edit} | ||||
| 					${follow} | ||||
| 					${block} | ||||
|   | ||||
| @@ -1,5 +1,5 @@ | ||||
| import {LitElement, html, unsafeHTML} from './lit-all.min.js'; | ||||
| import {styles} from './tf-styles.js'; | ||||
| import {styles, generate_theme} from './tf-styles.js'; | ||||
|  | ||||
| class TfReactionsModalElement extends LitElement { | ||||
| 	static get properties() { | ||||
| @@ -24,47 +24,57 @@ class TfReactionsModalElement extends LitElement { | ||||
| 	render() { | ||||
| 		let self = this; | ||||
| 		return this.votes?.length | ||||
| 			? html` <div | ||||
| 					class="w3-modal w3-animate-opacity" | ||||
| 					style="display: block; box-sizing: border-box; z-index: 10" | ||||
| 					@click=${this.clear} | ||||
| 				> | ||||
| 			? html` <style> | ||||
| 						${generate_theme()} | ||||
| 					</style> | ||||
| 					<div | ||||
| 						class="w3-modal-content w3-card-4 w3-theme-d1" | ||||
| 						onclick="event.stopPropagation()" | ||||
| 						class="w3-modal w3-animate-opacity" | ||||
| 						style="display: block; box-sizing: border-box; z-index: 10" | ||||
| 						@click=${this.clear} | ||||
| 					> | ||||
| 						<div class="w3-container w3-padding"> | ||||
| 							<header class="w3-container"> | ||||
| 								<h2>Reactions</h2> | ||||
| 								<span class="w3-button w3-display-topright" @click=${this.clear} | ||||
| 									>×</span | ||||
| 								> | ||||
| 							</header> | ||||
| 							<ul class="w3-theme-dark w3-container w3-ul"> | ||||
| 								${this.votes.map( | ||||
| 									(x) => html` | ||||
| 										<li class="w3-bar"> | ||||
| 											<span class="w3-bar-item" | ||||
| 												>${x?.content?.vote?.expression}</span | ||||
| 											> | ||||
| 											<tf-user | ||||
| 												class="w3-bar-item" | ||||
| 												id=${x.author} | ||||
| 												.users=${this.users} | ||||
| 											></tf-user> | ||||
| 											<span class="w3-bar-item w3-right" | ||||
| 												>${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 | ||||
| 							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> | ||||
| 				</div>` | ||||
| 					</div>` | ||||
| 			: undefined; | ||||
| 	} | ||||
| } | ||||
|   | ||||
| @@ -1,4 +1,5 @@ | ||||
| import {css, unsafeCSS} from './lit-all.min.js'; | ||||
| import {css, unsafeCSS, until} from './lit-all.min.js'; | ||||
| import * as tfrpc from '/static/tfrpc.js'; | ||||
|  | ||||
| const tf = css` | ||||
| 	img { | ||||
| @@ -43,6 +44,8 @@ const tf = css` | ||||
| 		border-left: 4px solid #fff; | ||||
| 		padding: 8px; | ||||
| 		padding-left: 12px; | ||||
| 		margin-left: 0; | ||||
| 		margin-right: 0; | ||||
| 	} | ||||
| `; | ||||
|  | ||||
| @@ -406,16 +409,8 @@ function is_dark(hex, value) { | ||||
| 	return (r * 299 + g * 587 + b * 114) / 1000 < value; | ||||
| } | ||||
|  | ||||
| function generated() { | ||||
| 	let now = new Date(); | ||||
| 	let k_color = rgb_to_hex([ | ||||
| 		(now.getDay() * 128) / 6, | ||||
| 		(now.getHours() * 128) / 23, | ||||
| 		(now.getSeconds() * 128) / 59, | ||||
| 	]); | ||||
| 	//let k_color = '#034f84'; | ||||
| 	//let k_color = rgb_to_hex([Math.random() * 256, Math.random() * 256, Math.random() * 256]); | ||||
| 	let [r, g, b] = hex_to_rgb(k_color); | ||||
| export function generate(color) { | ||||
| 	let [r, g, b] = hex_to_rgb(color); | ||||
| 	let [h, s, l] = rgb_to_hsl(r, g, b); | ||||
|  | ||||
| 	let theme1 = { | ||||
| @@ -459,4 +454,28 @@ function generated() { | ||||
| 	return unsafeCSS(result); | ||||
| } | ||||
|  | ||||
| export let styles = [tf, w3, generated()]; | ||||
| let g_theme; | ||||
| export function generate_theme() { | ||||
| 	return g_theme | ||||
| 		? g_theme | ||||
| 		: until( | ||||
| 				tfrpc.rpc.localStorageGet('color').then(function (value) { | ||||
| 					g_theme = generate(value ?? '#034f84'); | ||||
| 					return g_theme; | ||||
| 				}), | ||||
| 				generated_now() | ||||
| 			); | ||||
| } | ||||
|  | ||||
| function generated_now() { | ||||
| 	let now = new Date(); | ||||
| 	return generate( | ||||
| 		rgb_to_hex([ | ||||
| 			(now.getDay() * 128) / 6, | ||||
| 			(now.getHours() * 128) / 23, | ||||
| 			(now.getSeconds() * 128) / 59, | ||||
| 		]) | ||||
| 	); | ||||
| } | ||||
|  | ||||
| export let styles = [tf, w3]; | ||||
|   | ||||
| @@ -1,6 +1,6 @@ | ||||
| import {LitElement, html} from './lit-all.min.js'; | ||||
| import * as tfrpc from '/static/tfrpc.js'; | ||||
| import {styles} from './tf-styles.js'; | ||||
| import {styles, generate_theme} from './tf-styles.js'; | ||||
|  | ||||
| class TfTabConnectionsElement extends LitElement { | ||||
| 	static get properties() { | ||||
| @@ -15,6 +15,7 @@ class TfTabConnectionsElement extends LitElement { | ||||
| 			connect_attempt: {type: Object}, | ||||
| 			connect_message: {type: String}, | ||||
| 			connect_success: {type: Boolean}, | ||||
| 			peer_exchange: {type: Boolean}, | ||||
| 		}; | ||||
| 	} | ||||
|  | ||||
| @@ -47,6 +48,20 @@ class TfTabConnectionsElement extends LitElement { | ||||
| 		tfrpc.rpc.getServerIdentity().then(function (identity) { | ||||
| 			self.server_identity = identity; | ||||
| 		}); | ||||
| 		this.check_peer_exchange(); | ||||
| 	} | ||||
|  | ||||
| 	async check_peer_exchange() { | ||||
| 		if (await tfrpc.rpc.isAdministrator()) { | ||||
| 			this.peer_exchange = await tfrpc.rpc.globalSettingsGet('peer_exchange'); | ||||
| 		} else { | ||||
| 			this.peer_exchange = undefined; | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	async enable_peer_exchange() { | ||||
| 		await tfrpc.rpc.globalSettingsSet('peer_exchange', true); | ||||
| 		await this.check_peer_exchange(); | ||||
| 	} | ||||
|  | ||||
| 	render_connection_summary(connection) { | ||||
| @@ -251,7 +266,26 @@ class TfTabConnectionsElement extends LitElement { | ||||
| 	render() { | ||||
| 		let self = this; | ||||
| 		return html` | ||||
| 			<style> | ||||
| 				${generate_theme()} | ||||
| 			</style> | ||||
| 			<div class="w3-container" style="box-sizing: border-box"> | ||||
| 				<div | ||||
| 					class=${'w3-panel w3-padding w3-theme-l3' + | ||||
| 					(this.peer_exchange !== false ? ' w3-hide' : '')} | ||||
| 				> | ||||
| 					<p> | ||||
| 						Looking for connections? Enabling this option will include publicly | ||||
| 						advertised rooms and pubs among the list of discovered connections | ||||
| 						to help you replicate. | ||||
| 					</p> | ||||
| 					<button | ||||
| 						class="w3-button w3-theme-d1" | ||||
| 						@click=${this.enable_peer_exchange} | ||||
| 					> | ||||
| 						🔍🌐 Use publicly advertised peers | ||||
| 					</button> | ||||
| 				</div> | ||||
| 				<h2>New Connection</h2> | ||||
| 				<textarea class="w3-input w3-theme-d1" id="code"></textarea> | ||||
| 				${this.render_message(this.renderRoot.getElementById('code')?.value)} | ||||
| @@ -277,7 +311,7 @@ class TfTabConnectionsElement extends LitElement { | ||||
| 					class="w3-button w3-block w3-theme-d1" | ||||
| 					@click=${() => self.toggle_accordian('broadcasts')} | ||||
| 				> | ||||
| 					Broadcasts (${this.valid_broadcasts().length}) | ||||
| 					Discovery (${this.valid_broadcasts().length}) | ||||
| 				</h2> | ||||
| 				<ul class="w3-ul w3-border w3-hide" id="broadcasts"> | ||||
| 					${this.valid_broadcasts().map((x) => self.render_broadcast(x))} | ||||
| @@ -308,6 +342,12 @@ class TfTabConnectionsElement extends LitElement { | ||||
| 									<div class="w3-bar-item"> | ||||
| 										<tf-user id=${x.pubkey} .users=${self.users}></tf-user> | ||||
| 										<div><small>${x.address}:${x.port}</small></div> | ||||
| 										<div> | ||||
| 											<small | ||||
| 												>Last connection: | ||||
| 												${new Date(x.last_success * 1000)}</small | ||||
| 											> | ||||
| 										</div> | ||||
| 									</div> | ||||
| 								</div> | ||||
| 								${this.render_message(x)} | ||||
|   | ||||
| @@ -1,6 +1,6 @@ | ||||
| import {LitElement, cache, html, unsafeHTML, until} from './lit-all.min.js'; | ||||
| import * as tfrpc from '/static/tfrpc.js'; | ||||
| import {styles} from './tf-styles.js'; | ||||
| import {styles, generate_theme} from './tf-styles.js'; | ||||
|  | ||||
| class TfTabNewsFeedElement extends LitElement { | ||||
| 	static get properties() { | ||||
| @@ -18,6 +18,8 @@ class TfTabNewsFeedElement extends LitElement { | ||||
| 			time_range: {type: Array}, | ||||
| 			time_loading: {type: Array}, | ||||
| 			private_messages: {type: Array}, | ||||
| 			grouped_private_messages: {type: Object}, | ||||
| 			recent_reactions: {type: Array}, | ||||
| 		}; | ||||
| 	} | ||||
|  | ||||
| @@ -37,6 +39,7 @@ class TfTabNewsFeedElement extends LitElement { | ||||
| 		this.start_time = new Date().valueOf(); | ||||
| 		this.time_range = [0, 0]; | ||||
| 		this.time_loading = undefined; | ||||
| 		this.recent_reactions = []; | ||||
| 		this.loading = 0; | ||||
| 	} | ||||
|  | ||||
| @@ -81,21 +84,38 @@ class TfTabNewsFeedElement extends LitElement { | ||||
| 			`, | ||||
| 			[JSON.stringify(combined.map((x) => x.id))] | ||||
| 		); | ||||
| 		return [].concat(combined, await tfrpc.rpc.query( | ||||
| 			` | ||||
| 		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 messages | ||||
| 				JOIN json_each(?2) refs ON messages.id = refs.value | ||||
| 				JOIN json_each(?1) AS following ON messages.author = following.value | ||||
| 				WHERE messages.content ->> 'type' = 'vote' | ||||
| 				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))] | ||||
| 		)); | ||||
| 				[ | ||||
| 					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( | ||||
| 				` | ||||
| @@ -106,7 +126,7 @@ class TfTabNewsFeedElement extends LitElement { | ||||
| 						WHERE | ||||
| 							messages.author != ?1 AND | ||||
| 							(?3 IS NULL OR messages.timestamp >= ?3) AND messages.timestamp < ?4 | ||||
| 						ORDER BY timestamp DESC limit 20) | ||||
| 						ORDER BY timestamp DESC limit ?5) | ||||
| 					SELECT FALSE AS is_primary, messages.rowid, messages.id, messages.previous, messages.author, messages.sequence, messages.timestamp, messages.hash, json(messages.content) AS content, messages.signature | ||||
| 						FROM mentions | ||||
| 						JOIN messages_refs ON mentions.id = messages_refs.ref | ||||
| @@ -119,6 +139,7 @@ class TfTabNewsFeedElement extends LitElement { | ||||
| 					JSON.stringify(this.following), | ||||
| 					start_time, | ||||
| 					end_time, | ||||
| 					k_max_results, | ||||
| 				] | ||||
| 			); | ||||
| 		} else if (this.hash.startsWith('#@')) { | ||||
| @@ -128,7 +149,7 @@ class TfTabNewsFeedElement extends LitElement { | ||||
| 						selected AS (SELECT rowid, id, previous, author, sequence, timestamp, hash, json(content) AS content, signature | ||||
| 							FROM messages | ||||
| 							WHERE messages.author = ?1 AND (?2 IS NULL OR messages.timestamp >= 2) AND messages.timestamp < ?3 | ||||
| 							ORDER BY sequence DESC LIMIT 20 | ||||
| 							ORDER BY sequence DESC LIMIT ?4 | ||||
| 						) | ||||
| 					SELECT FALSE AS is_primary, messages.rowid, messages.id, messages.previous, messages.author, messages.sequence, messages.timestamp, messages.hash, json(messages.content) AS content, messages.signature | ||||
| 						FROM selected | ||||
| @@ -137,7 +158,7 @@ class TfTabNewsFeedElement extends LitElement { | ||||
| 					UNION | ||||
| 					SELECT TRUE AS is_primary, * FROM selected | ||||
| 				`, | ||||
| 				[this.hash.substring(1), start_time, end_time] | ||||
| 				[this.hash.substring(1), start_time, end_time, k_max_results] | ||||
| 			); | ||||
| 		} else if (this.hash.startsWith('#%')) { | ||||
| 			result = await tfrpc.rpc.query( | ||||
| @@ -162,25 +183,24 @@ class TfTabNewsFeedElement extends LitElement { | ||||
| 							SELECT messages.rowid, messages.id, messages.previous, messages.author, messages.sequence, messages.timestamp, messages.hash, json(messages.content) AS content, messages.signature | ||||
| 								FROM messages | ||||
| 								JOIN json_each(?) AS following ON messages.author = following.value | ||||
| 								WHERE messages.content ->> 'channel' = ?4 | ||||
| 								WHERE messages.content ->> 'channel' = ?4 AND messages.content ->> 'type' != 'vote' | ||||
| 							UNION | ||||
| 							SELECT messages.rowid, messages.id, messages.previous, messages.author, messages.sequence, messages.timestamp, messages.hash, json(messages.content) AS content, messages.signature | ||||
| 								FROM messages_fts(?5) | ||||
| 								JOIN messages ON messages.rowid = messages_fts.rowid | ||||
| 								FROM messages_refs | ||||
| 								JOIN messages ON messages.id = messages_refs.message | ||||
| 								JOIN json_each(?1) AS following ON messages.author = following.value | ||||
| 								JOIN json_tree(messages.content, '$.mentions') AS mention ON mention.value = '#' || ?4 | ||||
| 							), | ||||
| 						news AS (SELECT * FROM all_news | ||||
| 							WHERE (?2 IS NULL OR all_news.timestamp >= ?2) AND all_news.timestamp < ?3 | ||||
| 							ORDER BY all_news.timestamp DESC LIMIT 20) | ||||
| 					SELECT TRUE AS is_primary, news.* FROM news | ||||
| 								WHERE messages_refs.ref = '#' || ?4 AND messages.content ->> 'type' != 'vote' | ||||
| 							) | ||||
| 					SELECT TRUE AS is_primary, all_news.* FROM all_news | ||||
| 						WHERE (?2 IS NULL OR all_news.timestamp >= ?2) AND all_news.timestamp < ?3 | ||||
| 						ORDER BY all_news.timestamp DESC LIMIT ?5 | ||||
| 				`, | ||||
| 				[ | ||||
| 					JSON.stringify(this.following), | ||||
| 					start_time, | ||||
| 					end_time, | ||||
| 					this.hash.substring(2), | ||||
| 					'"#' + this.hash.substring(2).replace('"', '""') + '"', | ||||
| 					k_max_results, | ||||
| 				] | ||||
| 			); | ||||
| 			let t1 = new Date(); | ||||
| @@ -189,7 +209,9 @@ class TfTabNewsFeedElement extends LitElement { | ||||
| 			console.log( | ||||
| 				`load of ${result.length} rows took ${(t2 - t0) / 1000} (${(t1 - t0) / 1000} to find ${initial_messages.length} initial messages, ${(t2 - t1) / 1000} to find ${result.length} total messages) following=${this.following.length} st=${start_time} et=${end_time}` | ||||
| 			); | ||||
| 		} else if (this.hash == '#🔐') { | ||||
| 		} else if (this.hash.startsWith('#🔐')) { | ||||
| 			let ids = | ||||
| 				this.hash == '#🔐' ? [] : this.hash.substring('#🔐'.length).split(','); | ||||
| 			result = await tfrpc.rpc.query( | ||||
| 				` | ||||
| 					SELECT TRUE AS is_primary, messages.rowid, messages.id, previous, author, sequence, timestamp, hash, json(content) AS content, signature | ||||
| @@ -198,29 +220,64 @@ class TfTabNewsFeedElement extends LitElement { | ||||
| 					WHERE | ||||
| 						(?2 IS NULL OR (messages.timestamp >= ?2)) AND messages.timestamp < ?3 AND | ||||
| 						json(messages.content) LIKE '"%' | ||||
| 					ORDER BY messages.sequence DESC LIMIT 20 | ||||
| 					ORDER BY messages.rowid DESC LIMIT ?4 | ||||
| 				`, | ||||
| 				[JSON.stringify(this.private_messages), start_time, end_time] | ||||
| 				[ | ||||
| 					JSON.stringify( | ||||
| 						this.grouped_private_messages?.[JSON.stringify(ids)]?.map( | ||||
| 							(x) => x.id | ||||
| 						) ?? [] | ||||
| 					), | ||||
| 					start_time, | ||||
| 					end_time, | ||||
| 					k_max_results, | ||||
| 				] | ||||
| 			); | ||||
| 			result = (await this.decrypt(result)).filter((x) => x.decrypted); | ||||
| 		} else if (this.hash == '#👍') { | ||||
| 			result = await tfrpc.rpc.query( | ||||
| 				` | ||||
| 					WITH votes AS (SELECT messages.rowid, messages.id, messages.previous, messages.author, messages.sequence, messages.timestamp, messages.hash, json(messages.content) AS content, messages.signature | ||||
| 						FROM messages | ||||
| 						JOIN json_each(?1) AS following ON messages.author = following.value | ||||
| 						WHERE | ||||
| 							messages.content ->> 'type' = 'vote' AND | ||||
| 							(?2 IS NULL OR messages.timestamp >= ?2) AND messages.timestamp < ?3 | ||||
| 						ORDER BY timestamp DESC limit ?4) | ||||
| 					SELECT FALSE AS is_primary, messages.rowid, messages.id, messages.previous, messages.author, messages.sequence, messages.timestamp, messages.hash, json(messages.content) AS content, messages.signature | ||||
| 						FROM votes | ||||
| 						JOIN messages ON messages.id = votes.content ->> '$.vote.link' | ||||
| 					UNION | ||||
| 					SELECT TRUE AS is_primary, * FROM votes | ||||
| 				`, | ||||
| 				[JSON.stringify(this.following), start_time, end_time, k_max_results] | ||||
| 			); | ||||
| 		} else { | ||||
| 			let t0 = new Date(); | ||||
| 			let initial_messages = await tfrpc.rpc.query( | ||||
| 				` | ||||
| 					WITH | ||||
| 						all_news AS ( | ||||
| 							SELECT messages.rowid, messages.id, messages.previous, messages.author, messages.sequence, messages.timestamp, messages.hash, json(messages.content) AS content, messages.signature | ||||
| 						channels AS (SELECT '#' || value AS value FROM json_each(?5)) | ||||
| 					SELECT TRUE AS is_primary, messages.rowid, messages.id, messages.previous, messages.author, messages.sequence, messages.timestamp, messages.hash, json(messages.content) AS content, messages.signature | ||||
| 							FROM messages | ||||
| 							JOIN json_each(?) AS following ON messages.author = following.value | ||||
| 						), | ||||
| 						news AS ( | ||||
| 							SELECT * FROM all_news | ||||
| 							WHERE all_news.timestamp < ?3 AND (?2 IS NULL OR all_news.timestamp >= ?2) | ||||
| 							ORDER BY timestamp DESC LIMIT 20 | ||||
| 						) | ||||
| 					SELECT TRUE AS is_primary, news.* FROM news | ||||
| 							JOIN json_each(?1) AS following ON messages.author = following.value | ||||
| 							WHERE messages.timestamp < ?3 AND (?2 IS NULL OR messages.timestamp >= ?2) AND | ||||
| 								messages.content ->> 'type' != 'vote' AND | ||||
| 								(messages.content ->> 'root' IS NULL OR ( | ||||
| 									NOT EXISTS (SELECT * FROM messages root JOIN channels ON ('#' || (root.content ->> 'channel')) = channels.value WHERE root.id = messages.content ->> 'root') AND | ||||
| 									NOT EXISTS (SELECT * FROM messages root JOIN messages_refs ON root.id = messages.content ->> 'root' JOIN channels ON messages_refs.message = root.id AND messages_refs.ref = channels.value) | ||||
| 								)) AND | ||||
| 								(messages.content ->> 'channel' IS NULL OR ('#' || (messages.content ->> 'channel')) NOT IN (SELECT * FROM channels)) AND | ||||
| 								NOT EXISTS (SELECT * FROM messages_refs JOIN channels ON messages_refs.message = messages.id AND messages_refs.ref = channels.value) | ||||
| 					ORDER BY timestamp DESC LIMIT ?4 | ||||
| 				`, | ||||
| 				[JSON.stringify(this.following), start_time, end_time] | ||||
| 				[ | ||||
| 					JSON.stringify(this.following), | ||||
| 					start_time, | ||||
| 					end_time, | ||||
| 					k_max_results, | ||||
| 					JSON.stringify(Object.keys(this.channels_latest)), | ||||
| 				] | ||||
| 			); | ||||
| 			let t1 = new Date(); | ||||
| 			result = await this._fetch_related_messages(initial_messages); | ||||
| @@ -247,13 +304,24 @@ class TfTabNewsFeedElement extends LitElement { | ||||
| 		]; | ||||
| 	} | ||||
|  | ||||
| 	unread_allowed() { | ||||
| 		return ( | ||||
| 			this.hash == '#@' || | ||||
| 			(!this.hash.startsWith('#%') && !this.hash.startsWith('#@')) | ||||
| 		); | ||||
| 	} | ||||
|  | ||||
| 	async load_more() { | ||||
| 		this.loading++; | ||||
| 		this.loading_canceled = false; | ||||
| 		try { | ||||
| 			let more = []; | ||||
| 			let last_start_time = this.time_range[0]; | ||||
| 			more = await this.fetch_messages(null, last_start_time); | ||||
| 			try { | ||||
| 				more = await this.fetch_messages(null, last_start_time); | ||||
| 			} catch (e) { | ||||
| 				console.log(e); | ||||
| 			} | ||||
| 			this.update_time_range_from_messages( | ||||
| 				more.filter((x) => x.timestamp < last_start_time) | ||||
| 			); | ||||
| @@ -332,12 +400,19 @@ class TfTabNewsFeedElement extends LitElement { | ||||
| 		let self = this; | ||||
| 		this.loading++; | ||||
| 		let messages = []; | ||||
| 		let original_hash = this.hash; | ||||
| 		try { | ||||
| 			if (this._messages_hash !== this.hash) { | ||||
| 				this.messages = []; | ||||
| 				this._messages_hash = this.hash; | ||||
| 			} | ||||
| 			this._messages_following = this.following; | ||||
| 			this._messages_following = JSON.stringify(this.following); | ||||
| 			this._private_messages = | ||||
| 				JSON.stringify(this.private_messages) + | ||||
| 				JSON.stringify(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; | ||||
| @@ -350,7 +425,9 @@ class TfTabNewsFeedElement extends LitElement { | ||||
| 		} finally { | ||||
| 			this.loading--; | ||||
| 		} | ||||
| 		this.messages = this.merge_messages(this.messages, messages); | ||||
| 		if (this.hash == original_hash) { | ||||
| 			this.messages = this.merge_messages(this.messages, messages); | ||||
| 		} | ||||
| 		this.time_loading = undefined; | ||||
| 		console.log( | ||||
| 			`loading ${messages.length} messages done for ${self.whoami} in ${(new Date() - start_time) / 1000}s` | ||||
| @@ -376,12 +453,44 @@ class TfTabNewsFeedElement extends LitElement { | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	close_private_chat() { | ||||
| 		this.mark_all_read(); | ||||
| 		this.dispatchEvent( | ||||
| 			new CustomEvent('closeprivatechat', { | ||||
| 				bubbles: true, | ||||
| 				composed: true, | ||||
| 				detail: { | ||||
| 					key: JSON.stringify( | ||||
| 						this.hash == '#🔐' | ||||
| 							? [] | ||||
| 							: this.hash.substring('#🔐'.length).split(',') | ||||
| 					), | ||||
| 				}, | ||||
| 			}) | ||||
| 		); | ||||
| 		tfrpc.rpc.setHash('#'); | ||||
| 	} | ||||
|  | ||||
| 	render_close_chat_button() { | ||||
| 		if (this.hash.startsWith('#🔐')) { | ||||
| 			return html` | ||||
| 				<button class="w3-button w3-theme-d1" @click=${this.close_private_chat}> | ||||
| 					Close Chat | ||||
| 				</button> | ||||
| 			`; | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	render() { | ||||
| 		if ( | ||||
| 			!this.messages || | ||||
| 			this._messages_hash !== this.hash || | ||||
| 			JSON.stringify(this._messages_following) !== | ||||
| 				JSON.stringify(this.following) | ||||
| 			this._messages_following !== JSON.stringify(this.following) || | ||||
| 			this._private_messages !== | ||||
| 				JSON.stringify(this.private_messages) + | ||||
| 					JSON.stringify(this.grouped_private_messages) || | ||||
| 			this._channels_latest !== | ||||
| 				JSON.stringify(Object.keys(this.channels_latest)) | ||||
| 		) { | ||||
| 			console.log( | ||||
| 				`loading messages for ${this.whoami} (following ${this.following.length})` | ||||
| @@ -392,9 +501,16 @@ class TfTabNewsFeedElement extends LitElement { | ||||
| 		if (!this.hash.startsWith('#%')) { | ||||
| 			more = html` | ||||
| 				<p> | ||||
| 					<button class="w3-button w3-theme-d1" @click=${this.mark_all_read}> | ||||
| 						Mark All Read | ||||
| 					</button> | ||||
| 					${this.unread_allowed() | ||||
| 						? html` | ||||
| 								<button | ||||
| 									class="w3-button w3-theme-d1" | ||||
| 									@click=${this.mark_all_read} | ||||
| 								> | ||||
| 									Mark All Read | ||||
| 								</button> | ||||
| 							` | ||||
| 						: undefined} | ||||
| 					<button | ||||
| 						?disabled=${this.loading} | ||||
| 						class="w3-button w3-theme-d1" | ||||
| @@ -426,9 +542,18 @@ class TfTabNewsFeedElement extends LitElement { | ||||
| 			`; | ||||
| 		} | ||||
| 		return cache(html` | ||||
| 			<button class="w3-button w3-theme-d1" @click=${this.mark_all_read}> | ||||
| 				Mark All Read | ||||
| 			</button> | ||||
| 			<style> | ||||
| 				${generate_theme()} | ||||
| 			</style> | ||||
| 			${this.unread_allowed() | ||||
| 				? html`<button | ||||
| 						class="w3-button w3-theme-d1" | ||||
| 						@click=${this.mark_all_read} | ||||
| 					> | ||||
| 						Mark All Read | ||||
| 					</button>` | ||||
| 				: undefined} | ||||
| 			${this.render_close_chat_button()} | ||||
| 			<tf-news | ||||
| 				id="news" | ||||
| 				whoami=${this.whoami} | ||||
| @@ -437,8 +562,11 @@ class TfTabNewsFeedElement extends LitElement { | ||||
| 				.following=${this.following} | ||||
| 				.drafts=${this.drafts} | ||||
| 				.expanded=${this.expanded} | ||||
| 				hash=${this.hash} | ||||
| 				channel=${this.channel()} | ||||
| 				channel_unread=${this.channels_unread?.[this.channel()]} | ||||
| 				.recent_reactions=${this.recent_reactions} | ||||
| 				@mark_all_read=${this.mark_all_read} | ||||
| 			></tf-news> | ||||
| 			${more} | ||||
| 		`); | ||||
|   | ||||
| @@ -7,7 +7,7 @@ import { | ||||
| 	until, | ||||
| } from './lit-all.min.js'; | ||||
| import * as tfrpc from '/static/tfrpc.js'; | ||||
| import {styles} from './tf-styles.js'; | ||||
| import {styles, generate_theme} from './tf-styles.js'; | ||||
|  | ||||
| class TfTabNewsElement extends LitElement { | ||||
| 	static get properties() { | ||||
| @@ -24,6 +24,12 @@ class TfTabNewsElement extends LitElement { | ||||
| 			channels_latest: {type: Object}, | ||||
| 			connections: {type: Array}, | ||||
| 			private_messages: {type: Array}, | ||||
| 			grouped_private_messages: {type: Object}, | ||||
| 			visible_private_messages: {type: Object}, | ||||
| 			recent_reactions: {type: Array}, | ||||
| 			peer_exchange: {type: Boolean}, | ||||
| 			is_administrator: {type: Boolean}, | ||||
| 			stay_connected: {type: Boolean}, | ||||
| 		}; | ||||
| 	} | ||||
|  | ||||
| @@ -43,9 +49,11 @@ class TfTabNewsElement extends LitElement { | ||||
| 		this.channels_latest = {}; | ||||
| 		this.channels = []; | ||||
| 		this.connections = []; | ||||
| 		this.recent_reactions = []; | ||||
| 		tfrpc.rpc.localStorageGet('drafts').then(function (d) { | ||||
| 			self.drafts = JSON.parse(d || '{}'); | ||||
| 		}); | ||||
| 		this.check_peer_exchange(); | ||||
| 	} | ||||
|  | ||||
| 	connectedCallback() { | ||||
| @@ -58,6 +66,14 @@ class TfTabNewsElement extends LitElement { | ||||
| 		document.body.removeEventListener('keypress', this.on_keypress.bind(this)); | ||||
| 	} | ||||
|  | ||||
| 	async check_peer_exchange() { | ||||
| 		if (await tfrpc.rpc.isAdministrator()) { | ||||
| 			this.peer_exchange = await tfrpc.rpc.globalSettingsGet('peer_exchange'); | ||||
| 		} else { | ||||
| 			this.peer_exchange = undefined; | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	load_latest() { | ||||
| 		let news = this.shadowRoot?.getElementById('news'); | ||||
| 		if (news) { | ||||
| @@ -95,7 +111,26 @@ class TfTabNewsElement extends LitElement { | ||||
| 	} | ||||
|  | ||||
| 	unread_status(channel) { | ||||
| 		if ( | ||||
| 		if (channel === undefined) { | ||||
| 			if ( | ||||
| 				Object.keys(this.channels_unread).some((x) => this.unread_status(x)) | ||||
| 			) { | ||||
| 				return '✉️ '; | ||||
| 			} | ||||
| 		} else if (channel?.startsWith('🔐')) { | ||||
| 			let key = JSON.stringify(channel.substring('🔐'.length).split(',')); | ||||
| 			if (this.grouped_private_messages?.[key]) { | ||||
| 				let grouped_latest = Math.max( | ||||
| 					...this.grouped_private_messages?.[key]?.map((x) => x.rowid) | ||||
| 				); | ||||
| 				if ( | ||||
| 					this.channels_unread[channel] === undefined || | ||||
| 					grouped_latest > this.channels_unread[channel] | ||||
| 				) { | ||||
| 					return '✉️ '; | ||||
| 				} | ||||
| 			} | ||||
| 		} else if ( | ||||
| 			this.channels_latest[channel] && | ||||
| 			this.channels_latest[channel] > 0 && | ||||
| 			(this.channels_unread[channel] === undefined || | ||||
| @@ -136,11 +171,8 @@ class TfTabNewsElement extends LitElement { | ||||
| 		return this.hash.startsWith('##') ? this.hash.substring(2) : undefined; | ||||
| 	} | ||||
|  | ||||
| 	compare_follows() { | ||||
| 		const now = new Date().valueOf(); | ||||
| 		return function (a, b) { | ||||
| 			return (b[1].ts > now ? -1 : b[1].ts) - (a[1].ts > now ? -1 : a[1].ts); | ||||
| 		}; | ||||
| 	compare_follows(a, b) { | ||||
| 		return b[1].ts > a[1].ts ? 1 : b[1].ts < a[1].ts ? -1 : 0; | ||||
| 	} | ||||
|  | ||||
| 	suggested_follows() { | ||||
| @@ -149,13 +181,24 @@ class TfTabNewsElement extends LitElement { | ||||
| 		 ** pinned at the top. | ||||
| 		 */ | ||||
| 		let self = this; | ||||
| 		let now = new Date().valueOf(); | ||||
| 		return Object.entries(this.users) | ||||
| 			.filter((x) => x[1].ts < now) | ||||
| 			.filter((x) => x[1].follow_depth > 1) | ||||
| 			.sort(self.compare_follows()) | ||||
| 			.sort(self.compare_follows) | ||||
| 			.slice(0, 8) | ||||
| 			.map((x) => x[0]); | ||||
| 	} | ||||
|  | ||||
| 	async enable_peer_exchange() { | ||||
| 		await tfrpc.rpc.globalSettingsSet('peer_exchange', true); | ||||
| 		await this.check_peer_exchange(); | ||||
| 	} | ||||
|  | ||||
| 	is_loading() { | ||||
| 		return this.shadowRoot?.getElementById('news')?.loading; | ||||
| 	} | ||||
|  | ||||
| 	render_sidebar() { | ||||
| 		return html` | ||||
| 			<div | ||||
| @@ -177,7 +220,7 @@ class TfTabNewsElement extends LitElement { | ||||
| 								href="#" | ||||
| 								class="w3-bar-item w3-button" | ||||
| 								style="font-weight: bold" | ||||
| 								>${this.hash.substring(2)}</a | ||||
| 								>${this.hash.substring(1)}</a | ||||
| 							> | ||||
| 						` | ||||
| 					: undefined} | ||||
| @@ -195,11 +238,34 @@ class TfTabNewsElement extends LitElement { | ||||
| 					>${this.unread_status('@')}@mentions</a | ||||
| 				> | ||||
| 				<a | ||||
| 					href="#🔐" | ||||
| 					href="#👍" | ||||
| 					class="w3-bar-item w3-button" | ||||
| 					style=${this.hash == '#🔐' ? 'font-weight: bold' : undefined} | ||||
| 					>${this.unread_status('🔐')}🔐private</a | ||||
| 					style=${this.hash == '#👍' ? 'font-weight: bold' : undefined} | ||||
| 					>${this.unread_status('👍')}👍votes</a | ||||
| 				> | ||||
| 				${Object.keys(this?.visible_private_messages ?? []) | ||||
| 					?.sort() | ||||
| 					?.map( | ||||
| 						(key) => html` | ||||
| 							<a | ||||
| 								href=${'#🔐' + JSON.parse(key).join(',')} | ||||
| 								class="w3-bar-item w3-button" | ||||
| 								style=${this.hash == '#🔐' + JSON.parse(key).join(',') | ||||
| 									? 'font-weight: bold' | ||||
| 									: undefined} | ||||
| 								>${this.unread_status('🔐' + JSON.parse(key).join(','))} | ||||
| 								${(key != '[]' ? JSON.parse(key) : [this.whoami]).map( | ||||
| 									(id) => html` | ||||
| 										<tf-user | ||||
| 											id=${id} | ||||
| 											nolink="true" | ||||
| 											.users=${this.users} | ||||
| 										></tf-user> | ||||
| 									` | ||||
| 								)}</a | ||||
| 							> | ||||
| 						` | ||||
| 					)} | ||||
| 				${Object.keys(this.drafts) | ||||
| 					.sort() | ||||
| 					.map( | ||||
| @@ -223,15 +289,62 @@ class TfTabNewsElement extends LitElement { | ||||
| 					` | ||||
| 				)} | ||||
|  | ||||
| 				<h4 class="w3-bar-item w3-theme-d2">Connections</h4> | ||||
| 				<a class="w3-bar-item w3-theme-d2 w3-button" href="#connections"> | ||||
| 					<h4 style="margin: 0">Connections</h4> | ||||
| 				</a> | ||||
| 				${this.connections?.filter((x) => x.id)?.length == 0 | ||||
| 					? html` | ||||
| 							<button | ||||
| 								class=${'w3-bar-item w3-button' + | ||||
| 								(this.connections?.some((x) => x.flags.one_shot) | ||||
| 									? ' w3-spin' | ||||
| 									: '')} | ||||
| 								@click=${() => | ||||
| 									this.dispatchEvent( | ||||
| 										new Event('refresh', {bubbles: true, composed: true}) | ||||
| 									)} | ||||
| 							> | ||||
| 								↻ Sync now | ||||
| 							</button> | ||||
| 							<button | ||||
| 								class="w3-bar-item w3-button w3-ripple" | ||||
| 								@click=${() => | ||||
| 									this.dispatchEvent( | ||||
| 										new Event('toggle_stay_connected', { | ||||
| 											bubbles: true, | ||||
| 											composed: true, | ||||
| 										}) | ||||
| 									)} | ||||
| 							> | ||||
| 								<span style="display: inline-block; width: 1.8em" | ||||
| 									>${this.stay_connected ? '🔗' : '⛓️💥'}</span | ||||
| 								> | ||||
| 								${this.stay_connected ? 'Online mode' : 'Passive mode'} | ||||
| 							</button> | ||||
| 							<button | ||||
| 								class=${'w3-bar-item w3-button w3-border w3-leftbar w3-rightbar' + | ||||
| 								(this.peer_exchange !== false ? ' w3-hide' : '')} | ||||
| 								@click=${this.enable_peer_exchange} | ||||
| 							> | ||||
| 								🔍🌐 Use publicly advertised peers | ||||
| 							</button> | ||||
| 						` | ||||
| 					: undefined} | ||||
| 				${this.connections | ||||
| 					.filter((x) => x.id && !x.destroy_reason) | ||||
| 					.filter((x) => x.id) | ||||
| 					.map( | ||||
| 						(x) => html` | ||||
| 							<tf-user | ||||
| 								class="w3-bar-item" | ||||
| 								style="max-width: 100%" | ||||
| 								style=${x.destroy_reason | ||||
| 									? 'border-left: 4px solid red; border-right: 4px solid red' | ||||
| 									: x.connected | ||||
| 										? x.flags?.one_shot | ||||
| 											? 'border-left: 4px solid blue; border-right: 4px solid blue' | ||||
| 											: 'border-left: 4px solid green; border-right: 4px solid green' | ||||
| 										: ''} | ||||
| 								id=${x.id} | ||||
| 								fallback_name=${x.host} | ||||
| 								.users=${this.users} | ||||
| 							></tf-user> | ||||
| 						` | ||||
| @@ -283,9 +396,12 @@ class TfTabNewsElement extends LitElement { | ||||
| 			</div>`; | ||||
| 		} | ||||
| 		return cache(html` | ||||
| 			<style> | ||||
| 				${generate_theme()} | ||||
| 			</style> | ||||
| 			${this.render_sidebar()} | ||||
| 			<div | ||||
| 				style="margin-left: 2in; padding: 0px; top: 0; max-height: 100%; overflow: auto; contain: layout" | ||||
| 				style="margin-left: 2in; padding: 0px; top: 0; height: 100vh; max-height: 100%; overflow: auto; contain: layout" | ||||
| 				id="main" | ||||
| 				class="w3-main" | ||||
| 			> | ||||
| @@ -310,7 +426,7 @@ class TfTabNewsElement extends LitElement { | ||||
| 							class="w3-button w3-hide-large" | ||||
| 							@click=${this.show_sidebar} | ||||
| 						> | ||||
| 							☰ | ||||
| 							${this.unread_status()}☰ | ||||
| 						</div> | ||||
| 						Welcome, <tf-user id=${this.whoami} .users=${this.users}></tf-user>! | ||||
| 						${edit_profile} | ||||
| @@ -323,6 +439,9 @@ class TfTabNewsElement extends LitElement { | ||||
| 							.drafts=${this.drafts} | ||||
| 							@tf-draft=${this.draft} | ||||
| 							.channel=${this.channel()} | ||||
| 							.recipients=${this.hash.startsWith('#🔐') | ||||
| 								? this.hash.substring('#🔐'.length).split(',') | ||||
| 								: undefined} | ||||
| 						></tf-compose> | ||||
| 					</div> | ||||
| 					${profile} | ||||
| @@ -339,6 +458,8 @@ class TfTabNewsElement extends LitElement { | ||||
| 						.channels_unread=${this.channels_unread} | ||||
| 						.channels_latest=${this.channels_latest} | ||||
| 						.private_messages=${this.private_messages} | ||||
| 						.grouped_private_messages=${this.grouped_private_messages} | ||||
| 						.recent_reactions=${this.recent_reactions} | ||||
| 					></tf-tab-news-feed> | ||||
| 				</div> | ||||
| 			</div> | ||||
|   | ||||
| @@ -1,136 +0,0 @@ | ||||
| import {LitElement, html, unsafeHTML} from './lit-all.min.js'; | ||||
| import * as tfrpc from '/static/tfrpc.js'; | ||||
| import {styles} from './tf-styles.js'; | ||||
|  | ||||
| class TfTabQueryElement extends LitElement { | ||||
| 	static get properties() { | ||||
| 		return { | ||||
| 			whoami: {type: String}, | ||||
| 			users: {type: Object}, | ||||
| 			following: {type: Array}, | ||||
| 			query: {type: String}, | ||||
| 			expanded: {type: Object}, | ||||
| 			results: {type: Array}, | ||||
| 			error: {type: Object}, | ||||
| 			duration: {type: Number}, | ||||
| 		}; | ||||
| 	} | ||||
|  | ||||
| 	static styles = styles; | ||||
|  | ||||
| 	constructor() { | ||||
| 		super(); | ||||
| 		let self = this; | ||||
| 		this.whoami = null; | ||||
| 		this.users = {}; | ||||
| 		this.following = []; | ||||
| 		this.expanded = {}; | ||||
| 		this.duration = undefined; | ||||
| 	} | ||||
|  | ||||
| 	async search(query) { | ||||
| 		console.log('Searching...', this.whoami, query); | ||||
| 		this.results = []; | ||||
| 		this.error = undefined; | ||||
| 		this.duration = undefined; | ||||
| 		let search = this.renderRoot.getElementById('search'); | ||||
| 		if (search) { | ||||
| 			search.value = query; | ||||
| 			search.focus(); | ||||
| 		} | ||||
| 		await tfrpc.rpc.setHash('#sql=' + encodeURIComponent(query)); | ||||
| 		let start_time = new Date(); | ||||
| 		try { | ||||
| 			this.results = await tfrpc.rpc.query(query, []); | ||||
| 		} catch (error) { | ||||
| 			this.error = error; | ||||
| 		} | ||||
| 		let end_time = new Date(); | ||||
| 		this.duration = (end_time - start_time).valueOf(); | ||||
| 		console.log('Done.'); | ||||
| 		search = this.renderRoot.getElementById('search'); | ||||
| 		if (search) { | ||||
| 			search.value = query; | ||||
| 			search.focus(); | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	search_keydown(event) { | ||||
| 		if (event.keyCode == 13 && event.ctrlKey) { | ||||
| 			this.query = this.renderRoot.getElementById('search').value; | ||||
| 			event.preventDefault(); | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	on_expand(event) { | ||||
| 		if (event.detail.expanded) { | ||||
| 			let expand = {}; | ||||
| 			expand[event.detail.id] = true; | ||||
| 			this.expanded = Object.assign({}, this.expanded, expand); | ||||
| 		} else { | ||||
| 			delete this.expanded[event.detail.id]; | ||||
| 			this.expanded = Object.assign({}, this.expanded); | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	render_results() { | ||||
| 		if (!this.results?.length) { | ||||
| 			return html`<div>No results.</div>`; | ||||
| 		} else { | ||||
| 			let keys = Object.keys(this.results[0]).sort(); | ||||
| 			return html`<table style="width: 100%; max-width: 100%"> | ||||
| 				<tr> | ||||
| 					${keys.map((key) => html`<th>${key}</th>`)} | ||||
| 				</tr> | ||||
| 				${this.results.map( | ||||
| 					(row) => | ||||
| 						html`<tr> | ||||
| 							${keys.map((key) => html`<td>${row[key]}</td>`)} | ||||
| 						</tr>` | ||||
| 				)} | ||||
| 			</table>`; | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	render_error() { | ||||
| 		if (this.error) { | ||||
| 			return html`<h2 style="color: red">${this.error.message}</h2> | ||||
| 				<pre style="color: red">${this.error.stack}</pre>`; | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	render() { | ||||
| 		if (this.query !== this.last_query) { | ||||
| 			this.last_query = this.query; | ||||
| 			this.search(this.query); | ||||
| 		} | ||||
| 		let self = this; | ||||
| 		return html` | ||||
| 			<div style="display: flex; flex-direction: row; gap: 4px"> | ||||
| 				<textarea | ||||
| 					id="search" | ||||
| 					rows="8" | ||||
| 					class="w3-input w3-theme-d1" | ||||
| 					style="flex: 1; resize: vertical" | ||||
| 					@keydown=${this.search_keydown} | ||||
| 				> | ||||
| ${this.query}</textarea | ||||
| 				> | ||||
| 				<button | ||||
| 					class="w3-button w3-theme-d1" | ||||
| 					@click=${(event) => | ||||
| 						self.search(self.renderRoot.getElementById('search').value)} | ||||
| 				> | ||||
| 					Execute | ||||
| 				</button> | ||||
| 			</div> | ||||
| 			<div ?hidden=${this.duration === undefined}> | ||||
| 				Took ${this.duration / 1000.0} seconds. | ||||
| 			</div> | ||||
| 			<div ?hidden=${this.duration !== undefined}>Executing...</div> | ||||
| 			${this.render_error()} ${this.render_results()} | ||||
| 		`; | ||||
| 	} | ||||
| } | ||||
|  | ||||
| customElements.define('tf-tab-query', TfTabQueryElement); | ||||
| @@ -1,6 +1,6 @@ | ||||
| import {LitElement, html, unsafeHTML} from './lit-all.min.js'; | ||||
| import * as tfrpc from '/static/tfrpc.js'; | ||||
| import {styles} from './tf-styles.js'; | ||||
| import {styles, generate_theme} from './tf-styles.js'; | ||||
|  | ||||
| class TfTabSearchElement extends LitElement { | ||||
| 	static get properties() { | ||||
| @@ -11,6 +11,9 @@ class TfTabSearchElement extends LitElement { | ||||
| 			following: {type: Array}, | ||||
| 			query: {type: String}, | ||||
| 			expanded: {type: Object}, | ||||
| 			messages: {type: Array}, | ||||
| 			results: {type: Array}, | ||||
| 			error: {type: Object}, | ||||
| 		}; | ||||
| 	} | ||||
|  | ||||
| @@ -38,24 +41,40 @@ class TfTabSearchElement extends LitElement { | ||||
| 			search.select(); | ||||
| 		} | ||||
| 		await tfrpc.rpc.setHash('#q=' + encodeURIComponent(query)); | ||||
| 		let results = await tfrpc.rpc.query( | ||||
| 			` | ||||
| 				SELECT messages.id, messages.previous, messages.author, messages.sequence, messages.timestamp, messages.hash, json(messages.content) AS content, messages.signature | ||||
| 				FROM messages_fts(?) | ||||
| 				JOIN messages ON messages.rowid = messages_fts.rowid | ||||
| 				JOIN json_each(?) AS following ON messages.author = following.value | ||||
| 				ORDER BY timestamp DESC limit 100 | ||||
| 			`, | ||||
| 			['"' + query.replace('"', '""') + '"', JSON.stringify(this.following)] | ||||
| 		); | ||||
| 		console.log('Done.'); | ||||
| 		search = this.renderRoot.getElementById('search'); | ||||
| 		if (search) { | ||||
| 			search.value = query; | ||||
| 			search.focus(); | ||||
| 			search.select(); | ||||
| 		this.error = undefined; | ||||
| 		this.results = []; | ||||
| 		this.messages = []; | ||||
| 		if (query.startsWith('sql:')) { | ||||
| 			this.messages = []; | ||||
| 			try { | ||||
| 				this.results = await tfrpc.rpc.query( | ||||
| 					query.substring('sql:'.length), | ||||
| 					[] | ||||
| 				); | ||||
| 			} catch (e) { | ||||
| 				this.results = []; | ||||
| 				this.error = e; | ||||
| 			} | ||||
| 		} else { | ||||
| 			let results = await tfrpc.rpc.query( | ||||
| 				` | ||||
| 					SELECT messages.id, messages.previous, messages.author, messages.sequence, messages.timestamp, messages.hash, json(messages.content) AS content, messages.signature | ||||
| 					FROM messages_fts(?) | ||||
| 					JOIN messages ON messages.rowid = messages_fts.rowid | ||||
| 					JOIN json_each(?) AS following ON messages.author = following.value | ||||
| 					ORDER BY timestamp DESC limit 100 | ||||
| 				`, | ||||
| 				['"' + query.replace('"', '""') + '"', JSON.stringify(this.following)] | ||||
| 			); | ||||
| 			console.log('Done.'); | ||||
| 			search = this.renderRoot.getElementById('search'); | ||||
| 			if (search) { | ||||
| 				search.value = query; | ||||
| 				search.focus(); | ||||
| 				search.select(); | ||||
| 			} | ||||
| 			this.messages = results; | ||||
| 		} | ||||
| 		this.renderRoot.getElementById('news').messages = results; | ||||
| 	} | ||||
|  | ||||
| 	search_keydown(event) { | ||||
| @@ -87,6 +106,39 @@ class TfTabSearchElement extends LitElement { | ||||
| 		tfrpc.rpc.localStorageSet('drafts', JSON.stringify(this.drafts)); | ||||
| 	} | ||||
|  | ||||
| 	render_results() { | ||||
| 		if (this.error) { | ||||
| 			return html`<h2 style="color: red">${this.error.message}</h2> | ||||
| 				<pre style="color: red">${this.error.stack}</pre>`; | ||||
| 		} else if (this.messages?.length) { | ||||
| 			return html`<tf-news | ||||
| 				id="news" | ||||
| 				whoami=${this.whoami} | ||||
| 				.messages=${this.messages} | ||||
| 				.users=${this.users} | ||||
| 				.expanded=${this.expanded} | ||||
| 				.drafts=${this.drafts} | ||||
| 				@tf-expand=${this.on_expand} | ||||
| 				@tf-draft=${this.draft} | ||||
| 			></tf-news>`; | ||||
| 		} else if (this.results?.length) { | ||||
| 			let keys = Object.keys(this.results[0]).sort(); | ||||
| 			return html`<table style="width: 100%; max-width: 100%"> | ||||
| 				<tr> | ||||
| 					${keys.map((key) => html`<th>${key}</th>`)} | ||||
| 				</tr> | ||||
| 				${this.results.map( | ||||
| 					(row) => | ||||
| 						html`<tr> | ||||
| 							${keys.map((key) => html`<td>${row[key]}</td>`)} | ||||
| 						</tr>` | ||||
| 				)} | ||||
| 			</table>`; | ||||
| 		} else { | ||||
| 			return html`<div>No results.</div>`; | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	render() { | ||||
| 		if (this.query !== this.last_query) { | ||||
| 			this.last_query = this.query; | ||||
| @@ -94,11 +146,14 @@ class TfTabSearchElement extends LitElement { | ||||
| 		} | ||||
| 		let self = this; | ||||
| 		return html` | ||||
| 			<div style="display: flex; flex-direction: row; gap: 4px"> | ||||
| 				<input type="text" class="w3-input w3-theme-d1" id="search" value=${this.query} style="flex: 1" @keydown=${this.search_keydown}></input> | ||||
| 				<button class="w3-button w3-theme-d1" @click=${(event) => self.search(self.renderRoot.getElementById('search').value)}>Search</button> | ||||
| 			<style>${generate_theme()}</style> | ||||
| 			<div class="w3-padding"> | ||||
| 				<div style="display: flex; flex-direction: row; gap: 4px"> | ||||
| 					<input type="text" class="w3-input w3-theme-d1" id="search" value=${this.query} style="flex: 1" @keydown=${this.search_keydown}></input> | ||||
| 					<button class="w3-button w3-theme-d1" @click=${(event) => self.search(self.renderRoot.getElementById('search').value)}>Search</button> | ||||
| 				</div> | ||||
| 				${this.render_results()} | ||||
| 			</div> | ||||
| 			<tf-news id="news" whoami=${this.whoami} .messages=${this.messages} .users=${this.users} .expanded=${this.expanded} .drafts=${this.drafts} @tf-expand=${this.on_expand} @tf-draft=${this.draft}></tf-news> | ||||
| 		`; | ||||
| 	} | ||||
| } | ||||
|   | ||||
| @@ -1,5 +1,5 @@ | ||||
| import {LitElement, html, unsafeHTML} from './lit-all.min.js'; | ||||
| import {styles} from './tf-styles.js'; | ||||
| import {styles, generate_theme} from './tf-styles.js'; | ||||
|  | ||||
| class TfTagElement extends LitElement { | ||||
| 	static get properties() { | ||||
| @@ -17,11 +17,15 @@ class TfTagElement extends LitElement { | ||||
|  | ||||
| 	render() { | ||||
| 		let number = this.count ? html` (${this.count})` : undefined; | ||||
| 		return html`<a | ||||
| 			href=${'#' + encodeURIComponent(this.tag)} | ||||
| 			class="w3-tag w3-theme-d1 w3-round-4 w3-button" | ||||
| 			>${this.tag}${number}</a | ||||
| 		> `; | ||||
| 		return html` | ||||
| 			<style> | ||||
| 				${generate_theme()}</style | ||||
| 			><a | ||||
| 				href=${'#' + encodeURIComponent(this.tag)} | ||||
| 				class="w3-tag w3-theme-d1 w3-round-4 w3-button" | ||||
| 				>${this.tag}${number}</a | ||||
| 			> | ||||
| 		`; | ||||
| 	} | ||||
| } | ||||
|  | ||||
|   | ||||
| @@ -1,12 +1,15 @@ | ||||
| import {LitElement, html} from './lit-all.min.js'; | ||||
| import * as tfrpc from '/static/tfrpc.js'; | ||||
| import {styles} from './tf-styles.js'; | ||||
| import {styles, generate_theme} from './tf-styles.js'; | ||||
|  | ||||
| class TfUserElement extends LitElement { | ||||
| 	static get properties() { | ||||
| 		return { | ||||
| 			id: {type: String}, | ||||
| 			fallback_name: {type: String}, | ||||
| 			icon_only: {type: Boolean}, | ||||
| 			users: {type: Object}, | ||||
| 			nolink: {type: Boolean}, | ||||
| 		}; | ||||
| 	} | ||||
|  | ||||
| @@ -15,6 +18,8 @@ class TfUserElement extends LitElement { | ||||
| 	constructor() { | ||||
| 		super(); | ||||
| 		this.id = null; | ||||
| 		this.fallback_name = null; | ||||
| 		this.icon_only = false; | ||||
| 		this.users = {}; | ||||
| 	} | ||||
|  | ||||
| @@ -30,27 +35,38 @@ class TfUserElement extends LitElement { | ||||
| 			>😎</span | ||||
| 		>`; | ||||
| 		let name = this.users?.[this.id]?.name; | ||||
| 		name = html`<a target="_top" href=${'#' + this.id} | ||||
| 			>${name !== undefined ? name : this.id}</a | ||||
| 		>`; | ||||
| 		let name_string = name ?? this.fallback_name ?? this.id; | ||||
| 		name = this.icon_only | ||||
| 			? undefined | ||||
| 			: !this.nolink | ||||
| 				? html`<a target="_top" href=${'#' + this.id}>${name_string}</a>` | ||||
| 				: html`<span>${name_string}</span>`; | ||||
|  | ||||
| 		if (user) { | ||||
| 			let image_link = user.image; | ||||
| 			image_link = | ||||
| 				typeof image_link == 'string' ? image_link : image_link?.link; | ||||
| 			if (typeof image_link == 'string' && !image_link.startsWith('&')) { | ||||
| 				try { | ||||
| 					image_link = JSON.parse(image_link)?.link; | ||||
| 				} catch {} | ||||
| 			} | ||||
| 			if (image_link !== undefined) { | ||||
| 				image = html`<img | ||||
| 					class=${'w3-theme-l4 ' + shape} | ||||
| 					style="width: 2em; height: 2em; vertical-align: middle; object-fit: cover" | ||||
| 					src="/${image_link}/view" | ||||
| 					title=${name_string + ' (' + this.id + ')'} | ||||
| 				/>`; | ||||
| 			} | ||||
| 		} | ||||
| 		return html` <div | ||||
| 			style="display: inline-block; vertical-align: middle; font-weight: bold; text-wrap: nowrap; max-width: 100%; overflow: hidden; text-overflow: ellipsis" | ||||
| 		> | ||||
| 			${image} ${name} | ||||
| 		</div>`; | ||||
| 		return html` <style> | ||||
| 				${generate_theme()} | ||||
| 			</style> | ||||
| 			<div | ||||
| 				style=${'display: inline-block; vertical-align: middle; text-wrap: nowrap; max-width: 100%; overflow: hidden; text-overflow: ellipsis' + | ||||
| 				(this.nolink ? '' : '; font-weight: bold')} | ||||
| 			> | ||||
| 				${image} ${name} | ||||
| 			</div>`; | ||||
| 	} | ||||
| } | ||||
|  | ||||
|   | ||||
| @@ -50,9 +50,9 @@ function image(node, entering) { | ||||
| 						'</div>' | ||||
| 				); | ||||
| 				if (this.options.safe && potentiallyUnsafe(node.destination)) { | ||||
| 					this.lit('<img src="" alt="'); | ||||
| 					this.lit('<img src="" title="'); | ||||
| 				} else { | ||||
| 					this.lit('<img src="' + this.esc(node.destination) + '" alt="'); | ||||
| 					this.lit('<img src="' + this.esc(node.destination) + '" title="'); | ||||
| 				} | ||||
| 			} | ||||
| 			this.disableTags += 1; | ||||
| @@ -104,12 +104,12 @@ export function markdown(md) { | ||||
| 					node.destination.startsWith('@') && | ||||
| 					node.destination.endsWith('.ed25519') | ||||
| 				) { | ||||
| 					node.destination = '#' + node.destination; | ||||
| 					node.destination = '#' + encodeURIComponent(node.destination); | ||||
| 				} else if ( | ||||
| 					node.destination.startsWith('%') && | ||||
| 					node.destination.endsWith('.sha256') | ||||
| 				) { | ||||
| 					node.destination = '#' + node.destination; | ||||
| 					node.destination = '#' + encodeURIComponent(node.destination); | ||||
| 				} else if ( | ||||
| 					node.destination.startsWith('&') && | ||||
| 					node.destination.endsWith('.sha256') | ||||
|   | ||||
| @@ -1,5 +1,5 @@ | ||||
| { | ||||
| 	"type": "tildefriends-app", | ||||
| 	"emoji": "💾", | ||||
| 	"previous": "&mvGTlWKFR5QM/3nb4fJ2WQq0n/gNKvBmhGDkAvb8ki8=.sha256" | ||||
| 	"previous": "&tzZFIe7Y54O4sx1QtAPdemkXh+p5qHXSG/dlS7NP6OQ=.sha256" | ||||
| } | ||||
|   | ||||
| @@ -8,7 +8,7 @@ async function query(sql, args) { | ||||
|  | ||||
| async function get_biggest() { | ||||
| 	return query(` | ||||
| 		select author, sum(length(content)) as size from messages group by author order by size desc limit 10; | ||||
| 		select author, size from messages_stats group by author order by size desc limit 10; | ||||
| 	`); | ||||
| } | ||||
|  | ||||
| @@ -62,15 +62,14 @@ function nice_size(bytes) { | ||||
| } | ||||
|  | ||||
| async function main() { | ||||
| 	await app.setDocument( | ||||
| 		'<p style="color: #fff">Finding the top 10 largest feeds...</p>' | ||||
| 	); | ||||
| 	let most_follows = await get_most_follows(); | ||||
| 	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), | ||||
| @@ -94,7 +93,7 @@ async function main() { | ||||
| 	} | ||||
| 	let html = `<body style="color: #000; background-color: #ddd">\n | ||||
| 		<h1>Storage Summary</h1> | ||||
| 		<h2>Top 10 Accounts by Size</h2> | ||||
| 		<h2>Top Accounts by Size</h2> | ||||
| 		<ol>`; | ||||
| 	for (let item of biggest) { | ||||
| 		html += `<li> | ||||
| @@ -105,7 +104,7 @@ async function main() { | ||||
| 	} | ||||
| 	html += ` | ||||
| 		</ol> | ||||
| 		<h2>Top 10 Accounts by Follows</h2> | ||||
| 		<h2>Top Accounts by Follows</h2> | ||||
| 		<ol>`; | ||||
| 	for (let item of most_follows) { | ||||
| 		html += `<li> | ||||
|   | ||||
| @@ -1,5 +1,5 @@ | ||||
| { | ||||
| 	"type": "tildefriends-app", | ||||
| 	"emoji": "👋", | ||||
| 	"previous": "&wAb7J6E35xEXpiXsQ6t1RaWTGIvlatUnyH8ipF6pVic=.sha256" | ||||
| 	"previous": "&n1QkPkB5JoduFSx8UKOY3IlZqS2GwLiTUZv4ZrEOthQ=.sha256" | ||||
| } | ||||
|   | ||||
| @@ -1,5 +0,0 @@ | ||||
| async function main() { | ||||
| 	await app.setDocument(utf8Decode(getFile('index.html'))); | ||||
| } | ||||
|  | ||||
| main(); | ||||
							
								
								
									
										1
									
								
								apps/welcome/gitea.svg
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1 @@ | ||||
| <svg xmlns="http://www.w3.org/2000/svg" xml:space="preserve" viewBox="0 0 640 640" width="32" height="32"><path d="m395.9 484.2-126.9-61c-12.5-6-17.9-21.2-11.8-33.8l61-126.9c6-12.5 21.2-17.9 33.8-11.8 17.2 8.3 27.1 13 27.1 13l-.1-109.2 16.7-.1.1 117.1s57.4 24.2 83.1 40.1c3.7 2.3 10.2 6.8 12.9 14.4 2.1 6.1 2 13.1-1 19.3l-61 126.9c-6.2 12.7-21.4 18.1-33.9 12" style="fill:#fff"/><path d="M622.7 149.8c-4.1-4.1-9.6-4-9.6-4s-117.2 6.6-177.9 8c-13.3.3-26.5.6-39.6.7v117.2c-5.5-2.6-11.1-5.3-16.6-7.9 0-36.4-.1-109.2-.1-109.2-29 .4-89.2-2.2-89.2-2.2s-141.4-7.1-156.8-8.5c-9.8-.6-22.5-2.1-39 1.5-8.7 1.8-33.5 7.4-53.8 26.9C-4.9 212.4 6.6 276.2 8 285.8c1.7 11.7 6.9 44.2 31.7 72.5 45.8 56.1 144.4 54.8 144.4 54.8s12.1 28.9 30.6 55.5c25 33.1 50.7 58.9 75.7 62 63 0 188.9-.1 188.9-.1s12 .1 28.3-10.3c14-8.5 26.5-23.4 26.5-23.4S547 483 565 451.5c5.5-9.7 10.1-19.1 14.1-28 0 0 55.2-117.1 55.2-231.1-1.1-34.5-9.6-40.6-11.6-42.6M125.6 353.9c-25.9-8.5-36.9-18.7-36.9-18.7S69.6 321.8 60 295.4c-16.5-44.2-1.4-71.2-1.4-71.2s8.4-22.5 38.5-30c13.8-3.7 31-3.1 31-3.1s7.1 59.4 15.7 94.2c7.2 29.2 24.8 77.7 24.8 77.7s-26.1-3.1-43-9.1m300.3 107.6s-6.1 14.5-19.6 15.4c-5.8.4-10.3-1.2-10.3-1.2s-.3-.1-5.3-2.1l-112.9-55s-10.9-5.7-12.8-15.6c-2.2-8.1 2.7-18.1 2.7-18.1L322 273s4.8-9.7 12.2-13c.6-.3 2.3-1 4.5-1.5 8.1-2.1 18 2.8 18 2.8L467.4 315s12.6 5.7 15.3 16.2c1.9 7.4-.5 14-1.8 17.2-6.3 15.4-55 113.1-55 113.1" style="fill:#609926"/><path d="M326.8 380.1c-8.2.1-15.4 5.8-17.3 13.8s2 16.3 9.1 20c7.7 4 17.5 1.8 22.7-5.4 5.1-7.1 4.3-16.9-1.8-23.1l24-49.1c1.5.1 3.7.2 6.2-.5 4.1-.9 7.1-3.6 7.1-3.6 4.2 1.8 8.6 3.8 13.2 6.1 4.8 2.4 9.3 4.9 13.4 7.3.9.5 1.8 1.1 2.8 1.9 1.6 1.3 3.4 3.1 4.7 5.5 1.9 5.5-1.9 14.9-1.9 14.9-2.3 7.6-18.4 40.6-18.4 40.6-8.1-.2-15.3 5-17.7 12.5-2.6 8.1 1.1 17.3 8.9 21.3s17.4 1.7 22.5-5.3c5-6.8 4.6-16.3-1.1-22.6 1.9-3.7 3.7-7.4 5.6-11.3 5-10.4 13.5-30.4 13.5-30.4.9-1.7 5.7-10.3 2.7-21.3-2.5-11.4-12.6-16.7-12.6-16.7-12.2-7.9-29.2-15.2-29.2-15.2s0-4.1-1.1-7.1c-1.1-3.1-2.8-5.1-3.9-6.3 4.7-9.7 9.4-19.3 14.1-29-4.1-2-8.1-4-12.2-6.1-4.8 9.8-9.7 19.7-14.5 29.5-6.7-.1-12.9 3.5-16.1 9.4-3.4 6.3-2.7 14.1 1.9 19.8z" style="fill:#609926"/></svg> | ||||
| After Width: | Height: | Size: 2.1 KiB | 
							
								
								
									
										1521
									
								
								apps/welcome/hermietildefriends.svg
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 86 KiB | 
| @@ -28,65 +28,39 @@ | ||||
| 						<b>😎 Tilde Friends</b> | ||||
| 					</h1> | ||||
| 					<h1 class="w3-xxlarge w3-text-green"> | ||||
| 						<b | ||||
| 							>the Secure Scuttlebutt decentralized social network client that's | ||||
| 							<i>fancy🎩</i></b | ||||
| 						> | ||||
| 						<b>a Secure Scuttlebutt decentralized social network client</b> | ||||
| 					</h1> | ||||
| 					<p> | ||||
| 						In addition to participating in Secure Scuttlebutt, Tilde Friends is | ||||
| 						a platform for building, running, and sharing applications. | ||||
| 					</p> | ||||
| 					<p> | ||||
| 						Available for lots of devices: | ||||
| 						<i class="fa-brands fa-linux w3-xlarge"></i> | ||||
| 						<i class="fa-brands fa-android w3-xlarge"></i> | ||||
| 						<i class="fa-brands fa-apple w3-xlarge"></i> | ||||
| 						<i class="fa fa-mobile-screen w3-xlarge"></i> | ||||
| 						<i class="fa-brands fa-windows w3-xlarge"></i> | ||||
| 					</p> | ||||
| 					<a | ||||
| 						class="w3-button w3-blue w3-padding-large" | ||||
| 						href="https://www.tildefriends.net/~core/ssb/" | ||||
| 						>🦀 Try It</a | ||||
| 					> | ||||
| 					<a | ||||
| 						class="w3-button w3-black w3-padding-large" | ||||
| 						href="https://dev.tildefriends.net/cory/tildefriends/releases" | ||||
| 						href="https://dev.tildefriends.net/cory/tildefriends/releases/latest" | ||||
| 						><i class="fa fa-download"></i> Download</a | ||||
| 					> | ||||
| 					<a | ||||
| 						class="w3-button w3-black w3-padding-large" | ||||
| 						href="https://www.tildefriends.net/~core/ssb/" | ||||
| 						><i class="fa fa-link"></i> Try It</a | ||||
| 					> | ||||
| 					<a | ||||
| 						class="w3-button w3-black w3-padding-large" | ||||
| 						href="https://dev.tildefriends.net/" | ||||
| 						><i class="fa fa-mug-hot"></i> Development</a | ||||
| 						href="https://dev.tildefriends.net/cory/tildefriends" | ||||
| 					> | ||||
| 						<img src="gitea.svg" style="height: 1em; margin: 0" /> | ||||
| 						Development | ||||
| 					</a> | ||||
| 					<a | ||||
| 						class="w3-button w3-black w3-padding-large" | ||||
| 						href="https://docs.tildefriends.net/" | ||||
| 						><i class="fa fa-book"></i> Documentation</a | ||||
| 					> | ||||
| 					<p> | ||||
| 						<a | ||||
| 							class="w3-button w3-round-large w3-padding w3-blue-gray w3-margin-top" | ||||
| 							href="https://f-droid.org/en/packages/com.unprompted.tildefriends.fdroid/" | ||||
| 							><img src="f-droid.svg" style="height: 2em; margin: 0" /> Get it | ||||
| 							on F-Droid</a | ||||
| 						> | ||||
| 						<a | ||||
| 							class="w3-button w3-round-large w3-padding w3-blue-gray w3-margin-top" | ||||
| 							href="https://dev.tildefriends.net/releases/tildefriends-x86_64.AppImage" | ||||
| 						> | ||||
| 							<img src="appimage.svg" style="height: 2em; margin: 0" /> | ||||
| 							Get Linux 64-bit AppImage | ||||
| 						</a> | ||||
| 						<a | ||||
| 							class="w3-button w3-round-large w3-padding w3-blue-gray w3-margin-top" | ||||
| 							href="https://play.google.com/store/apps/details?id=com.unprompted.tildefriends" | ||||
| 						> | ||||
| 							<img src="googleplay.svg" style="height: 2em; margin: 0" /> | ||||
| 							Get it on Google Play (Open Testing) | ||||
| 						</a> | ||||
| 					</p> | ||||
| 					<a | ||||
| 						class="w3-button w3-black w3-padding-large" | ||||
| 						href="https://www.tildefriends.net/~cory/tildeblog/" | ||||
| 						><i class="fa fa-solid fa-square-rss"></i> Blog</a | ||||
| 					> | ||||
| 				</div> | ||||
| 				<div class="w3-col l4 m6"> | ||||
| 					<img src="tildefriends.png" class="w3-image w3-right w3-hide-small" /> | ||||
| @@ -104,15 +78,119 @@ | ||||
| 					<h2>First-time user checklist:</h2> | ||||
| 					<ol type="1" style="text-align: left"> | ||||
| 						<li> | ||||
| 							<a href="https://dev.tildefriends.net/cory/tildefriends/releases" | ||||
| 							<a | ||||
| 								href="https://dev.tildefriends.net/cory/tildefriends/releases/latest" | ||||
| 								>Download</a | ||||
| 							> | ||||
| 							Tilde Friends or use | ||||
| 							<a href="https://www.tildefriends.net/" | ||||
| 								>https://www.tildefriends.net/</a | ||||
| 							>. | ||||
| 							<div class="w3-cell-row"> | ||||
| 								<div class="w3-container w3-cell w3-mobile"> | ||||
| 									<h3>Mobile</h3> | ||||
| 									<p> | ||||
| 										<a | ||||
| 											class="w3-button w3-round-large w3-padding w3-blue-gray w3-margin-top" | ||||
| 											href="https://f-droid.org/en/packages/com.unprompted.tildefriends.fdroid/" | ||||
| 											><img src="f-droid.svg" style="height: 2em; margin: 0" /> | ||||
| 											Get it on F-Droid</a | ||||
| 										> | ||||
| 										<a | ||||
| 											class="w3-button w3-round-large w3-padding w3-blue-gray w3-margin-top" | ||||
| 											href="https://play.google.com/store/apps/details?id=com.unprompted.tildefriends" | ||||
| 										> | ||||
| 											<img | ||||
| 												src="googleplay.svg" | ||||
| 												style="height: 2em; margin: 0" | ||||
| 											/> | ||||
| 											Get it on Google Play | ||||
| 										</a> | ||||
| 										<a | ||||
| 											class="w3-button w3-round-large w3-padding w3-blue-gray w3-margin-top" | ||||
| 											href="https://testflight.apple.com/join/tXxgtSpE" | ||||
| 										> | ||||
| 											<img src="ios.svg" style="height: 2em; margin: 0" /> | ||||
| 											Get it on iOS (TestFlight) | ||||
| 										</a> | ||||
| 									</p> | ||||
| 									<p>Just launch the app.</p> | ||||
| 								</div> | ||||
| 								<div class="w3-container w3-cell w3-mobile"> | ||||
| 									<h3>Web</h3> | ||||
| 									<p> | ||||
| 										<a | ||||
| 											class="w3-button w3-round-large w3-blue w3-padding-large" | ||||
| 											href="https://www.tildefriends.net/~core/ssb/" | ||||
| 											>🦀 Try It</a | ||||
| 										> | ||||
| 									</p> | ||||
| 									<p> | ||||
| 										<a href="/login?return=/~core/intro" | ||||
| 											>Register an account with tildefriends.net</a | ||||
| 										> | ||||
| 										to take it for a spin right away. | ||||
| 									</p> | ||||
| 									<h3>PeachCloud</h3> | ||||
| 									<p> | ||||
| 										Tilde Friends is also a part of 🍑☁️<a | ||||
| 											href="https://peach-docs.commoninternet.net/" | ||||
| 											>PeachCloud</a | ||||
| 										>, which is available on | ||||
| 										<a href="https://apps.yunohost.org/app/peachpub" | ||||
| 											>YunoHost</a | ||||
| 										> | ||||
| 										for accessible self-hosting. | ||||
| 									</p> | ||||
| 								</div> | ||||
| 								<div class="w3-container w3-cell w3-mobile"> | ||||
| 									<h3>Desktop</h3> | ||||
| 									<p> | ||||
| 										<a | ||||
| 											class="w3-button w3-round-large w3-black w3-padding-large" | ||||
| 											href="https://dev.tildefriends.net/cory/tildefriends/releases" | ||||
| 											><i class="fa fa-download"></i> Download</a | ||||
| 										> | ||||
| 										<a | ||||
| 											class="w3-button w3-round-large w3-padding w3-blue-gray" | ||||
| 											href="https://dev.tildefriends.net/releases/tildefriends-x86_64.AppImage" | ||||
| 										> | ||||
| 											<img src="appimage.svg" style="height: 2em; margin: 0" /> | ||||
| 											Get Linux 64-bit AppImage | ||||
| 										</a> | ||||
| 									</p> | ||||
| 									<p> | ||||
| 										Tilde Friends is distributed as a single executable file (or | ||||
| 										source that you can | ||||
| 										<a href="http://dev.tildefriends.net">build yourself</a>) | ||||
| 										and stores all of its data in a single | ||||
| 										file(<code>db.sqlite</code>). You can generally download the | ||||
| 										latest executable from | ||||
| 										<a | ||||
| 											href="https://dev.tildefriends.net/cory/tildefriends/releases" | ||||
| 											>releases</a | ||||
| 										> | ||||
| 										for your platform, mark it as executable (<code | ||||
| 											>chmod +x tildefriends*</code | ||||
| 										> | ||||
| 										on macOS and Linux), and run. Run with <code>-h</code> to | ||||
| 										learn more. | ||||
| 									</p> | ||||
| 									<p> | ||||
| 										Tilde Friends will run in the console and provide a web | ||||
| 										interface at | ||||
| 										<a href="http://localhost:12345/">http://localhost:12345/</a | ||||
| 										>. You will have to register a username and password to sign | ||||
| 										into your instance. | ||||
| 									</p> | ||||
| 								</div> | ||||
| 							</div> | ||||
| 							<p> | ||||
| 								After a <a href="/~core/intro">brief introduction</a>, Tilde | ||||
| 								Friends will take you to the Secure Scuttlebutt social network | ||||
| 								app. | ||||
| 							</p> | ||||
| 						</li> | ||||
| 						<li>Create an account to identify yourself with that instance.</li> | ||||
| 						<li> | ||||
| 							Describe yourself in your profile in the <b>ssb</b> app. Give | ||||
| 							yourself a name and an avatar if you like. | ||||
| @@ -146,11 +224,11 @@ | ||||
| 		<!-- SSB Section --> | ||||
| 		<div class="w3-light-grey"> | ||||
| 			<div class="w3-row-padding w3-padding-64"> | ||||
| 				<div class="w3-col l4 m6 s4"> | ||||
| 				<div class="w3-col l4 m6 s4 w3-center"> | ||||
| 					<a href="https://scuttlebutt.nz/" | ||||
| 						><img | ||||
| 							class="w3-image w3-round-large" | ||||
| 							src="ssb.png" | ||||
| 							class="w3-image" | ||||
| 							src="hermietildefriends.svg" | ||||
| 							alt="Secure Scuttlebutt" | ||||
| 					/></a> | ||||
| 				</div> | ||||
| @@ -220,7 +298,7 @@ | ||||
|  | ||||
| 		<!-- Technlology Section --> | ||||
| 		<div class="w3-container w3-padding-64 w3-light-grey w3-center"> | ||||
| 			<h1 class="w3-jumbo"><b>Built the Old Fashioned Way</b></h1> | ||||
| 			<h1 class="w3-jumbo"><b>Built to Last</b></h1> | ||||
| 			<p> | ||||
| 				Tilde Friends strives to use only simple and widely adopted dependencies | ||||
| 				in order to keep it easy to build for all sorts of platforms and | ||||
| @@ -262,10 +340,6 @@ | ||||
| 					<i class="fa fa-lock w3-text-purple w3-jumbo"></i> | ||||
| 					<p>libsodium</p> | ||||
| 				</a> | ||||
| 				<a href="https://github.com/openssl/openssl/releases" class="w3-col s3"> | ||||
| 					<i class="fa fa-shield-halved w3-text-green w3-jumbo"></i> | ||||
| 					<p>OpenSSL</p> | ||||
| 				</a> | ||||
| 				<a | ||||
| 					href="https://github.com/ianlancetaylor/libbacktrace" | ||||
| 					class="w3-col s3" | ||||
| @@ -273,13 +347,13 @@ | ||||
| 					<i class="fa fa-burst w3-text-pink w3-jumbo"></i> | ||||
| 					<p>libbacktrace</p> | ||||
| 				</a> | ||||
| 			</div> | ||||
|  | ||||
| 			<div class="w3-row" style="margin-top: 64px"> | ||||
| 				<a href="https://codemirror.net/docs/changelog/" class="w3-col s3"> | ||||
| 					<i class="fa fa-keyboard w3-text-indigo w3-jumbo"></i> | ||||
| 					<p>CodeMirror</p> | ||||
| 				</a> | ||||
| 			</div> | ||||
|  | ||||
| 			<div class="w3-row" style="margin-top: 64px"> | ||||
| 				<a href="https://github.com/jlfwong/speedscope/" class="w3-col s3"> | ||||
| 					<i class="fa fa-microscope w3-text-orange w3-jumbo"></i> | ||||
| 					<p>Speedscope</p> | ||||
| @@ -292,9 +366,6 @@ | ||||
| 					<i class="fa fa-book-atlas w3-text-purple w3-jumbo"></i> | ||||
| 					<p>c-ares</p> | ||||
| 				</a> | ||||
| 			</div> | ||||
|  | ||||
| 			<div class="w3-row" style="margin-top: 64px"> | ||||
| 				<a href="https://www.gnu.org/software/make/" class="w3-col s3"> | ||||
| 					<i class="fa fa-hammer w3-text-teal w3-jumbo"></i> | ||||
| 					<p>GNU Make</p> | ||||
|   | ||||
							
								
								
									
										3
									
								
								apps/welcome/ios.svg
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,3 @@ | ||||
| <svg xmlns="http://www.w3.org/2000/svg" xml:space="preserve" width="814" height="1000"> | ||||
|   <path d="M788.1 340.9c-5.8 4.5-108.2 62.2-108.2 190.5 0 148.4 130.3 200.9 134.2 202.2-.6 3.2-20.7 71.9-68.7 141.9-42.8 61.6-87.5 123.1-155.5 123.1s-85.5-39.5-164-39.5c-76.5 0-103.7 40.8-165.9 40.8s-105.6-57-155.5-127C46.7 790.7 0 663 0 541.8c0-194.4 126.4-297.5 250.8-297.5 66.1 0 121.2 43.4 162.7 43.4 39.5 0 101.1-46 176.3-46 28.5 0 130.9 2.6 198.3 99.2zm-234-181.5c31.1-36.9 53.1-88.1 53.1-139.3 0-7.1-.6-14.3-1.9-20.1-50.6 1.9-110.8 33.7-147.1 75.8-28.5 32.4-55.1 83.6-55.1 135.5 0 7.8 1.3 15.6 1.9 18.1 3.2.6 8.4 1.3 13.6 1.3 45.4 0 102.5-30.4 135.5-71.3z"/> | ||||
| </svg> | ||||
| After Width: | Height: | Size: 660 B | 
| Before Width: | Height: | Size: 50 KiB | 
| Before Width: | Height: | Size: 141 KiB After Width: | Height: | Size: 141 KiB | 
| @@ -1,4 +1,4 @@ | ||||
| /* W3.CSS 5.01 March 14 2025 by Jan Egil and Borge Refsnes */ | ||||
| /* W3.CSS 5.02 March 31 2025 by Jan Egil and Borge Refsnes */ | ||||
| html{box-sizing:border-box}*,*:before,*:after{box-sizing:inherit} | ||||
| /* Extract from normalize.css by Nicolas Gallagher and Jonathan Neal git.io/normalize */ | ||||
| html{-ms-text-size-adjust:100%;-webkit-text-size-adjust:100%}body{margin:0} | ||||
| @@ -108,10 +108,8 @@ hr{border:0;border-top:1px solid #eee;margin:20px 0} | ||||
| .w3-round-small{border-radius:2px}.w3-round,.w3-round-medium{border-radius:4px}.w3-round-large{border-radius:8px}.w3-round-xlarge{border-radius:16px}.w3-round-xxlarge{border-radius:32px} | ||||
| .w3-row-padding,.w3-row-padding>.w3-half,.w3-row-padding>.w3-third,.w3-row-padding>.w3-twothird,.w3-row-padding>.w3-threequarter,.w3-row-padding>.w3-quarter,.w3-row-padding>.w3-col{padding:0 8px} | ||||
| .w3-container,.w3-panel{padding:0.01em 16px}.w3-panel{margin-top:16px;margin-bottom:16px} | ||||
|  | ||||
| .w3-grid{display:grid}.w3-grid-padding{display:grid;gap:16px}.w3-flex{display:flex} | ||||
| .w3-text-center{text-align:center}.w3-text-bold,.w3-bold{font-weight:bold}.w3-text-italic,.w3-italic{font-style:italic} | ||||
|  | ||||
| .w3-code,.w3-codespan{font-family:Consolas,"courier new";font-size:16px} | ||||
| .w3-code{width:auto;background-color:#fff;padding:8px 12px;border-left:4px solid #4CAF50;word-wrap:break-word} | ||||
| .w3-codespan{color:crimson;background-color:#f1f1f1;padding-left:4px;padding-right:4px;font-size:110%} | ||||
| @@ -152,10 +150,11 @@ hr{border:0;border-top:1px solid #eee;margin:20px 0} | ||||
| .w3-button:hover{color:#000!important;background-color:#ccc!important} | ||||
| .w3-transparent,.w3-hover-none:hover{background-color:transparent!important} | ||||
| .w3-hover-none:hover{box-shadow:none!important} | ||||
| .w3-rtl{direction:rtl}.w3-ltr{direction:ltr} | ||||
| /* Colors */ | ||||
| .w3-amber,.w3-hover-amber:hover,.w3-warning{color:#000!important;background-color:#ffc107!important} | ||||
| .w3-amber,.w3-hover-amber:hover{color:#000!important;background-color:#ffc107!important} | ||||
| .w3-aqua,.w3-hover-aqua:hover{color:#000!important;background-color:#00ffff!important} | ||||
| .w3-blue,.w3-hover-blue:hover,.w3-info,.w3-primary{color:#fff!important;background-color:#2196F3!important} | ||||
| .w3-blue,.w3-hover-blue:hover{color:#fff!important;background-color:#2196F3!important} | ||||
| .w3-light-blue,.w3-hover-light-blue:hover{color:#000!important;background-color:#87CEEB!important} | ||||
| .w3-brown,.w3-hover-brown:hover{color:#fff!important;background-color:#795548!important} | ||||
| .w3-cyan,.w3-hover-cyan:hover{color:#000!important;background-color:#00bcd4!important} | ||||
| @@ -170,24 +169,28 @@ hr{border:0;border-top:1px solid #eee;margin:20px 0} | ||||
| .w3-pink,.w3-hover-pink:hover{color:#fff!important;background-color:#e91e63!important} | ||||
| .w3-purple,.w3-hover-purple:hover{color:#fff!important;background-color:#9c27b0!important} | ||||
| .w3-deep-purple,.w3-hover-deep-purple:hover{color:#fff!important;background-color:#673ab7!important} | ||||
| .w3-red,.w3-hover-red:hover,.w3-danger{color:#fff!important;background-color:#f44336!important} | ||||
| .w3-red,.w3-hover-red:hover{color:#fff!important;background-color:#f44336!important} | ||||
| .w3-sand,.w3-hover-sand:hover{color:#000!important;background-color:#fdf5e6!important} | ||||
| .w3-teal,.w3-hover-teal:hover{color:#fff!important;background-color:#009688!important} | ||||
| .w3-yellow,.w3-hover-yellow:hover,.w3-note{color:#000!important;background-color:#ffeb3b!important} | ||||
| .w3-yellow,.w3-hover-yellow:hover{color:#000!important;background-color:#ffeb3b!important} | ||||
| .w3-white,.w3-hover-white:hover{color:#000!important;background-color:#fff!important} | ||||
| .w3-black,.w3-hover-black:hover{color:#fff!important;background-color:#000!important} | ||||
|  | ||||
| .w3-grey,.w3-hover-grey:hover,.w3-gray,.w3-hover-gray:hover,.w3-secondary{color:#000!important;background-color:#9e9e9e!important} | ||||
| .w3-grey,.w3-hover-grey:hover,.w3-gray,.w3-hover-gray:hover{color:#000!important;background-color:#9e9e9e!important} | ||||
| .w3-light-grey,.w3-hover-light-grey:hover,.w3-light-gray,.w3-hover-light-gray:hover{color:#000!important;background-color:#f1f1f1!important} | ||||
| .w3-dark-grey,.w3-hover-dark-grey:hover,.w3-dark-gray,.w3-hover-dark-gray:hover{color:#fff!important;background-color:#616161!important} | ||||
|  | ||||
| .w3-asphalt,.w3-hover-asphalt:hover{color:#fff!important;background-color:#343a40!important}.w3-crimson,.w3-hover-crimson:hover{color:#fff!important;background-color:#a20025!important} | ||||
| .w3-asphalt,.w3-hover-asphalt:hover{color:#fff!important;background-color:#343a40!important} | ||||
| .w3-crimson,.w3-hover-crimson:hover{color:#fff!important;background-color:#a20025!important} | ||||
| .w3-cobalt,w3-hover-cobalt:hover{color:#fff!important;background-color:#0050ef!important} | ||||
| .w3-emerald,.w3-hover-emerald:hover,.w3-success{color:#fff!important;background-color:#008a00!important} | ||||
| .w3-emerald,.w3-hover-emerald:hover{color:#fff!important;background-color:#008a00!important} | ||||
| .w3-olive,.w3-hover-olive:hover{color:#fff!important;background-color:#6d8764!important} | ||||
| .w3-paper,.w3-hover-paper:hover{color:#000!important;background-color:#f8f9fa!important}.w3-sienna,.w3-hover-sienna:hover{color:#fff!important;background-color:#a0522d!important} | ||||
| .w3-paper,.w3-hover-paper:hover{color:#000!important;background-color:#f8f9fa!important} | ||||
| .w3-sienna,.w3-hover-sienna:hover{color:#fff!important;background-color:#a0522d!important} | ||||
| .w3-taupe,.w3-hover-taupe:hover{color:#fff!important;background-color:#87794e!important} | ||||
|  | ||||
| .w3-danger{color:#fff!important;background-color:#dd0000!important} | ||||
| .w3-note{color:#000!important;background-color:#fff599!important} | ||||
| .w3-info{color:#fff!important;background-color:#0a6fc2!important} | ||||
| .w3-warning{color:#000!important;background-color:#ffb305!important} | ||||
| .w3-success{color:#fff!important;background-color:#008a00!important} | ||||
| .w3-pale-red,.w3-hover-pale-red:hover{color:#000!important;background-color:#ffdddd!important} | ||||
| .w3-pale-green,.w3-hover-pale-green:hover{color:#000!important;background-color:#ddffdd!important} | ||||
| .w3-pale-yellow,.w3-hover-pale-yellow:hover{color:#000!important;background-color:#ffffcc!important} | ||||
|   | ||||
							
								
								
									
										42
									
								
								apps/wiki/lit-all.min.js
									
									
									
									
										vendored
									
									
								
							
							
						
						
							
								
								
									
										39
									
								
								core/app.js
									
									
									
									
									
								
							
							
						
						| @@ -1,7 +1,23 @@ | ||||
| /** | ||||
|  * \file | ||||
|  * \defgroup tfapp Tilde Friends App JS | ||||
|  * Tilde Friends server-side app wrapper. | ||||
|  * @{ | ||||
|  */ | ||||
|  | ||||
| /** \cond */ | ||||
| import * as core from './core.js'; | ||||
|  | ||||
| let gSessionIndex = 0; | ||||
| export {App}; | ||||
| /** \endcond */ | ||||
|  | ||||
| /** A sequence number of apps. */ | ||||
| let g_session_index = 0; | ||||
|  | ||||
| /** | ||||
|  ** App constructor. | ||||
|  ** @return An app instance. | ||||
|  */ | ||||
| function App() { | ||||
| 	this._send_queue = []; | ||||
| 	this.calls = {}; | ||||
| @@ -9,6 +25,12 @@ function App() { | ||||
| 	return this; | ||||
| } | ||||
|  | ||||
| /** | ||||
|  ** Create a function wrapper that when called invokes a function on the app | ||||
|  ** itself. | ||||
|  ** @param api The function and argument names. | ||||
|  ** @return A function. | ||||
|  */ | ||||
| App.prototype.makeFunction = function (api) { | ||||
| 	let self = this; | ||||
| 	let result = function () { | ||||
| @@ -32,6 +54,10 @@ App.prototype.makeFunction = function (api) { | ||||
| 	return result; | ||||
| }; | ||||
|  | ||||
| /** | ||||
|  ** Send a message to the app. | ||||
|  ** @param message The message to send. | ||||
|  */ | ||||
| App.prototype.send = function (message) { | ||||
| 	if (this._send_queue) { | ||||
| 		if (this._on_output) { | ||||
| @@ -46,6 +72,11 @@ App.prototype.send = function (message) { | ||||
| 	} | ||||
| }; | ||||
|  | ||||
| /** | ||||
|  ** App socket handler. | ||||
|  ** @param request The HTTP request of the WebSocket connection. | ||||
|  ** @param response The HTTP response. | ||||
|  */ | ||||
| exports.app_socket = async function socket(request, response) { | ||||
| 	let process; | ||||
| 	let options = {}; | ||||
| @@ -118,7 +149,7 @@ exports.app_socket = async function socket(request, response) { | ||||
| 								parentApp: parentApp, | ||||
| 								id: blobId, | ||||
| 							}, | ||||
| 							await ssb.getIdentityInfo( | ||||
| 							await ssb_internal.getIdentityInfo( | ||||
| 								credentials?.session?.name, | ||||
| 								packageOwner, | ||||
| 								packageName | ||||
| @@ -133,7 +164,7 @@ exports.app_socket = async function socket(request, response) { | ||||
| 				options.packageOwner = packageOwner; | ||||
| 				options.packageName = packageName; | ||||
| 				options.url = message.url; | ||||
| 				let sessionId = 'session_' + (gSessionIndex++).toString(); | ||||
| 				let sessionId = 'session_' + (g_session_index++).toString(); | ||||
| 				if (blobId) { | ||||
| 					if (message.edit_only) { | ||||
| 						response.send( | ||||
| @@ -218,4 +249,4 @@ exports.app_socket = async function socket(request, response) { | ||||
| 	response.upgrade(100, {}); | ||||
| }; | ||||
|  | ||||
| export {App}; | ||||
| /** @} */ | ||||
|   | ||||
							
								
								
									
										689
									
								
								core/client.js
									
									
									
									
									
								
							
							
						
						
							
								
								
									
										222
									
								
								core/core.js
									
									
									
									
									
								
							
							
						
						| @@ -1,12 +1,29 @@ | ||||
| /** | ||||
|  * \file | ||||
|  * \defgroup tfcore Tilde Friends Core JS | ||||
|  * Tilde Friends process management, in JavaScript. | ||||
|  * @{ | ||||
|  */ | ||||
|  | ||||
| /** \cond */ | ||||
| import * as app from './app.js'; | ||||
| import * as http from './http.js'; | ||||
|  | ||||
| export {invoke, getProcessBlob}; | ||||
| /** \endcond */ | ||||
|  | ||||
| /** All running processes. */ | ||||
| let gProcesses = {}; | ||||
| /** Whether stats are currently being sent. */ | ||||
| let gStatsTimer = false; | ||||
| /** Effectively a process ID. */ | ||||
| let g_handler_index = 0; | ||||
|  | ||||
| /** Time between pings, in milliseconds. */ | ||||
| const k_ping_interval = 60 * 1000; | ||||
|  | ||||
| /** | ||||
|  * Print an error. | ||||
|  * @param error The error. | ||||
|  */ | ||||
| function printError(error) { | ||||
| 	if (error.stackTrace) { | ||||
| 		print(error.fileName + ':' + error.lineNumber + ': ' + error.message); | ||||
| @@ -19,6 +36,12 @@ function printError(error) { | ||||
| 	} | ||||
| } | ||||
|  | ||||
| /** | ||||
|  * Invoke a handler. | ||||
|  * @param handlers The handlers on which to invoke the callback. | ||||
|  * @param argv Arguments to pass to the handlers. | ||||
|  * @return A promise. | ||||
|  */ | ||||
| function invoke(handlers, argv) { | ||||
| 	let promises = []; | ||||
| 	if (handlers) { | ||||
| @@ -39,6 +62,12 @@ function invoke(handlers, argv) { | ||||
| 	return Promise.all(promises); | ||||
| } | ||||
|  | ||||
| /** | ||||
|  * Broadcast a named event to all registered apps. | ||||
|  * @param eventName the name of the event. | ||||
|  * @param argv Arguments to pass to the handlers. | ||||
|  * @return A promise. | ||||
|  */ | ||||
| function broadcastEvent(eventName, argv) { | ||||
| 	let promises = []; | ||||
| 	for (let process of Object.values(gProcesses)) { | ||||
| @@ -49,6 +78,11 @@ function broadcastEvent(eventName, argv) { | ||||
| 	return Promise.all(promises); | ||||
| } | ||||
|  | ||||
| /** | ||||
|  * Send a message to all other instances of the same app. | ||||
|  * @param message The message. | ||||
|  * @return A promise. | ||||
|  */ | ||||
| function broadcast(message) { | ||||
| 	let sender = this; | ||||
| 	let promises = []; | ||||
| @@ -65,6 +99,15 @@ function broadcast(message) { | ||||
| 	return Promise.all(promises); | ||||
| } | ||||
|  | ||||
| /** | ||||
|  * Send a message to all instances of the same app running as the same user. | ||||
|  * @param user The user. | ||||
|  * @param packageOwner The owner of the app. | ||||
|  * @param packageName The name of the app. | ||||
|  * @param eventName The name of the event. | ||||
|  * @param argv The arguments to pass. | ||||
|  * @return A promise. | ||||
|  */ | ||||
| function broadcastAppEventToUser( | ||||
| 	user, | ||||
| 	packageOwner, | ||||
| @@ -87,6 +130,11 @@ function broadcastAppEventToUser( | ||||
| 	return Promise.all(promises); | ||||
| } | ||||
|  | ||||
| /** | ||||
|  * Get user context information for a call. | ||||
|  * @param caller The calling process. | ||||
|  * @param process The receiving process. | ||||
|  */ | ||||
| function getUser(caller, process) { | ||||
| 	return { | ||||
| 		key: process.key, | ||||
| @@ -97,38 +145,26 @@ function getUser(caller, process) { | ||||
| 	}; | ||||
| } | ||||
|  | ||||
| async function getApps(user, process) { | ||||
| 	if ( | ||||
| 		process.credentials && | ||||
| 		process.credentials.session && | ||||
| 		process.credentials.session.name | ||||
| 	) { | ||||
| 		if (user && user !== process.credentials.session.name && user !== 'core') { | ||||
| 			return {}; | ||||
| 		} else if (!user) { | ||||
| 			user = process.credentials.session.name; | ||||
| 		} | ||||
| 	} | ||||
| 	if (user) { | ||||
| 		let db = new Database(user); | ||||
| 		try { | ||||
| 			let names = JSON.parse(await db.get('apps')); | ||||
| 			let result = {}; | ||||
| 			for (let name of names) { | ||||
| 				result[name] = await db.get('path:' + name); | ||||
| 			} | ||||
| 			return result; | ||||
| 		} catch {} | ||||
| 	} | ||||
| 	return {}; | ||||
| } | ||||
|  | ||||
| /** | ||||
|  * Send a message. | ||||
|  * @param from The calling process. | ||||
|  * @param to The receiving process. | ||||
|  * @param message The message. | ||||
|  * @return A promise. | ||||
|  */ | ||||
| function postMessageInternal(from, to, message) { | ||||
| 	if (to.eventHandlers['message']) { | ||||
| 		return invoke(to.eventHandlers['message'], [getUser(from, from), message]); | ||||
| 	} | ||||
| } | ||||
|  | ||||
| /** | ||||
|  * Get or create a process for an app blob. | ||||
|  * @param blobId The blob identifier. | ||||
|  * @param key A unique key for the invocation. | ||||
|  * @param options Other options. | ||||
|  * @return The process. | ||||
|  */ | ||||
| async function getProcessBlob(blobId, key, options) { | ||||
| 	let process = gProcesses[key]; | ||||
| 	if (!process && !(options && 'create' in options && !options.create)) { | ||||
| @@ -142,6 +178,7 @@ async function getProcessBlob(blobId, key, options) { | ||||
| 			process.task = new Task(); | ||||
| 			process.packageOwner = options.packageOwner; | ||||
| 			process.packageName = options.packageName; | ||||
| 			process.url = options?.url; | ||||
| 			process.eventHandlers = {}; | ||||
| 			if (!options?.script || options?.script === 'app.js') { | ||||
| 				process.app = new app.App(); | ||||
| @@ -155,54 +192,13 @@ async function getProcessBlob(blobId, key, options) { | ||||
| 			}); | ||||
| 			gProcesses[key] = process; | ||||
| 			process.task.onExit = function (exitCode, terminationSignal) { | ||||
| 				broadcastEvent('onSessionEnd', [getUser(process, process)]); | ||||
| 				process.task = null; | ||||
| 				delete gProcesses[key]; | ||||
| 			}; | ||||
| 			let imports = { | ||||
| 				core: { | ||||
| 					broadcast: broadcast.bind(process), | ||||
| 					register: function (eventName, handler) { | ||||
| 						if (!process.eventHandlers[eventName]) { | ||||
| 							process.eventHandlers[eventName] = []; | ||||
| 						} | ||||
| 						process.eventHandlers[eventName].push(handler); | ||||
| 					}, | ||||
| 					unregister: function (eventName, handler) { | ||||
| 						if (process.eventHandlers[eventName]) { | ||||
| 							let index = process.eventHandlers[eventName].indexOf(handler); | ||||
| 							if (index != -1) { | ||||
| 								process.eventHandlers[eventName].splice(index, 1); | ||||
| 							} | ||||
| 							if (process.eventHandlers[eventName].length == 0) { | ||||
| 								delete process.eventHandlers[eventName]; | ||||
| 							} | ||||
| 						} | ||||
| 					}, | ||||
| 					user: getUser(process, process), | ||||
| 					users: async function () { | ||||
| 						try { | ||||
| 							return JSON.parse(await new Database('auth').get('users')); | ||||
| 						} catch { | ||||
| 							return []; | ||||
| 						} | ||||
| 					}, | ||||
| 					permissionsGranted: async function () { | ||||
| 						let user = process?.credentials?.session?.name; | ||||
| 						let settings = await loadSettings(); | ||||
| 						if ( | ||||
| 							user && | ||||
| 							options?.packageOwner && | ||||
| 							options?.packageName && | ||||
| 							settings.userPermissions && | ||||
| 							settings.userPermissions[user] && | ||||
| 							settings.userPermissions[user][options.packageOwner] | ||||
| 						) { | ||||
| 							return settings.userPermissions[user][options.packageOwner][ | ||||
| 								options.packageName | ||||
| 							]; | ||||
| 						} | ||||
| 					}, | ||||
| 					allPermissionsGranted: async function () { | ||||
| 						let user = process?.credentials?.session?.name; | ||||
| 						let settings = await loadSettings(); | ||||
| @@ -216,12 +212,6 @@ async function getProcessBlob(blobId, key, options) { | ||||
| 							return settings.userPermissions[user]; | ||||
| 						} | ||||
| 					}, | ||||
| 					permissionsForUser: async function (user) { | ||||
| 						let settings = await loadSettings(); | ||||
| 						return settings?.permissions?.[user] ?? []; | ||||
| 					}, | ||||
| 					apps: (user) => getApps(user, process), | ||||
| 					getSockets: getSockets, | ||||
| 					permissionTest: async function (permission) { | ||||
| 						let user = process?.credentials?.session?.name; | ||||
| 						let settings = await loadSettings(); | ||||
| @@ -282,11 +272,6 @@ async function getProcessBlob(blobId, key, options) { | ||||
| 							throw Error(`Permission denied: ${permission}.`); | ||||
| 						} | ||||
| 					}, | ||||
| 					app: { | ||||
| 						owner: options?.packageOwner, | ||||
| 						name: options?.packageName, | ||||
| 					}, | ||||
| 					url: options?.url, | ||||
| 				}, | ||||
| 			}; | ||||
| 			process.sendIdentities = async function () { | ||||
| @@ -295,7 +280,7 @@ async function getProcessBlob(blobId, key, options) { | ||||
| 						{ | ||||
| 							action: 'identities', | ||||
| 						}, | ||||
| 						await ssb.getIdentityInfo( | ||||
| 						await ssb_internal.getIdentityInfo( | ||||
| 							process?.credentials?.session?.name, | ||||
| 							options?.packageOwner, | ||||
| 							options?.packageName | ||||
| @@ -338,7 +323,7 @@ async function getProcessBlob(blobId, key, options) { | ||||
| 						options.packageName, | ||||
| 						'setActiveIdentity', | ||||
| 						[ | ||||
| 							await ssb.getActiveIdentity( | ||||
| 							await imports.ssb.getActiveIdentity( | ||||
| 								process.credentials?.session?.name, | ||||
| 								options.packageOwner, | ||||
| 								options.packageName | ||||
| @@ -351,19 +336,6 @@ async function getProcessBlob(blobId, key, options) { | ||||
| 				} | ||||
| 			}; | ||||
| 			if (process.credentials?.permissions?.administration) { | ||||
| 				imports.core.globalSettingsDescriptions = async function () { | ||||
| 					let settings = Object.assign({}, defaultGlobalSettings()); | ||||
| 					for (let [key, value] of Object.entries(await loadSettings())) { | ||||
| 						if (settings[key]) { | ||||
| 							settings[key].value = value; | ||||
| 						} | ||||
| 					} | ||||
| 					return settings; | ||||
| 				}; | ||||
| 				imports.core.globalSettingsGet = async function (key) { | ||||
| 					let settings = await loadSettings(); | ||||
| 					return settings?.[key]; | ||||
| 				}; | ||||
| 				imports.core.globalSettingsSet = async function (key, value) { | ||||
| 					await imports.core.permissionTest('set_global_setting'); | ||||
| 					print('Setting', key, value); | ||||
| @@ -445,26 +417,6 @@ async function getProcessBlob(blobId, key, options) { | ||||
| 				} | ||||
| 			}; | ||||
| 			imports.ssb.setActiveIdentity = (id) => process.setActiveIdentity(id); | ||||
| 			imports.ssb.getActiveIdentity = () => | ||||
| 				ssb.getActiveIdentity( | ||||
| 					process.credentials?.session?.name, | ||||
| 					options.packageOwner, | ||||
| 					options.packageName | ||||
| 				); | ||||
| 			imports.ssb.getOwnerIdentities = function () { | ||||
| 				if (options.packageOwner) { | ||||
| 					return ssb.getIdentities(options.packageOwner); | ||||
| 				} | ||||
| 			}; | ||||
| 			imports.ssb.getIdentities = function () { | ||||
| 				if ( | ||||
| 					process.credentials && | ||||
| 					process.credentials.session && | ||||
| 					process.credentials.session.name | ||||
| 				) { | ||||
| 					return ssb.getIdentities(process.credentials.session.name); | ||||
| 				} | ||||
| 			}; | ||||
| 			imports.ssb.getPrivateKey = function (id) { | ||||
| 				if ( | ||||
| 					process.credentials && | ||||
| @@ -534,13 +486,6 @@ async function getProcessBlob(blobId, key, options) { | ||||
| 					); | ||||
| 				} | ||||
| 			}; | ||||
| 			imports.ssb.addEventListener = undefined; | ||||
| 			imports.ssb.removeEventListener = undefined; | ||||
| 			imports.ssb.getIdentityInfo = undefined; | ||||
| 			imports.fetch = async function (url, options) { | ||||
| 				let settings = await loadSettings(); | ||||
| 				return http.fetch(url, options, settings?.fetch_hosts); | ||||
| 			}; | ||||
|  | ||||
| 			if ( | ||||
| 				process.credentials && | ||||
| @@ -644,7 +589,6 @@ async function getProcessBlob(blobId, key, options) { | ||||
| 			} catch (e) { | ||||
| 				printError(e); | ||||
| 			} | ||||
| 			broadcastEvent('onSessionBegin', [getUser(process, process)]); | ||||
| 			if (process.app) { | ||||
| 				process.app.send({action: 'ready', version: version()}); | ||||
| 				await process.sendPermissions(); | ||||
| @@ -667,18 +611,29 @@ async function getProcessBlob(blobId, key, options) { | ||||
| 	return process; | ||||
| } | ||||
|  | ||||
| ssb.addEventListener('message', function () { | ||||
| /** | ||||
|  * SSB message added callback. | ||||
|  */ | ||||
| ssb_internal.addEventListener('message', function () { | ||||
| 	broadcastEvent('onMessage', [...arguments]); | ||||
| }); | ||||
|  | ||||
| ssb.addEventListener('broadcasts', function () { | ||||
| ssb_internal.addEventListener('blob', function () { | ||||
| 	broadcastEvent('onBlob', [...arguments]); | ||||
| }); | ||||
|  | ||||
| ssb_internal.addEventListener('broadcasts', function () { | ||||
| 	broadcastEvent('onBroadcastsChanged', []); | ||||
| }); | ||||
|  | ||||
| ssb.addEventListener('connections', function () { | ||||
| ssb_internal.addEventListener('connections', function () { | ||||
| 	broadcastEvent('onConnectionsChanged', []); | ||||
| }); | ||||
|  | ||||
| /** | ||||
|  * Load settings from the database. | ||||
|  * @return The settings as a key value pairs object. | ||||
|  */ | ||||
| async function loadSettings() { | ||||
| 	let data = {}; | ||||
| 	try { | ||||
| @@ -697,6 +652,9 @@ async function loadSettings() { | ||||
| 	return data; | ||||
| } | ||||
|  | ||||
| /** | ||||
|  * Send periodic stats to all clients. | ||||
|  */ | ||||
| function sendStats() { | ||||
| 	let apps = Object.values(gProcesses) | ||||
| 		.filter((process) => process.app) | ||||
| @@ -712,6 +670,16 @@ function sendStats() { | ||||
| 	} | ||||
| } | ||||
|  | ||||
| /** | ||||
|  * Invoke an app's handler.js. | ||||
|  * @param response The response object. | ||||
|  * @param app_blob_id The app's blob identifier. | ||||
|  * @param path The request path. | ||||
|  * @param query The request query string. | ||||
|  * @param headers The request headers. | ||||
|  * @param package_owner The app's owner. | ||||
|  * @param package_name The app's name. | ||||
|  */ | ||||
| exports.callAppHandler = async function callAppHandler( | ||||
| 	response, | ||||
| 	app_blob_id, | ||||
| @@ -777,4 +745,4 @@ exports.callAppHandler = async function callAppHandler( | ||||
| 	response.end(answer?.data); | ||||
| }; | ||||
|  | ||||
| export {invoke, getProcessBlob}; | ||||
| /** @} */ | ||||
|   | ||||
							
								
								
									
										113
									
								
								core/http.js
									
									
									
									
									
								
							
							
						
						| @@ -1,113 +0,0 @@ | ||||
| /** | ||||
|  * TODOC | ||||
|  * TODO: document so we can improve this | ||||
|  * @param {*} url | ||||
|  * @returns | ||||
|  */ | ||||
| function parseUrl(url) { | ||||
| 	// XXX: Hack. | ||||
| 	let match = url.match(new RegExp('(\\w+)://([^/:]+)(?::(\\d+))?(.*)')); | ||||
| 	return { | ||||
| 		protocol: match[1], | ||||
| 		host: match[2], | ||||
| 		path: match[4], | ||||
| 		port: match[3] ? parseInt(match[3]) : match[1] == 'http' ? 80 : 443, | ||||
| 	}; | ||||
| } | ||||
|  | ||||
| /** | ||||
|  * TODOC | ||||
|  * @param {*} data | ||||
|  * @returns | ||||
|  */ | ||||
| function parseResponse(data) { | ||||
| 	let firstLine; | ||||
| 	let headers = {}; | ||||
| 	while (true) { | ||||
| 		let endLine = data.indexOf('\r\n'); | ||||
| 		let line = data.substring(0, endLine); | ||||
| 		data = data.substring(endLine + 2); | ||||
| 		if (!line.length) { | ||||
| 			break; | ||||
| 		} else if (!firstLine) { | ||||
| 			firstLine = line; | ||||
| 		} else { | ||||
| 			let colon = line.indexOf(':'); | ||||
| 			headers[line.substring(colon)] = line.substring(colon + 1); | ||||
| 		} | ||||
| 	} | ||||
| 	return {body: data}; | ||||
| } | ||||
|  | ||||
| /** | ||||
|  * TODOC | ||||
|  * @param {*} url | ||||
|  * @param {*} options | ||||
|  * @param {*} allowed_hosts | ||||
|  * @returns | ||||
|  */ | ||||
| export function fetch(url, options, allowed_hosts) { | ||||
| 	let parsed = parseUrl(url); | ||||
| 	return new Promise(function (resolve, reject) { | ||||
| 		if ((allowed_hosts ?? []).indexOf(parsed.host) == -1) { | ||||
| 			throw new Error(`fetch() request to host ${parsed.host} is not allowed.`); | ||||
| 		} | ||||
| 		let socket = new Socket(); | ||||
| 		let buffer = new Uint8Array(0); | ||||
|  | ||||
| 		return socket | ||||
| 			.connect(parsed.host, parsed.port) | ||||
| 			.then(function () { | ||||
| 				socket.read(function (data) { | ||||
| 					if (data && data.length) { | ||||
| 						let newBuffer = new Uint8Array(buffer.length + data.length); | ||||
| 						newBuffer.set(buffer, 0); | ||||
| 						newBuffer.set(data, buffer.length); | ||||
| 						buffer = newBuffer; | ||||
| 					} else { | ||||
| 						let result = parseHttpResponse(buffer); | ||||
| 						if (!result) { | ||||
| 							reject(new Exception('Parse failed.')); | ||||
| 						} | ||||
| 						if (typeof result == 'number') { | ||||
| 							if (result == -2) { | ||||
| 								reject('Incomplete request.'); | ||||
| 							} else { | ||||
| 								reject('Bad request.'); | ||||
| 							} | ||||
| 						} else if (typeof result == 'object') { | ||||
| 							resolve({ | ||||
| 								body: buffer.slice(result.bytes_parsed), | ||||
| 								status: result.status, | ||||
| 								message: result.message, | ||||
| 								headers: result.headers, | ||||
| 							}); | ||||
| 						} else { | ||||
| 							reject(new Exception('Unexpected parse result.')); | ||||
| 						} | ||||
| 						resolve(parseResponse(utf8Decode(buffer))); | ||||
| 					} | ||||
| 				}); | ||||
|  | ||||
| 				if (parsed.port == 443) { | ||||
| 					return socket.startTls(); | ||||
| 				} | ||||
| 			}) | ||||
| 			.then(function () { | ||||
| 				let body = | ||||
| 					typeof options?.body == 'string' | ||||
| 						? utf8Encode(options.body) | ||||
| 						: options.body || new Uint8Array(0); | ||||
| 				let headers = utf8Encode( | ||||
| 					`${options?.method ?? 'GET'} ${parsed.path} HTTP/1.0\r\nHost: ${parsed.host}\r\nConnection: close\r\nContent-Length: ${body.length}\r\n\r\n` | ||||
| 				); | ||||
| 				let fullRequest = new Uint8Array(headers.length + body.length); | ||||
| 				fullRequest.set(headers, 0); | ||||
| 				fullRequest.set(body, headers.length); | ||||
| 				socket.write(fullRequest); | ||||
| 			}) | ||||
| 			.catch(function (error) { | ||||
| 				reject(error); | ||||
| 			}); | ||||
| 	}); | ||||
| } | ||||
| @@ -1,11 +1,22 @@ | ||||
| /** | ||||
|  * \file | ||||
|  * \defgroup tfrpc Tilde Friends RPC. | ||||
|  * Tilde Friends RPC. | ||||
|  * @{ | ||||
|  */ | ||||
|  | ||||
| /** Whether this module is being run in a web browser. */ | ||||
| const k_is_browser = get_is_browser(); | ||||
| /** Registered methods. */ | ||||
| let g_api = {}; | ||||
| /** The next method identifier. */ | ||||
| let g_next_id = 1; | ||||
| /** Identifiers of pending calls. */ | ||||
| let g_calls = {}; | ||||
|  | ||||
| /** | ||||
|  * TODOC | ||||
|  * @returns | ||||
|  * Check if being called from a browser vs. server-side. | ||||
|  * @return true if called from a browser. | ||||
|  */ | ||||
| function get_is_browser() { | ||||
| 	try { | ||||
| @@ -15,16 +26,30 @@ function get_is_browser() { | ||||
| 	} | ||||
| } | ||||
|  | ||||
| /** \cond */ | ||||
| if (k_is_browser) { | ||||
| 	print = console.log; | ||||
| } | ||||
|  | ||||
| if (k_is_browser) { | ||||
| 	window.addEventListener('message', function (event) { | ||||
| 		call_rpc(event.data); | ||||
| 	}); | ||||
| } else { | ||||
| 	core.register('message', function (message) { | ||||
| 		call_rpc(message?.message); | ||||
| 	}); | ||||
| } | ||||
|  | ||||
| export let rpc = new Proxy({}, {get: make_rpc}); | ||||
| /** \endcond */ | ||||
|  | ||||
| /** | ||||
|  * TODOC | ||||
|  * @param {*} target | ||||
|  * @param {*} prop | ||||
|  * @param {*} receiver | ||||
|  * @returns | ||||
|  * Make a function to invoke a remote procedure. | ||||
|  * @param target The target. | ||||
|  * @param prop The name of the function. | ||||
|  * @param receiver The receiver. | ||||
|  * @return A function. | ||||
|  */ | ||||
| function make_rpc(target, prop, receiver) { | ||||
| 	return function () { | ||||
| @@ -55,8 +80,8 @@ function make_rpc(target, prop, receiver) { | ||||
| } | ||||
|  | ||||
| /** | ||||
|  * TODOC | ||||
|  * @param {*} response | ||||
|  * Send a response. | ||||
|  * @param response The response. | ||||
|  */ | ||||
| function send(response) { | ||||
| 	if (k_is_browser) { | ||||
| @@ -67,8 +92,8 @@ function send(response) { | ||||
| } | ||||
|  | ||||
| /** | ||||
|  * TODOC | ||||
|  * @param {*} message | ||||
|  * Invoke a remote procedure. | ||||
|  * @param message An object describing the call. | ||||
|  */ | ||||
| function call_rpc(message) { | ||||
| 	if (message && message.message === 'tfrpc') { | ||||
| @@ -112,22 +137,12 @@ function call_rpc(message) { | ||||
| 	} | ||||
| } | ||||
|  | ||||
| if (k_is_browser) { | ||||
| 	window.addEventListener('message', function (event) { | ||||
| 		call_rpc(event.data); | ||||
| 	}); | ||||
| } else { | ||||
| 	core.register('message', function (message) { | ||||
| 		call_rpc(message?.message); | ||||
| 	}); | ||||
| } | ||||
|  | ||||
| export let rpc = new Proxy({}, {get: make_rpc}); | ||||
|  | ||||
| /** | ||||
|  * TODOC | ||||
|  * @param {*} method | ||||
|  * Register a function that to be called remotely. | ||||
|  * @param method The method. | ||||
|  */ | ||||
| export function register(method) { | ||||
| 	g_api[method.name] = method; | ||||
| } | ||||
|  | ||||
| /** @} */ | ||||
|   | ||||
							
								
								
									
										31
									
								
								core/w3.css
									
									
									
									
									
								
							
							
						
						| @@ -1,4 +1,4 @@ | ||||
| /* W3.CSS 5.01 March 14 2025 by Jan Egil and Borge Refsnes */ | ||||
| /* W3.CSS 5.02 March 31 2025 by Jan Egil and Borge Refsnes */ | ||||
| html{box-sizing:border-box}*,*:before,*:after{box-sizing:inherit} | ||||
| /* Extract from normalize.css by Nicolas Gallagher and Jonathan Neal git.io/normalize */ | ||||
| html{-ms-text-size-adjust:100%;-webkit-text-size-adjust:100%}body{margin:0} | ||||
| @@ -108,10 +108,8 @@ hr{border:0;border-top:1px solid #eee;margin:20px 0} | ||||
| .w3-round-small{border-radius:2px}.w3-round,.w3-round-medium{border-radius:4px}.w3-round-large{border-radius:8px}.w3-round-xlarge{border-radius:16px}.w3-round-xxlarge{border-radius:32px} | ||||
| .w3-row-padding,.w3-row-padding>.w3-half,.w3-row-padding>.w3-third,.w3-row-padding>.w3-twothird,.w3-row-padding>.w3-threequarter,.w3-row-padding>.w3-quarter,.w3-row-padding>.w3-col{padding:0 8px} | ||||
| .w3-container,.w3-panel{padding:0.01em 16px}.w3-panel{margin-top:16px;margin-bottom:16px} | ||||
|  | ||||
| .w3-grid{display:grid}.w3-grid-padding{display:grid;gap:16px}.w3-flex{display:flex} | ||||
| .w3-text-center{text-align:center}.w3-text-bold,.w3-bold{font-weight:bold}.w3-text-italic,.w3-italic{font-style:italic} | ||||
|  | ||||
| .w3-code,.w3-codespan{font-family:Consolas,"courier new";font-size:16px} | ||||
| .w3-code{width:auto;background-color:#fff;padding:8px 12px;border-left:4px solid #4CAF50;word-wrap:break-word} | ||||
| .w3-codespan{color:crimson;background-color:#f1f1f1;padding-left:4px;padding-right:4px;font-size:110%} | ||||
| @@ -152,10 +150,11 @@ hr{border:0;border-top:1px solid #eee;margin:20px 0} | ||||
| .w3-button:hover{color:#000!important;background-color:#ccc!important} | ||||
| .w3-transparent,.w3-hover-none:hover{background-color:transparent!important} | ||||
| .w3-hover-none:hover{box-shadow:none!important} | ||||
| .w3-rtl{direction:rtl}.w3-ltr{direction:ltr} | ||||
| /* Colors */ | ||||
| .w3-amber,.w3-hover-amber:hover,.w3-warning{color:#000!important;background-color:#ffc107!important} | ||||
| .w3-amber,.w3-hover-amber:hover{color:#000!important;background-color:#ffc107!important} | ||||
| .w3-aqua,.w3-hover-aqua:hover{color:#000!important;background-color:#00ffff!important} | ||||
| .w3-blue,.w3-hover-blue:hover,.w3-info,.w3-primary{color:#fff!important;background-color:#2196F3!important} | ||||
| .w3-blue,.w3-hover-blue:hover{color:#fff!important;background-color:#2196F3!important} | ||||
| .w3-light-blue,.w3-hover-light-blue:hover{color:#000!important;background-color:#87CEEB!important} | ||||
| .w3-brown,.w3-hover-brown:hover{color:#fff!important;background-color:#795548!important} | ||||
| .w3-cyan,.w3-hover-cyan:hover{color:#000!important;background-color:#00bcd4!important} | ||||
| @@ -170,24 +169,28 @@ hr{border:0;border-top:1px solid #eee;margin:20px 0} | ||||
| .w3-pink,.w3-hover-pink:hover{color:#fff!important;background-color:#e91e63!important} | ||||
| .w3-purple,.w3-hover-purple:hover{color:#fff!important;background-color:#9c27b0!important} | ||||
| .w3-deep-purple,.w3-hover-deep-purple:hover{color:#fff!important;background-color:#673ab7!important} | ||||
| .w3-red,.w3-hover-red:hover,.w3-danger{color:#fff!important;background-color:#f44336!important} | ||||
| .w3-red,.w3-hover-red:hover{color:#fff!important;background-color:#f44336!important} | ||||
| .w3-sand,.w3-hover-sand:hover{color:#000!important;background-color:#fdf5e6!important} | ||||
| .w3-teal,.w3-hover-teal:hover{color:#fff!important;background-color:#009688!important} | ||||
| .w3-yellow,.w3-hover-yellow:hover,.w3-note{color:#000!important;background-color:#ffeb3b!important} | ||||
| .w3-yellow,.w3-hover-yellow:hover{color:#000!important;background-color:#ffeb3b!important} | ||||
| .w3-white,.w3-hover-white:hover{color:#000!important;background-color:#fff!important} | ||||
| .w3-black,.w3-hover-black:hover{color:#fff!important;background-color:#000!important} | ||||
|  | ||||
| .w3-grey,.w3-hover-grey:hover,.w3-gray,.w3-hover-gray:hover,.w3-secondary{color:#000!important;background-color:#9e9e9e!important} | ||||
| .w3-grey,.w3-hover-grey:hover,.w3-gray,.w3-hover-gray:hover{color:#000!important;background-color:#9e9e9e!important} | ||||
| .w3-light-grey,.w3-hover-light-grey:hover,.w3-light-gray,.w3-hover-light-gray:hover{color:#000!important;background-color:#f1f1f1!important} | ||||
| .w3-dark-grey,.w3-hover-dark-grey:hover,.w3-dark-gray,.w3-hover-dark-gray:hover{color:#fff!important;background-color:#616161!important} | ||||
|  | ||||
| .w3-asphalt,.w3-hover-asphalt:hover{color:#fff!important;background-color:#343a40!important}.w3-crimson,.w3-hover-crimson:hover{color:#fff!important;background-color:#a20025!important} | ||||
| .w3-asphalt,.w3-hover-asphalt:hover{color:#fff!important;background-color:#343a40!important} | ||||
| .w3-crimson,.w3-hover-crimson:hover{color:#fff!important;background-color:#a20025!important} | ||||
| .w3-cobalt,w3-hover-cobalt:hover{color:#fff!important;background-color:#0050ef!important} | ||||
| .w3-emerald,.w3-hover-emerald:hover,.w3-success{color:#fff!important;background-color:#008a00!important} | ||||
| .w3-emerald,.w3-hover-emerald:hover{color:#fff!important;background-color:#008a00!important} | ||||
| .w3-olive,.w3-hover-olive:hover{color:#fff!important;background-color:#6d8764!important} | ||||
| .w3-paper,.w3-hover-paper:hover{color:#000!important;background-color:#f8f9fa!important}.w3-sienna,.w3-hover-sienna:hover{color:#fff!important;background-color:#a0522d!important} | ||||
| .w3-paper,.w3-hover-paper:hover{color:#000!important;background-color:#f8f9fa!important} | ||||
| .w3-sienna,.w3-hover-sienna:hover{color:#fff!important;background-color:#a0522d!important} | ||||
| .w3-taupe,.w3-hover-taupe:hover{color:#fff!important;background-color:#87794e!important} | ||||
|  | ||||
| .w3-danger{color:#fff!important;background-color:#dd0000!important} | ||||
| .w3-note{color:#000!important;background-color:#fff599!important} | ||||
| .w3-info{color:#fff!important;background-color:#0a6fc2!important} | ||||
| .w3-warning{color:#000!important;background-color:#ffb305!important} | ||||
| .w3-success{color:#fff!important;background-color:#008a00!important} | ||||
| .w3-pale-red,.w3-hover-pale-red:hover{color:#000!important;background-color:#ffdddd!important} | ||||
| .w3-pale-green,.w3-hover-pale-green:hover{color:#000!important;background-color:#ddffdd!important} | ||||
| .w3-pale-yellow,.w3-hover-pale-yellow:hover{color:#000!important;background-color:#ffffcc!important} | ||||
|   | ||||
| @@ -25,14 +25,14 @@ | ||||
| }: | ||||
| pkgs.stdenv.mkDerivation rec { | ||||
|   pname = "tildefriends"; | ||||
|   version = "0.0.29"; | ||||
|   version = "0.2025.9"; | ||||
|  | ||||
|   src = pkgs.fetchFromGitea { | ||||
|     domain = "dev.tildefriends.net"; | ||||
|     owner = "cory"; | ||||
|     repo = "tildefriends"; | ||||
|     rev = "v${version}"; | ||||
|     hash = "sha256-bQXFpocOYOlFmVj9OZeQhNrgFuTJ8sx2RSw1tgmelOM="; | ||||
|     hash = "sha256-1nhsfhdOO5HIiiTMb+uROB8nDPL/UpOYm52hZ/OpPyk="; | ||||
|     fetchSubmodules = true; | ||||
|   }; | ||||
|  | ||||
|   | ||||
							
								
								
									
										2
									
								
								deps/codemirror/cm6.js
									
									
									
									
										vendored
									
									
								
							
							
						
						
							
								
								
									
										490
									
								
								deps/codemirror_src/package-lock.json
									
									
									
										generated
									
									
										vendored
									
									
								
							
							
						
						| @@ -19,9 +19,10 @@ | ||||
|       } | ||||
|     }, | ||||
|     "node_modules/@codemirror/autocomplete": { | ||||
|       "version": "6.18.6", | ||||
|       "resolved": "https://registry.npmjs.org/@codemirror/autocomplete/-/autocomplete-6.18.6.tgz", | ||||
|       "integrity": "sha512-PHHBXFomUs5DF+9tCOM/UoW6XQ4R44lLNNhRaW9PKPTU0D7lIjRg3ElxaJnTwsl/oHiR93WSXDBrekhoUGCPtg==", | ||||
|       "version": "6.19.1", | ||||
|       "resolved": "https://registry.npmjs.org/@codemirror/autocomplete/-/autocomplete-6.19.1.tgz", | ||||
|       "integrity": "sha512-q6NenYkEy2fn9+JyjIxMWcNjzTL/IhwqfzOut1/G3PrIFkrbl4AL7Wkse5tLrQUUyqGoAKU5+Pi5jnnXxH5HGw==", | ||||
|       "license": "MIT", | ||||
|       "dependencies": { | ||||
|         "@codemirror/language": "^6.0.0", | ||||
|         "@codemirror/state": "^6.0.0", | ||||
| @@ -30,9 +31,10 @@ | ||||
|       } | ||||
|     }, | ||||
|     "node_modules/@codemirror/commands": { | ||||
|       "version": "6.8.1", | ||||
|       "resolved": "https://registry.npmjs.org/@codemirror/commands/-/commands-6.8.1.tgz", | ||||
|       "integrity": "sha512-KlGVYufHMQzxbdQONiLyGQDUW0itrLZwq3CcY7xpv9ZLRHqzkBSoteocBHtMCoY7/Ci4xhzSrToIeLg7FxHuaw==", | ||||
|       "version": "6.10.0", | ||||
|       "resolved": "https://registry.npmjs.org/@codemirror/commands/-/commands-6.10.0.tgz", | ||||
|       "integrity": "sha512-2xUIc5mHXQzT16JnyOFkh8PvfeXuIut3pslWGfsGOhxP/lpgRm9HOl/mpzLErgt5mXDovqA0d11P21gofRLb9w==", | ||||
|       "license": "MIT", | ||||
|       "dependencies": { | ||||
|         "@codemirror/language": "^6.0.0", | ||||
|         "@codemirror/state": "^6.4.0", | ||||
| @@ -44,6 +46,7 @@ | ||||
|       "version": "6.3.1", | ||||
|       "resolved": "https://registry.npmjs.org/@codemirror/lang-css/-/lang-css-6.3.1.tgz", | ||||
|       "integrity": "sha512-kr5fwBGiGtmz6l0LSJIbno9QrifNMUusivHbnA1H6Dmqy4HZFte3UAICix1VuKo0lMPKQr2rqB+0BkKi/S3Ejg==", | ||||
|       "license": "MIT", | ||||
|       "dependencies": { | ||||
|         "@codemirror/autocomplete": "^6.0.0", | ||||
|         "@codemirror/language": "^6.0.0", | ||||
| @@ -53,9 +56,10 @@ | ||||
|       } | ||||
|     }, | ||||
|     "node_modules/@codemirror/lang-html": { | ||||
|       "version": "6.4.9", | ||||
|       "resolved": "https://registry.npmjs.org/@codemirror/lang-html/-/lang-html-6.4.9.tgz", | ||||
|       "integrity": "sha512-aQv37pIMSlueybId/2PVSP6NPnmurFDVmZwzc7jszd2KAF8qd4VBbvNYPXWQq90WIARjsdVkPbw29pszmHws3Q==", | ||||
|       "version": "6.4.11", | ||||
|       "resolved": "https://registry.npmjs.org/@codemirror/lang-html/-/lang-html-6.4.11.tgz", | ||||
|       "integrity": "sha512-9NsXp7Nwp891pQchI7gPdTwBuSuT3K65NGTHWHNJ55HjYcHLllr0rbIZNdOzas9ztc1EUVBlHou85FFZS4BNnw==", | ||||
|       "license": "MIT", | ||||
|       "dependencies": { | ||||
|         "@codemirror/autocomplete": "^6.0.0", | ||||
|         "@codemirror/lang-css": "^6.0.0", | ||||
| @@ -65,13 +69,14 @@ | ||||
|         "@codemirror/view": "^6.17.0", | ||||
|         "@lezer/common": "^1.0.0", | ||||
|         "@lezer/css": "^1.1.0", | ||||
|         "@lezer/html": "^1.3.0" | ||||
|         "@lezer/html": "^1.3.12" | ||||
|       } | ||||
|     }, | ||||
|     "node_modules/@codemirror/lang-javascript": { | ||||
|       "version": "6.2.3", | ||||
|       "resolved": "https://registry.npmjs.org/@codemirror/lang-javascript/-/lang-javascript-6.2.3.tgz", | ||||
|       "integrity": "sha512-8PR3vIWg7pSu7ur8A07pGiYHgy3hHj+mRYRCSG8q+mPIrl0F02rgpGv+DsQTHRTc30rydOsf5PZ7yjKFg2Ackw==", | ||||
|       "version": "6.2.4", | ||||
|       "resolved": "https://registry.npmjs.org/@codemirror/lang-javascript/-/lang-javascript-6.2.4.tgz", | ||||
|       "integrity": "sha512-0WVmhp1QOqZ4Rt6GlVGwKJN3KW7Xh4H2q8ZZNGZaP6lRdxXJzmjm4FqvmOojVj6khWJHIb9sp7U/72W7xQgqAA==", | ||||
|       "license": "MIT", | ||||
|       "dependencies": { | ||||
|         "@codemirror/autocomplete": "^6.0.0", | ||||
|         "@codemirror/language": "^6.6.0", | ||||
| @@ -83,18 +88,20 @@ | ||||
|       } | ||||
|     }, | ||||
|     "node_modules/@codemirror/lang-json": { | ||||
|       "version": "6.0.1", | ||||
|       "resolved": "https://registry.npmjs.org/@codemirror/lang-json/-/lang-json-6.0.1.tgz", | ||||
|       "integrity": "sha512-+T1flHdgpqDDlJZ2Lkil/rLiRy684WMLc74xUnjJH48GQdfJo/pudlTRreZmKwzP8/tGdKf83wlbAdOCzlJOGQ==", | ||||
|       "version": "6.0.2", | ||||
|       "resolved": "https://registry.npmjs.org/@codemirror/lang-json/-/lang-json-6.0.2.tgz", | ||||
|       "integrity": "sha512-x2OtO+AvwEHrEwR0FyyPtfDUiloG3rnVTSZV1W8UteaLL8/MajQd8DpvUb2YVzC+/T18aSDv0H9mu+xw0EStoQ==", | ||||
|       "license": "MIT", | ||||
|       "dependencies": { | ||||
|         "@codemirror/language": "^6.0.0", | ||||
|         "@lezer/json": "^1.0.0" | ||||
|       } | ||||
|     }, | ||||
|     "node_modules/@codemirror/language": { | ||||
|       "version": "6.11.0", | ||||
|       "resolved": "https://registry.npmjs.org/@codemirror/language/-/language-6.11.0.tgz", | ||||
|       "integrity": "sha512-A7+f++LodNNc1wGgoRDTt78cOwWm9KVezApgjOMp1W4hM0898nsqBXwF+sbePE7ZRcjN7Sa1Z5m2oN27XkmEjQ==", | ||||
|       "version": "6.11.3", | ||||
|       "resolved": "https://registry.npmjs.org/@codemirror/language/-/language-6.11.3.tgz", | ||||
|       "integrity": "sha512-9HBM2XnwDj7fnu0551HkGdrUrrqmYq/WC5iv6nbY2WdicXdGbhR/gfbZOH73Aqj4351alY1+aoG9rCNfiwS1RA==", | ||||
|       "license": "MIT", | ||||
|       "dependencies": { | ||||
|         "@codemirror/state": "^6.0.0", | ||||
|         "@codemirror/view": "^6.23.0", | ||||
| @@ -105,9 +112,10 @@ | ||||
|       } | ||||
|     }, | ||||
|     "node_modules/@codemirror/lint": { | ||||
|       "version": "6.8.5", | ||||
|       "resolved": "https://registry.npmjs.org/@codemirror/lint/-/lint-6.8.5.tgz", | ||||
|       "integrity": "sha512-s3n3KisH7dx3vsoeGMxsbRAgKe4O1vbrnKBClm99PU0fWxmxsx5rR2PfqQgIt+2MMJBHbiJ5rfIdLYfB9NNvsA==", | ||||
|       "version": "6.9.1", | ||||
|       "resolved": "https://registry.npmjs.org/@codemirror/lint/-/lint-6.9.1.tgz", | ||||
|       "integrity": "sha512-te7To1EQHePBQQzasDKWmK2xKINIXpk+xAiSYr9ZN+VB4KaT+/Hi2PEkeErTk5BV3PTz1TLyQL4MtJfPkKZ9sw==", | ||||
|       "license": "MIT", | ||||
|       "dependencies": { | ||||
|         "@codemirror/state": "^6.0.0", | ||||
|         "@codemirror/view": "^6.35.0", | ||||
| @@ -115,9 +123,10 @@ | ||||
|       } | ||||
|     }, | ||||
|     "node_modules/@codemirror/search": { | ||||
|       "version": "6.5.10", | ||||
|       "resolved": "https://registry.npmjs.org/@codemirror/search/-/search-6.5.10.tgz", | ||||
|       "integrity": "sha512-RMdPdmsrUf53pb2VwflKGHEe1XVM07hI7vV2ntgw1dmqhimpatSJKva4VA9h4TLUDOD4EIF02201oZurpnEFsg==", | ||||
|       "version": "6.5.11", | ||||
|       "resolved": "https://registry.npmjs.org/@codemirror/search/-/search-6.5.11.tgz", | ||||
|       "integrity": "sha512-KmWepDE6jUdL6n8cAAqIpRmLPBZ5ZKnicE8oGU/s3QrAVID+0VhLFrzUucVKHG5035/BSykhExDL/Xm7dHthiA==", | ||||
|       "license": "MIT", | ||||
|       "dependencies": { | ||||
|         "@codemirror/state": "^6.0.0", | ||||
|         "@codemirror/view": "^6.0.0", | ||||
| @@ -128,14 +137,16 @@ | ||||
|       "version": "6.5.2", | ||||
|       "resolved": "https://registry.npmjs.org/@codemirror/state/-/state-6.5.2.tgz", | ||||
|       "integrity": "sha512-FVqsPqtPWKVVL3dPSxy8wEF/ymIEuVzF1PK3VbUgrxXpJUSHQWWZz4JMToquRxnkw+36LTamCZG2iua2Ptq0fA==", | ||||
|       "license": "MIT", | ||||
|       "dependencies": { | ||||
|         "@marijn/find-cluster-break": "^1.0.0" | ||||
|       } | ||||
|     }, | ||||
|     "node_modules/@codemirror/theme-one-dark": { | ||||
|       "version": "6.1.2", | ||||
|       "resolved": "https://registry.npmjs.org/@codemirror/theme-one-dark/-/theme-one-dark-6.1.2.tgz", | ||||
|       "integrity": "sha512-F+sH0X16j/qFLMAfbciKTxVOwkdAS336b7AXTKOZhy8BR3eH/RelsnLgLFINrpST63mmN2OuwUt0W2ndUgYwUA==", | ||||
|       "version": "6.1.3", | ||||
|       "resolved": "https://registry.npmjs.org/@codemirror/theme-one-dark/-/theme-one-dark-6.1.3.tgz", | ||||
|       "integrity": "sha512-NzBdIvEJmx6fjeremiGp3t/okrLPYT0d9orIc7AFun8oZcRk58aejkqhv6spnz4MLAevrKNPMQYXEWMg4s+sKA==", | ||||
|       "license": "MIT", | ||||
|       "dependencies": { | ||||
|         "@codemirror/language": "^6.0.0", | ||||
|         "@codemirror/state": "^6.0.0", | ||||
| @@ -144,27 +155,26 @@ | ||||
|       } | ||||
|     }, | ||||
|     "node_modules/@codemirror/view": { | ||||
|       "version": "6.36.5", | ||||
|       "resolved": "https://registry.npmjs.org/@codemirror/view/-/view-6.36.5.tgz", | ||||
|       "integrity": "sha512-cd+FZEUlu3GQCYnguYm3EkhJ8KJVisqqUsCOKedBoAt/d9c76JUUap6U0UrpElln5k6VyrEOYliMuDAKIeDQLg==", | ||||
|       "version": "6.38.6", | ||||
|       "resolved": "https://registry.npmjs.org/@codemirror/view/-/view-6.38.6.tgz", | ||||
|       "integrity": "sha512-qiS0z1bKs5WOvHIAC0Cybmv4AJSkAXgX5aD6Mqd2epSLlVJsQl8NG23jCVouIgkh4All/mrbdsf2UOLFnJw0tw==", | ||||
|       "license": "MIT", | ||||
|       "dependencies": { | ||||
|         "@codemirror/state": "^6.5.0", | ||||
|         "crelt": "^1.0.6", | ||||
|         "style-mod": "^4.1.0", | ||||
|         "w3c-keyname": "^2.2.4" | ||||
|       } | ||||
|     }, | ||||
|     "node_modules/@jridgewell/gen-mapping": { | ||||
|       "version": "0.3.8", | ||||
|       "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.8.tgz", | ||||
|       "integrity": "sha512-imAbBGkb+ebQyxKgzv5Hu2nmROxoDOXHh80evxdoXNOrvAnVx7zimzc1Oo5h9RlfV4vPXaE2iM5pOFbvOCClWA==", | ||||
|       "version": "0.3.13", | ||||
|       "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", | ||||
|       "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", | ||||
|       "dev": true, | ||||
|       "license": "MIT", | ||||
|       "dependencies": { | ||||
|         "@jridgewell/set-array": "^1.2.1", | ||||
|         "@jridgewell/sourcemap-codec": "^1.4.10", | ||||
|         "@jridgewell/sourcemap-codec": "^1.5.0", | ||||
|         "@jridgewell/trace-mapping": "^0.3.24" | ||||
|       }, | ||||
|       "engines": { | ||||
|         "node": ">=6.0.0" | ||||
|       } | ||||
|     }, | ||||
|     "node_modules/@jridgewell/resolve-uri": { | ||||
| @@ -172,72 +182,71 @@ | ||||
|       "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", | ||||
|       "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", | ||||
|       "dev": true, | ||||
|       "engines": { | ||||
|         "node": ">=6.0.0" | ||||
|       } | ||||
|     }, | ||||
|     "node_modules/@jridgewell/set-array": { | ||||
|       "version": "1.2.1", | ||||
|       "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.2.1.tgz", | ||||
|       "integrity": "sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==", | ||||
|       "dev": true, | ||||
|       "license": "MIT", | ||||
|       "engines": { | ||||
|         "node": ">=6.0.0" | ||||
|       } | ||||
|     }, | ||||
|     "node_modules/@jridgewell/source-map": { | ||||
|       "version": "0.3.6", | ||||
|       "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.6.tgz", | ||||
|       "integrity": "sha512-1ZJTZebgqllO79ue2bm3rIGud/bOe0pP5BjSRCRxxYkEZS8STV7zN84UBbiYu7jy+eCKSnVIUgoWWE/tt+shMQ==", | ||||
|       "version": "0.3.11", | ||||
|       "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.11.tgz", | ||||
|       "integrity": "sha512-ZMp1V8ZFcPG5dIWnQLr3NSI1MiCU7UETdS/A0G8V/XWHvJv3ZsFqutJn1Y5RPmAPX6F3BiE397OqveU/9NCuIA==", | ||||
|       "dev": true, | ||||
|       "license": "MIT", | ||||
|       "dependencies": { | ||||
|         "@jridgewell/gen-mapping": "^0.3.5", | ||||
|         "@jridgewell/trace-mapping": "^0.3.25" | ||||
|       } | ||||
|     }, | ||||
|     "node_modules/@jridgewell/sourcemap-codec": { | ||||
|       "version": "1.5.0", | ||||
|       "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz", | ||||
|       "integrity": "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==", | ||||
|       "dev": true | ||||
|       "version": "1.5.5", | ||||
|       "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", | ||||
|       "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", | ||||
|       "dev": true, | ||||
|       "license": "MIT" | ||||
|     }, | ||||
|     "node_modules/@jridgewell/trace-mapping": { | ||||
|       "version": "0.3.25", | ||||
|       "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz", | ||||
|       "integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==", | ||||
|       "version": "0.3.31", | ||||
|       "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", | ||||
|       "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", | ||||
|       "dev": true, | ||||
|       "license": "MIT", | ||||
|       "dependencies": { | ||||
|         "@jridgewell/resolve-uri": "^3.1.0", | ||||
|         "@jridgewell/sourcemap-codec": "^1.4.14" | ||||
|       } | ||||
|     }, | ||||
|     "node_modules/@lezer/common": { | ||||
|       "version": "1.2.3", | ||||
|       "resolved": "https://registry.npmjs.org/@lezer/common/-/common-1.2.3.tgz", | ||||
|       "integrity": "sha512-w7ojc8ejBqr2REPsWxJjrMFsA/ysDCFICn8zEOR9mrqzOu2amhITYuLD8ag6XZf0CFXDrhKqw7+tW8cX66NaDA==" | ||||
|       "version": "1.3.0", | ||||
|       "resolved": "https://registry.npmjs.org/@lezer/common/-/common-1.3.0.tgz", | ||||
|       "integrity": "sha512-L9X8uHCYU310o99L3/MpJKYxPzXPOS7S0NmBaM7UO/x2Kb2WbmMLSkfvdr1KxRIFYOpbY0Jhn7CfLSUDzL8arQ==", | ||||
|       "license": "MIT" | ||||
|     }, | ||||
|     "node_modules/@lezer/css": { | ||||
|       "version": "1.1.11", | ||||
|       "resolved": "https://registry.npmjs.org/@lezer/css/-/css-1.1.11.tgz", | ||||
|       "integrity": "sha512-FuAnusbLBl1SEAtfN8NdShxYJiESKw9LAFysfea1T96jD3ydBn12oYjaSG1a04BQRIUd93/0D8e5CV1cUMkmQg==", | ||||
|       "version": "1.3.0", | ||||
|       "resolved": "https://registry.npmjs.org/@lezer/css/-/css-1.3.0.tgz", | ||||
|       "integrity": "sha512-pBL7hup88KbI7hXnZV3PQsn43DHy6TWyzuyk2AO9UyoXcDltvIdqWKE1dLL/45JVZ+YZkHe1WVHqO6wugZZWcw==", | ||||
|       "license": "MIT", | ||||
|       "dependencies": { | ||||
|         "@lezer/common": "^1.2.0", | ||||
|         "@lezer/highlight": "^1.0.0", | ||||
|         "@lezer/lr": "^1.0.0" | ||||
|         "@lezer/lr": "^1.3.0" | ||||
|       } | ||||
|     }, | ||||
|     "node_modules/@lezer/highlight": { | ||||
|       "version": "1.2.1", | ||||
|       "resolved": "https://registry.npmjs.org/@lezer/highlight/-/highlight-1.2.1.tgz", | ||||
|       "integrity": "sha512-Z5duk4RN/3zuVO7Jq0pGLJ3qynpxUVsh7IbUbGj88+uV2ApSAn6kWg2au3iJb+0Zi7kKtqffIESgNcRXWZWmSA==", | ||||
|       "version": "1.2.2", | ||||
|       "resolved": "https://registry.npmjs.org/@lezer/highlight/-/highlight-1.2.2.tgz", | ||||
|       "integrity": "sha512-z8TQwaBXXQIvG6i2g3e9cgMwUUXu9Ib7jo2qRRggdhwKpM56Dw3PM3wmexn+EGaaOZ7az0K7sjc3/gcGW7sz7A==", | ||||
|       "license": "MIT", | ||||
|       "dependencies": { | ||||
|         "@lezer/common": "^1.0.0" | ||||
|         "@lezer/common": "^1.3.0" | ||||
|       } | ||||
|     }, | ||||
|     "node_modules/@lezer/html": { | ||||
|       "version": "1.3.10", | ||||
|       "resolved": "https://registry.npmjs.org/@lezer/html/-/html-1.3.10.tgz", | ||||
|       "integrity": "sha512-dqpT8nISx/p9Do3AchvYGV3qYc4/rKr3IBZxlHmpIKam56P47RSHkSF5f13Vu9hebS1jM0HmtJIwLbWz1VIY6w==", | ||||
|       "version": "1.3.12", | ||||
|       "resolved": "https://registry.npmjs.org/@lezer/html/-/html-1.3.12.tgz", | ||||
|       "integrity": "sha512-RJ7eRWdaJe3bsiiLLHjCFT1JMk8m1YP9kaUbvu2rMLEoOnke9mcTVDyfOslsln0LtujdWespjJ39w6zo+RsQYw==", | ||||
|       "license": "MIT", | ||||
|       "dependencies": { | ||||
|         "@lezer/common": "^1.2.0", | ||||
|         "@lezer/highlight": "^1.0.0", | ||||
| @@ -245,9 +254,10 @@ | ||||
|       } | ||||
|     }, | ||||
|     "node_modules/@lezer/javascript": { | ||||
|       "version": "1.4.21", | ||||
|       "resolved": "https://registry.npmjs.org/@lezer/javascript/-/javascript-1.4.21.tgz", | ||||
|       "integrity": "sha512-lL+1fcuxWYPURMM/oFZLEDm0XuLN128QPV+VuGtKpeaOGdcl9F2LYC3nh1S9LkPqx9M0mndZFdXCipNAZpzIkQ==", | ||||
|       "version": "1.5.4", | ||||
|       "resolved": "https://registry.npmjs.org/@lezer/javascript/-/javascript-1.5.4.tgz", | ||||
|       "integrity": "sha512-vvYx3MhWqeZtGPwDStM2dwgljd5smolYD2lR2UyFcHfxbBQebqx8yjmFmxtJ/E6nN6u1D9srOiVWm3Rb4tmcUA==", | ||||
|       "license": "MIT", | ||||
|       "dependencies": { | ||||
|         "@lezer/common": "^1.2.0", | ||||
|         "@lezer/highlight": "^1.1.3", | ||||
| @@ -258,6 +268,7 @@ | ||||
|       "version": "1.0.3", | ||||
|       "resolved": "https://registry.npmjs.org/@lezer/json/-/json-1.0.3.tgz", | ||||
|       "integrity": "sha512-BP9KzdF9Y35PDpv04r0VeSTKDeox5vVr3efE7eBbx3r4s3oNLfunchejZhjArmeieBH+nVOpgIiBJpEAv8ilqQ==", | ||||
|       "license": "MIT", | ||||
|       "dependencies": { | ||||
|         "@lezer/common": "^1.2.0", | ||||
|         "@lezer/highlight": "^1.0.0", | ||||
| @@ -268,6 +279,7 @@ | ||||
|       "version": "1.4.2", | ||||
|       "resolved": "https://registry.npmjs.org/@lezer/lr/-/lr-1.4.2.tgz", | ||||
|       "integrity": "sha512-pu0K1jCIdnQ12aWNaAVU5bzi7Bd1w54J3ECgANPmYLtQKP0HBj2cE/5coBD66MT10xbtIuUr7tg0Shbsvk0mDA==", | ||||
|       "license": "MIT", | ||||
|       "dependencies": { | ||||
|         "@lezer/common": "^1.0.0" | ||||
|       } | ||||
| @@ -275,12 +287,14 @@ | ||||
|     "node_modules/@marijn/find-cluster-break": { | ||||
|       "version": "1.0.2", | ||||
|       "resolved": "https://registry.npmjs.org/@marijn/find-cluster-break/-/find-cluster-break-1.0.2.tgz", | ||||
|       "integrity": "sha512-l0h88YhZFyKdXIFNfSWpyjStDjGHwZ/U7iobcK1cQQD8sejsONdQtTVU+1wVN1PBw40PiiHB1vA5S7VTfQiP9g==" | ||||
|       "integrity": "sha512-l0h88YhZFyKdXIFNfSWpyjStDjGHwZ/U7iobcK1cQQD8sejsONdQtTVU+1wVN1PBw40PiiHB1vA5S7VTfQiP9g==", | ||||
|       "license": "MIT" | ||||
|     }, | ||||
|     "node_modules/@rollup/plugin-node-resolve": { | ||||
|       "version": "15.3.1", | ||||
|       "resolved": "https://registry.npmjs.org/@rollup/plugin-node-resolve/-/plugin-node-resolve-15.3.1.tgz", | ||||
|       "integrity": "sha512-tgg6b91pAybXHJQMAAwW9VuWBO6Thi+q7BCNARLwSqlmsHz0XYURtGvh/AuwSADXSI4h/2uHbs7s4FzlZDGSGA==", | ||||
|       "license": "MIT", | ||||
|       "dependencies": { | ||||
|         "@rollup/pluginutils": "^5.0.1", | ||||
|         "@types/resolve": "1.20.2", | ||||
| @@ -305,6 +319,7 @@ | ||||
|       "resolved": "https://registry.npmjs.org/@rollup/plugin-terser/-/plugin-terser-0.4.4.tgz", | ||||
|       "integrity": "sha512-XHeJC5Bgvs8LfukDwWZp7yeqin6ns8RTl2B9avbejt6tZqsqvVoWI7ZTQrcNsfKEDWBTnTxM8nMDkO2IFFbd0A==", | ||||
|       "dev": true, | ||||
|       "license": "MIT", | ||||
|       "dependencies": { | ||||
|         "serialize-javascript": "^6.0.1", | ||||
|         "smob": "^1.0.0", | ||||
| @@ -323,9 +338,10 @@ | ||||
|       } | ||||
|     }, | ||||
|     "node_modules/@rollup/pluginutils": { | ||||
|       "version": "5.1.4", | ||||
|       "resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-5.1.4.tgz", | ||||
|       "integrity": "sha512-USm05zrsFxYLPdWWq+K3STlWiT/3ELn3RcV5hJMghpeAIhxfsUIg6mt12CBJBInWMV4VneoV7SfGv8xIwo2qNQ==", | ||||
|       "version": "5.3.0", | ||||
|       "resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-5.3.0.tgz", | ||||
|       "integrity": "sha512-5EdhGZtnu3V88ces7s53hhfK5KSASnJZv8Lulpc04cWO3REESroJXg73DFsOmgbU2BhwV0E20bu2IDZb3VKW4Q==", | ||||
|       "license": "MIT", | ||||
|       "dependencies": { | ||||
|         "@types/estree": "^1.0.0", | ||||
|         "estree-walker": "^2.0.2", | ||||
| @@ -344,260 +360,309 @@ | ||||
|       } | ||||
|     }, | ||||
|     "node_modules/@rollup/rollup-android-arm-eabi": { | ||||
|       "version": "4.39.0", | ||||
|       "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.39.0.tgz", | ||||
|       "integrity": "sha512-lGVys55Qb00Wvh8DMAocp5kIcaNzEFTmGhfFd88LfaogYTRKrdxgtlO5H6S49v2Nd8R2C6wLOal0qv6/kCkOwA==", | ||||
|       "version": "4.52.5", | ||||
|       "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.52.5.tgz", | ||||
|       "integrity": "sha512-8c1vW4ocv3UOMp9K+gToY5zL2XiiVw3k7f1ksf4yO1FlDFQ1C2u72iACFnSOceJFsWskc2WZNqeRhFRPzv+wtQ==", | ||||
|       "cpu": [ | ||||
|         "arm" | ||||
|       ], | ||||
|       "license": "MIT", | ||||
|       "optional": true, | ||||
|       "os": [ | ||||
|         "android" | ||||
|       ] | ||||
|     }, | ||||
|     "node_modules/@rollup/rollup-android-arm64": { | ||||
|       "version": "4.39.0", | ||||
|       "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.39.0.tgz", | ||||
|       "integrity": "sha512-It9+M1zE31KWfqh/0cJLrrsCPiF72PoJjIChLX+rEcujVRCb4NLQ5QzFkzIZW8Kn8FTbvGQBY5TkKBau3S8cCQ==", | ||||
|       "version": "4.52.5", | ||||
|       "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.52.5.tgz", | ||||
|       "integrity": "sha512-mQGfsIEFcu21mvqkEKKu2dYmtuSZOBMmAl5CFlPGLY94Vlcm+zWApK7F/eocsNzp8tKmbeBP8yXyAbx0XHsFNA==", | ||||
|       "cpu": [ | ||||
|         "arm64" | ||||
|       ], | ||||
|       "license": "MIT", | ||||
|       "optional": true, | ||||
|       "os": [ | ||||
|         "android" | ||||
|       ] | ||||
|     }, | ||||
|     "node_modules/@rollup/rollup-darwin-arm64": { | ||||
|       "version": "4.39.0", | ||||
|       "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.39.0.tgz", | ||||
|       "integrity": "sha512-lXQnhpFDOKDXiGxsU9/l8UEGGM65comrQuZ+lDcGUx+9YQ9dKpF3rSEGepyeR5AHZ0b5RgiligsBhWZfSSQh8Q==", | ||||
|       "version": "4.52.5", | ||||
|       "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.52.5.tgz", | ||||
|       "integrity": "sha512-takF3CR71mCAGA+v794QUZ0b6ZSrgJkArC+gUiG6LB6TQty9T0Mqh3m2ImRBOxS2IeYBo4lKWIieSvnEk2OQWA==", | ||||
|       "cpu": [ | ||||
|         "arm64" | ||||
|       ], | ||||
|       "license": "MIT", | ||||
|       "optional": true, | ||||
|       "os": [ | ||||
|         "darwin" | ||||
|       ] | ||||
|     }, | ||||
|     "node_modules/@rollup/rollup-darwin-x64": { | ||||
|       "version": "4.39.0", | ||||
|       "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.39.0.tgz", | ||||
|       "integrity": "sha512-mKXpNZLvtEbgu6WCkNij7CGycdw9cJi2k9v0noMb++Vab12GZjFgUXD69ilAbBh034Zwn95c2PNSz9xM7KYEAQ==", | ||||
|       "version": "4.52.5", | ||||
|       "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.52.5.tgz", | ||||
|       "integrity": "sha512-W901Pla8Ya95WpxDn//VF9K9u2JbocwV/v75TE0YIHNTbhqUTv9w4VuQ9MaWlNOkkEfFwkdNhXgcLqPSmHy0fA==", | ||||
|       "cpu": [ | ||||
|         "x64" | ||||
|       ], | ||||
|       "license": "MIT", | ||||
|       "optional": true, | ||||
|       "os": [ | ||||
|         "darwin" | ||||
|       ] | ||||
|     }, | ||||
|     "node_modules/@rollup/rollup-freebsd-arm64": { | ||||
|       "version": "4.39.0", | ||||
|       "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.39.0.tgz", | ||||
|       "integrity": "sha512-jivRRlh2Lod/KvDZx2zUR+I4iBfHcu2V/BA2vasUtdtTN2Uk3jfcZczLa81ESHZHPHy4ih3T/W5rPFZ/hX7RtQ==", | ||||
|       "version": "4.52.5", | ||||
|       "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.52.5.tgz", | ||||
|       "integrity": "sha512-QofO7i7JycsYOWxe0GFqhLmF6l1TqBswJMvICnRUjqCx8b47MTo46W8AoeQwiokAx3zVryVnxtBMcGcnX12LvA==", | ||||
|       "cpu": [ | ||||
|         "arm64" | ||||
|       ], | ||||
|       "license": "MIT", | ||||
|       "optional": true, | ||||
|       "os": [ | ||||
|         "freebsd" | ||||
|       ] | ||||
|     }, | ||||
|     "node_modules/@rollup/rollup-freebsd-x64": { | ||||
|       "version": "4.39.0", | ||||
|       "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.39.0.tgz", | ||||
|       "integrity": "sha512-8RXIWvYIRK9nO+bhVz8DwLBepcptw633gv/QT4015CpJ0Ht8punmoHU/DuEd3iw9Hr8UwUV+t+VNNuZIWYeY7Q==", | ||||
|       "version": "4.52.5", | ||||
|       "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.52.5.tgz", | ||||
|       "integrity": "sha512-jr21b/99ew8ujZubPo9skbrItHEIE50WdV86cdSoRkKtmWa+DDr6fu2c/xyRT0F/WazZpam6kk7IHBerSL7LDQ==", | ||||
|       "cpu": [ | ||||
|         "x64" | ||||
|       ], | ||||
|       "license": "MIT", | ||||
|       "optional": true, | ||||
|       "os": [ | ||||
|         "freebsd" | ||||
|       ] | ||||
|     }, | ||||
|     "node_modules/@rollup/rollup-linux-arm-gnueabihf": { | ||||
|       "version": "4.39.0", | ||||
|       "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.39.0.tgz", | ||||
|       "integrity": "sha512-mz5POx5Zu58f2xAG5RaRRhp3IZDK7zXGk5sdEDj4o96HeaXhlUwmLFzNlc4hCQi5sGdR12VDgEUqVSHer0lI9g==", | ||||
|       "version": "4.52.5", | ||||
|       "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.52.5.tgz", | ||||
|       "integrity": "sha512-PsNAbcyv9CcecAUagQefwX8fQn9LQ4nZkpDboBOttmyffnInRy8R8dSg6hxxl2Re5QhHBf6FYIDhIj5v982ATQ==", | ||||
|       "cpu": [ | ||||
|         "arm" | ||||
|       ], | ||||
|       "license": "MIT", | ||||
|       "optional": true, | ||||
|       "os": [ | ||||
|         "linux" | ||||
|       ] | ||||
|     }, | ||||
|     "node_modules/@rollup/rollup-linux-arm-musleabihf": { | ||||
|       "version": "4.39.0", | ||||
|       "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.39.0.tgz", | ||||
|       "integrity": "sha512-+YDwhM6gUAyakl0CD+bMFpdmwIoRDzZYaTWV3SDRBGkMU/VpIBYXXEvkEcTagw/7VVkL2vA29zU4UVy1mP0/Yw==", | ||||
|       "version": "4.52.5", | ||||
|       "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.52.5.tgz", | ||||
|       "integrity": "sha512-Fw4tysRutyQc/wwkmcyoqFtJhh0u31K+Q6jYjeicsGJJ7bbEq8LwPWV/w0cnzOqR2m694/Af6hpFayLJZkG2VQ==", | ||||
|       "cpu": [ | ||||
|         "arm" | ||||
|       ], | ||||
|       "license": "MIT", | ||||
|       "optional": true, | ||||
|       "os": [ | ||||
|         "linux" | ||||
|       ] | ||||
|     }, | ||||
|     "node_modules/@rollup/rollup-linux-arm64-gnu": { | ||||
|       "version": "4.39.0", | ||||
|       "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.39.0.tgz", | ||||
|       "integrity": "sha512-EKf7iF7aK36eEChvlgxGnk7pdJfzfQbNvGV/+l98iiMwU23MwvmV0Ty3pJ0p5WQfm3JRHOytSIqD9LB7Bq7xdQ==", | ||||
|       "version": "4.52.5", | ||||
|       "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.52.5.tgz", | ||||
|       "integrity": "sha512-a+3wVnAYdQClOTlyapKmyI6BLPAFYs0JM8HRpgYZQO02rMR09ZcV9LbQB+NL6sljzG38869YqThrRnfPMCDtZg==", | ||||
|       "cpu": [ | ||||
|         "arm64" | ||||
|       ], | ||||
|       "license": "MIT", | ||||
|       "optional": true, | ||||
|       "os": [ | ||||
|         "linux" | ||||
|       ] | ||||
|     }, | ||||
|     "node_modules/@rollup/rollup-linux-arm64-musl": { | ||||
|       "version": "4.39.0", | ||||
|       "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.39.0.tgz", | ||||
|       "integrity": "sha512-vYanR6MtqC7Z2SNr8gzVnzUul09Wi1kZqJaek3KcIlI/wq5Xtq4ZPIZ0Mr/st/sv/NnaPwy/D4yXg5x0B3aUUA==", | ||||
|       "version": "4.52.5", | ||||
|       "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.52.5.tgz", | ||||
|       "integrity": "sha512-AvttBOMwO9Pcuuf7m9PkC1PUIKsfaAJ4AYhy944qeTJgQOqJYJ9oVl2nYgY7Rk0mkbsuOpCAYSs6wLYB2Xiw0Q==", | ||||
|       "cpu": [ | ||||
|         "arm64" | ||||
|       ], | ||||
|       "license": "MIT", | ||||
|       "optional": true, | ||||
|       "os": [ | ||||
|         "linux" | ||||
|       ] | ||||
|     }, | ||||
|     "node_modules/@rollup/rollup-linux-loongarch64-gnu": { | ||||
|       "version": "4.39.0", | ||||
|       "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loongarch64-gnu/-/rollup-linux-loongarch64-gnu-4.39.0.tgz", | ||||
|       "integrity": "sha512-NMRUT40+h0FBa5fb+cpxtZoGAggRem16ocVKIv5gDB5uLDgBIwrIsXlGqYbLwW8YyO3WVTk1FkFDjMETYlDqiw==", | ||||
|     "node_modules/@rollup/rollup-linux-loong64-gnu": { | ||||
|       "version": "4.52.5", | ||||
|       "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.52.5.tgz", | ||||
|       "integrity": "sha512-DkDk8pmXQV2wVrF6oq5tONK6UHLz/XcEVow4JTTerdeV1uqPeHxwcg7aFsfnSm9L+OO8WJsWotKM2JJPMWrQtA==", | ||||
|       "cpu": [ | ||||
|         "loong64" | ||||
|       ], | ||||
|       "license": "MIT", | ||||
|       "optional": true, | ||||
|       "os": [ | ||||
|         "linux" | ||||
|       ] | ||||
|     }, | ||||
|     "node_modules/@rollup/rollup-linux-powerpc64le-gnu": { | ||||
|       "version": "4.39.0", | ||||
|       "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.39.0.tgz", | ||||
|       "integrity": "sha512-0pCNnmxgduJ3YRt+D+kJ6Ai/r+TaePu9ZLENl+ZDV/CdVczXl95CbIiwwswu4L+K7uOIGf6tMo2vm8uadRaICQ==", | ||||
|     "node_modules/@rollup/rollup-linux-ppc64-gnu": { | ||||
|       "version": "4.52.5", | ||||
|       "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.52.5.tgz", | ||||
|       "integrity": "sha512-W/b9ZN/U9+hPQVvlGwjzi+Wy4xdoH2I8EjaCkMvzpI7wJUs8sWJ03Rq96jRnHkSrcHTpQe8h5Tg3ZzUPGauvAw==", | ||||
|       "cpu": [ | ||||
|         "ppc64" | ||||
|       ], | ||||
|       "license": "MIT", | ||||
|       "optional": true, | ||||
|       "os": [ | ||||
|         "linux" | ||||
|       ] | ||||
|     }, | ||||
|     "node_modules/@rollup/rollup-linux-riscv64-gnu": { | ||||
|       "version": "4.39.0", | ||||
|       "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.39.0.tgz", | ||||
|       "integrity": "sha512-t7j5Zhr7S4bBtksT73bO6c3Qa2AV/HqiGlj9+KB3gNF5upcVkx+HLgxTm8DK4OkzsOYqbdqbLKwvGMhylJCPhQ==", | ||||
|       "version": "4.52.5", | ||||
|       "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.52.5.tgz", | ||||
|       "integrity": "sha512-sjQLr9BW7R/ZiXnQiWPkErNfLMkkWIoCz7YMn27HldKsADEKa5WYdobaa1hmN6slu9oWQbB6/jFpJ+P2IkVrmw==", | ||||
|       "cpu": [ | ||||
|         "riscv64" | ||||
|       ], | ||||
|       "license": "MIT", | ||||
|       "optional": true, | ||||
|       "os": [ | ||||
|         "linux" | ||||
|       ] | ||||
|     }, | ||||
|     "node_modules/@rollup/rollup-linux-riscv64-musl": { | ||||
|       "version": "4.39.0", | ||||
|       "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.39.0.tgz", | ||||
|       "integrity": "sha512-m6cwI86IvQ7M93MQ2RF5SP8tUjD39Y7rjb1qjHgYh28uAPVU8+k/xYWvxRO3/tBN2pZkSMa5RjnPuUIbrwVxeA==", | ||||
|       "version": "4.52.5", | ||||
|       "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.52.5.tgz", | ||||
|       "integrity": "sha512-hq3jU/kGyjXWTvAh2awn8oHroCbrPm8JqM7RUpKjalIRWWXE01CQOf/tUNWNHjmbMHg/hmNCwc/Pz3k1T/j/Lg==", | ||||
|       "cpu": [ | ||||
|         "riscv64" | ||||
|       ], | ||||
|       "license": "MIT", | ||||
|       "optional": true, | ||||
|       "os": [ | ||||
|         "linux" | ||||
|       ] | ||||
|     }, | ||||
|     "node_modules/@rollup/rollup-linux-s390x-gnu": { | ||||
|       "version": "4.39.0", | ||||
|       "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.39.0.tgz", | ||||
|       "integrity": "sha512-iRDJd2ebMunnk2rsSBYlsptCyuINvxUfGwOUldjv5M4tpa93K8tFMeYGpNk2+Nxl+OBJnBzy2/JCscGeO507kA==", | ||||
|       "version": "4.52.5", | ||||
|       "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.52.5.tgz", | ||||
|       "integrity": "sha512-gn8kHOrku8D4NGHMK1Y7NA7INQTRdVOntt1OCYypZPRt6skGbddska44K8iocdpxHTMMNui5oH4elPH4QOLrFQ==", | ||||
|       "cpu": [ | ||||
|         "s390x" | ||||
|       ], | ||||
|       "license": "MIT", | ||||
|       "optional": true, | ||||
|       "os": [ | ||||
|         "linux" | ||||
|       ] | ||||
|     }, | ||||
|     "node_modules/@rollup/rollup-linux-x64-gnu": { | ||||
|       "version": "4.39.0", | ||||
|       "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.39.0.tgz", | ||||
|       "integrity": "sha512-t9jqYw27R6Lx0XKfEFe5vUeEJ5pF3SGIM6gTfONSMb7DuG6z6wfj2yjcoZxHg129veTqU7+wOhY6GX8wmf90dA==", | ||||
|       "version": "4.52.5", | ||||
|       "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.52.5.tgz", | ||||
|       "integrity": "sha512-hXGLYpdhiNElzN770+H2nlx+jRog8TyynpTVzdlc6bndktjKWyZyiCsuDAlpd+j+W+WNqfcyAWz9HxxIGfZm1Q==", | ||||
|       "cpu": [ | ||||
|         "x64" | ||||
|       ], | ||||
|       "license": "MIT", | ||||
|       "optional": true, | ||||
|       "os": [ | ||||
|         "linux" | ||||
|       ] | ||||
|     }, | ||||
|     "node_modules/@rollup/rollup-linux-x64-musl": { | ||||
|       "version": "4.39.0", | ||||
|       "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.39.0.tgz", | ||||
|       "integrity": "sha512-ThFdkrFDP55AIsIZDKSBWEt/JcWlCzydbZHinZ0F/r1h83qbGeenCt/G/wG2O0reuENDD2tawfAj2s8VK7Bugg==", | ||||
|       "version": "4.52.5", | ||||
|       "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.52.5.tgz", | ||||
|       "integrity": "sha512-arCGIcuNKjBoKAXD+y7XomR9gY6Mw7HnFBv5Rw7wQRvwYLR7gBAgV7Mb2QTyjXfTveBNFAtPt46/36vV9STLNg==", | ||||
|       "cpu": [ | ||||
|         "x64" | ||||
|       ], | ||||
|       "license": "MIT", | ||||
|       "optional": true, | ||||
|       "os": [ | ||||
|         "linux" | ||||
|       ] | ||||
|     }, | ||||
|     "node_modules/@rollup/rollup-win32-arm64-msvc": { | ||||
|       "version": "4.39.0", | ||||
|       "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.39.0.tgz", | ||||
|       "integrity": "sha512-jDrLm6yUtbOg2TYB3sBF3acUnAwsIksEYjLeHL+TJv9jg+TmTwdyjnDex27jqEMakNKf3RwwPahDIt7QXCSqRQ==", | ||||
|     "node_modules/@rollup/rollup-openharmony-arm64": { | ||||
|       "version": "4.52.5", | ||||
|       "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.52.5.tgz", | ||||
|       "integrity": "sha512-QoFqB6+/9Rly/RiPjaomPLmR/13cgkIGfA40LHly9zcH1S0bN2HVFYk3a1eAyHQyjs3ZJYlXvIGtcCs5tko9Cw==", | ||||
|       "cpu": [ | ||||
|         "arm64" | ||||
|       ], | ||||
|       "license": "MIT", | ||||
|       "optional": true, | ||||
|       "os": [ | ||||
|         "openharmony" | ||||
|       ] | ||||
|     }, | ||||
|     "node_modules/@rollup/rollup-win32-arm64-msvc": { | ||||
|       "version": "4.52.5", | ||||
|       "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.52.5.tgz", | ||||
|       "integrity": "sha512-w0cDWVR6MlTstla1cIfOGyl8+qb93FlAVutcor14Gf5Md5ap5ySfQ7R9S/NjNaMLSFdUnKGEasmVnu3lCMqB7w==", | ||||
|       "cpu": [ | ||||
|         "arm64" | ||||
|       ], | ||||
|       "license": "MIT", | ||||
|       "optional": true, | ||||
|       "os": [ | ||||
|         "win32" | ||||
|       ] | ||||
|     }, | ||||
|     "node_modules/@rollup/rollup-win32-ia32-msvc": { | ||||
|       "version": "4.39.0", | ||||
|       "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.39.0.tgz", | ||||
|       "integrity": "sha512-6w9uMuza+LbLCVoNKL5FSLE7yvYkq9laSd09bwS0tMjkwXrmib/4KmoJcrKhLWHvw19mwU+33ndC69T7weNNjQ==", | ||||
|       "version": "4.52.5", | ||||
|       "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.52.5.tgz", | ||||
|       "integrity": "sha512-Aufdpzp7DpOTULJCuvzqcItSGDH73pF3ko/f+ckJhxQyHtp67rHw3HMNxoIdDMUITJESNE6a8uh4Lo4SLouOUg==", | ||||
|       "cpu": [ | ||||
|         "ia32" | ||||
|       ], | ||||
|       "license": "MIT", | ||||
|       "optional": true, | ||||
|       "os": [ | ||||
|         "win32" | ||||
|       ] | ||||
|     }, | ||||
|     "node_modules/@rollup/rollup-win32-x64-gnu": { | ||||
|       "version": "4.52.5", | ||||
|       "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.52.5.tgz", | ||||
|       "integrity": "sha512-UGBUGPFp1vkj6p8wCRraqNhqwX/4kNQPS57BCFc8wYh0g94iVIW33wJtQAx3G7vrjjNtRaxiMUylM0ktp/TRSQ==", | ||||
|       "cpu": [ | ||||
|         "x64" | ||||
|       ], | ||||
|       "license": "MIT", | ||||
|       "optional": true, | ||||
|       "os": [ | ||||
|         "win32" | ||||
|       ] | ||||
|     }, | ||||
|     "node_modules/@rollup/rollup-win32-x64-msvc": { | ||||
|       "version": "4.39.0", | ||||
|       "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.39.0.tgz", | ||||
|       "integrity": "sha512-yAkUOkIKZlK5dl7u6dg897doBgLXmUHhIINM2c+sND3DZwnrdQkkSiDh7N75Ll4mM4dxSkYfXqU9fW3lLkMFug==", | ||||
|       "version": "4.52.5", | ||||
|       "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.52.5.tgz", | ||||
|       "integrity": "sha512-TAcgQh2sSkykPRWLrdyy2AiceMckNf5loITqXxFI5VuQjS5tSuw3WlwdN8qv8vzjLAUTvYaH/mVjSFpbkFbpTg==", | ||||
|       "cpu": [ | ||||
|         "x64" | ||||
|       ], | ||||
|       "license": "MIT", | ||||
|       "optional": true, | ||||
|       "os": [ | ||||
|         "win32" | ||||
|       ] | ||||
|     }, | ||||
|     "node_modules/@types/estree": { | ||||
|       "version": "1.0.7", | ||||
|       "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.7.tgz", | ||||
|       "integrity": "sha512-w28IoSUCJpidD/TGviZwwMJckNESJZXFu7NBZ5YJ4mEUnNraUn9Pm8HSZm/jDF1pDWYKspWE7oVphigUPRakIQ==" | ||||
|       "version": "1.0.8", | ||||
|       "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", | ||||
|       "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", | ||||
|       "license": "MIT" | ||||
|     }, | ||||
|     "node_modules/@types/resolve": { | ||||
|       "version": "1.20.2", | ||||
|       "resolved": "https://registry.npmjs.org/@types/resolve/-/resolve-1.20.2.tgz", | ||||
|       "integrity": "sha512-60BCwRFOZCQhDncwQdxxeOEEkbc5dIMccYLwbxsS4TUNeVECQ/pBJ0j09mrHOl/JJvpRPGwO9SvE4nR2Nb/a4Q==" | ||||
|       "integrity": "sha512-60BCwRFOZCQhDncwQdxxeOEEkbc5dIMccYLwbxsS4TUNeVECQ/pBJ0j09mrHOl/JJvpRPGwO9SvE4nR2Nb/a4Q==", | ||||
|       "license": "MIT" | ||||
|     }, | ||||
|     "node_modules/acorn": { | ||||
|       "version": "8.14.1", | ||||
|       "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.14.1.tgz", | ||||
|       "integrity": "sha512-OvQ/2pUDKmgfCg++xsTX1wGxfTaszcHVcTctW4UJB4hibJx2HXxxO5UmVgyjMa+ZDsiaf5wWLXYpRWMmBI0QHg==", | ||||
|       "version": "8.15.0", | ||||
|       "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", | ||||
|       "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", | ||||
|       "dev": true, | ||||
|       "license": "MIT", | ||||
|       "bin": { | ||||
|         "acorn": "bin/acorn" | ||||
|       }, | ||||
| @@ -609,12 +674,14 @@ | ||||
|       "version": "1.1.2", | ||||
|       "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", | ||||
|       "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", | ||||
|       "dev": true | ||||
|       "dev": true, | ||||
|       "license": "MIT" | ||||
|     }, | ||||
|     "node_modules/codemirror": { | ||||
|       "version": "6.0.1", | ||||
|       "resolved": "https://registry.npmjs.org/codemirror/-/codemirror-6.0.1.tgz", | ||||
|       "integrity": "sha512-J8j+nZ+CdWmIeFIGXEFbFPtpiYacFMDR8GlHK3IyHQJMCaVRfGx9NT+Hxivv1ckLWPvNdZqndbr/7lVhrf/Svg==", | ||||
|       "version": "6.0.2", | ||||
|       "resolved": "https://registry.npmjs.org/codemirror/-/codemirror-6.0.2.tgz", | ||||
|       "integrity": "sha512-VhydHotNW5w1UGK0Qj96BwSk/Zqbp9WbnyK2W/eVMv4QyF41INRGpjUhFJY7/uDNuudSc33a/PKr4iDqRduvHw==", | ||||
|       "license": "MIT", | ||||
|       "dependencies": { | ||||
|         "@codemirror/autocomplete": "^6.0.0", | ||||
|         "@codemirror/commands": "^6.0.0", | ||||
| @@ -629,17 +696,20 @@ | ||||
|       "version": "2.20.3", | ||||
|       "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", | ||||
|       "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", | ||||
|       "dev": true | ||||
|       "dev": true, | ||||
|       "license": "MIT" | ||||
|     }, | ||||
|     "node_modules/crelt": { | ||||
|       "version": "1.0.6", | ||||
|       "resolved": "https://registry.npmjs.org/crelt/-/crelt-1.0.6.tgz", | ||||
|       "integrity": "sha512-VQ2MBenTq1fWZUH9DJNGti7kKv6EeAuYr3cLwxUWhIu1baTaXh4Ib5W2CqHVqib4/MqbYGJqiL3Zb8GJZr3l4g==" | ||||
|       "integrity": "sha512-VQ2MBenTq1fWZUH9DJNGti7kKv6EeAuYr3cLwxUWhIu1baTaXh4Ib5W2CqHVqib4/MqbYGJqiL3Zb8GJZr3l4g==", | ||||
|       "license": "MIT" | ||||
|     }, | ||||
|     "node_modules/deepmerge": { | ||||
|       "version": "4.3.1", | ||||
|       "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz", | ||||
|       "integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==", | ||||
|       "license": "MIT", | ||||
|       "engines": { | ||||
|         "node": ">=0.10.0" | ||||
|       } | ||||
| @@ -647,13 +717,15 @@ | ||||
|     "node_modules/estree-walker": { | ||||
|       "version": "2.0.2", | ||||
|       "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz", | ||||
|       "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==" | ||||
|       "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==", | ||||
|       "license": "MIT" | ||||
|     }, | ||||
|     "node_modules/fsevents": { | ||||
|       "version": "2.3.3", | ||||
|       "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", | ||||
|       "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", | ||||
|       "hasInstallScript": true, | ||||
|       "license": "MIT", | ||||
|       "optional": true, | ||||
|       "os": [ | ||||
|         "darwin" | ||||
| @@ -666,6 +738,7 @@ | ||||
|       "version": "1.1.2", | ||||
|       "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", | ||||
|       "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", | ||||
|       "license": "MIT", | ||||
|       "funding": { | ||||
|         "url": "https://github.com/sponsors/ljharb" | ||||
|       } | ||||
| @@ -674,6 +747,7 @@ | ||||
|       "version": "2.0.2", | ||||
|       "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", | ||||
|       "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", | ||||
|       "license": "MIT", | ||||
|       "dependencies": { | ||||
|         "function-bind": "^1.1.2" | ||||
|       }, | ||||
| @@ -685,6 +759,7 @@ | ||||
|       "version": "2.16.1", | ||||
|       "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.1.tgz", | ||||
|       "integrity": "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==", | ||||
|       "license": "MIT", | ||||
|       "dependencies": { | ||||
|         "hasown": "^2.0.2" | ||||
|       }, | ||||
| @@ -698,17 +773,20 @@ | ||||
|     "node_modules/is-module": { | ||||
|       "version": "1.0.0", | ||||
|       "resolved": "https://registry.npmjs.org/is-module/-/is-module-1.0.0.tgz", | ||||
|       "integrity": "sha512-51ypPSPCoTEIN9dy5Oy+h4pShgJmPCygKfyRCISBI+JoWT/2oJvK8QPxmwv7b/p239jXrm9M1mlQbyKJ5A152g==" | ||||
|       "integrity": "sha512-51ypPSPCoTEIN9dy5Oy+h4pShgJmPCygKfyRCISBI+JoWT/2oJvK8QPxmwv7b/p239jXrm9M1mlQbyKJ5A152g==", | ||||
|       "license": "MIT" | ||||
|     }, | ||||
|     "node_modules/path-parse": { | ||||
|       "version": "1.0.7", | ||||
|       "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", | ||||
|       "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==" | ||||
|       "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", | ||||
|       "license": "MIT" | ||||
|     }, | ||||
|     "node_modules/picomatch": { | ||||
|       "version": "4.0.2", | ||||
|       "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.2.tgz", | ||||
|       "integrity": "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==", | ||||
|       "version": "4.0.3", | ||||
|       "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", | ||||
|       "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", | ||||
|       "license": "MIT", | ||||
|       "engines": { | ||||
|         "node": ">=12" | ||||
|       }, | ||||
| @@ -721,16 +799,18 @@ | ||||
|       "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", | ||||
|       "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", | ||||
|       "dev": true, | ||||
|       "license": "MIT", | ||||
|       "dependencies": { | ||||
|         "safe-buffer": "^5.1.0" | ||||
|       } | ||||
|     }, | ||||
|     "node_modules/resolve": { | ||||
|       "version": "1.22.10", | ||||
|       "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.10.tgz", | ||||
|       "integrity": "sha512-NPRy+/ncIMeDlTAsuqwKIiferiawhefFJtkNSW0qZJEqMEb+qBt/77B/jGeeek+F0uOeN05CDa6HXbbIgtVX4w==", | ||||
|       "version": "1.22.11", | ||||
|       "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.11.tgz", | ||||
|       "integrity": "sha512-RfqAvLnMl313r7c9oclB1HhUEAezcpLjz95wFH4LVuhk9JF/r22qmVP9AMmOU4vMX7Q8pN8jwNg/CSpdFnMjTQ==", | ||||
|       "license": "MIT", | ||||
|       "dependencies": { | ||||
|         "is-core-module": "^2.16.0", | ||||
|         "is-core-module": "^2.16.1", | ||||
|         "path-parse": "^1.0.7", | ||||
|         "supports-preserve-symlinks-flag": "^1.0.0" | ||||
|       }, | ||||
| @@ -745,11 +825,12 @@ | ||||
|       } | ||||
|     }, | ||||
|     "node_modules/rollup": { | ||||
|       "version": "4.39.0", | ||||
|       "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.39.0.tgz", | ||||
|       "integrity": "sha512-thI8kNc02yNvnmJp8dr3fNWJ9tCONDhp6TV35X6HkKGGs9E6q7YWCHbe5vKiTa7TAiNcFEmXKj3X/pG2b3ci0g==", | ||||
|       "version": "4.52.5", | ||||
|       "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.52.5.tgz", | ||||
|       "integrity": "sha512-3GuObel8h7Kqdjt0gxkEzaifHTqLVW56Y/bjN7PSQtkKr0w3V/QYSdt6QWYtd7A1xUtYQigtdUfgj1RvWVtorw==", | ||||
|       "license": "MIT", | ||||
|       "dependencies": { | ||||
|         "@types/estree": "1.0.7" | ||||
|         "@types/estree": "1.0.8" | ||||
|       }, | ||||
|       "bin": { | ||||
|         "rollup": "dist/bin/rollup" | ||||
| @@ -759,26 +840,28 @@ | ||||
|         "npm": ">=8.0.0" | ||||
|       }, | ||||
|       "optionalDependencies": { | ||||
|         "@rollup/rollup-android-arm-eabi": "4.39.0", | ||||
|         "@rollup/rollup-android-arm64": "4.39.0", | ||||
|         "@rollup/rollup-darwin-arm64": "4.39.0", | ||||
|         "@rollup/rollup-darwin-x64": "4.39.0", | ||||
|         "@rollup/rollup-freebsd-arm64": "4.39.0", | ||||
|         "@rollup/rollup-freebsd-x64": "4.39.0", | ||||
|         "@rollup/rollup-linux-arm-gnueabihf": "4.39.0", | ||||
|         "@rollup/rollup-linux-arm-musleabihf": "4.39.0", | ||||
|         "@rollup/rollup-linux-arm64-gnu": "4.39.0", | ||||
|         "@rollup/rollup-linux-arm64-musl": "4.39.0", | ||||
|         "@rollup/rollup-linux-loongarch64-gnu": "4.39.0", | ||||
|         "@rollup/rollup-linux-powerpc64le-gnu": "4.39.0", | ||||
|         "@rollup/rollup-linux-riscv64-gnu": "4.39.0", | ||||
|         "@rollup/rollup-linux-riscv64-musl": "4.39.0", | ||||
|         "@rollup/rollup-linux-s390x-gnu": "4.39.0", | ||||
|         "@rollup/rollup-linux-x64-gnu": "4.39.0", | ||||
|         "@rollup/rollup-linux-x64-musl": "4.39.0", | ||||
|         "@rollup/rollup-win32-arm64-msvc": "4.39.0", | ||||
|         "@rollup/rollup-win32-ia32-msvc": "4.39.0", | ||||
|         "@rollup/rollup-win32-x64-msvc": "4.39.0", | ||||
|         "@rollup/rollup-android-arm-eabi": "4.52.5", | ||||
|         "@rollup/rollup-android-arm64": "4.52.5", | ||||
|         "@rollup/rollup-darwin-arm64": "4.52.5", | ||||
|         "@rollup/rollup-darwin-x64": "4.52.5", | ||||
|         "@rollup/rollup-freebsd-arm64": "4.52.5", | ||||
|         "@rollup/rollup-freebsd-x64": "4.52.5", | ||||
|         "@rollup/rollup-linux-arm-gnueabihf": "4.52.5", | ||||
|         "@rollup/rollup-linux-arm-musleabihf": "4.52.5", | ||||
|         "@rollup/rollup-linux-arm64-gnu": "4.52.5", | ||||
|         "@rollup/rollup-linux-arm64-musl": "4.52.5", | ||||
|         "@rollup/rollup-linux-loong64-gnu": "4.52.5", | ||||
|         "@rollup/rollup-linux-ppc64-gnu": "4.52.5", | ||||
|         "@rollup/rollup-linux-riscv64-gnu": "4.52.5", | ||||
|         "@rollup/rollup-linux-riscv64-musl": "4.52.5", | ||||
|         "@rollup/rollup-linux-s390x-gnu": "4.52.5", | ||||
|         "@rollup/rollup-linux-x64-gnu": "4.52.5", | ||||
|         "@rollup/rollup-linux-x64-musl": "4.52.5", | ||||
|         "@rollup/rollup-openharmony-arm64": "4.52.5", | ||||
|         "@rollup/rollup-win32-arm64-msvc": "4.52.5", | ||||
|         "@rollup/rollup-win32-ia32-msvc": "4.52.5", | ||||
|         "@rollup/rollup-win32-x64-gnu": "4.52.5", | ||||
|         "@rollup/rollup-win32-x64-msvc": "4.52.5", | ||||
|         "fsevents": "~2.3.2" | ||||
|       } | ||||
|     }, | ||||
| @@ -800,13 +883,15 @@ | ||||
|           "type": "consulting", | ||||
|           "url": "https://feross.org/support" | ||||
|         } | ||||
|       ] | ||||
|       ], | ||||
|       "license": "MIT" | ||||
|     }, | ||||
|     "node_modules/serialize-javascript": { | ||||
|       "version": "6.0.2", | ||||
|       "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.2.tgz", | ||||
|       "integrity": "sha512-Saa1xPByTTq2gdeFZYLLo+RFE35NHZkAbqZeWNd3BpzppeVisAqpDjcp8dyf6uIvEqJRd46jemmyA4iFIeVk8g==", | ||||
|       "dev": true, | ||||
|       "license": "BSD-3-Clause", | ||||
|       "dependencies": { | ||||
|         "randombytes": "^2.1.0" | ||||
|       } | ||||
| @@ -815,13 +900,15 @@ | ||||
|       "version": "1.5.0", | ||||
|       "resolved": "https://registry.npmjs.org/smob/-/smob-1.5.0.tgz", | ||||
|       "integrity": "sha512-g6T+p7QO8npa+/hNx9ohv1E5pVCmWrVCUzUXJyLdMmftX6ER0oiWY/w9knEonLpnOp6b6FenKnMfR8gqwWdwig==", | ||||
|       "dev": true | ||||
|       "dev": true, | ||||
|       "license": "MIT" | ||||
|     }, | ||||
|     "node_modules/source-map": { | ||||
|       "version": "0.6.1", | ||||
|       "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", | ||||
|       "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", | ||||
|       "dev": true, | ||||
|       "license": "BSD-3-Clause", | ||||
|       "engines": { | ||||
|         "node": ">=0.10.0" | ||||
|       } | ||||
| @@ -831,20 +918,23 @@ | ||||
|       "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", | ||||
|       "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", | ||||
|       "dev": true, | ||||
|       "license": "MIT", | ||||
|       "dependencies": { | ||||
|         "buffer-from": "^1.0.0", | ||||
|         "source-map": "^0.6.0" | ||||
|       } | ||||
|     }, | ||||
|     "node_modules/style-mod": { | ||||
|       "version": "4.1.2", | ||||
|       "resolved": "https://registry.npmjs.org/style-mod/-/style-mod-4.1.2.tgz", | ||||
|       "integrity": "sha512-wnD1HyVqpJUI2+eKZ+eo1UwghftP6yuFheBqqe+bWCotBjC2K1YnteJILRMs3SM4V/0dLEW1SC27MWP5y+mwmw==" | ||||
|       "version": "4.1.3", | ||||
|       "resolved": "https://registry.npmjs.org/style-mod/-/style-mod-4.1.3.tgz", | ||||
|       "integrity": "sha512-i/n8VsZydrugj3Iuzll8+x/00GH2vnYsk1eomD8QiRrSAeW6ItbCQDtfXCeJHd0iwiNagqjQkvpvREEPtW3IoQ==", | ||||
|       "license": "MIT" | ||||
|     }, | ||||
|     "node_modules/supports-preserve-symlinks-flag": { | ||||
|       "version": "1.0.0", | ||||
|       "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", | ||||
|       "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", | ||||
|       "license": "MIT", | ||||
|       "engines": { | ||||
|         "node": ">= 0.4" | ||||
|       }, | ||||
| @@ -853,13 +943,14 @@ | ||||
|       } | ||||
|     }, | ||||
|     "node_modules/terser": { | ||||
|       "version": "5.39.0", | ||||
|       "resolved": "https://registry.npmjs.org/terser/-/terser-5.39.0.tgz", | ||||
|       "integrity": "sha512-LBAhFyLho16harJoWMg/nZsQYgTrg5jXOn2nCYjRUcZZEdE3qa2zb8QEDRUGVZBW4rlazf2fxkg8tztybTaqWw==", | ||||
|       "version": "5.44.0", | ||||
|       "resolved": "https://registry.npmjs.org/terser/-/terser-5.44.0.tgz", | ||||
|       "integrity": "sha512-nIVck8DK+GM/0Frwd+nIhZ84pR/BX7rmXMfYwyg+Sri5oGVE99/E3KvXqpC2xHFxyqXyGHTKBSioxxplrO4I4w==", | ||||
|       "dev": true, | ||||
|       "license": "BSD-2-Clause", | ||||
|       "dependencies": { | ||||
|         "@jridgewell/source-map": "^0.3.3", | ||||
|         "acorn": "^8.8.2", | ||||
|         "acorn": "^8.15.0", | ||||
|         "commander": "^2.20.0", | ||||
|         "source-map-support": "~0.5.20" | ||||
|       }, | ||||
| @@ -873,7 +964,8 @@ | ||||
|     "node_modules/w3c-keyname": { | ||||
|       "version": "2.2.8", | ||||
|       "resolved": "https://registry.npmjs.org/w3c-keyname/-/w3c-keyname-2.2.8.tgz", | ||||
|       "integrity": "sha512-dpojBhNsCNN7T82Tm7k26A6G9ML3NkhDsnw9n/eoxSRlVBB4CEtIQ/KTCLI2Fwf3ataSXRhYFkQi3SlnFwPvPQ==" | ||||
|       "integrity": "sha512-dpojBhNsCNN7T82Tm7k26A6G9ML3NkhDsnw9n/eoxSRlVBB4CEtIQ/KTCLI2Fwf3ataSXRhYFkQi3SlnFwPvPQ==", | ||||
|       "license": "MIT" | ||||
|     } | ||||
|   } | ||||
| } | ||||
|   | ||||
							
								
								
									
										2
									
								
								deps/libbacktrace
									
									
									
									
										vendored
									
									
								
							
							
								
								
								
								
								
							
						
						
							
								
								
									
										2
									
								
								deps/libuv
									
									
									
									
										vendored
									
									
								
							
							
								
								
								
								
								
							
						
						
							
								
								
									
										42
									
								
								deps/lit/lit-all.min.js
									
									
									
									
										vendored
									
									
								
							
							
						
						
							
								
								
									
										2
									
								
								deps/lit/lit-all.min.js.map
									
									
									
									
										vendored
									
									
								
							
							
						
						
							
								
								
									
										1
									
								
								deps/openssl_src
									
									
									
									
										vendored
									
									
								
							
							
								
								
								
								
								
							
						
						
							
								
								
									
										2
									
								
								deps/picohttpparser
									
									
									
									
										vendored
									
									
								
							
							
								
								
								
								
								
							
						
						
							
								
								
									
										2
									
								
								deps/quickjs
									
									
									
									
										vendored
									
									
								
							
							
								
								
								
								
								
							
						
						
							
								
								
									
										2
									
								
								deps/speedscope/index.html
									
									
									
									
										vendored
									
									
								
							
							
						
						| @@ -11,7 +11,7 @@ | ||||
|     <link rel="icon" type="image/x-icon" href="favicon-FOKUP5Y5.ico"> | ||||
|   </head> | ||||
|   <body> | ||||
|     <script src="speedscope-VHEG2FVF.js"></script> | ||||
|     <script src="speedscope-432XE7GS.js"></script> | ||||
|      | ||||
|      | ||||
|      | ||||
|   | ||||
							
								
								
									
										6
									
								
								deps/speedscope/release.txt
									
									
									
									
										vendored
									
									
								
							
							
						
						| @@ -1,3 +1,3 @@ | ||||
| speedscope@1.22.2 | ||||
| Sat Feb 15 13:02:38 PST 2025 | ||||
| 1c254dcb3e2b4f6d921340d20e972d9d27b788f4 | ||||
| speedscope@1.24.0 | ||||
| Mon Oct 20 18:11:29 PDT 2025 | ||||
| fc76932551754a442cd5c4f0afdba28032d14d8a | ||||
|   | ||||
							
								
								
									
										1037
									
								
								deps/sqlite/shell.c
									
									
									
									
										vendored
									
									
								
							
							
						
						
							
								
								
									
										4759
									
								
								deps/sqlite/sqlite3.c
									
									
									
									
										vendored
									
									
								
							
							
						
						
							
								
								
									
										316
									
								
								deps/sqlite/sqlite3.h
									
									
									
									
										vendored
									
									
								
							
							
						
						| @@ -133,7 +133,7 @@ extern "C" { | ||||
| ** | ||||
| ** Since [version 3.6.18] ([dateof:3.6.18]), | ||||
| ** SQLite source code has been stored in the | ||||
| ** <a href="http://www.fossil-scm.org/">Fossil configuration management | ||||
| ** <a href="http://fossil-scm.org/">Fossil configuration management | ||||
| ** system</a>.  ^The SQLITE_SOURCE_ID macro evaluates to | ||||
| ** a string which identifies a particular check-in of SQLite | ||||
| ** within its configuration management system.  ^The SQLITE_SOURCE_ID | ||||
| @@ -146,9 +146,9 @@ extern "C" { | ||||
| ** [sqlite3_libversion_number()], [sqlite3_sourceid()], | ||||
| ** [sqlite_version()] and [sqlite_source_id()]. | ||||
| */ | ||||
| #define SQLITE_VERSION        "3.49.1" | ||||
| #define SQLITE_VERSION_NUMBER 3049001 | ||||
| #define SQLITE_SOURCE_ID      "2025-02-18 13:38:58 873d4e274b4988d260ba8354a9718324a1c26187a4ab4c1cc0227c03d0f10e70" | ||||
| #define SQLITE_VERSION        "3.50.4" | ||||
| #define SQLITE_VERSION_NUMBER 3050004 | ||||
| #define SQLITE_SOURCE_ID      "2025-07-30 19:33:53 4d8adfb30e03f9cf27f800a2c1ba3c48fb4ca1b08b0f5ed59a4d5ecbf45e20a3" | ||||
|  | ||||
| /* | ||||
| ** CAPI3REF: Run-Time Library Version Numbers | ||||
| @@ -1163,6 +1163,12 @@ struct sqlite3_io_methods { | ||||
| ** the value that M is to be set to. Before returning, the 32-bit signed | ||||
| ** integer is overwritten with the previous value of M. | ||||
| ** | ||||
| ** <li>[[SQLITE_FCNTL_BLOCK_ON_CONNECT]] | ||||
| ** The [SQLITE_FCNTL_BLOCK_ON_CONNECT] opcode is used to configure the | ||||
| ** VFS to block when taking a SHARED lock to connect to a wal mode database. | ||||
| ** This is used to implement the functionality associated with | ||||
| ** SQLITE_SETLK_BLOCK_ON_CONNECT. | ||||
| ** | ||||
| ** <li>[[SQLITE_FCNTL_DATA_VERSION]] | ||||
| ** The [SQLITE_FCNTL_DATA_VERSION] opcode is used to detect changes to | ||||
| ** a database file.  The argument is a pointer to a 32-bit unsigned integer. | ||||
| @@ -1259,6 +1265,7 @@ struct sqlite3_io_methods { | ||||
| #define SQLITE_FCNTL_CKSM_FILE              41 | ||||
| #define SQLITE_FCNTL_RESET_CACHE            42 | ||||
| #define SQLITE_FCNTL_NULL_IO                43 | ||||
| #define SQLITE_FCNTL_BLOCK_ON_CONNECT       44 | ||||
|  | ||||
| /* deprecated names */ | ||||
| #define SQLITE_GET_LOCKPROXYFILE      SQLITE_FCNTL_GET_LOCKPROXYFILE | ||||
| @@ -1989,13 +1996,16 @@ struct sqlite3_mem_methods { | ||||
| ** | ||||
| ** [[SQLITE_CONFIG_LOOKASIDE]] <dt>SQLITE_CONFIG_LOOKASIDE</dt> | ||||
| ** <dd> ^(The SQLITE_CONFIG_LOOKASIDE option takes two arguments that determine | ||||
| ** the default size of lookaside memory on each [database connection]. | ||||
| ** the default size of [lookaside memory] on each [database connection]. | ||||
| ** The first argument is the | ||||
| ** size of each lookaside buffer slot and the second is the number of | ||||
| ** slots allocated to each database connection.)^  ^(SQLITE_CONFIG_LOOKASIDE | ||||
| ** sets the <i>default</i> lookaside size. The [SQLITE_DBCONFIG_LOOKASIDE] | ||||
| ** option to [sqlite3_db_config()] can be used to change the lookaside | ||||
| ** configuration on individual connections.)^ </dd> | ||||
| ** size of each lookaside buffer slot ("sz") and the second is the number of | ||||
| ** slots allocated to each database connection ("cnt").)^ | ||||
| ** ^(SQLITE_CONFIG_LOOKASIDE sets the <i>default</i> lookaside size. | ||||
| ** The [SQLITE_DBCONFIG_LOOKASIDE] option to [sqlite3_db_config()] can | ||||
| ** be used to change the lookaside configuration on individual connections.)^ | ||||
| ** The [-DSQLITE_DEFAULT_LOOKASIDE] option can be used to change the | ||||
| ** default lookaside configuration at compile-time. | ||||
| ** </dd> | ||||
| ** | ||||
| ** [[SQLITE_CONFIG_PCACHE2]] <dt>SQLITE_CONFIG_PCACHE2</dt> | ||||
| ** <dd> ^(The SQLITE_CONFIG_PCACHE2 option takes a single argument which is | ||||
| @@ -2232,31 +2242,50 @@ struct sqlite3_mem_methods { | ||||
| ** [[SQLITE_DBCONFIG_LOOKASIDE]] | ||||
| ** <dt>SQLITE_DBCONFIG_LOOKASIDE</dt> | ||||
| ** <dd> The SQLITE_DBCONFIG_LOOKASIDE option is used to adjust the | ||||
| ** configuration of the lookaside memory allocator within a database | ||||
| ** configuration of the [lookaside memory allocator] within a database | ||||
| ** connection. | ||||
| ** The arguments to the SQLITE_DBCONFIG_LOOKASIDE option are <i>not</i> | ||||
| ** in the [DBCONFIG arguments|usual format]. | ||||
| ** The SQLITE_DBCONFIG_LOOKASIDE option takes three arguments, not two, | ||||
| ** so that a call to [sqlite3_db_config()] that uses SQLITE_DBCONFIG_LOOKASIDE | ||||
| ** should have a total of five parameters. | ||||
| ** ^The first argument (the third parameter to [sqlite3_db_config()] is a | ||||
| ** <ol> | ||||
| ** <li><p>The first argument ("buf") is a | ||||
| ** pointer to a memory buffer to use for lookaside memory. | ||||
| ** ^The first argument after the SQLITE_DBCONFIG_LOOKASIDE verb | ||||
| ** may be NULL in which case SQLite will allocate the | ||||
| ** lookaside buffer itself using [sqlite3_malloc()]. ^The second argument is the | ||||
| ** size of each lookaside buffer slot.  ^The third argument is the number of | ||||
| ** slots.  The size of the buffer in the first argument must be greater than | ||||
| ** or equal to the product of the second and third arguments.  The buffer | ||||
| ** must be aligned to an 8-byte boundary.  ^If the second argument to | ||||
| ** SQLITE_DBCONFIG_LOOKASIDE is not a multiple of 8, it is internally | ||||
| ** rounded down to the next smaller multiple of 8.  ^(The lookaside memory | ||||
| ** The first argument may be NULL in which case SQLite will allocate the | ||||
| ** lookaside buffer itself using [sqlite3_malloc()]. | ||||
| ** <li><P>The second argument ("sz") is the | ||||
| ** size of each lookaside buffer slot.  Lookaside is disabled if "sz" | ||||
| ** is less than 8.  The "sz" argument should be a multiple of 8 less than | ||||
| ** 65536.  If "sz" does not meet this constraint, it is reduced in size until | ||||
| ** it does. | ||||
| ** <li><p>The third argument ("cnt") is the number of slots. Lookaside is disabled | ||||
| ** if "cnt"is less than 1.  The "cnt" value will be reduced, if necessary, so | ||||
| ** that the product of "sz" and "cnt" does not exceed 2,147,418,112.  The "cnt" | ||||
| ** parameter is usually chosen so that the product of "sz" and "cnt" is less | ||||
| ** than 1,000,000. | ||||
| ** </ol> | ||||
| ** <p>If the "buf" argument is not NULL, then it must | ||||
| ** point to a memory buffer with a size that is greater than | ||||
| ** or equal to the product of "sz" and "cnt". | ||||
| ** The buffer must be aligned to an 8-byte boundary. | ||||
| ** The lookaside memory | ||||
| ** configuration for a database connection can only be changed when that | ||||
| ** connection is not currently using lookaside memory, or in other words | ||||
| ** when the "current value" returned by | ||||
| ** [sqlite3_db_status](D,[SQLITE_DBSTATUS_LOOKASIDE_USED],...) is zero. | ||||
| ** when the value returned by [SQLITE_DBSTATUS_LOOKASIDE_USED] is zero. | ||||
| ** Any attempt to change the lookaside memory configuration when lookaside | ||||
| ** memory is in use leaves the configuration unchanged and returns | ||||
| ** [SQLITE_BUSY].)^</dd> | ||||
| ** [SQLITE_BUSY]. | ||||
| ** If the "buf" argument is NULL and an attempt | ||||
| ** to allocate memory based on "sz" and "cnt" fails, then | ||||
| ** lookaside is silently disabled. | ||||
| ** <p> | ||||
| ** The [SQLITE_CONFIG_LOOKASIDE] configuration option can be used to set the | ||||
| ** default lookaside configuration at initialization.  The | ||||
| ** [-DSQLITE_DEFAULT_LOOKASIDE] option can be used to set the default lookaside | ||||
| ** configuration at compile-time.  Typical values for lookaside are 1200 for | ||||
| ** "sz" and 40 to 100 for "cnt". | ||||
| ** </dd> | ||||
| ** | ||||
| ** [[SQLITE_DBCONFIG_ENABLE_FKEY]] | ||||
| ** <dt>SQLITE_DBCONFIG_ENABLE_FKEY</dt> | ||||
| @@ -2993,6 +3022,44 @@ SQLITE_API int sqlite3_busy_handler(sqlite3*,int(*)(void*,int),void*); | ||||
| */ | ||||
| SQLITE_API int sqlite3_busy_timeout(sqlite3*, int ms); | ||||
|  | ||||
| /* | ||||
| ** CAPI3REF: Set the Setlk Timeout | ||||
| ** METHOD: sqlite3 | ||||
| ** | ||||
| ** This routine is only useful in SQLITE_ENABLE_SETLK_TIMEOUT builds. If | ||||
| ** the VFS supports blocking locks, it sets the timeout in ms used by | ||||
| ** eligible locks taken on wal mode databases by the specified database | ||||
| ** handle. In non-SQLITE_ENABLE_SETLK_TIMEOUT builds, or if the VFS does | ||||
| ** not support blocking locks, this function is a no-op. | ||||
| ** | ||||
| ** Passing 0 to this function disables blocking locks altogether. Passing | ||||
| ** -1 to this function requests that the VFS blocks for a long time - | ||||
| ** indefinitely if possible. The results of passing any other negative value | ||||
| ** are undefined. | ||||
| ** | ||||
| ** Internally, each SQLite database handle store two timeout values - the | ||||
| ** busy-timeout (used for rollback mode databases, or if the VFS does not | ||||
| ** support blocking locks) and the setlk-timeout (used for blocking locks | ||||
| ** on wal-mode databases). The sqlite3_busy_timeout() method sets both | ||||
| ** values, this function sets only the setlk-timeout value. Therefore, | ||||
| ** to configure separate busy-timeout and setlk-timeout values for a single | ||||
| ** database handle, call sqlite3_busy_timeout() followed by this function. | ||||
| ** | ||||
| ** Whenever the number of connections to a wal mode database falls from | ||||
| ** 1 to 0, the last connection takes an exclusive lock on the database, | ||||
| ** then checkpoints and deletes the wal file. While it is doing this, any | ||||
| ** new connection that tries to read from the database fails with an | ||||
| ** SQLITE_BUSY error. Or, if the SQLITE_SETLK_BLOCK_ON_CONNECT flag is | ||||
| ** passed to this API, the new connection blocks until the exclusive lock | ||||
| ** has been released. | ||||
| */ | ||||
| SQLITE_API int sqlite3_setlk_timeout(sqlite3*, int ms, int flags); | ||||
|  | ||||
| /* | ||||
| ** CAPI3REF: Flags for sqlite3_setlk_timeout() | ||||
| */ | ||||
| #define SQLITE_SETLK_BLOCK_ON_CONNECT 0x01 | ||||
|  | ||||
| /* | ||||
| ** CAPI3REF: Convenience Routines For Running Queries | ||||
| ** METHOD: sqlite3 | ||||
| @@ -4012,7 +4079,7 @@ SQLITE_API sqlite3_file *sqlite3_database_file_object(const char*); | ||||
| ** | ||||
| ** The sqlite3_create_filename(D,J,W,N,P) allocates memory to hold a version of | ||||
| ** database filename D with corresponding journal file J and WAL file W and | ||||
| ** with N URI parameters key/values pairs in the array P.  The result from | ||||
| ** an array P of N URI Key/Value pairs.  The result from | ||||
| ** sqlite3_create_filename(D,J,W,N,P) is a pointer to a database filename that | ||||
| ** is safe to pass to routines like: | ||||
| ** <ul> | ||||
| @@ -4693,7 +4760,7 @@ typedef struct sqlite3_context sqlite3_context; | ||||
| ** METHOD: sqlite3_stmt | ||||
| ** | ||||
| ** ^(In the SQL statement text input to [sqlite3_prepare_v2()] and its variants, | ||||
| ** literals may be replaced by a [parameter] that matches one of following | ||||
| ** literals may be replaced by a [parameter] that matches one of the following | ||||
| ** templates: | ||||
| ** | ||||
| ** <ul> | ||||
| @@ -4738,7 +4805,7 @@ typedef struct sqlite3_context sqlite3_context; | ||||
| ** | ||||
| ** [[byte-order determination rules]] ^The byte-order of | ||||
| ** UTF16 input text is determined by the byte-order mark (BOM, U+FEFF) | ||||
| ** found in first character, which is removed, or in the absence of a BOM | ||||
| ** found in the first character, which is removed, or in the absence of a BOM | ||||
| ** the byte order is the native byte order of the host | ||||
| ** machine for sqlite3_bind_text16() or the byte order specified in | ||||
| ** the 6th parameter for sqlite3_bind_text64().)^ | ||||
| @@ -4758,7 +4825,7 @@ typedef struct sqlite3_context sqlite3_context; | ||||
| ** or sqlite3_bind_text16() or sqlite3_bind_text64() then | ||||
| ** that parameter must be the byte offset | ||||
| ** where the NUL terminator would occur assuming the string were NUL | ||||
| ** terminated.  If any NUL characters occurs at byte offsets less than | ||||
| ** terminated.  If any NUL characters occur at byte offsets less than | ||||
| ** the value of the fourth parameter then the resulting string value will | ||||
| ** contain embedded NULs.  The result of expressions involving strings | ||||
| ** with embedded NULs is undefined. | ||||
| @@ -4970,7 +5037,7 @@ SQLITE_API const void *sqlite3_column_name16(sqlite3_stmt*, int N); | ||||
| ** METHOD: sqlite3_stmt | ||||
| ** | ||||
| ** ^These routines provide a means to determine the database, table, and | ||||
| ** table column that is the origin of a particular result column in | ||||
| ** table column that is the origin of a particular result column in a | ||||
| ** [SELECT] statement. | ||||
| ** ^The name of the database or table or column can be returned as | ||||
| ** either a UTF-8 or UTF-16 string.  ^The _database_ routines return | ||||
| @@ -5108,7 +5175,7 @@ SQLITE_API const void *sqlite3_column_decltype16(sqlite3_stmt*,int); | ||||
| ** other than [SQLITE_ROW] before any subsequent invocation of | ||||
| ** sqlite3_step().  Failure to reset the prepared statement using | ||||
| ** [sqlite3_reset()] would result in an [SQLITE_MISUSE] return from | ||||
| ** sqlite3_step().  But after [version 3.6.23.1] ([dateof:3.6.23.1], | ||||
| ** sqlite3_step().  But after [version 3.6.23.1] ([dateof:3.6.23.1]), | ||||
| ** sqlite3_step() began | ||||
| ** calling [sqlite3_reset()] automatically in this circumstance rather | ||||
| ** than returning [SQLITE_MISUSE].  This is not considered a compatibility | ||||
| @@ -5539,8 +5606,8 @@ SQLITE_API int sqlite3_reset(sqlite3_stmt *pStmt); | ||||
| ** | ||||
| ** For best security, the [SQLITE_DIRECTONLY] flag is recommended for | ||||
| ** all application-defined SQL functions that do not need to be | ||||
| ** used inside of triggers, view, CHECK constraints, or other elements of | ||||
| ** the database schema.  This flags is especially recommended for SQL | ||||
| ** used inside of triggers, views, CHECK constraints, or other elements of | ||||
| ** the database schema.  This flag is especially recommended for SQL | ||||
| ** functions that have side effects or reveal internal application state. | ||||
| ** Without this flag, an attacker might be able to modify the schema of | ||||
| ** a database file to include invocations of the function with parameters | ||||
| @@ -5571,7 +5638,7 @@ SQLITE_API int sqlite3_reset(sqlite3_stmt *pStmt); | ||||
| ** [user-defined window functions|available here]. | ||||
| ** | ||||
| ** ^(If the final parameter to sqlite3_create_function_v2() or | ||||
| ** sqlite3_create_window_function() is not NULL, then it is destructor for | ||||
| ** sqlite3_create_window_function() is not NULL, then it is the destructor for | ||||
| ** the application data pointer. The destructor is invoked when the function | ||||
| ** is deleted, either by being overloaded or when the database connection | ||||
| ** closes.)^ ^The destructor is also invoked if the call to | ||||
| @@ -5971,7 +6038,7 @@ SQLITE_API unsigned int sqlite3_value_subtype(sqlite3_value*); | ||||
| ** METHOD: sqlite3_value | ||||
| ** | ||||
| ** ^The sqlite3_value_dup(V) interface makes a copy of the [sqlite3_value] | ||||
| ** object D and returns a pointer to that copy.  ^The [sqlite3_value] returned | ||||
| ** object V and returns a pointer to that copy.  ^The [sqlite3_value] returned | ||||
| ** is a [protected sqlite3_value] object even if the input is not. | ||||
| ** ^The sqlite3_value_dup(V) interface returns NULL if V is NULL or if a | ||||
| ** memory allocation fails. ^If V is a [pointer value], then the result | ||||
| @@ -6009,7 +6076,7 @@ SQLITE_API void sqlite3_value_free(sqlite3_value*); | ||||
| ** allocation error occurs. | ||||
| ** | ||||
| ** ^(The amount of space allocated by sqlite3_aggregate_context(C,N) is | ||||
| ** determined by the N parameter on first successful call.  Changing the | ||||
| ** determined by the N parameter on the first successful call.  Changing the | ||||
| ** value of N in any subsequent call to sqlite3_aggregate_context() within | ||||
| ** the same aggregate function instance will not resize the memory | ||||
| ** allocation.)^  Within the xFinal callback, it is customary to set | ||||
| @@ -6171,7 +6238,7 @@ SQLITE_API void sqlite3_set_auxdata(sqlite3_context*, int N, void*, void (*)(voi | ||||
| ** | ||||
| ** Security Warning:  These interfaces should not be exposed in scripting | ||||
| ** languages or in other circumstances where it might be possible for an | ||||
| ** an attacker to invoke them.  Any agent that can invoke these interfaces | ||||
| ** attacker to invoke them.  Any agent that can invoke these interfaces | ||||
| ** can probably also take control of the process. | ||||
| ** | ||||
| ** Database connection client data is only available for SQLite | ||||
| @@ -6285,7 +6352,7 @@ typedef void (*sqlite3_destructor_type)(void*); | ||||
| ** pointed to by the 2nd parameter are taken as the application-defined | ||||
| ** function result.  If the 3rd parameter is non-negative, then it | ||||
| ** must be the byte offset into the string where the NUL terminator would | ||||
| ** appear if the string where NUL terminated.  If any NUL characters occur | ||||
| ** appear if the string were NUL terminated.  If any NUL characters occur | ||||
| ** in the string at a byte offset that is less than the value of the 3rd | ||||
| ** parameter, then the resulting string will contain embedded NULs and the | ||||
| ** result of expressions operating on strings with embedded NULs is undefined. | ||||
| @@ -6343,7 +6410,7 @@ typedef void (*sqlite3_destructor_type)(void*); | ||||
| ** string and preferably a string literal. The sqlite3_result_pointer() | ||||
| ** routine is part of the [pointer passing interface] added for SQLite 3.20.0. | ||||
| ** | ||||
| ** If these routines are called from within the different thread | ||||
| ** If these routines are called from within a different thread | ||||
| ** than the one containing the application-defined function that received | ||||
| ** the [sqlite3_context] pointer, the results are undefined. | ||||
| */ | ||||
| @@ -6749,7 +6816,7 @@ SQLITE_API sqlite3 *sqlite3_db_handle(sqlite3_stmt*); | ||||
| ** METHOD: sqlite3 | ||||
| ** | ||||
| ** ^The sqlite3_db_name(D,N) interface returns a pointer to the schema name | ||||
| ** for the N-th database on database connection D, or a NULL pointer of N is | ||||
| ** for the N-th database on database connection D, or a NULL pointer if N is | ||||
| ** out of range.  An N value of 0 means the main database file.  An N of 1 is | ||||
| ** the "temp" schema.  Larger values of N correspond to various ATTACH-ed | ||||
| ** databases. | ||||
| @@ -6844,7 +6911,7 @@ SQLITE_API int sqlite3_txn_state(sqlite3*,const char *zSchema); | ||||
| ** <dd>The SQLITE_TXN_READ state means that the database is currently | ||||
| ** in a read transaction.  Content has been read from the database file | ||||
| ** but nothing in the database file has changed.  The transaction state | ||||
| ** will advanced to SQLITE_TXN_WRITE if any changes occur and there are | ||||
| ** will be advanced to SQLITE_TXN_WRITE if any changes occur and there are | ||||
| ** no other conflicting concurrent write transactions.  The transaction | ||||
| ** state will revert to SQLITE_TXN_NONE following a [ROLLBACK] or | ||||
| ** [COMMIT].</dd> | ||||
| @@ -6853,7 +6920,7 @@ SQLITE_API int sqlite3_txn_state(sqlite3*,const char *zSchema); | ||||
| ** <dd>The SQLITE_TXN_WRITE state means that the database is currently | ||||
| ** in a write transaction.  Content has been written to the database file | ||||
| ** but has not yet committed.  The transaction state will change to | ||||
| ** to SQLITE_TXN_NONE at the next [ROLLBACK] or [COMMIT].</dd> | ||||
| ** SQLITE_TXN_NONE at the next [ROLLBACK] or [COMMIT].</dd> | ||||
| */ | ||||
| #define SQLITE_TXN_NONE  0 | ||||
| #define SQLITE_TXN_READ  1 | ||||
| @@ -7004,6 +7071,8 @@ SQLITE_API int sqlite3_autovacuum_pages( | ||||
| ** | ||||
| ** ^The second argument is a pointer to the function to invoke when a | ||||
| ** row is updated, inserted or deleted in a rowid table. | ||||
| ** ^The update hook is disabled by invoking sqlite3_update_hook() | ||||
| ** with a NULL pointer as the second parameter. | ||||
| ** ^The first argument to the callback is a copy of the third argument | ||||
| ** to sqlite3_update_hook(). | ||||
| ** ^The second callback argument is one of [SQLITE_INSERT], [SQLITE_DELETE], | ||||
| @@ -7132,7 +7201,7 @@ SQLITE_API int sqlite3_db_release_memory(sqlite3*); | ||||
| ** CAPI3REF: Impose A Limit On Heap Size | ||||
| ** | ||||
| ** These interfaces impose limits on the amount of heap memory that will be | ||||
| ** by all database connections within a single process. | ||||
| ** used by all database connections within a single process. | ||||
| ** | ||||
| ** ^The sqlite3_soft_heap_limit64() interface sets and/or queries the | ||||
| ** soft limit on the amount of heap memory that may be allocated by SQLite. | ||||
| @@ -7190,7 +7259,7 @@ SQLITE_API int sqlite3_db_release_memory(sqlite3*); | ||||
| ** </ul>)^ | ||||
| ** | ||||
| ** The circumstances under which SQLite will enforce the heap limits may | ||||
| ** changes in future releases of SQLite. | ||||
| ** change in future releases of SQLite. | ||||
| */ | ||||
| SQLITE_API sqlite3_int64 sqlite3_soft_heap_limit64(sqlite3_int64 N); | ||||
| SQLITE_API sqlite3_int64 sqlite3_hard_heap_limit64(sqlite3_int64 N); | ||||
| @@ -7305,8 +7374,8 @@ SQLITE_API int sqlite3_table_column_metadata( | ||||
| ** ^The entry point is zProc. | ||||
| ** ^(zProc may be 0, in which case SQLite will try to come up with an | ||||
| ** entry point name on its own.  It first tries "sqlite3_extension_init". | ||||
| ** If that does not work, it constructs a name "sqlite3_X_init" where the | ||||
| ** X is consists of the lower-case equivalent of all ASCII alphabetic | ||||
| ** If that does not work, it constructs a name "sqlite3_X_init" where | ||||
| ** X consists of the lower-case equivalent of all ASCII alphabetic | ||||
| ** characters in the filename from the last "/" to the first following | ||||
| ** "." and omitting any initial "lib".)^ | ||||
| ** ^The sqlite3_load_extension() interface returns | ||||
| @@ -7377,7 +7446,7 @@ SQLITE_API int sqlite3_enable_load_extension(sqlite3 *db, int onoff); | ||||
| ** ^(Even though the function prototype shows that xEntryPoint() takes | ||||
| ** no arguments and returns void, SQLite invokes xEntryPoint() with three | ||||
| ** arguments and expects an integer result as if the signature of the | ||||
| ** entry point where as follows: | ||||
| ** entry point were as follows: | ||||
| ** | ||||
| ** <blockquote><pre> | ||||
| **    int xEntryPoint( | ||||
| @@ -7541,7 +7610,7 @@ struct sqlite3_module { | ||||
| ** virtual table and might not be checked again by the byte code.)^ ^(The | ||||
| ** aConstraintUsage[].omit flag is an optimization hint. When the omit flag | ||||
| ** is left in its default setting of false, the constraint will always be | ||||
| ** checked separately in byte code.  If the omit flag is change to true, then | ||||
| ** checked separately in byte code.  If the omit flag is changed to true, then | ||||
| ** the constraint may or may not be checked in byte code.  In other words, | ||||
| ** when the omit flag is true there is no guarantee that the constraint will | ||||
| ** not be checked again using byte code.)^ | ||||
| @@ -7567,7 +7636,7 @@ struct sqlite3_module { | ||||
| ** The xBestIndex method may optionally populate the idxFlags field with a | ||||
| ** mask of SQLITE_INDEX_SCAN_* flags. One such flag is | ||||
| ** [SQLITE_INDEX_SCAN_HEX], which if set causes the [EXPLAIN QUERY PLAN] | ||||
| ** output to show the idxNum has hex instead of as decimal.  Another flag is | ||||
| ** output to show the idxNum as hex instead of as decimal.  Another flag is | ||||
| ** SQLITE_INDEX_SCAN_UNIQUE, which if set indicates that the query plan will | ||||
| ** return at most one row. | ||||
| ** | ||||
| @@ -7708,7 +7777,7 @@ struct sqlite3_index_info { | ||||
| ** the implementation of the [virtual table module].   ^The fourth | ||||
| ** parameter is an arbitrary client data pointer that is passed through | ||||
| ** into the [xCreate] and [xConnect] methods of the virtual table module | ||||
| ** when a new virtual table is be being created or reinitialized. | ||||
| ** when a new virtual table is being created or reinitialized. | ||||
| ** | ||||
| ** ^The sqlite3_create_module_v2() interface has a fifth parameter which | ||||
| ** is a pointer to a destructor for the pClientData.  ^SQLite will | ||||
| @@ -7873,7 +7942,7 @@ typedef struct sqlite3_blob sqlite3_blob; | ||||
| ** in *ppBlob. Otherwise an [error code] is returned and, unless the error | ||||
| ** code is SQLITE_MISUSE, *ppBlob is set to NULL.)^ ^This means that, provided | ||||
| ** the API is not misused, it is always safe to call [sqlite3_blob_close()] | ||||
| ** on *ppBlob after this function it returns. | ||||
| ** on *ppBlob after this function returns. | ||||
| ** | ||||
| ** This function fails with SQLITE_ERROR if any of the following are true: | ||||
| ** <ul> | ||||
| @@ -7993,7 +8062,7 @@ SQLITE_API int sqlite3_blob_close(sqlite3_blob *); | ||||
| ** | ||||
| ** ^Returns the size in bytes of the BLOB accessible via the | ||||
| ** successfully opened [BLOB handle] in its only argument.  ^The | ||||
| ** incremental blob I/O routines can only read or overwriting existing | ||||
| ** incremental blob I/O routines can only read or overwrite existing | ||||
| ** blob content; they cannot change the size of a blob. | ||||
| ** | ||||
| ** This routine only works on a [BLOB handle] which has been created | ||||
| @@ -8143,7 +8212,7 @@ SQLITE_API int sqlite3_vfs_unregister(sqlite3_vfs*); | ||||
| ** ^The sqlite3_mutex_alloc() routine allocates a new | ||||
| ** mutex and returns a pointer to it. ^The sqlite3_mutex_alloc() | ||||
| ** routine returns NULL if it is unable to allocate the requested | ||||
| ** mutex.  The argument to sqlite3_mutex_alloc() must one of these | ||||
| ** mutex.  The argument to sqlite3_mutex_alloc() must be one of these | ||||
| ** integer constants: | ||||
| ** | ||||
| ** <ul> | ||||
| @@ -8376,7 +8445,7 @@ SQLITE_API int sqlite3_mutex_notheld(sqlite3_mutex*); | ||||
| ** CAPI3REF: Retrieve the mutex for a database connection | ||||
| ** METHOD: sqlite3 | ||||
| ** | ||||
| ** ^This interface returns a pointer the [sqlite3_mutex] object that | ||||
| ** ^This interface returns a pointer to the [sqlite3_mutex] object that | ||||
| ** serializes access to the [database connection] given in the argument | ||||
| ** when the [threading mode] is Serialized. | ||||
| ** ^If the [threading mode] is Single-thread or Multi-thread then this | ||||
| @@ -8499,7 +8568,7 @@ SQLITE_API int sqlite3_test_control(int op, ...); | ||||
| ** CAPI3REF: SQL Keyword Checking | ||||
| ** | ||||
| ** These routines provide access to the set of SQL language keywords | ||||
| ** recognized by SQLite.  Applications can uses these routines to determine | ||||
| ** recognized by SQLite.  Applications can use these routines to determine | ||||
| ** whether or not a specific identifier needs to be escaped (for example, | ||||
| ** by enclosing in double-quotes) so as not to confuse the parser. | ||||
| ** | ||||
| @@ -8667,7 +8736,7 @@ SQLITE_API void sqlite3_str_reset(sqlite3_str*); | ||||
| ** content of the dynamic string under construction in X.  The value | ||||
| ** returned by [sqlite3_str_value(X)] is managed by the sqlite3_str object X | ||||
| ** and might be freed or altered by any subsequent method on the same | ||||
| ** [sqlite3_str] object.  Applications must not used the pointer returned | ||||
| ** [sqlite3_str] object.  Applications must not use the pointer returned by | ||||
| ** [sqlite3_str_value(X)] after any subsequent method call on the same | ||||
| ** object.  ^Applications may change the content of the string returned | ||||
| ** by [sqlite3_str_value(X)] as long as they do not write into any bytes | ||||
| @@ -8753,7 +8822,7 @@ SQLITE_API int sqlite3_status64( | ||||
| ** allocation which could not be satisfied by the [SQLITE_CONFIG_PAGECACHE] | ||||
| ** buffer and where forced to overflow to [sqlite3_malloc()].  The | ||||
| ** returned value includes allocations that overflowed because they | ||||
| ** where too large (they were larger than the "sz" parameter to | ||||
| ** were too large (they were larger than the "sz" parameter to | ||||
| ** [SQLITE_CONFIG_PAGECACHE]) and allocations that overflowed because | ||||
| ** no space was left in the page cache.</dd>)^ | ||||
| ** | ||||
| @@ -8837,28 +8906,29 @@ SQLITE_API int sqlite3_db_status(sqlite3*, int op, int *pCur, int *pHiwtr, int r | ||||
| ** [[SQLITE_DBSTATUS_LOOKASIDE_HIT]] ^(<dt>SQLITE_DBSTATUS_LOOKASIDE_HIT</dt> | ||||
| ** <dd>This parameter returns the number of malloc attempts that were | ||||
| ** satisfied using lookaside memory. Only the high-water value is meaningful; | ||||
| ** the current value is always zero.)^ | ||||
| ** the current value is always zero.</dd>)^ | ||||
| ** | ||||
| ** [[SQLITE_DBSTATUS_LOOKASIDE_MISS_SIZE]] | ||||
| ** ^(<dt>SQLITE_DBSTATUS_LOOKASIDE_MISS_SIZE</dt> | ||||
| ** <dd>This parameter returns the number malloc attempts that might have | ||||
| ** <dd>This parameter returns the number of malloc attempts that might have | ||||
| ** been satisfied using lookaside memory but failed due to the amount of | ||||
| ** memory requested being larger than the lookaside slot size. | ||||
| ** Only the high-water value is meaningful; | ||||
| ** the current value is always zero.)^ | ||||
| ** the current value is always zero.</dd>)^ | ||||
| ** | ||||
| ** [[SQLITE_DBSTATUS_LOOKASIDE_MISS_FULL]] | ||||
| ** ^(<dt>SQLITE_DBSTATUS_LOOKASIDE_MISS_FULL</dt> | ||||
| ** <dd>This parameter returns the number malloc attempts that might have | ||||
| ** <dd>This parameter returns the number of malloc attempts that might have | ||||
| ** been satisfied using lookaside memory but failed due to all lookaside | ||||
| ** memory already being in use. | ||||
| ** Only the high-water value is meaningful; | ||||
| ** the current value is always zero.)^ | ||||
| ** the current value is always zero.</dd>)^ | ||||
| ** | ||||
| ** [[SQLITE_DBSTATUS_CACHE_USED]] ^(<dt>SQLITE_DBSTATUS_CACHE_USED</dt> | ||||
| ** <dd>This parameter returns the approximate number of bytes of heap | ||||
| ** memory used by all pager caches associated with the database connection.)^ | ||||
| ** ^The highwater mark associated with SQLITE_DBSTATUS_CACHE_USED is always 0. | ||||
| ** </dd> | ||||
| ** | ||||
| ** [[SQLITE_DBSTATUS_CACHE_USED_SHARED]] | ||||
| ** ^(<dt>SQLITE_DBSTATUS_CACHE_USED_SHARED</dt> | ||||
| @@ -8867,10 +8937,10 @@ SQLITE_API int sqlite3_db_status(sqlite3*, int op, int *pCur, int *pHiwtr, int r | ||||
| ** memory used by that pager cache is divided evenly between the attached | ||||
| ** connections.)^  In other words, if none of the pager caches associated | ||||
| ** with the database connection are shared, this request returns the same | ||||
| ** value as DBSTATUS_CACHE_USED. Or, if one or more or the pager caches are | ||||
| ** value as DBSTATUS_CACHE_USED. Or, if one or more of the pager caches are | ||||
| ** shared, the value returned by this call will be smaller than that returned | ||||
| ** by DBSTATUS_CACHE_USED. ^The highwater mark associated with | ||||
| ** SQLITE_DBSTATUS_CACHE_USED_SHARED is always 0. | ||||
| ** SQLITE_DBSTATUS_CACHE_USED_SHARED is always 0.</dd> | ||||
| ** | ||||
| ** [[SQLITE_DBSTATUS_SCHEMA_USED]] ^(<dt>SQLITE_DBSTATUS_SCHEMA_USED</dt> | ||||
| ** <dd>This parameter returns the approximate number of bytes of heap | ||||
| @@ -8880,6 +8950,7 @@ SQLITE_API int sqlite3_db_status(sqlite3*, int op, int *pCur, int *pHiwtr, int r | ||||
| ** schema memory is shared with other database connections due to | ||||
| ** [shared cache mode] being enabled. | ||||
| ** ^The highwater mark associated with SQLITE_DBSTATUS_SCHEMA_USED is always 0. | ||||
| ** </dd> | ||||
| ** | ||||
| ** [[SQLITE_DBSTATUS_STMT_USED]] ^(<dt>SQLITE_DBSTATUS_STMT_USED</dt> | ||||
| ** <dd>This parameter returns the approximate number of bytes of heap | ||||
| @@ -8916,7 +8987,7 @@ SQLITE_API int sqlite3_db_status(sqlite3*, int op, int *pCur, int *pHiwtr, int r | ||||
| ** been written to disk in the middle of a transaction due to the page | ||||
| ** cache overflowing. Transactions are more efficient if they are written | ||||
| ** to disk all at once. When pages spill mid-transaction, that introduces | ||||
| ** additional overhead. This parameter can be used help identify | ||||
| ** additional overhead. This parameter can be used to help identify | ||||
| ** inefficiencies that can be resolved by increasing the cache size. | ||||
| ** </dd> | ||||
| ** | ||||
| @@ -8987,13 +9058,13 @@ SQLITE_API int sqlite3_stmt_status(sqlite3_stmt*, int op,int resetFlg); | ||||
| ** [[SQLITE_STMTSTATUS_SORT]] <dt>SQLITE_STMTSTATUS_SORT</dt> | ||||
| ** <dd>^This is the number of sort operations that have occurred. | ||||
| ** A non-zero value in this counter may indicate an opportunity to | ||||
| ** improvement performance through careful use of indices.</dd> | ||||
| ** improve performance through careful use of indices.</dd> | ||||
| ** | ||||
| ** [[SQLITE_STMTSTATUS_AUTOINDEX]] <dt>SQLITE_STMTSTATUS_AUTOINDEX</dt> | ||||
| ** <dd>^This is the number of rows inserted into transient indices that | ||||
| ** were created automatically in order to help joins run faster. | ||||
| ** A non-zero value in this counter may indicate an opportunity to | ||||
| ** improvement performance by adding permanent indices that do not | ||||
| ** improve performance by adding permanent indices that do not | ||||
| ** need to be reinitialized each time the statement is run.</dd> | ||||
| ** | ||||
| ** [[SQLITE_STMTSTATUS_VM_STEP]] <dt>SQLITE_STMTSTATUS_VM_STEP</dt> | ||||
| @@ -9002,19 +9073,19 @@ SQLITE_API int sqlite3_stmt_status(sqlite3_stmt*, int op,int resetFlg); | ||||
| ** to 2147483647.  The number of virtual machine operations can be | ||||
| ** used as a proxy for the total work done by the prepared statement. | ||||
| ** If the number of virtual machine operations exceeds 2147483647 | ||||
| ** then the value returned by this statement status code is undefined. | ||||
| ** then the value returned by this statement status code is undefined.</dd> | ||||
| ** | ||||
| ** [[SQLITE_STMTSTATUS_REPREPARE]] <dt>SQLITE_STMTSTATUS_REPREPARE</dt> | ||||
| ** <dd>^This is the number of times that the prepare statement has been | ||||
| ** automatically regenerated due to schema changes or changes to | ||||
| ** [bound parameters] that might affect the query plan. | ||||
| ** [bound parameters] that might affect the query plan.</dd> | ||||
| ** | ||||
| ** [[SQLITE_STMTSTATUS_RUN]] <dt>SQLITE_STMTSTATUS_RUN</dt> | ||||
| ** <dd>^This is the number of times that the prepared statement has | ||||
| ** been run.  A single "run" for the purposes of this counter is one | ||||
| ** or more calls to [sqlite3_step()] followed by a call to [sqlite3_reset()]. | ||||
| ** The counter is incremented on the first [sqlite3_step()] call of each | ||||
| ** cycle. | ||||
| ** cycle.</dd> | ||||
| ** | ||||
| ** [[SQLITE_STMTSTATUS_FILTER_MISS]] | ||||
| ** [[SQLITE_STMTSTATUS_FILTER HIT]] | ||||
| @@ -9024,7 +9095,7 @@ SQLITE_API int sqlite3_stmt_status(sqlite3_stmt*, int op,int resetFlg); | ||||
| ** step was bypassed because a Bloom filter returned not-found.  The | ||||
| ** corresponding SQLITE_STMTSTATUS_FILTER_MISS value is the number of | ||||
| ** times that the Bloom filter returned a find, and thus the join step | ||||
| ** had to be processed as normal. | ||||
| ** had to be processed as normal.</dd> | ||||
| ** | ||||
| ** [[SQLITE_STMTSTATUS_MEMUSED]] <dt>SQLITE_STMTSTATUS_MEMUSED</dt> | ||||
| ** <dd>^This is the approximate number of bytes of heap memory | ||||
| @@ -9129,9 +9200,9 @@ struct sqlite3_pcache_page { | ||||
| ** SQLite will typically create one cache instance for each open database file, | ||||
| ** though this is not guaranteed. ^The | ||||
| ** first parameter, szPage, is the size in bytes of the pages that must | ||||
| ** be allocated by the cache.  ^szPage will always a power of two.  ^The | ||||
| ** be allocated by the cache.  ^szPage will always be a power of two.  ^The | ||||
| ** second parameter szExtra is a number of bytes of extra storage | ||||
| ** associated with each page cache entry.  ^The szExtra parameter will | ||||
| ** associated with each page cache entry.  ^The szExtra parameter will be | ||||
| ** a number less than 250.  SQLite will use the | ||||
| ** extra szExtra bytes on each page to store metadata about the underlying | ||||
| ** database page on disk.  The value passed into szExtra depends | ||||
| @@ -9139,17 +9210,17 @@ struct sqlite3_pcache_page { | ||||
| ** ^The third argument to xCreate(), bPurgeable, is true if the cache being | ||||
| ** created will be used to cache database pages of a file stored on disk, or | ||||
| ** false if it is used for an in-memory database. The cache implementation | ||||
| ** does not have to do anything special based with the value of bPurgeable; | ||||
| ** does not have to do anything special based upon the value of bPurgeable; | ||||
| ** it is purely advisory.  ^On a cache where bPurgeable is false, SQLite will | ||||
| ** never invoke xUnpin() except to deliberately delete a page. | ||||
| ** ^In other words, calls to xUnpin() on a cache with bPurgeable set to | ||||
| ** false will always have the "discard" flag set to true. | ||||
| ** ^Hence, a cache created with bPurgeable false will | ||||
| ** ^Hence, a cache created with bPurgeable set to false will | ||||
| ** never contain any unpinned pages. | ||||
| ** | ||||
| ** [[the xCachesize() page cache method]] | ||||
| ** ^(The xCachesize() method may be called at any time by SQLite to set the | ||||
| ** suggested maximum cache-size (number of pages stored by) the cache | ||||
| ** suggested maximum cache-size (number of pages stored) for the cache | ||||
| ** instance passed as the first argument. This is the value configured using | ||||
| ** the SQLite "[PRAGMA cache_size]" command.)^  As with the bPurgeable | ||||
| ** parameter, the implementation is not required to do anything with this | ||||
| @@ -9176,12 +9247,12 @@ struct sqlite3_pcache_page { | ||||
| ** implementation must return a pointer to the page buffer with its content | ||||
| ** intact.  If the requested page is not already in the cache, then the | ||||
| ** cache implementation should use the value of the createFlag | ||||
| ** parameter to help it determined what action to take: | ||||
| ** parameter to help it determine what action to take: | ||||
| ** | ||||
| ** <table border=1 width=85% align=center> | ||||
| ** <tr><th> createFlag <th> Behavior when page is not already in cache | ||||
| ** <tr><td> 0 <td> Do not allocate a new page.  Return NULL. | ||||
| ** <tr><td> 1 <td> Allocate a new page if it easy and convenient to do so. | ||||
| ** <tr><td> 1 <td> Allocate a new page if it is easy and convenient to do so. | ||||
| **                 Otherwise return NULL. | ||||
| ** <tr><td> 2 <td> Make every effort to allocate a new page.  Only return | ||||
| **                 NULL if allocating a new page is effectively impossible. | ||||
| @@ -9198,7 +9269,7 @@ struct sqlite3_pcache_page { | ||||
| ** as its second argument.  If the third parameter, discard, is non-zero, | ||||
| ** then the page must be evicted from the cache. | ||||
| ** ^If the discard parameter is | ||||
| ** zero, then the page may be discarded or retained at the discretion of | ||||
| ** zero, then the page may be discarded or retained at the discretion of the | ||||
| ** page cache implementation. ^The page cache implementation | ||||
| ** may choose to evict unpinned pages at any time. | ||||
| ** | ||||
| @@ -9216,7 +9287,7 @@ struct sqlite3_pcache_page { | ||||
| ** When SQLite calls the xTruncate() method, the cache must discard all | ||||
| ** existing cache entries with page numbers (keys) greater than or equal | ||||
| ** to the value of the iLimit parameter passed to xTruncate(). If any | ||||
| ** of these pages are pinned, they are implicitly unpinned, meaning that | ||||
| ** of these pages are pinned, they become implicitly unpinned, meaning that | ||||
| ** they can be safely discarded. | ||||
| ** | ||||
| ** [[the xDestroy() page cache method]] | ||||
| @@ -9396,7 +9467,7 @@ typedef struct sqlite3_backup sqlite3_backup; | ||||
| ** external process or via a database connection other than the one being | ||||
| ** used by the backup operation, then the backup will be automatically | ||||
| ** restarted by the next call to sqlite3_backup_step(). ^If the source | ||||
| ** database is modified by the using the same database connection as is used | ||||
| ** database is modified by using the same database connection as is used | ||||
| ** by the backup operation, then the backup database is automatically | ||||
| ** updated at the same time. | ||||
| ** | ||||
| @@ -9413,7 +9484,7 @@ typedef struct sqlite3_backup sqlite3_backup; | ||||
| ** and may not be used following a call to sqlite3_backup_finish(). | ||||
| ** | ||||
| ** ^The value returned by sqlite3_backup_finish is [SQLITE_OK] if no | ||||
| ** sqlite3_backup_step() errors occurred, regardless or whether or not | ||||
| ** sqlite3_backup_step() errors occurred, regardless of whether or not | ||||
| ** sqlite3_backup_step() completed. | ||||
| ** ^If an out-of-memory condition or IO error occurred during any prior | ||||
| ** sqlite3_backup_step() call on the same [sqlite3_backup] object, then | ||||
| @@ -9515,7 +9586,7 @@ SQLITE_API int sqlite3_backup_pagecount(sqlite3_backup *p); | ||||
| ** application receives an SQLITE_LOCKED error, it may call the | ||||
| ** sqlite3_unlock_notify() method with the blocked connection handle as | ||||
| ** the first argument to register for a callback that will be invoked | ||||
| ** when the blocking connections current transaction is concluded. ^The | ||||
| ** when the blocking connection's current transaction is concluded. ^The | ||||
| ** callback is invoked from within the [sqlite3_step] or [sqlite3_close] | ||||
| ** call that concludes the blocking connection's transaction. | ||||
| ** | ||||
| @@ -9535,7 +9606,7 @@ SQLITE_API int sqlite3_backup_pagecount(sqlite3_backup *p); | ||||
| ** blocked connection already has a registered unlock-notify callback, | ||||
| ** then the new callback replaces the old.)^ ^If sqlite3_unlock_notify() is | ||||
| ** called with a NULL pointer as its second argument, then any existing | ||||
| ** unlock-notify callback is canceled. ^The blocked connections | ||||
| ** unlock-notify callback is canceled. ^The blocked connection's | ||||
| ** unlock-notify callback may also be canceled by closing the blocked | ||||
| ** connection using [sqlite3_close()]. | ||||
| ** | ||||
| @@ -9933,7 +10004,7 @@ SQLITE_API int sqlite3_vtab_config(sqlite3*, int op, ...); | ||||
| ** support constraints.  In this configuration (which is the default) if | ||||
| ** a call to the [xUpdate] method returns [SQLITE_CONSTRAINT], then the entire | ||||
| ** statement is rolled back as if [ON CONFLICT | OR ABORT] had been | ||||
| ** specified as part of the users SQL statement, regardless of the actual | ||||
| ** specified as part of the user's SQL statement, regardless of the actual | ||||
| ** ON CONFLICT mode specified. | ||||
| ** | ||||
| ** If X is non-zero, then the virtual table implementation guarantees | ||||
| @@ -9967,7 +10038,7 @@ SQLITE_API int sqlite3_vtab_config(sqlite3*, int op, ...); | ||||
| ** [[SQLITE_VTAB_INNOCUOUS]]<dt>SQLITE_VTAB_INNOCUOUS</dt> | ||||
| ** <dd>Calls of the form | ||||
| ** [sqlite3_vtab_config](db,SQLITE_VTAB_INNOCUOUS) from within the | ||||
| ** the [xConnect] or [xCreate] methods of a [virtual table] implementation | ||||
| ** [xConnect] or [xCreate] methods of a [virtual table] implementation | ||||
| ** identify that virtual table as being safe to use from within triggers | ||||
| ** and views.  Conceptually, the SQLITE_VTAB_INNOCUOUS tag means that the | ||||
| ** virtual table can do no serious harm even if it is controlled by a | ||||
| @@ -10135,7 +10206,7 @@ SQLITE_API const char *sqlite3_vtab_collation(sqlite3_index_info*,int); | ||||
| ** </table> | ||||
| ** | ||||
| ** ^For the purposes of comparing virtual table output values to see if the | ||||
| ** values are same value for sorting purposes, two NULL values are considered | ||||
| ** values are the same value for sorting purposes, two NULL values are considered | ||||
| ** to be the same.  In other words, the comparison operator is "IS" | ||||
| ** (or "IS NOT DISTINCT FROM") and not "==". | ||||
| ** | ||||
| @@ -10145,7 +10216,7 @@ SQLITE_API const char *sqlite3_vtab_collation(sqlite3_index_info*,int); | ||||
| ** | ||||
| ** ^A virtual table implementation is always free to return rows in any order | ||||
| ** it wants, as long as the "orderByConsumed" flag is not set.  ^When the | ||||
| ** the "orderByConsumed" flag is unset, the query planner will add extra | ||||
| ** "orderByConsumed" flag is unset, the query planner will add extra | ||||
| ** [bytecode] to ensure that the final results returned by the SQL query are | ||||
| ** ordered correctly.  The use of the "orderByConsumed" flag and the | ||||
| ** sqlite3_vtab_distinct() interface is merely an optimization.  ^Careful | ||||
| @@ -10242,7 +10313,7 @@ SQLITE_API int sqlite3_vtab_in(sqlite3_index_info*, int iCons, int bHandle); | ||||
| ** sqlite3_vtab_in_next(X,P) should be one of the parameters to the | ||||
| ** xFilter method which invokes these routines, and specifically | ||||
| ** a parameter that was previously selected for all-at-once IN constraint | ||||
| ** processing use the [sqlite3_vtab_in()] interface in the | ||||
| ** processing using the [sqlite3_vtab_in()] interface in the | ||||
| ** [xBestIndex|xBestIndex method].  ^(If the X parameter is not | ||||
| ** an xFilter argument that was selected for all-at-once IN constraint | ||||
| ** processing, then these routines return [SQLITE_ERROR].)^ | ||||
| @@ -10297,7 +10368,7 @@ SQLITE_API int sqlite3_vtab_in_next(sqlite3_value *pVal, sqlite3_value **ppOut); | ||||
| ** and only if *V is set to a value.  ^The sqlite3_vtab_rhs_value(P,J,V) | ||||
| ** inteface returns SQLITE_NOTFOUND if the right-hand side of the J-th | ||||
| ** constraint is not available.  ^The sqlite3_vtab_rhs_value() interface | ||||
| ** can return an result code other than SQLITE_OK or SQLITE_NOTFOUND if | ||||
| ** can return a result code other than SQLITE_OK or SQLITE_NOTFOUND if | ||||
| ** something goes wrong. | ||||
| ** | ||||
| ** The sqlite3_vtab_rhs_value() interface is usually only successful if | ||||
| @@ -10325,8 +10396,8 @@ SQLITE_API int sqlite3_vtab_rhs_value(sqlite3_index_info*, int, sqlite3_value ** | ||||
| ** KEYWORDS: {conflict resolution mode} | ||||
| ** | ||||
| ** These constants are returned by [sqlite3_vtab_on_conflict()] to | ||||
| ** inform a [virtual table] implementation what the [ON CONFLICT] mode | ||||
| ** is for the SQL statement being evaluated. | ||||
| ** inform a [virtual table] implementation of the [ON CONFLICT] mode | ||||
| ** for the SQL statement being evaluated. | ||||
| ** | ||||
| ** Note that the [SQLITE_IGNORE] constant is also used as a potential | ||||
| ** return value from the [sqlite3_set_authorizer()] callback and that | ||||
| @@ -10366,39 +10437,39 @@ SQLITE_API int sqlite3_vtab_rhs_value(sqlite3_index_info*, int, sqlite3_value ** | ||||
| ** [[SQLITE_SCANSTAT_EST]] <dt>SQLITE_SCANSTAT_EST</dt> | ||||
| ** <dd>^The "double" variable pointed to by the V parameter will be set to the | ||||
| ** query planner's estimate for the average number of rows output from each | ||||
| ** iteration of the X-th loop.  If the query planner's estimates was accurate, | ||||
| ** iteration of the X-th loop.  If the query planner's estimate was accurate, | ||||
| ** then this value will approximate the quotient NVISIT/NLOOP and the | ||||
| ** product of this value for all prior loops with the same SELECTID will | ||||
| ** be the NLOOP value for the current loop. | ||||
| ** be the NLOOP value for the current loop.</dd> | ||||
| ** | ||||
| ** [[SQLITE_SCANSTAT_NAME]] <dt>SQLITE_SCANSTAT_NAME</dt> | ||||
| ** <dd>^The "const char *" variable pointed to by the V parameter will be set | ||||
| ** to a zero-terminated UTF-8 string containing the name of the index or table | ||||
| ** used for the X-th loop. | ||||
| ** used for the X-th loop.</dd> | ||||
| ** | ||||
| ** [[SQLITE_SCANSTAT_EXPLAIN]] <dt>SQLITE_SCANSTAT_EXPLAIN</dt> | ||||
| ** <dd>^The "const char *" variable pointed to by the V parameter will be set | ||||
| ** to a zero-terminated UTF-8 string containing the [EXPLAIN QUERY PLAN] | ||||
| ** description for the X-th loop. | ||||
| ** description for the X-th loop.</dd> | ||||
| ** | ||||
| ** [[SQLITE_SCANSTAT_SELECTID]] <dt>SQLITE_SCANSTAT_SELECTID</dt> | ||||
| ** <dd>^The "int" variable pointed to by the V parameter will be set to the | ||||
| ** id for the X-th query plan element. The id value is unique within the | ||||
| ** statement. The select-id is the same value as is output in the first | ||||
| ** column of an [EXPLAIN QUERY PLAN] query. | ||||
| ** column of an [EXPLAIN QUERY PLAN] query.</dd> | ||||
| ** | ||||
| ** [[SQLITE_SCANSTAT_PARENTID]] <dt>SQLITE_SCANSTAT_PARENTID</dt> | ||||
| ** <dd>The "int" variable pointed to by the V parameter will be set to the | ||||
| ** the id of the parent of the current query element, if applicable, or | ||||
| ** id of the parent of the current query element, if applicable, or | ||||
| ** to zero if the query element has no parent. This is the same value as | ||||
| ** returned in the second column of an [EXPLAIN QUERY PLAN] query. | ||||
| ** returned in the second column of an [EXPLAIN QUERY PLAN] query.</dd> | ||||
| ** | ||||
| ** [[SQLITE_SCANSTAT_NCYCLE]] <dt>SQLITE_SCANSTAT_NCYCLE</dt> | ||||
| ** <dd>The sqlite3_int64 output value is set to the number of cycles, | ||||
| ** according to the processor time-stamp counter, that elapsed while the | ||||
| ** query element was being processed. This value is not available for | ||||
| ** all query elements - if it is unavailable the output variable is | ||||
| ** set to -1. | ||||
| ** set to -1.</dd> | ||||
| ** </dl> | ||||
| */ | ||||
| #define SQLITE_SCANSTAT_NLOOP    0 | ||||
| @@ -10439,8 +10510,8 @@ SQLITE_API int sqlite3_vtab_rhs_value(sqlite3_index_info*, int, sqlite3_value ** | ||||
| ** sqlite3_stmt_scanstatus_v2() with a zeroed flags parameter. | ||||
| ** | ||||
| ** Parameter "idx" identifies the specific query element to retrieve statistics | ||||
| ** for. Query elements are numbered starting from zero. A value of -1 may be | ||||
| ** to query for statistics regarding the entire query. ^If idx is out of range | ||||
| ** for. Query elements are numbered starting from zero. A value of -1 may | ||||
| ** retrieve statistics for the entire query. ^If idx is out of range | ||||
| ** - less than -1 or greater than or equal to the total number of query | ||||
| ** elements used to implement the statement - a non-zero value is returned and | ||||
| ** the variable that pOut points to is unchanged. | ||||
| @@ -10483,7 +10554,7 @@ SQLITE_API void sqlite3_stmt_scanstatus_reset(sqlite3_stmt*); | ||||
| ** METHOD: sqlite3 | ||||
| ** | ||||
| ** ^If a write-transaction is open on [database connection] D when the | ||||
| ** [sqlite3_db_cacheflush(D)] interface invoked, any dirty | ||||
| ** [sqlite3_db_cacheflush(D)] interface is invoked, any dirty | ||||
| ** pages in the pager-cache that are not currently in use are written out | ||||
| ** to disk. A dirty page may be in use if a database cursor created by an | ||||
| ** active SQL statement is reading from it, or if it is page 1 of a database | ||||
| @@ -10597,8 +10668,8 @@ SQLITE_API int sqlite3_db_cacheflush(sqlite3*); | ||||
| ** triggers; and so forth. | ||||
| ** | ||||
| ** When the [sqlite3_blob_write()] API is used to update a blob column, | ||||
| ** the pre-update hook is invoked with SQLITE_DELETE. This is because the | ||||
| ** in this case the new values are not available. In this case, when a | ||||
| ** the pre-update hook is invoked with SQLITE_DELETE, because | ||||
| ** the new values are not yet available. In this case, when a | ||||
| ** callback made with op==SQLITE_DELETE is actually a write using the | ||||
| ** sqlite3_blob_write() API, the [sqlite3_preupdate_blobwrite()] returns | ||||
| ** the index of the column being written. In other cases, where the | ||||
| @@ -10851,7 +10922,7 @@ SQLITE_API SQLITE_EXPERIMENTAL int sqlite3_snapshot_recover(sqlite3 *db, const c | ||||
| ** For an ordinary on-disk database file, the serialization is just a | ||||
| ** copy of the disk file.  For an in-memory database or a "TEMP" database, | ||||
| ** the serialization is the same sequence of bytes which would be written | ||||
| ** to disk if that database where backed up to disk. | ||||
| ** to disk if that database were backed up to disk. | ||||
| ** | ||||
| ** The usual case is that sqlite3_serialize() copies the serialization of | ||||
| ** the database into memory obtained from [sqlite3_malloc64()] and returns | ||||
| @@ -10860,7 +10931,7 @@ SQLITE_API SQLITE_EXPERIMENTAL int sqlite3_snapshot_recover(sqlite3 *db, const c | ||||
| ** contains the SQLITE_SERIALIZE_NOCOPY bit, then no memory allocations | ||||
| ** are made, and the sqlite3_serialize() function will return a pointer | ||||
| ** to the contiguous memory representation of the database that SQLite | ||||
| ** is currently using for that database, or NULL if the no such contiguous | ||||
| ** is currently using for that database, or NULL if no such contiguous | ||||
| ** memory representation of the database exists.  A contiguous memory | ||||
| ** representation of the database will usually only exist if there has | ||||
| ** been a prior call to [sqlite3_deserialize(D,S,...)] with the same | ||||
| @@ -10931,7 +11002,7 @@ SQLITE_API unsigned char *sqlite3_serialize( | ||||
| ** database is currently in a read transaction or is involved in a backup | ||||
| ** operation. | ||||
| ** | ||||
| ** It is not possible to deserialized into the TEMP database.  If the | ||||
| ** It is not possible to deserialize into the TEMP database.  If the | ||||
| ** S argument to sqlite3_deserialize(D,S,P,N,M,F) is "temp" then the | ||||
| ** function returns SQLITE_ERROR. | ||||
| ** | ||||
| @@ -10953,7 +11024,7 @@ SQLITE_API int sqlite3_deserialize( | ||||
|   sqlite3 *db,            /* The database connection */ | ||||
|   const char *zSchema,    /* Which DB to reopen with the deserialization */ | ||||
|   unsigned char *pData,   /* The serialized database content */ | ||||
|   sqlite3_int64 szDb,     /* Number bytes in the deserialization */ | ||||
|   sqlite3_int64 szDb,     /* Number of bytes in the deserialization */ | ||||
|   sqlite3_int64 szBuf,    /* Total size of buffer pData[] */ | ||||
|   unsigned mFlags         /* Zero or more SQLITE_DESERIALIZE_* flags */ | ||||
| ); | ||||
| @@ -10961,7 +11032,7 @@ SQLITE_API int sqlite3_deserialize( | ||||
| /* | ||||
| ** CAPI3REF: Flags for sqlite3_deserialize() | ||||
| ** | ||||
| ** The following are allowed values for 6th argument (the F argument) to | ||||
| ** The following are allowed values for the 6th argument (the F argument) to | ||||
| ** the [sqlite3_deserialize(D,S,P,N,M,F)] interface. | ||||
| ** | ||||
| ** The SQLITE_DESERIALIZE_FREEONCLOSE means that the database serialization | ||||
| @@ -11486,9 +11557,10 @@ SQLITE_API void sqlite3session_table_filter( | ||||
| ** is inserted while a session object is enabled, then later deleted while | ||||
| ** the same session object is disabled, no INSERT record will appear in the | ||||
| ** changeset, even though the delete took place while the session was disabled. | ||||
| ** Or, if one field of a row is updated while a session is disabled, and | ||||
| ** another field of the same row is updated while the session is enabled, the | ||||
| ** resulting changeset will contain an UPDATE change that updates both fields. | ||||
| ** Or, if one field of a row is updated while a session is enabled, and | ||||
| ** then another field of the same row is updated while the session is disabled, | ||||
| ** the resulting changeset will contain an UPDATE change that updates both | ||||
| ** fields. | ||||
| */ | ||||
| SQLITE_API int sqlite3session_changeset( | ||||
|   sqlite3_session *pSession,      /* Session object */ | ||||
| @@ -11560,8 +11632,9 @@ SQLITE_API sqlite3_int64 sqlite3session_changeset_size(sqlite3_session *pSession | ||||
| ** database zFrom the contents of the two compatible tables would be | ||||
| ** identical. | ||||
| ** | ||||
| ** It an error if database zFrom does not exist or does not contain the | ||||
| ** required compatible table. | ||||
| ** Unless the call to this function is a no-op as described above, it is an | ||||
| ** error if database zFrom does not exist or does not contain the required | ||||
| ** compatible table. | ||||
| ** | ||||
| ** If the operation is successful, SQLITE_OK is returned. Otherwise, an SQLite | ||||
| ** error code. In this case, if argument pzErrMsg is not NULL, *pzErrMsg | ||||
| @@ -11696,7 +11769,7 @@ SQLITE_API int sqlite3changeset_start_v2( | ||||
| ** The following flags may passed via the 4th parameter to | ||||
| ** [sqlite3changeset_start_v2] and [sqlite3changeset_start_v2_strm]: | ||||
| ** | ||||
| ** <dt>SQLITE_CHANGESETAPPLY_INVERT <dd> | ||||
| ** <dt>SQLITE_CHANGESETSTART_INVERT <dd> | ||||
| **   Invert the changeset while iterating through it. This is equivalent to | ||||
| **   inverting a changeset using sqlite3changeset_invert() before applying it. | ||||
| **   It is an error to specify this flag with a patchset. | ||||
| @@ -12011,19 +12084,6 @@ SQLITE_API int sqlite3changeset_concat( | ||||
|   void **ppOut                    /* OUT: Buffer containing output changeset */ | ||||
| ); | ||||
|  | ||||
|  | ||||
| /* | ||||
| ** CAPI3REF: Upgrade the Schema of a Changeset/Patchset | ||||
| */ | ||||
| SQLITE_API int sqlite3changeset_upgrade( | ||||
|   sqlite3 *db, | ||||
|   const char *zDb, | ||||
|   int nIn, const void *pIn,       /* Input changeset */ | ||||
|   int *pnOut, void **ppOut        /* OUT: Inverse of input */ | ||||
| ); | ||||
|  | ||||
|  | ||||
|  | ||||
| /* | ||||
| ** CAPI3REF: Changegroup Handle | ||||
| ** | ||||
|   | ||||
							
								
								
									
										4
									
								
								deps/sqlite/sqlite3ext.h
									
									
									
									
										vendored
									
									
								
							
							
						
						| @@ -366,6 +366,8 @@ struct sqlite3_api_routines { | ||||
|   /* Version 3.44.0 and later */ | ||||
|   void *(*get_clientdata)(sqlite3*,const char*); | ||||
|   int (*set_clientdata)(sqlite3*, const char*, void*, void(*)(void*)); | ||||
|   /* Version 3.50.0 and later */ | ||||
|   int (*setlk_timeout)(sqlite3*,int,int); | ||||
| }; | ||||
|  | ||||
| /* | ||||
| @@ -699,6 +701,8 @@ typedef int (*sqlite3_loadext_entry)( | ||||
| /* Version 3.44.0 and later */ | ||||
| #define sqlite3_get_clientdata         sqlite3_api->get_clientdata | ||||
| #define sqlite3_set_clientdata         sqlite3_api->set_clientdata | ||||
| /* Version 3.50.0 and later */ | ||||
| #define sqlite3_setlk_timeout          sqlite3_api->setlk_timeout | ||||
| #endif /* !defined(SQLITE_CORE) && !defined(SQLITE_OMIT_LOAD_EXTENSION) */ | ||||
|  | ||||
| #if !defined(SQLITE_CORE) && !defined(SQLITE_OMIT_LOAD_EXTENSION) | ||||
|   | ||||
							
								
								
									
										40
									
								
								docs/connecting_manyverse.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,40 @@ | ||||
| # Connecting with Manyverse | ||||
|  | ||||
| Communication with [Manyverse](https://www.manyver.se/) should Just Work (tm). | ||||
|  | ||||
| This document is intended as a cheat sheet for the instances where it doesn't. | ||||
| If your experience differs, please share so we can make things better. | ||||
|  | ||||
| ## Connecting Manyverse to the tildefriends.net room | ||||
|  | ||||
| Open the `Connections` tab. This is from the desktop app, but mobile is similar. | ||||
|  | ||||
|  | ||||
|  | ||||
| Open the `Connections Panel`. | ||||
|  | ||||
|  | ||||
|  | ||||
| Use the `Add Connection` button at the bottom right to open the dialog to enter | ||||
| a connections string to add a new connection. | ||||
|  | ||||
|  | ||||
|  | ||||
| Copy the tildefriends.net room code from https://www.tildefriends.net/~cory/room/. | ||||
|  | ||||
|  | ||||
|  | ||||
| Paste. | ||||
|  | ||||
| On mobile especially, make sure the full text is pasted without modification. | ||||
|  | ||||
|  | ||||
|  | ||||
| Click `Done`, and you should be connected successfully. tildefriends.net is | ||||
| all things: a room, a pub, and a client, so you should be able to start replicating | ||||
| immediately as well as find other similarly connected people with whom to establish | ||||
| further connections. | ||||
|  | ||||
| When logged into tildefriends.net, active connections it sees can be found on | ||||
| the `Connections` tab: https://www.tildefriends.net/~core/ssb/#connections, | ||||
| which may indicate errors if you find yourself disconnecting. | ||||
							
								
								
									
										
											BIN
										
									
								
								docs/images/manyverse_code.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 43 KiB | 
							
								
								
									
										
											BIN
										
									
								
								docs/images/manyverse_connections_panel.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 27 KiB | 
							
								
								
									
										
											BIN
										
									
								
								docs/images/manyverse_connections_tab.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 48 KiB | 
							
								
								
									
										
											BIN
										
									
								
								docs/images/manyverse_paste_invite_code.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 25 KiB | 
							
								
								
									
										
											BIN
										
									
								
								docs/images/tildefriends_room_app.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 136 KiB | 
| @@ -14,7 +14,7 @@ | ||||
| - upload to Apple with dist-ios on macos | ||||
| - nix | ||||
|   - june and december: update release version | ||||
|   - run `nix flake update` | ||||
|   - run `nix --extra-experimental-features nix-command --extra-experimental-features flakes flake update` | ||||
|   - comment out the hash in default.nix | ||||
|   - update the version | ||||
|   - run `nix-build` | ||||
|   | ||||
							
								
								
									
										287
									
								
								docs/usage.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,287 @@ | ||||
| # CLI Usage | ||||
|  | ||||
| ## tildefriends -h | ||||
|  | ||||
| ``` | ||||
| Usage: out/debug/tildefriends command [command-options] | ||||
| commands: | ||||
|   run - Run tildefriends (default). | ||||
|   sandbox - Run a sandboxed tildefriends sandbox process (used internally). | ||||
|   import - Import apps from file to the database. | ||||
|   export - Export apps from the database to file. | ||||
|   publish - Append a message to a feed. | ||||
|   private - Append a private post message to a feed. | ||||
|   create_invite - Create an invite. | ||||
|   get_sequence - Get the last sequence number for a feed. | ||||
|   get_identity - Get the server account identity. | ||||
|   get_profile - Get profile information for the given identity. | ||||
|   get_contacts - Get information about followed, blocked, and friend identities. | ||||
|   has_blob - Check whether a blob is in the blob store. | ||||
|   get_blob - Read a file from the blob store. | ||||
|   store_blob - Write a file to the blob store. | ||||
|   verify - Verify a feed. | ||||
|   test - Test SSB. | ||||
| ``` | ||||
|  | ||||
| ## tildefriends run -h | ||||
|  | ||||
| ``` | ||||
|  | ||||
| Usage: out/debug/tildefriends run [options] | ||||
|  | ||||
| Run tildefriends (default). | ||||
|  | ||||
| options: | ||||
|   -s, --script script        Script to run (default: core/core.js). | ||||
|   -d, --db-path path         SQLite database path (default: /home/cory/.local/share/tildefriends/db.sqlite). | ||||
|   -k, --ssb-network-key key  SSB network key to use. | ||||
|   -n, --count count          Number of instances to run. | ||||
|   -a, --args args            Arguments of the format key=value,foo=bar,verbose=true (note: these are persisted to the database). | ||||
|                                  code_of_conduct (default: ""): Code of conduct presented at sign-in. | ||||
|                                  ssb_port (default: 8008): Port on which to listen for SSB secure handshake connections. | ||||
|                                  http_local_only (default: false): Whether to bind http(s) to the loopback address.  Otherwise any. | ||||
|                                  http_port (default: 12345): Port on which to listen for HTTP connections. | ||||
|                                  out_http_port_file (default: ""): File to which to write bound HTTP port. | ||||
|                                  blob_fetch_age_seconds (default: -1): Only blobs mentioned more recently than this age will be automatically fetched. | ||||
|                                  blob_expire_age_seconds (default: -1): Blobs older than this will be automatically deleted. | ||||
|                                  http_redirect (default: ""): If connecting by HTTP and HTTPS is configured, Location header prefix (ie, "http://example.com") | ||||
|                                  index (default: "/~core/intro/"): Default path. | ||||
|                                  index_map (default: ""): Mappings from hostname to redirect path, one per line, as in: "www.tildefriends.net=/~core/index/" | ||||
|                                  peer_exchange (default: false): Enable discovery of, sharing of, and connecting to internet peer strangers, including announcing this instance. | ||||
|                                  replicator (default: true): Enable message and blob replication. | ||||
|                                  room (default: true): Enable peers to tunnel through this instance as a room. | ||||
|                                  room_name (default: "tilde friends tunnel"): Name of the room. | ||||
|                                  seeds_host (default: "seeds.tildefriends.net"): Hostname for seed connections. | ||||
|                                  account_registration (default: true): Allow registration of new accounts. | ||||
|                                  replication_hops (default: 2): Number of hops to replicate (1 = direct follows, 2 = follows of follows, etc.). | ||||
|                                  delete_stale_feeds (default: false): Periodically delete feeds that aren't visible from local accounts or related follows. | ||||
|                                  talk_to_strangers (default: true): Whether connections are accepted from accounts that aren't in the replication range or otherwise already known. | ||||
|                                  autologin (default: false): Whether mobile autologin is supported. | ||||
|                                  broadcast (default: true): Send network discovery broadcasts. | ||||
|                                  discovery (default: true): Receive network discovery broadcasts. | ||||
|                                  stay_connected (default: false): Whether to attempt to keep several peer connections open. | ||||
|   -o, --one-proc             Run everything in one process (unsafely!). | ||||
|   -z, --zip path             Zip archive from which to load files. | ||||
|   -v, --verbose              Log raw messages. | ||||
|   -h, --help                 Show this usage information. | ||||
| ``` | ||||
|  | ||||
| ## tildefriends sandbox -h | ||||
|  | ||||
| ``` | ||||
|  | ||||
| Usage: out/debug/tildefriends sandbox [options] | ||||
|  | ||||
| Run a sandboxed tildefriends sandbox process (used internally). | ||||
|  | ||||
| options: | ||||
|   -h, --help    Show this usage information. | ||||
|   -f, --fd      File descriptor with which to communicate with parent process. | ||||
| ``` | ||||
|  | ||||
| ## tildefriends import -h | ||||
|  | ||||
| ``` | ||||
|  | ||||
| Usage: out/debug/tildefriends import [options] [paths...] | ||||
|  | ||||
| Import apps from file to the database. | ||||
|  | ||||
| options: | ||||
|   -u, --user user          User into whose account apps will be imported (default: "import"). | ||||
|   -d, --db-path db_path    SQLite database path (default: /home/cory/.local/share/tildefriends/db.sqlite). | ||||
|   -h, --help               Show this usage information. | ||||
| ``` | ||||
|  | ||||
| ## tildefriends export -h | ||||
|  | ||||
| ``` | ||||
|  | ||||
| Usage: out/debug/tildefriends export [options] [paths...] | ||||
|  | ||||
| Export apps from the database to file. | ||||
|  | ||||
| options: | ||||
|   -u, --user user          User from whose account apps will be exported (default: "core"). | ||||
|   -d, --db-path db_path    SQLite database path (default: /home/cory/.local/share/tildefriends/db.sqlite). | ||||
|   -h, --help               Show this usage information. | ||||
|  | ||||
| paths                      Paths of apps to export (example: /~core/ssb /~user/app). | ||||
| ``` | ||||
|  | ||||
| ## tildefriends publish -h | ||||
|  | ||||
| ``` | ||||
|  | ||||
| Usage: out/debug/tildefriends publish [options] | ||||
|  | ||||
| Append a message to a feed. | ||||
|  | ||||
| options: | ||||
|   -u, --user user          User owning identity with which to publish. | ||||
|   -i, --id identity        Identity with which to publish message. | ||||
|   -d, --db-path db_path    SQLite database path (default: /home/cory/.local/share/tildefriends/db.sqlite). | ||||
|   -c, --content json       JSON content of message to publish. | ||||
|   -h, --help               Show this usage information. | ||||
| ``` | ||||
|  | ||||
| ## tildefriends private -h | ||||
|  | ||||
| ``` | ||||
|  | ||||
| Usage: out/debug/tildefriends private [options] | ||||
|  | ||||
| Append a private post message to a feed. | ||||
|  | ||||
| options: | ||||
|   -u, --user user              User owning identity with which to publish (optional). | ||||
|   -i, --id identity            Identity with which to publish message. | ||||
|   -r, --recipients recipients  Recipient identities. | ||||
|   -d, --db-path db_path        SQLite database path (default: /home/cory/.local/share/tildefriends/db.sqlite). | ||||
|   -t, --text text              Private post text. | ||||
|   -h, --help                   Show this usage information. | ||||
| ``` | ||||
|  | ||||
| ## tildefriends create_invite -h | ||||
|  | ||||
| ``` | ||||
|  | ||||
| Usage: out/debug/tildefriends create_invite [options] | ||||
|  | ||||
| Create an invite. | ||||
|  | ||||
| options: | ||||
|   -d, --db-path db_path    SQLite database path (default: /home/cory/.local/share/tildefriends/db.sqlite). | ||||
|   -i, --identity identity  Account from which to get latest sequence number. | ||||
|   -a, --address address    Address to which the recipient will connect. | ||||
|   -p, --port port          Port to which the recipient will connect. | ||||
|   -u, --use_count count    Number of times this invite may be used (default: 1). | ||||
|   -e, --expires seconds    How long this invite is valid in seconds (-1 for indefinitely, default: 1 hour). | ||||
|   -h, --help               Show this usage information. | ||||
| ``` | ||||
|  | ||||
| ## tildefriends get_sequence -h | ||||
|  | ||||
| ``` | ||||
|  | ||||
| Usage: out/debug/tildefriends get_sequence [options] | ||||
|  | ||||
| Get the last sequence number for a feed. | ||||
|  | ||||
| options: | ||||
|   -d, --db-path db_path    SQLite database path (default: /home/cory/.local/share/tildefriends/db.sqlite). | ||||
|   -i, --identity identity  Account from which to get latest sequence number. | ||||
|   -h, --help               Show this usage information. | ||||
| ``` | ||||
|  | ||||
| ## tildefriends get_identity -h | ||||
|  | ||||
| ``` | ||||
|  | ||||
| Usage: out/debug/tildefriends get_identity [options] | ||||
|  | ||||
| Get the server account identity. | ||||
|  | ||||
| options: | ||||
|   -d, --db-path db_path    SQLite database path (default: /home/cory/.local/share/tildefriends/db.sqlite). | ||||
|   -h, --help               Show this usage information. | ||||
| ``` | ||||
|  | ||||
| ## tildefriends get_profile -h | ||||
|  | ||||
| ``` | ||||
|  | ||||
| Usage: out/debug/tildefriends get_profile [options] | ||||
|  | ||||
| Get profile information for the given identity. | ||||
|  | ||||
| options: | ||||
|   -d, --db-path db_path    SQLite database path (default: /home/cory/.local/share/tildefriends/db.sqlite). | ||||
|   -i, --identity identity  Account for which to get profile information. | ||||
|   -h, --help               Show this usage information. | ||||
| ``` | ||||
|  | ||||
| ## tildefriends get_contacts -h | ||||
|  | ||||
| ``` | ||||
|  | ||||
| Usage: out/debug/tildefriends get_contacts [options] | ||||
|  | ||||
| Get information about followed, blocked, and friend identities. | ||||
|  | ||||
| options: | ||||
|   -d, --db-path db_path    SQLite database path (default: /home/cory/.local/share/tildefriends/db.sqlite). | ||||
|   -i, --identity identity  Account from which to get contact information. | ||||
|   -h, --help               Show this usage information. | ||||
| ``` | ||||
|  | ||||
| ## tildefriends has_blob -h | ||||
|  | ||||
| ``` | ||||
|  | ||||
| Usage: out/debug/tildefriends has_blob [options] | ||||
|  | ||||
| Check whether a blob is in the blob store. | ||||
|  | ||||
| options: | ||||
|   -d, --db-path db_path    SQLite database path (default: /home/cory/.local/share/tildefriends/db.sqlite). | ||||
|   -b, --blob_id blob_id    ID of blob to query. | ||||
|   -h, --help               Show this usage information. | ||||
| ``` | ||||
|  | ||||
| ## tildefriends get_blob -h | ||||
|  | ||||
| ``` | ||||
|  | ||||
| Usage: out/debug/tildefriends get_blob [options] | ||||
|  | ||||
| Read a file from the blob store. | ||||
|  | ||||
| options: | ||||
|   -d, --db-path db_path    SQLite database path (default: /home/cory/.local/share/tildefriends/db.sqlite). | ||||
|   -b, --blob blob_id       Blob identifier to retrieve. | ||||
|   -o, --output file_path   Location to write the retrieved blob. | ||||
|   -h, --help               Show this usage information. | ||||
| ``` | ||||
|  | ||||
| ## tildefriends store_blob -h | ||||
|  | ||||
| ``` | ||||
|  | ||||
| Usage: out/debug/tildefriends store_blob [options] | ||||
|  | ||||
| Write a file to the blob store. | ||||
|  | ||||
| options: | ||||
|   -d, --db-path db_path    SQLite database path (default: /home/cory/.local/share/tildefriends/db.sqlite). | ||||
|   -f, --file file_path     Path to file to add to the blob store. | ||||
|   -h, --help               Show this usage information. | ||||
| ``` | ||||
|  | ||||
| ## tildefriends verify -h | ||||
|  | ||||
| ``` | ||||
|  | ||||
| Usage: out/debug/tildefriends verify [options] | ||||
|  | ||||
| Verify a feed. | ||||
|  | ||||
| options: | ||||
|   -i, --identity identity  Identity to verify. | ||||
|   -s, --sequence sequence  Sequence number to debug. | ||||
|   -d, --db-path db_path    SQLite database path (default: /home/cory/.local/share/tildefriends/db.sqlite). | ||||
|   -h, --help               Show this usage information. | ||||
| ``` | ||||
|  | ||||
| ## tildefriends test -h | ||||
|  | ||||
| ``` | ||||
|  | ||||
| Usage: out/debug/tildefriends test [options] | ||||
|  | ||||
| Test SSB. | ||||
|  | ||||
| options: | ||||
|   -t, --tests tests      Comma-separated list of tests to run.  (default: all) | ||||
|   -h, --help             Show this usage information. | ||||
| ``` | ||||
							
								
								
									
										8
									
								
								flake.lock
									
									
									
										generated
									
									
									
								
							
							
						
						| @@ -20,16 +20,16 @@ | ||||
|     }, | ||||
|     "nixpkgs": { | ||||
|       "locked": { | ||||
|         "lastModified": 1739758141, | ||||
|         "narHash": "sha256-uq6A2L7o1/tR6VfmYhZWoVAwb3gTy7j4Jx30MIrH0rE=", | ||||
|         "lastModified": 1758589230, | ||||
|         "narHash": "sha256-zMTCFGe8aVGTEr2RqUi/QzC1nOIQ0N1HRsbqB4f646k=", | ||||
|         "owner": "NixOS", | ||||
|         "repo": "nixpkgs", | ||||
|         "rev": "c618e28f70257593de75a7044438efc1c1fc0791", | ||||
|         "rev": "d1d883129b193f0b495d75c148c2c3a7d95789a0", | ||||
|         "type": "github" | ||||
|       }, | ||||
|       "original": { | ||||
|         "owner": "NixOS", | ||||
|         "ref": "nixos-24.11", | ||||
|         "ref": "nixos-25.05", | ||||
|         "repo": "nixpkgs", | ||||
|         "type": "github" | ||||
|       } | ||||
|   | ||||
| @@ -2,7 +2,7 @@ | ||||
|   description = "Tilde Friends is a platform for making, running, and sharing web applications."; | ||||
|  | ||||
|   inputs = { | ||||
|     nixpkgs.url = "github:NixOS/nixpkgs/nixos-24.11"; | ||||
|     nixpkgs.url = "github:NixOS/nixpkgs/nixos-25.05"; | ||||
|     flake-utils.url = "github:numtide/flake-utils"; | ||||
|   }; | ||||
|  | ||||
|   | ||||
							
								
								
									
										12
									
								
								metadata/en-US/changelogs/35.txt
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,12 @@ | ||||
| * Faster loads. | ||||
| * Replication fixes. | ||||
| * Shutdown fixes. | ||||
| * Consolidated message actions into a % menu. | ||||
| * Fixed and tested handling of user permissions. | ||||
| * Add a very work in progress "web" app. | ||||
| * Updates: | ||||
|   * CodeMirror | ||||
|   * Lit 3.3.0 | ||||
|   * OpenSSL 3.5.0 | ||||
|   * c-ares 1.34.5 | ||||
|   * libbacktrace | ||||
							
								
								
									
										13
									
								
								metadata/en-US/changelogs/37.txt
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,13 @@ | ||||
| * Faster loads. | ||||
| * Minor UI tweaks. | ||||
| * Added an intro app as part of the initial flow for first-time users. | ||||
| * Fixed more shutdown issues. | ||||
| * Fixed a longstanding potential database issue. | ||||
| * Added a blob export command. | ||||
| * Refresh blob wants for blobs that are requested over the web. | ||||
| * Updates: | ||||
|   * CodeMirror | ||||
|   * QuickJS 2025-04-26 | ||||
|   * libuv 1.51.0 | ||||
|   * sqlite 3.49.2 | ||||
|   * w3.css 5.02 | ||||
							
								
								
									
										14
									
								
								metadata/en-US/changelogs/38.txt
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,14 @@ | ||||
| * Improve load times. | ||||
| * Fix the messages_refs table, and make it usable for hashtags. | ||||
| * Fix a circumstance where we would fail to call promise callbacks. | ||||
| * Fix the private messages tab. | ||||
| * Fix the room app. | ||||
| * Expose followed accounts in a user's profile. | ||||
| * Show connection status in the sidebar. | ||||
| * Simplify placeholder messages. | ||||
| * Only show "Mark as Read" when relevant. | ||||
| * Treat profile images more like post images. | ||||
| * Limit the WAL file size. | ||||
| * Updates: | ||||
|   * CodeMirror | ||||
|   * sqlite 3.50.1 | ||||
							
								
								
									
										2
									
								
								metadata/en-US/changelogs/39.txt
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,2 @@ | ||||
| * Updating Android SDK+target versions. | ||||
| * Minor UI improvements. | ||||
							
								
								
									
										14
									
								
								metadata/en-US/changelogs/40.txt
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,14 @@ | ||||
| * Added an option to stay connected to a handful of peers. | ||||
| * Load more messages at a time. | ||||
| * Fix a set of Android not responding errors. | ||||
| * Target Android 15 (API level 35) to meet new requirements. | ||||
| * Support JS-less webapps. | ||||
| * Fix unnecessary tunnel disconnects. | ||||
| * Many small user interface tweaks. | ||||
| * Update: | ||||
|   * CodeMirror | ||||
|   * OpenSSL 3.5.1 | ||||
|   * lit 3.3.1 | ||||
|   * picohttpparser | ||||
|   * speedscope 1.23.0 | ||||
|   * sqlite 3.50.4 | ||||
							
								
								
									
										9
									
								
								metadata/en-US/changelogs/42.txt
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,9 @@ | ||||
| * Private messages interface overhaul in progress. | ||||
| * Added a loading indicator. | ||||
| * Documented the core JavaScript. | ||||
| * Fixed @-completion. | ||||
| * Covered up launch on Android with the splash screen. | ||||
| * Update: | ||||
|   * CodeMirror | ||||
|   * OpenSSL 3.5.2 | ||||
|   * speedscope 1.23.1 | ||||
							
								
								
									
										8
									
								
								metadata/en-US/changelogs/43.txt
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,8 @@ | ||||
| * Fixed multiple issues with blob replication. | ||||
| * Fixed some link encoding issues. | ||||
| * Fixed some context menus being cut off. | ||||
| * Minor Android fixes. | ||||
| * Updates: | ||||
|   * CodeMirror | ||||
|   * OpenSSL 3.5.3 | ||||
|   * QuickJS 2025-09-13 | ||||