commit 17801e9ae3daa3439fa4ae0957e9210ec34692e7 Author: Philipp Date: Wed Jul 27 21:27:46 2022 +0200 First 'develop' help site diff --git a/develop/404.html b/develop/404.html new file mode 100644 index 0000000..e6bfc0e --- /dev/null +++ b/develop/404.html @@ -0,0 +1,3188 @@ + + + + + + + + + + + + + + + + + + + + Friendica documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+
+ +
+ + + + +
+ + +
+ +
+ + + + + + + + + +
+
+ + + +
+
+
+ + + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + +
+
+ +

404 - Not found

+ +
+ +
+
+ + + +
+ + + +
+
+
+
+ + + + + + + + \ No newline at end of file diff --git a/develop/admin/config/index.html b/develop/admin/config/index.html new file mode 100644 index 0000000..d93e69e --- /dev/null +++ b/develop/admin/config/index.html @@ -0,0 +1,3731 @@ + + + + + + + + + + + + + + + + + + + + + + Config Values - Friendica documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + Skip to content + + +
+
+ +
+ + + + +
+ + +
+ +
+ + + + + + + + + +
+
+ + + +
+
+
+ + + + + + +
+
+
+ + + + + + +
+
+ + + + + + + + + + + + + +

Config values that can only be set in config/local.config.php#

+

Friendica's configuration is done in two places: in PHP array configuration files and in the config database table. +Database config values overwrite the same file config values.

+

File configuration#

+

The configuration format for file configuration is an array returned from a PHP file. +This prevents your webserver from displaying your private configuration. It interprets the configuration files and displays nothing.

+

A typical configuration file looks like this:

+
<?php
+
+/*
+ * Comment block
+ */
+
+return [
+    'section1' => [
+        // Comment line
+        'key' => 'value',
+    ],
+    'section2' => [
+        'array' => ['value0', 'value1', 'value2'],
+    ],
+];
+
+

Configuration location#

+

The config directory holds key configuration files and can have different config files. +All of them have to end with .config.php and must not include -sample in their name.

+

Some examples of common known configuration files: +- local.config.php holds the current node custom configuration. +- addon.config.php is optional and holds the custom configuration for specific addons.

+

Addons can define their own default configuration values in addon/[addon]/config/[addon].config.php which is loaded when the addon is activated.

+

If needed, an alternative config path can be used by using the FRIENDICA_CONFIG_DIR environment variable (full path required!). +This is useful in case of hardening the system by separating configuration from program binaries.

+

Static Configuration location#

+

The static directory holds the codebase default configurations files. +They must not be changed by users, because they can get changed from release to release.

+

Currently, the following configurations are included: +- defaults.config.php holds the default values for all the configuration keys that can only be set in local.config.php. +- settings.config.php holds the default values for some configuration keys that are set through the admin settings page.

+

Migrating from .htconfig.php to config/local.config.php#

+

The legacy .htconfig.php configuration file is still supported, but is deprecated and will be removed in a subsequent Friendica release.

+

The migration is pretty straightforward: +If you had any addon-specific configuration in your .htconfig.php, just copy config/addon-sample.config.php to config/addon.config.php and move your configuration values. +Afterwards, copy config/local-sample.config.php to config/local.config.php, move the remaining configuration values to it according to the following conversion chart, then rename your .htconfig.php to check your node is working as expected before deleting it.

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
.htconfig.phpconfig/local.config.php
+$db_host = 'localhost';
+$db_user = 'mysqlusername';
+$db_pass = 'mysqlpassword';
+$db_data = 'mysqldatabasename';
+$a->config["system"]["db_charset"] = 'utf8mb4';
+
+'database' => [
+    'hostname' => 'localhost',
+    'username' => 'mysqlusername',
+    'password' => 'mysqlpassword',
+    'database' => 'database',
+    'charset' => 'utf8mb4',
+],
+
+$a->config["section"]["key"] = "value";
+
+'section' => [
+    'key' => 'value',
+],
+
+$a->config["section"]["key"] = array(
+    "value1",
+    "value2",
+    "value3"
+);
+
+'section' => [
+    'key' => ['value1', 'value2', 'value3'],
+],
+
+$a->config["key"] = "value";
+
+'config' => [
+    'key' => 'value',
+],
+
+$a->config['register_policy'] = REGISTER_CLOSED;
+
+'config' => [
+    'register_policy' => \Friendica\Module\Register::CLOSED,
+],
+
+$a->path = "value";
+
+'system' => [
+    'urlpath' => 'value',
+],
+
+$default_timezone = "value";
+
+'system' => [
+    'default_timezone' => 'value',
+],
+
+$pidfile = "value";
+
+'system' => [
+    'pidfile' => 'value',
+],
+
+$lang = "value";
+
+'system' => [
+    'language' => 'value',
+],
+
+ +

Migrating from config/local.ini.php to config/local.config.php#

+

The legacy config/local.ini.php configuration file is still supported, but is deprecated and will be removed in a subsequent Friendica release.

+

The migration is pretty straightforward: +If you had any addon-specific configuration in your config/addon.ini.php, just copy config/addon-sample.config.php to config/addon.config.php and move your configuration values. +Afterwards, copy config/local-sample.config.php to config/local.config.php, move the remaining configuration values to it according to the following conversion chart, then rename your config/local.ini.php file to check your node is working as expected before deleting it.

+ + + + + + + + + + + + + + + + + + + + + + + + + +
config/local.ini.phpconfig/local.config.php
+[database]
+hostname = localhost
+username = mysqlusername
+password = mysqlpassword
+database = mysqldatabasename
+charset = utf8mb4
+
+'database' => [
+    'hostname' => 'localhost',
+    'username' => 'mysqlusername',
+    'password' => 'mysqlpassword',
+    'database' => 'database',
+    'charset' => 'utf8mb4',
+],
+
+[section]
+key = value
+
+'section' => [
+    'key' => 'value',
+],
+
+[config]
+register_policty = REGISTER_CLOSED
+
+'config' => [
+    'register_policy' => \Friendica\Module\Register::CLOSED,
+],
+
+[section]
+key[] = value1
+key[] = value2
+key[] = value3
+
+'section' => [
+    'key' => ['value1', 'value2', 'value3'],
+],
+
+ +

Database Settings#

+

The configuration variables database.hostname, database.username, database.password, database.database and database.charset are holding your credentials for the database connection. +If you need to specify a port to access the database, you can do so by appending :portnumber to the database.hostname variable.

+
'database' => [
+    'hostname' => 'your.mysqlhost.com:123456',
+]
+
+

If all the following environment variables are set, Friendica will use them instead of the previously configured variables for the db:

+
MYSQL_HOST
+MYSQL_PORT
+MYSQL_USERNAME
+MYSQL_PASSWORD
+MYSQL_DATABASE
+
+

Config values that can only be set in config/local.config.php#

+

There are some config values that haven't found their way into the administration page. +This has several reasons. +Maybe they are part of a current development that isn't considered stable and will be added later in the administration page when it is considered safe. +Or it triggers something that isn't expected to be of public interest. +Or it is for testing purposes only.

+

Attention: Please be warned that you shouldn't use one of these values without the knowledge what it could trigger. +Especially don't do that with undocumented values.

+

These configurations keys and their default value are listed in static/defaults.config.php and should be overwritten in config/local.config.php.

+

Administrator Options#

+

Enabling the admin panel for an account, and thus making the account holder admin of the node, is done by setting the variable

+
'config' => [
+    'admin_email' => 'someone@example.com',
+]
+
+

Where you have to match the email address used for the account with the one you enter to the config/local.config.php file. +If more than one account should be able to access the admin panel, separate the email addresses with a comma.

+
'config' => [
+    'admin_email' => 'someone@example.com,someoneelse@example.com',
+]
+
+

If you want to have a more personalized closing line for the notification emails you can set a variable for the admin_name.

+
'config' => [
+    'admin_name' => 'Marvin',
+]
+
+ + +
+ +
+
+ + + +
+ + + +
+
+
+
+ + + + + + + + \ No newline at end of file diff --git a/develop/admin/faq/index.html b/develop/admin/faq/index.html new file mode 100644 index 0000000..4c43c80 --- /dev/null +++ b/develop/admin/faq/index.html @@ -0,0 +1,3392 @@ + + + + + + + + + + + + + + + + + + + + + + FAQ - Friendica documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + Skip to content + + +
+
+ +
+ + + + +
+ + +
+ +
+ + + + + + + + + +
+
+ + + +
+
+
+ + + + + + +
+
+
+ + + + + + +
+
+ + + + + + + + + + + + + +

Frequently Asked Questions (Admin) - FAQ#

+

Can I configure multiple domains with the same code instance?#

+

No, this function is no longer supported as of Friendica 3.3 onwards.

+

Where can I find the source code of friendica, addons and themes?#

+

You can find the main repository here. +There you will always find the current stable version of friendica.

+

Addons are listed at this page.

+

If you are searching for new themes, you can find them at github.com/bkil/friendica-themes

+

I've changed my email address now the admin panel is gone?#

+

Have a look into your config/local.config.php and fix your email address there.

+

Can there be more than one admin for a node?#

+

Yes. +You just have to list more than one email address in the config/local.config.php file. +The listed emails need to be separated by a comma.

+

The Database structure seems not to be updated. What can I do?#

+

Please have a look at the Admin panel under DB updates (/admin/dbsync/) and follow the link to check database structure. +This will start a background process to check if the structure is up to the current definition.

+

You can manually execute the structure update from the CLI in the base directory of your Friendica installation by running the following command:

+
bin/console dbstructure update
+
+

if there occur any errors, please contact the support forum.

+ + +
+ +
+
+ + + +
+ + + +
+
+
+
+ + + + + + + + \ No newline at end of file diff --git a/develop/admin/improve-performance/index.html b/develop/admin/improve-performance/index.html new file mode 100644 index 0000000..4cd73df --- /dev/null +++ b/develop/admin/improve-performance/index.html @@ -0,0 +1,3518 @@ + + + + + + + + + + + + + + + + + + + + + + Improve Performance - Friendica documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + Skip to content + + +
+
+ +
+ + + + +
+ + +
+ +
+ + + + + + + + + +
+
+ + + +
+
+
+ + + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + +
+
+ + + + + + + + + + + + + +

How to improve the performance of a Friendica site#

+

Feel free to ask in the Friendica support forum if you need some clarification about the following instructions or if you need help in any other way.

+

System configuration#

+

Please go to /admin/site/ on your system and change the following values:

+
Set "JPEG image quality" to 50.
+
+

This value reduces the data that is sent from the server to the client. 50 is a value that doesn't influence image quality too much.

+
Set "OStatus conversation completion interval" to "never".
+
+

If you have many OStatus contacts then completing of conversations can take some time. Since you will miss several comments in OStatus threads, you maybe should consider the option "At post arrival" instead.

+
Enable "Use MySQL full text engine"
+
+

When using MyISAM (default) or InnoDB on MariaDB 10 this speeds up search.

+

Addons#

+

Active the following addons:

+
rendertime
+
+

rendertime#

+

This addon doesn't speed up your system. +It helps to analyze your bottlenecks.

+

When enabled you see some values at the bottom of every page. +They show your performance problems.

+
Performance: Database: 0.244, Network: 0.002, Rendering: 0.044, Parser: 0.001, I/O: 0.021, Other: 0.237, Total: 0.548
+
+Database: This is the time for all database queries
+Network: Time that is needed to fetch content from external sites
+Rendering: Time for theme rendering
+Parser: The time that the BBCode parser needed to create the output
+I/O: Time for local file access
+Others: Everything else :)
+Total: The sum of all above values
+
+

Apache Webserver#

+

The following Apache modules are recommended:

+

Cache-Control#

+

This module tells the client to cache the content of static files so that they aren't fetched with every request. +Enable the module mod_expires by typing in a2enmod expires as root. +Please add the following lines to your site configuration in the "directory" context.

+
ExpiresActive on ExpiresDefault "access plus 1 week"
+
+

Also see the Apache 2.2 / 2.4 documentation.

+

Compress content#

+

This module compresses the traffic between the web server and the client. +Enable the module mod_deflate by typing in a2enmod deflate as root.

+

Also see the Apache 2.2 / 2.4 documentation.

+

PHP#

+

FCGI#

+

When using Apache think about using FCGI. +In a Debian-based distribution you will need to install the packages named php5-cgi and libapache2-mod-fcgid.

+

Please refer to external documentation for a more detailed explanation how to set up a system based upon FCGI.

+

Database#

+

There are scripts like tuning-primer.sh and mysqltuner.pl that analyze your database server and give hints on values that could be changed.

+

Please enable the slow query log. This helps to find performance problems.

+ + +
+ +
+
+ + + +
+ + + +
+
+
+
+ + + + + + + + \ No newline at end of file diff --git a/develop/admin/install-ejabberd/index.html b/develop/admin/install-ejabberd/index.html new file mode 100644 index 0000000..6df1b9c --- /dev/null +++ b/develop/admin/install-ejabberd/index.html @@ -0,0 +1,3381 @@ + + + + + + + + + + + + + + + + + + + + + + Install ejabberd - Friendica documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + Skip to content + + +
+
+ +
+ + + + +
+ + +
+ +
+ + + + + + + + + +
+
+ + + +
+
+
+ + + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + +
+
+ + + + + + + + + + + + + +

Install an ejabberd with synchronized credentials#

+

Ejabberd is a chat server that uses XMPP as messaging protocol that you can use with a large amount of clients. +In conjunction with the "xmpp" addon it can be used for a web based chat solution for your users.

+

Installation#

+
    +
  • +

    Change its owner to whichever user is running the server, i.e. ejabberd

    +
    $ chown ejabberd:ejabberd /path/to/friendica/bin/auth_ejabberd.php
    +
    +
  • +
  • +

    Change the access mode, so it is readable only to the user ejabberd and has exec

    +
    $ chmod 700 /path/to/friendica/bin/auth_ejabberd.php
    +
    +
  • +
  • +

    Edit your ejabberd.cfg file, comment out your auth_method and add:

    +
    {auth_method, external}.
    +{extauth_program, "/path/to/friendica/bin/auth_ejabberd.php"}.
    +
    +
  • +
  • +

    Disable the module "mod_register" and disable the registration:

    +
    {access, register, [{deny, all}]}.
    +
    +
  • +
  • +

    Enable BOSH:

    +
  • +
  • Enable the module "mod_http_bind"
  • +
  • +

    Edit this line:

    +
    {5280, ejabberd_http,    [captcha, http_poll, http_bind]}
    +
    +
  • +
  • +

    In your apache configuration for your site add this line:

    +
    ProxyPass /http-bind http://127.0.0.1:5280/http-bind retry=0
    +
    +
  • +
  • +

    Restart your ejabberd service, you should be able to log in with your friendica credentials

    +
  • +
+

Other hints#

+
    +
  • if a user has a space or a @ in the nickname, the user has to replace these characters:
  • +
  • " " (space) is replaced with "%20"
  • +
  • "@" is replaced with "(a)"
  • +
+ + +
+ +
+
+ + + +
+ + + +
+
+
+
+ + + + + + + + \ No newline at end of file diff --git a/develop/admin/install/index.html b/develop/admin/install/index.html new file mode 100644 index 0000000..a03004c --- /dev/null +++ b/develop/admin/install/index.html @@ -0,0 +1,4227 @@ + + + + + + + + + + + + + + + + + + + + + + Installation - Friendica documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + Skip to content + + +
+
+ +
+ + + + +
+ + +
+ +
+ + + + + + + + + +
+
+ + + +
+
+
+ + + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + +
+
+ + + + + + + + + + + + + +

Friendica Installation#

+

We've tried very hard to ensure that Friendica will run on commodity hosting platforms - such as those used to host WordPress blogs and Drupal websites. +We offer a manual and an automatic installation. +But be aware that Friendica is more than a simple web application.

+

It is a complex communications system which more closely resembles an email server than a web server. +For reliability and performance, messages are delivered in the background and are queued for later delivery when sites are down. +This kind of functionality requires a bit more of the host system than the typical blog.

+

Not every PHP/MySQL hosting provider will be able to support Friendica. +Many will.

+

But please review the requirements and confirm these with your hosting provider prior to installation.

+

Support#

+

If you encounter installation issues, please let us know via the helper or the developer forum or file an issue.

+

Please be as clear as you can about your operating environment and provide as much detail as possible about any error messages you may see, so that we can prevent it from happening in the future. +Due to the large variety of operating systems and PHP platforms in existence we may have only limited ability to debug your PHP installation or acquire any missing modules - but we will do our best to solve any general code issues.

+

Prerequisites#

+
    +
  • Choose a domain name or subdomain name for your server. Put some thought into this. While changing it after installation is supported, things still might break.
  • +
  • Setup HTTPS on your domain.
  • +
+

Requirements#

+
    +
  • Apache with mod-rewrite enabled and "Options All" so you can use a local .htaccess file
  • +
  • PHP 7.3+ (PHP8 is not fully supported yet)
  • +
  • PHP command line access with register_argc_argv set to true in the php.ini file
  • +
  • Curl, GD, GMP, PDO, mbstrings, MySQLi, hash, xml, zip and OpenSSL extensions
  • +
  • The POSIX module of PHP needs to be activated (e.g. RHEL, CentOS have disabled it)
  • +
  • Some form of email server or email gateway such that PHP mail() works. + If you cannot set up your own email server, you can use the phpmailer addon and use a remote SMTP server.
  • +
  • MySQL 5.6+ or an equivalent alternative for MySQL (MariaDB, Percona Server etc.)
  • +
  • ability to schedule jobs with cron (Linux/Mac) or Scheduled Tasks (Windows)
  • +
  • installation into a top-level domain or subdomain (without a directory/path component in the URL) is RECOMMENDED. Directory paths will not be as convenient to use and have not been thoroughly tested. This is REQUIRED if you wish to communicate with the Diaspora network.
  • +
+

If your hosting provider doesn't allow Unix shell access, you might have trouble getting everything to work.

+

For alternative server configurations (such as Nginx server and MariaDB database engine), refer to the Friendica wiki.

+

Optional#

+
    +
  • PHP ImageMagick extension (php-imagick) for animated GIF support.
  • +
+

Installation procedure#

+

Alternative Installation Methods#

+

This guide will walk you through the manual installation process of Friendica. +If this is nothing for you, you might be interested in

+ +

Get Friendica#

+

Download the full archive of the stable release of Friendica core and the addons from the project homepage. +Make sure that the version of the Friendica archive and the addons match. +Unpack the Friendica files into the root of your web server document area.

+

If you copy the directory tree to your webserver, make sure that you also copy .htaccess-dist - as "dot" files are often hidden and aren't normally copied.

+

OR

+

Clone the friendica/friendica GitHub repository and import dependencies. +This makes the software much easier to update.

+

The Linux commands to clone the repository into a directory "mywebsite" would be

+
git clone https://github.com/friendica/friendica.git -b stable mywebsite
+cd mywebsite
+bin/composer.phar install --no-dev
+
+

Make sure the folder view/smarty3 exists and is writable by the webserver user, in this case www-data

+
mkdir -p view/smarty3
+chown www-data:www-data view/smarty3
+chmod 775 view/smarty3
+
+

Get the addons by going into your website folder.

+
cd mywebsite
+
+

Clone the addon repository (separately):

+
git clone https://github.com/friendica/friendica-addons.git -b stable addon
+
+

If you want to use the development version of Friendica you can switch to the develop branch in the repository by running

+
git checkout develop
+bin/composer.phar install
+cd addon
+git checkout develop
+
+

Be aware that the develop branch is unstable and may break your Friendica node at any time. +You should have a recent backup before updating. +If you encounter a bug, please let us know.

+

Create a database#

+

Create an empty database and note the access details (hostname, username, password, database name). +Generate a strong password, then enter mysql with:

+
mysql
+
+

Then use the following script using the password you just generated:

+
CREATE DATABASE friendicadb;
+CREATE USER 'friendica'@'localhost' IDENTIFIED BY '<<your mysql password here>>';
+GRANT ALL ON friendicadb.* TO 'friendica'@'localhost';
+FLUSH PRIVILEGES;
+EXIT;
+
+

Friendica needs the permission to create and delete fields and tables in its own database.

+

Please check the troubleshooting section if running on MySQL 5.7.17 or newer.

+

Option A: Run the installer#

+

Before you point your web browser to the new site you need to copy .htaccess-dist to .htaccess for Apache installs. +Follow the instructions. +Please note any error messages and correct these before continuing.

+

If you need to specify a port for the connection to the database, you can do so in the host name setting for the database.

+

If the manual installation fails for any reason, check the following:

+
    +
  • Does config/local.config.php exist? If not, edit config/local-sample.config.php and change the system settings.
  • +
  • Rename to config/local.config.php.
  • +
  • Is the database populated? If not, import the contents of database.sql with phpmyadmin or the mysql command line.
  • +
+

At this point visit your website again, and register your personal account. +Registration errors should all be recoverable automatically. +If you get any critical failure at this point, it generally indicates the database was not installed correctly. +You might wish to move/rename config/local.config.php to another name and empty (called 'dropping') the database tables, so that you can start fresh.

+

Option B: Run the automatic installation script#

+

You have the following options to automatically install Friendica: +- creating a prepared config file (f.e. prepared.config.php) +- using environment variables (f.e. MYSQL_HOST) +- using options (f.e. --dbhost <host>)

+

You can combine environment variables and options, but be aware that options are prioritized over environment variables.

+

For more information during the installation, you can use this command line option

+
bin/console autoinstall -v
+
+

If you wish to include all optional checks, use -a like this statement:

+
bin/console autoinstall -a
+
+

If the automatic installation fails for any reason, check the following:

+
    +
  • Does config/local.config.php already exist? If yes, the automatic installation won't start
  • +
  • Are the options in the config/local.config.php correct? If not, edit them directly.
  • +
  • Is the empty MySQL-database created? If not, create it.
  • +
+

B.1: Config file#

+

You can use a prepared config file like "local-sample.config.php".

+

Navigate to the main Friendica directory and execute the following command:

+
bin/console autoinstall -f <prepared.config.php>
+
+

B.2: Environment variables#

+

There are two types of environment variables. +- those you can use in normal mode too (Currently just database credentials) +- those you can only use during installation (because Friendica will normally ignore it)

+

You can use the options during installation too and skip some environment variables.

+

Database credentials

+

if you don't use the option --savedb during installation, the DB credentials will not be saved in the config/local.config.php.

+
    +
  • MYSQL_HOST The host of the mysql/mariadb database
  • +
  • MYSQL_PORT The port of the mysql/mariadb database
  • +
  • MYSQL_USERNAME The username of the mysql database login (used for mysql)
  • +
  • MYSQL_USER The username of the mysql database login (used for mariadb)
  • +
  • MYSQL_PASSWORD The password of the mysql/mariadb database login
  • +
  • MYSQL_DATABASE The name of the mysql/mariadb database
  • +
+

Friendica settings

+

These variables won't be used at normal Friendica runtime. +Instead, they get saved into config/local.config.php.

+
    +
  • FRIENDICA_URL_PATH The URL path of Friendica (f.e. '/friendica')
  • +
  • FRIENDICA_PHP_PATH The path of the PHP binary
  • +
  • FRIENDICA_ADMIN_MAIL The admin email address of Friendica (this email will be used for admin access)
  • +
  • FRIENDICA_TZ The timezone of Friendica
  • +
  • FRIENDICA_LANG The language of Friendica
  • +
+

Navigate to the main Friendica directory and execute the following command:

+
bin/console autoinstall [--savedb]
+
+

B.3: Execution options#

+

All options will be saved in the config/local.config.php and are overruling the associated environment variables.

+
    +
  • -H|--dbhost <host> The host of the mysql/mariadb database (env MYSQL_HOST)
  • +
  • -p|--dbport <port> The port of the mysql/mariadb database (env MYSQL_PORT)
  • +
  • -U|--dbuser <username> The username of the mysql/mariadb database login (env MYSQL_USER or MYSQL_USERNAME)
  • +
  • -P|--dbpass <password> The password of the mysql/mariadb database login (env MYSQL_PASSWORD)
  • +
  • -d|--dbdata <database> The name of the mysql/mariadb database (env MYSQL_DATABASE)
  • +
  • -u|--urlpath <url_path> The URL path of Friendica - f.e. '/friendica' (env FRIENDICA_URL_PATH)
  • +
  • -b|--phppath <php_path> The path of the PHP binary (env FRIENDICA_PHP_PATH)
  • +
  • -A|--admin <mail> The admin email address of Friendica (env FRIENDICA_ADMIN_MAIL)
  • +
  • -T|--tz <timezone> The timezone of Friendica (env FRIENDICA_TZ)
  • +
  • -L|--lang <language> The language of Friendica (env FRIENDICA_LANG)
  • +
+

Navigate to the main Friendica directory and execute the following command:

+
bin/console autoinstall [options]
+
+

Prepare .htaccess file#

+

Copy .htaccess-dist to .htaccess (be careful under Windows) to have working mod-rewrite again. If you have installed Friendica into a subdirectory, like /friendica/ set this path in RewriteBase accordingly.

+

Example:

+
cp .htacces-dist .htaccess
+
+

Note: Do not rename the .htaccess-dist file as it is tracked by GIT and renaming will cause a dirty working directory.

+

Verify the "host-meta" page is working#

+

Friendica should respond automatically to important addresses under the /.well-known/ rewrite path. +One critical URL would look like, for example: https://example.com/.well-known/host-meta
+It must be visible to the public and must respond with an XML file that is automatically customized to your site.

+

If that URL is not working, it is possible that some other software is using the /.well-known/ path. +Other symptoms may include an error message in the Admin settings that says "host-meta is not reachable on your system. +This is a severe configuration issue that prevents server to server communication." +Another common error related to host-meta is the "Invalid profile URL."

+

Check for a .well-known directory that did not come with Friendica. +The preferred configuration is to remove the directory, however this is not always possible. +If there is any /.well-known/.htaccess file, it could interfere with this Friendica core requirement. +You should remove any RewriteRules from that file, or remove that whole file if appropriate. +It may be necessary to chmod the /.well-known/.htaccess file if you were not given write permissions by default.

+

Register the admin account#

+

At this point visit your website again, and register your personal account with the same email as in the config.admin_email config value. +Registration errors should all be recoverable automatically.

+

If you get any critical failure at this point, it generally indicates the database was not installed correctly. +You might wish to delete/rename config/local.config.php to another name and drop all the database tables so that you can start fresh.

+

Post Install Configuration#

+

(REQUIRED) Background tasks#

+

Set up a cron job or scheduled task to run the worker once every 5-10 minutes in order to perform background processing. +Example:

+
cd /base/directory; /path/to/php bin/worker.php
+
+

Change "/base/directory", and "/path/to/php" as appropriate for your situation.

+

cron job for worker#

+

If you are using a Linux server, run "crontab -e" and add a line like the +one shown, substituting for your unique paths and settings:

+
*/10 * * * * cd /home/myname/mywebsite; /usr/bin/php bin/worker.php
+
+

You can generally find the location of PHP by executing "which php". +If you run into trouble with this section please contact your hosting provider for assistance. +Friendica will not work correctly if you cannot perform this step.

+

If it is not possible to set up a cron job then please activate the "frontend worker" in the administration interface.

+

Once you have installed Friendica and created an admin account as part of the process, you can access the admin panel of your installation and do most of the server wide configuration from there.

+

worker alternative: daemon#

+

Otherwise, you’ll need to use the command line on your remote server and start the Friendica daemon (background task) using the following command:

+
cd /path/to/friendica; php bin/daemon.php start
+
+

Once started, you can check the daemon status using the following command:

+
cd /path/to/friendica; php bin/daemon.php status
+
+

After a server restart or any other failure, the daemon needs to be restarted. +This could be achieved by a cronjob.

+ +

At this point it is recommended that you set up logging and logrotate. +To do so please visit Settings and search the 'Logs' section for more information.

+ +

Bad things will happen. +Let there be a hardware failure, a corrupted database or whatever you can think of. +So once the installation of your Friendica node is done, you should make yourself a backup plan.

+

The most important file is the config/local.config.php file. +As it stores all your data, you should also have a recent dump of your Friendica database at hand, should you have to recover your node.

+

(OPTIONAL) Reverse-proxying and HTTPS#

+

Friendica looks for some well-known HTTP headers indicating a reverse-proxy +terminating an HTTPS connection. +While the standard from RFC 7239 specifies the use of the Forwarded header.

+
Forwarded: for=192.0.2.1; proto=https; by=192.0.2.2
+
+

Friendica also supports a number on non-standard headers in common use.

+
X-Forwarded-Proto: https
+
+Front-End-Https: on
+
+X-Forwarded-Ssl: on
+
+

It is however preferable to use the standard approach if configuring a new server.

+

Troubleshooting#

+

"System is currently unavailable. Please try again later"#

+

Check your database settings. +It usually means your database could not be opened or accessed. +If the database resides on the same machine, check that the database server name is "localhost".

+

500 Internal Error#

+

This could be the result of one of our Apache directives not being supported by your version of Apache. Examine your apache server logs. +You might remove the line "Options -Indexes" from the .htaccess file if you are using a Windows server as this has been known to cause problems. +Also check your file permissions. Your website and all contents must generally be world-readable.

+

It is likely that your web server reported the source of the problem in its error log files. +Please review these system error logs to determine what caused the issue. +Often this will need to be resolved with your hosting provider or (if self-hosted) your web server configuration.

+

400 and 4xx "File not found" errors#

+

First check your file permissions. +Your website and all contents must generally be world-readable.

+

Ensure that mod-rewrite is installed and working, and that your .htaccess file +is being used. To verify the latter, create a file test.out containing the +word "test" in the top directory of Friendica, make it world readable and point +your web browser to

+
http://yoursitenamehere.com/test.out
+
+

This file should be blocked. You should get a permission denied message.

+

If you see the word "test" your Apache configuration is not allowing your +.htaccess file to be used (there are rules in this file to block access to any +file with .out at the end, as these are typically used for system logs).

+

Make certain the .htaccess file exists and is readable by everybody, then look +for the existence of "AllowOverride None" in the Apache server configuration for your site. +This will need to be changed to "AllowOverride All".

+

If you do not see the word "test", your .htaccess is working, but it is likely +that mod-rewrite is not installed in your web server or is not working.

+

On most Linux flavors:

+
% a2enmod rewrite
+% /etc/init.d/apache2 restart
+
+

Consult your hosting provider, experts on your particular Linux distribution or +(if Windows) the provider of your Apache server software if you need to change +either of these and can not figure out how. There is a lot of help available on +the web. Search "mod-rewrite" along with the name of your operating system +distribution or Apache package (if using Windows).

+

Unable to write the file config/local.config.php due to permissions issues#

+

Create an empty config/local.config.phpfile and apply world-write permission.

+

On Linux:

+
% touch config/local.config.php
+% chmod 664 config/local.config.php
+
+

Retry the installation. As soon as the database has been created,

+

* this is important ***

+
% chmod 644 config/local.config.php
+
+

Suhosin issues#

+

Some configurations with "suhosin" security are configured without an ability to +run external processes. Friendica requires this ability. Following are some notes +provided by one of our members.

+
+

On my server I use the php protection system Suhosin [http://www.hardened-php.net/suhosin/]. +One of the things it does is to block certain functions like proc_open, as +configured in /etc/php5/conf.d/suhosin.ini:

+
suhosin.executor.func.blacklist = proc_open, ...
+
+

For those sites like Friendica that really need these functions they can be +enabled, e.g. in /etc/apache2/sites-available/friendica:

+

+ php_admin_value suhosin.executor.func.blacklist none + php_admin_value suhosin.executor.eval.blacklist none +

+

This enables every function for Friendica if accessed via browser, but not for +the cronjob that is called via php command line. I attempted to enable it for +cron by using something like:

+

/10 * * * cd /var/www/friendica/friendica/ && sudo -u www-data /usr/bin/php \ + -d suhosin.executor.func.blacklist=none \ + -d suhosin.executor.eval.blacklist=none -f bin/worker.php

+

This worked well for simple test cases, but the friendica-cron still failed +with a fatal error:

+

suhosin[22962]: ALERT - function within blacklist called: proc_open() + (attacker 'REMOTE_ADDR not set', file '/var/www/friendica/friendica/boot.php', + line 1341)

+

After a while I noticed, that bin/worker.php calls further PHP scripts via proc_open. +These scripts themselves also use proc_open and fail, because they are NOT +called with -d suhosin.executor.func.blacklist=none.

+

So the simple solution is to put the correct parameters into config/local.config.php:

+

'config' => [ + //Location of PHP command line processor + 'php_path' => '/usr/bin/php -d suhosin.executor.func.blacklist=none \ + -d suhosin.executor.eval.blacklist=none', + ],

+

This is obvious as soon as you notice that the friendica-cron uses proc_open +to execute PHP scripts that also use proc_open, but it took me quite some time to find that out. +I hope this saves some time for other people using suhosin with function blocklists.

+
+

Unable to create all mysql tables on MySQL 5.7.17 or newer#

+

If the setup fails to create all the database tables and/or manual creation from +the command line fails, with this error:

+
ERROR 1067 (42000) at line XX: Invalid default value for 'created'
+
+

You need to adjust your my.cnf and add the following setting under the [mysqld] +section:

+
sql_mode = '';
+
+

After that, restart mysql and try again.

+

Your worker never or rarely runs#

+

Friendica is coded to always play nice. It checks whether the host machine is idle enough and if it seems to be overloaded, it intermittently refuses to process the worker queue.

+

Such checks originate from the days of single-user single-core machines and involves thresholds that you should adjust based on the number of exclusive CPU cores you have. See this issue for more information:

+
    +
  • https://github.com/friendica/friendica/issues/10131
  • +
+

If you want to be neighborly and are using a shared web hosting PaaS provider, especially within the free tier, you need to set maxloadavg to say twice the maximum value of /proc/loadavg during peak hours.

+

If you have the whole (virtual) machine for yourself such as in case of an IaaS VPS, you can set it to orders of magnitude higher than its commonly observed value, such as 1000.

+

You should instead enact limits in your web server configuration based on the number of entry processes to cap the concurrent memory usage of your PHP processes. +See RLimitMEM, RLimitCPU, RLimitNPROC, StartServers, ServerLimit, MaxRequestsPerChild, pm.max_children, pm.start_servers and related options in your server.

+

Error uploading even small image files#

+

You tried to upload an image up to 100kB, and it failed.

+

You may not have the ownership or file mode set correctly if you are using the file system storage backend.

+

Change the backend to database. If this solves it, that is what needs to be fixed.

+

Error uploading large files#

+

You may find 413 Request Entity Too Large or 500 Internal Error in the network inspector of the browser if the file is too large, for example if it is a video.

+

First try to upload a very small file, up to 100kB. If that succeeds, you will need to increase limits at multiple places, including on any web proxy that you are using.

+

In your PHP ini:

+
    +
  • upload_max_filesize: defaults to 2MB
  • +
  • post_max_size: defaults to 8MB, must be greater than upload_max_filesize
  • +
  • memory_limit: defaults to 128MB, must be greater than post_max_size
  • +
+

You should verify whether you changed them in the right file by checking the web interface at the end of the overview on the Admin panel.

+

For Apache2:

+
    +
  • LimitRequestBody: defaults to unlimited
  • +
  • SSLRenegBufferSize: defaults to 128kB, only if your site uses TLS and perhaps only when using SSLVerifyClient or SSLVerifyDepth
  • +
+

For nginx:

+
    +
  • client_max_body_size: defaults to 1MB
  • +
+

If you are using the database backend for storage, increase this in your SQL configuration:

+
    +
  • max_allowed_packet: defaults to 32MB
  • +
+

If you use the ModSecurity WAF:

+
    +
  • SecRequestBodyLimit: defaults to 12MB
  • +
  • SecRequestBodyNoFilesLimit: defaults to 128kB, should not apply to Friendica
  • +
+ + +
+ +
+
+ + + +
+ + + +
+
+
+
+ + + + + + + + \ No newline at end of file diff --git a/develop/admin/installing-connectors/index.html b/develop/admin/installing-connectors/index.html new file mode 100644 index 0000000..9fd1a9f --- /dev/null +++ b/develop/admin/installing-connectors/index.html @@ -0,0 +1,3482 @@ + + + + + + + + + + + + + + + + + + + + + + Connectors - Friendica documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + Skip to content + + +
+
+ +
+ + + + +
+ + +
+ +
+ + + + + + + + + +
+
+ + + +
+
+
+ + + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + +
+
+ + + + + + + + + + + + + +

Installing Connectors (Twitter/GNU Social)#

+

Friendica uses addons to provide connectivity to some networks, such as Twitter.

+

There is also an addon to post through to an existing account on a GNU Social service. +You only need this to post to an already existing GNU Social account, but not to communicate with GNU Social members in general.

+

All three addons require an account on the target network. +In addition, you (or typically the server administrator) will need to obtain an API key to provide authenticated access to your Friendica server.

+

Site Configuration#

+

Addons must be installed by the site administrator before they can be used. +This is accomplished through the site administration panel.

+

Each of the connectors also requires an "API key" from the service you wish to connect with. +Some addons allow you to enter this information in the site administration pages, while others may require you to edit your configuration file (config/local.config.php). +The ways to obtain these keys vary between the services, but they all require an existing account on the target service. +Once installed, these API keys can usually be shared by all site members.

+

The details of configuring each service follow (much of this information comes directly from the addon source files):

+

Twitter Addon for Friendica#

+
    +
  • Author: Tobias Diekershoff
  • +
  • tobias.diekershoff@gmx.net
  • +
  • License: 3-clause BSD license
  • +
+

Configuration#

+

To use this addon you need a OAuth Consumer key pair (key & secret). +You can get it from Twitter.

+

Register your Friendica site as "Client" application with "Read & Write" access. +We do not need "Twitter as login". +When you've registered the app you get a key pair with an OAuth Consumer key and a secret key for your application/site. +Add this key pair to your config/local.config.php:

+
[twitter]
+consumerkey = your consumer_key here
+consumersecret = your consumer_secret here
+
+

After this, your users can configure their Twitter account settings from "Settings -> Connector Settings".

+

More documentation#

+

Find the author's documentation here: http://diekershoff.homeunix.net/redmine/wiki/friendikaplugin/Twitter_Plugin

+

GNU Social Addon for Friendica#

+
    +
  • Author: Tobias Diekershoff
  • +
  • tobias.diekershoff@gmx.net
  • +
  • License: 3-clause BSD license
  • +
+

Configuration#

+

When the addon is activated the user has to acquire the following in order to connect to the GNU Social account of choice.

+
    +
  • The base URL for the GNU Social API, for quitter.se this is https://quitter.se/api/
  • +
  • OAuth Consumer key & secret
  • +
+

To get the OAuth Consumer key pair the user has to

+

1 ask her Friendica admin if a pair already exists or +2 has to register the Friendica server as a client application on the GNU Social server.

+

This can be done from the account settings under "Settings -> Connections -> Register an OAuth client application -> Register a new application" on the GNU Social server.

+

During the registration of the OAuth client remember the following:

+
    +
  • Application names must be unique on the GNU Social site, so we recommend a Name of 'friendica-nnnn', replace 'nnnn' with a random number or your website name.
  • +
  • there is no callback url
  • +
  • register a desktop client
  • +
  • with read & write access
  • +
  • the Source URL should be the URL of your Friendica server
  • +
+

After the required credentials for the application are stored in the configuration you have to actually connect your Friendica account with GNU Social. +This is done from the Settings -> Connector Settings page. +Follow the Sign in with GNU Social button, allow access and then copy the security code into the box provided. +Friendica will then try to acquire the final OAuth credentials from the API.

+

If successful, the addon settings will allow you to select to post your public messages to your GNU Social account (have a look behind the little lock symbol beneath the status "editor" on your Home or Network pages).

+ + +
+ +
+
+ + + +
+ + + +
+
+
+
+ + + + + + + + \ No newline at end of file diff --git a/develop/admin/migrate/index.html b/develop/admin/migrate/index.html new file mode 100644 index 0000000..8cc0aa6 --- /dev/null +++ b/develop/admin/migrate/index.html @@ -0,0 +1,3584 @@ + + + + + + + + + + + + + + + + + + + + + + Migrate - Friendica documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + Skip to content + + +
+
+ +
+ + + + +
+ + +
+ +
+ + + + + + + + + +
+
+ + + +
+
+
+ + + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + +
+
+ + + + + + + + + + + + + +

Migrating to a new server installation#

+

Preparation#

+

New server#

+

Set up your new server as described here; follow the installation procedure until you have created a database.

+

Heads up to users#

+

Inform your users of an upcoming interruption to your service. +To ensure data consistency, your server needs to be offline during some steps of the migration processes.

+

You may also find these addons useful for communicating with your users prior to the migration process: +* blackout +* notifyall

+

Storage#

+

Check your storage backend with bin/console storage list in the root folder. +The output should look like this: +

Sel | Name
+-----------------------
+     | Filesystem
+ *   | Database
+

+

If you are not using Database run the following commands: +1. bin/console storage set Database to activate the database backend. +2. bin/console storage move to initiate moving the stored image files.

+

This process may take a long time depending on the size of your storage and your server's capacity. +Prior to initiating this process, you may want to check the number of files in the storage with the following command: tree -if -I index.html /path/to/storage/.

+

Cleaning up#

+

Before transferring your database, you may want to clean it up; ensure the expiration of database items is set to a reasonable value and activated via the administrator panel. +Admin > Site > Performance > Enable "Clean up database" +After adjusting these settings, the database cleaning up processes will be initiated according to your configured daily cron job.

+

To review the size of your database, log into MySQL with mysql -p run the following query: +

SELECT table_schema AS "Database", SUM(data_length + index_length) / 1024 / 1024 / 1024 AS "Size (GB)" FROM information_schema.TABLES GROUP BY table_schema;
+

+

You should see an output like this: +

+--------------------+----------------+
+| Database           | Size (GB)      |
++--------------------+----------------+
+| friendica_db       | 8.054092407227 |
+| [..........]       | [...........]  |
++--------------------+----------------+
+

+

Finally, you may also want to optimise your database with the following command: mysqloptimize -p friendica-db

+

Going offline#

+

Stop background tasks and put your server in maintenance mode. +1. If you had set up a worker cron job like this */10 * * * * cd /var/www/friendica; /usr/bin/php bin/worker.php run crontab -e and comment out this line. Alternatively if you deploy a worker daemon, disable this instead. +2. Put your server into maintenance mode: bin/console maintenance 1 "We are currently upgrading our system and will be back soon."

+

Dumping DB#

+

Export your database: mysqldump -p friendica_db > friendica_db-$(date +%Y%m%d).sql and possibly compress it.

+

Transferring to new server#

+

Transfer your database and a copy of your configuration file config/local.config.php.copy to your new server installation.

+

Restoring your DB#

+

Import your database on your new server: mysql -p friendica_db < your-friendica_db-file.sql

+

Completing migration#

+

Configuration file#

+

Copy your old server's configuration file to config/local.config.php. +Ensure the newly created database credentials are identical to the setting in the configuration file; otherwise update them accordingly.

+

Cron job for worker#

+

Set up the required daily cron job. +Run crontab -e and add the following line according to your system specification +

*/10 * * * * cd /var/www/friendica; /usr/bin/php bin/worker.php
+

+

DNS settings#

+

Adjust your DNS records by pointing them to your new server.

+

Troubleshooting#

+

If you are unable to log in to your newly migrated Friendica installation, check your web server's error and access logs and mysql logs for obvious issues.

+

If still unable to resolve the problem, it's likely an issue with your installation. +In this case, you may try to an entirely new Friendica installation on your new server, but use a different FQDN and DNS name. +Once you have this up and running, take it offline and purge the database and configuration file and try migrating to this installation.

+ + +
+ +
+
+ + + +
+ + + +
+
+
+
+ + + + + + + + \ No newline at end of file diff --git a/develop/admin/settings/index.html b/develop/admin/settings/index.html new file mode 100644 index 0000000..5f40b09 --- /dev/null +++ b/develop/admin/settings/index.html @@ -0,0 +1,4372 @@ + + + + + + + + + + + + + + + + + + + + + + Settings - Friendica documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + Skip to content + + +
+
+ +
+ + + + +
+ + +
+ +
+ + + + + + + + + +
+
+ + + +
+
+
+ + + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + +
+
+ + + + + + + + + + + + + +

Settings#

+

If you are the admin of a Friendica node, you have access to the Admin Panel where you can configure your Friendica node.

+

Overview#

+

In the main page of the admin panel you will see an information summary about your node.

+

Queues#

+

The three numbers shown are respectively: +- The retry queue: These outgoing messages couldn't be received by the remote host, and will be resent at longer intervals before being dropped entirely after 30 days. +- The deferred queue: These internal tasks failed and will be retried at most 14 times. +- The task queue: These internal tasks are queued for execution during the next background worker run.

+

Additional information#

+

Then you get an overview of the accounts on your node, which can be moderated in the "Users" section of the panel. +As well as an overview of the currently active addons. +The list is linked, so you can have quick access to the Addon settings. +And finally you are informed about the version of Friendica you have installed. +If you contact the developers with a bug or problem, please also mention the version of your node.

+

The admin panel is separated into subsections accessible from the sidebar of the panel.

+

Site#

+

This section of the admin panel contains the main configuration of your Friendica node. +It is separated into several subsection beginning with the basic settings at the top, advancing towards the bottom of the page.

+

Most configuration options have a help text in the admin panel. +Therefore, this document does not yet cover all the options

+

Basic Settings#

+ +

Set the content for the site banner. +The default logo is the Friendica logo and name. +You may wish to provide HTML/CSS to style and/or position this content, as it may not be themed by default.

+

Language#

+

This option will set the default language for the node. +It is used as fall back setting should Friendica fail to recognize the visitors preferences and can be overwritten by user settings.

+

The Friendica community offers some translations. +Some more complete than others. +See this help page for more information about the translation process.

+

System Theme#

+

Choose a theme to be the default system theme. +This can be over-ridden by user profiles. +Default theme is vier at the moment.

+

You may also want to set a special theme for mobile interfaces. +Which may or may not be necessary depending on the mobile friendliness of the desktop theme you have chosen. +The vier theme for instance is mobile friendly.

+

Registration#

+

Register policy#

+

With this dropdown selector you can set the nodes' registration policy. +You can choose between the following modes:

+
    +
  • open: Everybody can register a new account and start using it right away.
  • +
  • requires approval: Everybody can register a new account, but the admin has to approve it before it can be used.
  • +
  • closed: No new registrations are possible.
  • +
+
Invitation based registry#
+

Additionally, to the setting in the admin panel, you can decide if registrations are only possible using an invitation code or not. +To enable invitation based registration, you have to set the invitation_only setting to true in the system section of the config/local.config.php file. +If you want to use this method, the registration policy has to be set to either open or requires approval.

+

Check Full Names#

+

You may find a lot of spammers trying to register on your site. +During testing we discovered that since these registrations were automatic, the "Full Name" field was often set to just an account name with no space between first and last name. +If you would like to support people with only one name as their full name, you may change this setting to true. +Default is false.

+

OpenID#

+

By default, OpenID may be used for both registration and logins. +If you do not wish to make OpenID facilities available on your system (at all), set 'no_openid' to true. +Default is false.

+

Multiple Registrations#

+

The ability to create "Pages" requires a person to register more than once. +Your site configuration can block registration (or require approval to register). +By default, logged-in users can register additional accounts for use as pages. +These will still require approval if the registration policy is set to require approval +You may prohibit logged-in users from creating additional accounts by setting block multiple registrations to true. +Default is false.

+

File upload#

+

File storage backend#

+

Set the backend used by Friendica to store uploaded file data. +Two storage backends are available with Friendica:

+
    +
  • Database : Data is stored in a dedicated table in database (storage)
  • +
  • Filesystem : Data is stored as file on the filesystem.
  • +
+

More storage backends can be available from third-party addons. +If you use those, please refer to the documentation of those addons for further information.

+

Default value is 'Database (legacy)': it's the legacy way used to store data directly in database.

+

Existing data can be moved to the current active backend using the 'storage move' console command

+

If selected backend has configurable options, new fields are shown here.

+
Filesystem: Storage base path#
+

The base path where Filesystem storage backend saves data.

+

For maximum security, this path should be outside the folder tree served by the web server: this way files can't be downloaded bypassing the privacy checks.

+

Default value is storage, that is the storage folder in Friendica code root folder.

+

Maximum Image Size#

+

Maximum size in bytes of uploaded images. +The default is set to 0, which means no limits.

+

Policies#

+

Global Directory#

+

This configures the URL to update the global directory, and is supplied in the default configuration. +The undocumented part is that if this is not set, the global directory is completely unavailable to the application. +This allows a private community to be completely isolated from the global network.

+

Force Publish#

+

By default, each user can choose on their Settings page whether to have their profile published in the site directory. +This setting forces all profiles on this site to be listed in the site directory and there is no option provided to the user to change it. +Default is false.

+

Block Public#

+

Set to true to block public access to all otherwise public personal pages on this site unless you are currently logged in. +This blocks the viewing of profiles, friends, photos, the site directory and search pages to unauthorised persons. +A side effect is that entries from this site will not appear in the global directory. +We recommend specifically disabling that also (setting is described elsewhere on this page). +Note: this is specifically for sites that desire to be "standalone" and do not wish to be connected to any other Friendica sites. +Unauthorised persons will also not be able to request friendship with site members. +Default is false. +Available in version 2.2 or greater.

+

Community pages for Visitors#

+

The community pages show all public postings, separated by their origin being local or the entire network. +With this setting you can select which community pages will be shown to visitors of your Friendica node. +Your local users will always have access to both pages.

+

Note: Several settings, like users hiding their contacts from the public will prevent the postings to show up on the global community page.

+

Allowed Friend Domains#

+

Comma separated list of domains which are allowed to establish friendships with this site. +Wildcards are accepted. +By default, any (valid) domain may establish friendships with this site.

+

This is useful if you want to set up a closed network for educational groups, cooperatives and similar communities that don't want to communicate with the rest of the network.

+

Allowed Email Domains#

+

Comma separated list of domains which are allowed in email addresses for registrations to this site. +This can lockout those who are not part of this organisation from registering here. +Wildcards are accepted. +By default, any (valid) email address is allowed in registrations.

+

Allow Users to set remote_self#

+

If you enable the Allow Users to set remote_self users can select Atom feeds from their contact list being their remote self in the contact settings. +Which means that postings by the remote self are automatically reposted by Friendica in their names.

+

This feature can be used to let the user mirror e.g. blog postings into their Friendica postings. +It is disabled by default, as it causes additional load on the server and may be misused to distribute SPAM.

+

As admin of the node you can also set this flag directly in the database. +Before doing so, you should be sure you know what you do and have a backup of the database.

+

Explicit Content#

+

If you are running a node with explicit content, you can announce this with this option. +When checked an information flag will be set in the published information about your node. +(Should Publish Server Information be enabled.)

+

Additionally, a note will be displayed on the registration page for new users.

+

Advanced#

+

Proxy Configuration Settings#

+

If your site uses a proxy to connect to the internet, you may use these settings to communicate with the outside world. +The outside world still needs to be able to see your website, or this will not be very useful.

+

Network Timeout#

+

How long to wait on a network communication before timing out. +Value is in seconds. +Default is 60 seconds. +Set to 0 for unlimited (not recommended).

+

Verify SSL Certificates#

+

By default, Friendica allows SSL communication between websites that have "self-signed" SSL certificates. +For the widest compatibility with browsers and other networks we do not recommend using self-signed certificates, but we will not prevent you from using them. +SSL encrypts all the data transmitted between sites (and to your browser). +This allows you to have completely encrypted communications, and also protect your login session from hijacking. +Self-signed certificates can be generated for free, without paying top-dollar for a website SSL certificate. +However, these aren't looked upon favourably in the security community because they can be subject to so-called "man-in-the-middle" attacks. +If you wish, you can turn on strict certificate checking. +This will mean you cannot connect (at all) to self-signed SSL sites.

+

Check upstream version#

+

If this option is enabled your Friendica node will check the upstream version once per day from the GitHub repository. +You can select if the stable version or the development version should be checked out. +If there is a new version published, you will get notified in the admin panel summary page.

+

Auto Discovered Contact Directory#

+

Performance#

+

Worker#

+

This section allows you to configure the background process that is triggered by the cron job that was created during the installation. +The process does check the available system resources before creating a new worker for a task. +Because of this, it may happen that the maximum number of worker processes you allow will not be reached.

+

The tasks for the background process have priorities. +To guarantee that important tasks are executed even though the system has a lot of work to do, it is useful to enable the fastlane.

+

Relocate#

+

Users#

+

This section of the panel let the admin control the users registered on the node.

+

If you have selected "Requires approval" for the Register policy in the general nodes' configuration, new registrations will be listed at the top of the page. +There the admin can then approve or disapprove the request.

+

Below the new registration block the current accounts on the Friendica node are listed. +You can sort the user list by name, email, registration date, date of last login, date of last posting and the account type. +Here the admin can also block/unblock users from accessing the node or delete the accounts entirely.

+

In the last section of the page admins can create new accounts on the node. +The password for the new account will be sent by email to the chosen email address.

+

Addons#

+

This page is for selecting and configuration of extensions for Friendica which have to be placed into the /addon subdirectory of your Friendica installation. +You are presented with a long list of available addons. +The name of each addon is linked to a separate page for that addon which offers more information and configuration possibilities. +Also shown is the version of the addon and an indicator if the addon is currently active or not.

+

When you update your node and the addons they may have to be reloaded. +To simplify this process there is a button at the top of the page to reload all active Addons.

+

Themes#

+

The Themes' section of the admin panel works similar to the Addons section but let you control the themes on your Friendica node. +Each theme has a dedicated subpage showing the current status, some information about the theme and a screenshot of the Friendica interface using the theme. +Should the theme offer special settings, admins can set a global default value here.

+

You can activate and deactivate themes on their dedicated sub-pages thus making them available for the users of the node. +To select a default theme for the Friendica node, see the Site section of the admin panel.

+

Additional Features#

+

There are several optional features in Friendica like the dislike button. +In this section of the admin panel you can select a default setting for your node and eventually fix it, so users cannot change the setting anymore.

+

DB Updates#

+

Should the database structure of Friendica change, it will apply the changes automatically. +In case you are suspecting the update might not have worked, you can use this section of the admin panel to check the situation.

+

Inspect Queue#

+

In the admin panel summary there are two numbers for the message queues. +The second number represents messages which could not be delivered and are queued for later retry. +If this number goes sky-rocking you might ask yourself which recipient is not receiving.

+

Behind the inspect queue section of the admin panel you will find a list of the messages that could not be delivered. +The listing is sorted by the recipient name so identifying potential broken communication lines should be simple. +These lines might be broken for various reasons. +The receiving end might be off-line, there might be a high system load and so on.

+

Don't panic! +Friendica will not queue messages for all time but will sort out dead nodes automatically after a while and remove messages from the queue then.

+

Server Blocklist#

+

This page allows to block all communications (inbound and outbound) with a specific domain name. +Each blocked domain entry requires a reason that will be displayed on the friendica (/friendica) page. +Matching is exact, blocking a domain doesn't block subdomains.

+

Federation Statistics#

+

The federation statistics page gives you a short summery of the nodes/servers/pods of the decentralized social network federation your node knows. +These numbers are not complete and only contain nodes from networks Friendica federates directly with.

+

Delete Item#

+

Using this page an admin can delete postings and eventually associated discussion threads from their Friendica node. +To do so, they need to know the GUID of the posting. +This can be found on the /display page of the posting, it is the last part of the URL displayed in the browsers' navigation bar. +You can get to the /display page by following the Link to source.

+

Addon Features#

+

Some addons you can install for your Friendica node have settings which have to be set by the admin. +All those addons will be listed in this area of the admin panels sidebar with their names.

+

Logs#

+

The log section of the admin panel is separated into two pages. +On the first, following the "log" link, you can configure how much Friendica shall log. +And on the second you can read the log.

+

You should not place your logs into any directory that is accessible from the web. +If you have to, and you are using the default configuration from Apache, you should choose a name for the logfile ending in .log or .out. +Should you use another web server, please make sure that you have the correct access rules in place so that your log files are not accessible.

+

There are five different log levels: Normal, Trace, Debug, Data and All. +Specifying different verbosity of information and data written out to the log file. +Normally you should not need to log at all. +The DEBUG level will show a good deal of information about system activity but will not include detailed data. +In the ALL level Friendica will log everything to the file. +But due to the volume of information we recommend only enabling this when you are tracking down a specific problem.

+

The amount of data can grow the filesize of the logfile quickly. +You should set up some kind of log rotation to keep the log file from growing too big.

+

Known Issues: The filename friendica.log can cause problems depending on your server configuration (see issue 2209).

+

By default, PHP warnings and error messages are suppressed. +If you want to enable those, you have to activate them in the config/local.config.php file. +Use the following settings to redirect PHP errors to a file.

+

Config:

+
<?php
+error_reporting(E_ERROR | E_WARNING | E_PARSE );
+ini_set('error_log','php.out');
+ini_set('log_errors','1');
+ini_set('display_errors', '0');
+
+

This will put all PHP errors in the file php.out (which must be writeable by the webserver). +Undeclared variables are occasionally referenced in the program, and therefore we do not recommend using E_NOTICE or E_ALL. +The vast majority of issues reported at these levels are completely harmless. +Please report to the developers any errors you encounter in the logs using the recommended settings above. +They generally indicate issues which need to be resolved.

+

If you encounter a blank (white) page when using the application, view the PHP logs - as this almost always indicates an error has occurred.

+

Diagnostics#

+

In this section of the admin panel you find two tools to investigate what Friendica sees for certain resources. +These tools can help to clarify communication problems.

+

For the probe address Friendica will display information for the address provided.

+

With the second tool check webfinger you can request information about the thing identified by a webfinger (someone@example.com).

+

Exceptions to the rule#

+

There are four exceptions to the rule, that all the config will be read from the database. +These are the database settings, the admin account settings, the path of PHP and information about an eventual installation of the node in a subdirectory of the (sub)domain.

+

DB Settings#

+

With the following settings, you specify the database server, the username and password for Friendica and the database to use.

+
'database' => [
+    'hostname' => 'localhost',
+    'username' => 'mysqlusername',
+    'password' => 'mysqlpassword',
+    'database' => 'mysqldatabasename',
+    'charset' => 'utf8mb4',
+],
+
+

Admin users#

+

You can set one, or more, accounts to be Admin. +By default, this will be the one account you create during the installation process. +But you can expand the list of email addresses by any used email address you want. +Registration of new accounts with a listed email address is not possible.

+
'config' => [
+    'admin_email' => 'you@example.com, buddy@example.com',
+],
+
+

PHP Path#

+

Some of Friendica's processes are running in the background. +For this you need to specify the path to the PHP binary to be used.

+
'config' => [
+    'php_path' => '/usr/bin/php',
+],
+
+

Subdirectory configuration#

+

It is possible to install Friendica into a subdirectory of your web server. +We strongly discourage you from doing so, as this will break federation to other networks (e.g. Diaspora, GNU Social, Hubzilla) +Say you have a subdirectory for tests and put Friendica into a further subdirectory, the config would be:

+
'system' => [
+    'urlpath' => 'tests/friendica',
+],
+
+

Other exceptions#

+

Furthermore, there are some experimental settings, you can read-up in the Config values that can only be set in config/local.config.php section of the documentation.

+ + +
+ +
+
+ + + +
+ + + +
+
+
+
+ + + + + + + + \ No newline at end of file diff --git a/develop/admin/ssl/index.html b/develop/admin/ssl/index.html new file mode 100644 index 0000000..6f8fe76 --- /dev/null +++ b/develop/admin/ssl/index.html @@ -0,0 +1,3539 @@ + + + + + + + + + + + + + + + + + + + + + + SSL - Friendica documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + Skip to content + + +
+
+ +
+ + + + +
+ + +
+ +
+ + + + + + + + + +
+
+ + + +
+
+
+ + + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + +
+
+ + + + + + + + + + + + + +

Using SSL with Friendica#

+

Disclaimer#

+

This document has been updated in November 2016. +SSL encryption is relevant for security. +This means that recommended settings change fast. +Keep your setup up to date and do not rely on this document being updated as fast as technologies change!

+

Intro#

+

If you are running your own Friendica site, you may want to use SSL (https) to encrypt communication between servers and between yourself and your server.

+

There are basically two sorts of SSL certificates: Self-signed certificates and certificates signed by a certificate authority (CA). +Technically, both provide the same valid encryption. +There is a problem with self-signed certificates though: +They are neither installed in browsers nor on other servers. +That is why they provoke warnings about "mistrusted certificates". +This is confusing and disturbing.

+

For this reason, we recommend to get a certificate signed by a CA. +Normally, you have to pay for them - and they are valid for a limited period of time (e.g. a year or two).

+

There are ways to get a trusted certificate for free.

+

Choose your domain name#

+

Your SSL certificate will be valid for a domain or even only for a subdomain. +Make your final decision about your domain resp. subdomain before ordering the certificate. +Once you have it, changing the domain name means getting a new certificate.

+

Shared hosts#

+

If your Friendica instance is running on a shared hosting platform, you should first check with your hosting provider. +They have instructions for you on how to do it there. +You can always order a paid certificate with your provider. +They will either install it for you or provide an easy way to upload the certificate and the key via a web interface. +With some providers, you have to send them your certificate. +They need the certificate, the key and the CA's intermediate certificate. +To be sure, send those three files. +You should send them to your provider via an encrypted channel!

+

Own server#

+

If you run your own server, we recommend to check out the "Let's Encrypt" initiative. +Not only do they offer free SSL certificates, but also a way to automate their renewal. +You need to install a client software on your server to use it. +Instructions for the official client are here. +Depending on your needs, you might want to look at the list of alternative LetsEncrypt clients.

+

Web server settings#

+

Visit the Mozilla's wiki for instructions on how to configure a secure webserver. +They provide recommendations for different web servers.

+

Test your SSL settings#

+

When you are done, visit the test site SSL Labs to have them check if you succeeded.

+

Configure Friendica#

+

If you can successfully access your Friendica instance through https, there are a number of steps you can take to ensure your users will use SSL to access your instance.

+

Web server redirection#

+

This is the simplest way to enforce site-wide secure access. +Every time a user tries to access any Friendica page by any mean (manual address bar entry or link), the web server issues a Permanent Redirect response with the secure protocol prepended to the requested URL.

+

With Apache, enable the modules rewrite and ssl (with a shared hosting provider, this should be enabled already):

+
sudo a2enmod rewrite ssl
+
+

Add the following lines to the .htaccess file in the root folder of your Friendica instance (thanks to AlfredSK):

+
RewriteEngine On
+RewriteCond %{SERVER_PORT} 80
+RewriteRule ^(.*)$ https://your.friendica.domain/$1 [R=301,L]
+
+

With nginx, configure your server directive this way (documentation):

+
server {
+     listen 80;
+     server_name your.friendica.domain;
+     return 301 https://$server_name$request_uri;
+}
+
+

SSL Settings#

+

In the Admin Settings, there are three SSL-related settings:

+
    +
  1. SSL link policy: this affects how Friendica generates internal links. If your SSL installation was successful, we recommend "Force all links to SSL" just in case your web server configuration can't be altered like described above.
  2. +
  3. Force SSL: This forces all external links to HTTPS, which may solve Mixed-Content issues, but not all websites support HTTPS yet. Use at your own risk.
  4. +
  5. Verify SSL: Enabling this will prevent Friendica to interact with self-signed SSL sites. We recommend you leave it on as a self-signed SSL certificate can be a vector for a man-in-the-middle attack.
  6. +
+ + +
+ +
+
+ + + +
+ + + +
+
+
+
+ + + + + + + + \ No newline at end of file diff --git a/develop/admin/tools/index.html b/develop/admin/tools/index.html new file mode 100644 index 0000000..e04dfc4 --- /dev/null +++ b/develop/admin/tools/index.html @@ -0,0 +1,3433 @@ + + + + + + + + + + + + + + + + + + + + + + Tools - Friendica documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + Skip to content + + +
+
+ +
+ + + + +
+ + +
+ +
+ + + + + + + + + +
+
+ + + +
+
+
+ + + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + +
+
+ + + + + + + + + + + + + +

Admin Tools#

+

Friendica Tools#

+

Friendica has a build in command console you can find in the bin directory. +The console provides the following commands:

+
    +
  • cache: Manage node cache
  • +
  • config: Edit site config
  • +
  • createdoxygen: Generate Doxygen headers
  • +
  • dbstructure: Do database updates
  • +
  • docbloxerrorchecker: Check the file tree for DocBlox errors
  • +
  • extract: Generate translation string file for the Friendica project (deprecated)
  • +
  • globalcommunityblock: Block remote profile from interacting with this node
  • +
  • globalcommunitysilence: Silence remote profile from global community page
  • +
  • archivecontact: Archive a contact when you know that it isn't existing anymore
  • +
  • help: Show help about a command, e.g (bin/console help config)
  • +
  • autoinstall: Starts automatic installation of friendica based on values from htconfig.php
  • +
  • maintenance: Set maintenance mode for this node
  • +
  • newpassword: Set a new password for a given user
  • +
  • php2po: Generate a messages.po file from a strings.php file
  • +
  • po2php: Generate a strings.php file from a messages.po file
  • +
  • typo: Checks for parse errors in Friendica files
  • +
  • postupdate: Execute pending post update scripts (can last days)
  • +
  • storage: Manage storage backend
  • +
  • relay: Manage ActivityPub relay servers
  • +
+

Please consult bin/console help on the command line interface of your server for details about the commands.

+

3rd Party Tools#

+

In addition to the tools Friendica includes, some 3rd party tools can make your admin days easier.

+

Fail2ban#

+

Fail2ban is an intrusion prevention framework (see Wikipedia) that you can use to forbid access to a server under certain conditions, e.g. 3 failed attempts to log in, for a certain amount of time.

+

The following configuration was provided by Steffen K9 using Debian. +You need to adjust the logpath in the jail.local file and the bantime (value is in seconds).

+

In /etc/fail2ban/jail.local create a section for Friendica:

+
[friendica]
+enabled = true
+findtime = 300
+bantime  = 900
+filter = friendica
+port = http,https
+logpath = /var/log/friend.log
+logencoding = utf-8
+
+

And create a filter definition in /etc/fail2ban/filter.d/friendica.conf:

+
[Definition]
+failregex = ^.*authenticate\: failed login attempt.*\"ip\"\:\"<HOST>\".*$
+ignoreregex =
+
+

Additionally, you have to define the number of failed logins before the ban should be activated. +This is done either in the global configuration or for each jail separately. +You should inform your users about the number of failed login attempts you grant them. +Otherwise, you'll get many reports about the server not functioning if the number is too low.

+

Log rotation#

+

If you have activated the logs in Friendica, be aware that they can grow to a significant size. +To keep them in control you should add them to the automatic log rotation, e.g. using the logrotate command.

+

In /etc/logrotate.d/ add a file called friendica that contains the configuration. +The following will compress /var/log/friendica (assuming this is the location of the log file) on a daily basis and keep 2 days of back-log.

+
/var/log/friendica.log {
+    compress
+    daily
+    rotate 2
+}
+
+ + +
+ +
+
+ + + +
+ + + +
+
+
+
+ + + + + + + + \ No newline at end of file diff --git a/develop/admin/update/index.html b/develop/admin/update/index.html new file mode 100644 index 0000000..dedf62d --- /dev/null +++ b/develop/admin/update/index.html @@ -0,0 +1,3507 @@ + + + + + + + + + + + + + + + + + + + + + + Update - Friendica documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + Skip to content + + +
+
+ +
+ + + + +
+ + +
+ +
+ + + + + + + + + +
+
+ + + +
+
+
+ + + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + +
+
+ + + + + + + + + + + + + +

Updating Friendica#

+

Using a Friendica archive#

+

If you installed Friendica in the path/to/friendica folder:

+
    +
  1. Unpack the new Friendica archive in path/to/friendica_new.
  2. +
  3. Copy the following items from path/to/friendica to path/to/friendica_new:
  4. +
  5. config/local.config.php
  6. +
  7. proxy/
    +The following items only need to be copied if they are located inside your friendica path:
  8. +
  9. your storage folder as set in Admin -> Site -> File Upload -> Storage base path
  10. +
  11. your item cache as set in Admin -> Site -> Performance -> Path to item cache
  12. +
  13. your temp folder as set in Admin -> Site -> Advanced -> Temp path
  14. +
  15. Rename the path/to/friendica folder to path/to/friendica_old.
  16. +
  17. Rename the path/to/friendica_new folder to path/to/friendica.
  18. +
  19. Check your site. Note: it may go into maintenance mode to update the database schema.
  20. +
  21. If everything works, just delete the path/to/friendica_old folder.
  22. +
+

To update Addons from an archive, simply delete the path/to/friendica/addon and replace it with the provided archive.

+

Using Git#

+

You can get the latest changes at any time with

+
cd path/to/friendica
+git pull
+bin/composer.phar install --no-dev
+
+

The addon tree has to be updated separately like so:

+
cd path/to/friendica/addon
+git pull
+
+

For both repositories: +The default branch to use is the stable branch, which is the stable version of Friendica. +It is updated about four times a year on a fixed schedule.

+

If you want to use and test bleeding edge code please check out the develop branch. +The new features and fixes will be merged from develop into stable after a release candidate period before each release.

+

Warning: The develop branch is unstable, and breaks on average once a month for at most 24 hours until a patch is submitted and merged. +Be sure to pull frequently if you choose the develop branch.

+

Considerations before upgrading Friendica#

+

MySQL >= 5.7.4#

+

Starting from MySQL version 5.7.4, the IGNORE keyword in ALTER TABLE statements is ignored. +This prevents automatic table deduplication if a UNIQUE index is added to a Friendica table's structure. +If a DB update fails for you while creating a UNIQUE index, make sure to manually deduplicate the table before trying the update again.

+

Manual deduplication#

+

There are two main ways of doing it, either by manually removing the duplicates or by recreating the table. +Manually removing the duplicates is usually faster if they're not too numerous. +To manually remove the duplicates, you need to know the UNIQUE index columns available in database.sql.

+
SELECT GROUP_CONCAT(id), <index columns>, count(*) as count FROM users
+GROUP BY <index columns> HAVING count >= 2;
+
+/* delete or merge duplicate from above query */;
+
+

If there are too many rows to handle manually, you can create a new table with the same structure as the table with duplicates and insert the existing content with INSERT IGNORE. +To recreate the table you need to know the table structure available in database.sql.

+
CREATE TABLE <table_name>_new <rest of the CREATE TABLE>;
+INSERT IGNORE INTO <table_name>_new SELECT * FROM <table_name>;
+DROP TABLE <table_name>;
+RENAME TABLE <table_name>_new TO <table_name>;
+
+

This method is slower overall, but it is better suited for large numbers of duplicates.

+

Resolving Possible Database Issues Post Upgrading#

+

Foreign Keys#

+

Some updates include the use of foreign keys now that will bump into issues with previous versions, which would sometimes shove bad data into tables, preventing, causing errors such as below.

+
Error 1452 occurred during database update:
+Cannot add or update a child row: a foreign key constraint fails (`friendica`.`#sql-10ea6_5a6d`, CONSTRAINT `#sql-10ea6_5a6d_ibfk_1` FOREIGN KEY (`contact-id`) REFERENCES `contact` (`id`))
+ALTER TABLE `thread` ADD FOREIGN KEY (`iid`) REFERENCES `item` (`id`) ON UPDATE RESTRICT ON DELETE CASCADE; 
+
+

All current known fixes for possible items that can go wrong are as below.

+
DELETE FROM `item` WHERE `owner-id` NOT IN (SELECT `id` FROM `contact`);
+DELETE FROM `item` WHERE `contact-id` NOT IN (SELECT `id` FROM `contact`);
+DELETE FROM `notify` WHERE `uri-id` NOT IN (SELECT `id` FROM `item-uri`);
+DELETE FROM `photo` WHERE `contact-id` NOT IN (SELECT `id` FROM `contact`);
+DELETE FROM `thread` WHERE `iid` NOT IN (SELECT `id` FROM `item`);
+DELETE FROM `item` WHERE `author-id` NOT IN (SELECT `id` FROM `contact`);
+DELETE FROM `diaspora-interaction` WHERE `uri-id` NOT IN (SELECT `id` FROM `item-uri`);
+
+

This all has been compiled as of currently from issue #9746, #9753, and #9878.

+ + +
+ +
+
+ + + +
+ + + +
+
+
+
+ + + + + + + + \ No newline at end of file diff --git a/develop/assets/images/acl_win.png b/develop/assets/images/acl_win.png new file mode 100644 index 0000000..559493d Binary files /dev/null and b/develop/assets/images/acl_win.png differ diff --git a/develop/assets/images/camera.png b/develop/assets/images/camera.png new file mode 100644 index 0000000..89acdbd Binary files /dev/null and b/develop/assets/images/camera.png differ diff --git a/develop/assets/images/chain.png b/develop/assets/images/chain.png new file mode 100644 index 0000000..6537c07 Binary files /dev/null and b/develop/assets/images/chain.png differ diff --git a/develop/assets/images/darkbubble.png b/develop/assets/images/darkbubble.png new file mode 100644 index 0000000..08c7a93 Binary files /dev/null and b/develop/assets/images/darkbubble.png differ diff --git a/develop/assets/images/darkzero.png b/develop/assets/images/darkzero.png new file mode 100644 index 0000000..00dc3ee Binary files /dev/null and b/develop/assets/images/darkzero.png differ diff --git a/develop/assets/images/diabook.png b/develop/assets/images/diabook.png new file mode 100644 index 0000000..1a1f8d9 Binary files /dev/null and b/develop/assets/images/diabook.png differ diff --git a/develop/assets/images/dispy.png b/develop/assets/images/dispy.png new file mode 100644 index 0000000..476fa33 Binary files /dev/null and b/develop/assets/images/dispy.png differ diff --git a/develop/assets/images/editor_darkbubble.png b/develop/assets/images/editor_darkbubble.png new file mode 100644 index 0000000..d766648 Binary files /dev/null and b/develop/assets/images/editor_darkbubble.png differ diff --git a/develop/assets/images/editor_dpzero.png b/develop/assets/images/editor_dpzero.png new file mode 100644 index 0000000..79f0cb3 Binary files /dev/null and b/develop/assets/images/editor_dpzero.png differ diff --git a/develop/assets/images/editor_frio.png b/develop/assets/images/editor_frio.png new file mode 100644 index 0000000..d969f26 Binary files /dev/null and b/develop/assets/images/editor_frio.png differ diff --git a/develop/assets/images/editor_vier.png b/develop/assets/images/editor_vier.png new file mode 100644 index 0000000..7ffac7f Binary files /dev/null and b/develop/assets/images/editor_vier.png differ diff --git a/develop/assets/images/editor_zero.png b/develop/assets/images/editor_zero.png new file mode 100644 index 0000000..a1ee37b Binary files /dev/null and b/develop/assets/images/editor_zero.png differ diff --git a/develop/assets/images/favicon.png b/develop/assets/images/favicon.png new file mode 100644 index 0000000..1cf13b9 Binary files /dev/null and b/develop/assets/images/favicon.png differ diff --git a/develop/assets/images/friendica-32.png b/develop/assets/images/friendica-32.png new file mode 100644 index 0000000..e025e4c Binary files /dev/null and b/develop/assets/images/friendica-32.png differ diff --git a/develop/assets/images/friendica.svg b/develop/assets/images/friendica.svg new file mode 100644 index 0000000..180fe2a --- /dev/null +++ b/develop/assets/images/friendica.svg @@ -0,0 +1,4 @@ + + + + diff --git a/develop/assets/images/friendica_rich_editor.png b/develop/assets/images/friendica_rich_editor.png new file mode 100644 index 0000000..170b8ec Binary files /dev/null and b/develop/assets/images/friendica_rich_editor.png differ diff --git a/develop/assets/images/frio_location.png b/develop/assets/images/frio_location.png new file mode 100644 index 0000000..8850c80 Binary files /dev/null and b/develop/assets/images/frio_location.png differ diff --git a/develop/assets/images/globe.png b/develop/assets/images/globe.png new file mode 100644 index 0000000..7a563ab Binary files /dev/null and b/develop/assets/images/globe.png differ diff --git a/develop/assets/images/lock.png b/develop/assets/images/lock.png new file mode 100644 index 0000000..b9a1cef Binary files /dev/null and b/develop/assets/images/lock.png differ diff --git a/develop/assets/images/mic.png b/develop/assets/images/mic.png new file mode 100644 index 0000000..c8d4c0e Binary files /dev/null and b/develop/assets/images/mic.png differ diff --git a/develop/assets/images/padlock.png b/develop/assets/images/padlock.png new file mode 100644 index 0000000..40f60cf Binary files /dev/null and b/develop/assets/images/padlock.png differ diff --git a/develop/assets/images/paper_clip.png b/develop/assets/images/paper_clip.png new file mode 100644 index 0000000..97aea40 Binary files /dev/null and b/develop/assets/images/paper_clip.png differ diff --git a/develop/assets/images/post_categorize.png b/develop/assets/images/post_categorize.png new file mode 100644 index 0000000..86aee53 Binary files /dev/null and b/develop/assets/images/post_categorize.png differ diff --git a/develop/assets/images/post_choose.png b/develop/assets/images/post_choose.png new file mode 100644 index 0000000..762cea8 Binary files /dev/null and b/develop/assets/images/post_choose.png differ diff --git a/develop/assets/images/post_delete.png b/develop/assets/images/post_delete.png new file mode 100644 index 0000000..3d0a92e Binary files /dev/null and b/develop/assets/images/post_delete.png differ diff --git a/develop/assets/images/post_link.png b/develop/assets/images/post_link.png new file mode 100644 index 0000000..cd7df78 Binary files /dev/null and b/develop/assets/images/post_link.png differ diff --git a/develop/assets/images/post_mark.png b/develop/assets/images/post_mark.png new file mode 100644 index 0000000..7781b8c Binary files /dev/null and b/develop/assets/images/post_mark.png differ diff --git a/develop/assets/images/post_share.png b/develop/assets/images/post_share.png new file mode 100644 index 0000000..a75ce2a Binary files /dev/null and b/develop/assets/images/post_share.png differ diff --git a/develop/assets/images/post_tag.png b/develop/assets/images/post_tag.png new file mode 100644 index 0000000..fa9fca6 Binary files /dev/null and b/develop/assets/images/post_tag.png differ diff --git a/develop/assets/images/post_thumbs_down.png b/develop/assets/images/post_thumbs_down.png new file mode 100644 index 0000000..0117779 Binary files /dev/null and b/develop/assets/images/post_thumbs_down.png differ diff --git a/develop/assets/images/post_thumbs_up.png b/develop/assets/images/post_thumbs_up.png new file mode 100644 index 0000000..aea54ab Binary files /dev/null and b/develop/assets/images/post_thumbs_up.png differ diff --git a/develop/assets/images/posts_define.png b/develop/assets/images/posts_define.png new file mode 100644 index 0000000..1d2cb08 Binary files /dev/null and b/develop/assets/images/posts_define.png differ diff --git a/develop/assets/images/video.png b/develop/assets/images/video.png new file mode 100644 index 0000000..3ee2d12 Binary files /dev/null and b/develop/assets/images/video.png differ diff --git a/develop/assets/images/vier_icons.png b/develop/assets/images/vier_icons.png new file mode 100644 index 0000000..b880e95 Binary files /dev/null and b/develop/assets/images/vier_icons.png differ diff --git a/develop/assets/javascripts/bundle.0238f547.min.js b/develop/assets/javascripts/bundle.0238f547.min.js new file mode 100644 index 0000000..c95c932 --- /dev/null +++ b/develop/assets/javascripts/bundle.0238f547.min.js @@ -0,0 +1,29 @@ +"use strict";(()=>{var ra=Object.create;var xr=Object.defineProperty;var na=Object.getOwnPropertyDescriptor;var oa=Object.getOwnPropertyNames,kt=Object.getOwnPropertySymbols,ia=Object.getPrototypeOf,Sr=Object.prototype.hasOwnProperty,sn=Object.prototype.propertyIsEnumerable;var an=(e,t,r)=>t in e?xr(e,t,{enumerable:!0,configurable:!0,writable:!0,value:r}):e[t]=r,U=(e,t)=>{for(var r in t||(t={}))Sr.call(t,r)&&an(e,r,t[r]);if(kt)for(var r of kt(t))sn.call(t,r)&&an(e,r,t[r]);return e};var cn=(e,t)=>{var r={};for(var n in e)Sr.call(e,n)&&t.indexOf(n)<0&&(r[n]=e[n]);if(e!=null&&kt)for(var n of kt(e))t.indexOf(n)<0&&sn.call(e,n)&&(r[n]=e[n]);return r};var gt=(e,t)=>()=>(t||e((t={exports:{}}).exports,t),t.exports);var aa=(e,t,r,n)=>{if(t&&typeof t=="object"||typeof t=="function")for(let o of oa(t))!Sr.call(e,o)&&o!==r&&xr(e,o,{get:()=>t[o],enumerable:!(n=na(t,o))||n.enumerable});return e};var Ye=(e,t,r)=>(r=e!=null?ra(ia(e)):{},aa(t||!e||!e.__esModule?xr(r,"default",{value:e,enumerable:!0}):r,e));var un=gt((wr,fn)=>{(function(e,t){typeof wr=="object"&&typeof fn!="undefined"?t():typeof define=="function"&&define.amd?define(t):t()})(wr,function(){"use strict";function e(r){var n=!0,o=!1,i=null,a={text:!0,search:!0,url:!0,tel:!0,email:!0,password:!0,number:!0,date:!0,month:!0,week:!0,time:!0,datetime:!0,"datetime-local":!0};function s(w){return!!(w&&w!==document&&w.nodeName!=="HTML"&&w.nodeName!=="BODY"&&"classList"in w&&"contains"in w.classList)}function c(w){var Ue=w.type,He=w.tagName;return!!(He==="INPUT"&&a[Ue]&&!w.readOnly||He==="TEXTAREA"&&!w.readOnly||w.isContentEditable)}function f(w){w.classList.contains("focus-visible")||(w.classList.add("focus-visible"),w.setAttribute("data-focus-visible-added",""))}function u(w){!w.hasAttribute("data-focus-visible-added")||(w.classList.remove("focus-visible"),w.removeAttribute("data-focus-visible-added"))}function p(w){w.metaKey||w.altKey||w.ctrlKey||(s(r.activeElement)&&f(r.activeElement),n=!0)}function l(w){n=!1}function d(w){!s(w.target)||(n||c(w.target))&&f(w.target)}function h(w){!s(w.target)||(w.target.classList.contains("focus-visible")||w.target.hasAttribute("data-focus-visible-added"))&&(o=!0,window.clearTimeout(i),i=window.setTimeout(function(){o=!1},100),u(w.target))}function b(w){document.visibilityState==="hidden"&&(o&&(n=!0),F())}function F(){document.addEventListener("mousemove",W),document.addEventListener("mousedown",W),document.addEventListener("mouseup",W),document.addEventListener("pointermove",W),document.addEventListener("pointerdown",W),document.addEventListener("pointerup",W),document.addEventListener("touchmove",W),document.addEventListener("touchstart",W),document.addEventListener("touchend",W)}function G(){document.removeEventListener("mousemove",W),document.removeEventListener("mousedown",W),document.removeEventListener("mouseup",W),document.removeEventListener("pointermove",W),document.removeEventListener("pointerdown",W),document.removeEventListener("pointerup",W),document.removeEventListener("touchmove",W),document.removeEventListener("touchstart",W),document.removeEventListener("touchend",W)}function W(w){w.target.nodeName&&w.target.nodeName.toLowerCase()==="html"||(n=!1,G())}document.addEventListener("keydown",p,!0),document.addEventListener("mousedown",l,!0),document.addEventListener("pointerdown",l,!0),document.addEventListener("touchstart",l,!0),document.addEventListener("visibilitychange",b,!0),F(),r.addEventListener("focus",d,!0),r.addEventListener("blur",h,!0),r.nodeType===Node.DOCUMENT_FRAGMENT_NODE&&r.host?r.host.setAttribute("data-js-focus-visible",""):r.nodeType===Node.DOCUMENT_NODE&&(document.documentElement.classList.add("js-focus-visible"),document.documentElement.setAttribute("data-js-focus-visible",""))}if(typeof window!="undefined"&&typeof document!="undefined"){window.applyFocusVisiblePolyfill=e;var t;try{t=new CustomEvent("focus-visible-polyfill-ready")}catch(r){t=document.createEvent("CustomEvent"),t.initCustomEvent("focus-visible-polyfill-ready",!1,!1,{})}window.dispatchEvent(t)}typeof document!="undefined"&&e(document)})});var pn=gt(Er=>{(function(e){var t=function(){try{return!!Symbol.iterator}catch(f){return!1}},r=t(),n=function(f){var u={next:function(){var p=f.shift();return{done:p===void 0,value:p}}};return r&&(u[Symbol.iterator]=function(){return u}),u},o=function(f){return encodeURIComponent(f).replace(/%20/g,"+")},i=function(f){return decodeURIComponent(String(f).replace(/\+/g," "))},a=function(){var f=function(p){Object.defineProperty(this,"_entries",{writable:!0,value:{}});var l=typeof p;if(l!=="undefined")if(l==="string")p!==""&&this._fromString(p);else if(p instanceof f){var d=this;p.forEach(function(G,W){d.append(W,G)})}else if(p!==null&&l==="object")if(Object.prototype.toString.call(p)==="[object Array]")for(var h=0;hd[0]?1:0}),f._entries&&(f._entries={});for(var p=0;p1?i(d[1]):"")}})})(typeof global!="undefined"?global:typeof window!="undefined"?window:typeof self!="undefined"?self:Er);(function(e){var t=function(){try{var o=new e.URL("b","http://a");return o.pathname="c d",o.href==="http://a/c%20d"&&o.searchParams}catch(i){return!1}},r=function(){var o=e.URL,i=function(c,f){typeof c!="string"&&(c=String(c)),f&&typeof f!="string"&&(f=String(f));var u=document,p;if(f&&(e.location===void 0||f!==e.location.href)){f=f.toLowerCase(),u=document.implementation.createHTMLDocument(""),p=u.createElement("base"),p.href=f,u.head.appendChild(p);try{if(p.href.indexOf(f)!==0)throw new Error(p.href)}catch(w){throw new Error("URL unable to set base "+f+" due to "+w)}}var l=u.createElement("a");l.href=c,p&&(u.body.appendChild(l),l.href=l.href);var d=u.createElement("input");if(d.type="url",d.value=c,l.protocol===":"||!/:/.test(l.href)||!d.checkValidity()&&!f)throw new TypeError("Invalid URL");Object.defineProperty(this,"_anchorElement",{value:l});var h=new e.URLSearchParams(this.search),b=!0,F=!0,G=this;["append","delete","set"].forEach(function(w){var Ue=h[w];h[w]=function(){Ue.apply(h,arguments),b&&(F=!1,G.search=h.toString(),F=!0)}}),Object.defineProperty(this,"searchParams",{value:h,enumerable:!0});var W=void 0;Object.defineProperty(this,"_updateSearchParams",{enumerable:!1,configurable:!1,writable:!1,value:function(){this.search!==W&&(W=this.search,F&&(b=!1,this.searchParams._fromString(this.search),b=!0))}})},a=i.prototype,s=function(c){Object.defineProperty(a,c,{get:function(){return this._anchorElement[c]},set:function(f){this._anchorElement[c]=f},enumerable:!0})};["hash","host","hostname","port","protocol"].forEach(function(c){s(c)}),Object.defineProperty(a,"search",{get:function(){return this._anchorElement.search},set:function(c){this._anchorElement.search=c,this._updateSearchParams()},enumerable:!0}),Object.defineProperties(a,{toString:{get:function(){var c=this;return function(){return c.href}}},href:{get:function(){return this._anchorElement.href.replace(/\?$/,"")},set:function(c){this._anchorElement.href=c,this._updateSearchParams()},enumerable:!0},pathname:{get:function(){return this._anchorElement.pathname.replace(/(^\/?)/,"/")},set:function(c){this._anchorElement.pathname=c},enumerable:!0},origin:{get:function(){var c={"http:":80,"https:":443,"ftp:":21}[this._anchorElement.protocol],f=this._anchorElement.port!=c&&this._anchorElement.port!=="";return this._anchorElement.protocol+"//"+this._anchorElement.hostname+(f?":"+this._anchorElement.port:"")},enumerable:!0},password:{get:function(){return""},set:function(c){},enumerable:!0},username:{get:function(){return""},set:function(c){},enumerable:!0}}),i.createObjectURL=function(c){return o.createObjectURL.apply(o,arguments)},i.revokeObjectURL=function(c){return o.revokeObjectURL.apply(o,arguments)},e.URL=i};if(t()||r(),e.location!==void 0&&!("origin"in e.location)){var n=function(){return e.location.protocol+"//"+e.location.hostname+(e.location.port?":"+e.location.port:"")};try{Object.defineProperty(e.location,"origin",{get:n,enumerable:!0})}catch(o){setInterval(function(){e.location.origin=n()},100)}}})(typeof global!="undefined"?global:typeof window!="undefined"?window:typeof self!="undefined"?self:Er)});var kn=gt((Ds,It)=>{/*! ***************************************************************************** +Copyright (c) Microsoft Corporation. + +Permission to use, copy, modify, and/or distribute this software for any +purpose with or without fee is hereby granted. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH +REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY +AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, +INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM +LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR +OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR +PERFORMANCE OF THIS SOFTWARE. +***************************************************************************** */var ln,mn,dn,hn,bn,vn,gn,yn,xn,Ht,Or,Sn,wn,En,tt,On,_n,Tn,Mn,Ln,An,Cn,Rn,Pt;(function(e){var t=typeof global=="object"?global:typeof self=="object"?self:typeof this=="object"?this:{};typeof define=="function"&&define.amd?define("tslib",["exports"],function(n){e(r(t,r(n)))}):typeof It=="object"&&typeof It.exports=="object"?e(r(t,r(It.exports))):e(r(t));function r(n,o){return n!==t&&(typeof Object.create=="function"?Object.defineProperty(n,"__esModule",{value:!0}):n.__esModule=!0),function(i,a){return n[i]=o?o(i,a):a}}})(function(e){var t=Object.setPrototypeOf||{__proto__:[]}instanceof Array&&function(n,o){n.__proto__=o}||function(n,o){for(var i in o)Object.prototype.hasOwnProperty.call(o,i)&&(n[i]=o[i])};ln=function(n,o){if(typeof o!="function"&&o!==null)throw new TypeError("Class extends value "+String(o)+" is not a constructor or null");t(n,o);function i(){this.constructor=n}n.prototype=o===null?Object.create(o):(i.prototype=o.prototype,new i)},mn=Object.assign||function(n){for(var o,i=1,a=arguments.length;i=0;u--)(f=n[u])&&(c=(s<3?f(c):s>3?f(o,i,c):f(o,i))||c);return s>3&&c&&Object.defineProperty(o,i,c),c},bn=function(n,o){return function(i,a){o(i,a,n)}},vn=function(n,o){if(typeof Reflect=="object"&&typeof Reflect.metadata=="function")return Reflect.metadata(n,o)},gn=function(n,o,i,a){function s(c){return c instanceof i?c:new i(function(f){f(c)})}return new(i||(i=Promise))(function(c,f){function u(d){try{l(a.next(d))}catch(h){f(h)}}function p(d){try{l(a.throw(d))}catch(h){f(h)}}function l(d){d.done?c(d.value):s(d.value).then(u,p)}l((a=a.apply(n,o||[])).next())})},yn=function(n,o){var i={label:0,sent:function(){if(c[0]&1)throw c[1];return c[1]},trys:[],ops:[]},a,s,c,f;return f={next:u(0),throw:u(1),return:u(2)},typeof Symbol=="function"&&(f[Symbol.iterator]=function(){return this}),f;function u(l){return function(d){return p([l,d])}}function p(l){if(a)throw new TypeError("Generator is already executing.");for(;i;)try{if(a=1,s&&(c=l[0]&2?s.return:l[0]?s.throw||((c=s.return)&&c.call(s),0):s.next)&&!(c=c.call(s,l[1])).done)return c;switch(s=0,c&&(l=[l[0]&2,c.value]),l[0]){case 0:case 1:c=l;break;case 4:return i.label++,{value:l[1],done:!1};case 5:i.label++,s=l[1],l=[0];continue;case 7:l=i.ops.pop(),i.trys.pop();continue;default:if(c=i.trys,!(c=c.length>0&&c[c.length-1])&&(l[0]===6||l[0]===2)){i=0;continue}if(l[0]===3&&(!c||l[1]>c[0]&&l[1]=n.length&&(n=void 0),{value:n&&n[a++],done:!n}}};throw new TypeError(o?"Object is not iterable.":"Symbol.iterator is not defined.")},Or=function(n,o){var i=typeof Symbol=="function"&&n[Symbol.iterator];if(!i)return n;var a=i.call(n),s,c=[],f;try{for(;(o===void 0||o-- >0)&&!(s=a.next()).done;)c.push(s.value)}catch(u){f={error:u}}finally{try{s&&!s.done&&(i=a.return)&&i.call(a)}finally{if(f)throw f.error}}return c},Sn=function(){for(var n=[],o=0;o1||u(b,F)})})}function u(b,F){try{p(a[b](F))}catch(G){h(c[0][3],G)}}function p(b){b.value instanceof tt?Promise.resolve(b.value.v).then(l,d):h(c[0][2],b)}function l(b){u("next",b)}function d(b){u("throw",b)}function h(b,F){b(F),c.shift(),c.length&&u(c[0][0],c[0][1])}},_n=function(n){var o,i;return o={},a("next"),a("throw",function(s){throw s}),a("return"),o[Symbol.iterator]=function(){return this},o;function a(s,c){o[s]=n[s]?function(f){return(i=!i)?{value:tt(n[s](f)),done:s==="return"}:c?c(f):f}:c}},Tn=function(n){if(!Symbol.asyncIterator)throw new TypeError("Symbol.asyncIterator is not defined.");var o=n[Symbol.asyncIterator],i;return o?o.call(n):(n=typeof Ht=="function"?Ht(n):n[Symbol.iterator](),i={},a("next"),a("throw"),a("return"),i[Symbol.asyncIterator]=function(){return this},i);function a(c){i[c]=n[c]&&function(f){return new Promise(function(u,p){f=n[c](f),s(u,p,f.done,f.value)})}}function s(c,f,u,p){Promise.resolve(p).then(function(l){c({value:l,done:u})},f)}},Mn=function(n,o){return Object.defineProperty?Object.defineProperty(n,"raw",{value:o}):n.raw=o,n};var r=Object.create?function(n,o){Object.defineProperty(n,"default",{enumerable:!0,value:o})}:function(n,o){n.default=o};Ln=function(n){if(n&&n.__esModule)return n;var o={};if(n!=null)for(var i in n)i!=="default"&&Object.prototype.hasOwnProperty.call(n,i)&&Pt(o,n,i);return r(o,n),o},An=function(n){return n&&n.__esModule?n:{default:n}},Cn=function(n,o,i,a){if(i==="a"&&!a)throw new TypeError("Private accessor was defined without a getter");if(typeof o=="function"?n!==o||!a:!o.has(n))throw new TypeError("Cannot read private member from an object whose class did not declare it");return i==="m"?a:i==="a"?a.call(n):a?a.value:o.get(n)},Rn=function(n,o,i,a,s){if(a==="m")throw new TypeError("Private method is not writable");if(a==="a"&&!s)throw new TypeError("Private accessor was defined without a setter");if(typeof o=="function"?n!==o||!s:!o.has(n))throw new TypeError("Cannot write private member to an object whose class did not declare it");return a==="a"?s.call(n,i):s?s.value=i:o.set(n,i),i},e("__extends",ln),e("__assign",mn),e("__rest",dn),e("__decorate",hn),e("__param",bn),e("__metadata",vn),e("__awaiter",gn),e("__generator",yn),e("__exportStar",xn),e("__createBinding",Pt),e("__values",Ht),e("__read",Or),e("__spread",Sn),e("__spreadArrays",wn),e("__spreadArray",En),e("__await",tt),e("__asyncGenerator",On),e("__asyncDelegator",_n),e("__asyncValues",Tn),e("__makeTemplateObject",Mn),e("__importStar",Ln),e("__importDefault",An),e("__classPrivateFieldGet",Cn),e("__classPrivateFieldSet",Rn)})});var Kr=gt((Lt,Yr)=>{/*! + * clipboard.js v2.0.11 + * https://clipboardjs.com/ + * + * Licensed MIT © Zeno Rocha + */(function(t,r){typeof Lt=="object"&&typeof Yr=="object"?Yr.exports=r():typeof define=="function"&&define.amd?define([],r):typeof Lt=="object"?Lt.ClipboardJS=r():t.ClipboardJS=r()})(Lt,function(){return function(){var e={686:function(n,o,i){"use strict";i.d(o,{default:function(){return ta}});var a=i(279),s=i.n(a),c=i(370),f=i.n(c),u=i(817),p=i.n(u);function l(I){try{return document.execCommand(I)}catch(E){return!1}}var d=function(E){var S=p()(E);return l("cut"),S},h=d;function b(I){var E=document.documentElement.getAttribute("dir")==="rtl",S=document.createElement("textarea");S.style.fontSize="12pt",S.style.border="0",S.style.padding="0",S.style.margin="0",S.style.position="absolute",S.style[E?"right":"left"]="-9999px";var R=window.pageYOffset||document.documentElement.scrollTop;return S.style.top="".concat(R,"px"),S.setAttribute("readonly",""),S.value=I,S}var F=function(E,S){var R=b(E);S.container.appendChild(R);var H=p()(R);return l("copy"),R.remove(),H},G=function(E){var S=arguments.length>1&&arguments[1]!==void 0?arguments[1]:{container:document.body},R="";return typeof E=="string"?R=F(E,S):E instanceof HTMLInputElement&&!["text","search","url","tel","password"].includes(E==null?void 0:E.type)?R=F(E.value,S):(R=p()(E),l("copy")),R},W=G;function w(I){return typeof Symbol=="function"&&typeof Symbol.iterator=="symbol"?w=function(S){return typeof S}:w=function(S){return S&&typeof Symbol=="function"&&S.constructor===Symbol&&S!==Symbol.prototype?"symbol":typeof S},w(I)}var Ue=function(){var E=arguments.length>0&&arguments[0]!==void 0?arguments[0]:{},S=E.action,R=S===void 0?"copy":S,H=E.container,z=E.target,Oe=E.text;if(R!=="copy"&&R!=="cut")throw new Error('Invalid "action" value, use either "copy" or "cut"');if(z!==void 0)if(z&&w(z)==="object"&&z.nodeType===1){if(R==="copy"&&z.hasAttribute("disabled"))throw new Error('Invalid "target" attribute. Please use "readonly" instead of "disabled" attribute');if(R==="cut"&&(z.hasAttribute("readonly")||z.hasAttribute("disabled")))throw new Error(`Invalid "target" attribute. You can't cut text from elements with "readonly" or "disabled" attributes`)}else throw new Error('Invalid "target" value, use a valid Element');if(Oe)return W(Oe,{container:H});if(z)return R==="cut"?h(z):W(z,{container:H})},He=Ue;function Ce(I){return typeof Symbol=="function"&&typeof Symbol.iterator=="symbol"?Ce=function(S){return typeof S}:Ce=function(S){return S&&typeof Symbol=="function"&&S.constructor===Symbol&&S!==Symbol.prototype?"symbol":typeof S},Ce(I)}function Yi(I,E){if(!(I instanceof E))throw new TypeError("Cannot call a class as a function")}function on(I,E){for(var S=0;S0&&arguments[0]!==void 0?arguments[0]:{};this.action=typeof H.action=="function"?H.action:this.defaultAction,this.target=typeof H.target=="function"?H.target:this.defaultTarget,this.text=typeof H.text=="function"?H.text:this.defaultText,this.container=Ce(H.container)==="object"?H.container:document.body}},{key:"listenClick",value:function(H){var z=this;this.listener=f()(H,"click",function(Oe){return z.onClick(Oe)})}},{key:"onClick",value:function(H){var z=H.delegateTarget||H.currentTarget,Oe=this.action(z)||"copy",Rt=He({action:Oe,container:this.container,target:this.target(z),text:this.text(z)});this.emit(Rt?"success":"error",{action:Oe,text:Rt,trigger:z,clearSelection:function(){z&&z.focus(),window.getSelection().removeAllRanges()}})}},{key:"defaultAction",value:function(H){return yr("action",H)}},{key:"defaultTarget",value:function(H){var z=yr("target",H);if(z)return document.querySelector(z)}},{key:"defaultText",value:function(H){return yr("text",H)}},{key:"destroy",value:function(){this.listener.destroy()}}],[{key:"copy",value:function(H){var z=arguments.length>1&&arguments[1]!==void 0?arguments[1]:{container:document.body};return W(H,z)}},{key:"cut",value:function(H){return h(H)}},{key:"isSupported",value:function(){var H=arguments.length>0&&arguments[0]!==void 0?arguments[0]:["copy","cut"],z=typeof H=="string"?[H]:H,Oe=!!document.queryCommandSupported;return z.forEach(function(Rt){Oe=Oe&&!!document.queryCommandSupported(Rt)}),Oe}}]),S}(s()),ta=ea},828:function(n){var o=9;if(typeof Element!="undefined"&&!Element.prototype.matches){var i=Element.prototype;i.matches=i.matchesSelector||i.mozMatchesSelector||i.msMatchesSelector||i.oMatchesSelector||i.webkitMatchesSelector}function a(s,c){for(;s&&s.nodeType!==o;){if(typeof s.matches=="function"&&s.matches(c))return s;s=s.parentNode}}n.exports=a},438:function(n,o,i){var a=i(828);function s(u,p,l,d,h){var b=f.apply(this,arguments);return u.addEventListener(l,b,h),{destroy:function(){u.removeEventListener(l,b,h)}}}function c(u,p,l,d,h){return typeof u.addEventListener=="function"?s.apply(null,arguments):typeof l=="function"?s.bind(null,document).apply(null,arguments):(typeof u=="string"&&(u=document.querySelectorAll(u)),Array.prototype.map.call(u,function(b){return s(b,p,l,d,h)}))}function f(u,p,l,d){return function(h){h.delegateTarget=a(h.target,p),h.delegateTarget&&d.call(u,h)}}n.exports=c},879:function(n,o){o.node=function(i){return i!==void 0&&i instanceof HTMLElement&&i.nodeType===1},o.nodeList=function(i){var a=Object.prototype.toString.call(i);return i!==void 0&&(a==="[object NodeList]"||a==="[object HTMLCollection]")&&"length"in i&&(i.length===0||o.node(i[0]))},o.string=function(i){return typeof i=="string"||i instanceof String},o.fn=function(i){var a=Object.prototype.toString.call(i);return a==="[object Function]"}},370:function(n,o,i){var a=i(879),s=i(438);function c(l,d,h){if(!l&&!d&&!h)throw new Error("Missing required arguments");if(!a.string(d))throw new TypeError("Second argument must be a String");if(!a.fn(h))throw new TypeError("Third argument must be a Function");if(a.node(l))return f(l,d,h);if(a.nodeList(l))return u(l,d,h);if(a.string(l))return p(l,d,h);throw new TypeError("First argument must be a String, HTMLElement, HTMLCollection, or NodeList")}function f(l,d,h){return l.addEventListener(d,h),{destroy:function(){l.removeEventListener(d,h)}}}function u(l,d,h){return Array.prototype.forEach.call(l,function(b){b.addEventListener(d,h)}),{destroy:function(){Array.prototype.forEach.call(l,function(b){b.removeEventListener(d,h)})}}}function p(l,d,h){return s(document.body,l,d,h)}n.exports=c},817:function(n){function o(i){var a;if(i.nodeName==="SELECT")i.focus(),a=i.value;else if(i.nodeName==="INPUT"||i.nodeName==="TEXTAREA"){var s=i.hasAttribute("readonly");s||i.setAttribute("readonly",""),i.select(),i.setSelectionRange(0,i.value.length),s||i.removeAttribute("readonly"),a=i.value}else{i.hasAttribute("contenteditable")&&i.focus();var c=window.getSelection(),f=document.createRange();f.selectNodeContents(i),c.removeAllRanges(),c.addRange(f),a=c.toString()}return a}n.exports=o},279:function(n){function o(){}o.prototype={on:function(i,a,s){var c=this.e||(this.e={});return(c[i]||(c[i]=[])).push({fn:a,ctx:s}),this},once:function(i,a,s){var c=this;function f(){c.off(i,f),a.apply(s,arguments)}return f._=a,this.on(i,f,s)},emit:function(i){var a=[].slice.call(arguments,1),s=((this.e||(this.e={}))[i]||[]).slice(),c=0,f=s.length;for(c;c{"use strict";/*! + * escape-html + * Copyright(c) 2012-2013 TJ Holowaychuk + * Copyright(c) 2015 Andreas Lubbe + * Copyright(c) 2015 Tiancheng "Timothy" Gu + * MIT Licensed + */var Ss=/["'&<>]/;yi.exports=ws;function ws(e){var t=""+e,r=Ss.exec(t);if(!r)return t;var n,o="",i=0,a=0;for(i=r.index;i0},enumerable:!1,configurable:!0}),t.prototype._trySubscribe=function(r){return this._throwIfClosed(),e.prototype._trySubscribe.call(this,r)},t.prototype._subscribe=function(r){return this._throwIfClosed(),this._checkFinalizedStatuses(r),this._innerSubscribe(r)},t.prototype._innerSubscribe=function(r){var n=this,o=this,i=o.hasError,a=o.isStopped,s=o.observers;return i||a?_r:(this.currentObservers=null,s.push(r),new Re(function(){n.currentObservers=null,Pe(s,r)}))},t.prototype._checkFinalizedStatuses=function(r){var n=this,o=n.hasError,i=n.thrownError,a=n.isStopped;o?r.error(i):a&&r.complete()},t.prototype.asObservable=function(){var r=new $;return r.source=this,r},t.create=function(r,n){return new qn(r,n)},t}($);var qn=function(e){te(t,e);function t(r,n){var o=e.call(this)||this;return o.destination=r,o.source=n,o}return t.prototype.next=function(r){var n,o;(o=(n=this.destination)===null||n===void 0?void 0:n.next)===null||o===void 0||o.call(n,r)},t.prototype.error=function(r){var n,o;(o=(n=this.destination)===null||n===void 0?void 0:n.error)===null||o===void 0||o.call(n,r)},t.prototype.complete=function(){var r,n;(n=(r=this.destination)===null||r===void 0?void 0:r.complete)===null||n===void 0||n.call(r)},t.prototype._subscribe=function(r){var n,o;return(o=(n=this.source)===null||n===void 0?void 0:n.subscribe(r))!==null&&o!==void 0?o:_r},t}(_);var xt={now:function(){return(xt.delegate||Date).now()},delegate:void 0};var St=function(e){te(t,e);function t(r,n,o){r===void 0&&(r=1/0),n===void 0&&(n=1/0),o===void 0&&(o=xt);var i=e.call(this)||this;return i._bufferSize=r,i._windowTime=n,i._timestampProvider=o,i._buffer=[],i._infiniteTimeWindow=!0,i._infiniteTimeWindow=n===1/0,i._bufferSize=Math.max(1,r),i._windowTime=Math.max(1,n),i}return t.prototype.next=function(r){var n=this,o=n.isStopped,i=n._buffer,a=n._infiniteTimeWindow,s=n._timestampProvider,c=n._windowTime;o||(i.push(r),!a&&i.push(s.now()+c)),this._trimBuffer(),e.prototype.next.call(this,r)},t.prototype._subscribe=function(r){this._throwIfClosed(),this._trimBuffer();for(var n=this._innerSubscribe(r),o=this,i=o._infiniteTimeWindow,a=o._buffer,s=a.slice(),c=0;c0?e.prototype.requestAsyncId.call(this,r,n,o):(r.actions.push(this),r._scheduled||(r._scheduled=at.requestAnimationFrame(function(){return r.flush(void 0)})))},t.prototype.recycleAsyncId=function(r,n,o){if(o===void 0&&(o=0),o!=null&&o>0||o==null&&this.delay>0)return e.prototype.recycleAsyncId.call(this,r,n,o);r.actions.some(function(i){return i.id===n})||(at.cancelAnimationFrame(n),r._scheduled=void 0)},t}(Nt);var Kn=function(e){te(t,e);function t(){return e!==null&&e.apply(this,arguments)||this}return t.prototype.flush=function(r){this._active=!0;var n=this._scheduled;this._scheduled=void 0;var o=this.actions,i;r=r||o.shift();do if(i=r.execute(r.state,r.delay))break;while((r=o[0])&&r.id===n&&o.shift());if(this._active=!1,i){for(;(r=o[0])&&r.id===n&&o.shift();)r.unsubscribe();throw i}},t}(zt);var Te=new Kn(Yn);var k=new $(function(e){return e.complete()});function qt(e){return e&&T(e.schedule)}function kr(e){return e[e.length-1]}function De(e){return T(kr(e))?e.pop():void 0}function ye(e){return qt(kr(e))?e.pop():void 0}function Qt(e,t){return typeof kr(e)=="number"?e.pop():t}var st=function(e){return e&&typeof e.length=="number"&&typeof e!="function"};function Yt(e){return T(e==null?void 0:e.then)}function Kt(e){return T(e[it])}function Bt(e){return Symbol.asyncIterator&&T(e==null?void 0:e[Symbol.asyncIterator])}function Gt(e){return new TypeError("You provided "+(e!==null&&typeof e=="object"?"an invalid object":"'"+e+"'")+" where a stream was expected. You can provide an Observable, Promise, ReadableStream, Array, AsyncIterable, or Iterable.")}function ha(){return typeof Symbol!="function"||!Symbol.iterator?"@@iterator":Symbol.iterator}var Jt=ha();function Xt(e){return T(e==null?void 0:e[Jt])}function Zt(e){return In(this,arguments,function(){var r,n,o,i;return $t(this,function(a){switch(a.label){case 0:r=e.getReader(),a.label=1;case 1:a.trys.push([1,,9,10]),a.label=2;case 2:return[4,jt(r.read())];case 3:return n=a.sent(),o=n.value,i=n.done,i?[4,jt(void 0)]:[3,5];case 4:return[2,a.sent()];case 5:return[4,jt(o)];case 6:return[4,a.sent()];case 7:return a.sent(),[3,2];case 8:return[3,10];case 9:return r.releaseLock(),[7];case 10:return[2]}})})}function er(e){return T(e==null?void 0:e.getReader)}function N(e){if(e instanceof $)return e;if(e!=null){if(Kt(e))return ba(e);if(st(e))return va(e);if(Yt(e))return ga(e);if(Bt(e))return Bn(e);if(Xt(e))return ya(e);if(er(e))return xa(e)}throw Gt(e)}function ba(e){return new $(function(t){var r=e[it]();if(T(r.subscribe))return r.subscribe(t);throw new TypeError("Provided object does not correctly implement Symbol.observable")})}function va(e){return new $(function(t){for(var r=0;r=2,!0))}function ne(e){e===void 0&&(e={});var t=e.connector,r=t===void 0?function(){return new _}:t,n=e.resetOnError,o=n===void 0?!0:n,i=e.resetOnComplete,a=i===void 0?!0:i,s=e.resetOnRefCountZero,c=s===void 0?!0:s;return function(f){var u=null,p=null,l=null,d=0,h=!1,b=!1,F=function(){p==null||p.unsubscribe(),p=null},G=function(){F(),u=l=null,h=b=!1},W=function(){var w=u;G(),w==null||w.unsubscribe()};return v(function(w,Ue){d++,!b&&!h&&F();var He=l=l!=null?l:r();Ue.add(function(){d--,d===0&&!b&&!h&&(p=Ur(W,c))}),He.subscribe(Ue),u||(u=new ot({next:function(Ce){return He.next(Ce)},error:function(Ce){b=!0,F(),p=Ur(G,o,Ce),He.error(Ce)},complete:function(){h=!0,F(),p=Ur(G,a),He.complete()}}),ie(w).subscribe(u))})(f)}}function Ur(e,t){for(var r=[],n=2;ne.next(document)),e}function B(e,t=document){return Array.from(t.querySelectorAll(e))}function Q(e,t=document){let r=pe(e,t);if(typeof r=="undefined")throw new ReferenceError(`Missing element: expected "${e}" to be present`);return r}function pe(e,t=document){return t.querySelector(e)||void 0}function Ne(){return document.activeElement instanceof HTMLElement&&document.activeElement||void 0}function nr(e){return A(g(document.body,"focusin"),g(document.body,"focusout")).pipe(Xe(1),m(()=>{let t=Ne();return typeof t!="undefined"?e.contains(t):!1}),q(e===Ne()),K())}function ze(e){return{x:e.offsetLeft,y:e.offsetTop}}function vo(e){return A(g(window,"load"),g(window,"resize")).pipe($e(0,Te),m(()=>ze(e)),q(ze(e)))}function or(e){return{x:e.scrollLeft,y:e.scrollTop}}function pt(e){return A(g(e,"scroll"),g(window,"resize")).pipe($e(0,Te),m(()=>or(e)),q(or(e)))}var yo=function(){if(typeof Map!="undefined")return Map;function e(t,r){var n=-1;return t.some(function(o,i){return o[0]===r?(n=i,!0):!1}),n}return function(){function t(){this.__entries__=[]}return Object.defineProperty(t.prototype,"size",{get:function(){return this.__entries__.length},enumerable:!0,configurable:!0}),t.prototype.get=function(r){var n=e(this.__entries__,r),o=this.__entries__[n];return o&&o[1]},t.prototype.set=function(r,n){var o=e(this.__entries__,r);~o?this.__entries__[o][1]=n:this.__entries__.push([r,n])},t.prototype.delete=function(r){var n=this.__entries__,o=e(n,r);~o&&n.splice(o,1)},t.prototype.has=function(r){return!!~e(this.__entries__,r)},t.prototype.clear=function(){this.__entries__.splice(0)},t.prototype.forEach=function(r,n){n===void 0&&(n=null);for(var o=0,i=this.__entries__;o0},e.prototype.connect_=function(){!zr||this.connected_||(document.addEventListener("transitionend",this.onTransitionEnd_),window.addEventListener("resize",this.refresh),Va?(this.mutationsObserver_=new MutationObserver(this.refresh),this.mutationsObserver_.observe(document,{attributes:!0,childList:!0,characterData:!0,subtree:!0})):(document.addEventListener("DOMSubtreeModified",this.refresh),this.mutationEventsAdded_=!0),this.connected_=!0)},e.prototype.disconnect_=function(){!zr||!this.connected_||(document.removeEventListener("transitionend",this.onTransitionEnd_),window.removeEventListener("resize",this.refresh),this.mutationsObserver_&&this.mutationsObserver_.disconnect(),this.mutationEventsAdded_&&document.removeEventListener("DOMSubtreeModified",this.refresh),this.mutationsObserver_=null,this.mutationEventsAdded_=!1,this.connected_=!1)},e.prototype.onTransitionEnd_=function(t){var r=t.propertyName,n=r===void 0?"":r,o=Wa.some(function(i){return!!~n.indexOf(i)});o&&this.refresh()},e.getInstance=function(){return this.instance_||(this.instance_=new e),this.instance_},e.instance_=null,e}(),xo=function(e,t){for(var r=0,n=Object.keys(t);r0},e}(),wo=typeof WeakMap!="undefined"?new WeakMap:new yo,Eo=function(){function e(t){if(!(this instanceof e))throw new TypeError("Cannot call a class as a function.");if(!arguments.length)throw new TypeError("1 argument required, but only 0 present.");var r=Na.getInstance(),n=new Za(t,r,this);wo.set(this,n)}return e}();["observe","unobserve","disconnect"].forEach(function(e){Eo.prototype[e]=function(){var t;return(t=wo.get(this))[e].apply(t,arguments)}});var es=function(){return typeof ir.ResizeObserver!="undefined"?ir.ResizeObserver:Eo}(),Oo=es;var _o=new _,ts=j(()=>P(new Oo(e=>{for(let t of e)_o.next(t)}))).pipe(x(e=>A(xe,P(e)).pipe(C(()=>e.disconnect()))),X(1));function Ae(e){return{width:e.offsetWidth,height:e.offsetHeight}}function de(e){return ts.pipe(O(t=>t.observe(e)),x(t=>_o.pipe(M(({target:r})=>r===e),C(()=>t.unobserve(e)),m(()=>Ae(e)))),q(Ae(e)))}function mt(e){return{width:e.scrollWidth,height:e.scrollHeight}}var To=new _,rs=j(()=>P(new IntersectionObserver(e=>{for(let t of e)To.next(t)},{threshold:0}))).pipe(x(e=>A(xe,P(e)).pipe(C(()=>e.disconnect()))),X(1));function cr(e){return rs.pipe(O(t=>t.observe(e)),x(t=>To.pipe(M(({target:r})=>r===e),C(()=>t.unobserve(e)),m(({isIntersecting:r})=>r))))}function Mo(e,t=16){return pt(e).pipe(m(({y:r})=>{let n=Ae(e),o=mt(e);return r>=o.height-n.height-t}),K())}var fr={drawer:Q("[data-md-toggle=drawer]"),search:Q("[data-md-toggle=search]")};function Lo(e){return fr[e].checked}function qe(e,t){fr[e].checked!==t&&fr[e].click()}function dt(e){let t=fr[e];return g(t,"change").pipe(m(()=>t.checked),q(t.checked))}function ns(e,t){switch(e.constructor){case HTMLInputElement:return e.type==="radio"?/^Arrow/.test(t):!0;case HTMLSelectElement:case HTMLTextAreaElement:return!0;default:return e.isContentEditable}}function Ao(){return g(window,"keydown").pipe(M(e=>!(e.metaKey||e.ctrlKey)),m(e=>({mode:Lo("search")?"search":"global",type:e.key,claim(){e.preventDefault(),e.stopPropagation()}})),M(({mode:e,type:t})=>{if(e==="global"){let r=Ne();if(typeof r!="undefined")return!ns(r,t)}return!0}),ne())}function Se(){return new URL(location.href)}function ur(e){location.href=e.href}function Co(){return new _}function Ro(e,t){if(typeof t=="string"||typeof t=="number")e.innerHTML+=t.toString();else if(t instanceof Node)e.appendChild(t);else if(Array.isArray(t))for(let r of t)Ro(e,r)}function L(e,t,...r){let n=document.createElement(e);if(t)for(let o of Object.keys(t))typeof t[o]!="undefined"&&(typeof t[o]!="boolean"?n.setAttribute(o,t[o]):n.setAttribute(o,""));for(let o of r)Ro(n,o);return n}function ko(e,t){let r=t;if(e.length>r){for(;e[r]!==" "&&--r>0;);return`${e.substring(0,r)}...`}return e}function pr(e){if(e>999){let t=+((e-950)%1e3>99);return`${((e+1e-6)/1e3).toFixed(t)}k`}else return e.toString()}function Ho(){return location.hash.substring(1)}function Po(e){let t=L("a",{href:e});t.addEventListener("click",r=>r.stopPropagation()),t.click()}function os(){return g(window,"hashchange").pipe(m(Ho),q(Ho()),M(e=>e.length>0),X(1))}function Io(){return os().pipe(m(e=>pe(`[id="${e}"]`)),M(e=>typeof e!="undefined"))}function qr(e){let t=matchMedia(e);return rr(r=>t.addListener(()=>r(t.matches))).pipe(q(t.matches))}function $o(){let e=matchMedia("print");return A(g(window,"beforeprint").pipe(m(()=>!0)),g(window,"afterprint").pipe(m(()=>!1))).pipe(q(e.matches))}function Qr(e,t){return e.pipe(x(r=>r?t():k))}function lr(e,t={credentials:"same-origin"}){return ie(fetch(`${e}`,t)).pipe(ce(()=>k),x(r=>r.status!==200?Et(()=>new Error(r.statusText)):P(r)))}function ke(e,t){return lr(e,t).pipe(x(r=>r.json()),X(1))}function jo(e,t){let r=new DOMParser;return lr(e,t).pipe(x(n=>n.text()),m(n=>r.parseFromString(n,"text/xml")),X(1))}function Fo(e){let t=L("script",{src:e});return j(()=>(document.head.appendChild(t),A(g(t,"load"),g(t,"error").pipe(x(()=>Et(()=>new ReferenceError(`Invalid script: ${e}`))))).pipe(m(()=>{}),C(()=>document.head.removeChild(t)),re(1))))}function Uo(){return{x:Math.max(0,scrollX),y:Math.max(0,scrollY)}}function Do(){return A(g(window,"scroll",{passive:!0}),g(window,"resize",{passive:!0})).pipe(m(Uo),q(Uo()))}function Wo(){return{width:innerWidth,height:innerHeight}}function Vo(){return g(window,"resize",{passive:!0}).pipe(m(Wo),q(Wo()))}function No(){return Y([Do(),Vo()]).pipe(m(([e,t])=>({offset:e,size:t})),X(1))}function mr(e,{viewport$:t,header$:r}){let n=t.pipe(J("size")),o=Y([n,r]).pipe(m(()=>ze(e)));return Y([r,t,o]).pipe(m(([{height:i},{offset:a,size:s},{x:c,y:f}])=>({offset:{x:a.x-c,y:a.y-f+i},size:s})))}function zo(e,{tx$:t}){let r=g(e,"message").pipe(m(({data:n})=>n));return t.pipe(Mt(()=>r,{leading:!0,trailing:!0}),O(n=>e.postMessage(n)),x(()=>r),ne())}var is=Q("#__config"),ht=JSON.parse(is.textContent);ht.base=`${new URL(ht.base,Se())}`;function he(){return ht}function oe(e){return ht.features.includes(e)}function ee(e,t){return typeof t!="undefined"?ht.translations[e].replace("#",t.toString()):ht.translations[e]}function we(e,t=document){return Q(`[data-md-component=${e}]`,t)}function ae(e,t=document){return B(`[data-md-component=${e}]`,t)}var ti=Ye(Kr());function qo(e){return L("aside",{class:"md-annotation",tabIndex:0},L("div",{class:"md-annotation__inner md-tooltip"},L("div",{class:"md-tooltip__inner md-typeset"})),L("span",{class:"md-annotation__index"},L("span",{"data-md-annotation-id":e})))}function Qo(e){return L("button",{class:"md-clipboard md-icon",title:ee("clipboard.copy"),"data-clipboard-target":`#${e} > code`})}function Br(e,t){let r=t&2,n=t&1,o=Object.keys(e.terms).filter(a=>!e.terms[a]).reduce((a,s)=>[...a,L("del",null,s)," "],[]).slice(0,-1),i=new URL(e.location);return oe("search.highlight")&&i.searchParams.set("h",Object.entries(e.terms).filter(([,a])=>a).reduce((a,[s])=>`${a} ${s}`.trim(),"")),L("a",{href:`${i}`,class:"md-search-result__link",tabIndex:-1},L("article",{class:["md-search-result__article",...r?["md-search-result__article--document"]:[]].join(" "),"data-md-score":e.score.toFixed(2)},r>0&&L("div",{class:"md-search-result__icon md-icon"}),L("h1",{class:"md-search-result__title"},e.title),n>0&&e.text.length>0&&L("p",{class:"md-search-result__teaser"},ko(e.text,320)),e.tags&&e.tags.map(a=>L("span",{class:"md-tag"},a)),n>0&&o.length>0&&L("p",{class:"md-search-result__terms"},ee("search.result.term.missing"),": ",...o)))}function Yo(e){let t=e[0].score,r=[...e],n=r.findIndex(f=>!f.location.includes("#")),[o]=r.splice(n,1),i=r.findIndex(f=>f.scoreBr(f,1)),...s.length?[L("details",{class:"md-search-result__more"},L("summary",{tabIndex:-1},s.length>0&&s.length===1?ee("search.result.more.one"):ee("search.result.more.other",s.length)),...s.map(f=>Br(f,1)))]:[]];return L("li",{class:"md-search-result__item"},c)}function Ko(e){return L("ul",{class:"md-source__facts"},Object.entries(e).map(([t,r])=>L("li",{class:`md-source__fact md-source__fact--${t}`},typeof r=="number"?pr(r):r)))}function Gr(e){let t=`tabbed-control tabbed-control--${e}`;return L("div",{class:t,hidden:!0},L("button",{class:"tabbed-button",tabIndex:-1}))}function Bo(e){return L("div",{class:"md-typeset__scrollwrap"},L("div",{class:"md-typeset__table"},e))}function as(e){let t=he(),r=new URL(`../${e.version}/`,t.base);return L("li",{class:"md-version__item"},L("a",{href:`${r}`,class:"md-version__link"},e.title))}function Go(e,t){return L("div",{class:"md-version"},L("button",{class:"md-version__current","aria-label":ee("select.version.title")},t.title),L("ul",{class:"md-version__list"},e.map(as)))}function ss(e,t){let r=j(()=>Y([vo(e),pt(t)])).pipe(m(([{x:n,y:o},i])=>{let{width:a}=Ae(e);return{x:n-i.x+a/2,y:o-i.y}}));return nr(e).pipe(x(n=>r.pipe(m(o=>({active:n,offset:o})),re(+!n||1/0))))}function Jo(e,t){return j(()=>{let r=new _;r.subscribe({next({offset:a}){e.style.setProperty("--md-tooltip-x",`${a.x}px`),e.style.setProperty("--md-tooltip-y",`${a.y}px`)},complete(){e.style.removeProperty("--md-tooltip-x"),e.style.removeProperty("--md-tooltip-y")}});let n=r.pipe(fe(1));cr(e).pipe(Z(n)).subscribe(a=>{e.toggleAttribute("data-md-visible",a)}),r.pipe(Vr(500,Te),m(()=>t.getBoundingClientRect()),m(({x:a})=>a)).subscribe({next(a){a?e.style.setProperty("--md-tooltip-0",`${-a}px`):e.style.removeProperty("--md-tooltip-0")},complete(){e.style.removeProperty("--md-tooltip-0")}});let o=Q(":scope > :last-child",e),i=g(o,"mousedown",{once:!0});return r.pipe(x(({active:a})=>a?i:k),O(a=>a.preventDefault())).subscribe(()=>e.blur()),ss(e,t).pipe(O(a=>r.next(a)),C(()=>r.complete()),m(a=>U({ref:e},a)))})}function cs(e){let t=[];for(let r of B(".c, .c1, .cm",e)){let n,o=r.firstChild;if(o instanceof Text)for(;n=/\((\d+)\)/.exec(o.textContent);){let i=o.splitText(n.index);o=i.splitText(n[0].length),t.push(i)}}return t}function Xo(e,t){t.append(...Array.from(e.childNodes))}function Zo(e,t,{print$:r}){let n=new Map;for(let o of cs(t)){let[,i]=o.textContent.match(/\((\d+)\)/);pe(`li:nth-child(${i})`,e)&&(n.set(+i,qo(+i)),o.replaceWith(n.get(+i)))}return n.size===0?k:j(()=>{let o=new _;return r.pipe(Z(o.pipe(fe(1)))).subscribe(i=>{e.hidden=!i;for(let[a,s]of n){let c=Q(".md-typeset",s),f=Q(`li:nth-child(${a})`,e);i?Xo(c,f):Xo(f,c)}}),A(...[...n].map(([,i])=>Jo(i,t))).pipe(C(()=>o.complete()),ne())})}var fs=0;function ri(e){if(e.nextElementSibling){let t=e.nextElementSibling;if(t.tagName==="OL")return t;if(t.tagName==="P"&&!t.children.length)return ri(t)}}function ei(e){return de(e).pipe(m(({width:t})=>({scrollable:mt(e).width>t})),J("scrollable"))}function ni(e,t){let{matches:r}=matchMedia("(hover)"),n=j(()=>{let o=new _;if(o.subscribe(({scrollable:a})=>{a&&r?e.setAttribute("tabindex","0"):e.removeAttribute("tabindex")}),ti.default.isSupported()){let a=e.closest("pre");a.id=`__code_${++fs}`,a.insertBefore(Qo(a.id),e)}let i=e.closest(".highlight");if(i instanceof HTMLElement){let a=ri(i);if(typeof a!="undefined"&&(i.classList.contains("annotate")||oe("content.code.annotate"))){let s=Zo(a,e,t);return ei(e).pipe(O(c=>o.next(c)),C(()=>o.complete()),m(c=>U({ref:e},c)),Ze(de(i).pipe(Z(o.pipe(fe(1))),m(({width:c,height:f})=>c&&f),K(),x(c=>c?s:k))))}}return ei(e).pipe(O(a=>o.next(a)),C(()=>o.complete()),m(a=>U({ref:e},a)))});return cr(e).pipe(M(o=>o),re(1),x(()=>n))}var oi=".node circle,.node ellipse,.node path,.node polygon,.node rect{fill:var(--md-mermaid-node-bg-color);stroke:var(--md-mermaid-node-fg-color)}marker{fill:var(--md-mermaid-edge-color)!important}.edgeLabel .label rect{fill:transparent}.label{color:var(--md-mermaid-label-fg-color);font-family:var(--md-mermaid-font-family)}.label foreignObject{line-height:normal;overflow:visible}.label div .edgeLabel{color:var(--md-mermaid-label-fg-color)}.edgeLabel,.edgeLabel rect,.label div .edgeLabel{background-color:var(--md-mermaid-label-bg-color)}.edgeLabel,.edgeLabel rect{fill:var(--md-mermaid-label-bg-color);color:var(--md-mermaid-edge-color)}.edgePath .path,.flowchart-link{stroke:var(--md-mermaid-edge-color)}.edgePath .arrowheadPath{fill:var(--md-mermaid-edge-color);stroke:none}.cluster rect{fill:var(--md-default-fg-color--lightest);stroke:var(--md-default-fg-color--lighter)}.cluster span{color:var(--md-mermaid-label-fg-color);font-family:var(--md-mermaid-font-family)}defs #flowchart-circleEnd,defs #flowchart-circleStart,defs #flowchart-crossEnd,defs #flowchart-crossStart,defs #flowchart-pointEnd,defs #flowchart-pointStart{stroke:none}g.classGroup line,g.classGroup rect{fill:var(--md-mermaid-node-bg-color);stroke:var(--md-mermaid-node-fg-color)}g.classGroup text{fill:var(--md-mermaid-label-fg-color);font-family:var(--md-mermaid-font-family)}.classLabel .box{fill:var(--md-mermaid-label-bg-color);background-color:var(--md-mermaid-label-bg-color);opacity:1}.classLabel .label{fill:var(--md-mermaid-label-fg-color);font-family:var(--md-mermaid-font-family)}.node .divider{stroke:var(--md-mermaid-node-fg-color)}.relation{stroke:var(--md-mermaid-edge-color)}.cardinality{fill:var(--md-mermaid-label-fg-color);font-family:var(--md-mermaid-font-family)}.cardinality text{fill:inherit!important}defs #classDiagram-compositionEnd,defs #classDiagram-compositionStart,defs #classDiagram-dependencyEnd,defs #classDiagram-dependencyStart,defs #classDiagram-extensionEnd,defs #classDiagram-extensionStart{fill:var(--md-mermaid-edge-color)!important;stroke:var(--md-mermaid-edge-color)!important}defs #classDiagram-aggregationEnd,defs #classDiagram-aggregationStart{fill:var(--md-mermaid-label-bg-color)!important;stroke:var(--md-mermaid-edge-color)!important}g.stateGroup rect{fill:var(--md-mermaid-node-bg-color);stroke:var(--md-mermaid-node-fg-color)}g.stateGroup .state-title{fill:var(--md-mermaid-label-fg-color)!important;font-family:var(--md-mermaid-font-family)}g.stateGroup .composit{fill:var(--md-mermaid-label-bg-color)}.nodeLabel{color:var(--md-mermaid-label-fg-color);font-family:var(--md-mermaid-font-family)}.node circle.state-end,.node circle.state-start,.start-state{fill:var(--md-mermaid-edge-color);stroke:none}.end-state-inner,.end-state-outer{fill:var(--md-mermaid-edge-color)}.end-state-inner,.node circle.state-end{stroke:var(--md-mermaid-label-bg-color)}.transition{stroke:var(--md-mermaid-edge-color)}[id^=state-fork] rect,[id^=state-join] rect{fill:var(--md-mermaid-edge-color)!important;stroke:none!important}.statediagram-cluster.statediagram-cluster .inner{fill:var(--md-default-bg-color)}.statediagram-cluster rect{fill:var(--md-mermaid-node-bg-color);stroke:var(--md-mermaid-node-fg-color)}.statediagram-state rect.divider{fill:var(--md-default-fg-color--lightest);stroke:var(--md-default-fg-color--lighter)}defs #statediagram-barbEnd{stroke:var(--md-mermaid-edge-color)}.entityBox{fill:var(--md-mermaid-label-bg-color);stroke:var(--md-mermaid-node-fg-color)}.entityLabel{fill:var(--md-mermaid-label-fg-color);font-family:var(--md-mermaid-font-family)}.relationshipLabelBox{fill:var(--md-mermaid-label-bg-color);fill-opacity:1;background-color:var(--md-mermaid-label-bg-color);opacity:1}.relationshipLabel{fill:var(--md-mermaid-label-fg-color)}.relationshipLine{stroke:var(--md-mermaid-edge-color)}defs #ONE_OR_MORE_END *,defs #ONE_OR_MORE_START *,defs #ONLY_ONE_END *,defs #ONLY_ONE_START *,defs #ZERO_OR_MORE_END *,defs #ZERO_OR_MORE_START *,defs #ZERO_OR_ONE_END *,defs #ZERO_OR_ONE_START *{stroke:var(--md-mermaid-edge-color)!important}.actor,defs #ZERO_OR_MORE_END circle,defs #ZERO_OR_MORE_START circle{fill:var(--md-mermaid-label-bg-color)}.actor{stroke:var(--md-mermaid-node-fg-color)}text.actor>tspan{fill:var(--md-mermaid-label-fg-color);font-family:var(--md-mermaid-font-family)}line{stroke:var(--md-default-fg-color--lighter)}.messageLine0,.messageLine1{stroke:var(--md-mermaid-edge-color)}.loopText>tspan,.messageText{font-family:var(--md-mermaid-font-family)!important}#arrowhead path,.loopText>tspan,.messageText{fill:var(--md-mermaid-edge-color);stroke:none}.loopLine{stroke:var(--md-mermaid-node-fg-color)}.labelBox,.loopLine{fill:var(--md-mermaid-node-bg-color)}.labelBox{stroke:none}.labelText,.labelText>span{fill:var(--md-mermaid-node-fg-color);font-family:var(--md-mermaid-font-family)}";var Jr,ps=0;function ls(){return typeof mermaid=="undefined"||mermaid instanceof Element?Fo("https://unpkg.com/mermaid@9.0.1/dist/mermaid.min.js"):P(void 0)}function ii(e){return e.classList.remove("mermaid"),Jr||(Jr=ls().pipe(O(()=>mermaid.initialize({startOnLoad:!1,themeCSS:oi})),m(()=>{}),X(1))),Jr.subscribe(()=>{e.classList.add("mermaid");let t=`__mermaid_${ps++}`,r=L("div",{class:"mermaid"});mermaid.mermaidAPI.render(t,e.textContent,n=>{let o=r.attachShadow({mode:"closed"});o.innerHTML=n,e.replaceWith(r)})}),Jr.pipe(m(()=>({ref:e})))}function ms(e,{target$:t,print$:r}){let n=!0;return A(t.pipe(m(o=>o.closest("details:not([open])")),M(o=>e===o),m(()=>({action:"open",reveal:!0}))),r.pipe(M(o=>o||!n),O(()=>n=e.open),m(o=>({action:o?"open":"close"}))))}function ai(e,t){return j(()=>{let r=new _;return r.subscribe(({action:n,reveal:o})=>{n==="open"?e.setAttribute("open",""):e.removeAttribute("open"),o&&e.scrollIntoView()}),ms(e,t).pipe(O(n=>r.next(n)),C(()=>r.complete()),m(n=>U({ref:e},n)))})}var si=L("table");function ci(e){return e.replaceWith(si),si.replaceWith(Bo(e)),P({ref:e})}function ds(e){let t=B(":scope > input",e),r=t.find(n=>n.checked)||t[0];return A(...t.map(n=>g(n,"change").pipe(m(()=>Q(`label[for=${n.id}]`))))).pipe(q(Q(`label[for=${r.id}]`)),m(n=>({active:n})))}function fi(e){let t=Gr("prev");e.append(t);let r=Gr("next");e.append(r);let n=Q(".tabbed-labels",e);return j(()=>{let o=new _,i=o.pipe(fe(1));return Y([o,de(e)]).pipe($e(1,Te),Z(i)).subscribe({next([{active:a},s]){let c=ze(a),{width:f}=Ae(a);e.style.setProperty("--md-indicator-x",`${c.x}px`),e.style.setProperty("--md-indicator-width",`${f}px`);let u=or(n);(c.xu.x+s.width)&&n.scrollTo({left:Math.max(0,c.x-16),behavior:"smooth"})},complete(){e.style.removeProperty("--md-indicator-x"),e.style.removeProperty("--md-indicator-width")}}),Y([pt(n),de(n)]).pipe(Z(i)).subscribe(([a,s])=>{let c=mt(n);t.hidden=a.x<16,r.hidden=a.x>c.width-s.width-16}),A(g(t,"click").pipe(m(()=>-1)),g(r,"click").pipe(m(()=>1))).pipe(Z(i)).subscribe(a=>{let{width:s}=Ae(n);n.scrollBy({left:s*a,behavior:"smooth"})}),oe("content.tabs.link")&&o.pipe(Le(1)).subscribe(({active:a})=>{let s=a.innerText.trim();for(let f of B("[data-tabs]"))for(let u of B(":scope > input",f))if(Q(`label[for=${u.id}]`).innerText.trim()===s){u.click();break}let c=__md_get("__tabs")||[];__md_set("__tabs",[...new Set([s,...c])])}),ds(e).pipe(O(a=>o.next(a)),C(()=>o.complete()),m(a=>U({ref:e},a)))}).pipe(Be(ue))}function ui(e,{target$:t,print$:r}){return A(...B("pre:not(.mermaid) > code",e).map(n=>ni(n,{print$:r})),...B("pre.mermaid",e).map(n=>ii(n)),...B("table:not([class])",e).map(n=>ci(n)),...B("details",e).map(n=>ai(n,{target$:t,print$:r})),...B("[data-tabs]",e).map(n=>fi(n)))}function hs(e,{alert$:t}){return t.pipe(x(r=>A(P(!0),P(!1).pipe(Fe(2e3))).pipe(m(n=>({message:r,active:n})))))}function pi(e,t){let r=Q(".md-typeset",e);return j(()=>{let n=new _;return n.subscribe(({message:o,active:i})=>{e.classList.toggle("md-dialog--active",i),r.textContent=o}),hs(e,t).pipe(O(o=>n.next(o)),C(()=>n.complete()),m(o=>U({ref:e},o)))})}function bs({viewport$:e}){if(!oe("header.autohide"))return P(!1);let t=e.pipe(m(({offset:{y:o}})=>o),Me(2,1),m(([o,i])=>[oMath.abs(i-o.y)>100),m(([,[o]])=>o),K()),n=dt("search");return Y([e,n]).pipe(m(([{offset:o},i])=>o.y>400&&!i),K(),x(o=>o?r:P(!1)),q(!1))}function li(e,t){return j(()=>Y([de(e),bs(t)])).pipe(m(([{height:r},n])=>({height:r,hidden:n})),K((r,n)=>r.height===n.height&&r.hidden===n.hidden),X(1))}function mi(e,{header$:t,main$:r}){return j(()=>{let n=new _,o=n.pipe(fe(1));return n.pipe(J("active"),Je(t)).subscribe(([{active:i},{hidden:a}])=>{e.classList.toggle("md-header--shadow",i&&!a),e.hidden=a}),r.subscribe(n),t.pipe(Z(o),m(i=>U({ref:e},i)))})}function vs(e,{viewport$:t,header$:r}){return mr(e,{viewport$:t,header$:r}).pipe(m(({offset:{y:n}})=>{let{height:o}=Ae(e);return{active:n>=o}}),J("active"))}function di(e,t){return j(()=>{let r=new _;r.subscribe(({active:o})=>{e.classList.toggle("md-header__title--active",o)});let n=pe("article h1");return typeof n=="undefined"?k:vs(n,t).pipe(O(o=>r.next(o)),C(()=>r.complete()),m(o=>U({ref:e},o)))})}function hi(e,{viewport$:t,header$:r}){let n=r.pipe(m(({height:i})=>i),K()),o=n.pipe(x(()=>de(e).pipe(m(({height:i})=>({top:e.offsetTop,bottom:e.offsetTop+i})),J("bottom"))));return Y([n,o,t]).pipe(m(([i,{top:a,bottom:s},{offset:{y:c},size:{height:f}}])=>(f=Math.max(0,f-Math.max(0,a-c,i)-Math.max(0,f+c-s)),{offset:a-i,height:f,active:a-i<=c})),K((i,a)=>i.offset===a.offset&&i.height===a.height&&i.active===a.active))}function gs(e){let t=__md_get("__palette")||{index:e.findIndex(r=>matchMedia(r.getAttribute("data-md-color-media")).matches)};return P(...e).pipe(se(r=>g(r,"change").pipe(m(()=>r))),q(e[Math.max(0,t.index)]),m(r=>({index:e.indexOf(r),color:{scheme:r.getAttribute("data-md-color-scheme"),primary:r.getAttribute("data-md-color-primary"),accent:r.getAttribute("data-md-color-accent")}})),X(1))}function bi(e){return j(()=>{let t=new _;t.subscribe(n=>{document.body.setAttribute("data-md-color-switching","");for(let[o,i]of Object.entries(n.color))document.body.setAttribute(`data-md-color-${o}`,i);for(let o=0;o{document.body.removeAttribute("data-md-color-switching")});let r=B("input",e);return gs(r).pipe(O(n=>t.next(n)),C(()=>t.complete()),m(n=>U({ref:e},n)))})}var Xr=Ye(Kr());function ys(e){e.setAttribute("data-md-copying","");let t=e.innerText;return e.removeAttribute("data-md-copying"),t}function vi({alert$:e}){Xr.default.isSupported()&&new $(t=>{new Xr.default("[data-clipboard-target], [data-clipboard-text]",{text:r=>r.getAttribute("data-clipboard-text")||ys(Q(r.getAttribute("data-clipboard-target")))}).on("success",r=>t.next(r))}).pipe(O(t=>{t.trigger.focus()}),m(()=>ee("clipboard.copied"))).subscribe(e)}function xs(e){if(e.length<2)return[""];let[t,r]=[...e].sort((o,i)=>o.length-i.length).map(o=>o.replace(/[^/]+$/,"")),n=0;if(t===r)n=t.length;else for(;t.charCodeAt(n)===r.charCodeAt(n);)n++;return e.map(o=>o.replace(t.slice(0,n),""))}function dr(e){let t=__md_get("__sitemap",sessionStorage,e);if(t)return P(t);{let r=he();return jo(new URL("sitemap.xml",e||r.base)).pipe(m(n=>xs(B("loc",n).map(o=>o.textContent))),ce(()=>k),je([]),O(n=>__md_set("__sitemap",n,sessionStorage,e)))}}function gi({document$:e,location$:t,viewport$:r}){let n=he();if(location.protocol==="file:")return;"scrollRestoration"in history&&(history.scrollRestoration="manual",g(window,"beforeunload").subscribe(()=>{history.scrollRestoration="auto"}));let o=pe("link[rel=icon]");typeof o!="undefined"&&(o.href=o.href);let i=dr().pipe(m(f=>f.map(u=>`${new URL(u,n.base)}`)),x(f=>g(document.body,"click").pipe(M(u=>!u.metaKey&&!u.ctrlKey),x(u=>{if(u.target instanceof Element){let p=u.target.closest("a");if(p&&!p.target){let l=new URL(p.href);if(l.search="",l.hash="",l.pathname!==location.pathname&&f.includes(l.toString()))return u.preventDefault(),P({url:new URL(p.href)})}}return xe}))),ne()),a=g(window,"popstate").pipe(M(f=>f.state!==null),m(f=>({url:new URL(location.href),offset:f.state})),ne());A(i,a).pipe(K((f,u)=>f.url.href===u.url.href),m(({url:f})=>f)).subscribe(t);let s=t.pipe(J("pathname"),x(f=>lr(f.href).pipe(ce(()=>(ur(f),xe)))),ne());i.pipe(ut(s)).subscribe(({url:f})=>{history.pushState({},"",`${f}`)});let c=new DOMParser;s.pipe(x(f=>f.text()),m(f=>c.parseFromString(f,"text/html"))).subscribe(e),e.pipe(Le(1)).subscribe(f=>{for(let u of["title","link[rel=canonical]","meta[name=author]","meta[name=description]","[data-md-component=announce]","[data-md-component=container]","[data-md-component=header-topic]","[data-md-component=outdated]","[data-md-component=logo]","[data-md-component=skip]",...oe("navigation.tabs.sticky")?["[data-md-component=tabs]"]:[]]){let p=pe(u),l=pe(u,f);typeof p!="undefined"&&typeof l!="undefined"&&p.replaceWith(l)}}),e.pipe(Le(1),m(()=>we("container")),x(f=>B("script",f)),Ir(f=>{let u=L("script");if(f.src){for(let p of f.getAttributeNames())u.setAttribute(p,f.getAttribute(p));return f.replaceWith(u),new $(p=>{u.onload=()=>p.complete()})}else return u.textContent=f.textContent,f.replaceWith(u),k})).subscribe(),A(i,a).pipe(ut(e)).subscribe(({url:f,offset:u})=>{f.hash&&!u?Po(f.hash):window.scrollTo(0,(u==null?void 0:u.y)||0)}),r.pipe(Tt(i),Xe(250),J("offset")).subscribe(({offset:f})=>{history.replaceState(f,"")}),A(i,a).pipe(Me(2,1),M(([f,u])=>f.url.pathname===u.url.pathname),m(([,f])=>f)).subscribe(({offset:f})=>{window.scrollTo(0,(f==null?void 0:f.y)||0)})}var Es=Ye(Zr());var xi=Ye(Zr());function en(e,t){let r=new RegExp(e.separator,"img"),n=(o,i,a)=>`${i}${a}`;return o=>{o=o.replace(/[\s*+\-:~^]+/g," ").trim();let i=new RegExp(`(^|${e.separator})(${o.replace(/[|\\{}()[\]^$+*?.-]/g,"\\$&").replace(r,"|")})`,"img");return a=>(t?(0,xi.default)(a):a).replace(i,n).replace(/<\/mark>(\s+)]*>/img,"$1")}}function Si(e){return e.split(/"([^"]+)"/g).map((t,r)=>r&1?t.replace(/^\b|^(?![^\x00-\x7F]|$)|\s+/g," +"):t).join("").replace(/"|(?:^|\s+)[*+\-:^~]+(?=\s+|$)/g,"").trim()}function bt(e){return e.type===1}function wi(e){return e.type===2}function vt(e){return e.type===3}function _s({config:e,docs:t}){e.lang.length===1&&e.lang[0]==="en"&&(e.lang=[ee("search.config.lang")]),e.separator==="[\\s\\-]+"&&(e.separator=ee("search.config.separator"));let n={pipeline:ee("search.config.pipeline").split(/\s*,\s*/).filter(Boolean),suggestions:oe("search.suggest")};return{config:e,docs:t,options:n}}function Ei(e,t){let r=he(),n=new Worker(e),o=new _,i=zo(n,{tx$:o}).pipe(m(a=>{if(vt(a))for(let s of a.data.items)for(let c of s)c.location=`${new URL(c.location,r.base)}`;return a}),ne());return ie(t).pipe(m(a=>({type:0,data:_s(a)}))).subscribe(o.next.bind(o)),{tx$:o,rx$:i}}function Oi({document$:e}){let t=he(),r=ke(new URL("../versions.json",t.base)).pipe(ce(()=>k)),n=r.pipe(m(o=>{let[,i]=t.base.match(/([^/]+)\/?$/);return o.find(({version:a,aliases:s})=>a===i||s.includes(i))||o[0]}));r.pipe(m(o=>new Map(o.map(i=>[`${new URL(`../${i.version}/`,t.base)}`,i]))),x(o=>g(document.body,"click").pipe(M(i=>!i.metaKey&&!i.ctrlKey),ge(n),x(([i,a])=>{if(i.target instanceof Element){let s=i.target.closest("a");if(s&&!s.target&&o.has(s.href)){i.preventDefault();let c=s.href;return!i.target.closest(".md-version")&&o.get(c)===a?k:P(c)}}return k}),x(i=>{let{version:a}=o.get(i);return dr(new URL(i)).pipe(m(s=>{let f=Se().href.replace(t.base,"");return s.includes(f)?new URL(`../${a}/${f}`,t.base):new URL(i)}))})))).subscribe(o=>ur(o)),Y([r,n]).subscribe(([o,i])=>{Q(".md-header__topic").appendChild(Go(o,i))}),e.pipe(x(()=>n)).subscribe(o=>{var a;let i=__md_get("__outdated",sessionStorage);if(i===null){let s=((a=t.version)==null?void 0:a.default)||"latest";i=!o.aliases.includes(s),__md_set("__outdated",i,sessionStorage)}if(i)for(let s of ae("outdated"))s.hidden=!1})}function Ts(e,{rx$:t}){let r=(__search==null?void 0:__search.transform)||Si,{searchParams:n}=Se();n.has("q")&&qe("search",!0);let o=t.pipe(M(bt),re(1),m(()=>n.get("q")||""));dt("search").pipe(M(s=>!s),re(1)).subscribe(()=>{let s=new URL(location.href);s.searchParams.delete("q"),history.replaceState({},"",`${s}`)}),o.subscribe(s=>{s&&(e.value=s,e.focus())});let i=nr(e),a=A(g(e,"keyup"),g(e,"focus").pipe(Fe(1)),o).pipe(m(()=>r(e.value)),q(""),K());return Y([a,i]).pipe(m(([s,c])=>({value:s,focus:c})),X(1))}function _i(e,{tx$:t,rx$:r}){let n=new _,o=n.pipe(fe(1));return n.pipe(J("value"),m(({value:i})=>({type:2,data:i}))).subscribe(t.next.bind(t)),n.pipe(J("focus")).subscribe(({focus:i})=>{i?(qe("search",i),e.placeholder=""):e.placeholder=ee("search.placeholder")}),g(e.form,"reset").pipe(Z(o)).subscribe(()=>e.focus()),Ts(e,{tx$:t,rx$:r}).pipe(O(i=>n.next(i)),C(()=>n.complete()),m(i=>U({ref:e},i)),ne())}function Ti(e,{rx$:t},{query$:r}){let n=new _,o=Mo(e.parentElement).pipe(M(Boolean)),i=Q(":scope > :first-child",e),a=Q(":scope > :last-child",e),s=t.pipe(M(bt),re(1));return n.pipe(ge(r),Tt(s)).subscribe(([{items:f},{value:u}])=>{if(u)switch(f.length){case 0:i.textContent=ee("search.result.none");break;case 1:i.textContent=ee("search.result.one");break;default:i.textContent=ee("search.result.other",pr(f.length))}else i.textContent=ee("search.result.placeholder")}),n.pipe(O(()=>a.innerHTML=""),x(({items:f})=>A(P(...f.slice(0,10)),P(...f.slice(10)).pipe(Me(4),Nr(o),x(([u])=>u))))).subscribe(f=>a.appendChild(Yo(f))),t.pipe(M(vt),m(({data:f})=>f)).pipe(O(f=>n.next(f)),C(()=>n.complete()),m(f=>U({ref:e},f)))}function Ms(e,{query$:t}){return t.pipe(m(({value:r})=>{let n=Se();return n.hash="",n.searchParams.delete("h"),n.searchParams.set("q",r),{url:n}}))}function Mi(e,t){let r=new _;return r.subscribe(({url:n})=>{e.setAttribute("data-clipboard-text",e.href),e.href=`${n}`}),g(e,"click").subscribe(n=>n.preventDefault()),Ms(e,t).pipe(O(n=>r.next(n)),C(()=>r.complete()),m(n=>U({ref:e},n)))}function Li(e,{rx$:t},{keyboard$:r}){let n=new _,o=we("search-query"),i=A(g(o,"keydown"),g(o,"focus")).pipe(Ie(ue),m(()=>o.value),K());return n.pipe(Je(i),m(([{suggestions:s},c])=>{let f=c.split(/([\s-]+)/);if((s==null?void 0:s.length)&&f[f.length-1]){let u=s[s.length-1];u.startsWith(f[f.length-1])&&(f[f.length-1]=u)}else f.length=0;return f})).subscribe(s=>e.innerHTML=s.join("").replace(/\s/g," ")),r.pipe(M(({mode:s})=>s==="search")).subscribe(s=>{switch(s.type){case"ArrowRight":e.innerText.length&&o.selectionStart===o.value.length&&(o.value=e.innerText);break}}),t.pipe(M(vt),m(({data:s})=>s)).pipe(O(s=>n.next(s)),C(()=>n.complete()),m(()=>({ref:e})))}function Ai(e,{index$:t,keyboard$:r}){let n=he();try{let o=(__search==null?void 0:__search.worker)||n.search,i=Ei(o,t),a=we("search-query",e),s=we("search-result",e),{tx$:c,rx$:f}=i;c.pipe(M(wi),ut(f.pipe(M(bt))),re(1)).subscribe(c.next.bind(c)),r.pipe(M(({mode:l})=>l==="search")).subscribe(l=>{let d=Ne();switch(l.type){case"Enter":if(d===a){let h=new Map;for(let b of B(":first-child [href]",s)){let F=b.firstElementChild;h.set(b,parseFloat(F.getAttribute("data-md-score")))}if(h.size){let[[b]]=[...h].sort(([,F],[,G])=>G-F);b.click()}l.claim()}break;case"Escape":case"Tab":qe("search",!1),a.blur();break;case"ArrowUp":case"ArrowDown":if(typeof d=="undefined")a.focus();else{let h=[a,...B(":not(details) > [href], summary, details[open] [href]",s)],b=Math.max(0,(Math.max(0,h.indexOf(d))+h.length+(l.type==="ArrowUp"?-1:1))%h.length);h[b].focus()}l.claim();break;default:a!==Ne()&&a.focus()}}),r.pipe(M(({mode:l})=>l==="global")).subscribe(l=>{switch(l.type){case"f":case"s":case"/":a.focus(),a.select(),l.claim();break}});let u=_i(a,i),p=Ti(s,i,{query$:u});return A(u,p).pipe(Ze(...ae("search-share",e).map(l=>Mi(l,{query$:u})),...ae("search-suggest",e).map(l=>Li(l,i,{keyboard$:r}))))}catch(o){return e.hidden=!0,xe}}function Ci(e,{index$:t,location$:r}){return Y([t,r.pipe(q(Se()),M(n=>!!n.searchParams.get("h")))]).pipe(m(([n,o])=>en(n.config,!0)(o.searchParams.get("h"))),m(n=>{var a;let o=new Map,i=document.createNodeIterator(e,NodeFilter.SHOW_TEXT);for(let s=i.nextNode();s;s=i.nextNode())if((a=s.parentElement)!=null&&a.offsetHeight){let c=s.textContent,f=n(c);f.length>c.length&&o.set(s,f)}for(let[s,c]of o){let{childNodes:f}=L("span",null,c);s.replaceWith(...Array.from(f))}return{ref:e,nodes:o}}))}function Ls(e,{viewport$:t,main$:r}){let n=e.parentElement,o=n.offsetTop-n.parentElement.offsetTop;return Y([r,t]).pipe(m(([{offset:i,height:a},{offset:{y:s}}])=>(a=a+Math.min(o,Math.max(0,s-i))-o,{height:a,locked:s>=i+o})),K((i,a)=>i.height===a.height&&i.locked===a.locked))}function tn(e,n){var o=n,{header$:t}=o,r=cn(o,["header$"]);let i=Q(".md-sidebar__scrollwrap",e),{y:a}=ze(i);return j(()=>{let s=new _;return s.pipe($e(0,Te),ge(t)).subscribe({next([{height:c},{height:f}]){i.style.height=`${c-2*a}px`,e.style.top=`${f}px`},complete(){i.style.height="",e.style.top=""}}),Ls(e,r).pipe(O(c=>s.next(c)),C(()=>s.complete()),m(c=>U({ref:e},c)))})}function Ri(e,t){if(typeof t!="undefined"){let r=`https://api.github.com/repos/${e}/${t}`;return Ot(ke(`${r}/releases/latest`).pipe(ce(()=>k),m(n=>({version:n.tag_name})),je({})),ke(r).pipe(ce(()=>k),m(n=>({stars:n.stargazers_count,forks:n.forks_count})),je({}))).pipe(m(([n,o])=>U(U({},n),o)))}else{let r=`https://api.github.com/users/${e}`;return ke(r).pipe(m(n=>({repositories:n.public_repos})),je({}))}}function ki(e,t){let r=`https://${e}/api/v4/projects/${encodeURIComponent(t)}`;return ke(r).pipe(ce(()=>k),m(({star_count:n,forks_count:o})=>({stars:n,forks:o})),je({}))}function Hi(e){let[t]=e.match(/(git(?:hub|lab))/i)||[];switch(t.toLowerCase()){case"github":let[,r,n]=e.match(/^.+github\.com\/([^/]+)\/?([^/]+)?/i);return Ri(r,n);case"gitlab":let[,o,i]=e.match(/^.+?([^/]*gitlab[^/]+)\/(.+?)\/?$/i);return ki(o,i);default:return k}}var As;function Cs(e){return As||(As=j(()=>{let t=__md_get("__source",sessionStorage);return t?P(t):Hi(e.href).pipe(O(r=>__md_set("__source",r,sessionStorage)))}).pipe(ce(()=>k),M(t=>Object.keys(t).length>0),m(t=>({facts:t})),X(1)))}function Pi(e){let t=Q(":scope > :last-child",e);return j(()=>{let r=new _;return r.subscribe(({facts:n})=>{t.appendChild(Ko(n)),t.classList.add("md-source__repository--active")}),Cs(e).pipe(O(n=>r.next(n)),C(()=>r.complete()),m(n=>U({ref:e},n)))})}function Rs(e,{viewport$:t,header$:r}){return de(document.body).pipe(x(()=>mr(e,{header$:r,viewport$:t})),m(({offset:{y:n}})=>({hidden:n>=10})),J("hidden"))}function Ii(e,t){return j(()=>{let r=new _;return r.subscribe({next({hidden:n}){e.hidden=n},complete(){e.hidden=!1}}),(oe("navigation.tabs.sticky")?P({hidden:!1}):Rs(e,t)).pipe(O(n=>r.next(n)),C(()=>r.complete()),m(n=>U({ref:e},n)))})}function ks(e,{viewport$:t,header$:r}){let n=new Map,o=B("[href^=\\#]",e);for(let s of o){let c=decodeURIComponent(s.hash.substring(1)),f=pe(`[id="${c}"]`);typeof f!="undefined"&&n.set(s,f)}let i=r.pipe(J("height"),m(({height:s})=>{let c=we("main"),f=Q(":scope > :first-child",c);return s+.8*(f.offsetTop-c.offsetTop)}),ne());return de(document.body).pipe(J("height"),x(s=>j(()=>{let c=[];return P([...n].reduce((f,[u,p])=>{for(;c.length&&n.get(c[c.length-1]).tagName>=p.tagName;)c.pop();let l=p.offsetTop;for(;!l&&p.parentElement;)p=p.parentElement,l=p.offsetTop;return f.set([...c=[...c,u]].reverse(),l)},new Map))}).pipe(m(c=>new Map([...c].sort(([,f],[,u])=>f-u))),Je(i),x(([c,f])=>t.pipe(Fr(([u,p],{offset:{y:l},size:d})=>{let h=l+d.height>=Math.floor(s.height);for(;p.length;){let[,b]=p[0];if(b-f=l&&!h)p=[u.pop(),...p];else break}return[u,p]},[[],[...c]]),K((u,p)=>u[0]===p[0]&&u[1]===p[1])))))).pipe(m(([s,c])=>({prev:s.map(([f])=>f),next:c.map(([f])=>f)})),q({prev:[],next:[]}),Me(2,1),m(([s,c])=>s.prev.length{let o=new _,i=o.pipe(fe(1));return o.subscribe(({prev:a,next:s})=>{for(let[c]of s)c.classList.remove("md-nav__link--passed"),c.classList.remove("md-nav__link--active");for(let[c,[f]]of a.entries())f.classList.add("md-nav__link--passed"),f.classList.toggle("md-nav__link--active",c===a.length-1)}),oe("navigation.tracking")&&t.pipe(Z(i),J("offset"),Xe(250),Le(1),Z(n.pipe(Le(1))),_t({delay:250}),ge(o)).subscribe(([,{prev:a}])=>{let s=Se(),c=a[a.length-1];if(c&&c.length){let[f]=c,{hash:u}=new URL(f.href);s.hash!==u&&(s.hash=u,history.replaceState({},"",`${s}`))}else s.hash="",history.replaceState({},"",`${s}`)}),ks(e,{viewport$:t,header$:r}).pipe(O(a=>o.next(a)),C(()=>o.complete()),m(a=>U({ref:e},a)))})}function Hs(e,{viewport$:t,main$:r,target$:n}){let o=t.pipe(m(({offset:{y:a}})=>a),Me(2,1),m(([a,s])=>a>s&&s>0),K()),i=r.pipe(m(({active:a})=>a));return Y([i,o]).pipe(m(([a,s])=>!(a&&s)),K(),Z(n.pipe(Le(1))),jr(!0),_t({delay:250}),m(a=>({hidden:a})))}function ji(e,{viewport$:t,header$:r,main$:n,target$:o}){let i=new _,a=i.pipe(fe(1));return i.subscribe({next({hidden:s}){e.hidden=s,s?(e.setAttribute("tabindex","-1"),e.blur()):e.removeAttribute("tabindex")},complete(){e.style.top="",e.hidden=!0,e.removeAttribute("tabindex")}}),r.pipe(Z(a),J("height")).subscribe(({height:s})=>{e.style.top=`${s+16}px`}),Hs(e,{viewport$:t,main$:n,target$:o}).pipe(O(s=>i.next(s)),C(()=>i.complete()),m(s=>U({ref:e},s)))}function Fi({document$:e,tablet$:t}){e.pipe(x(()=>B(".md-toggle--indeterminate, [data-md-state=indeterminate]")),O(r=>{r.indeterminate=!0,r.checked=!1}),se(r=>g(r,"change").pipe(Dr(()=>r.classList.contains("md-toggle--indeterminate")),m(()=>r))),ge(t)).subscribe(([r,n])=>{r.classList.remove("md-toggle--indeterminate"),n&&(r.checked=!1)})}function Ps(){return/(iPad|iPhone|iPod)/.test(navigator.userAgent)}function Ui({document$:e}){e.pipe(x(()=>B("[data-md-scrollfix]")),O(t=>t.removeAttribute("data-md-scrollfix")),M(Ps),se(t=>g(t,"touchstart").pipe(m(()=>t)))).subscribe(t=>{let r=t.scrollTop;r===0?t.scrollTop=1:r+t.offsetHeight===t.scrollHeight&&(t.scrollTop=r-1)})}function Di({viewport$:e,tablet$:t}){Y([dt("search"),t]).pipe(m(([r,n])=>r&&!n),x(r=>P(r).pipe(Fe(r?400:100))),ge(e)).subscribe(([r,{offset:{y:n}}])=>{if(r)document.body.setAttribute("data-md-scrolllock",""),document.body.style.top=`-${n}px`;else{let o=-1*parseInt(document.body.style.top,10);document.body.removeAttribute("data-md-scrolllock"),document.body.style.top="",o&&window.scrollTo(0,o)}})}Object.entries||(Object.entries=function(e){let t=[];for(let r of Object.keys(e))t.push([r,e[r]]);return t});Object.values||(Object.values=function(e){let t=[];for(let r of Object.keys(e))t.push(e[r]);return t});typeof Element!="undefined"&&(Element.prototype.scrollTo||(Element.prototype.scrollTo=function(e,t){typeof e=="object"?(this.scrollLeft=e.left,this.scrollTop=e.top):(this.scrollLeft=e,this.scrollTop=t)}),Element.prototype.replaceWith||(Element.prototype.replaceWith=function(...e){let t=this.parentNode;if(t){e.length===0&&t.removeChild(this);for(let r=e.length-1;r>=0;r--){let n=e[r];typeof n!="object"?n=document.createTextNode(n):n.parentNode&&n.parentNode.removeChild(n),r?t.insertBefore(this.previousSibling,n):t.replaceChild(n,this)}}}));document.documentElement.classList.remove("no-js");document.documentElement.classList.add("js");var et=bo(),br=Co(),At=Io(),rn=Ao(),Ee=No(),vr=qr("(min-width: 960px)"),Vi=qr("(min-width: 1220px)"),Ni=$o(),zi=he(),qi=document.forms.namedItem("search")?(__search==null?void 0:__search.index)||ke(new URL("search/search_index.json",zi.base)):xe,nn=new _;vi({alert$:nn});oe("navigation.instant")&&gi({document$:et,location$:br,viewport$:Ee});var Wi;((Wi=zi.version)==null?void 0:Wi.provider)==="mike"&&Oi({document$:et});A(br,At).pipe(Fe(125)).subscribe(()=>{qe("drawer",!1),qe("search",!1)});rn.pipe(M(({mode:e})=>e==="global")).subscribe(e=>{switch(e.type){case"p":case",":let t=pe("[href][rel=prev]");typeof t!="undefined"&&t.click();break;case"n":case".":let r=pe("[href][rel=next]");typeof r!="undefined"&&r.click();break}});Fi({document$:et,tablet$:vr});Ui({document$:et});Di({viewport$:Ee,tablet$:vr});var Qe=li(we("header"),{viewport$:Ee}),hr=et.pipe(m(()=>we("main")),x(e=>hi(e,{viewport$:Ee,header$:Qe})),X(1)),Is=A(...ae("dialog").map(e=>pi(e,{alert$:nn})),...ae("header").map(e=>mi(e,{viewport$:Ee,header$:Qe,main$:hr})),...ae("palette").map(e=>bi(e)),...ae("search").map(e=>Ai(e,{index$:qi,keyboard$:rn})),...ae("source").map(e=>Pi(e))),$s=j(()=>A(...ae("content").map(e=>ui(e,{target$:At,print$:Ni})),...ae("content").map(e=>oe("search.highlight")?Ci(e,{index$:qi,location$:br}):k),...ae("header-title").map(e=>di(e,{viewport$:Ee,header$:Qe})),...ae("sidebar").map(e=>e.getAttribute("data-md-type")==="navigation"?Qr(Vi,()=>tn(e,{viewport$:Ee,header$:Qe,main$:hr})):Qr(vr,()=>tn(e,{viewport$:Ee,header$:Qe,main$:hr}))),...ae("tabs").map(e=>Ii(e,{viewport$:Ee,header$:Qe})),...ae("toc").map(e=>$i(e,{viewport$:Ee,header$:Qe,target$:At})),...ae("top").map(e=>ji(e,{viewport$:Ee,header$:Qe,main$:hr,target$:At})))),Qi=et.pipe(x(()=>$s),Ze(Is),X(1));Qi.subscribe();window.document$=et;window.location$=br;window.target$=At;window.keyboard$=rn;window.viewport$=Ee;window.tablet$=vr;window.screen$=Vi;window.print$=Ni;window.alert$=nn;window.component$=Qi;})(); +//# sourceMappingURL=bundle.0238f547.min.js.map + diff --git a/develop/assets/javascripts/bundle.0238f547.min.js.map b/develop/assets/javascripts/bundle.0238f547.min.js.map new file mode 100644 index 0000000..a97c778 --- /dev/null +++ b/develop/assets/javascripts/bundle.0238f547.min.js.map @@ -0,0 +1,8 @@ +{ + "version": 3, + "sources": ["node_modules/focus-visible/dist/focus-visible.js", "node_modules/url-polyfill/url-polyfill.js", "node_modules/rxjs/node_modules/tslib/tslib.js", "node_modules/clipboard/dist/clipboard.js", "node_modules/escape-html/index.js", "node_modules/array-flat-polyfill/index.mjs", "src/assets/javascripts/bundle.ts", "node_modules/unfetch/polyfill/index.js", "node_modules/rxjs/node_modules/tslib/modules/index.js", "node_modules/rxjs/src/internal/util/isFunction.ts", "node_modules/rxjs/src/internal/util/createErrorClass.ts", "node_modules/rxjs/src/internal/util/UnsubscriptionError.ts", "node_modules/rxjs/src/internal/util/arrRemove.ts", "node_modules/rxjs/src/internal/Subscription.ts", "node_modules/rxjs/src/internal/config.ts", "node_modules/rxjs/src/internal/scheduler/timeoutProvider.ts", "node_modules/rxjs/src/internal/util/reportUnhandledError.ts", "node_modules/rxjs/src/internal/util/noop.ts", "node_modules/rxjs/src/internal/NotificationFactories.ts", "node_modules/rxjs/src/internal/util/errorContext.ts", "node_modules/rxjs/src/internal/Subscriber.ts", "node_modules/rxjs/src/internal/symbol/observable.ts", "node_modules/rxjs/src/internal/util/identity.ts", "node_modules/rxjs/src/internal/util/pipe.ts", "node_modules/rxjs/src/internal/Observable.ts", "node_modules/rxjs/src/internal/util/lift.ts", "node_modules/rxjs/src/internal/operators/OperatorSubscriber.ts", "node_modules/rxjs/src/internal/scheduler/animationFrameProvider.ts", "node_modules/rxjs/src/internal/util/ObjectUnsubscribedError.ts", "node_modules/rxjs/src/internal/Subject.ts", "node_modules/rxjs/src/internal/scheduler/dateTimestampProvider.ts", "node_modules/rxjs/src/internal/ReplaySubject.ts", "node_modules/rxjs/src/internal/scheduler/Action.ts", "node_modules/rxjs/src/internal/scheduler/intervalProvider.ts", "node_modules/rxjs/src/internal/scheduler/AsyncAction.ts", "node_modules/rxjs/src/internal/Scheduler.ts", "node_modules/rxjs/src/internal/scheduler/AsyncScheduler.ts", "node_modules/rxjs/src/internal/scheduler/async.ts", "node_modules/rxjs/src/internal/scheduler/AnimationFrameAction.ts", "node_modules/rxjs/src/internal/scheduler/AnimationFrameScheduler.ts", "node_modules/rxjs/src/internal/scheduler/animationFrame.ts", "node_modules/rxjs/src/internal/observable/empty.ts", "node_modules/rxjs/src/internal/util/isScheduler.ts", "node_modules/rxjs/src/internal/util/args.ts", "node_modules/rxjs/src/internal/util/isArrayLike.ts", "node_modules/rxjs/src/internal/util/isPromise.ts", "node_modules/rxjs/src/internal/util/isInteropObservable.ts", "node_modules/rxjs/src/internal/util/isAsyncIterable.ts", "node_modules/rxjs/src/internal/util/throwUnobservableError.ts", "node_modules/rxjs/src/internal/symbol/iterator.ts", "node_modules/rxjs/src/internal/util/isIterable.ts", "node_modules/rxjs/src/internal/util/isReadableStreamLike.ts", "node_modules/rxjs/src/internal/observable/innerFrom.ts", "node_modules/rxjs/src/internal/util/executeSchedule.ts", "node_modules/rxjs/src/internal/operators/observeOn.ts", "node_modules/rxjs/src/internal/operators/subscribeOn.ts", "node_modules/rxjs/src/internal/scheduled/scheduleObservable.ts", "node_modules/rxjs/src/internal/scheduled/schedulePromise.ts", "node_modules/rxjs/src/internal/scheduled/scheduleArray.ts", "node_modules/rxjs/src/internal/scheduled/scheduleIterable.ts", "node_modules/rxjs/src/internal/scheduled/scheduleAsyncIterable.ts", "node_modules/rxjs/src/internal/scheduled/scheduleReadableStreamLike.ts", "node_modules/rxjs/src/internal/scheduled/scheduled.ts", "node_modules/rxjs/src/internal/observable/from.ts", "node_modules/rxjs/src/internal/observable/of.ts", "node_modules/rxjs/src/internal/observable/throwError.ts", "node_modules/rxjs/src/internal/util/isDate.ts", "node_modules/rxjs/src/internal/operators/map.ts", "node_modules/rxjs/src/internal/util/mapOneOrManyArgs.ts", "node_modules/rxjs/src/internal/util/argsArgArrayOrObject.ts", "node_modules/rxjs/src/internal/util/createObject.ts", "node_modules/rxjs/src/internal/observable/combineLatest.ts", "node_modules/rxjs/src/internal/operators/mergeInternals.ts", "node_modules/rxjs/src/internal/operators/mergeMap.ts", "node_modules/rxjs/src/internal/operators/mergeAll.ts", "node_modules/rxjs/src/internal/operators/concatAll.ts", "node_modules/rxjs/src/internal/observable/concat.ts", "node_modules/rxjs/src/internal/observable/defer.ts", "node_modules/rxjs/src/internal/observable/fromEvent.ts", "node_modules/rxjs/src/internal/observable/fromEventPattern.ts", "node_modules/rxjs/src/internal/observable/timer.ts", "node_modules/rxjs/src/internal/observable/merge.ts", "node_modules/rxjs/src/internal/observable/never.ts", "node_modules/rxjs/src/internal/util/argsOrArgArray.ts", "node_modules/rxjs/src/internal/operators/filter.ts", "node_modules/rxjs/src/internal/observable/zip.ts", "node_modules/rxjs/src/internal/operators/audit.ts", "node_modules/rxjs/src/internal/operators/auditTime.ts", "node_modules/rxjs/src/internal/operators/bufferCount.ts", "node_modules/rxjs/src/internal/operators/catchError.ts", "node_modules/rxjs/src/internal/operators/scanInternals.ts", "node_modules/rxjs/src/internal/operators/combineLatest.ts", "node_modules/rxjs/src/internal/operators/combineLatestWith.ts", "node_modules/rxjs/src/internal/operators/concatMap.ts", "node_modules/rxjs/src/internal/operators/debounceTime.ts", "node_modules/rxjs/src/internal/operators/defaultIfEmpty.ts", "node_modules/rxjs/src/internal/operators/take.ts", "node_modules/rxjs/src/internal/operators/ignoreElements.ts", "node_modules/rxjs/src/internal/operators/mapTo.ts", "node_modules/rxjs/src/internal/operators/delayWhen.ts", "node_modules/rxjs/src/internal/operators/delay.ts", "node_modules/rxjs/src/internal/operators/distinctUntilChanged.ts", "node_modules/rxjs/src/internal/operators/distinctUntilKeyChanged.ts", "node_modules/rxjs/src/internal/operators/endWith.ts", "node_modules/rxjs/src/internal/operators/finalize.ts", "node_modules/rxjs/src/internal/operators/takeLast.ts", "node_modules/rxjs/src/internal/operators/merge.ts", "node_modules/rxjs/src/internal/operators/mergeWith.ts", "node_modules/rxjs/src/internal/operators/repeat.ts", "node_modules/rxjs/src/internal/operators/sample.ts", "node_modules/rxjs/src/internal/operators/scan.ts", "node_modules/rxjs/src/internal/operators/share.ts", "node_modules/rxjs/src/internal/operators/shareReplay.ts", "node_modules/rxjs/src/internal/operators/skip.ts", "node_modules/rxjs/src/internal/operators/skipUntil.ts", "node_modules/rxjs/src/internal/operators/startWith.ts", "node_modules/rxjs/src/internal/operators/switchMap.ts", "node_modules/rxjs/src/internal/operators/takeUntil.ts", "node_modules/rxjs/src/internal/operators/takeWhile.ts", "node_modules/rxjs/src/internal/operators/tap.ts", "node_modules/rxjs/src/internal/operators/throttle.ts", "node_modules/rxjs/src/internal/operators/throttleTime.ts", "node_modules/rxjs/src/internal/operators/withLatestFrom.ts", "node_modules/rxjs/src/internal/operators/zip.ts", "node_modules/rxjs/src/internal/operators/zipWith.ts", "src/assets/javascripts/browser/document/index.ts", "src/assets/javascripts/browser/element/_/index.ts", "src/assets/javascripts/browser/element/focus/index.ts", "src/assets/javascripts/browser/element/offset/_/index.ts", "src/assets/javascripts/browser/element/offset/content/index.ts", "node_modules/resize-observer-polyfill/dist/ResizeObserver.es.js", "src/assets/javascripts/browser/element/size/_/index.ts", "src/assets/javascripts/browser/element/size/content/index.ts", "src/assets/javascripts/browser/element/visibility/index.ts", "src/assets/javascripts/browser/toggle/index.ts", "src/assets/javascripts/browser/keyboard/index.ts", "src/assets/javascripts/browser/location/_/index.ts", "src/assets/javascripts/utilities/h/index.ts", "src/assets/javascripts/utilities/string/index.ts", "src/assets/javascripts/browser/location/hash/index.ts", "src/assets/javascripts/browser/media/index.ts", "src/assets/javascripts/browser/request/index.ts", "src/assets/javascripts/browser/script/index.ts", "src/assets/javascripts/browser/viewport/offset/index.ts", "src/assets/javascripts/browser/viewport/size/index.ts", "src/assets/javascripts/browser/viewport/_/index.ts", "src/assets/javascripts/browser/viewport/at/index.ts", "src/assets/javascripts/browser/worker/index.ts", "src/assets/javascripts/_/index.ts", "src/assets/javascripts/components/_/index.ts", "src/assets/javascripts/components/content/code/_/index.ts", "src/assets/javascripts/templates/annotation/index.tsx", "src/assets/javascripts/templates/clipboard/index.tsx", "src/assets/javascripts/templates/search/index.tsx", "src/assets/javascripts/templates/source/index.tsx", "src/assets/javascripts/templates/tabbed/index.tsx", "src/assets/javascripts/templates/table/index.tsx", "src/assets/javascripts/templates/version/index.tsx", "src/assets/javascripts/components/content/annotation/_/index.ts", "src/assets/javascripts/components/content/annotation/list/index.ts", "src/assets/javascripts/components/content/code/mermaid/index.ts", "src/assets/javascripts/components/content/details/index.ts", "src/assets/javascripts/components/content/table/index.ts", "src/assets/javascripts/components/content/tabs/index.ts", "src/assets/javascripts/components/content/_/index.ts", "src/assets/javascripts/components/dialog/index.ts", "src/assets/javascripts/components/header/_/index.ts", "src/assets/javascripts/components/header/title/index.ts", "src/assets/javascripts/components/main/index.ts", "src/assets/javascripts/components/palette/index.ts", "src/assets/javascripts/integrations/clipboard/index.ts", "src/assets/javascripts/integrations/sitemap/index.ts", "src/assets/javascripts/integrations/instant/index.ts", "src/assets/javascripts/integrations/search/document/index.ts", "src/assets/javascripts/integrations/search/highlighter/index.ts", "src/assets/javascripts/integrations/search/query/transform/index.ts", "src/assets/javascripts/integrations/search/worker/message/index.ts", "src/assets/javascripts/integrations/search/worker/_/index.ts", "src/assets/javascripts/integrations/version/index.ts", "src/assets/javascripts/components/search/query/index.ts", "src/assets/javascripts/components/search/result/index.ts", "src/assets/javascripts/components/search/share/index.ts", "src/assets/javascripts/components/search/suggest/index.ts", "src/assets/javascripts/components/search/_/index.ts", "src/assets/javascripts/components/search/highlight/index.ts", "src/assets/javascripts/components/sidebar/index.ts", "src/assets/javascripts/components/source/facts/github/index.ts", "src/assets/javascripts/components/source/facts/gitlab/index.ts", "src/assets/javascripts/components/source/facts/_/index.ts", "src/assets/javascripts/components/source/_/index.ts", "src/assets/javascripts/components/tabs/index.ts", "src/assets/javascripts/components/toc/index.ts", "src/assets/javascripts/components/top/index.ts", "src/assets/javascripts/patches/indeterminate/index.ts", "src/assets/javascripts/patches/scrollfix/index.ts", "src/assets/javascripts/patches/scrolllock/index.ts", "src/assets/javascripts/polyfills/index.ts"], + "sourceRoot": "../../../..", + "sourcesContent": ["(function (global, factory) {\n typeof exports === 'object' && typeof module !== 'undefined' ? factory() :\n typeof define === 'function' && define.amd ? define(factory) :\n (factory());\n}(this, (function () { 'use strict';\n\n /**\n * Applies the :focus-visible polyfill at the given scope.\n * A scope in this case is either the top-level Document or a Shadow Root.\n *\n * @param {(Document|ShadowRoot)} scope\n * @see https://github.com/WICG/focus-visible\n */\n function applyFocusVisiblePolyfill(scope) {\n var hadKeyboardEvent = true;\n var hadFocusVisibleRecently = false;\n var hadFocusVisibleRecentlyTimeout = null;\n\n var inputTypesAllowlist = {\n text: true,\n search: true,\n url: true,\n tel: true,\n email: true,\n password: true,\n number: true,\n date: true,\n month: true,\n week: true,\n time: true,\n datetime: true,\n 'datetime-local': true\n };\n\n /**\n * Helper function for legacy browsers and iframes which sometimes focus\n * elements like document, body, and non-interactive SVG.\n * @param {Element} el\n */\n function isValidFocusTarget(el) {\n if (\n el &&\n el !== document &&\n el.nodeName !== 'HTML' &&\n el.nodeName !== 'BODY' &&\n 'classList' in el &&\n 'contains' in el.classList\n ) {\n return true;\n }\n return false;\n }\n\n /**\n * Computes whether the given element should automatically trigger the\n * `focus-visible` class being added, i.e. whether it should always match\n * `:focus-visible` when focused.\n * @param {Element} el\n * @return {boolean}\n */\n function focusTriggersKeyboardModality(el) {\n var type = el.type;\n var tagName = el.tagName;\n\n if (tagName === 'INPUT' && inputTypesAllowlist[type] && !el.readOnly) {\n return true;\n }\n\n if (tagName === 'TEXTAREA' && !el.readOnly) {\n return true;\n }\n\n if (el.isContentEditable) {\n return true;\n }\n\n return false;\n }\n\n /**\n * Add the `focus-visible` class to the given element if it was not added by\n * the author.\n * @param {Element} el\n */\n function addFocusVisibleClass(el) {\n if (el.classList.contains('focus-visible')) {\n return;\n }\n el.classList.add('focus-visible');\n el.setAttribute('data-focus-visible-added', '');\n }\n\n /**\n * Remove the `focus-visible` class from the given element if it was not\n * originally added by the author.\n * @param {Element} el\n */\n function removeFocusVisibleClass(el) {\n if (!el.hasAttribute('data-focus-visible-added')) {\n return;\n }\n el.classList.remove('focus-visible');\n el.removeAttribute('data-focus-visible-added');\n }\n\n /**\n * If the most recent user interaction was via the keyboard;\n * and the key press did not include a meta, alt/option, or control key;\n * then the modality is keyboard. Otherwise, the modality is not keyboard.\n * Apply `focus-visible` to any current active element and keep track\n * of our keyboard modality state with `hadKeyboardEvent`.\n * @param {KeyboardEvent} e\n */\n function onKeyDown(e) {\n if (e.metaKey || e.altKey || e.ctrlKey) {\n return;\n }\n\n if (isValidFocusTarget(scope.activeElement)) {\n addFocusVisibleClass(scope.activeElement);\n }\n\n hadKeyboardEvent = true;\n }\n\n /**\n * If at any point a user clicks with a pointing device, ensure that we change\n * the modality away from keyboard.\n * This avoids the situation where a user presses a key on an already focused\n * element, and then clicks on a different element, focusing it with a\n * pointing device, while we still think we're in keyboard modality.\n * @param {Event} e\n */\n function onPointerDown(e) {\n hadKeyboardEvent = false;\n }\n\n /**\n * On `focus`, add the `focus-visible` class to the target if:\n * - the target received focus as a result of keyboard navigation, or\n * - the event target is an element that will likely require interaction\n * via the keyboard (e.g. a text box)\n * @param {Event} e\n */\n function onFocus(e) {\n // Prevent IE from focusing the document or HTML element.\n if (!isValidFocusTarget(e.target)) {\n return;\n }\n\n if (hadKeyboardEvent || focusTriggersKeyboardModality(e.target)) {\n addFocusVisibleClass(e.target);\n }\n }\n\n /**\n * On `blur`, remove the `focus-visible` class from the target.\n * @param {Event} e\n */\n function onBlur(e) {\n if (!isValidFocusTarget(e.target)) {\n return;\n }\n\n if (\n e.target.classList.contains('focus-visible') ||\n e.target.hasAttribute('data-focus-visible-added')\n ) {\n // To detect a tab/window switch, we look for a blur event followed\n // rapidly by a visibility change.\n // If we don't see a visibility change within 100ms, it's probably a\n // regular focus change.\n hadFocusVisibleRecently = true;\n window.clearTimeout(hadFocusVisibleRecentlyTimeout);\n hadFocusVisibleRecentlyTimeout = window.setTimeout(function() {\n hadFocusVisibleRecently = false;\n }, 100);\n removeFocusVisibleClass(e.target);\n }\n }\n\n /**\n * If the user changes tabs, keep track of whether or not the previously\n * focused element had .focus-visible.\n * @param {Event} e\n */\n function onVisibilityChange(e) {\n if (document.visibilityState === 'hidden') {\n // If the tab becomes active again, the browser will handle calling focus\n // on the element (Safari actually calls it twice).\n // If this tab change caused a blur on an element with focus-visible,\n // re-apply the class when the user switches back to the tab.\n if (hadFocusVisibleRecently) {\n hadKeyboardEvent = true;\n }\n addInitialPointerMoveListeners();\n }\n }\n\n /**\n * Add a group of listeners to detect usage of any pointing devices.\n * These listeners will be added when the polyfill first loads, and anytime\n * the window is blurred, so that they are active when the window regains\n * focus.\n */\n function addInitialPointerMoveListeners() {\n document.addEventListener('mousemove', onInitialPointerMove);\n document.addEventListener('mousedown', onInitialPointerMove);\n document.addEventListener('mouseup', onInitialPointerMove);\n document.addEventListener('pointermove', onInitialPointerMove);\n document.addEventListener('pointerdown', onInitialPointerMove);\n document.addEventListener('pointerup', onInitialPointerMove);\n document.addEventListener('touchmove', onInitialPointerMove);\n document.addEventListener('touchstart', onInitialPointerMove);\n document.addEventListener('touchend', onInitialPointerMove);\n }\n\n function removeInitialPointerMoveListeners() {\n document.removeEventListener('mousemove', onInitialPointerMove);\n document.removeEventListener('mousedown', onInitialPointerMove);\n document.removeEventListener('mouseup', onInitialPointerMove);\n document.removeEventListener('pointermove', onInitialPointerMove);\n document.removeEventListener('pointerdown', onInitialPointerMove);\n document.removeEventListener('pointerup', onInitialPointerMove);\n document.removeEventListener('touchmove', onInitialPointerMove);\n document.removeEventListener('touchstart', onInitialPointerMove);\n document.removeEventListener('touchend', onInitialPointerMove);\n }\n\n /**\n * When the polfyill first loads, assume the user is in keyboard modality.\n * If any event is received from a pointing device (e.g. mouse, pointer,\n * touch), turn off keyboard modality.\n * This accounts for situations where focus enters the page from the URL bar.\n * @param {Event} e\n */\n function onInitialPointerMove(e) {\n // Work around a Safari quirk that fires a mousemove on whenever the\n // window blurs, even if you're tabbing out of the page. \u00AF\\_(\u30C4)_/\u00AF\n if (e.target.nodeName && e.target.nodeName.toLowerCase() === 'html') {\n return;\n }\n\n hadKeyboardEvent = false;\n removeInitialPointerMoveListeners();\n }\n\n // For some kinds of state, we are interested in changes at the global scope\n // only. For example, global pointer input, global key presses and global\n // visibility change should affect the state at every scope:\n document.addEventListener('keydown', onKeyDown, true);\n document.addEventListener('mousedown', onPointerDown, true);\n document.addEventListener('pointerdown', onPointerDown, true);\n document.addEventListener('touchstart', onPointerDown, true);\n document.addEventListener('visibilitychange', onVisibilityChange, true);\n\n addInitialPointerMoveListeners();\n\n // For focus and blur, we specifically care about state changes in the local\n // scope. This is because focus / blur events that originate from within a\n // shadow root are not re-dispatched from the host element if it was already\n // the active element in its own scope:\n scope.addEventListener('focus', onFocus, true);\n scope.addEventListener('blur', onBlur, true);\n\n // We detect that a node is a ShadowRoot by ensuring that it is a\n // DocumentFragment and also has a host property. This check covers native\n // implementation and polyfill implementation transparently. If we only cared\n // about the native implementation, we could just check if the scope was\n // an instance of a ShadowRoot.\n if (scope.nodeType === Node.DOCUMENT_FRAGMENT_NODE && scope.host) {\n // Since a ShadowRoot is a special kind of DocumentFragment, it does not\n // have a root element to add a class to. So, we add this attribute to the\n // host element instead:\n scope.host.setAttribute('data-js-focus-visible', '');\n } else if (scope.nodeType === Node.DOCUMENT_NODE) {\n document.documentElement.classList.add('js-focus-visible');\n document.documentElement.setAttribute('data-js-focus-visible', '');\n }\n }\n\n // It is important to wrap all references to global window and document in\n // these checks to support server-side rendering use cases\n // @see https://github.com/WICG/focus-visible/issues/199\n if (typeof window !== 'undefined' && typeof document !== 'undefined') {\n // Make the polyfill helper globally available. This can be used as a signal\n // to interested libraries that wish to coordinate with the polyfill for e.g.,\n // applying the polyfill to a shadow root:\n window.applyFocusVisiblePolyfill = applyFocusVisiblePolyfill;\n\n // Notify interested libraries of the polyfill's presence, in case the\n // polyfill was loaded lazily:\n var event;\n\n try {\n event = new CustomEvent('focus-visible-polyfill-ready');\n } catch (error) {\n // IE11 does not support using CustomEvent as a constructor directly:\n event = document.createEvent('CustomEvent');\n event.initCustomEvent('focus-visible-polyfill-ready', false, false, {});\n }\n\n window.dispatchEvent(event);\n }\n\n if (typeof document !== 'undefined') {\n // Apply the polyfill to the global document, so that no JavaScript\n // coordination is required to use the polyfill in the top-level document:\n applyFocusVisiblePolyfill(document);\n }\n\n})));\n", "(function(global) {\r\n /**\r\n * Polyfill URLSearchParams\r\n *\r\n * Inspired from : https://github.com/WebReflection/url-search-params/blob/master/src/url-search-params.js\r\n */\r\n\r\n var checkIfIteratorIsSupported = function() {\r\n try {\r\n return !!Symbol.iterator;\r\n } catch (error) {\r\n return false;\r\n }\r\n };\r\n\r\n\r\n var iteratorSupported = checkIfIteratorIsSupported();\r\n\r\n var createIterator = function(items) {\r\n var iterator = {\r\n next: function() {\r\n var value = items.shift();\r\n return { done: value === void 0, value: value };\r\n }\r\n };\r\n\r\n if (iteratorSupported) {\r\n iterator[Symbol.iterator] = function() {\r\n return iterator;\r\n };\r\n }\r\n\r\n return iterator;\r\n };\r\n\r\n /**\r\n * Search param name and values should be encoded according to https://url.spec.whatwg.org/#urlencoded-serializing\r\n * encodeURIComponent() produces the same result except encoding spaces as `%20` instead of `+`.\r\n */\r\n var serializeParam = function(value) {\r\n return encodeURIComponent(value).replace(/%20/g, '+');\r\n };\r\n\r\n var deserializeParam = function(value) {\r\n return decodeURIComponent(String(value).replace(/\\+/g, ' '));\r\n };\r\n\r\n var polyfillURLSearchParams = function() {\r\n\r\n var URLSearchParams = function(searchString) {\r\n Object.defineProperty(this, '_entries', { writable: true, value: {} });\r\n var typeofSearchString = typeof searchString;\r\n\r\n if (typeofSearchString === 'undefined') {\r\n // do nothing\r\n } else if (typeofSearchString === 'string') {\r\n if (searchString !== '') {\r\n this._fromString(searchString);\r\n }\r\n } else if (searchString instanceof URLSearchParams) {\r\n var _this = this;\r\n searchString.forEach(function(value, name) {\r\n _this.append(name, value);\r\n });\r\n } else if ((searchString !== null) && (typeofSearchString === 'object')) {\r\n if (Object.prototype.toString.call(searchString) === '[object Array]') {\r\n for (var i = 0; i < searchString.length; i++) {\r\n var entry = searchString[i];\r\n if ((Object.prototype.toString.call(entry) === '[object Array]') || (entry.length !== 2)) {\r\n this.append(entry[0], entry[1]);\r\n } else {\r\n throw new TypeError('Expected [string, any] as entry at index ' + i + ' of URLSearchParams\\'s input');\r\n }\r\n }\r\n } else {\r\n for (var key in searchString) {\r\n if (searchString.hasOwnProperty(key)) {\r\n this.append(key, searchString[key]);\r\n }\r\n }\r\n }\r\n } else {\r\n throw new TypeError('Unsupported input\\'s type for URLSearchParams');\r\n }\r\n };\r\n\r\n var proto = URLSearchParams.prototype;\r\n\r\n proto.append = function(name, value) {\r\n if (name in this._entries) {\r\n this._entries[name].push(String(value));\r\n } else {\r\n this._entries[name] = [String(value)];\r\n }\r\n };\r\n\r\n proto.delete = function(name) {\r\n delete this._entries[name];\r\n };\r\n\r\n proto.get = function(name) {\r\n return (name in this._entries) ? this._entries[name][0] : null;\r\n };\r\n\r\n proto.getAll = function(name) {\r\n return (name in this._entries) ? this._entries[name].slice(0) : [];\r\n };\r\n\r\n proto.has = function(name) {\r\n return (name in this._entries);\r\n };\r\n\r\n proto.set = function(name, value) {\r\n this._entries[name] = [String(value)];\r\n };\r\n\r\n proto.forEach = function(callback, thisArg) {\r\n var entries;\r\n for (var name in this._entries) {\r\n if (this._entries.hasOwnProperty(name)) {\r\n entries = this._entries[name];\r\n for (var i = 0; i < entries.length; i++) {\r\n callback.call(thisArg, entries[i], name, this);\r\n }\r\n }\r\n }\r\n };\r\n\r\n proto.keys = function() {\r\n var items = [];\r\n this.forEach(function(value, name) {\r\n items.push(name);\r\n });\r\n return createIterator(items);\r\n };\r\n\r\n proto.values = function() {\r\n var items = [];\r\n this.forEach(function(value) {\r\n items.push(value);\r\n });\r\n return createIterator(items);\r\n };\r\n\r\n proto.entries = function() {\r\n var items = [];\r\n this.forEach(function(value, name) {\r\n items.push([name, value]);\r\n });\r\n return createIterator(items);\r\n };\r\n\r\n if (iteratorSupported) {\r\n proto[Symbol.iterator] = proto.entries;\r\n }\r\n\r\n proto.toString = function() {\r\n var searchArray = [];\r\n this.forEach(function(value, name) {\r\n searchArray.push(serializeParam(name) + '=' + serializeParam(value));\r\n });\r\n return searchArray.join('&');\r\n };\r\n\r\n\r\n global.URLSearchParams = URLSearchParams;\r\n };\r\n\r\n var checkIfURLSearchParamsSupported = function() {\r\n try {\r\n var URLSearchParams = global.URLSearchParams;\r\n\r\n return (\r\n (new URLSearchParams('?a=1').toString() === 'a=1') &&\r\n (typeof URLSearchParams.prototype.set === 'function') &&\r\n (typeof URLSearchParams.prototype.entries === 'function')\r\n );\r\n } catch (e) {\r\n return false;\r\n }\r\n };\r\n\r\n if (!checkIfURLSearchParamsSupported()) {\r\n polyfillURLSearchParams();\r\n }\r\n\r\n var proto = global.URLSearchParams.prototype;\r\n\r\n if (typeof proto.sort !== 'function') {\r\n proto.sort = function() {\r\n var _this = this;\r\n var items = [];\r\n this.forEach(function(value, name) {\r\n items.push([name, value]);\r\n if (!_this._entries) {\r\n _this.delete(name);\r\n }\r\n });\r\n items.sort(function(a, b) {\r\n if (a[0] < b[0]) {\r\n return -1;\r\n } else if (a[0] > b[0]) {\r\n return +1;\r\n } else {\r\n return 0;\r\n }\r\n });\r\n if (_this._entries) { // force reset because IE keeps keys index\r\n _this._entries = {};\r\n }\r\n for (var i = 0; i < items.length; i++) {\r\n this.append(items[i][0], items[i][1]);\r\n }\r\n };\r\n }\r\n\r\n if (typeof proto._fromString !== 'function') {\r\n Object.defineProperty(proto, '_fromString', {\r\n enumerable: false,\r\n configurable: false,\r\n writable: false,\r\n value: function(searchString) {\r\n if (this._entries) {\r\n this._entries = {};\r\n } else {\r\n var keys = [];\r\n this.forEach(function(value, name) {\r\n keys.push(name);\r\n });\r\n for (var i = 0; i < keys.length; i++) {\r\n this.delete(keys[i]);\r\n }\r\n }\r\n\r\n searchString = searchString.replace(/^\\?/, '');\r\n var attributes = searchString.split('&');\r\n var attribute;\r\n for (var i = 0; i < attributes.length; i++) {\r\n attribute = attributes[i].split('=');\r\n this.append(\r\n deserializeParam(attribute[0]),\r\n (attribute.length > 1) ? deserializeParam(attribute[1]) : ''\r\n );\r\n }\r\n }\r\n });\r\n }\r\n\r\n // HTMLAnchorElement\r\n\r\n})(\r\n (typeof global !== 'undefined') ? global\r\n : ((typeof window !== 'undefined') ? window\r\n : ((typeof self !== 'undefined') ? self : this))\r\n);\r\n\r\n(function(global) {\r\n /**\r\n * Polyfill URL\r\n *\r\n * Inspired from : https://github.com/arv/DOM-URL-Polyfill/blob/master/src/url.js\r\n */\r\n\r\n var checkIfURLIsSupported = function() {\r\n try {\r\n var u = new global.URL('b', 'http://a');\r\n u.pathname = 'c d';\r\n return (u.href === 'http://a/c%20d') && u.searchParams;\r\n } catch (e) {\r\n return false;\r\n }\r\n };\r\n\r\n\r\n var polyfillURL = function() {\r\n var _URL = global.URL;\r\n\r\n var URL = function(url, base) {\r\n if (typeof url !== 'string') url = String(url);\r\n if (base && typeof base !== 'string') base = String(base);\r\n\r\n // Only create another document if the base is different from current location.\r\n var doc = document, baseElement;\r\n if (base && (global.location === void 0 || base !== global.location.href)) {\r\n base = base.toLowerCase();\r\n doc = document.implementation.createHTMLDocument('');\r\n baseElement = doc.createElement('base');\r\n baseElement.href = base;\r\n doc.head.appendChild(baseElement);\r\n try {\r\n if (baseElement.href.indexOf(base) !== 0) throw new Error(baseElement.href);\r\n } catch (err) {\r\n throw new Error('URL unable to set base ' + base + ' due to ' + err);\r\n }\r\n }\r\n\r\n var anchorElement = doc.createElement('a');\r\n anchorElement.href = url;\r\n if (baseElement) {\r\n doc.body.appendChild(anchorElement);\r\n anchorElement.href = anchorElement.href; // force href to refresh\r\n }\r\n\r\n var inputElement = doc.createElement('input');\r\n inputElement.type = 'url';\r\n inputElement.value = url;\r\n\r\n if (anchorElement.protocol === ':' || !/:/.test(anchorElement.href) || (!inputElement.checkValidity() && !base)) {\r\n throw new TypeError('Invalid URL');\r\n }\r\n\r\n Object.defineProperty(this, '_anchorElement', {\r\n value: anchorElement\r\n });\r\n\r\n\r\n // create a linked searchParams which reflect its changes on URL\r\n var searchParams = new global.URLSearchParams(this.search);\r\n var enableSearchUpdate = true;\r\n var enableSearchParamsUpdate = true;\r\n var _this = this;\r\n ['append', 'delete', 'set'].forEach(function(methodName) {\r\n var method = searchParams[methodName];\r\n searchParams[methodName] = function() {\r\n method.apply(searchParams, arguments);\r\n if (enableSearchUpdate) {\r\n enableSearchParamsUpdate = false;\r\n _this.search = searchParams.toString();\r\n enableSearchParamsUpdate = true;\r\n }\r\n };\r\n });\r\n\r\n Object.defineProperty(this, 'searchParams', {\r\n value: searchParams,\r\n enumerable: true\r\n });\r\n\r\n var search = void 0;\r\n Object.defineProperty(this, '_updateSearchParams', {\r\n enumerable: false,\r\n configurable: false,\r\n writable: false,\r\n value: function() {\r\n if (this.search !== search) {\r\n search = this.search;\r\n if (enableSearchParamsUpdate) {\r\n enableSearchUpdate = false;\r\n this.searchParams._fromString(this.search);\r\n enableSearchUpdate = true;\r\n }\r\n }\r\n }\r\n });\r\n };\r\n\r\n var proto = URL.prototype;\r\n\r\n var linkURLWithAnchorAttribute = function(attributeName) {\r\n Object.defineProperty(proto, attributeName, {\r\n get: function() {\r\n return this._anchorElement[attributeName];\r\n },\r\n set: function(value) {\r\n this._anchorElement[attributeName] = value;\r\n },\r\n enumerable: true\r\n });\r\n };\r\n\r\n ['hash', 'host', 'hostname', 'port', 'protocol']\r\n .forEach(function(attributeName) {\r\n linkURLWithAnchorAttribute(attributeName);\r\n });\r\n\r\n Object.defineProperty(proto, 'search', {\r\n get: function() {\r\n return this._anchorElement['search'];\r\n },\r\n set: function(value) {\r\n this._anchorElement['search'] = value;\r\n this._updateSearchParams();\r\n },\r\n enumerable: true\r\n });\r\n\r\n Object.defineProperties(proto, {\r\n\r\n 'toString': {\r\n get: function() {\r\n var _this = this;\r\n return function() {\r\n return _this.href;\r\n };\r\n }\r\n },\r\n\r\n 'href': {\r\n get: function() {\r\n return this._anchorElement.href.replace(/\\?$/, '');\r\n },\r\n set: function(value) {\r\n this._anchorElement.href = value;\r\n this._updateSearchParams();\r\n },\r\n enumerable: true\r\n },\r\n\r\n 'pathname': {\r\n get: function() {\r\n return this._anchorElement.pathname.replace(/(^\\/?)/, '/');\r\n },\r\n set: function(value) {\r\n this._anchorElement.pathname = value;\r\n },\r\n enumerable: true\r\n },\r\n\r\n 'origin': {\r\n get: function() {\r\n // get expected port from protocol\r\n var expectedPort = { 'http:': 80, 'https:': 443, 'ftp:': 21 }[this._anchorElement.protocol];\r\n // add port to origin if, expected port is different than actual port\r\n // and it is not empty f.e http://foo:8080\r\n // 8080 != 80 && 8080 != ''\r\n var addPortToOrigin = this._anchorElement.port != expectedPort &&\r\n this._anchorElement.port !== '';\r\n\r\n return this._anchorElement.protocol +\r\n '//' +\r\n this._anchorElement.hostname +\r\n (addPortToOrigin ? (':' + this._anchorElement.port) : '');\r\n },\r\n enumerable: true\r\n },\r\n\r\n 'password': { // TODO\r\n get: function() {\r\n return '';\r\n },\r\n set: function(value) {\r\n },\r\n enumerable: true\r\n },\r\n\r\n 'username': { // TODO\r\n get: function() {\r\n return '';\r\n },\r\n set: function(value) {\r\n },\r\n enumerable: true\r\n },\r\n });\r\n\r\n URL.createObjectURL = function(blob) {\r\n return _URL.createObjectURL.apply(_URL, arguments);\r\n };\r\n\r\n URL.revokeObjectURL = function(url) {\r\n return _URL.revokeObjectURL.apply(_URL, arguments);\r\n };\r\n\r\n global.URL = URL;\r\n\r\n };\r\n\r\n if (!checkIfURLIsSupported()) {\r\n polyfillURL();\r\n }\r\n\r\n if ((global.location !== void 0) && !('origin' in global.location)) {\r\n var getOrigin = function() {\r\n return global.location.protocol + '//' + global.location.hostname + (global.location.port ? (':' + global.location.port) : '');\r\n };\r\n\r\n try {\r\n Object.defineProperty(global.location, 'origin', {\r\n get: getOrigin,\r\n enumerable: true\r\n });\r\n } catch (e) {\r\n setInterval(function() {\r\n global.location.origin = getOrigin();\r\n }, 100);\r\n }\r\n }\r\n\r\n})(\r\n (typeof global !== 'undefined') ? global\r\n : ((typeof window !== 'undefined') ? window\r\n : ((typeof self !== 'undefined') ? self : this))\r\n);\r\n", "/*! *****************************************************************************\r\nCopyright (c) Microsoft Corporation.\r\n\r\nPermission to use, copy, modify, and/or distribute this software for any\r\npurpose with or without fee is hereby granted.\r\n\r\nTHE SOFTWARE IS PROVIDED \"AS IS\" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH\r\nREGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY\r\nAND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,\r\nINDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM\r\nLOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR\r\nOTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR\r\nPERFORMANCE OF THIS SOFTWARE.\r\n***************************************************************************** */\r\n/* global global, define, System, Reflect, Promise */\r\nvar __extends;\r\nvar __assign;\r\nvar __rest;\r\nvar __decorate;\r\nvar __param;\r\nvar __metadata;\r\nvar __awaiter;\r\nvar __generator;\r\nvar __exportStar;\r\nvar __values;\r\nvar __read;\r\nvar __spread;\r\nvar __spreadArrays;\r\nvar __spreadArray;\r\nvar __await;\r\nvar __asyncGenerator;\r\nvar __asyncDelegator;\r\nvar __asyncValues;\r\nvar __makeTemplateObject;\r\nvar __importStar;\r\nvar __importDefault;\r\nvar __classPrivateFieldGet;\r\nvar __classPrivateFieldSet;\r\nvar __createBinding;\r\n(function (factory) {\r\n var root = typeof global === \"object\" ? global : typeof self === \"object\" ? self : typeof this === \"object\" ? this : {};\r\n if (typeof define === \"function\" && define.amd) {\r\n define(\"tslib\", [\"exports\"], function (exports) { factory(createExporter(root, createExporter(exports))); });\r\n }\r\n else if (typeof module === \"object\" && typeof module.exports === \"object\") {\r\n factory(createExporter(root, createExporter(module.exports)));\r\n }\r\n else {\r\n factory(createExporter(root));\r\n }\r\n function createExporter(exports, previous) {\r\n if (exports !== root) {\r\n if (typeof Object.create === \"function\") {\r\n Object.defineProperty(exports, \"__esModule\", { value: true });\r\n }\r\n else {\r\n exports.__esModule = true;\r\n }\r\n }\r\n return function (id, v) { return exports[id] = previous ? previous(id, v) : v; };\r\n }\r\n})\r\n(function (exporter) {\r\n var extendStatics = Object.setPrototypeOf ||\r\n ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) ||\r\n function (d, b) { for (var p in b) if (Object.prototype.hasOwnProperty.call(b, p)) d[p] = b[p]; };\r\n\r\n __extends = function (d, b) {\r\n if (typeof b !== \"function\" && b !== null)\r\n throw new TypeError(\"Class extends value \" + String(b) + \" is not a constructor or null\");\r\n extendStatics(d, b);\r\n function __() { this.constructor = d; }\r\n d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());\r\n };\r\n\r\n __assign = Object.assign || function (t) {\r\n for (var s, i = 1, n = arguments.length; i < n; i++) {\r\n s = arguments[i];\r\n for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p)) t[p] = s[p];\r\n }\r\n return t;\r\n };\r\n\r\n __rest = function (s, e) {\r\n var t = {};\r\n for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)\r\n t[p] = s[p];\r\n if (s != null && typeof Object.getOwnPropertySymbols === \"function\")\r\n for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {\r\n if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i]))\r\n t[p[i]] = s[p[i]];\r\n }\r\n return t;\r\n };\r\n\r\n __decorate = function (decorators, target, key, desc) {\r\n var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;\r\n if (typeof Reflect === \"object\" && typeof Reflect.decorate === \"function\") r = Reflect.decorate(decorators, target, key, desc);\r\n else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;\r\n return c > 3 && r && Object.defineProperty(target, key, r), r;\r\n };\r\n\r\n __param = function (paramIndex, decorator) {\r\n return function (target, key) { decorator(target, key, paramIndex); }\r\n };\r\n\r\n __metadata = function (metadataKey, metadataValue) {\r\n if (typeof Reflect === \"object\" && typeof Reflect.metadata === \"function\") return Reflect.metadata(metadataKey, metadataValue);\r\n };\r\n\r\n __awaiter = function (thisArg, _arguments, P, generator) {\r\n function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }\r\n return new (P || (P = Promise))(function (resolve, reject) {\r\n function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }\r\n function rejected(value) { try { step(generator[\"throw\"](value)); } catch (e) { reject(e); } }\r\n function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }\r\n step((generator = generator.apply(thisArg, _arguments || [])).next());\r\n });\r\n };\r\n\r\n __generator = function (thisArg, body) {\r\n var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g;\r\n return g = { next: verb(0), \"throw\": verb(1), \"return\": verb(2) }, typeof Symbol === \"function\" && (g[Symbol.iterator] = function() { return this; }), g;\r\n function verb(n) { return function (v) { return step([n, v]); }; }\r\n function step(op) {\r\n if (f) throw new TypeError(\"Generator is already executing.\");\r\n while (_) try {\r\n if (f = 1, y && (t = op[0] & 2 ? y[\"return\"] : op[0] ? y[\"throw\"] || ((t = y[\"return\"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t;\r\n if (y = 0, t) op = [op[0] & 2, t.value];\r\n switch (op[0]) {\r\n case 0: case 1: t = op; break;\r\n case 4: _.label++; return { value: op[1], done: false };\r\n case 5: _.label++; y = op[1]; op = [0]; continue;\r\n case 7: op = _.ops.pop(); _.trys.pop(); continue;\r\n default:\r\n if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; }\r\n if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; }\r\n if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; }\r\n if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; }\r\n if (t[2]) _.ops.pop();\r\n _.trys.pop(); continue;\r\n }\r\n op = body.call(thisArg, _);\r\n } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; }\r\n if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };\r\n }\r\n };\r\n\r\n __exportStar = function(m, o) {\r\n for (var p in m) if (p !== \"default\" && !Object.prototype.hasOwnProperty.call(o, p)) __createBinding(o, m, p);\r\n };\r\n\r\n __createBinding = Object.create ? (function(o, m, k, k2) {\r\n if (k2 === undefined) k2 = k;\r\n Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } });\r\n }) : (function(o, m, k, k2) {\r\n if (k2 === undefined) k2 = k;\r\n o[k2] = m[k];\r\n });\r\n\r\n __values = function (o) {\r\n var s = typeof Symbol === \"function\" && Symbol.iterator, m = s && o[s], i = 0;\r\n if (m) return m.call(o);\r\n if (o && typeof o.length === \"number\") return {\r\n next: function () {\r\n if (o && i >= o.length) o = void 0;\r\n return { value: o && o[i++], done: !o };\r\n }\r\n };\r\n throw new TypeError(s ? \"Object is not iterable.\" : \"Symbol.iterator is not defined.\");\r\n };\r\n\r\n __read = function (o, n) {\r\n var m = typeof Symbol === \"function\" && o[Symbol.iterator];\r\n if (!m) return o;\r\n var i = m.call(o), r, ar = [], e;\r\n try {\r\n while ((n === void 0 || n-- > 0) && !(r = i.next()).done) ar.push(r.value);\r\n }\r\n catch (error) { e = { error: error }; }\r\n finally {\r\n try {\r\n if (r && !r.done && (m = i[\"return\"])) m.call(i);\r\n }\r\n finally { if (e) throw e.error; }\r\n }\r\n return ar;\r\n };\r\n\r\n /** @deprecated */\r\n __spread = function () {\r\n for (var ar = [], i = 0; i < arguments.length; i++)\r\n ar = ar.concat(__read(arguments[i]));\r\n return ar;\r\n };\r\n\r\n /** @deprecated */\r\n __spreadArrays = function () {\r\n for (var s = 0, i = 0, il = arguments.length; i < il; i++) s += arguments[i].length;\r\n for (var r = Array(s), k = 0, i = 0; i < il; i++)\r\n for (var a = arguments[i], j = 0, jl = a.length; j < jl; j++, k++)\r\n r[k] = a[j];\r\n return r;\r\n };\r\n\r\n __spreadArray = function (to, from, pack) {\r\n if (pack || arguments.length === 2) for (var i = 0, l = from.length, ar; i < l; i++) {\r\n if (ar || !(i in from)) {\r\n if (!ar) ar = Array.prototype.slice.call(from, 0, i);\r\n ar[i] = from[i];\r\n }\r\n }\r\n return to.concat(ar || Array.prototype.slice.call(from));\r\n };\r\n\r\n __await = function (v) {\r\n return this instanceof __await ? (this.v = v, this) : new __await(v);\r\n };\r\n\r\n __asyncGenerator = function (thisArg, _arguments, generator) {\r\n if (!Symbol.asyncIterator) throw new TypeError(\"Symbol.asyncIterator is not defined.\");\r\n var g = generator.apply(thisArg, _arguments || []), i, q = [];\r\n return i = {}, verb(\"next\"), verb(\"throw\"), verb(\"return\"), i[Symbol.asyncIterator] = function () { return this; }, i;\r\n function verb(n) { if (g[n]) i[n] = function (v) { return new Promise(function (a, b) { q.push([n, v, a, b]) > 1 || resume(n, v); }); }; }\r\n function resume(n, v) { try { step(g[n](v)); } catch (e) { settle(q[0][3], e); } }\r\n function step(r) { r.value instanceof __await ? Promise.resolve(r.value.v).then(fulfill, reject) : settle(q[0][2], r); }\r\n function fulfill(value) { resume(\"next\", value); }\r\n function reject(value) { resume(\"throw\", value); }\r\n function settle(f, v) { if (f(v), q.shift(), q.length) resume(q[0][0], q[0][1]); }\r\n };\r\n\r\n __asyncDelegator = function (o) {\r\n var i, p;\r\n return i = {}, verb(\"next\"), verb(\"throw\", function (e) { throw e; }), verb(\"return\"), i[Symbol.iterator] = function () { return this; }, i;\r\n function verb(n, f) { i[n] = o[n] ? function (v) { return (p = !p) ? { value: __await(o[n](v)), done: n === \"return\" } : f ? f(v) : v; } : f; }\r\n };\r\n\r\n __asyncValues = function (o) {\r\n if (!Symbol.asyncIterator) throw new TypeError(\"Symbol.asyncIterator is not defined.\");\r\n var m = o[Symbol.asyncIterator], i;\r\n return m ? m.call(o) : (o = typeof __values === \"function\" ? __values(o) : o[Symbol.iterator](), i = {}, verb(\"next\"), verb(\"throw\"), verb(\"return\"), i[Symbol.asyncIterator] = function () { return this; }, i);\r\n function verb(n) { i[n] = o[n] && function (v) { return new Promise(function (resolve, reject) { v = o[n](v), settle(resolve, reject, v.done, v.value); }); }; }\r\n function settle(resolve, reject, d, v) { Promise.resolve(v).then(function(v) { resolve({ value: v, done: d }); }, reject); }\r\n };\r\n\r\n __makeTemplateObject = function (cooked, raw) {\r\n if (Object.defineProperty) { Object.defineProperty(cooked, \"raw\", { value: raw }); } else { cooked.raw = raw; }\r\n return cooked;\r\n };\r\n\r\n var __setModuleDefault = Object.create ? (function(o, v) {\r\n Object.defineProperty(o, \"default\", { enumerable: true, value: v });\r\n }) : function(o, v) {\r\n o[\"default\"] = v;\r\n };\r\n\r\n __importStar = function (mod) {\r\n if (mod && mod.__esModule) return mod;\r\n var result = {};\r\n if (mod != null) for (var k in mod) if (k !== \"default\" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);\r\n __setModuleDefault(result, mod);\r\n return result;\r\n };\r\n\r\n __importDefault = function (mod) {\r\n return (mod && mod.__esModule) ? mod : { \"default\": mod };\r\n };\r\n\r\n __classPrivateFieldGet = function (receiver, state, kind, f) {\r\n if (kind === \"a\" && !f) throw new TypeError(\"Private accessor was defined without a getter\");\r\n if (typeof state === \"function\" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError(\"Cannot read private member from an object whose class did not declare it\");\r\n return kind === \"m\" ? f : kind === \"a\" ? f.call(receiver) : f ? f.value : state.get(receiver);\r\n };\r\n\r\n __classPrivateFieldSet = function (receiver, state, value, kind, f) {\r\n if (kind === \"m\") throw new TypeError(\"Private method is not writable\");\r\n if (kind === \"a\" && !f) throw new TypeError(\"Private accessor was defined without a setter\");\r\n if (typeof state === \"function\" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError(\"Cannot write private member to an object whose class did not declare it\");\r\n return (kind === \"a\" ? f.call(receiver, value) : f ? f.value = value : state.set(receiver, value)), value;\r\n };\r\n\r\n exporter(\"__extends\", __extends);\r\n exporter(\"__assign\", __assign);\r\n exporter(\"__rest\", __rest);\r\n exporter(\"__decorate\", __decorate);\r\n exporter(\"__param\", __param);\r\n exporter(\"__metadata\", __metadata);\r\n exporter(\"__awaiter\", __awaiter);\r\n exporter(\"__generator\", __generator);\r\n exporter(\"__exportStar\", __exportStar);\r\n exporter(\"__createBinding\", __createBinding);\r\n exporter(\"__values\", __values);\r\n exporter(\"__read\", __read);\r\n exporter(\"__spread\", __spread);\r\n exporter(\"__spreadArrays\", __spreadArrays);\r\n exporter(\"__spreadArray\", __spreadArray);\r\n exporter(\"__await\", __await);\r\n exporter(\"__asyncGenerator\", __asyncGenerator);\r\n exporter(\"__asyncDelegator\", __asyncDelegator);\r\n exporter(\"__asyncValues\", __asyncValues);\r\n exporter(\"__makeTemplateObject\", __makeTemplateObject);\r\n exporter(\"__importStar\", __importStar);\r\n exporter(\"__importDefault\", __importDefault);\r\n exporter(\"__classPrivateFieldGet\", __classPrivateFieldGet);\r\n exporter(\"__classPrivateFieldSet\", __classPrivateFieldSet);\r\n});\r\n", "/*!\n * clipboard.js v2.0.11\n * https://clipboardjs.com/\n *\n * Licensed MIT \u00A9 Zeno Rocha\n */\n(function webpackUniversalModuleDefinition(root, factory) {\n\tif(typeof exports === 'object' && typeof module === 'object')\n\t\tmodule.exports = factory();\n\telse if(typeof define === 'function' && define.amd)\n\t\tdefine([], factory);\n\telse if(typeof exports === 'object')\n\t\texports[\"ClipboardJS\"] = factory();\n\telse\n\t\troot[\"ClipboardJS\"] = factory();\n})(this, function() {\nreturn /******/ (function() { // webpackBootstrap\n/******/ \tvar __webpack_modules__ = ({\n\n/***/ 686:\n/***/ (function(__unused_webpack_module, __webpack_exports__, __webpack_require__) {\n\n\"use strict\";\n\n// EXPORTS\n__webpack_require__.d(__webpack_exports__, {\n \"default\": function() { return /* binding */ clipboard; }\n});\n\n// EXTERNAL MODULE: ./node_modules/tiny-emitter/index.js\nvar tiny_emitter = __webpack_require__(279);\nvar tiny_emitter_default = /*#__PURE__*/__webpack_require__.n(tiny_emitter);\n// EXTERNAL MODULE: ./node_modules/good-listener/src/listen.js\nvar listen = __webpack_require__(370);\nvar listen_default = /*#__PURE__*/__webpack_require__.n(listen);\n// EXTERNAL MODULE: ./node_modules/select/src/select.js\nvar src_select = __webpack_require__(817);\nvar select_default = /*#__PURE__*/__webpack_require__.n(src_select);\n;// CONCATENATED MODULE: ./src/common/command.js\n/**\n * Executes a given operation type.\n * @param {String} type\n * @return {Boolean}\n */\nfunction command(type) {\n try {\n return document.execCommand(type);\n } catch (err) {\n return false;\n }\n}\n;// CONCATENATED MODULE: ./src/actions/cut.js\n\n\n/**\n * Cut action wrapper.\n * @param {String|HTMLElement} target\n * @return {String}\n */\n\nvar ClipboardActionCut = function ClipboardActionCut(target) {\n var selectedText = select_default()(target);\n command('cut');\n return selectedText;\n};\n\n/* harmony default export */ var actions_cut = (ClipboardActionCut);\n;// CONCATENATED MODULE: ./src/common/create-fake-element.js\n/**\n * Creates a fake textarea element with a value.\n * @param {String} value\n * @return {HTMLElement}\n */\nfunction createFakeElement(value) {\n var isRTL = document.documentElement.getAttribute('dir') === 'rtl';\n var fakeElement = document.createElement('textarea'); // Prevent zooming on iOS\n\n fakeElement.style.fontSize = '12pt'; // Reset box model\n\n fakeElement.style.border = '0';\n fakeElement.style.padding = '0';\n fakeElement.style.margin = '0'; // Move element out of screen horizontally\n\n fakeElement.style.position = 'absolute';\n fakeElement.style[isRTL ? 'right' : 'left'] = '-9999px'; // Move element to the same position vertically\n\n var yPosition = window.pageYOffset || document.documentElement.scrollTop;\n fakeElement.style.top = \"\".concat(yPosition, \"px\");\n fakeElement.setAttribute('readonly', '');\n fakeElement.value = value;\n return fakeElement;\n}\n;// CONCATENATED MODULE: ./src/actions/copy.js\n\n\n\n/**\n * Create fake copy action wrapper using a fake element.\n * @param {String} target\n * @param {Object} options\n * @return {String}\n */\n\nvar fakeCopyAction = function fakeCopyAction(value, options) {\n var fakeElement = createFakeElement(value);\n options.container.appendChild(fakeElement);\n var selectedText = select_default()(fakeElement);\n command('copy');\n fakeElement.remove();\n return selectedText;\n};\n/**\n * Copy action wrapper.\n * @param {String|HTMLElement} target\n * @param {Object} options\n * @return {String}\n */\n\n\nvar ClipboardActionCopy = function ClipboardActionCopy(target) {\n var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {\n container: document.body\n };\n var selectedText = '';\n\n if (typeof target === 'string') {\n selectedText = fakeCopyAction(target, options);\n } else if (target instanceof HTMLInputElement && !['text', 'search', 'url', 'tel', 'password'].includes(target === null || target === void 0 ? void 0 : target.type)) {\n // If input type doesn't support `setSelectionRange`. Simulate it. https://developer.mozilla.org/en-US/docs/Web/API/HTMLInputElement/setSelectionRange\n selectedText = fakeCopyAction(target.value, options);\n } else {\n selectedText = select_default()(target);\n command('copy');\n }\n\n return selectedText;\n};\n\n/* harmony default export */ var actions_copy = (ClipboardActionCopy);\n;// CONCATENATED MODULE: ./src/actions/default.js\nfunction _typeof(obj) { \"@babel/helpers - typeof\"; if (typeof Symbol === \"function\" && typeof Symbol.iterator === \"symbol\") { _typeof = function _typeof(obj) { return typeof obj; }; } else { _typeof = function _typeof(obj) { return obj && typeof Symbol === \"function\" && obj.constructor === Symbol && obj !== Symbol.prototype ? \"symbol\" : typeof obj; }; } return _typeof(obj); }\n\n\n\n/**\n * Inner function which performs selection from either `text` or `target`\n * properties and then executes copy or cut operations.\n * @param {Object} options\n */\n\nvar ClipboardActionDefault = function ClipboardActionDefault() {\n var options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};\n // Defines base properties passed from constructor.\n var _options$action = options.action,\n action = _options$action === void 0 ? 'copy' : _options$action,\n container = options.container,\n target = options.target,\n text = options.text; // Sets the `action` to be performed which can be either 'copy' or 'cut'.\n\n if (action !== 'copy' && action !== 'cut') {\n throw new Error('Invalid \"action\" value, use either \"copy\" or \"cut\"');\n } // Sets the `target` property using an element that will be have its content copied.\n\n\n if (target !== undefined) {\n if (target && _typeof(target) === 'object' && target.nodeType === 1) {\n if (action === 'copy' && target.hasAttribute('disabled')) {\n throw new Error('Invalid \"target\" attribute. Please use \"readonly\" instead of \"disabled\" attribute');\n }\n\n if (action === 'cut' && (target.hasAttribute('readonly') || target.hasAttribute('disabled'))) {\n throw new Error('Invalid \"target\" attribute. You can\\'t cut text from elements with \"readonly\" or \"disabled\" attributes');\n }\n } else {\n throw new Error('Invalid \"target\" value, use a valid Element');\n }\n } // Define selection strategy based on `text` property.\n\n\n if (text) {\n return actions_copy(text, {\n container: container\n });\n } // Defines which selection strategy based on `target` property.\n\n\n if (target) {\n return action === 'cut' ? actions_cut(target) : actions_copy(target, {\n container: container\n });\n }\n};\n\n/* harmony default export */ var actions_default = (ClipboardActionDefault);\n;// CONCATENATED MODULE: ./src/clipboard.js\nfunction clipboard_typeof(obj) { \"@babel/helpers - typeof\"; if (typeof Symbol === \"function\" && typeof Symbol.iterator === \"symbol\") { clipboard_typeof = function _typeof(obj) { return typeof obj; }; } else { clipboard_typeof = function _typeof(obj) { return obj && typeof Symbol === \"function\" && obj.constructor === Symbol && obj !== Symbol.prototype ? \"symbol\" : typeof obj; }; } return clipboard_typeof(obj); }\n\nfunction _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError(\"Cannot call a class as a function\"); } }\n\nfunction _defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if (\"value\" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } }\n\nfunction _createClass(Constructor, protoProps, staticProps) { if (protoProps) _defineProperties(Constructor.prototype, protoProps); if (staticProps) _defineProperties(Constructor, staticProps); return Constructor; }\n\nfunction _inherits(subClass, superClass) { if (typeof superClass !== \"function\" && superClass !== null) { throw new TypeError(\"Super expression must either be null or a function\"); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, writable: true, configurable: true } }); if (superClass) _setPrototypeOf(subClass, superClass); }\n\nfunction _setPrototypeOf(o, p) { _setPrototypeOf = Object.setPrototypeOf || function _setPrototypeOf(o, p) { o.__proto__ = p; return o; }; return _setPrototypeOf(o, p); }\n\nfunction _createSuper(Derived) { var hasNativeReflectConstruct = _isNativeReflectConstruct(); return function _createSuperInternal() { var Super = _getPrototypeOf(Derived), result; if (hasNativeReflectConstruct) { var NewTarget = _getPrototypeOf(this).constructor; result = Reflect.construct(Super, arguments, NewTarget); } else { result = Super.apply(this, arguments); } return _possibleConstructorReturn(this, result); }; }\n\nfunction _possibleConstructorReturn(self, call) { if (call && (clipboard_typeof(call) === \"object\" || typeof call === \"function\")) { return call; } return _assertThisInitialized(self); }\n\nfunction _assertThisInitialized(self) { if (self === void 0) { throw new ReferenceError(\"this hasn't been initialised - super() hasn't been called\"); } return self; }\n\nfunction _isNativeReflectConstruct() { if (typeof Reflect === \"undefined\" || !Reflect.construct) return false; if (Reflect.construct.sham) return false; if (typeof Proxy === \"function\") return true; try { Date.prototype.toString.call(Reflect.construct(Date, [], function () {})); return true; } catch (e) { return false; } }\n\nfunction _getPrototypeOf(o) { _getPrototypeOf = Object.setPrototypeOf ? Object.getPrototypeOf : function _getPrototypeOf(o) { return o.__proto__ || Object.getPrototypeOf(o); }; return _getPrototypeOf(o); }\n\n\n\n\n\n\n/**\n * Helper function to retrieve attribute value.\n * @param {String} suffix\n * @param {Element} element\n */\n\nfunction getAttributeValue(suffix, element) {\n var attribute = \"data-clipboard-\".concat(suffix);\n\n if (!element.hasAttribute(attribute)) {\n return;\n }\n\n return element.getAttribute(attribute);\n}\n/**\n * Base class which takes one or more elements, adds event listeners to them,\n * and instantiates a new `ClipboardAction` on each click.\n */\n\n\nvar Clipboard = /*#__PURE__*/function (_Emitter) {\n _inherits(Clipboard, _Emitter);\n\n var _super = _createSuper(Clipboard);\n\n /**\n * @param {String|HTMLElement|HTMLCollection|NodeList} trigger\n * @param {Object} options\n */\n function Clipboard(trigger, options) {\n var _this;\n\n _classCallCheck(this, Clipboard);\n\n _this = _super.call(this);\n\n _this.resolveOptions(options);\n\n _this.listenClick(trigger);\n\n return _this;\n }\n /**\n * Defines if attributes would be resolved using internal setter functions\n * or custom functions that were passed in the constructor.\n * @param {Object} options\n */\n\n\n _createClass(Clipboard, [{\n key: \"resolveOptions\",\n value: function resolveOptions() {\n var options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};\n this.action = typeof options.action === 'function' ? options.action : this.defaultAction;\n this.target = typeof options.target === 'function' ? options.target : this.defaultTarget;\n this.text = typeof options.text === 'function' ? options.text : this.defaultText;\n this.container = clipboard_typeof(options.container) === 'object' ? options.container : document.body;\n }\n /**\n * Adds a click event listener to the passed trigger.\n * @param {String|HTMLElement|HTMLCollection|NodeList} trigger\n */\n\n }, {\n key: \"listenClick\",\n value: function listenClick(trigger) {\n var _this2 = this;\n\n this.listener = listen_default()(trigger, 'click', function (e) {\n return _this2.onClick(e);\n });\n }\n /**\n * Defines a new `ClipboardAction` on each click event.\n * @param {Event} e\n */\n\n }, {\n key: \"onClick\",\n value: function onClick(e) {\n var trigger = e.delegateTarget || e.currentTarget;\n var action = this.action(trigger) || 'copy';\n var text = actions_default({\n action: action,\n container: this.container,\n target: this.target(trigger),\n text: this.text(trigger)\n }); // Fires an event based on the copy operation result.\n\n this.emit(text ? 'success' : 'error', {\n action: action,\n text: text,\n trigger: trigger,\n clearSelection: function clearSelection() {\n if (trigger) {\n trigger.focus();\n }\n\n window.getSelection().removeAllRanges();\n }\n });\n }\n /**\n * Default `action` lookup function.\n * @param {Element} trigger\n */\n\n }, {\n key: \"defaultAction\",\n value: function defaultAction(trigger) {\n return getAttributeValue('action', trigger);\n }\n /**\n * Default `target` lookup function.\n * @param {Element} trigger\n */\n\n }, {\n key: \"defaultTarget\",\n value: function defaultTarget(trigger) {\n var selector = getAttributeValue('target', trigger);\n\n if (selector) {\n return document.querySelector(selector);\n }\n }\n /**\n * Allow fire programmatically a copy action\n * @param {String|HTMLElement} target\n * @param {Object} options\n * @returns Text copied.\n */\n\n }, {\n key: \"defaultText\",\n\n /**\n * Default `text` lookup function.\n * @param {Element} trigger\n */\n value: function defaultText(trigger) {\n return getAttributeValue('text', trigger);\n }\n /**\n * Destroy lifecycle.\n */\n\n }, {\n key: \"destroy\",\n value: function destroy() {\n this.listener.destroy();\n }\n }], [{\n key: \"copy\",\n value: function copy(target) {\n var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {\n container: document.body\n };\n return actions_copy(target, options);\n }\n /**\n * Allow fire programmatically a cut action\n * @param {String|HTMLElement} target\n * @returns Text cutted.\n */\n\n }, {\n key: \"cut\",\n value: function cut(target) {\n return actions_cut(target);\n }\n /**\n * Returns the support of the given action, or all actions if no action is\n * given.\n * @param {String} [action]\n */\n\n }, {\n key: \"isSupported\",\n value: function isSupported() {\n var action = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : ['copy', 'cut'];\n var actions = typeof action === 'string' ? [action] : action;\n var support = !!document.queryCommandSupported;\n actions.forEach(function (action) {\n support = support && !!document.queryCommandSupported(action);\n });\n return support;\n }\n }]);\n\n return Clipboard;\n}((tiny_emitter_default()));\n\n/* harmony default export */ var clipboard = (Clipboard);\n\n/***/ }),\n\n/***/ 828:\n/***/ (function(module) {\n\nvar DOCUMENT_NODE_TYPE = 9;\n\n/**\n * A polyfill for Element.matches()\n */\nif (typeof Element !== 'undefined' && !Element.prototype.matches) {\n var proto = Element.prototype;\n\n proto.matches = proto.matchesSelector ||\n proto.mozMatchesSelector ||\n proto.msMatchesSelector ||\n proto.oMatchesSelector ||\n proto.webkitMatchesSelector;\n}\n\n/**\n * Finds the closest parent that matches a selector.\n *\n * @param {Element} element\n * @param {String} selector\n * @return {Function}\n */\nfunction closest (element, selector) {\n while (element && element.nodeType !== DOCUMENT_NODE_TYPE) {\n if (typeof element.matches === 'function' &&\n element.matches(selector)) {\n return element;\n }\n element = element.parentNode;\n }\n}\n\nmodule.exports = closest;\n\n\n/***/ }),\n\n/***/ 438:\n/***/ (function(module, __unused_webpack_exports, __webpack_require__) {\n\nvar closest = __webpack_require__(828);\n\n/**\n * Delegates event to a selector.\n *\n * @param {Element} element\n * @param {String} selector\n * @param {String} type\n * @param {Function} callback\n * @param {Boolean} useCapture\n * @return {Object}\n */\nfunction _delegate(element, selector, type, callback, useCapture) {\n var listenerFn = listener.apply(this, arguments);\n\n element.addEventListener(type, listenerFn, useCapture);\n\n return {\n destroy: function() {\n element.removeEventListener(type, listenerFn, useCapture);\n }\n }\n}\n\n/**\n * Delegates event to a selector.\n *\n * @param {Element|String|Array} [elements]\n * @param {String} selector\n * @param {String} type\n * @param {Function} callback\n * @param {Boolean} useCapture\n * @return {Object}\n */\nfunction delegate(elements, selector, type, callback, useCapture) {\n // Handle the regular Element usage\n if (typeof elements.addEventListener === 'function') {\n return _delegate.apply(null, arguments);\n }\n\n // Handle Element-less usage, it defaults to global delegation\n if (typeof type === 'function') {\n // Use `document` as the first parameter, then apply arguments\n // This is a short way to .unshift `arguments` without running into deoptimizations\n return _delegate.bind(null, document).apply(null, arguments);\n }\n\n // Handle Selector-based usage\n if (typeof elements === 'string') {\n elements = document.querySelectorAll(elements);\n }\n\n // Handle Array-like based usage\n return Array.prototype.map.call(elements, function (element) {\n return _delegate(element, selector, type, callback, useCapture);\n });\n}\n\n/**\n * Finds closest match and invokes callback.\n *\n * @param {Element} element\n * @param {String} selector\n * @param {String} type\n * @param {Function} callback\n * @return {Function}\n */\nfunction listener(element, selector, type, callback) {\n return function(e) {\n e.delegateTarget = closest(e.target, selector);\n\n if (e.delegateTarget) {\n callback.call(element, e);\n }\n }\n}\n\nmodule.exports = delegate;\n\n\n/***/ }),\n\n/***/ 879:\n/***/ (function(__unused_webpack_module, exports) {\n\n/**\n * Check if argument is a HTML element.\n *\n * @param {Object} value\n * @return {Boolean}\n */\nexports.node = function(value) {\n return value !== undefined\n && value instanceof HTMLElement\n && value.nodeType === 1;\n};\n\n/**\n * Check if argument is a list of HTML elements.\n *\n * @param {Object} value\n * @return {Boolean}\n */\nexports.nodeList = function(value) {\n var type = Object.prototype.toString.call(value);\n\n return value !== undefined\n && (type === '[object NodeList]' || type === '[object HTMLCollection]')\n && ('length' in value)\n && (value.length === 0 || exports.node(value[0]));\n};\n\n/**\n * Check if argument is a string.\n *\n * @param {Object} value\n * @return {Boolean}\n */\nexports.string = function(value) {\n return typeof value === 'string'\n || value instanceof String;\n};\n\n/**\n * Check if argument is a function.\n *\n * @param {Object} value\n * @return {Boolean}\n */\nexports.fn = function(value) {\n var type = Object.prototype.toString.call(value);\n\n return type === '[object Function]';\n};\n\n\n/***/ }),\n\n/***/ 370:\n/***/ (function(module, __unused_webpack_exports, __webpack_require__) {\n\nvar is = __webpack_require__(879);\nvar delegate = __webpack_require__(438);\n\n/**\n * Validates all params and calls the right\n * listener function based on its target type.\n *\n * @param {String|HTMLElement|HTMLCollection|NodeList} target\n * @param {String} type\n * @param {Function} callback\n * @return {Object}\n */\nfunction listen(target, type, callback) {\n if (!target && !type && !callback) {\n throw new Error('Missing required arguments');\n }\n\n if (!is.string(type)) {\n throw new TypeError('Second argument must be a String');\n }\n\n if (!is.fn(callback)) {\n throw new TypeError('Third argument must be a Function');\n }\n\n if (is.node(target)) {\n return listenNode(target, type, callback);\n }\n else if (is.nodeList(target)) {\n return listenNodeList(target, type, callback);\n }\n else if (is.string(target)) {\n return listenSelector(target, type, callback);\n }\n else {\n throw new TypeError('First argument must be a String, HTMLElement, HTMLCollection, or NodeList');\n }\n}\n\n/**\n * Adds an event listener to a HTML element\n * and returns a remove listener function.\n *\n * @param {HTMLElement} node\n * @param {String} type\n * @param {Function} callback\n * @return {Object}\n */\nfunction listenNode(node, type, callback) {\n node.addEventListener(type, callback);\n\n return {\n destroy: function() {\n node.removeEventListener(type, callback);\n }\n }\n}\n\n/**\n * Add an event listener to a list of HTML elements\n * and returns a remove listener function.\n *\n * @param {NodeList|HTMLCollection} nodeList\n * @param {String} type\n * @param {Function} callback\n * @return {Object}\n */\nfunction listenNodeList(nodeList, type, callback) {\n Array.prototype.forEach.call(nodeList, function(node) {\n node.addEventListener(type, callback);\n });\n\n return {\n destroy: function() {\n Array.prototype.forEach.call(nodeList, function(node) {\n node.removeEventListener(type, callback);\n });\n }\n }\n}\n\n/**\n * Add an event listener to a selector\n * and returns a remove listener function.\n *\n * @param {String} selector\n * @param {String} type\n * @param {Function} callback\n * @return {Object}\n */\nfunction listenSelector(selector, type, callback) {\n return delegate(document.body, selector, type, callback);\n}\n\nmodule.exports = listen;\n\n\n/***/ }),\n\n/***/ 817:\n/***/ (function(module) {\n\nfunction select(element) {\n var selectedText;\n\n if (element.nodeName === 'SELECT') {\n element.focus();\n\n selectedText = element.value;\n }\n else if (element.nodeName === 'INPUT' || element.nodeName === 'TEXTAREA') {\n var isReadOnly = element.hasAttribute('readonly');\n\n if (!isReadOnly) {\n element.setAttribute('readonly', '');\n }\n\n element.select();\n element.setSelectionRange(0, element.value.length);\n\n if (!isReadOnly) {\n element.removeAttribute('readonly');\n }\n\n selectedText = element.value;\n }\n else {\n if (element.hasAttribute('contenteditable')) {\n element.focus();\n }\n\n var selection = window.getSelection();\n var range = document.createRange();\n\n range.selectNodeContents(element);\n selection.removeAllRanges();\n selection.addRange(range);\n\n selectedText = selection.toString();\n }\n\n return selectedText;\n}\n\nmodule.exports = select;\n\n\n/***/ }),\n\n/***/ 279:\n/***/ (function(module) {\n\nfunction E () {\n // Keep this empty so it's easier to inherit from\n // (via https://github.com/lipsmack from https://github.com/scottcorgan/tiny-emitter/issues/3)\n}\n\nE.prototype = {\n on: function (name, callback, ctx) {\n var e = this.e || (this.e = {});\n\n (e[name] || (e[name] = [])).push({\n fn: callback,\n ctx: ctx\n });\n\n return this;\n },\n\n once: function (name, callback, ctx) {\n var self = this;\n function listener () {\n self.off(name, listener);\n callback.apply(ctx, arguments);\n };\n\n listener._ = callback\n return this.on(name, listener, ctx);\n },\n\n emit: function (name) {\n var data = [].slice.call(arguments, 1);\n var evtArr = ((this.e || (this.e = {}))[name] || []).slice();\n var i = 0;\n var len = evtArr.length;\n\n for (i; i < len; i++) {\n evtArr[i].fn.apply(evtArr[i].ctx, data);\n }\n\n return this;\n },\n\n off: function (name, callback) {\n var e = this.e || (this.e = {});\n var evts = e[name];\n var liveEvents = [];\n\n if (evts && callback) {\n for (var i = 0, len = evts.length; i < len; i++) {\n if (evts[i].fn !== callback && evts[i].fn._ !== callback)\n liveEvents.push(evts[i]);\n }\n }\n\n // Remove event from queue to prevent memory leak\n // Suggested by https://github.com/lazd\n // Ref: https://github.com/scottcorgan/tiny-emitter/commit/c6ebfaa9bc973b33d110a84a307742b7cf94c953#commitcomment-5024910\n\n (liveEvents.length)\n ? e[name] = liveEvents\n : delete e[name];\n\n return this;\n }\n};\n\nmodule.exports = E;\nmodule.exports.TinyEmitter = E;\n\n\n/***/ })\n\n/******/ \t});\n/************************************************************************/\n/******/ \t// The module cache\n/******/ \tvar __webpack_module_cache__ = {};\n/******/ \t\n/******/ \t// The require function\n/******/ \tfunction __webpack_require__(moduleId) {\n/******/ \t\t// Check if module is in cache\n/******/ \t\tif(__webpack_module_cache__[moduleId]) {\n/******/ \t\t\treturn __webpack_module_cache__[moduleId].exports;\n/******/ \t\t}\n/******/ \t\t// Create a new module (and put it into the cache)\n/******/ \t\tvar module = __webpack_module_cache__[moduleId] = {\n/******/ \t\t\t// no module.id needed\n/******/ \t\t\t// no module.loaded needed\n/******/ \t\t\texports: {}\n/******/ \t\t};\n/******/ \t\n/******/ \t\t// Execute the module function\n/******/ \t\t__webpack_modules__[moduleId](module, module.exports, __webpack_require__);\n/******/ \t\n/******/ \t\t// Return the exports of the module\n/******/ \t\treturn module.exports;\n/******/ \t}\n/******/ \t\n/************************************************************************/\n/******/ \t/* webpack/runtime/compat get default export */\n/******/ \t!function() {\n/******/ \t\t// getDefaultExport function for compatibility with non-harmony modules\n/******/ \t\t__webpack_require__.n = function(module) {\n/******/ \t\t\tvar getter = module && module.__esModule ?\n/******/ \t\t\t\tfunction() { return module['default']; } :\n/******/ \t\t\t\tfunction() { return module; };\n/******/ \t\t\t__webpack_require__.d(getter, { a: getter });\n/******/ \t\t\treturn getter;\n/******/ \t\t};\n/******/ \t}();\n/******/ \t\n/******/ \t/* webpack/runtime/define property getters */\n/******/ \t!function() {\n/******/ \t\t// define getter functions for harmony exports\n/******/ \t\t__webpack_require__.d = function(exports, definition) {\n/******/ \t\t\tfor(var key in definition) {\n/******/ \t\t\t\tif(__webpack_require__.o(definition, key) && !__webpack_require__.o(exports, key)) {\n/******/ \t\t\t\t\tObject.defineProperty(exports, key, { enumerable: true, get: definition[key] });\n/******/ \t\t\t\t}\n/******/ \t\t\t}\n/******/ \t\t};\n/******/ \t}();\n/******/ \t\n/******/ \t/* webpack/runtime/hasOwnProperty shorthand */\n/******/ \t!function() {\n/******/ \t\t__webpack_require__.o = function(obj, prop) { return Object.prototype.hasOwnProperty.call(obj, prop); }\n/******/ \t}();\n/******/ \t\n/************************************************************************/\n/******/ \t// module exports must be returned from runtime so entry inlining is disabled\n/******/ \t// startup\n/******/ \t// Load entry module and return exports\n/******/ \treturn __webpack_require__(686);\n/******/ })()\n.default;\n});", "/*!\n * escape-html\n * Copyright(c) 2012-2013 TJ Holowaychuk\n * Copyright(c) 2015 Andreas Lubbe\n * Copyright(c) 2015 Tiancheng \"Timothy\" Gu\n * MIT Licensed\n */\n\n'use strict';\n\n/**\n * Module variables.\n * @private\n */\n\nvar matchHtmlRegExp = /[\"'&<>]/;\n\n/**\n * Module exports.\n * @public\n */\n\nmodule.exports = escapeHtml;\n\n/**\n * Escape special characters in the given string of html.\n *\n * @param {string} string The string to escape for inserting into HTML\n * @return {string}\n * @public\n */\n\nfunction escapeHtml(string) {\n var str = '' + string;\n var match = matchHtmlRegExp.exec(str);\n\n if (!match) {\n return str;\n }\n\n var escape;\n var html = '';\n var index = 0;\n var lastIndex = 0;\n\n for (index = match.index; index < str.length; index++) {\n switch (str.charCodeAt(index)) {\n case 34: // \"\n escape = '"';\n break;\n case 38: // &\n escape = '&';\n break;\n case 39: // '\n escape = ''';\n break;\n case 60: // <\n escape = '<';\n break;\n case 62: // >\n escape = '>';\n break;\n default:\n continue;\n }\n\n if (lastIndex !== index) {\n html += str.substring(lastIndex, index);\n }\n\n lastIndex = index + 1;\n html += escape;\n }\n\n return lastIndex !== index\n ? html + str.substring(lastIndex, index)\n : html;\n}\n", "Array.prototype.flat||Object.defineProperty(Array.prototype,\"flat\",{configurable:!0,value:function r(){var t=isNaN(arguments[0])?1:Number(arguments[0]);return t?Array.prototype.reduce.call(this,function(a,e){return Array.isArray(e)?a.push.apply(a,r.call(e,t-1)):a.push(e),a},[]):Array.prototype.slice.call(this)},writable:!0}),Array.prototype.flatMap||Object.defineProperty(Array.prototype,\"flatMap\",{configurable:!0,value:function(r){return Array.prototype.map.apply(this,arguments).flat()},writable:!0})\n", "/*\n * Copyright (c) 2016-2022 Martin Donath \n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\nimport \"array-flat-polyfill\"\nimport \"focus-visible\"\nimport \"unfetch/polyfill\"\nimport \"url-polyfill\"\n\nimport {\n EMPTY,\n NEVER,\n Subject,\n defer,\n delay,\n filter,\n map,\n merge,\n mergeWith,\n shareReplay,\n switchMap\n} from \"rxjs\"\n\nimport { configuration, feature } from \"./_\"\nimport {\n at,\n getOptionalElement,\n requestJSON,\n setToggle,\n watchDocument,\n watchKeyboard,\n watchLocation,\n watchLocationTarget,\n watchMedia,\n watchPrint,\n watchViewport\n} from \"./browser\"\nimport {\n getComponentElement,\n getComponentElements,\n mountBackToTop,\n mountContent,\n mountDialog,\n mountHeader,\n mountHeaderTitle,\n mountPalette,\n mountSearch,\n mountSearchHiglight,\n mountSidebar,\n mountSource,\n mountTableOfContents,\n mountTabs,\n watchHeader,\n watchMain\n} from \"./components\"\nimport {\n SearchIndex,\n setupClipboardJS,\n setupInstantLoading,\n setupVersionSelector\n} from \"./integrations\"\nimport {\n patchIndeterminate,\n patchScrollfix,\n patchScrolllock\n} from \"./patches\"\nimport \"./polyfills\"\n\n/* ----------------------------------------------------------------------------\n * Application\n * ------------------------------------------------------------------------- */\n\n/* Yay, JavaScript is available */\ndocument.documentElement.classList.remove(\"no-js\")\ndocument.documentElement.classList.add(\"js\")\n\n/* Set up navigation observables and subjects */\nconst document$ = watchDocument()\nconst location$ = watchLocation()\nconst target$ = watchLocationTarget()\nconst keyboard$ = watchKeyboard()\n\n/* Set up media observables */\nconst viewport$ = watchViewport()\nconst tablet$ = watchMedia(\"(min-width: 960px)\")\nconst screen$ = watchMedia(\"(min-width: 1220px)\")\nconst print$ = watchPrint()\n\n/* Retrieve search index, if search is enabled */\nconst config = configuration()\nconst index$ = document.forms.namedItem(\"search\")\n ? __search?.index || requestJSON(\n new URL(\"search/search_index.json\", config.base)\n )\n : NEVER\n\n/* Set up Clipboard.js integration */\nconst alert$ = new Subject()\nsetupClipboardJS({ alert$ })\n\n/* Set up instant loading, if enabled */\nif (feature(\"navigation.instant\"))\n setupInstantLoading({ document$, location$, viewport$ })\n\n/* Set up version selector */\nif (config.version?.provider === \"mike\")\n setupVersionSelector({ document$ })\n\n/* Always close drawer and search on navigation */\nmerge(location$, target$)\n .pipe(\n delay(125)\n )\n .subscribe(() => {\n setToggle(\"drawer\", false)\n setToggle(\"search\", false)\n })\n\n/* Set up global keyboard handlers */\nkeyboard$\n .pipe(\n filter(({ mode }) => mode === \"global\")\n )\n .subscribe(key => {\n switch (key.type) {\n\n /* Go to previous page */\n case \"p\":\n case \",\":\n const prev = getOptionalElement(\"[href][rel=prev]\")\n if (typeof prev !== \"undefined\")\n prev.click()\n break\n\n /* Go to next page */\n case \"n\":\n case \".\":\n const next = getOptionalElement(\"[href][rel=next]\")\n if (typeof next !== \"undefined\")\n next.click()\n break\n }\n })\n\n/* Set up patches */\npatchIndeterminate({ document$, tablet$ })\npatchScrollfix({ document$ })\npatchScrolllock({ viewport$, tablet$ })\n\n/* Set up header and main area observable */\nconst header$ = watchHeader(getComponentElement(\"header\"), { viewport$ })\nconst main$ = document$\n .pipe(\n map(() => getComponentElement(\"main\")),\n switchMap(el => watchMain(el, { viewport$, header$ })),\n shareReplay(1)\n )\n\n/* Set up control component observables */\nconst control$ = merge(\n\n /* Dialog */\n ...getComponentElements(\"dialog\")\n .map(el => mountDialog(el, { alert$ })),\n\n /* Header */\n ...getComponentElements(\"header\")\n .map(el => mountHeader(el, { viewport$, header$, main$ })),\n\n /* Color palette */\n ...getComponentElements(\"palette\")\n .map(el => mountPalette(el)),\n\n /* Search */\n ...getComponentElements(\"search\")\n .map(el => mountSearch(el, { index$, keyboard$ })),\n\n /* Repository information */\n ...getComponentElements(\"source\")\n .map(el => mountSource(el))\n)\n\n/* Set up content component observables */\nconst content$ = defer(() => merge(\n\n /* Content */\n ...getComponentElements(\"content\")\n .map(el => mountContent(el, { target$, print$ })),\n\n /* Search highlighting */\n ...getComponentElements(\"content\")\n .map(el => feature(\"search.highlight\")\n ? mountSearchHiglight(el, { index$, location$ })\n : EMPTY\n ),\n\n /* Header title */\n ...getComponentElements(\"header-title\")\n .map(el => mountHeaderTitle(el, { viewport$, header$ })),\n\n /* Sidebar */\n ...getComponentElements(\"sidebar\")\n .map(el => el.getAttribute(\"data-md-type\") === \"navigation\"\n ? at(screen$, () => mountSidebar(el, { viewport$, header$, main$ }))\n : at(tablet$, () => mountSidebar(el, { viewport$, header$, main$ }))\n ),\n\n /* Navigation tabs */\n ...getComponentElements(\"tabs\")\n .map(el => mountTabs(el, { viewport$, header$ })),\n\n /* Table of contents */\n ...getComponentElements(\"toc\")\n .map(el => mountTableOfContents(el, { viewport$, header$, target$ })),\n\n /* Back-to-top button */\n ...getComponentElements(\"top\")\n .map(el => mountBackToTop(el, { viewport$, header$, main$, target$ }))\n))\n\n/* Set up component observables */\nconst component$ = document$\n .pipe(\n switchMap(() => content$),\n mergeWith(control$),\n shareReplay(1)\n )\n\n/* Subscribe to all components */\ncomponent$.subscribe()\n\n/* ----------------------------------------------------------------------------\n * Exports\n * ------------------------------------------------------------------------- */\n\nwindow.document$ = document$ /* Document observable */\nwindow.location$ = location$ /* Location subject */\nwindow.target$ = target$ /* Location target observable */\nwindow.keyboard$ = keyboard$ /* Keyboard observable */\nwindow.viewport$ = viewport$ /* Viewport observable */\nwindow.tablet$ = tablet$ /* Media tablet observable */\nwindow.screen$ = screen$ /* Media screen observable */\nwindow.print$ = print$ /* Media print observable */\nwindow.alert$ = alert$ /* Alert subject */\nwindow.component$ = component$ /* Component observable */\n", "self.fetch||(self.fetch=function(e,n){return n=n||{},new Promise(function(t,s){var r=new XMLHttpRequest,o=[],u=[],i={},a=function(){return{ok:2==(r.status/100|0),statusText:r.statusText,status:r.status,url:r.responseURL,text:function(){return Promise.resolve(r.responseText)},json:function(){return Promise.resolve(r.responseText).then(JSON.parse)},blob:function(){return Promise.resolve(new Blob([r.response]))},clone:a,headers:{keys:function(){return o},entries:function(){return u},get:function(e){return i[e.toLowerCase()]},has:function(e){return e.toLowerCase()in i}}}};for(var c in r.open(n.method||\"get\",e,!0),r.onload=function(){r.getAllResponseHeaders().replace(/^(.*?):[^\\S\\n]*([\\s\\S]*?)$/gm,function(e,n,t){o.push(n=n.toLowerCase()),u.push([n,t]),i[n]=i[n]?i[n]+\",\"+t:t}),t(a())},r.onerror=s,r.withCredentials=\"include\"==n.credentials,n.headers)r.setRequestHeader(c,n.headers[c]);r.send(n.body||null)})});\n", "import tslib from '../tslib.js';\r\nconst {\r\n __extends,\r\n __assign,\r\n __rest,\r\n __decorate,\r\n __param,\r\n __metadata,\r\n __awaiter,\r\n __generator,\r\n __exportStar,\r\n __createBinding,\r\n __values,\r\n __read,\r\n __spread,\r\n __spreadArrays,\r\n __spreadArray,\r\n __await,\r\n __asyncGenerator,\r\n __asyncDelegator,\r\n __asyncValues,\r\n __makeTemplateObject,\r\n __importStar,\r\n __importDefault,\r\n __classPrivateFieldGet,\r\n __classPrivateFieldSet,\r\n} = tslib;\r\nexport {\r\n __extends,\r\n __assign,\r\n __rest,\r\n __decorate,\r\n __param,\r\n __metadata,\r\n __awaiter,\r\n __generator,\r\n __exportStar,\r\n __createBinding,\r\n __values,\r\n __read,\r\n __spread,\r\n __spreadArrays,\r\n __spreadArray,\r\n __await,\r\n __asyncGenerator,\r\n __asyncDelegator,\r\n __asyncValues,\r\n __makeTemplateObject,\r\n __importStar,\r\n __importDefault,\r\n __classPrivateFieldGet,\r\n __classPrivateFieldSet,\r\n};\r\n", null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, "/*\n * Copyright (c) 2016-2022 Martin Donath \n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\nimport {\n ReplaySubject,\n Subject,\n fromEvent\n} from \"rxjs\"\n\n/* ----------------------------------------------------------------------------\n * Functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Watch document\n *\n * Documents are implemented as subjects, so all downstream observables are\n * automatically updated when a new document is emitted.\n *\n * @returns Document subject\n */\nexport function watchDocument(): Subject {\n const document$ = new ReplaySubject(1)\n fromEvent(document, \"DOMContentLoaded\", { once: true })\n .subscribe(() => document$.next(document))\n\n /* Return document */\n return document$\n}\n", "/*\n * Copyright (c) 2016-2022 Martin Donath \n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\n/* ----------------------------------------------------------------------------\n * Functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Retrieve all elements matching the query selector\n *\n * @template T - Element type\n *\n * @param selector - Query selector\n * @param node - Node of reference\n *\n * @returns Elements\n */\nexport function getElements(\n selector: T, node?: ParentNode\n): HTMLElementTagNameMap[T][]\n\nexport function getElements(\n selector: string, node?: ParentNode\n): T[]\n\nexport function getElements(\n selector: string, node: ParentNode = document\n): T[] {\n return Array.from(node.querySelectorAll(selector))\n}\n\n/**\n * Retrieve an element matching a query selector or throw a reference error\n *\n * Note that this function assumes that the element is present. If unsure if an\n * element is existent, use the `getOptionalElement` function instead.\n *\n * @template T - Element type\n *\n * @param selector - Query selector\n * @param node - Node of reference\n *\n * @returns Element\n */\nexport function getElement(\n selector: T, node?: ParentNode\n): HTMLElementTagNameMap[T]\n\nexport function getElement(\n selector: string, node?: ParentNode\n): T\n\nexport function getElement(\n selector: string, node: ParentNode = document\n): T {\n const el = getOptionalElement(selector, node)\n if (typeof el === \"undefined\")\n throw new ReferenceError(\n `Missing element: expected \"${selector}\" to be present`\n )\n\n /* Return element */\n return el\n}\n\n/* ------------------------------------------------------------------------- */\n\n/**\n * Retrieve an optional element matching the query selector\n *\n * @template T - Element type\n *\n * @param selector - Query selector\n * @param node - Node of reference\n *\n * @returns Element or nothing\n */\nexport function getOptionalElement(\n selector: T, node?: ParentNode\n): HTMLElementTagNameMap[T] | undefined\n\nexport function getOptionalElement(\n selector: string, node?: ParentNode\n): T | undefined\n\nexport function getOptionalElement(\n selector: string, node: ParentNode = document\n): T | undefined {\n return node.querySelector(selector) || undefined\n}\n\n/**\n * Retrieve the currently active element\n *\n * @returns Element or nothing\n */\nexport function getActiveElement(): HTMLElement | undefined {\n return document.activeElement instanceof HTMLElement\n ? document.activeElement || undefined\n : undefined\n}\n", "/*\n * Copyright (c) 2016-2022 Martin Donath \n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\nimport {\n Observable,\n debounceTime,\n distinctUntilChanged,\n fromEvent,\n map,\n merge,\n startWith\n} from \"rxjs\"\n\nimport { getActiveElement } from \"../_\"\n\n/* ----------------------------------------------------------------------------\n * Functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Watch element focus\n *\n * Previously, this function used `focus` and `blur` events to determine whether\n * an element is focused, but this doesn't work if there are focusable elements\n * within the elements itself. A better solutions are `focusin` and `focusout`\n * events, which bubble up the tree and allow for more fine-grained control.\n *\n * `debounceTime` is necessary, because when a focus change happens inside an\n * element, the observable would first emit `false` and then `true` again.\n *\n * @param el - Element\n *\n * @returns Element focus observable\n */\nexport function watchElementFocus(\n el: HTMLElement\n): Observable {\n return merge(\n fromEvent(document.body, \"focusin\"),\n fromEvent(document.body, \"focusout\")\n )\n .pipe(\n debounceTime(1),\n map(() => {\n const active = getActiveElement()\n return typeof active !== \"undefined\"\n ? el.contains(active)\n : false\n }),\n startWith(el === getActiveElement()),\n distinctUntilChanged()\n )\n}\n", "/*\n * Copyright (c) 2016-2022 Martin Donath \n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\nimport {\n Observable,\n animationFrameScheduler,\n auditTime,\n fromEvent,\n map,\n merge,\n startWith\n} from \"rxjs\"\n\n/* ----------------------------------------------------------------------------\n * Types\n * ------------------------------------------------------------------------- */\n\n/**\n * Element offset\n */\nexport interface ElementOffset {\n x: number /* Horizontal offset */\n y: number /* Vertical offset */\n}\n\n/* ----------------------------------------------------------------------------\n * Functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Retrieve element offset\n *\n * @param el - Element\n *\n * @returns Element offset\n */\nexport function getElementOffset(\n el: HTMLElement\n): ElementOffset {\n return {\n x: el.offsetLeft,\n y: el.offsetTop\n }\n}\n\n/* ------------------------------------------------------------------------- */\n\n/**\n * Watch element offset\n *\n * @param el - Element\n *\n * @returns Element offset observable\n */\nexport function watchElementOffset(\n el: HTMLElement\n): Observable {\n return merge(\n fromEvent(window, \"load\"),\n fromEvent(window, \"resize\")\n )\n .pipe(\n auditTime(0, animationFrameScheduler),\n map(() => getElementOffset(el)),\n startWith(getElementOffset(el))\n )\n}\n", "/*\n * Copyright (c) 2016-2022 Martin Donath \n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\nimport {\n Observable,\n animationFrameScheduler,\n auditTime,\n fromEvent,\n map,\n merge,\n startWith\n} from \"rxjs\"\n\nimport { ElementOffset } from \"../_\"\n\n/* ----------------------------------------------------------------------------\n * Functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Retrieve element content offset (= scroll offset)\n *\n * @param el - Element\n *\n * @returns Element content offset\n */\nexport function getElementContentOffset(\n el: HTMLElement\n): ElementOffset {\n return {\n x: el.scrollLeft,\n y: el.scrollTop\n }\n}\n\n/* ------------------------------------------------------------------------- */\n\n/**\n * Watch element content offset\n *\n * @param el - Element\n *\n * @returns Element content offset observable\n */\nexport function watchElementContentOffset(\n el: HTMLElement\n): Observable {\n return merge(\n fromEvent(el, \"scroll\"),\n fromEvent(window, \"resize\")\n )\n .pipe(\n auditTime(0, animationFrameScheduler),\n map(() => getElementContentOffset(el)),\n startWith(getElementContentOffset(el))\n )\n}\n", "/**\r\n * A collection of shims that provide minimal functionality of the ES6 collections.\r\n *\r\n * These implementations are not meant to be used outside of the ResizeObserver\r\n * modules as they cover only a limited range of use cases.\r\n */\r\n/* eslint-disable require-jsdoc, valid-jsdoc */\r\nvar MapShim = (function () {\r\n if (typeof Map !== 'undefined') {\r\n return Map;\r\n }\r\n /**\r\n * Returns index in provided array that matches the specified key.\r\n *\r\n * @param {Array} arr\r\n * @param {*} key\r\n * @returns {number}\r\n */\r\n function getIndex(arr, key) {\r\n var result = -1;\r\n arr.some(function (entry, index) {\r\n if (entry[0] === key) {\r\n result = index;\r\n return true;\r\n }\r\n return false;\r\n });\r\n return result;\r\n }\r\n return /** @class */ (function () {\r\n function class_1() {\r\n this.__entries__ = [];\r\n }\r\n Object.defineProperty(class_1.prototype, \"size\", {\r\n /**\r\n * @returns {boolean}\r\n */\r\n get: function () {\r\n return this.__entries__.length;\r\n },\r\n enumerable: true,\r\n configurable: true\r\n });\r\n /**\r\n * @param {*} key\r\n * @returns {*}\r\n */\r\n class_1.prototype.get = function (key) {\r\n var index = getIndex(this.__entries__, key);\r\n var entry = this.__entries__[index];\r\n return entry && entry[1];\r\n };\r\n /**\r\n * @param {*} key\r\n * @param {*} value\r\n * @returns {void}\r\n */\r\n class_1.prototype.set = function (key, value) {\r\n var index = getIndex(this.__entries__, key);\r\n if (~index) {\r\n this.__entries__[index][1] = value;\r\n }\r\n else {\r\n this.__entries__.push([key, value]);\r\n }\r\n };\r\n /**\r\n * @param {*} key\r\n * @returns {void}\r\n */\r\n class_1.prototype.delete = function (key) {\r\n var entries = this.__entries__;\r\n var index = getIndex(entries, key);\r\n if (~index) {\r\n entries.splice(index, 1);\r\n }\r\n };\r\n /**\r\n * @param {*} key\r\n * @returns {void}\r\n */\r\n class_1.prototype.has = function (key) {\r\n return !!~getIndex(this.__entries__, key);\r\n };\r\n /**\r\n * @returns {void}\r\n */\r\n class_1.prototype.clear = function () {\r\n this.__entries__.splice(0);\r\n };\r\n /**\r\n * @param {Function} callback\r\n * @param {*} [ctx=null]\r\n * @returns {void}\r\n */\r\n class_1.prototype.forEach = function (callback, ctx) {\r\n if (ctx === void 0) { ctx = null; }\r\n for (var _i = 0, _a = this.__entries__; _i < _a.length; _i++) {\r\n var entry = _a[_i];\r\n callback.call(ctx, entry[1], entry[0]);\r\n }\r\n };\r\n return class_1;\r\n }());\r\n})();\n\n/**\r\n * Detects whether window and document objects are available in current environment.\r\n */\r\nvar isBrowser = typeof window !== 'undefined' && typeof document !== 'undefined' && window.document === document;\n\n// Returns global object of a current environment.\r\nvar global$1 = (function () {\r\n if (typeof global !== 'undefined' && global.Math === Math) {\r\n return global;\r\n }\r\n if (typeof self !== 'undefined' && self.Math === Math) {\r\n return self;\r\n }\r\n if (typeof window !== 'undefined' && window.Math === Math) {\r\n return window;\r\n }\r\n // eslint-disable-next-line no-new-func\r\n return Function('return this')();\r\n})();\n\n/**\r\n * A shim for the requestAnimationFrame which falls back to the setTimeout if\r\n * first one is not supported.\r\n *\r\n * @returns {number} Requests' identifier.\r\n */\r\nvar requestAnimationFrame$1 = (function () {\r\n if (typeof requestAnimationFrame === 'function') {\r\n // It's required to use a bounded function because IE sometimes throws\r\n // an \"Invalid calling object\" error if rAF is invoked without the global\r\n // object on the left hand side.\r\n return requestAnimationFrame.bind(global$1);\r\n }\r\n return function (callback) { return setTimeout(function () { return callback(Date.now()); }, 1000 / 60); };\r\n})();\n\n// Defines minimum timeout before adding a trailing call.\r\nvar trailingTimeout = 2;\r\n/**\r\n * Creates a wrapper function which ensures that provided callback will be\r\n * invoked only once during the specified delay period.\r\n *\r\n * @param {Function} callback - Function to be invoked after the delay period.\r\n * @param {number} delay - Delay after which to invoke callback.\r\n * @returns {Function}\r\n */\r\nfunction throttle (callback, delay) {\r\n var leadingCall = false, trailingCall = false, lastCallTime = 0;\r\n /**\r\n * Invokes the original callback function and schedules new invocation if\r\n * the \"proxy\" was called during current request.\r\n *\r\n * @returns {void}\r\n */\r\n function resolvePending() {\r\n if (leadingCall) {\r\n leadingCall = false;\r\n callback();\r\n }\r\n if (trailingCall) {\r\n proxy();\r\n }\r\n }\r\n /**\r\n * Callback invoked after the specified delay. It will further postpone\r\n * invocation of the original function delegating it to the\r\n * requestAnimationFrame.\r\n *\r\n * @returns {void}\r\n */\r\n function timeoutCallback() {\r\n requestAnimationFrame$1(resolvePending);\r\n }\r\n /**\r\n * Schedules invocation of the original function.\r\n *\r\n * @returns {void}\r\n */\r\n function proxy() {\r\n var timeStamp = Date.now();\r\n if (leadingCall) {\r\n // Reject immediately following calls.\r\n if (timeStamp - lastCallTime < trailingTimeout) {\r\n return;\r\n }\r\n // Schedule new call to be in invoked when the pending one is resolved.\r\n // This is important for \"transitions\" which never actually start\r\n // immediately so there is a chance that we might miss one if change\r\n // happens amids the pending invocation.\r\n trailingCall = true;\r\n }\r\n else {\r\n leadingCall = true;\r\n trailingCall = false;\r\n setTimeout(timeoutCallback, delay);\r\n }\r\n lastCallTime = timeStamp;\r\n }\r\n return proxy;\r\n}\n\n// Minimum delay before invoking the update of observers.\r\nvar REFRESH_DELAY = 20;\r\n// A list of substrings of CSS properties used to find transition events that\r\n// might affect dimensions of observed elements.\r\nvar transitionKeys = ['top', 'right', 'bottom', 'left', 'width', 'height', 'size', 'weight'];\r\n// Check if MutationObserver is available.\r\nvar mutationObserverSupported = typeof MutationObserver !== 'undefined';\r\n/**\r\n * Singleton controller class which handles updates of ResizeObserver instances.\r\n */\r\nvar ResizeObserverController = /** @class */ (function () {\r\n /**\r\n * Creates a new instance of ResizeObserverController.\r\n *\r\n * @private\r\n */\r\n function ResizeObserverController() {\r\n /**\r\n * Indicates whether DOM listeners have been added.\r\n *\r\n * @private {boolean}\r\n */\r\n this.connected_ = false;\r\n /**\r\n * Tells that controller has subscribed for Mutation Events.\r\n *\r\n * @private {boolean}\r\n */\r\n this.mutationEventsAdded_ = false;\r\n /**\r\n * Keeps reference to the instance of MutationObserver.\r\n *\r\n * @private {MutationObserver}\r\n */\r\n this.mutationsObserver_ = null;\r\n /**\r\n * A list of connected observers.\r\n *\r\n * @private {Array}\r\n */\r\n this.observers_ = [];\r\n this.onTransitionEnd_ = this.onTransitionEnd_.bind(this);\r\n this.refresh = throttle(this.refresh.bind(this), REFRESH_DELAY);\r\n }\r\n /**\r\n * Adds observer to observers list.\r\n *\r\n * @param {ResizeObserverSPI} observer - Observer to be added.\r\n * @returns {void}\r\n */\r\n ResizeObserverController.prototype.addObserver = function (observer) {\r\n if (!~this.observers_.indexOf(observer)) {\r\n this.observers_.push(observer);\r\n }\r\n // Add listeners if they haven't been added yet.\r\n if (!this.connected_) {\r\n this.connect_();\r\n }\r\n };\r\n /**\r\n * Removes observer from observers list.\r\n *\r\n * @param {ResizeObserverSPI} observer - Observer to be removed.\r\n * @returns {void}\r\n */\r\n ResizeObserverController.prototype.removeObserver = function (observer) {\r\n var observers = this.observers_;\r\n var index = observers.indexOf(observer);\r\n // Remove observer if it's present in registry.\r\n if (~index) {\r\n observers.splice(index, 1);\r\n }\r\n // Remove listeners if controller has no connected observers.\r\n if (!observers.length && this.connected_) {\r\n this.disconnect_();\r\n }\r\n };\r\n /**\r\n * Invokes the update of observers. It will continue running updates insofar\r\n * it detects changes.\r\n *\r\n * @returns {void}\r\n */\r\n ResizeObserverController.prototype.refresh = function () {\r\n var changesDetected = this.updateObservers_();\r\n // Continue running updates if changes have been detected as there might\r\n // be future ones caused by CSS transitions.\r\n if (changesDetected) {\r\n this.refresh();\r\n }\r\n };\r\n /**\r\n * Updates every observer from observers list and notifies them of queued\r\n * entries.\r\n *\r\n * @private\r\n * @returns {boolean} Returns \"true\" if any observer has detected changes in\r\n * dimensions of it's elements.\r\n */\r\n ResizeObserverController.prototype.updateObservers_ = function () {\r\n // Collect observers that have active observations.\r\n var activeObservers = this.observers_.filter(function (observer) {\r\n return observer.gatherActive(), observer.hasActive();\r\n });\r\n // Deliver notifications in a separate cycle in order to avoid any\r\n // collisions between observers, e.g. when multiple instances of\r\n // ResizeObserver are tracking the same element and the callback of one\r\n // of them changes content dimensions of the observed target. Sometimes\r\n // this may result in notifications being blocked for the rest of observers.\r\n activeObservers.forEach(function (observer) { return observer.broadcastActive(); });\r\n return activeObservers.length > 0;\r\n };\r\n /**\r\n * Initializes DOM listeners.\r\n *\r\n * @private\r\n * @returns {void}\r\n */\r\n ResizeObserverController.prototype.connect_ = function () {\r\n // Do nothing if running in a non-browser environment or if listeners\r\n // have been already added.\r\n if (!isBrowser || this.connected_) {\r\n return;\r\n }\r\n // Subscription to the \"Transitionend\" event is used as a workaround for\r\n // delayed transitions. This way it's possible to capture at least the\r\n // final state of an element.\r\n document.addEventListener('transitionend', this.onTransitionEnd_);\r\n window.addEventListener('resize', this.refresh);\r\n if (mutationObserverSupported) {\r\n this.mutationsObserver_ = new MutationObserver(this.refresh);\r\n this.mutationsObserver_.observe(document, {\r\n attributes: true,\r\n childList: true,\r\n characterData: true,\r\n subtree: true\r\n });\r\n }\r\n else {\r\n document.addEventListener('DOMSubtreeModified', this.refresh);\r\n this.mutationEventsAdded_ = true;\r\n }\r\n this.connected_ = true;\r\n };\r\n /**\r\n * Removes DOM listeners.\r\n *\r\n * @private\r\n * @returns {void}\r\n */\r\n ResizeObserverController.prototype.disconnect_ = function () {\r\n // Do nothing if running in a non-browser environment or if listeners\r\n // have been already removed.\r\n if (!isBrowser || !this.connected_) {\r\n return;\r\n }\r\n document.removeEventListener('transitionend', this.onTransitionEnd_);\r\n window.removeEventListener('resize', this.refresh);\r\n if (this.mutationsObserver_) {\r\n this.mutationsObserver_.disconnect();\r\n }\r\n if (this.mutationEventsAdded_) {\r\n document.removeEventListener('DOMSubtreeModified', this.refresh);\r\n }\r\n this.mutationsObserver_ = null;\r\n this.mutationEventsAdded_ = false;\r\n this.connected_ = false;\r\n };\r\n /**\r\n * \"Transitionend\" event handler.\r\n *\r\n * @private\r\n * @param {TransitionEvent} event\r\n * @returns {void}\r\n */\r\n ResizeObserverController.prototype.onTransitionEnd_ = function (_a) {\r\n var _b = _a.propertyName, propertyName = _b === void 0 ? '' : _b;\r\n // Detect whether transition may affect dimensions of an element.\r\n var isReflowProperty = transitionKeys.some(function (key) {\r\n return !!~propertyName.indexOf(key);\r\n });\r\n if (isReflowProperty) {\r\n this.refresh();\r\n }\r\n };\r\n /**\r\n * Returns instance of the ResizeObserverController.\r\n *\r\n * @returns {ResizeObserverController}\r\n */\r\n ResizeObserverController.getInstance = function () {\r\n if (!this.instance_) {\r\n this.instance_ = new ResizeObserverController();\r\n }\r\n return this.instance_;\r\n };\r\n /**\r\n * Holds reference to the controller's instance.\r\n *\r\n * @private {ResizeObserverController}\r\n */\r\n ResizeObserverController.instance_ = null;\r\n return ResizeObserverController;\r\n}());\n\n/**\r\n * Defines non-writable/enumerable properties of the provided target object.\r\n *\r\n * @param {Object} target - Object for which to define properties.\r\n * @param {Object} props - Properties to be defined.\r\n * @returns {Object} Target object.\r\n */\r\nvar defineConfigurable = (function (target, props) {\r\n for (var _i = 0, _a = Object.keys(props); _i < _a.length; _i++) {\r\n var key = _a[_i];\r\n Object.defineProperty(target, key, {\r\n value: props[key],\r\n enumerable: false,\r\n writable: false,\r\n configurable: true\r\n });\r\n }\r\n return target;\r\n});\n\n/**\r\n * Returns the global object associated with provided element.\r\n *\r\n * @param {Object} target\r\n * @returns {Object}\r\n */\r\nvar getWindowOf = (function (target) {\r\n // Assume that the element is an instance of Node, which means that it\r\n // has the \"ownerDocument\" property from which we can retrieve a\r\n // corresponding global object.\r\n var ownerGlobal = target && target.ownerDocument && target.ownerDocument.defaultView;\r\n // Return the local global object if it's not possible extract one from\r\n // provided element.\r\n return ownerGlobal || global$1;\r\n});\n\n// Placeholder of an empty content rectangle.\r\nvar emptyRect = createRectInit(0, 0, 0, 0);\r\n/**\r\n * Converts provided string to a number.\r\n *\r\n * @param {number|string} value\r\n * @returns {number}\r\n */\r\nfunction toFloat(value) {\r\n return parseFloat(value) || 0;\r\n}\r\n/**\r\n * Extracts borders size from provided styles.\r\n *\r\n * @param {CSSStyleDeclaration} styles\r\n * @param {...string} positions - Borders positions (top, right, ...)\r\n * @returns {number}\r\n */\r\nfunction getBordersSize(styles) {\r\n var positions = [];\r\n for (var _i = 1; _i < arguments.length; _i++) {\r\n positions[_i - 1] = arguments[_i];\r\n }\r\n return positions.reduce(function (size, position) {\r\n var value = styles['border-' + position + '-width'];\r\n return size + toFloat(value);\r\n }, 0);\r\n}\r\n/**\r\n * Extracts paddings sizes from provided styles.\r\n *\r\n * @param {CSSStyleDeclaration} styles\r\n * @returns {Object} Paddings box.\r\n */\r\nfunction getPaddings(styles) {\r\n var positions = ['top', 'right', 'bottom', 'left'];\r\n var paddings = {};\r\n for (var _i = 0, positions_1 = positions; _i < positions_1.length; _i++) {\r\n var position = positions_1[_i];\r\n var value = styles['padding-' + position];\r\n paddings[position] = toFloat(value);\r\n }\r\n return paddings;\r\n}\r\n/**\r\n * Calculates content rectangle of provided SVG element.\r\n *\r\n * @param {SVGGraphicsElement} target - Element content rectangle of which needs\r\n * to be calculated.\r\n * @returns {DOMRectInit}\r\n */\r\nfunction getSVGContentRect(target) {\r\n var bbox = target.getBBox();\r\n return createRectInit(0, 0, bbox.width, bbox.height);\r\n}\r\n/**\r\n * Calculates content rectangle of provided HTMLElement.\r\n *\r\n * @param {HTMLElement} target - Element for which to calculate the content rectangle.\r\n * @returns {DOMRectInit}\r\n */\r\nfunction getHTMLElementContentRect(target) {\r\n // Client width & height properties can't be\r\n // used exclusively as they provide rounded values.\r\n var clientWidth = target.clientWidth, clientHeight = target.clientHeight;\r\n // By this condition we can catch all non-replaced inline, hidden and\r\n // detached elements. Though elements with width & height properties less\r\n // than 0.5 will be discarded as well.\r\n //\r\n // Without it we would need to implement separate methods for each of\r\n // those cases and it's not possible to perform a precise and performance\r\n // effective test for hidden elements. E.g. even jQuery's ':visible' filter\r\n // gives wrong results for elements with width & height less than 0.5.\r\n if (!clientWidth && !clientHeight) {\r\n return emptyRect;\r\n }\r\n var styles = getWindowOf(target).getComputedStyle(target);\r\n var paddings = getPaddings(styles);\r\n var horizPad = paddings.left + paddings.right;\r\n var vertPad = paddings.top + paddings.bottom;\r\n // Computed styles of width & height are being used because they are the\r\n // only dimensions available to JS that contain non-rounded values. It could\r\n // be possible to utilize the getBoundingClientRect if only it's data wasn't\r\n // affected by CSS transformations let alone paddings, borders and scroll bars.\r\n var width = toFloat(styles.width), height = toFloat(styles.height);\r\n // Width & height include paddings and borders when the 'border-box' box\r\n // model is applied (except for IE).\r\n if (styles.boxSizing === 'border-box') {\r\n // Following conditions are required to handle Internet Explorer which\r\n // doesn't include paddings and borders to computed CSS dimensions.\r\n //\r\n // We can say that if CSS dimensions + paddings are equal to the \"client\"\r\n // properties then it's either IE, and thus we don't need to subtract\r\n // anything, or an element merely doesn't have paddings/borders styles.\r\n if (Math.round(width + horizPad) !== clientWidth) {\r\n width -= getBordersSize(styles, 'left', 'right') + horizPad;\r\n }\r\n if (Math.round(height + vertPad) !== clientHeight) {\r\n height -= getBordersSize(styles, 'top', 'bottom') + vertPad;\r\n }\r\n }\r\n // Following steps can't be applied to the document's root element as its\r\n // client[Width/Height] properties represent viewport area of the window.\r\n // Besides, it's as well not necessary as the itself neither has\r\n // rendered scroll bars nor it can be clipped.\r\n if (!isDocumentElement(target)) {\r\n // In some browsers (only in Firefox, actually) CSS width & height\r\n // include scroll bars size which can be removed at this step as scroll\r\n // bars are the only difference between rounded dimensions + paddings\r\n // and \"client\" properties, though that is not always true in Chrome.\r\n var vertScrollbar = Math.round(width + horizPad) - clientWidth;\r\n var horizScrollbar = Math.round(height + vertPad) - clientHeight;\r\n // Chrome has a rather weird rounding of \"client\" properties.\r\n // E.g. for an element with content width of 314.2px it sometimes gives\r\n // the client width of 315px and for the width of 314.7px it may give\r\n // 314px. And it doesn't happen all the time. So just ignore this delta\r\n // as a non-relevant.\r\n if (Math.abs(vertScrollbar) !== 1) {\r\n width -= vertScrollbar;\r\n }\r\n if (Math.abs(horizScrollbar) !== 1) {\r\n height -= horizScrollbar;\r\n }\r\n }\r\n return createRectInit(paddings.left, paddings.top, width, height);\r\n}\r\n/**\r\n * Checks whether provided element is an instance of the SVGGraphicsElement.\r\n *\r\n * @param {Element} target - Element to be checked.\r\n * @returns {boolean}\r\n */\r\nvar isSVGGraphicsElement = (function () {\r\n // Some browsers, namely IE and Edge, don't have the SVGGraphicsElement\r\n // interface.\r\n if (typeof SVGGraphicsElement !== 'undefined') {\r\n return function (target) { return target instanceof getWindowOf(target).SVGGraphicsElement; };\r\n }\r\n // If it's so, then check that element is at least an instance of the\r\n // SVGElement and that it has the \"getBBox\" method.\r\n // eslint-disable-next-line no-extra-parens\r\n return function (target) { return (target instanceof getWindowOf(target).SVGElement &&\r\n typeof target.getBBox === 'function'); };\r\n})();\r\n/**\r\n * Checks whether provided element is a document element ().\r\n *\r\n * @param {Element} target - Element to be checked.\r\n * @returns {boolean}\r\n */\r\nfunction isDocumentElement(target) {\r\n return target === getWindowOf(target).document.documentElement;\r\n}\r\n/**\r\n * Calculates an appropriate content rectangle for provided html or svg element.\r\n *\r\n * @param {Element} target - Element content rectangle of which needs to be calculated.\r\n * @returns {DOMRectInit}\r\n */\r\nfunction getContentRect(target) {\r\n if (!isBrowser) {\r\n return emptyRect;\r\n }\r\n if (isSVGGraphicsElement(target)) {\r\n return getSVGContentRect(target);\r\n }\r\n return getHTMLElementContentRect(target);\r\n}\r\n/**\r\n * Creates rectangle with an interface of the DOMRectReadOnly.\r\n * Spec: https://drafts.fxtf.org/geometry/#domrectreadonly\r\n *\r\n * @param {DOMRectInit} rectInit - Object with rectangle's x/y coordinates and dimensions.\r\n * @returns {DOMRectReadOnly}\r\n */\r\nfunction createReadOnlyRect(_a) {\r\n var x = _a.x, y = _a.y, width = _a.width, height = _a.height;\r\n // If DOMRectReadOnly is available use it as a prototype for the rectangle.\r\n var Constr = typeof DOMRectReadOnly !== 'undefined' ? DOMRectReadOnly : Object;\r\n var rect = Object.create(Constr.prototype);\r\n // Rectangle's properties are not writable and non-enumerable.\r\n defineConfigurable(rect, {\r\n x: x, y: y, width: width, height: height,\r\n top: y,\r\n right: x + width,\r\n bottom: height + y,\r\n left: x\r\n });\r\n return rect;\r\n}\r\n/**\r\n * Creates DOMRectInit object based on the provided dimensions and the x/y coordinates.\r\n * Spec: https://drafts.fxtf.org/geometry/#dictdef-domrectinit\r\n *\r\n * @param {number} x - X coordinate.\r\n * @param {number} y - Y coordinate.\r\n * @param {number} width - Rectangle's width.\r\n * @param {number} height - Rectangle's height.\r\n * @returns {DOMRectInit}\r\n */\r\nfunction createRectInit(x, y, width, height) {\r\n return { x: x, y: y, width: width, height: height };\r\n}\n\n/**\r\n * Class that is responsible for computations of the content rectangle of\r\n * provided DOM element and for keeping track of it's changes.\r\n */\r\nvar ResizeObservation = /** @class */ (function () {\r\n /**\r\n * Creates an instance of ResizeObservation.\r\n *\r\n * @param {Element} target - Element to be observed.\r\n */\r\n function ResizeObservation(target) {\r\n /**\r\n * Broadcasted width of content rectangle.\r\n *\r\n * @type {number}\r\n */\r\n this.broadcastWidth = 0;\r\n /**\r\n * Broadcasted height of content rectangle.\r\n *\r\n * @type {number}\r\n */\r\n this.broadcastHeight = 0;\r\n /**\r\n * Reference to the last observed content rectangle.\r\n *\r\n * @private {DOMRectInit}\r\n */\r\n this.contentRect_ = createRectInit(0, 0, 0, 0);\r\n this.target = target;\r\n }\r\n /**\r\n * Updates content rectangle and tells whether it's width or height properties\r\n * have changed since the last broadcast.\r\n *\r\n * @returns {boolean}\r\n */\r\n ResizeObservation.prototype.isActive = function () {\r\n var rect = getContentRect(this.target);\r\n this.contentRect_ = rect;\r\n return (rect.width !== this.broadcastWidth ||\r\n rect.height !== this.broadcastHeight);\r\n };\r\n /**\r\n * Updates 'broadcastWidth' and 'broadcastHeight' properties with a data\r\n * from the corresponding properties of the last observed content rectangle.\r\n *\r\n * @returns {DOMRectInit} Last observed content rectangle.\r\n */\r\n ResizeObservation.prototype.broadcastRect = function () {\r\n var rect = this.contentRect_;\r\n this.broadcastWidth = rect.width;\r\n this.broadcastHeight = rect.height;\r\n return rect;\r\n };\r\n return ResizeObservation;\r\n}());\n\nvar ResizeObserverEntry = /** @class */ (function () {\r\n /**\r\n * Creates an instance of ResizeObserverEntry.\r\n *\r\n * @param {Element} target - Element that is being observed.\r\n * @param {DOMRectInit} rectInit - Data of the element's content rectangle.\r\n */\r\n function ResizeObserverEntry(target, rectInit) {\r\n var contentRect = createReadOnlyRect(rectInit);\r\n // According to the specification following properties are not writable\r\n // and are also not enumerable in the native implementation.\r\n //\r\n // Property accessors are not being used as they'd require to define a\r\n // private WeakMap storage which may cause memory leaks in browsers that\r\n // don't support this type of collections.\r\n defineConfigurable(this, { target: target, contentRect: contentRect });\r\n }\r\n return ResizeObserverEntry;\r\n}());\n\nvar ResizeObserverSPI = /** @class */ (function () {\r\n /**\r\n * Creates a new instance of ResizeObserver.\r\n *\r\n * @param {ResizeObserverCallback} callback - Callback function that is invoked\r\n * when one of the observed elements changes it's content dimensions.\r\n * @param {ResizeObserverController} controller - Controller instance which\r\n * is responsible for the updates of observer.\r\n * @param {ResizeObserver} callbackCtx - Reference to the public\r\n * ResizeObserver instance which will be passed to callback function.\r\n */\r\n function ResizeObserverSPI(callback, controller, callbackCtx) {\r\n /**\r\n * Collection of resize observations that have detected changes in dimensions\r\n * of elements.\r\n *\r\n * @private {Array}\r\n */\r\n this.activeObservations_ = [];\r\n /**\r\n * Registry of the ResizeObservation instances.\r\n *\r\n * @private {Map}\r\n */\r\n this.observations_ = new MapShim();\r\n if (typeof callback !== 'function') {\r\n throw new TypeError('The callback provided as parameter 1 is not a function.');\r\n }\r\n this.callback_ = callback;\r\n this.controller_ = controller;\r\n this.callbackCtx_ = callbackCtx;\r\n }\r\n /**\r\n * Starts observing provided element.\r\n *\r\n * @param {Element} target - Element to be observed.\r\n * @returns {void}\r\n */\r\n ResizeObserverSPI.prototype.observe = function (target) {\r\n if (!arguments.length) {\r\n throw new TypeError('1 argument required, but only 0 present.');\r\n }\r\n // Do nothing if current environment doesn't have the Element interface.\r\n if (typeof Element === 'undefined' || !(Element instanceof Object)) {\r\n return;\r\n }\r\n if (!(target instanceof getWindowOf(target).Element)) {\r\n throw new TypeError('parameter 1 is not of type \"Element\".');\r\n }\r\n var observations = this.observations_;\r\n // Do nothing if element is already being observed.\r\n if (observations.has(target)) {\r\n return;\r\n }\r\n observations.set(target, new ResizeObservation(target));\r\n this.controller_.addObserver(this);\r\n // Force the update of observations.\r\n this.controller_.refresh();\r\n };\r\n /**\r\n * Stops observing provided element.\r\n *\r\n * @param {Element} target - Element to stop observing.\r\n * @returns {void}\r\n */\r\n ResizeObserverSPI.prototype.unobserve = function (target) {\r\n if (!arguments.length) {\r\n throw new TypeError('1 argument required, but only 0 present.');\r\n }\r\n // Do nothing if current environment doesn't have the Element interface.\r\n if (typeof Element === 'undefined' || !(Element instanceof Object)) {\r\n return;\r\n }\r\n if (!(target instanceof getWindowOf(target).Element)) {\r\n throw new TypeError('parameter 1 is not of type \"Element\".');\r\n }\r\n var observations = this.observations_;\r\n // Do nothing if element is not being observed.\r\n if (!observations.has(target)) {\r\n return;\r\n }\r\n observations.delete(target);\r\n if (!observations.size) {\r\n this.controller_.removeObserver(this);\r\n }\r\n };\r\n /**\r\n * Stops observing all elements.\r\n *\r\n * @returns {void}\r\n */\r\n ResizeObserverSPI.prototype.disconnect = function () {\r\n this.clearActive();\r\n this.observations_.clear();\r\n this.controller_.removeObserver(this);\r\n };\r\n /**\r\n * Collects observation instances the associated element of which has changed\r\n * it's content rectangle.\r\n *\r\n * @returns {void}\r\n */\r\n ResizeObserverSPI.prototype.gatherActive = function () {\r\n var _this = this;\r\n this.clearActive();\r\n this.observations_.forEach(function (observation) {\r\n if (observation.isActive()) {\r\n _this.activeObservations_.push(observation);\r\n }\r\n });\r\n };\r\n /**\r\n * Invokes initial callback function with a list of ResizeObserverEntry\r\n * instances collected from active resize observations.\r\n *\r\n * @returns {void}\r\n */\r\n ResizeObserverSPI.prototype.broadcastActive = function () {\r\n // Do nothing if observer doesn't have active observations.\r\n if (!this.hasActive()) {\r\n return;\r\n }\r\n var ctx = this.callbackCtx_;\r\n // Create ResizeObserverEntry instance for every active observation.\r\n var entries = this.activeObservations_.map(function (observation) {\r\n return new ResizeObserverEntry(observation.target, observation.broadcastRect());\r\n });\r\n this.callback_.call(ctx, entries, ctx);\r\n this.clearActive();\r\n };\r\n /**\r\n * Clears the collection of active observations.\r\n *\r\n * @returns {void}\r\n */\r\n ResizeObserverSPI.prototype.clearActive = function () {\r\n this.activeObservations_.splice(0);\r\n };\r\n /**\r\n * Tells whether observer has active observations.\r\n *\r\n * @returns {boolean}\r\n */\r\n ResizeObserverSPI.prototype.hasActive = function () {\r\n return this.activeObservations_.length > 0;\r\n };\r\n return ResizeObserverSPI;\r\n}());\n\n// Registry of internal observers. If WeakMap is not available use current shim\r\n// for the Map collection as it has all required methods and because WeakMap\r\n// can't be fully polyfilled anyway.\r\nvar observers = typeof WeakMap !== 'undefined' ? new WeakMap() : new MapShim();\r\n/**\r\n * ResizeObserver API. Encapsulates the ResizeObserver SPI implementation\r\n * exposing only those methods and properties that are defined in the spec.\r\n */\r\nvar ResizeObserver = /** @class */ (function () {\r\n /**\r\n * Creates a new instance of ResizeObserver.\r\n *\r\n * @param {ResizeObserverCallback} callback - Callback that is invoked when\r\n * dimensions of the observed elements change.\r\n */\r\n function ResizeObserver(callback) {\r\n if (!(this instanceof ResizeObserver)) {\r\n throw new TypeError('Cannot call a class as a function.');\r\n }\r\n if (!arguments.length) {\r\n throw new TypeError('1 argument required, but only 0 present.');\r\n }\r\n var controller = ResizeObserverController.getInstance();\r\n var observer = new ResizeObserverSPI(callback, controller, this);\r\n observers.set(this, observer);\r\n }\r\n return ResizeObserver;\r\n}());\r\n// Expose public methods of ResizeObserver.\r\n[\r\n 'observe',\r\n 'unobserve',\r\n 'disconnect'\r\n].forEach(function (method) {\r\n ResizeObserver.prototype[method] = function () {\r\n var _a;\r\n return (_a = observers.get(this))[method].apply(_a, arguments);\r\n };\r\n});\n\nvar index = (function () {\r\n // Export existing implementation if available.\r\n if (typeof global$1.ResizeObserver !== 'undefined') {\r\n return global$1.ResizeObserver;\r\n }\r\n return ResizeObserver;\r\n})();\n\nexport default index;\n", "/*\n * Copyright (c) 2016-2022 Martin Donath \n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\nimport ResizeObserver from \"resize-observer-polyfill\"\nimport {\n NEVER,\n Observable,\n Subject,\n defer,\n filter,\n finalize,\n map,\n merge,\n of,\n shareReplay,\n startWith,\n switchMap,\n tap\n} from \"rxjs\"\n\n/* ----------------------------------------------------------------------------\n * Types\n * ------------------------------------------------------------------------- */\n\n/**\n * Element offset\n */\nexport interface ElementSize {\n width: number /* Element width */\n height: number /* Element height */\n}\n\n/* ----------------------------------------------------------------------------\n * Data\n * ------------------------------------------------------------------------- */\n\n/**\n * Resize observer entry subject\n */\nconst entry$ = new Subject()\n\n/**\n * Resize observer observable\n *\n * This observable will create a `ResizeObserver` on the first subscription\n * and will automatically terminate it when there are no more subscribers.\n * It's quite important to centralize observation in a single `ResizeObserver`,\n * as the performance difference can be quite dramatic, as the link shows.\n *\n * @see https://bit.ly/3iIYfEm - Google Groups on performance\n */\nconst observer$ = defer(() => of(\n new ResizeObserver(entries => {\n for (const entry of entries)\n entry$.next(entry)\n })\n))\n .pipe(\n switchMap(observer => merge(NEVER, of(observer))\n .pipe(\n finalize(() => observer.disconnect())\n )\n ),\n shareReplay(1)\n )\n\n/* ----------------------------------------------------------------------------\n * Functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Retrieve element size\n *\n * @param el - Element\n *\n * @returns Element size\n */\nexport function getElementSize(\n el: HTMLElement\n): ElementSize {\n return {\n width: el.offsetWidth,\n height: el.offsetHeight\n }\n}\n\n/* ------------------------------------------------------------------------- */\n\n/**\n * Watch element size\n *\n * This function returns an observable that subscribes to a single internal\n * instance of `ResizeObserver` upon subscription, and emit resize events until\n * termination. Note that this function should not be called with the same\n * element twice, as the first unsubscription will terminate observation.\n *\n * Sadly, we can't use the `DOMRect` objects returned by the observer, because\n * we need the emitted values to be consistent with `getElementSize`, which will\n * return the used values (rounded) and not actual values (unrounded). Thus, we\n * use the `offset*` properties. See the linked GitHub issue.\n *\n * @see https://bit.ly/3m0k3he - GitHub issue\n *\n * @param el - Element\n *\n * @returns Element size observable\n */\nexport function watchElementSize(\n el: HTMLElement\n): Observable {\n return observer$\n .pipe(\n tap(observer => observer.observe(el)),\n switchMap(observer => entry$\n .pipe(\n filter(({ target }) => target === el),\n finalize(() => observer.unobserve(el)),\n map(() => getElementSize(el))\n )\n ),\n startWith(getElementSize(el))\n )\n}\n", "/*\n * Copyright (c) 2016-2022 Martin Donath \n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\nimport { ElementSize } from \"../_\"\n\n/* ----------------------------------------------------------------------------\n * Functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Retrieve element content size (= scroll width and height)\n *\n * @param el - Element\n *\n * @returns Element content size\n */\nexport function getElementContentSize(\n el: HTMLElement\n): ElementSize {\n return {\n width: el.scrollWidth,\n height: el.scrollHeight\n }\n}\n", "/*\n * Copyright (c) 2016-2022 Martin Donath \n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\nimport {\n NEVER,\n Observable,\n Subject,\n defer,\n distinctUntilChanged,\n filter,\n finalize,\n map,\n merge,\n of,\n shareReplay,\n switchMap,\n tap\n} from \"rxjs\"\n\nimport {\n getElementContentSize,\n getElementSize,\n watchElementContentOffset\n} from \"~/browser\"\n\n/* ----------------------------------------------------------------------------\n * Data\n * ------------------------------------------------------------------------- */\n\n/**\n * Intersection observer entry subject\n */\nconst entry$ = new Subject()\n\n/**\n * Intersection observer observable\n *\n * This observable will create an `IntersectionObserver` on first subscription\n * and will automatically terminate it when there are no more subscribers.\n *\n * @see https://bit.ly/3iIYfEm - Google Groups on performance\n */\nconst observer$ = defer(() => of(\n new IntersectionObserver(entries => {\n for (const entry of entries)\n entry$.next(entry)\n }, {\n threshold: 0\n })\n))\n .pipe(\n switchMap(observer => merge(NEVER, of(observer))\n .pipe(\n finalize(() => observer.disconnect())\n )\n ),\n shareReplay(1)\n )\n\n/* ----------------------------------------------------------------------------\n * Functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Watch element visibility\n *\n * @param el - Element\n *\n * @returns Element visibility observable\n */\nexport function watchElementVisibility(\n el: HTMLElement\n): Observable {\n return observer$\n .pipe(\n tap(observer => observer.observe(el)),\n switchMap(observer => entry$\n .pipe(\n filter(({ target }) => target === el),\n finalize(() => observer.unobserve(el)),\n map(({ isIntersecting }) => isIntersecting)\n )\n )\n )\n}\n\n/**\n * Watch element boundary\n *\n * This function returns an observable which emits whether the bottom content\n * boundary (= scroll offset) of an element is within a certain threshold.\n *\n * @param el - Element\n * @param threshold - Threshold\n *\n * @returns Element boundary observable\n */\nexport function watchElementBoundary(\n el: HTMLElement, threshold = 16\n): Observable {\n return watchElementContentOffset(el)\n .pipe(\n map(({ y }) => {\n const visible = getElementSize(el)\n const content = getElementContentSize(el)\n return y >= (\n content.height - visible.height - threshold\n )\n }),\n distinctUntilChanged()\n )\n}\n", "/*\n * Copyright (c) 2016-2022 Martin Donath \n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\nimport {\n Observable,\n fromEvent,\n map,\n startWith\n} from \"rxjs\"\n\nimport { getElement } from \"../element\"\n\n/* ----------------------------------------------------------------------------\n * Types\n * ------------------------------------------------------------------------- */\n\n/**\n * Toggle\n */\nexport type Toggle =\n | \"drawer\" /* Toggle for drawer */\n | \"search\" /* Toggle for search */\n\n/* ----------------------------------------------------------------------------\n * Data\n * ------------------------------------------------------------------------- */\n\n/**\n * Toggle map\n */\nconst toggles: Record = {\n drawer: getElement(\"[data-md-toggle=drawer]\"),\n search: getElement(\"[data-md-toggle=search]\")\n}\n\n/* ----------------------------------------------------------------------------\n * Functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Retrieve the value of a toggle\n *\n * @param name - Toggle\n *\n * @returns Toggle value\n */\nexport function getToggle(name: Toggle): boolean {\n return toggles[name].checked\n}\n\n/**\n * Set toggle\n *\n * Simulating a click event seems to be the most cross-browser compatible way\n * of changing the value while also emitting a `change` event. Before, Material\n * used `CustomEvent` to programmatically change the value of a toggle, but this\n * is a much simpler and cleaner solution which doesn't require a polyfill.\n *\n * @param name - Toggle\n * @param value - Toggle value\n */\nexport function setToggle(name: Toggle, value: boolean): void {\n if (toggles[name].checked !== value)\n toggles[name].click()\n}\n\n/* ------------------------------------------------------------------------- */\n\n/**\n * Watch toggle\n *\n * @param name - Toggle\n *\n * @returns Toggle value observable\n */\nexport function watchToggle(name: Toggle): Observable {\n const el = toggles[name]\n return fromEvent(el, \"change\")\n .pipe(\n map(() => el.checked),\n startWith(el.checked)\n )\n}\n", "/*\n * Copyright (c) 2016-2022 Martin Donath \n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\nimport {\n Observable,\n filter,\n fromEvent,\n map,\n share\n} from \"rxjs\"\n\nimport { getActiveElement } from \"../element\"\nimport { getToggle } from \"../toggle\"\n\n/* ----------------------------------------------------------------------------\n * Types\n * ------------------------------------------------------------------------- */\n\n/**\n * Keyboard mode\n */\nexport type KeyboardMode =\n | \"global\" /* Global */\n | \"search\" /* Search is open */\n\n/* ------------------------------------------------------------------------- */\n\n/**\n * Keyboard\n */\nexport interface Keyboard {\n mode: KeyboardMode /* Keyboard mode */\n type: string /* Key type */\n claim(): void /* Key claim */\n}\n\n/* ----------------------------------------------------------------------------\n * Helper functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Check whether an element may receive keyboard input\n *\n * @param el - Element\n * @param type - Key type\n *\n * @returns Test result\n */\nfunction isSusceptibleToKeyboard(\n el: HTMLElement, type: string\n): boolean {\n switch (el.constructor) {\n\n /* Input elements */\n case HTMLInputElement:\n /* @ts-expect-error - omit unnecessary type cast */\n if (el.type === \"radio\")\n return /^Arrow/.test(type)\n else\n return true\n\n /* Select element and textarea */\n case HTMLSelectElement:\n case HTMLTextAreaElement:\n return true\n\n /* Everything else */\n default:\n return el.isContentEditable\n }\n}\n\n/* ----------------------------------------------------------------------------\n * Functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Watch keyboard\n *\n * @returns Keyboard observable\n */\nexport function watchKeyboard(): Observable {\n return fromEvent(window, \"keydown\")\n .pipe(\n filter(ev => !(ev.metaKey || ev.ctrlKey)),\n map(ev => ({\n mode: getToggle(\"search\") ? \"search\" : \"global\",\n type: ev.key,\n claim() {\n ev.preventDefault()\n ev.stopPropagation()\n }\n } as Keyboard)),\n filter(({ mode, type }) => {\n if (mode === \"global\") {\n const active = getActiveElement()\n if (typeof active !== \"undefined\")\n return !isSusceptibleToKeyboard(active, type)\n }\n return true\n }),\n share()\n )\n}\n", "/*\n * Copyright (c) 2016-2022 Martin Donath \n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\nimport { Subject } from \"rxjs\"\n\n/* ----------------------------------------------------------------------------\n * Functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Retrieve location\n *\n * This function returns a `URL` object (and not `Location`) to normalize the\n * typings across the application. Furthermore, locations need to be tracked\n * without setting them and `Location` is a singleton which represents the\n * current location.\n *\n * @returns URL\n */\nexport function getLocation(): URL {\n return new URL(location.href)\n}\n\n/**\n * Set location\n *\n * @param url - URL to change to\n */\nexport function setLocation(url: URL): void {\n location.href = url.href\n}\n\n/* ------------------------------------------------------------------------- */\n\n/**\n * Watch location\n *\n * @returns Location subject\n */\nexport function watchLocation(): Subject {\n return new Subject()\n}\n", "/*\n * Copyright (c) 2016-2022 Martin Donath \n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\nimport { JSX as JSXInternal } from \"preact\"\n\n/* ----------------------------------------------------------------------------\n * Helper types\n * ------------------------------------------------------------------------- */\n\n/**\n * HTML attributes\n */\ntype Attributes =\n & JSXInternal.HTMLAttributes\n & JSXInternal.SVGAttributes\n & Record\n\n/**\n * Child element\n */\ntype Child =\n | HTMLElement\n | Text\n | string\n | number\n\n/* ----------------------------------------------------------------------------\n * Helper functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Append a child node to an element\n *\n * @param el - Element\n * @param child - Child node(s)\n */\nfunction appendChild(el: HTMLElement, child: Child | Child[]): void {\n\n /* Handle primitive types (including raw HTML) */\n if (typeof child === \"string\" || typeof child === \"number\") {\n el.innerHTML += child.toString()\n\n /* Handle nodes */\n } else if (child instanceof Node) {\n el.appendChild(child)\n\n /* Handle nested children */\n } else if (Array.isArray(child)) {\n for (const node of child)\n appendChild(el, node)\n }\n}\n\n/* ----------------------------------------------------------------------------\n * Functions\n * ------------------------------------------------------------------------- */\n\n/**\n * JSX factory\n *\n * @template T - Element type\n *\n * @param tag - HTML tag\n * @param attributes - HTML attributes\n * @param children - Child elements\n *\n * @returns Element\n */\nexport function h(\n tag: T, attributes?: Attributes | null, ...children: Child[]\n): HTMLElementTagNameMap[T]\n\nexport function h(\n tag: string, attributes?: Attributes | null, ...children: Child[]\n): T\n\nexport function h(\n tag: string, attributes?: Attributes | null, ...children: Child[]\n): T {\n const el = document.createElement(tag)\n\n /* Set attributes, if any */\n if (attributes)\n for (const attr of Object.keys(attributes)) {\n if (typeof attributes[attr] === \"undefined\")\n continue\n\n /* Set default attribute or boolean */\n if (typeof attributes[attr] !== \"boolean\")\n el.setAttribute(attr, attributes[attr])\n else\n el.setAttribute(attr, \"\")\n }\n\n /* Append child nodes */\n for (const child of children)\n appendChild(el, child)\n\n /* Return element */\n return el as T\n}\n\n/* ----------------------------------------------------------------------------\n * Namespace\n * ------------------------------------------------------------------------- */\n\nexport declare namespace h {\n namespace JSX {\n type Element = HTMLElement\n type IntrinsicElements = JSXInternal.IntrinsicElements\n }\n}\n", "/*\n * Copyright (c) 2016-2022 Martin Donath \n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\n/* ----------------------------------------------------------------------------\n * Functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Truncate a string after the given number of characters\n *\n * This is not a very reasonable approach, since the summaries kind of suck.\n * It would be better to create something more intelligent, highlighting the\n * search occurrences and making a better summary out of it, but this note was\n * written three years ago, so who knows if we'll ever fix it.\n *\n * @param value - Value to be truncated\n * @param n - Number of characters\n *\n * @returns Truncated value\n */\nexport function truncate(value: string, n: number): string {\n let i = n\n if (value.length > i) {\n while (value[i] !== \" \" && --i > 0) { /* keep eating */ }\n return `${value.substring(0, i)}...`\n }\n return value\n}\n\n/**\n * Round a number for display with repository facts\n *\n * This is a reverse-engineered version of GitHub's weird rounding algorithm\n * for stars, forks and all other numbers. While all numbers below `1,000` are\n * returned as-is, bigger numbers are converted to fixed numbers:\n *\n * - `1,049` => `1k`\n * - `1,050` => `1.1k`\n * - `1,949` => `1.9k`\n * - `1,950` => `2k`\n *\n * @param value - Original value\n *\n * @returns Rounded value\n */\nexport function round(value: number): string {\n if (value > 999) {\n const digits = +((value - 950) % 1000 > 99)\n return `${((value + 0.000001) / 1000).toFixed(digits)}k`\n } else {\n return value.toString()\n }\n}\n", "/*\n * Copyright (c) 2016-2022 Martin Donath \n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\nimport {\n Observable,\n filter,\n fromEvent,\n map,\n shareReplay,\n startWith\n} from \"rxjs\"\n\nimport { getOptionalElement } from \"~/browser\"\nimport { h } from \"~/utilities\"\n\n/* ----------------------------------------------------------------------------\n * Functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Retrieve location hash\n *\n * @returns Location hash\n */\nexport function getLocationHash(): string {\n return location.hash.substring(1)\n}\n\n/**\n * Set location hash\n *\n * Setting a new fragment identifier via `location.hash` will have no effect\n * if the value doesn't change. When a new fragment identifier is set, we want\n * the browser to target the respective element at all times, which is why we\n * use this dirty little trick.\n *\n * @param hash - Location hash\n */\nexport function setLocationHash(hash: string): void {\n const el = h(\"a\", { href: hash })\n el.addEventListener(\"click\", ev => ev.stopPropagation())\n el.click()\n}\n\n/* ------------------------------------------------------------------------- */\n\n/**\n * Watch location hash\n *\n * @returns Location hash observable\n */\nexport function watchLocationHash(): Observable {\n return fromEvent(window, \"hashchange\")\n .pipe(\n map(getLocationHash),\n startWith(getLocationHash()),\n filter(hash => hash.length > 0),\n shareReplay(1)\n )\n}\n\n/**\n * Watch location target\n *\n * @returns Location target observable\n */\nexport function watchLocationTarget(): Observable {\n return watchLocationHash()\n .pipe(\n map(id => getOptionalElement(`[id=\"${id}\"]`)!),\n filter(el => typeof el !== \"undefined\")\n )\n}\n", "/*\n * Copyright (c) 2016-2022 Martin Donath \n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\nimport {\n EMPTY,\n Observable,\n fromEvent,\n fromEventPattern,\n map,\n merge,\n startWith,\n switchMap\n} from \"rxjs\"\n\n/* ----------------------------------------------------------------------------\n * Functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Watch media query\n *\n * Note that although `MediaQueryList.addListener` is deprecated we have to\n * use it, because it's the only way to ensure proper downward compatibility.\n *\n * @see https://bit.ly/3dUBH2m - GitHub issue\n *\n * @param query - Media query\n *\n * @returns Media observable\n */\nexport function watchMedia(query: string): Observable {\n const media = matchMedia(query)\n return fromEventPattern(next => (\n media.addListener(() => next(media.matches))\n ))\n .pipe(\n startWith(media.matches)\n )\n}\n\n/**\n * Watch print mode\n *\n * @returns Print observable\n */\nexport function watchPrint(): Observable {\n const media = matchMedia(\"print\")\n return merge(\n fromEvent(window, \"beforeprint\").pipe(map(() => true)),\n fromEvent(window, \"afterprint\").pipe(map(() => false))\n )\n .pipe(\n startWith(media.matches)\n )\n}\n\n/* ------------------------------------------------------------------------- */\n\n/**\n * Toggle an observable with a media observable\n *\n * @template T - Data type\n *\n * @param query$ - Media observable\n * @param factory - Observable factory\n *\n * @returns Toggled observable\n */\nexport function at(\n query$: Observable, factory: () => Observable\n): Observable {\n return query$\n .pipe(\n switchMap(active => active ? factory() : EMPTY)\n )\n}\n", "/*\n * Copyright (c) 2016-2022 Martin Donath \n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\nimport {\n EMPTY,\n Observable,\n catchError,\n from,\n map,\n of,\n shareReplay,\n switchMap,\n throwError\n} from \"rxjs\"\n\n/* ----------------------------------------------------------------------------\n * Functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Fetch the given URL\n *\n * If the request fails (e.g. when dispatched from `file://` locations), the\n * observable will complete without emitting a value.\n *\n * @param url - Request URL\n * @param options - Options\n *\n * @returns Response observable\n */\nexport function request(\n url: URL | string, options: RequestInit = { credentials: \"same-origin\" }\n): Observable {\n return from(fetch(`${url}`, options))\n .pipe(\n catchError(() => EMPTY),\n switchMap(res => res.status !== 200\n ? throwError(() => new Error(res.statusText))\n : of(res)\n )\n )\n}\n\n/**\n * Fetch JSON from the given URL\n *\n * @template T - Data type\n *\n * @param url - Request URL\n * @param options - Options\n *\n * @returns Data observable\n */\nexport function requestJSON(\n url: URL | string, options?: RequestInit\n): Observable {\n return request(url, options)\n .pipe(\n switchMap(res => res.json()),\n shareReplay(1)\n )\n}\n\n/**\n * Fetch XML from the given URL\n *\n * @param url - Request URL\n * @param options - Options\n *\n * @returns Data observable\n */\nexport function requestXML(\n url: URL | string, options?: RequestInit\n): Observable {\n const dom = new DOMParser()\n return request(url, options)\n .pipe(\n switchMap(res => res.text()),\n map(res => dom.parseFromString(res, \"text/xml\")),\n shareReplay(1)\n )\n}\n", "/*\n * Copyright (c) 2016-2022 Martin Donath \n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\nimport {\n Observable,\n defer,\n finalize,\n fromEvent,\n map,\n merge,\n switchMap,\n take,\n throwError\n} from \"rxjs\"\n\nimport { h } from \"~/utilities\"\n\n/* ----------------------------------------------------------------------------\n * Functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Create and load a `script` element\n *\n * This function returns an observable that will emit when the script was\n * successfully loaded, or throw an error if it didn't.\n *\n * @param src - Script URL\n *\n * @returns Script observable\n */\nexport function watchScript(src: string): Observable {\n const script = h(\"script\", { src })\n return defer(() => {\n document.head.appendChild(script)\n return merge(\n fromEvent(script, \"load\"),\n fromEvent(script, \"error\")\n .pipe(\n switchMap(() => (\n throwError(() => new ReferenceError(`Invalid script: ${src}`))\n ))\n )\n )\n .pipe(\n map(() => undefined),\n finalize(() => document.head.removeChild(script)),\n take(1)\n )\n })\n}\n", "/*\n * Copyright (c) 2016-2022 Martin Donath \n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\nimport {\n Observable,\n fromEvent,\n map,\n merge,\n startWith\n} from \"rxjs\"\n\n/* ----------------------------------------------------------------------------\n * Types\n * ------------------------------------------------------------------------- */\n\n/**\n * Viewport offset\n */\nexport interface ViewportOffset {\n x: number /* Horizontal offset */\n y: number /* Vertical offset */\n}\n\n/* ----------------------------------------------------------------------------\n * Functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Retrieve viewport offset\n *\n * On iOS Safari, viewport offset can be negative due to overflow scrolling.\n * As this may induce strange behaviors downstream, we'll just limit it to 0.\n *\n * @returns Viewport offset\n */\nexport function getViewportOffset(): ViewportOffset {\n return {\n x: Math.max(0, scrollX),\n y: Math.max(0, scrollY)\n }\n}\n\n/* ------------------------------------------------------------------------- */\n\n/**\n * Watch viewport offset\n *\n * @returns Viewport offset observable\n */\nexport function watchViewportOffset(): Observable {\n return merge(\n fromEvent(window, \"scroll\", { passive: true }),\n fromEvent(window, \"resize\", { passive: true })\n )\n .pipe(\n map(getViewportOffset),\n startWith(getViewportOffset())\n )\n}\n", "/*\n * Copyright (c) 2016-2022 Martin Donath \n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\nimport {\n Observable,\n fromEvent,\n map,\n startWith\n} from \"rxjs\"\n\n/* ----------------------------------------------------------------------------\n * Types\n * ------------------------------------------------------------------------- */\n\n/**\n * Viewport size\n */\nexport interface ViewportSize {\n width: number /* Viewport width */\n height: number /* Viewport height */\n}\n\n/* ----------------------------------------------------------------------------\n * Functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Retrieve viewport size\n *\n * @returns Viewport size\n */\nexport function getViewportSize(): ViewportSize {\n return {\n width: innerWidth,\n height: innerHeight\n }\n}\n\n/* ------------------------------------------------------------------------- */\n\n/**\n * Watch viewport size\n *\n * @returns Viewport size observable\n */\nexport function watchViewportSize(): Observable {\n return fromEvent(window, \"resize\", { passive: true })\n .pipe(\n map(getViewportSize),\n startWith(getViewportSize())\n )\n}\n", "/*\n * Copyright (c) 2016-2022 Martin Donath \n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\nimport {\n Observable,\n combineLatest,\n map,\n shareReplay\n} from \"rxjs\"\n\nimport {\n ViewportOffset,\n watchViewportOffset\n} from \"../offset\"\nimport {\n ViewportSize,\n watchViewportSize\n} from \"../size\"\n\n/* ----------------------------------------------------------------------------\n * Types\n * ------------------------------------------------------------------------- */\n\n/**\n * Viewport\n */\nexport interface Viewport {\n offset: ViewportOffset /* Viewport offset */\n size: ViewportSize /* Viewport size */\n}\n\n/* ----------------------------------------------------------------------------\n * Functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Watch viewport\n *\n * @returns Viewport observable\n */\nexport function watchViewport(): Observable {\n return combineLatest([\n watchViewportOffset(),\n watchViewportSize()\n ])\n .pipe(\n map(([offset, size]) => ({ offset, size })),\n shareReplay(1)\n )\n}\n", "/*\n * Copyright (c) 2016-2022 Martin Donath \n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\nimport {\n Observable,\n combineLatest,\n distinctUntilKeyChanged,\n map\n} from \"rxjs\"\n\nimport { Header } from \"~/components\"\n\nimport { getElementOffset } from \"../../element\"\nimport { Viewport } from \"../_\"\n\n/* ----------------------------------------------------------------------------\n * Helper types\n * ------------------------------------------------------------------------- */\n\n/**\n * Watch options\n */\ninterface WatchOptions {\n viewport$: Observable /* Viewport observable */\n header$: Observable
/* Header observable */\n}\n\n/* ----------------------------------------------------------------------------\n * Functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Watch viewport relative to element\n *\n * @param el - Element\n * @param options - Options\n *\n * @returns Viewport observable\n */\nexport function watchViewportAt(\n el: HTMLElement, { viewport$, header$ }: WatchOptions\n): Observable {\n const size$ = viewport$\n .pipe(\n distinctUntilKeyChanged(\"size\")\n )\n\n /* Compute element offset */\n const offset$ = combineLatest([size$, header$])\n .pipe(\n map(() => getElementOffset(el))\n )\n\n /* Compute relative viewport, return hot observable */\n return combineLatest([header$, viewport$, offset$])\n .pipe(\n map(([{ height }, { offset, size }, { x, y }]) => ({\n offset: {\n x: offset.x - x,\n y: offset.y - y + height\n },\n size\n }))\n )\n}\n", "/*\n * Copyright (c) 2016-2022 Martin Donath \n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\nimport {\n Observable,\n Subject,\n fromEvent,\n map,\n share,\n switchMap,\n tap,\n throttle\n} from \"rxjs\"\n\n/* ----------------------------------------------------------------------------\n * Types\n * ------------------------------------------------------------------------- */\n\n/**\n * Worker message\n */\nexport interface WorkerMessage {\n type: unknown /* Message type */\n data?: unknown /* Message data */\n}\n\n/**\n * Worker handler\n *\n * @template T - Message type\n */\nexport interface WorkerHandler<\n T extends WorkerMessage\n> {\n tx$: Subject /* Message transmission subject */\n rx$: Observable /* Message receive observable */\n}\n\n/* ----------------------------------------------------------------------------\n * Helper types\n * ------------------------------------------------------------------------- */\n\n/**\n * Watch options\n *\n * @template T - Worker message type\n */\ninterface WatchOptions {\n tx$: Observable /* Message transmission observable */\n}\n\n/* ----------------------------------------------------------------------------\n * Functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Watch a web worker\n *\n * This function returns an observable that sends all values emitted by the\n * message observable to the web worker. Web worker communication is expected\n * to be bidirectional (request-response) and synchronous. Messages that are\n * emitted during a pending request are throttled, the last one is emitted.\n *\n * @param worker - Web worker\n * @param options - Options\n *\n * @returns Worker message observable\n */\nexport function watchWorker(\n worker: Worker, { tx$ }: WatchOptions\n): Observable {\n\n /* Intercept messages from worker-like objects */\n const rx$ = fromEvent(worker, \"message\")\n .pipe(\n map(({ data }) => data as T)\n )\n\n /* Send and receive messages, return hot observable */\n return tx$\n .pipe(\n throttle(() => rx$, { leading: true, trailing: true }),\n tap(message => worker.postMessage(message)),\n switchMap(() => rx$),\n share()\n )\n}\n", "/*\n * Copyright (c) 2016-2022 Martin Donath \n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\nimport { getElement, getLocation } from \"~/browser\"\n\n/* ----------------------------------------------------------------------------\n * Types\n * ------------------------------------------------------------------------- */\n\n/**\n * Feature flag\n */\nexport type Flag =\n | \"content.code.annotate\" /* Code annotations */\n | \"content.tabs.link\" /* Link content tabs */\n | \"header.autohide\" /* Hide header */\n | \"navigation.expand\" /* Automatic expansion */\n | \"navigation.indexes\" /* Section pages */\n | \"navigation.instant\" /* Instant loading */\n | \"navigation.sections\" /* Section navigation */\n | \"navigation.tabs\" /* Tabs navigation */\n | \"navigation.tabs.sticky\" /* Tabs navigation (sticky) */\n | \"navigation.top\" /* Back-to-top button */\n | \"navigation.tracking\" /* Anchor tracking */\n | \"search.highlight\" /* Search highlighting */\n | \"search.share\" /* Search sharing */\n | \"search.suggest\" /* Search suggestions */\n | \"toc.integrate\" /* Integrated table of contents */\n\n/* ------------------------------------------------------------------------- */\n\n/**\n * Translation\n */\nexport type Translation =\n | \"clipboard.copy\" /* Copy to clipboard */\n | \"clipboard.copied\" /* Copied to clipboard */\n | \"search.config.lang\" /* Search language */\n | \"search.config.pipeline\" /* Search pipeline */\n | \"search.config.separator\" /* Search separator */\n | \"search.placeholder\" /* Search */\n | \"search.result.placeholder\" /* Type to start searching */\n | \"search.result.none\" /* No matching documents */\n | \"search.result.one\" /* 1 matching document */\n | \"search.result.other\" /* # matching documents */\n | \"search.result.more.one\" /* 1 more on this page */\n | \"search.result.more.other\" /* # more on this page */\n | \"search.result.term.missing\" /* Missing */\n | \"select.version.title\" /* Version selector */\n\n/**\n * Translations\n */\nexport type Translations = Record\n\n/* ------------------------------------------------------------------------- */\n\n/**\n * Versioning\n */\nexport interface Versioning {\n provider: \"mike\" /* Version provider */\n default?: string /* Default version */\n}\n\n/**\n * Configuration\n */\nexport interface Config {\n base: string /* Base URL */\n features: Flag[] /* Feature flags */\n translations: Translations /* Translations */\n search: string /* Search worker URL */\n version?: Versioning /* Versioning */\n}\n\n/* ----------------------------------------------------------------------------\n * Data\n * ------------------------------------------------------------------------- */\n\n/**\n * Retrieve global configuration and make base URL absolute\n */\nconst script = getElement(\"#__config\")\nconst config: Config = JSON.parse(script.textContent!)\nconfig.base = `${new URL(config.base, getLocation())}`\n\n/* ----------------------------------------------------------------------------\n * Functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Retrieve global configuration\n *\n * @returns Global configuration\n */\nexport function configuration(): Config {\n return config\n}\n\n/**\n * Check whether a feature flag is enabled\n *\n * @param flag - Feature flag\n *\n * @returns Test result\n */\nexport function feature(flag: Flag): boolean {\n return config.features.includes(flag)\n}\n\n/**\n * Retrieve the translation for the given key\n *\n * @param key - Key to be translated\n * @param value - Positional value, if any\n *\n * @returns Translation\n */\nexport function translation(\n key: Translation, value?: string | number\n): string {\n return typeof value !== \"undefined\"\n ? config.translations[key].replace(\"#\", value.toString())\n : config.translations[key]\n}\n", "/*\n * Copyright (c) 2016-2022 Martin Donath \n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\nimport { getElement, getElements } from \"~/browser\"\n\n/* ----------------------------------------------------------------------------\n * Types\n * ------------------------------------------------------------------------- */\n\n/**\n * Component type\n */\nexport type ComponentType =\n | \"announce\" /* Announcement bar */\n | \"container\" /* Container */\n | \"content\" /* Content */\n | \"dialog\" /* Dialog */\n | \"header\" /* Header */\n | \"header-title\" /* Header title */\n | \"header-topic\" /* Header topic */\n | \"main\" /* Main area */\n | \"outdated\" /* Version warning */\n | \"palette\" /* Color palette */\n | \"search\" /* Search */\n | \"search-query\" /* Search input */\n | \"search-result\" /* Search results */\n | \"search-share\" /* Search sharing */\n | \"search-suggest\" /* Search suggestions */\n | \"sidebar\" /* Sidebar */\n | \"skip\" /* Skip link */\n | \"source\" /* Repository information */\n | \"tabs\" /* Navigation tabs */\n | \"toc\" /* Table of contents */\n | \"top\" /* Back-to-top button */\n\n/**\n * Component\n *\n * @template T - Component type\n * @template U - Reference type\n */\nexport type Component<\n T extends {} = {},\n U extends HTMLElement = HTMLElement\n> =\n T & {\n ref: U /* Component reference */\n }\n\n/* ----------------------------------------------------------------------------\n * Helper types\n * ------------------------------------------------------------------------- */\n\n/**\n * Component type map\n */\ninterface ComponentTypeMap {\n \"announce\": HTMLElement /* Announcement bar */\n \"container\": HTMLElement /* Container */\n \"content\": HTMLElement /* Content */\n \"dialog\": HTMLElement /* Dialog */\n \"header\": HTMLElement /* Header */\n \"header-title\": HTMLElement /* Header title */\n \"header-topic\": HTMLElement /* Header topic */\n \"main\": HTMLElement /* Main area */\n \"outdated\": HTMLElement /* Version warning */\n \"palette\": HTMLElement /* Color palette */\n \"search\": HTMLElement /* Search */\n \"search-query\": HTMLInputElement /* Search input */\n \"search-result\": HTMLElement /* Search results */\n \"search-share\": HTMLAnchorElement /* Search sharing */\n \"search-suggest\": HTMLElement /* Search suggestions */\n \"sidebar\": HTMLElement /* Sidebar */\n \"skip\": HTMLAnchorElement /* Skip link */\n \"source\": HTMLAnchorElement /* Repository information */\n \"tabs\": HTMLElement /* Navigation tabs */\n \"toc\": HTMLElement /* Table of contents */\n \"top\": HTMLAnchorElement /* Back-to-top button */\n}\n\n/* ----------------------------------------------------------------------------\n * Functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Retrieve the element for a given component or throw a reference error\n *\n * @template T - Component type\n *\n * @param type - Component type\n * @param node - Node of reference\n *\n * @returns Element\n */\nexport function getComponentElement(\n type: T, node: ParentNode = document\n): ComponentTypeMap[T] {\n return getElement(`[data-md-component=${type}]`, node)\n}\n\n/**\n * Retrieve all elements for a given component\n *\n * @template T - Component type\n *\n * @param type - Component type\n * @param node - Node of reference\n *\n * @returns Elements\n */\nexport function getComponentElements(\n type: T, node: ParentNode = document\n): ComponentTypeMap[T][] {\n return getElements(`[data-md-component=${type}]`, node)\n}\n", "/*\n * Copyright (c) 2016-2022 Martin Donath \n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\nimport ClipboardJS from \"clipboard\"\nimport {\n EMPTY,\n Observable,\n Subject,\n defer,\n distinctUntilChanged,\n distinctUntilKeyChanged,\n filter,\n finalize,\n map,\n mergeWith,\n switchMap,\n take,\n takeLast,\n takeUntil,\n tap\n} from \"rxjs\"\n\nimport { feature } from \"~/_\"\nimport {\n getElementContentSize,\n watchElementSize,\n watchElementVisibility\n} from \"~/browser\"\nimport { renderClipboardButton } from \"~/templates\"\n\nimport { Component } from \"../../../_\"\nimport {\n Annotation,\n mountAnnotationList\n} from \"../../annotation\"\n\n/* ----------------------------------------------------------------------------\n * Types\n * ------------------------------------------------------------------------- */\n\n/**\n * Code block\n */\nexport interface CodeBlock {\n scrollable: boolean /* Code block overflows */\n}\n\n/* ----------------------------------------------------------------------------\n * Helper types\n * ------------------------------------------------------------------------- */\n\n/**\n * Mount options\n */\ninterface MountOptions {\n print$: Observable /* Media print observable */\n}\n\n/* ----------------------------------------------------------------------------\n * Data\n * ------------------------------------------------------------------------- */\n\n/**\n * Global sequence number for Clipboard.js integration\n */\nlet sequence = 0\n\n/* ----------------------------------------------------------------------------\n * Helper functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Find candidate list element directly following a code block\n *\n * @param el - Code block element\n *\n * @returns List element or nothing\n */\nfunction findCandidateList(el: HTMLElement): HTMLElement | undefined {\n if (el.nextElementSibling) {\n const sibling = el.nextElementSibling as HTMLElement\n if (sibling.tagName === \"OL\")\n return sibling\n\n /* Skip empty paragraphs - see https://bit.ly/3r4ZJ2O */\n else if (sibling.tagName === \"P\" && !sibling.children.length)\n return findCandidateList(sibling)\n }\n\n /* Everything else */\n return undefined\n}\n\n/* ----------------------------------------------------------------------------\n * Functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Watch code block\n *\n * This function monitors size changes of the viewport, as well as switches of\n * content tabs with embedded code blocks, as both may trigger overflow.\n *\n * @param el - Code block element\n *\n * @returns Code block observable\n */\nexport function watchCodeBlock(\n el: HTMLElement\n): Observable {\n return watchElementSize(el)\n .pipe(\n map(({ width }) => {\n const content = getElementContentSize(el)\n return {\n scrollable: content.width > width\n }\n }),\n distinctUntilKeyChanged(\"scrollable\")\n )\n}\n\n/**\n * Mount code block\n *\n * This function ensures that an overflowing code block is focusable through\n * keyboard, so it can be scrolled without a mouse to improve on accessibility.\n * Furthermore, if code annotations are enabled, they are mounted if and only\n * if the code block is currently visible, e.g., not in a hidden content tab.\n *\n * @param el - Code block element\n * @param options - Options\n *\n * @returns Code block and annotation component observable\n */\nexport function mountCodeBlock(\n el: HTMLElement, options: MountOptions\n): Observable> {\n const { matches: hover } = matchMedia(\"(hover)\")\n\n /* Defer mounting of code block - see https://bit.ly/3vHVoVD */\n const factory$ = defer(() => {\n const push$ = new Subject()\n push$.subscribe(({ scrollable }) => {\n if (scrollable && hover)\n el.setAttribute(\"tabindex\", \"0\")\n else\n el.removeAttribute(\"tabindex\")\n })\n\n /* Render button for Clipboard.js integration */\n if (ClipboardJS.isSupported()) {\n const parent = el.closest(\"pre\")!\n parent.id = `__code_${++sequence}`\n parent.insertBefore(\n renderClipboardButton(parent.id),\n el\n )\n }\n\n /* Handle code annotations */\n const container = el.closest(\".highlight\")\n if (container instanceof HTMLElement) {\n const list = findCandidateList(container)\n\n /* Mount code annotations, if enabled */\n if (typeof list !== \"undefined\" && (\n container.classList.contains(\"annotate\") ||\n feature(\"content.code.annotate\")\n )) {\n const annotations$ = mountAnnotationList(list, el, options)\n\n /* Create and return component */\n return watchCodeBlock(el)\n .pipe(\n tap(state => push$.next(state)),\n finalize(() => push$.complete()),\n map(state => ({ ref: el, ...state })),\n mergeWith(\n watchElementSize(container)\n .pipe(\n takeUntil(push$.pipe(takeLast(1))),\n map(({ width, height }) => width && height),\n distinctUntilChanged(),\n switchMap(active => active ? annotations$ : EMPTY)\n )\n )\n )\n }\n }\n\n /* Create and return component */\n return watchCodeBlock(el)\n .pipe(\n tap(state => push$.next(state)),\n finalize(() => push$.complete()),\n map(state => ({ ref: el, ...state }))\n )\n })\n\n /* Mount code block on first sight */\n return watchElementVisibility(el)\n .pipe(\n filter(visible => visible),\n take(1),\n switchMap(() => factory$)\n )\n}\n", "/*\n * Copyright (c) 2016-2022 Martin Donath \n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\nimport { h } from \"~/utilities\"\n\n/* ----------------------------------------------------------------------------\n * Functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Render an empty annotation\n *\n * @param id - Annotation identifier\n *\n * @returns Element\n */\nexport function renderAnnotation(id: number): HTMLElement {\n return (\n \n )\n}\n", "/*\n * Copyright (c) 2016-2022 Martin Donath \n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\nimport { translation } from \"~/_\"\nimport { h } from \"~/utilities\"\n\n/* ----------------------------------------------------------------------------\n * Functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Render a 'copy-to-clipboard' button\n *\n * @param id - Unique identifier\n *\n * @returns Element\n */\nexport function renderClipboardButton(id: string): HTMLElement {\n return (\n code`}\n >\n )\n}\n", "/*\n * Copyright (c) 2016-2022 Martin Donath \n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\nimport { ComponentChild } from \"preact\"\n\nimport { feature, translation } from \"~/_\"\nimport {\n SearchDocument,\n SearchMetadata,\n SearchResultItem\n} from \"~/integrations/search\"\nimport { h, truncate } from \"~/utilities\"\n\n/* ----------------------------------------------------------------------------\n * Helper types\n * ------------------------------------------------------------------------- */\n\n/**\n * Render flag\n */\nconst enum Flag {\n TEASER = 1, /* Render teaser */\n PARENT = 2 /* Render as parent */\n}\n\n/* ----------------------------------------------------------------------------\n * Helper function\n * ------------------------------------------------------------------------- */\n\n/**\n * Render a search document\n *\n * @param document - Search document\n * @param flag - Render flags\n *\n * @returns Element\n */\nfunction renderSearchDocument(\n document: SearchDocument & SearchMetadata, flag: Flag\n): HTMLElement {\n const parent = flag & Flag.PARENT\n const teaser = flag & Flag.TEASER\n\n /* Render missing query terms */\n const missing = Object.keys(document.terms)\n .filter(key => !document.terms[key])\n .reduce((list, key) => [\n ...list, {key}, \" \"\n ], [])\n .slice(0, -1)\n\n /* Assemble query string for highlighting */\n const url = new URL(document.location)\n if (feature(\"search.highlight\"))\n url.searchParams.set(\"h\", Object.entries(document.terms)\n .filter(([, match]) => match)\n .reduce((highlight, [value]) => `${highlight} ${value}`.trim(), \"\")\n )\n\n /* Render article or section, depending on flags */\n return (\n \n \n {parent > 0 &&
}\n

{document.title}

\n {teaser > 0 && document.text.length > 0 &&\n

\n {truncate(document.text, 320)}\n

\n }\n {document.tags && document.tags.map(tag => (\n {tag}\n ))}\n {teaser > 0 && missing.length > 0 &&\n

\n {translation(\"search.result.term.missing\")}: {...missing}\n

\n }\n \n
\n )\n}\n\n/* ----------------------------------------------------------------------------\n * Functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Render a search result\n *\n * @param result - Search result\n *\n * @returns Element\n */\nexport function renderSearchResultItem(\n result: SearchResultItem\n): HTMLElement {\n const threshold = result[0].score\n const docs = [...result]\n\n /* Find and extract parent article */\n const parent = docs.findIndex(doc => !doc.location.includes(\"#\"))\n const [article] = docs.splice(parent, 1)\n\n /* Determine last index above threshold */\n let index = docs.findIndex(doc => doc.score < threshold)\n if (index === -1)\n index = docs.length\n\n /* Partition sections */\n const best = docs.slice(0, index)\n const more = docs.slice(index)\n\n /* Render children */\n const children = [\n renderSearchDocument(article, Flag.PARENT | +(!parent && index === 0)),\n ...best.map(section => renderSearchDocument(section, Flag.TEASER)),\n ...more.length ? [\n
\n \n {more.length > 0 && more.length === 1\n ? translation(\"search.result.more.one\")\n : translation(\"search.result.more.other\", more.length)\n }\n \n {...more.map(section => renderSearchDocument(section, Flag.TEASER))}\n
\n ] : []\n ]\n\n /* Render search result */\n return (\n
  • \n {children}\n
  • \n )\n}\n", "/*\n * Copyright (c) 2016-2022 Martin Donath \n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\nimport { SourceFacts } from \"~/components\"\nimport { h, round } from \"~/utilities\"\n\n/* ----------------------------------------------------------------------------\n * Functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Render repository facts\n *\n * @param facts - Repository facts\n *\n * @returns Element\n */\nexport function renderSourceFacts(facts: SourceFacts): HTMLElement {\n return (\n
      \n {Object.entries(facts).map(([key, value]) => (\n
    • \n {typeof value === \"number\" ? round(value) : value}\n
    • \n ))}\n
    \n )\n}\n", "/*\n * Copyright (c) 2016-2022 Martin Donath \n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\nimport { h } from \"~/utilities\"\n\n/* ----------------------------------------------------------------------------\n * Helper types\n * ------------------------------------------------------------------------- */\n\n/**\n * Tabbed control type\n */\ntype TabbedControlType =\n | \"prev\"\n | \"next\"\n\n/* ----------------------------------------------------------------------------\n * Functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Render control for content tabs\n *\n * @param type - Control type\n *\n * @returns Element\n */\nexport function renderTabbedControl(\n type: TabbedControlType\n): HTMLElement {\n const classes = `tabbed-control tabbed-control--${type}`\n return (\n \n )\n}\n", "/*\n * Copyright (c) 2016-2022 Martin Donath \n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\nimport { h } from \"~/utilities\"\n\n/* ----------------------------------------------------------------------------\n * Functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Render a table inside a wrapper to improve scrolling on mobile\n *\n * @param table - Table element\n *\n * @returns Element\n */\nexport function renderTable(table: HTMLElement): HTMLElement {\n return (\n
    \n
    \n {table}\n
    \n
    \n )\n}\n", "/*\n * Copyright (c) 2016-2022 Martin Donath \n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\nimport { configuration, translation } from \"~/_\"\nimport { h } from \"~/utilities\"\n\n/* ----------------------------------------------------------------------------\n * Types\n * ------------------------------------------------------------------------- */\n\n/**\n * Version\n */\nexport interface Version {\n version: string /* Version identifier */\n title: string /* Version title */\n aliases: string[] /* Version aliases */\n}\n\n/* ----------------------------------------------------------------------------\n * Helper functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Render a version\n *\n * @param version - Version\n *\n * @returns Element\n */\nfunction renderVersion(version: Version): HTMLElement {\n const config = configuration()\n\n /* Ensure trailing slash, see https://bit.ly/3rL5u3f */\n const url = new URL(`../${version.version}/`, config.base)\n return (\n
  • \n \n {version.title}\n \n
  • \n )\n}\n\n/* ----------------------------------------------------------------------------\n * Functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Render a version selector\n *\n * @param versions - Versions\n * @param active - Active version\n *\n * @returns Element\n */\nexport function renderVersionSelector(\n versions: Version[], active: Version\n): HTMLElement {\n return (\n
    \n \n {active.title}\n \n
      \n {versions.map(renderVersion)}\n
    \n
    \n )\n}\n", "/*\n * Copyright (c) 2016-2022 Martin Donath \n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\nimport {\n EMPTY,\n Observable,\n Subject,\n animationFrameScheduler,\n combineLatest,\n defer,\n finalize,\n fromEvent,\n map,\n switchMap,\n take,\n takeLast,\n takeUntil,\n tap,\n throttleTime\n} from \"rxjs\"\n\nimport {\n ElementOffset,\n getElement,\n getElementSize,\n watchElementContentOffset,\n watchElementFocus,\n watchElementOffset,\n watchElementVisibility\n} from \"~/browser\"\n\nimport { Component } from \"../../../_\"\n\n/* ----------------------------------------------------------------------------\n * Types\n * ------------------------------------------------------------------------- */\n\n/**\n * Annotation\n */\nexport interface Annotation {\n active: boolean /* Annotation is active */\n offset: ElementOffset /* Annotation offset */\n}\n\n/* ----------------------------------------------------------------------------\n * Functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Watch annotation\n *\n * @param el - Annotation element\n * @param container - Containing element\n *\n * @returns Annotation observable\n */\nexport function watchAnnotation(\n el: HTMLElement, container: HTMLElement\n): Observable {\n const offset$ = defer(() => combineLatest([\n watchElementOffset(el),\n watchElementContentOffset(container)\n ]))\n .pipe(\n map(([{ x, y }, scroll]) => {\n const { width } = getElementSize(el)\n return ({\n x: x - scroll.x + width / 2,\n y: y - scroll.y\n })\n })\n )\n\n /* Actively watch annotation on focus */\n return watchElementFocus(el)\n .pipe(\n switchMap(active => offset$\n .pipe(\n map(offset => ({ active, offset })),\n take(+!active || Infinity)\n )\n )\n )\n}\n\n/**\n * Mount annotation\n *\n * @param el - Annotation element\n * @param container - Containing element\n *\n * @returns Annotation component observable\n */\nexport function mountAnnotation(\n el: HTMLElement, container: HTMLElement\n): Observable> {\n return defer(() => {\n const push$ = new Subject()\n push$.subscribe({\n\n /* Handle emission */\n next({ offset }) {\n el.style.setProperty(\"--md-tooltip-x\", `${offset.x}px`)\n el.style.setProperty(\"--md-tooltip-y\", `${offset.y}px`)\n },\n\n /* Handle complete */\n complete() {\n el.style.removeProperty(\"--md-tooltip-x\")\n el.style.removeProperty(\"--md-tooltip-y\")\n }\n })\n\n /* Start animation only when annotation is visible */\n const done$ = push$.pipe(takeLast(1))\n watchElementVisibility(el)\n .pipe(\n takeUntil(done$)\n )\n .subscribe(visible => {\n el.toggleAttribute(\"data-md-visible\", visible)\n })\n\n /* Track relative origin of tooltip */\n push$\n .pipe(\n throttleTime(500, animationFrameScheduler),\n map(() => container.getBoundingClientRect()),\n map(({ x }) => x)\n )\n .subscribe({\n\n /* Handle emission */\n next(origin) {\n if (origin)\n el.style.setProperty(\"--md-tooltip-0\", `${-origin}px`)\n else\n el.style.removeProperty(\"--md-tooltip-0\")\n },\n\n /* Handle complete */\n complete() {\n el.style.removeProperty(\"--md-tooltip-0\")\n }\n })\n\n /* Close open annotation on click */\n const index = getElement(\":scope > :last-child\", el)\n const blur$ = fromEvent(index, \"mousedown\", { once: true })\n push$\n .pipe(\n switchMap(({ active }) => active ? blur$ : EMPTY),\n tap(ev => ev.preventDefault())\n )\n .subscribe(() => el.blur())\n\n /* Create and return component */\n return watchAnnotation(el, container)\n .pipe(\n tap(state => push$.next(state)),\n finalize(() => push$.complete()),\n map(state => ({ ref: el, ...state }))\n )\n })\n}\n", "/*\n * Copyright (c) 2016-2022 Martin Donath \n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\nimport {\n EMPTY,\n Observable,\n Subject,\n defer,\n finalize,\n merge,\n share,\n takeLast,\n takeUntil\n} from \"rxjs\"\n\nimport {\n getElement,\n getElements,\n getOptionalElement\n} from \"~/browser\"\nimport { renderAnnotation } from \"~/templates\"\n\nimport { Component } from \"../../../_\"\nimport {\n Annotation,\n mountAnnotation\n} from \"../_\"\n\n/* ----------------------------------------------------------------------------\n * Helper types\n * ------------------------------------------------------------------------- */\n\n/**\n * Mount options\n */\ninterface MountOptions {\n print$: Observable /* Media print observable */\n}\n\n/* ----------------------------------------------------------------------------\n * Helper functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Find all annotation markers in the given code block\n *\n * @param container - Containing element\n *\n * @returns Annotation markers\n */\nfunction findAnnotationMarkers(container: HTMLElement): Text[] {\n const markers: Text[] = []\n for (const comment of getElements(\".c, .c1, .cm\", container)) {\n let match: RegExpExecArray | null\n\n /* Split text at marker and add to list */\n let text = comment.firstChild as Text\n if (text instanceof Text)\n while ((match = /\\((\\d+)\\)/.exec(text.textContent!))) {\n const marker = text.splitText(match.index)\n text = marker.splitText(match[0].length)\n markers.push(marker)\n }\n }\n return markers\n}\n\n/**\n * Swap the child nodes of two elements\n *\n * @param source - Source element\n * @param target - Target element\n */\nfunction swap(source: HTMLElement, target: HTMLElement): void {\n target.append(...Array.from(source.childNodes))\n}\n\n/* ----------------------------------------------------------------------------\n * Functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Mount annotation list\n *\n * This function analyzes the containing code block and checks for markers\n * referring to elements in the given annotation list. If no markers are found,\n * the list is left untouched. Otherwise, list elements are rendered as\n * annotations inside the code block.\n *\n * @param el - Annotation list element\n * @param container - Containing element\n * @param options - Options\n *\n * @returns Annotation component observable\n */\nexport function mountAnnotationList(\n el: HTMLElement, container: HTMLElement, { print$ }: MountOptions\n): Observable> {\n\n /* Find and replace all markers with empty annotations */\n const annotations = new Map()\n for (const marker of findAnnotationMarkers(container)) {\n const [, id] = marker.textContent!.match(/\\((\\d+)\\)/)!\n if (getOptionalElement(`li:nth-child(${id})`, el)) {\n annotations.set(+id, renderAnnotation(+id))\n marker.replaceWith(annotations.get(+id)!)\n }\n }\n\n /* Keep list if there are no annotations to render */\n if (annotations.size === 0)\n return EMPTY\n\n /* Create and return component */\n return defer(() => {\n const done$ = new Subject()\n\n /* Handle print mode - see https://bit.ly/3rgPdpt */\n print$\n .pipe(\n takeUntil(done$.pipe(takeLast(1)))\n )\n .subscribe(active => {\n el.hidden = !active\n\n /* Show annotations in code block or list (print) */\n for (const [id, annotation] of annotations) {\n const inner = getElement(\".md-typeset\", annotation)\n const child = getElement(`li:nth-child(${id})`, el)\n if (!active)\n swap(child, inner)\n else\n swap(inner, child)\n }\n })\n\n /* Create and return component */\n return merge(...[...annotations]\n .map(([, annotation]) => (\n mountAnnotation(annotation, container)\n ))\n )\n .pipe(\n finalize(() => done$.complete()),\n share()\n )\n })\n}\n", "/*\n * Copyright (c) 2016-2022 Martin Donath \n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\nimport {\n Observable,\n map,\n of,\n shareReplay,\n tap\n} from \"rxjs\"\n\nimport { watchScript } from \"~/browser\"\nimport { h } from \"~/utilities\"\n\nimport { Component } from \"../../../_\"\n\nimport themeCSS from \"./index.css\"\n\n/* ----------------------------------------------------------------------------\n * Types\n * ------------------------------------------------------------------------- */\n\n/**\n * Mermaid diagram\n */\nexport interface Mermaid {}\n\n/* ----------------------------------------------------------------------------\n * Data\n * ------------------------------------------------------------------------- */\n\n/**\n * Mermaid instance observable\n */\nlet mermaid$: Observable\n\n/**\n * Global sequence number for diagrams\n */\nlet sequence = 0\n\n/* ----------------------------------------------------------------------------\n * Helper functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Fetch Mermaid script\n *\n * @returns Mermaid scripts observable\n */\nfunction fetchScripts(): Observable {\n return typeof mermaid === \"undefined\" || mermaid instanceof Element\n ? watchScript(\"https://unpkg.com/mermaid@9.0.1/dist/mermaid.min.js\")\n : of(undefined)\n}\n\n/* ----------------------------------------------------------------------------\n * Functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Mount Mermaid diagram\n *\n * @param el - Code block element\n *\n * @returns Mermaid diagram component observable\n */\nexport function mountMermaid(\n el: HTMLElement\n): Observable> {\n el.classList.remove(\"mermaid\") // Hack: mitigate https://bit.ly/3CiN6Du\n mermaid$ ||= fetchScripts()\n .pipe(\n tap(() => mermaid.initialize({\n startOnLoad: false,\n themeCSS\n })),\n map(() => undefined),\n shareReplay(1)\n )\n\n /* Render diagram */\n mermaid$.subscribe(() => {\n el.classList.add(\"mermaid\") // Hack: mitigate https://bit.ly/3CiN6Du\n const id = `__mermaid_${sequence++}`\n const host = h(\"div\", { class: \"mermaid\" })\n mermaid.mermaidAPI.render(id, el.textContent, (svg: string) => {\n\n /* Create a shadow root and inject diagram */\n const shadow = host.attachShadow({ mode: \"closed\" })\n shadow.innerHTML = svg\n\n /* Replace code block with diagram */\n el.replaceWith(host)\n })\n })\n\n /* Create and return component */\n return mermaid$\n .pipe(\n map(() => ({ ref: el }))\n )\n}\n", "/*\n * Copyright (c) 2016-2022 Martin Donath \n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\nimport {\n Observable,\n Subject,\n defer,\n filter,\n finalize,\n map,\n merge,\n tap\n} from \"rxjs\"\n\nimport { Component } from \"../../_\"\n\n/* ----------------------------------------------------------------------------\n * Types\n * ------------------------------------------------------------------------- */\n\n/**\n * Details\n */\nexport interface Details {\n action: \"open\" | \"close\" /* Details state */\n reveal?: boolean /* Details is revealed */\n}\n\n/* ----------------------------------------------------------------------------\n * Helper types\n * ------------------------------------------------------------------------- */\n\n/**\n * Watch options\n */\ninterface WatchOptions {\n target$: Observable /* Location target observable */\n print$: Observable /* Media print observable */\n}\n\n/**\n * Mount options\n */\ninterface MountOptions {\n target$: Observable /* Location target observable */\n print$: Observable /* Media print observable */\n}\n\n/* ----------------------------------------------------------------------------\n * Functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Watch details\n *\n * @param el - Details element\n * @param options - Options\n *\n * @returns Details observable\n */\nexport function watchDetails(\n el: HTMLDetailsElement, { target$, print$ }: WatchOptions\n): Observable
    {\n let open = true\n return merge(\n\n /* Open and focus details on location target */\n target$\n .pipe(\n map(target => target.closest(\"details:not([open])\")!),\n filter(details => el === details),\n map(() => ({\n action: \"open\", reveal: true\n }) as Details)\n ),\n\n /* Open details on print and close afterwards */\n print$\n .pipe(\n filter(active => active || !open),\n tap(() => open = el.open),\n map(active => ({\n action: active ? \"open\" : \"close\"\n }) as Details)\n )\n )\n}\n\n/**\n * Mount details\n *\n * This function ensures that `details` tags are opened on anchor jumps and\n * prior to printing, so the whole content of the page is visible.\n *\n * @param el - Details element\n * @param options - Options\n *\n * @returns Details component observable\n */\nexport function mountDetails(\n el: HTMLDetailsElement, options: MountOptions\n): Observable> {\n return defer(() => {\n const push$ = new Subject
    ()\n push$.subscribe(({ action, reveal }) => {\n if (action === \"open\")\n el.setAttribute(\"open\", \"\")\n else\n el.removeAttribute(\"open\")\n if (reveal)\n el.scrollIntoView()\n })\n\n /* Create and return component */\n return watchDetails(el, options)\n .pipe(\n tap(state => push$.next(state)),\n finalize(() => push$.complete()),\n map(state => ({ ref: el, ...state }))\n )\n })\n}\n", "/*\n * Copyright (c) 2016-2022 Martin Donath \n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\nimport { Observable, of } from \"rxjs\"\n\nimport { renderTable } from \"~/templates\"\nimport { h } from \"~/utilities\"\n\nimport { Component } from \"../../_\"\n\n/* ----------------------------------------------------------------------------\n * Types\n * ------------------------------------------------------------------------- */\n\n/**\n * Data table\n */\nexport interface DataTable {}\n\n/* ----------------------------------------------------------------------------\n * Data\n * ------------------------------------------------------------------------- */\n\n/**\n * Sentinel for replacement\n */\nconst sentinel = h(\"table\")\n\n/* ----------------------------------------------------------------------------\n * Functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Mount data table\n *\n * This function wraps a data table in another scrollable container, so it can\n * be smoothly scrolled on smaller screen sizes and won't break the layout.\n *\n * @param el - Data table element\n *\n * @returns Data table component observable\n */\nexport function mountDataTable(\n el: HTMLElement\n): Observable> {\n el.replaceWith(sentinel)\n sentinel.replaceWith(renderTable(el))\n\n /* Create and return component */\n return of({ ref: el })\n}\n", "/*\n * Copyright (c) 2016-2022 Martin Donath \n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\nimport {\n Observable,\n Subject,\n animationFrameScheduler,\n asyncScheduler,\n auditTime,\n combineLatest,\n defer,\n finalize,\n fromEvent,\n map,\n merge,\n skip,\n startWith,\n subscribeOn,\n takeLast,\n takeUntil,\n tap\n} from \"rxjs\"\n\nimport { feature } from \"~/_\"\nimport {\n getElement,\n getElementContentOffset,\n getElementContentSize,\n getElementOffset,\n getElementSize,\n getElements,\n watchElementContentOffset,\n watchElementSize\n} from \"~/browser\"\nimport { renderTabbedControl } from \"~/templates\"\n\nimport { Component } from \"../../_\"\n\n/* ----------------------------------------------------------------------------\n * Types\n * ------------------------------------------------------------------------- */\n\n/**\n * Content tabs\n */\nexport interface ContentTabs {\n active: HTMLLabelElement /* Active tab label */\n}\n\n/* ----------------------------------------------------------------------------\n * Functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Watch content tabs\n *\n * @param el - Content tabs element\n *\n * @returns Content tabs observable\n */\nexport function watchContentTabs(\n el: HTMLElement\n): Observable {\n const inputs = getElements(\":scope > input\", el)\n const initial = inputs.find(input => input.checked) || inputs[0]\n return merge(...inputs.map(input => fromEvent(input, \"change\")\n .pipe(\n map(() => getElement(`label[for=${input.id}]`))\n )\n ))\n .pipe(\n startWith(getElement(`label[for=${initial.id}]`)),\n map(active => ({ active }))\n )\n}\n\n/**\n * Mount content tabs\n *\n * This function scrolls the active tab into view. While this functionality is\n * provided by browsers as part of `scrollInfoView`, browsers will always also\n * scroll the vertical axis, which we do not want. Thus, we decided to provide\n * this functionality ourselves.\n *\n * @param el - Content tabs element\n *\n * @returns Content tabs component observable\n */\nexport function mountContentTabs(\n el: HTMLElement\n): Observable> {\n\n /* Render content tab previous button for pagination */\n const prev = renderTabbedControl(\"prev\")\n el.append(prev)\n\n /* Render content tab next button for pagination */\n const next = renderTabbedControl(\"next\")\n el.append(next)\n\n /* Mount component on subscription */\n const container = getElement(\".tabbed-labels\", el)\n return defer(() => {\n const push$ = new Subject()\n const done$ = push$.pipe(takeLast(1))\n combineLatest([push$, watchElementSize(el)])\n .pipe(\n auditTime(1, animationFrameScheduler),\n takeUntil(done$)\n )\n .subscribe({\n\n /* Handle emission */\n next([{ active }, size]) {\n const offset = getElementOffset(active)\n const { width } = getElementSize(active)\n\n /* Set tab indicator offset and width */\n el.style.setProperty(\"--md-indicator-x\", `${offset.x}px`)\n el.style.setProperty(\"--md-indicator-width\", `${width}px`)\n\n /* Scroll container to active content tab */\n const content = getElementContentOffset(container)\n if (\n offset.x < content.x ||\n offset.x + width > content.x + size.width\n )\n container.scrollTo({\n left: Math.max(0, offset.x - 16),\n behavior: \"smooth\"\n })\n },\n\n /* Handle complete */\n complete() {\n el.style.removeProperty(\"--md-indicator-x\")\n el.style.removeProperty(\"--md-indicator-width\")\n }\n })\n\n /* Hide content tab buttons on borders */\n combineLatest([\n watchElementContentOffset(container),\n watchElementSize(container)\n ])\n .pipe(\n takeUntil(done$)\n )\n .subscribe(([offset, size]) => {\n const content = getElementContentSize(container)\n prev.hidden = offset.x < 16\n next.hidden = offset.x > content.width - size.width - 16\n })\n\n /* Paginate content tab container on click */\n merge(\n fromEvent(prev, \"click\").pipe(map(() => -1)),\n fromEvent(next, \"click\").pipe(map(() => +1))\n )\n .pipe(\n takeUntil(done$)\n )\n .subscribe(direction => {\n const { width } = getElementSize(container)\n container.scrollBy({\n left: width * direction,\n behavior: \"smooth\"\n })\n })\n\n /* Set up linking of content tabs, if enabled */\n if (feature(\"content.tabs.link\"))\n push$.pipe(skip(1))\n .subscribe(({ active }) => {\n const tab = active.innerText.trim()\n for (const set of getElements(\"[data-tabs]\"))\n for (const input of getElements(\n \":scope > input\", set\n )) {\n const label = getElement(`label[for=${input.id}]`)\n if (label.innerText.trim() === tab) {\n input.click()\n break\n }\n }\n\n /* Persist active tabs in local storage */\n const tabs = __md_get(\"__tabs\") || []\n __md_set(\"__tabs\", [...new Set([tab, ...tabs])])\n })\n\n /* Create and return component */\n return watchContentTabs(el)\n .pipe(\n tap(state => push$.next(state)),\n finalize(() => push$.complete()),\n map(state => ({ ref: el, ...state }))\n )\n })\n .pipe(\n subscribeOn(asyncScheduler)\n )\n}\n", "/*\n * Copyright (c) 2016-2022 Martin Donath \n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\nimport { Observable, merge } from \"rxjs\"\n\nimport { getElements } from \"~/browser\"\n\nimport { Component } from \"../../_\"\nimport { Annotation } from \"../annotation\"\nimport {\n CodeBlock,\n Mermaid,\n mountCodeBlock,\n mountMermaid\n} from \"../code\"\nimport {\n Details,\n mountDetails\n} from \"../details\"\nimport {\n DataTable,\n mountDataTable\n} from \"../table\"\nimport {\n ContentTabs,\n mountContentTabs\n} from \"../tabs\"\n\n/* ----------------------------------------------------------------------------\n * Types\n * ------------------------------------------------------------------------- */\n\n/**\n * Content\n */\nexport type Content =\n | Annotation\n | ContentTabs\n | CodeBlock\n | Mermaid\n | DataTable\n | Details\n\n/* ----------------------------------------------------------------------------\n * Helper types\n * ------------------------------------------------------------------------- */\n\n/**\n * Mount options\n */\ninterface MountOptions {\n target$: Observable /* Location target observable */\n print$: Observable /* Media print observable */\n}\n\n/* ----------------------------------------------------------------------------\n * Functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Mount content\n *\n * This function mounts all components that are found in the content of the\n * actual article, including code blocks, data tables and details.\n *\n * @param el - Content element\n * @param options - Options\n *\n * @returns Content component observable\n */\nexport function mountContent(\n el: HTMLElement, { target$, print$ }: MountOptions\n): Observable> {\n return merge(\n\n /* Code blocks */\n ...getElements(\"pre:not(.mermaid) > code\", el)\n .map(child => mountCodeBlock(child, { print$ })),\n\n /* Mermaid diagrams */\n ...getElements(\"pre.mermaid\", el)\n .map(child => mountMermaid(child)),\n\n /* Data tables */\n ...getElements(\"table:not([class])\", el)\n .map(child => mountDataTable(child)),\n\n /* Details */\n ...getElements(\"details\", el)\n .map(child => mountDetails(child, { target$, print$ })),\n\n /* Content tabs */\n ...getElements(\"[data-tabs]\", el)\n .map(child => mountContentTabs(child))\n )\n}\n", "/*\n * Copyright (c) 2016-2022 Martin Donath \n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\nimport {\n Observable,\n Subject,\n defer,\n delay,\n finalize,\n map,\n merge,\n of,\n switchMap,\n tap\n} from \"rxjs\"\n\nimport { getElement } from \"~/browser\"\n\nimport { Component } from \"../_\"\n\n/* ----------------------------------------------------------------------------\n * Types\n * ------------------------------------------------------------------------- */\n\n/**\n * Dialog\n */\nexport interface Dialog {\n message: string /* Dialog message */\n active: boolean /* Dialog is active */\n}\n\n/* ----------------------------------------------------------------------------\n * Helper types\n * ------------------------------------------------------------------------- */\n\n/**\n * Watch options\n */\ninterface WatchOptions {\n alert$: Subject /* Alert subject */\n}\n\n/**\n * Mount options\n */\ninterface MountOptions {\n alert$: Subject /* Alert subject */\n}\n\n/* ----------------------------------------------------------------------------\n * Functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Watch dialog\n *\n * @param _el - Dialog element\n * @param options - Options\n *\n * @returns Dialog observable\n */\nexport function watchDialog(\n _el: HTMLElement, { alert$ }: WatchOptions\n): Observable {\n return alert$\n .pipe(\n switchMap(message => merge(\n of(true),\n of(false).pipe(delay(2000))\n )\n .pipe(\n map(active => ({ message, active }))\n )\n )\n )\n}\n\n/**\n * Mount dialog\n *\n * This function reveals the dialog in the right corner when a new alert is\n * emitted through the subject that is passed as part of the options.\n *\n * @param el - Dialog element\n * @param options - Options\n *\n * @returns Dialog component observable\n */\nexport function mountDialog(\n el: HTMLElement, options: MountOptions\n): Observable> {\n const inner = getElement(\".md-typeset\", el)\n return defer(() => {\n const push$ = new Subject()\n push$.subscribe(({ message, active }) => {\n el.classList.toggle(\"md-dialog--active\", active)\n inner.textContent = message\n })\n\n /* Create and return component */\n return watchDialog(el, options)\n .pipe(\n tap(state => push$.next(state)),\n finalize(() => push$.complete()),\n map(state => ({ ref: el, ...state }))\n )\n })\n}\n", "/*\n * Copyright (c) 2016-2022 Martin Donath \n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\nimport {\n Observable,\n Subject,\n bufferCount,\n combineLatest,\n combineLatestWith,\n defer,\n distinctUntilChanged,\n distinctUntilKeyChanged,\n filter,\n map,\n of,\n shareReplay,\n startWith,\n switchMap,\n takeLast,\n takeUntil\n} from \"rxjs\"\n\nimport { feature } from \"~/_\"\nimport {\n Viewport,\n watchElementSize,\n watchToggle\n} from \"~/browser\"\n\nimport { Component } from \"../../_\"\nimport { Main } from \"../../main\"\n\n/* ----------------------------------------------------------------------------\n * Types\n * ------------------------------------------------------------------------- */\n\n/**\n * Header\n */\nexport interface Header {\n height: number /* Header visible height */\n hidden: boolean /* Header is hidden */\n}\n\n/* ----------------------------------------------------------------------------\n * Helper types\n * ------------------------------------------------------------------------- */\n\n/**\n * Watch options\n */\ninterface WatchOptions {\n viewport$: Observable /* Viewport observable */\n}\n\n/**\n * Mount options\n */\ninterface MountOptions {\n viewport$: Observable /* Viewport observable */\n header$: Observable
    /* Header observable */\n main$: Observable
    /* Main area observable */\n}\n\n/* ----------------------------------------------------------------------------\n * Helper functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Compute whether the header is hidden\n *\n * If the user scrolls past a certain threshold, the header can be hidden when\n * scrolling down, and shown when scrolling up.\n *\n * @param options - Options\n *\n * @returns Toggle observable\n */\nfunction isHidden({ viewport$ }: WatchOptions): Observable {\n if (!feature(\"header.autohide\"))\n return of(false)\n\n /* Compute direction and turning point */\n const direction$ = viewport$\n .pipe(\n map(({ offset: { y } }) => y),\n bufferCount(2, 1),\n map(([a, b]) => [a < b, b] as const),\n distinctUntilKeyChanged(0)\n )\n\n /* Compute whether header should be hidden */\n const hidden$ = combineLatest([viewport$, direction$])\n .pipe(\n filter(([{ offset }, [, y]]) => Math.abs(y - offset.y) > 100),\n map(([, [direction]]) => direction),\n distinctUntilChanged()\n )\n\n /* Compute threshold for hiding */\n const search$ = watchToggle(\"search\")\n return combineLatest([viewport$, search$])\n .pipe(\n map(([{ offset }, search]) => offset.y > 400 && !search),\n distinctUntilChanged(),\n switchMap(active => active ? hidden$ : of(false)),\n startWith(false)\n )\n}\n\n/* ----------------------------------------------------------------------------\n * Functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Watch header\n *\n * @param el - Header element\n * @param options - Options\n *\n * @returns Header observable\n */\nexport function watchHeader(\n el: HTMLElement, options: WatchOptions\n): Observable
    {\n return defer(() => combineLatest([\n watchElementSize(el),\n isHidden(options)\n ]))\n .pipe(\n map(([{ height }, hidden]) => ({\n height,\n hidden\n })),\n distinctUntilChanged((a, b) => (\n a.height === b.height &&\n a.hidden === b.hidden\n )),\n shareReplay(1)\n )\n}\n\n/**\n * Mount header\n *\n * This function manages the different states of the header, i.e. whether it's\n * hidden or rendered with a shadow. This depends heavily on the main area.\n *\n * @param el - Header element\n * @param options - Options\n *\n * @returns Header component observable\n */\nexport function mountHeader(\n el: HTMLElement, { header$, main$ }: MountOptions\n): Observable> {\n return defer(() => {\n const push$ = new Subject
    ()\n const done$ = push$.pipe(takeLast(1))\n push$\n .pipe(\n distinctUntilKeyChanged(\"active\"),\n combineLatestWith(header$)\n )\n .subscribe(([{ active }, { hidden }]) => {\n el.classList.toggle(\"md-header--shadow\", active && !hidden)\n el.hidden = hidden\n })\n\n /* Link to main area */\n main$.subscribe(push$)\n\n /* Create and return component */\n return header$\n .pipe(\n takeUntil(done$),\n map(state => ({ ref: el, ...state }))\n )\n })\n}\n", "/*\n * Copyright (c) 2016-2022 Martin Donath \n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\nimport {\n EMPTY,\n Observable,\n Subject,\n defer,\n distinctUntilKeyChanged,\n finalize,\n map,\n tap\n} from \"rxjs\"\n\nimport {\n Viewport,\n getElementSize,\n getOptionalElement,\n watchViewportAt\n} from \"~/browser\"\n\nimport { Component } from \"../../_\"\nimport { Header } from \"../_\"\n\n/* ----------------------------------------------------------------------------\n * Types\n * ------------------------------------------------------------------------- */\n\n/**\n * Header\n */\nexport interface HeaderTitle {\n active: boolean /* Header title is active */\n}\n\n/* ----------------------------------------------------------------------------\n * Helper types\n * ------------------------------------------------------------------------- */\n\n/**\n * Watch options\n */\ninterface WatchOptions {\n viewport$: Observable /* Viewport observable */\n header$: Observable
    /* Header observable */\n}\n\n/**\n * Mount options\n */\ninterface MountOptions {\n viewport$: Observable /* Viewport observable */\n header$: Observable
    /* Header observable */\n}\n\n/* ----------------------------------------------------------------------------\n * Functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Watch header title\n *\n * @param el - Heading element\n * @param options - Options\n *\n * @returns Header title observable\n */\nexport function watchHeaderTitle(\n el: HTMLElement, { viewport$, header$ }: WatchOptions\n): Observable {\n return watchViewportAt(el, { viewport$, header$ })\n .pipe(\n map(({ offset: { y } }) => {\n const { height } = getElementSize(el)\n return {\n active: y >= height\n }\n }),\n distinctUntilKeyChanged(\"active\")\n )\n}\n\n/**\n * Mount header title\n *\n * This function swaps the header title from the site title to the title of the\n * current page when the user scrolls past the first headline.\n *\n * @param el - Header title element\n * @param options - Options\n *\n * @returns Header title component observable\n */\nexport function mountHeaderTitle(\n el: HTMLElement, options: MountOptions\n): Observable> {\n return defer(() => {\n const push$ = new Subject()\n push$.subscribe(({ active }) => {\n el.classList.toggle(\"md-header__title--active\", active)\n })\n\n /* Obtain headline, if any */\n const heading = getOptionalElement(\"article h1\")\n if (typeof heading === \"undefined\")\n return EMPTY\n\n /* Create and return component */\n return watchHeaderTitle(heading, options)\n .pipe(\n tap(state => push$.next(state)),\n finalize(() => push$.complete()),\n map(state => ({ ref: el, ...state }))\n )\n })\n}\n", "/*\n * Copyright (c) 2016-2022 Martin Donath \n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\nimport {\n Observable,\n combineLatest,\n distinctUntilChanged,\n distinctUntilKeyChanged,\n map,\n switchMap\n} from \"rxjs\"\n\nimport {\n Viewport,\n watchElementSize\n} from \"~/browser\"\n\nimport { Header } from \"../header\"\n\n/* ----------------------------------------------------------------------------\n * Types\n * ------------------------------------------------------------------------- */\n\n/**\n * Main area\n */\nexport interface Main {\n offset: number /* Main area top offset */\n height: number /* Main area visible height */\n active: boolean /* Main area is active */\n}\n\n/* ----------------------------------------------------------------------------\n * Helper types\n * ------------------------------------------------------------------------- */\n\n/**\n * Watch options\n */\ninterface WatchOptions {\n viewport$: Observable /* Viewport observable */\n header$: Observable
    /* Header observable */\n}\n\n/* ----------------------------------------------------------------------------\n * Functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Watch main area\n *\n * This function returns an observable that computes the visual parameters of\n * the main area which depends on the viewport vertical offset and height, as\n * well as the height of the header element, if the header is fixed.\n *\n * @param el - Main area element\n * @param options - Options\n *\n * @returns Main area observable\n */\nexport function watchMain(\n el: HTMLElement, { viewport$, header$ }: WatchOptions\n): Observable
    {\n\n /* Compute necessary adjustment for header */\n const adjust$ = header$\n .pipe(\n map(({ height }) => height),\n distinctUntilChanged()\n )\n\n /* Compute the main area's top and bottom borders */\n const border$ = adjust$\n .pipe(\n switchMap(() => watchElementSize(el)\n .pipe(\n map(({ height }) => ({\n top: el.offsetTop,\n bottom: el.offsetTop + height\n })),\n distinctUntilKeyChanged(\"bottom\")\n )\n )\n )\n\n /* Compute the main area's offset, visible height and if we scrolled past */\n return combineLatest([adjust$, border$, viewport$])\n .pipe(\n map(([header, { top, bottom }, { offset: { y }, size: { height } }]) => {\n height = Math.max(0, height\n - Math.max(0, top - y, header)\n - Math.max(0, height + y - bottom)\n )\n return {\n offset: top - header,\n height,\n active: top - header <= y\n }\n }),\n distinctUntilChanged((a, b) => (\n a.offset === b.offset &&\n a.height === b.height &&\n a.active === b.active\n ))\n )\n}\n", "/*\n * Copyright (c) 2016-2022 Martin Donath \n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\nimport {\n Observable,\n Subject,\n asyncScheduler,\n defer,\n finalize,\n fromEvent,\n map,\n mergeMap,\n observeOn,\n of,\n shareReplay,\n startWith,\n tap\n} from \"rxjs\"\n\nimport { getElements } from \"~/browser\"\n\nimport { Component } from \"../_\"\n\n/* ----------------------------------------------------------------------------\n * Types\n * ------------------------------------------------------------------------- */\n\n/**\n * Palette colors\n */\nexport interface PaletteColor {\n scheme?: string /* Color scheme */\n primary?: string /* Primary color */\n accent?: string /* Accent color */\n}\n\n/**\n * Palette\n */\nexport interface Palette {\n index: number /* Palette index */\n color: PaletteColor /* Palette colors */\n}\n\n/* ----------------------------------------------------------------------------\n * Functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Watch color palette\n *\n * @param inputs - Color palette element\n *\n * @returns Color palette observable\n */\nexport function watchPalette(\n inputs: HTMLInputElement[]\n): Observable {\n const current = __md_get(\"__palette\") || {\n index: inputs.findIndex(input => matchMedia(\n input.getAttribute(\"data-md-color-media\")!\n ).matches)\n }\n\n /* Emit changes in color palette */\n return of(...inputs)\n .pipe(\n mergeMap(input => fromEvent(input, \"change\")\n .pipe(\n map(() => input)\n )\n ),\n startWith(inputs[Math.max(0, current.index)]),\n map(input => ({\n index: inputs.indexOf(input),\n color: {\n scheme: input.getAttribute(\"data-md-color-scheme\"),\n primary: input.getAttribute(\"data-md-color-primary\"),\n accent: input.getAttribute(\"data-md-color-accent\")\n }\n } as Palette)),\n shareReplay(1)\n )\n}\n\n/**\n * Mount color palette\n *\n * @param el - Color palette element\n *\n * @returns Color palette component observable\n */\nexport function mountPalette(\n el: HTMLElement\n): Observable> {\n return defer(() => {\n const push$ = new Subject()\n push$.subscribe(palette => {\n document.body.setAttribute(\"data-md-color-switching\", \"\")\n\n /* Set color palette */\n for (const [key, value] of Object.entries(palette.color))\n document.body.setAttribute(`data-md-color-${key}`, value)\n\n /* Toggle visibility */\n for (let index = 0; index < inputs.length; index++) {\n const label = inputs[index].nextElementSibling\n if (label instanceof HTMLElement)\n label.hidden = palette.index !== index\n }\n\n /* Persist preference in local storage */\n __md_set(\"__palette\", palette)\n })\n\n /* Revert transition durations after color switch */\n push$.pipe(observeOn(asyncScheduler))\n .subscribe(() => {\n document.body.removeAttribute(\"data-md-color-switching\")\n })\n\n /* Create and return component */\n const inputs = getElements(\"input\", el)\n return watchPalette(inputs)\n .pipe(\n tap(state => push$.next(state)),\n finalize(() => push$.complete()),\n map(state => ({ ref: el, ...state }))\n )\n })\n}\n", "/*\n * Copyright (c) 2016-2022 Martin Donath \n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\nimport ClipboardJS from \"clipboard\"\nimport {\n Observable,\n Subject,\n map,\n tap\n} from \"rxjs\"\n\nimport { translation } from \"~/_\"\nimport { getElement } from \"~/browser\"\n\n/* ----------------------------------------------------------------------------\n * Helper types\n * ------------------------------------------------------------------------- */\n\n/**\n * Setup options\n */\ninterface SetupOptions {\n alert$: Subject /* Alert subject */\n}\n\n/* ----------------------------------------------------------------------------\n * Helper functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Extract text to copy\n *\n * @param el - HTML element\n *\n * @returns Extracted text\n */\nfunction extract(el: HTMLElement): string {\n el.setAttribute(\"data-md-copying\", \"\")\n const text = el.innerText\n el.removeAttribute(\"data-md-copying\")\n return text\n}\n\n/* ----------------------------------------------------------------------------\n * Functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Set up Clipboard.js integration\n *\n * @param options - Options\n */\nexport function setupClipboardJS(\n { alert$ }: SetupOptions\n): void {\n if (ClipboardJS.isSupported()) {\n new Observable(subscriber => {\n new ClipboardJS(\"[data-clipboard-target], [data-clipboard-text]\", {\n text: el => (\n el.getAttribute(\"data-clipboard-text\")! ||\n extract(getElement(\n el.getAttribute(\"data-clipboard-target\")!\n ))\n )\n })\n .on(\"success\", ev => subscriber.next(ev))\n })\n .pipe(\n tap(ev => {\n const trigger = ev.trigger as HTMLElement\n trigger.focus()\n }),\n map(() => translation(\"clipboard.copied\"))\n )\n .subscribe(alert$)\n }\n}\n", "/*\n * Copyright (c) 2016-2022 Martin Donath \n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\nimport {\n EMPTY,\n Observable,\n catchError,\n defaultIfEmpty,\n map,\n of,\n tap\n} from \"rxjs\"\n\nimport { configuration } from \"~/_\"\nimport { getElements, requestXML } from \"~/browser\"\n\n/* ----------------------------------------------------------------------------\n * Types\n * ------------------------------------------------------------------------- */\n\n/**\n * Sitemap, i.e. a list of URLs\n */\nexport type Sitemap = string[]\n\n/* ----------------------------------------------------------------------------\n * Helper functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Preprocess a list of URLs\n *\n * This function replaces the `site_url` in the sitemap with the actual base\n * URL, to allow instant loading to work in occasions like Netlify previews.\n *\n * @param urls - URLs\n *\n * @returns URL path parts\n */\nfunction preprocess(urls: Sitemap): Sitemap {\n if (urls.length < 2)\n return [\"\"]\n\n /* Take the first two URLs and remove everything after the last slash */\n const [root, next] = [...urls]\n .sort((a, b) => a.length - b.length)\n .map(url => url.replace(/[^/]+$/, \"\"))\n\n /* Compute common prefix */\n let index = 0\n if (root === next)\n index = root.length\n else\n while (root.charCodeAt(index) === next.charCodeAt(index))\n index++\n\n /* Remove common prefix and return in original order */\n return urls.map(url => url.replace(root.slice(0, index), \"\"))\n}\n\n/* ----------------------------------------------------------------------------\n * Functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Fetch the sitemap for the given base URL\n *\n * @param base - Base URL\n *\n * @returns Sitemap observable\n */\nexport function fetchSitemap(base?: URL): Observable {\n const cached = __md_get(\"__sitemap\", sessionStorage, base)\n if (cached) {\n return of(cached)\n } else {\n const config = configuration()\n return requestXML(new URL(\"sitemap.xml\", base || config.base))\n .pipe(\n map(sitemap => preprocess(getElements(\"loc\", sitemap)\n .map(node => node.textContent!)\n )),\n catchError(() => EMPTY), // @todo refactor instant loading\n defaultIfEmpty([]),\n tap(sitemap => __md_set(\"__sitemap\", sitemap, sessionStorage, base))\n )\n }\n}\n", "/*\n * Copyright (c) 2016-2022 Martin Donath \n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\nimport {\n EMPTY,\n NEVER,\n Observable,\n Subject,\n bufferCount,\n catchError,\n concatMap,\n debounceTime,\n distinctUntilChanged,\n distinctUntilKeyChanged,\n filter,\n fromEvent,\n map,\n merge,\n of,\n sample,\n share,\n skip,\n skipUntil,\n switchMap\n} from \"rxjs\"\n\nimport { configuration, feature } from \"~/_\"\nimport {\n Viewport,\n ViewportOffset,\n getElements,\n getOptionalElement,\n request,\n setLocation,\n setLocationHash\n} from \"~/browser\"\nimport { getComponentElement } from \"~/components\"\nimport { h } from \"~/utilities\"\n\nimport { fetchSitemap } from \"../sitemap\"\n\n/* ----------------------------------------------------------------------------\n * Types\n * ------------------------------------------------------------------------- */\n\n/**\n * History state\n */\nexport interface HistoryState {\n url: URL /* State URL */\n offset?: ViewportOffset /* State viewport offset */\n}\n\n/* ----------------------------------------------------------------------------\n * Helper types\n * ------------------------------------------------------------------------- */\n\n/**\n * Setup options\n */\ninterface SetupOptions {\n document$: Subject /* Document subject */\n location$: Subject /* Location subject */\n viewport$: Observable /* Viewport observable */\n}\n\n/* ----------------------------------------------------------------------------\n * Functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Set up instant loading\n *\n * When fetching, theoretically, we could use `responseType: \"document\"`, but\n * since all MkDocs links are relative, we need to make sure that the current\n * location matches the document we just loaded. Otherwise any relative links\n * in the document could use the old location.\n *\n * This is the reason why we need to synchronize history events and the process\n * of fetching the document for navigation changes (except `popstate` events):\n *\n * 1. Fetch document via `XMLHTTPRequest`\n * 2. Set new location via `history.pushState`\n * 3. Parse and emit fetched document\n *\n * For `popstate` events, we must not use `history.pushState`, or the forward\n * history will be irreversibly overwritten. In case the request fails, the\n * location change is dispatched regularly.\n *\n * @param options - Options\n */\nexport function setupInstantLoading(\n { document$, location$, viewport$ }: SetupOptions\n): void {\n const config = configuration()\n if (location.protocol === \"file:\")\n return\n\n /* Disable automatic scroll restoration */\n if (\"scrollRestoration\" in history) {\n history.scrollRestoration = \"manual\"\n\n /* Hack: ensure that reloads restore viewport offset */\n fromEvent(window, \"beforeunload\")\n .subscribe(() => {\n history.scrollRestoration = \"auto\"\n })\n }\n\n /* Hack: ensure absolute favicon link to omit 404s when switching */\n const favicon = getOptionalElement(\"link[rel=icon]\")\n if (typeof favicon !== \"undefined\")\n favicon.href = favicon.href\n\n /* Intercept internal navigation */\n const push$ = fetchSitemap()\n .pipe(\n map(paths => paths.map(path => `${new URL(path, config.base)}`)),\n switchMap(urls => fromEvent(document.body, \"click\")\n .pipe(\n filter(ev => !ev.metaKey && !ev.ctrlKey),\n switchMap(ev => {\n if (ev.target instanceof Element) {\n const el = ev.target.closest(\"a\")\n if (el && !el.target) {\n const url = new URL(el.href)\n\n /* Canonicalize URL */\n url.search = \"\"\n url.hash = \"\"\n\n /* Check if URL should be intercepted */\n if (\n url.pathname !== location.pathname &&\n urls.includes(url.toString())\n ) {\n ev.preventDefault()\n return of({\n url: new URL(el.href)\n })\n }\n }\n }\n return NEVER\n })\n )\n ),\n share()\n )\n\n /* Intercept history back and forward */\n const pop$ = fromEvent(window, \"popstate\")\n .pipe(\n filter(ev => ev.state !== null),\n map(ev => ({\n url: new URL(location.href),\n offset: ev.state\n })),\n share()\n )\n\n /* Emit location change */\n merge(push$, pop$)\n .pipe(\n distinctUntilChanged((a, b) => a.url.href === b.url.href),\n map(({ url }) => url)\n )\n .subscribe(location$)\n\n /* Fetch document via `XMLHTTPRequest` */\n const response$ = location$\n .pipe(\n distinctUntilKeyChanged(\"pathname\"),\n switchMap(url => request(url.href)\n .pipe(\n catchError(() => {\n setLocation(url)\n return NEVER\n })\n )\n ),\n share()\n )\n\n /* Set new location via `history.pushState` */\n push$\n .pipe(\n sample(response$)\n )\n .subscribe(({ url }) => {\n history.pushState({}, \"\", `${url}`)\n })\n\n /* Parse and emit fetched document */\n const dom = new DOMParser()\n response$\n .pipe(\n switchMap(res => res.text()),\n map(res => dom.parseFromString(res, \"text/html\"))\n )\n .subscribe(document$)\n\n /* Replace meta tags and components */\n document$\n .pipe(\n skip(1)\n )\n .subscribe(replacement => {\n for (const selector of [\n\n /* Meta tags */\n \"title\",\n \"link[rel=canonical]\",\n \"meta[name=author]\",\n \"meta[name=description]\",\n\n /* Components */\n \"[data-md-component=announce]\",\n \"[data-md-component=container]\",\n \"[data-md-component=header-topic]\",\n \"[data-md-component=outdated]\",\n \"[data-md-component=logo]\",\n \"[data-md-component=skip]\",\n ...feature(\"navigation.tabs.sticky\")\n ? [\"[data-md-component=tabs]\"]\n : []\n ]) {\n const source = getOptionalElement(selector)\n const target = getOptionalElement(selector, replacement)\n if (\n typeof source !== \"undefined\" &&\n typeof target !== \"undefined\"\n ) {\n source.replaceWith(target)\n }\n }\n })\n\n /* Re-evaluate scripts */\n document$\n .pipe(\n skip(1),\n map(() => getComponentElement(\"container\")),\n switchMap(el => getElements(\"script\", el)),\n concatMap(el => {\n const script = h(\"script\")\n if (el.src) {\n for (const name of el.getAttributeNames())\n script.setAttribute(name, el.getAttribute(name)!)\n el.replaceWith(script)\n\n /* Complete when script is loaded */\n return new Observable(observer => {\n script.onload = () => observer.complete()\n })\n\n /* Complete immediately */\n } else {\n script.textContent = el.textContent\n el.replaceWith(script)\n return EMPTY\n }\n })\n )\n .subscribe()\n\n /* Emit history state change */\n merge(push$, pop$)\n .pipe(\n sample(document$)\n )\n .subscribe(({ url, offset }) => {\n if (url.hash && !offset) {\n setLocationHash(url.hash)\n } else {\n window.scrollTo(0, offset?.y || 0)\n }\n })\n\n /* Debounce update of viewport offset */\n viewport$\n .pipe(\n skipUntil(push$),\n debounceTime(250),\n distinctUntilKeyChanged(\"offset\")\n )\n .subscribe(({ offset }) => {\n history.replaceState(offset, \"\")\n })\n\n /* Set viewport offset from history */\n merge(push$, pop$)\n .pipe(\n bufferCount(2, 1),\n filter(([a, b]) => a.url.pathname === b.url.pathname),\n map(([, state]) => state)\n )\n .subscribe(({ offset }) => {\n window.scrollTo(0, offset?.y || 0)\n })\n}\n", "/*\n * Copyright (c) 2016-2022 Martin Donath \n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\nimport escapeHTML from \"escape-html\"\n\nimport { SearchIndexDocument } from \"../_\"\n\n/* ----------------------------------------------------------------------------\n * Types\n * ------------------------------------------------------------------------- */\n\n/**\n * Search document\n */\nexport interface SearchDocument extends SearchIndexDocument {\n parent?: SearchIndexDocument /* Parent article */\n}\n\n/* ------------------------------------------------------------------------- */\n\n/**\n * Search document mapping\n */\nexport type SearchDocumentMap = Map\n\n/* ----------------------------------------------------------------------------\n * Functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Create a search document mapping\n *\n * @param docs - Search index documents\n *\n * @returns Search document map\n */\nexport function setupSearchDocumentMap(\n docs: SearchIndexDocument[]\n): SearchDocumentMap {\n const documents = new Map()\n const parents = new Set()\n for (const doc of docs) {\n const [path, hash] = doc.location.split(\"#\")\n\n /* Extract location, title and tags */\n const location = doc.location\n const title = doc.title\n const tags = doc.tags\n\n /* Escape and cleanup text */\n const text = escapeHTML(doc.text)\n .replace(/\\s+(?=[,.:;!?])/g, \"\")\n .replace(/\\s+/g, \" \")\n\n /* Handle section */\n if (hash) {\n const parent = documents.get(path)!\n\n /* Ignore first section, override article */\n if (!parents.has(parent)) {\n parent.title = doc.title\n parent.text = text\n\n /* Remember that we processed the article */\n parents.add(parent)\n\n /* Add subsequent section */\n } else {\n documents.set(location, {\n location,\n title,\n text,\n parent\n })\n }\n\n /* Add article */\n } else {\n documents.set(location, {\n location,\n title,\n text,\n ...tags && { tags }\n })\n }\n }\n return documents\n}\n", "/*\n * Copyright (c) 2016-2022 Martin Donath \n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\nimport escapeHTML from \"escape-html\"\n\nimport { SearchIndexConfig } from \"../_\"\n\n/* ----------------------------------------------------------------------------\n * Types\n * ------------------------------------------------------------------------- */\n\n/**\n * Search highlight function\n *\n * @param value - Value\n *\n * @returns Highlighted value\n */\nexport type SearchHighlightFn = (value: string) => string\n\n/**\n * Search highlight factory function\n *\n * @param query - Query value\n *\n * @returns Search highlight function\n */\nexport type SearchHighlightFactoryFn = (query: string) => SearchHighlightFn\n\n/* ----------------------------------------------------------------------------\n * Functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Create a search highlighter\n *\n * @param config - Search index configuration\n * @param escape - Whether to escape HTML\n *\n * @returns Search highlight factory function\n */\nexport function setupSearchHighlighter(\n config: SearchIndexConfig, escape: boolean\n): SearchHighlightFactoryFn {\n const separator = new RegExp(config.separator, \"img\")\n const highlight = (_: unknown, data: string, term: string) => {\n return `${data}${term}`\n }\n\n /* Return factory function */\n return (query: string) => {\n query = query\n .replace(/[\\s*+\\-:~^]+/g, \" \")\n .trim()\n\n /* Create search term match expression */\n const match = new RegExp(`(^|${config.separator})(${\n query\n .replace(/[|\\\\{}()[\\]^$+*?.-]/g, \"\\\\$&\")\n .replace(separator, \"|\")\n })`, \"img\")\n\n /* Highlight string value */\n return value => (\n escape\n ? escapeHTML(value)\n : value\n )\n .replace(match, highlight)\n .replace(/<\\/mark>(\\s+)]*>/img, \"$1\")\n }\n}\n", "/*\n * Copyright (c) 2016-2022 Martin Donath \n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\n/* ----------------------------------------------------------------------------\n * Types\n * ------------------------------------------------------------------------- */\n\n/**\n * Search transformation function\n *\n * @param value - Query value\n *\n * @returns Transformed query value\n */\nexport type SearchTransformFn = (value: string) => string\n\n/* ----------------------------------------------------------------------------\n * Functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Default transformation function\n *\n * 1. Search for terms in quotation marks and prepend a `+` modifier to denote\n * that the resulting document must contain all terms, converting the query\n * to an `AND` query (as opposed to the default `OR` behavior). While users\n * may expect terms enclosed in quotation marks to map to span queries, i.e.\n * for which order is important, Lunr.js doesn't support them, so the best\n * we can do is to convert the terms to an `AND` query.\n *\n * 2. Replace control characters which are not located at the beginning of the\n * query or preceded by white space, or are not followed by a non-whitespace\n * character or are at the end of the query string. Furthermore, filter\n * unmatched quotation marks.\n *\n * 3. Trim excess whitespace from left and right.\n *\n * @param query - Query value\n *\n * @returns Transformed query value\n */\nexport function defaultTransform(query: string): string {\n return query\n .split(/\"([^\"]+)\"/g) /* => 1 */\n .map((terms, index) => index & 1\n ? terms.replace(/^\\b|^(?![^\\x00-\\x7F]|$)|\\s+/g, \" +\")\n : terms\n )\n .join(\"\")\n .replace(/\"|(?:^|\\s+)[*+\\-:^~]+(?=\\s+|$)/g, \"\") /* => 2 */\n .trim() /* => 3 */\n}\n", "/*\n * Copyright (c) 2016-2022 Martin Donath \n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A RTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\nimport { SearchIndex, SearchResult } from \"../../_\"\n\n/* ----------------------------------------------------------------------------\n * Types\n * ------------------------------------------------------------------------- */\n\n/**\n * Search message type\n */\nexport const enum SearchMessageType {\n SETUP, /* Search index setup */\n READY, /* Search index ready */\n QUERY, /* Search query */\n RESULT /* Search results */\n}\n\n/* ------------------------------------------------------------------------- */\n\n/**\n * Message containing the data necessary to setup the search index\n */\nexport interface SearchSetupMessage {\n type: SearchMessageType.SETUP /* Message type */\n data: SearchIndex /* Message data */\n}\n\n/**\n * Message indicating the search index is ready\n */\nexport interface SearchReadyMessage {\n type: SearchMessageType.READY /* Message type */\n}\n\n/**\n * Message containing a search query\n */\nexport interface SearchQueryMessage {\n type: SearchMessageType.QUERY /* Message type */\n data: string /* Message data */\n}\n\n/**\n * Message containing results for a search query\n */\nexport interface SearchResultMessage {\n type: SearchMessageType.RESULT /* Message type */\n data: SearchResult /* Message data */\n}\n\n/* ------------------------------------------------------------------------- */\n\n/**\n * Message exchanged with the search worker\n */\nexport type SearchMessage =\n | SearchSetupMessage\n | SearchReadyMessage\n | SearchQueryMessage\n | SearchResultMessage\n\n/* ----------------------------------------------------------------------------\n * Functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Type guard for search setup messages\n *\n * @param message - Search worker message\n *\n * @returns Test result\n */\nexport function isSearchSetupMessage(\n message: SearchMessage\n): message is SearchSetupMessage {\n return message.type === SearchMessageType.SETUP\n}\n\n/**\n * Type guard for search ready messages\n *\n * @param message - Search worker message\n *\n * @returns Test result\n */\nexport function isSearchReadyMessage(\n message: SearchMessage\n): message is SearchReadyMessage {\n return message.type === SearchMessageType.READY\n}\n\n/**\n * Type guard for search query messages\n *\n * @param message - Search worker message\n *\n * @returns Test result\n */\nexport function isSearchQueryMessage(\n message: SearchMessage\n): message is SearchQueryMessage {\n return message.type === SearchMessageType.QUERY\n}\n\n/**\n * Type guard for search result messages\n *\n * @param message - Search worker message\n *\n * @returns Test result\n */\nexport function isSearchResultMessage(\n message: SearchMessage\n): message is SearchResultMessage {\n return message.type === SearchMessageType.RESULT\n}\n", "/*\n * Copyright (c) 2016-2022 Martin Donath \n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A RTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\nimport {\n ObservableInput,\n Subject,\n from,\n map,\n share\n} from \"rxjs\"\n\nimport { configuration, feature, translation } from \"~/_\"\nimport { WorkerHandler, watchWorker } from \"~/browser\"\n\nimport { SearchIndex } from \"../../_\"\nimport {\n SearchOptions,\n SearchPipeline\n} from \"../../options\"\nimport {\n SearchMessage,\n SearchMessageType,\n SearchSetupMessage,\n isSearchResultMessage\n} from \"../message\"\n\n/* ----------------------------------------------------------------------------\n * Types\n * ------------------------------------------------------------------------- */\n\n/**\n * Search worker\n */\nexport type SearchWorker = WorkerHandler\n\n/* ----------------------------------------------------------------------------\n * Helper functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Set up search index\n *\n * @param data - Search index\n *\n * @returns Search index\n */\nfunction setupSearchIndex({ config, docs }: SearchIndex): SearchIndex {\n\n /* Override default language with value from translation */\n if (config.lang.length === 1 && config.lang[0] === \"en\")\n config.lang = [\n translation(\"search.config.lang\")\n ]\n\n /* Override default separator with value from translation */\n if (config.separator === \"[\\\\s\\\\-]+\")\n config.separator = translation(\"search.config.separator\")\n\n /* Set pipeline from translation */\n const pipeline = translation(\"search.config.pipeline\")\n .split(/\\s*,\\s*/)\n .filter(Boolean) as SearchPipeline\n\n /* Determine search options */\n const options: SearchOptions = {\n pipeline,\n suggestions: feature(\"search.suggest\")\n }\n\n /* Return search index after defaulting */\n return { config, docs, options }\n}\n\n/* ----------------------------------------------------------------------------\n * Functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Set up search worker\n *\n * This function creates a web worker to set up and query the search index,\n * which is done using Lunr.js. The index must be passed as an observable to\n * enable hacks like _localsearch_ via search index embedding as JSON.\n *\n * @param url - Worker URL\n * @param index - Search index observable input\n *\n * @returns Search worker\n */\nexport function setupSearchWorker(\n url: string, index: ObservableInput\n): SearchWorker {\n const config = configuration()\n const worker = new Worker(url)\n\n /* Create communication channels and resolve relative links */\n const tx$ = new Subject()\n const rx$ = watchWorker(worker, { tx$ })\n .pipe(\n map(message => {\n if (isSearchResultMessage(message)) {\n for (const result of message.data.items)\n for (const document of result)\n document.location = `${new URL(document.location, config.base)}`\n }\n return message\n }),\n share()\n )\n\n /* Set up search index */\n from(index)\n .pipe(\n map(data => ({\n type: SearchMessageType.SETUP,\n data: setupSearchIndex(data)\n } as SearchSetupMessage))\n )\n .subscribe(tx$.next.bind(tx$))\n\n /* Return search worker */\n return { tx$, rx$ }\n}\n", "/*\n * Copyright (c) 2016-2022 Martin Donath \n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\nimport {\n EMPTY,\n Subject,\n catchError,\n combineLatest,\n filter,\n fromEvent,\n map,\n of,\n switchMap,\n withLatestFrom\n} from \"rxjs\"\n\nimport { configuration } from \"~/_\"\nimport {\n getElement,\n getLocation,\n requestJSON,\n setLocation\n} from \"~/browser\"\nimport { getComponentElements } from \"~/components\"\nimport {\n Version,\n renderVersionSelector\n} from \"~/templates\"\n\nimport { fetchSitemap } from \"../sitemap\"\n\n/* ----------------------------------------------------------------------------\n * Helper types\n * ------------------------------------------------------------------------- */\n\n/**\n * Setup options\n */\ninterface SetupOptions {\n document$: Subject /* Document subject */\n}\n\n/* ----------------------------------------------------------------------------\n * Functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Set up version selector\n *\n * @param options - Options\n */\nexport function setupVersionSelector(\n { document$ }: SetupOptions\n): void {\n const config = configuration()\n const versions$ = requestJSON(\n new URL(\"../versions.json\", config.base)\n )\n .pipe(\n catchError(() => EMPTY) // @todo refactor instant loading\n )\n\n /* Determine current version */\n const current$ = versions$\n .pipe(\n map(versions => {\n const [, current] = config.base.match(/([^/]+)\\/?$/)!\n return versions.find(({ version, aliases }) => (\n version === current || aliases.includes(current)\n )) || versions[0]\n })\n )\n\n /* Intercept inter-version navigation */\n versions$\n .pipe(\n map(versions => new Map(versions.map(version => [\n `${new URL(`../${version.version}/`, config.base)}`,\n version\n ]))),\n switchMap(urls => fromEvent(document.body, \"click\")\n .pipe(\n filter(ev => !ev.metaKey && !ev.ctrlKey),\n withLatestFrom(current$),\n switchMap(([ev, current]) => {\n if (ev.target instanceof Element) {\n const el = ev.target.closest(\"a\")\n if (el && !el.target && urls.has(el.href)) {\n ev.preventDefault()\n const url = el.href\n // This is a temporary hack to detect if a version inside the\n // version selector or on another part of the site was clicked.\n // If we're inside the version selector, we definitely want to\n // find the same page, as we might have different deployments\n // due to aliases. However, if we're outside the version\n // selector, we must abort here, because we might otherwise\n // interfere with instant loading. We need to refactor this\n // at some point together with instant loading.\n //\n // See https://github.com/squidfunk/mkdocs-material/issues/4012\n if (!ev.target.closest(\".md-version\")) {\n const version = urls.get(url)!\n if (version === current)\n return EMPTY\n }\n return of(url)\n }\n }\n return EMPTY\n }),\n switchMap(url => {\n const { version } = urls.get(url)!\n return fetchSitemap(new URL(url))\n .pipe(\n map(sitemap => {\n const location = getLocation()\n const path = location.href.replace(config.base, \"\")\n return sitemap.includes(path)\n ? new URL(`../${version}/${path}`, config.base)\n : new URL(url)\n })\n )\n })\n )\n )\n )\n .subscribe(url => setLocation(url))\n\n /* Render version selector and warning */\n combineLatest([versions$, current$])\n .subscribe(([versions, current]) => {\n const topic = getElement(\".md-header__topic\")\n topic.appendChild(renderVersionSelector(versions, current))\n })\n\n /* Integrate outdated version banner with instant loading */\n document$.pipe(switchMap(() => current$))\n .subscribe(current => {\n\n /* Check if version state was already determined */\n let outdated = __md_get(\"__outdated\", sessionStorage)\n if (outdated === null) {\n const latest = config.version?.default || \"latest\"\n outdated = !current.aliases.includes(latest)\n\n /* Persist version state in session storage */\n __md_set(\"__outdated\", outdated, sessionStorage)\n }\n\n /* Unhide outdated version banner */\n if (outdated)\n for (const warning of getComponentElements(\"outdated\"))\n warning.hidden = false\n })\n}\n", "/*\n * Copyright (c) 2016-2022 Martin Donath \n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\nimport {\n Observable,\n Subject,\n combineLatest,\n delay,\n distinctUntilChanged,\n distinctUntilKeyChanged,\n filter,\n finalize,\n fromEvent,\n map,\n merge,\n share,\n shareReplay,\n startWith,\n take,\n takeLast,\n takeUntil,\n tap\n} from \"rxjs\"\n\nimport { translation } from \"~/_\"\nimport {\n getLocation,\n setToggle,\n watchElementFocus,\n watchToggle\n} from \"~/browser\"\nimport {\n SearchMessageType,\n SearchQueryMessage,\n SearchWorker,\n defaultTransform,\n isSearchReadyMessage\n} from \"~/integrations\"\n\nimport { Component } from \"../../_\"\n\n/* ----------------------------------------------------------------------------\n * Types\n * ------------------------------------------------------------------------- */\n\n/**\n * Search query\n */\nexport interface SearchQuery {\n value: string /* Query value */\n focus: boolean /* Query focus */\n}\n\n/* ----------------------------------------------------------------------------\n * Functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Watch search query\n *\n * Note that the focus event which triggers re-reading the current query value\n * is delayed by `1ms` so the input's empty state is allowed to propagate.\n *\n * @param el - Search query element\n * @param worker - Search worker\n *\n * @returns Search query observable\n */\nexport function watchSearchQuery(\n el: HTMLInputElement, { rx$ }: SearchWorker\n): Observable {\n const fn = __search?.transform || defaultTransform\n\n /* Immediately show search dialog */\n const { searchParams } = getLocation()\n if (searchParams.has(\"q\"))\n setToggle(\"search\", true)\n\n /* Intercept query parameter (deep link) */\n const param$ = rx$\n .pipe(\n filter(isSearchReadyMessage),\n take(1),\n map(() => searchParams.get(\"q\") || \"\")\n )\n\n /* Remove query parameter when search is closed */\n watchToggle(\"search\")\n .pipe(\n filter(active => !active),\n take(1)\n )\n .subscribe(() => {\n const url = new URL(location.href)\n url.searchParams.delete(\"q\")\n history.replaceState({}, \"\", `${url}`)\n })\n\n /* Set query from parameter */\n param$.subscribe(value => { // TODO: not ideal - find a better way\n if (value) {\n el.value = value\n el.focus()\n }\n })\n\n /* Intercept focus and input events */\n const focus$ = watchElementFocus(el)\n const value$ = merge(\n fromEvent(el, \"keyup\"),\n fromEvent(el, \"focus\").pipe(delay(1)),\n param$\n )\n .pipe(\n map(() => fn(el.value)),\n startWith(\"\"),\n distinctUntilChanged(),\n )\n\n /* Combine into single observable */\n return combineLatest([value$, focus$])\n .pipe(\n map(([value, focus]) => ({ value, focus })),\n shareReplay(1)\n )\n}\n\n/**\n * Mount search query\n *\n * @param el - Search query element\n * @param worker - Search worker\n *\n * @returns Search query component observable\n */\nexport function mountSearchQuery(\n el: HTMLInputElement, { tx$, rx$ }: SearchWorker\n): Observable> {\n const push$ = new Subject()\n const done$ = push$.pipe(takeLast(1))\n\n /* Handle value changes */\n push$\n .pipe(\n distinctUntilKeyChanged(\"value\"),\n map(({ value }): SearchQueryMessage => ({\n type: SearchMessageType.QUERY,\n data: value\n }))\n )\n .subscribe(tx$.next.bind(tx$))\n\n /* Handle focus changes */\n push$\n .pipe(\n distinctUntilKeyChanged(\"focus\")\n )\n .subscribe(({ focus }) => {\n if (focus) {\n setToggle(\"search\", focus)\n el.placeholder = \"\"\n } else {\n el.placeholder = translation(\"search.placeholder\")\n }\n })\n\n /* Handle reset */\n fromEvent(el.form!, \"reset\")\n .pipe(\n takeUntil(done$)\n )\n .subscribe(() => el.focus())\n\n /* Create and return component */\n return watchSearchQuery(el, { tx$, rx$ })\n .pipe(\n tap(state => push$.next(state)),\n finalize(() => push$.complete()),\n map(state => ({ ref: el, ...state })),\n share()\n )\n}\n", "/*\n * Copyright (c) 2016-2022 Martin Donath \n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\nimport {\n Observable,\n Subject,\n bufferCount,\n filter,\n finalize,\n map,\n merge,\n of,\n skipUntil,\n switchMap,\n take,\n tap,\n withLatestFrom,\n zipWith\n} from \"rxjs\"\n\nimport { translation } from \"~/_\"\nimport {\n getElement,\n watchElementBoundary\n} from \"~/browser\"\nimport {\n SearchResult,\n SearchWorker,\n isSearchReadyMessage,\n isSearchResultMessage\n} from \"~/integrations\"\nimport { renderSearchResultItem } from \"~/templates\"\nimport { round } from \"~/utilities\"\n\nimport { Component } from \"../../_\"\nimport { SearchQuery } from \"../query\"\n\n/* ----------------------------------------------------------------------------\n * Helper types\n * ------------------------------------------------------------------------- */\n\n/**\n * Mount options\n */\ninterface MountOptions {\n query$: Observable /* Search query observable */\n}\n\n/* ----------------------------------------------------------------------------\n * Functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Mount search result list\n *\n * This function performs a lazy rendering of the search results, depending on\n * the vertical offset of the search result container.\n *\n * @param el - Search result list element\n * @param worker - Search worker\n * @param options - Options\n *\n * @returns Search result list component observable\n */\nexport function mountSearchResult(\n el: HTMLElement, { rx$ }: SearchWorker, { query$ }: MountOptions\n): Observable> {\n const push$ = new Subject()\n const boundary$ = watchElementBoundary(el.parentElement!)\n .pipe(\n filter(Boolean)\n )\n\n /* Retrieve nested components */\n const meta = getElement(\":scope > :first-child\", el)\n const list = getElement(\":scope > :last-child\", el)\n\n /* Wait until search is ready */\n const ready$ = rx$\n .pipe(\n filter(isSearchReadyMessage),\n take(1)\n )\n\n /* Update search result metadata */\n push$\n .pipe(\n withLatestFrom(query$),\n skipUntil(ready$)\n )\n .subscribe(([{ items }, { value }]) => {\n if (value) {\n switch (items.length) {\n\n /* No results */\n case 0:\n meta.textContent = translation(\"search.result.none\")\n break\n\n /* One result */\n case 1:\n meta.textContent = translation(\"search.result.one\")\n break\n\n /* Multiple result */\n default:\n meta.textContent = translation(\n \"search.result.other\",\n round(items.length)\n )\n }\n } else {\n meta.textContent = translation(\"search.result.placeholder\")\n }\n })\n\n /* Update search result list */\n push$\n .pipe(\n tap(() => list.innerHTML = \"\"),\n switchMap(({ items }) => merge(\n of(...items.slice(0, 10)),\n of(...items.slice(10))\n .pipe(\n bufferCount(4),\n zipWith(boundary$),\n switchMap(([chunk]) => chunk)\n )\n ))\n )\n .subscribe(result => list.appendChild(\n renderSearchResultItem(result)\n ))\n\n /* Filter search result message */\n const result$ = rx$\n .pipe(\n filter(isSearchResultMessage),\n map(({ data }) => data)\n )\n\n /* Create and return component */\n return result$\n .pipe(\n tap(state => push$.next(state)),\n finalize(() => push$.complete()),\n map(state => ({ ref: el, ...state }))\n )\n}\n", "/*\n * Copyright (c) 2016-2022 Martin Donath \n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\nimport {\n Observable,\n Subject,\n finalize,\n fromEvent,\n map,\n tap\n} from \"rxjs\"\n\nimport { getLocation } from \"~/browser\"\n\nimport { Component } from \"../../_\"\nimport { SearchQuery } from \"../query\"\n\n/* ----------------------------------------------------------------------------\n * Types\n * ------------------------------------------------------------------------- */\n\n/**\n * Search sharing\n */\nexport interface SearchShare {\n url: URL /* Deep link for sharing */\n}\n\n/* ----------------------------------------------------------------------------\n * Helper types\n * ------------------------------------------------------------------------- */\n\n/**\n * Watch options\n */\ninterface WatchOptions {\n query$: Observable /* Search query observable */\n}\n\n/**\n * Mount options\n */\ninterface MountOptions {\n query$: Observable /* Search query observable */\n}\n\n/* ----------------------------------------------------------------------------\n * Functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Mount search sharing\n *\n * @param _el - Search sharing element\n * @param options - Options\n *\n * @returns Search sharing observable\n */\nexport function watchSearchShare(\n _el: HTMLElement, { query$ }: WatchOptions\n): Observable {\n return query$\n .pipe(\n map(({ value }) => {\n const url = getLocation()\n url.hash = \"\"\n url.searchParams.delete(\"h\")\n url.searchParams.set(\"q\", value)\n return { url }\n })\n )\n}\n\n/**\n * Mount search sharing\n *\n * @param el - Search sharing element\n * @param options - Options\n *\n * @returns Search sharing component observable\n */\nexport function mountSearchShare(\n el: HTMLAnchorElement, options: MountOptions\n): Observable> {\n const push$ = new Subject()\n push$.subscribe(({ url }) => {\n el.setAttribute(\"data-clipboard-text\", el.href)\n el.href = `${url}`\n })\n\n /* Prevent following of link */\n fromEvent(el, \"click\")\n .subscribe(ev => ev.preventDefault())\n\n /* Create and return component */\n return watchSearchShare(el, options)\n .pipe(\n tap(state => push$.next(state)),\n finalize(() => push$.complete()),\n map(state => ({ ref: el, ...state }))\n )\n}\n", "/*\n * Copyright (c) 2016-2022 Martin Donath \n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\nimport {\n Observable,\n Subject,\n asyncScheduler,\n combineLatestWith,\n distinctUntilChanged,\n filter,\n finalize,\n fromEvent,\n map,\n merge,\n observeOn,\n tap\n} from \"rxjs\"\n\nimport { Keyboard } from \"~/browser\"\nimport {\n SearchResult,\n SearchWorker,\n isSearchResultMessage\n} from \"~/integrations\"\n\nimport { Component, getComponentElement } from \"../../_\"\n\n/* ----------------------------------------------------------------------------\n * Types\n * ------------------------------------------------------------------------- */\n\n/**\n * Search suggestions\n */\nexport interface SearchSuggest {}\n\n/* ----------------------------------------------------------------------------\n * Helper types\n * ------------------------------------------------------------------------- */\n\n/**\n * Mount options\n */\ninterface MountOptions {\n keyboard$: Observable /* Keyboard observable */\n}\n\n/* ----------------------------------------------------------------------------\n * Functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Mount search suggestions\n *\n * This function will perform a lazy rendering of the search results, depending\n * on the vertical offset of the search result container.\n *\n * @param el - Search result list element\n * @param worker - Search worker\n * @param options - Options\n *\n * @returns Search result list component observable\n */\nexport function mountSearchSuggest(\n el: HTMLElement, { rx$ }: SearchWorker, { keyboard$ }: MountOptions\n): Observable> {\n const push$ = new Subject()\n\n /* Retrieve query component and track all changes */\n const query = getComponentElement(\"search-query\")\n const query$ = merge(\n fromEvent(query, \"keydown\"),\n fromEvent(query, \"focus\")\n )\n .pipe(\n observeOn(asyncScheduler),\n map(() => query.value),\n distinctUntilChanged(),\n )\n\n /* Update search suggestions */\n push$\n .pipe(\n combineLatestWith(query$),\n map(([{ suggestions }, value]) => {\n const words = value.split(/([\\s-]+)/)\n if (suggestions?.length && words[words.length - 1]) {\n const last = suggestions[suggestions.length - 1]\n if (last.startsWith(words[words.length - 1]))\n words[words.length - 1] = last\n } else {\n words.length = 0\n }\n return words\n })\n )\n .subscribe(words => el.innerHTML = words\n .join(\"\")\n .replace(/\\s/g, \" \")\n )\n\n /* Set up search keyboard handlers */\n keyboard$\n .pipe(\n filter(({ mode }) => mode === \"search\")\n )\n .subscribe(key => {\n switch (key.type) {\n\n /* Right arrow: accept current suggestion */\n case \"ArrowRight\":\n if (\n el.innerText.length &&\n query.selectionStart === query.value.length\n )\n query.value = el.innerText\n break\n }\n })\n\n /* Filter search result message */\n const result$ = rx$\n .pipe(\n filter(isSearchResultMessage),\n map(({ data }) => data)\n )\n\n /* Create and return component */\n return result$\n .pipe(\n tap(state => push$.next(state)),\n finalize(() => push$.complete()),\n map(() => ({ ref: el }))\n )\n}\n", "/*\n * Copyright (c) 2016-2022 Martin Donath \n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\nimport {\n NEVER,\n Observable,\n ObservableInput,\n filter,\n merge,\n mergeWith,\n sample,\n take\n} from \"rxjs\"\n\nimport { configuration } from \"~/_\"\nimport {\n Keyboard,\n getActiveElement,\n getElements,\n setToggle\n} from \"~/browser\"\nimport {\n SearchIndex,\n SearchResult,\n isSearchQueryMessage,\n isSearchReadyMessage,\n setupSearchWorker\n} from \"~/integrations\"\n\nimport {\n Component,\n getComponentElement,\n getComponentElements\n} from \"../../_\"\nimport {\n SearchQuery,\n mountSearchQuery\n} from \"../query\"\nimport { mountSearchResult } from \"../result\"\nimport {\n SearchShare,\n mountSearchShare\n} from \"../share\"\nimport {\n SearchSuggest,\n mountSearchSuggest\n} from \"../suggest\"\n\n/* ----------------------------------------------------------------------------\n * Types\n * ------------------------------------------------------------------------- */\n\n/**\n * Search\n */\nexport type Search =\n | SearchQuery\n | SearchResult\n | SearchShare\n | SearchSuggest\n\n/* ----------------------------------------------------------------------------\n * Helper types\n * ------------------------------------------------------------------------- */\n\n/**\n * Mount options\n */\ninterface MountOptions {\n index$: ObservableInput /* Search index observable */\n keyboard$: Observable /* Keyboard observable */\n}\n\n/* ----------------------------------------------------------------------------\n * Functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Mount search\n *\n * This function sets up the search functionality, including the underlying\n * web worker and all keyboard bindings.\n *\n * @param el - Search element\n * @param options - Options\n *\n * @returns Search component observable\n */\nexport function mountSearch(\n el: HTMLElement, { index$, keyboard$ }: MountOptions\n): Observable> {\n const config = configuration()\n try {\n const url = __search?.worker || config.search\n const worker = setupSearchWorker(url, index$)\n\n /* Retrieve query and result components */\n const query = getComponentElement(\"search-query\", el)\n const result = getComponentElement(\"search-result\", el)\n\n /* Re-emit query when search is ready */\n const { tx$, rx$ } = worker\n tx$\n .pipe(\n filter(isSearchQueryMessage),\n sample(rx$.pipe(filter(isSearchReadyMessage))),\n take(1)\n )\n .subscribe(tx$.next.bind(tx$))\n\n /* Set up search keyboard handlers */\n keyboard$\n .pipe(\n filter(({ mode }) => mode === \"search\")\n )\n .subscribe(key => {\n const active = getActiveElement()\n switch (key.type) {\n\n /* Enter: go to first (best) result */\n case \"Enter\":\n if (active === query) {\n const anchors = new Map()\n for (const anchor of getElements(\n \":first-child [href]\", result\n )) {\n const article = anchor.firstElementChild!\n anchors.set(anchor, parseFloat(\n article.getAttribute(\"data-md-score\")!\n ))\n }\n\n /* Go to result with highest score, if any */\n if (anchors.size) {\n const [[best]] = [...anchors].sort(([, a], [, b]) => b - a)\n best.click()\n }\n\n /* Otherwise omit form submission */\n key.claim()\n }\n break\n\n /* Escape or Tab: close search */\n case \"Escape\":\n case \"Tab\":\n setToggle(\"search\", false)\n query.blur()\n break\n\n /* Vertical arrows: select previous or next search result */\n case \"ArrowUp\":\n case \"ArrowDown\":\n if (typeof active === \"undefined\") {\n query.focus()\n } else {\n const els = [query, ...getElements(\n \":not(details) > [href], summary, details[open] [href]\",\n result\n )]\n const i = Math.max(0, (\n Math.max(0, els.indexOf(active)) + els.length + (\n key.type === \"ArrowUp\" ? -1 : +1\n )\n ) % els.length)\n els[i].focus()\n }\n\n /* Prevent scrolling of page */\n key.claim()\n break\n\n /* All other keys: hand to search query */\n default:\n if (query !== getActiveElement())\n query.focus()\n }\n })\n\n /* Set up global keyboard handlers */\n keyboard$\n .pipe(\n filter(({ mode }) => mode === \"global\"),\n )\n .subscribe(key => {\n switch (key.type) {\n\n /* Open search and select query */\n case \"f\":\n case \"s\":\n case \"/\":\n query.focus()\n query.select()\n\n /* Prevent scrolling of page */\n key.claim()\n break\n }\n })\n\n /* Create and return component */\n const query$ = mountSearchQuery(query, worker)\n const result$ = mountSearchResult(result, worker, { query$ })\n return merge(query$, result$)\n .pipe(\n mergeWith(\n\n /* Search sharing */\n ...getComponentElements(\"search-share\", el)\n .map(child => mountSearchShare(child, { query$ })),\n\n /* Search suggestions */\n ...getComponentElements(\"search-suggest\", el)\n .map(child => mountSearchSuggest(child, worker, { keyboard$ }))\n )\n )\n\n /* Gracefully handle broken search */\n } catch (err) {\n el.hidden = true\n return NEVER\n }\n}\n", "/*\n * Copyright (c) 2016-2022 Martin Donath \n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\nimport {\n Observable,\n ObservableInput,\n combineLatest,\n filter,\n map,\n startWith\n} from \"rxjs\"\n\nimport { getLocation } from \"~/browser\"\nimport {\n SearchIndex,\n setupSearchHighlighter\n} from \"~/integrations\"\nimport { h } from \"~/utilities\"\n\nimport { Component } from \"../../_\"\n\n/* ----------------------------------------------------------------------------\n * Types\n * ------------------------------------------------------------------------- */\n\n/**\n * Search highlighting\n */\nexport interface SearchHighlight {\n nodes: Map /* Map of replacements */\n}\n\n/* ----------------------------------------------------------------------------\n * Helper types\n * ------------------------------------------------------------------------- */\n\n/**\n * Mount options\n */\ninterface MountOptions {\n index$: ObservableInput /* Search index observable */\n location$: Observable /* Location observable */\n}\n\n/* ----------------------------------------------------------------------------\n * Functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Mount search highlighting\n *\n * @param el - Content element\n * @param options - Options\n *\n * @returns Search highlighting component observable\n */\nexport function mountSearchHiglight(\n el: HTMLElement, { index$, location$ }: MountOptions\n): Observable> {\n return combineLatest([\n index$,\n location$\n .pipe(\n startWith(getLocation()),\n filter(url => !!url.searchParams.get(\"h\"))\n )\n ])\n .pipe(\n map(([index, url]) => setupSearchHighlighter(index.config, true)(\n url.searchParams.get(\"h\")!\n )),\n map(fn => {\n const nodes = new Map()\n\n /* Traverse text nodes and collect matches */\n const it = document.createNodeIterator(el, NodeFilter.SHOW_TEXT)\n for (let node = it.nextNode(); node; node = it.nextNode()) {\n if (node.parentElement?.offsetHeight) {\n const original = node.textContent!\n const replaced = fn(original)\n if (replaced.length > original.length)\n nodes.set(node as ChildNode, replaced)\n }\n }\n\n /* Replace original nodes with matches */\n for (const [node, text] of nodes) {\n const { childNodes } = h(\"span\", null, text)\n node.replaceWith(...Array.from(childNodes))\n }\n\n /* Return component */\n return { ref: el, nodes }\n })\n )\n}\n", "/*\n * Copyright (c) 2016-2022 Martin Donath \n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\nimport {\n Observable,\n Subject,\n animationFrameScheduler,\n auditTime,\n combineLatest,\n defer,\n distinctUntilChanged,\n finalize,\n map,\n tap,\n withLatestFrom\n} from \"rxjs\"\n\nimport {\n Viewport,\n getElement,\n getElementOffset\n} from \"~/browser\"\n\nimport { Component } from \"../_\"\nimport { Header } from \"../header\"\nimport { Main } from \"../main\"\n\n/* ----------------------------------------------------------------------------\n * Types\n * ------------------------------------------------------------------------- */\n\n/**\n * Sidebar\n */\nexport interface Sidebar {\n height: number /* Sidebar height */\n locked: boolean /* Sidebar is locked */\n}\n\n/* ----------------------------------------------------------------------------\n * Helper types\n * ------------------------------------------------------------------------- */\n\n/**\n * Watch options\n */\ninterface WatchOptions {\n viewport$: Observable /* Viewport observable */\n main$: Observable
    /* Main area observable */\n}\n\n/**\n * Mount options\n */\ninterface MountOptions {\n viewport$: Observable /* Viewport observable */\n header$: Observable
    /* Header observable */\n main$: Observable
    /* Main area observable */\n}\n\n/* ----------------------------------------------------------------------------\n * Functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Watch sidebar\n *\n * This function returns an observable that computes the visual parameters of\n * the sidebar which depends on the vertical viewport offset, as well as the\n * height of the main area. When the page is scrolled beyond the header, the\n * sidebar is locked and fills the remaining space.\n *\n * @param el - Sidebar element\n * @param options - Options\n *\n * @returns Sidebar observable\n */\nexport function watchSidebar(\n el: HTMLElement, { viewport$, main$ }: WatchOptions\n): Observable {\n const parent = el.parentElement!\n const adjust =\n parent.offsetTop -\n parent.parentElement!.offsetTop\n\n /* Compute the sidebar's available height and if it should be locked */\n return combineLatest([main$, viewport$])\n .pipe(\n map(([{ offset, height }, { offset: { y } }]) => {\n height = height\n + Math.min(adjust, Math.max(0, y - offset))\n - adjust\n return {\n height,\n locked: y >= offset + adjust\n }\n }),\n distinctUntilChanged((a, b) => (\n a.height === b.height &&\n a.locked === b.locked\n ))\n )\n}\n\n/**\n * Mount sidebar\n *\n * This function doesn't set the height of the actual sidebar, but of its first\n * child \u2013 the `.md-sidebar__scrollwrap` element in order to mitigiate jittery\n * sidebars when the footer is scrolled into view. At some point we switched\n * from `absolute` / `fixed` positioning to `sticky` positioning, significantly\n * reducing jitter in some browsers (respectively Firefox and Safari) when\n * scrolling from the top. However, top-aligned sticky positioning means that\n * the sidebar snaps to the bottom when the end of the container is reached.\n * This is what leads to the mentioned jitter, as the sidebar's height may be\n * updated too slowly.\n *\n * This behaviour can be mitigiated by setting the height of the sidebar to `0`\n * while preserving the padding, and the height on its first element.\n *\n * @param el - Sidebar element\n * @param options - Options\n *\n * @returns Sidebar component observable\n */\nexport function mountSidebar(\n el: HTMLElement, { header$, ...options }: MountOptions\n): Observable> {\n const inner = getElement(\".md-sidebar__scrollwrap\", el)\n const { y } = getElementOffset(inner)\n return defer(() => {\n const push$ = new Subject()\n push$\n .pipe(\n auditTime(0, animationFrameScheduler),\n withLatestFrom(header$)\n )\n .subscribe({\n\n /* Handle emission */\n next([{ height }, { height: offset }]) {\n inner.style.height = `${height - 2 * y}px`\n el.style.top = `${offset}px`\n },\n\n /* Handle complete */\n complete() {\n inner.style.height = \"\"\n el.style.top = \"\"\n }\n })\n\n /* Create and return component */\n return watchSidebar(el, options)\n .pipe(\n tap(state => push$.next(state)),\n finalize(() => push$.complete()),\n map(state => ({ ref: el, ...state }))\n )\n })\n}\n", "/*\n * Copyright (c) 2016-2022 Martin Donath \n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\nimport { Repo, User } from \"github-types\"\nimport {\n EMPTY,\n Observable,\n catchError,\n defaultIfEmpty,\n map,\n zip\n} from \"rxjs\"\n\nimport { requestJSON } from \"~/browser\"\n\nimport { SourceFacts } from \"../_\"\n\n/* ----------------------------------------------------------------------------\n * Helper types\n * ------------------------------------------------------------------------- */\n\n/**\n * GitHub release (partial)\n */\ninterface Release {\n tag_name: string /* Tag name */\n}\n\n/* ----------------------------------------------------------------------------\n * Functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Fetch GitHub repository facts\n *\n * @param user - GitHub user or organization\n * @param repo - GitHub repository\n *\n * @returns Repository facts observable\n */\nexport function fetchSourceFactsFromGitHub(\n user: string, repo?: string\n): Observable {\n if (typeof repo !== \"undefined\") {\n const url = `https://api.github.com/repos/${user}/${repo}`\n return zip(\n\n /* Fetch version */\n requestJSON(`${url}/releases/latest`)\n .pipe(\n catchError(() => EMPTY), // @todo refactor instant loading\n map(release => ({\n version: release.tag_name\n })),\n defaultIfEmpty({})\n ),\n\n /* Fetch stars and forks */\n requestJSON(url)\n .pipe(\n catchError(() => EMPTY), // @todo refactor instant loading\n map(info => ({\n stars: info.stargazers_count,\n forks: info.forks_count\n })),\n defaultIfEmpty({})\n )\n )\n .pipe(\n map(([release, info]) => ({ ...release, ...info }))\n )\n\n /* User or organization */\n } else {\n const url = `https://api.github.com/users/${user}`\n return requestJSON(url)\n .pipe(\n map(info => ({\n repositories: info.public_repos\n })),\n defaultIfEmpty({})\n )\n }\n}\n", "/*\n * Copyright (c) 2016-2022 Martin Donath \n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\nimport { ProjectSchema } from \"gitlab\"\nimport {\n EMPTY,\n Observable,\n catchError,\n defaultIfEmpty,\n map\n} from \"rxjs\"\n\nimport { requestJSON } from \"~/browser\"\n\nimport { SourceFacts } from \"../_\"\n\n/* ----------------------------------------------------------------------------\n * Functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Fetch GitLab repository facts\n *\n * @param base - GitLab base\n * @param project - GitLab project\n *\n * @returns Repository facts observable\n */\nexport function fetchSourceFactsFromGitLab(\n base: string, project: string\n): Observable {\n const url = `https://${base}/api/v4/projects/${encodeURIComponent(project)}`\n return requestJSON(url)\n .pipe(\n catchError(() => EMPTY), // @todo refactor instant loading\n map(({ star_count, forks_count }) => ({\n stars: star_count,\n forks: forks_count\n })),\n defaultIfEmpty({})\n )\n}\n", "/*\n * Copyright (c) 2016-2022 Martin Donath \n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\nimport { EMPTY, Observable } from \"rxjs\"\n\nimport { fetchSourceFactsFromGitHub } from \"../github\"\nimport { fetchSourceFactsFromGitLab } from \"../gitlab\"\n\n/* ----------------------------------------------------------------------------\n * Types\n * ------------------------------------------------------------------------- */\n\n/**\n * Repository facts for repositories\n */\nexport interface RepositoryFacts {\n stars?: number /* Number of stars */\n forks?: number /* Number of forks */\n version?: string /* Latest version */\n}\n\n/**\n * Repository facts for organizations\n */\nexport interface OrganizationFacts {\n repositories?: number /* Number of repositories */\n}\n\n/* ------------------------------------------------------------------------- */\n\n/**\n * Repository facts\n */\nexport type SourceFacts =\n | RepositoryFacts\n | OrganizationFacts\n\n/* ----------------------------------------------------------------------------\n * Functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Fetch repository facts\n *\n * @param url - Repository URL\n *\n * @returns Repository facts observable\n */\nexport function fetchSourceFacts(\n url: string\n): Observable {\n const [type] = url.match(/(git(?:hub|lab))/i) || []\n switch (type.toLowerCase()) {\n\n /* GitHub repository */\n case \"github\":\n const [, user, repo] = url.match(/^.+github\\.com\\/([^/]+)\\/?([^/]+)?/i)!\n return fetchSourceFactsFromGitHub(user, repo)\n\n /* GitLab repository */\n case \"gitlab\":\n const [, base, slug] = url.match(/^.+?([^/]*gitlab[^/]+)\\/(.+?)\\/?$/i)!\n return fetchSourceFactsFromGitLab(base, slug)\n\n /* Everything else */\n default:\n return EMPTY\n }\n}\n", "/*\n * Copyright (c) 2016-2022 Martin Donath \n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\nimport {\n EMPTY,\n Observable,\n Subject,\n catchError,\n defer,\n filter,\n finalize,\n map,\n of,\n shareReplay,\n tap\n} from \"rxjs\"\n\nimport { getElement } from \"~/browser\"\nimport { renderSourceFacts } from \"~/templates\"\n\nimport { Component } from \"../../_\"\nimport {\n SourceFacts,\n fetchSourceFacts\n} from \"../facts\"\n\n/* ----------------------------------------------------------------------------\n * Types\n * ------------------------------------------------------------------------- */\n\n/**\n * Repository information\n */\nexport interface Source {\n facts: SourceFacts /* Repository facts */\n}\n\n/* ----------------------------------------------------------------------------\n * Data\n * ------------------------------------------------------------------------- */\n\n/**\n * Repository information observable\n */\nlet fetch$: Observable\n\n/* ----------------------------------------------------------------------------\n * Functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Watch repository information\n *\n * This function tries to read the repository facts from session storage, and\n * if unsuccessful, fetches them from the underlying provider.\n *\n * @param el - Repository information element\n *\n * @returns Repository information observable\n */\nexport function watchSource(\n el: HTMLAnchorElement\n): Observable {\n return fetch$ ||= defer(() => {\n const cached = __md_get(\"__source\", sessionStorage)\n if (cached)\n return of(cached)\n else\n return fetchSourceFacts(el.href)\n .pipe(\n tap(facts => __md_set(\"__source\", facts, sessionStorage))\n )\n })\n .pipe(\n catchError(() => EMPTY),\n filter(facts => Object.keys(facts).length > 0),\n map(facts => ({ facts })),\n shareReplay(1)\n )\n}\n\n/**\n * Mount repository information\n *\n * @param el - Repository information element\n *\n * @returns Repository information component observable\n */\nexport function mountSource(\n el: HTMLAnchorElement\n): Observable> {\n const inner = getElement(\":scope > :last-child\", el)\n return defer(() => {\n const push$ = new Subject()\n push$.subscribe(({ facts }) => {\n inner.appendChild(renderSourceFacts(facts))\n inner.classList.add(\"md-source__repository--active\")\n })\n\n /* Create and return component */\n return watchSource(el)\n .pipe(\n tap(state => push$.next(state)),\n finalize(() => push$.complete()),\n map(state => ({ ref: el, ...state }))\n )\n })\n}\n", "/*\n * Copyright (c) 2016-2022 Martin Donath \n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\nimport {\n Observable,\n Subject,\n defer,\n distinctUntilKeyChanged,\n finalize,\n map,\n of,\n switchMap,\n tap\n} from \"rxjs\"\n\nimport { feature } from \"~/_\"\nimport {\n Viewport,\n watchElementSize,\n watchViewportAt\n} from \"~/browser\"\n\nimport { Component } from \"../_\"\nimport { Header } from \"../header\"\n\n/* ----------------------------------------------------------------------------\n * Types\n * ------------------------------------------------------------------------- */\n\n/**\n * Navigation tabs\n */\nexport interface Tabs {\n hidden: boolean /* Navigation tabs are hidden */\n}\n\n/* ----------------------------------------------------------------------------\n * Helper types\n * ------------------------------------------------------------------------- */\n\n/**\n * Watch options\n */\ninterface WatchOptions {\n viewport$: Observable /* Viewport observable */\n header$: Observable
    /* Header observable */\n}\n\n/**\n * Mount options\n */\ninterface MountOptions {\n viewport$: Observable /* Viewport observable */\n header$: Observable
    /* Header observable */\n}\n\n/* ----------------------------------------------------------------------------\n * Functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Watch navigation tabs\n *\n * @param el - Navigation tabs element\n * @param options - Options\n *\n * @returns Navigation tabs observable\n */\nexport function watchTabs(\n el: HTMLElement, { viewport$, header$ }: WatchOptions\n): Observable {\n return watchElementSize(document.body)\n .pipe(\n switchMap(() => watchViewportAt(el, { header$, viewport$ })),\n map(({ offset: { y } }) => {\n return {\n hidden: y >= 10\n }\n }),\n distinctUntilKeyChanged(\"hidden\")\n )\n}\n\n/**\n * Mount navigation tabs\n *\n * This function hides the navigation tabs when scrolling past the threshold\n * and makes them reappear in a nice CSS animation when scrolling back up.\n *\n * @param el - Navigation tabs element\n * @param options - Options\n *\n * @returns Navigation tabs component observable\n */\nexport function mountTabs(\n el: HTMLElement, options: MountOptions\n): Observable> {\n return defer(() => {\n const push$ = new Subject()\n push$.subscribe({\n\n /* Handle emission */\n next({ hidden }) {\n el.hidden = hidden\n },\n\n /* Handle complete */\n complete() {\n el.hidden = false\n }\n })\n\n /* Create and return component */\n return (\n feature(\"navigation.tabs.sticky\")\n ? of({ hidden: false })\n : watchTabs(el, options)\n )\n .pipe(\n tap(state => push$.next(state)),\n finalize(() => push$.complete()),\n map(state => ({ ref: el, ...state }))\n )\n })\n}\n", "/*\n * Copyright (c) 2016-2022 Martin Donath \n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\nimport {\n Observable,\n Subject,\n bufferCount,\n combineLatestWith,\n debounceTime,\n defer,\n distinctUntilChanged,\n distinctUntilKeyChanged,\n finalize,\n map,\n of,\n repeat,\n scan,\n share,\n skip,\n startWith,\n switchMap,\n takeLast,\n takeUntil,\n tap,\n withLatestFrom\n} from \"rxjs\"\n\nimport { feature } from \"~/_\"\nimport {\n Viewport,\n getElement,\n getElements,\n getLocation,\n getOptionalElement,\n watchElementSize\n} from \"~/browser\"\n\nimport {\n Component,\n getComponentElement\n} from \"../_\"\nimport { Header } from \"../header\"\n\n/* ----------------------------------------------------------------------------\n * Types\n * ------------------------------------------------------------------------- */\n\n/**\n * Table of contents\n */\nexport interface TableOfContents {\n prev: HTMLAnchorElement[][] /* Anchors (previous) */\n next: HTMLAnchorElement[][] /* Anchors (next) */\n}\n\n/* ----------------------------------------------------------------------------\n * Helper types\n * ------------------------------------------------------------------------- */\n\n/**\n * Watch options\n */\ninterface WatchOptions {\n viewport$: Observable /* Viewport observable */\n header$: Observable
    /* Header observable */\n}\n\n/**\n * Mount options\n */\ninterface MountOptions {\n viewport$: Observable /* Viewport observable */\n header$: Observable
    /* Header observable */\n target$: Observable /* Location target observable */\n}\n\n/* ----------------------------------------------------------------------------\n * Functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Watch table of contents\n *\n * This is effectively a scroll spy implementation which will account for the\n * fixed header and automatically re-calculate anchor offsets when the viewport\n * is resized. The returned observable will only emit if the table of contents\n * needs to be repainted.\n *\n * This implementation tracks an anchor element's entire path starting from its\n * level up to the top-most anchor element, e.g. `[h3, h2, h1]`. Although the\n * Material theme currently doesn't make use of this information, it enables\n * the styling of the entire hierarchy through customization.\n *\n * Note that the current anchor is the last item of the `prev` anchor list.\n *\n * @param el - Table of contents element\n * @param options - Options\n *\n * @returns Table of contents observable\n */\nexport function watchTableOfContents(\n el: HTMLElement, { viewport$, header$ }: WatchOptions\n): Observable {\n const table = new Map()\n\n /* Compute anchor-to-target mapping */\n const anchors = getElements(\"[href^=\\\\#]\", el)\n for (const anchor of anchors) {\n const id = decodeURIComponent(anchor.hash.substring(1))\n const target = getOptionalElement(`[id=\"${id}\"]`)\n if (typeof target !== \"undefined\")\n table.set(anchor, target)\n }\n\n /* Compute necessary adjustment for header */\n const adjust$ = header$\n .pipe(\n distinctUntilKeyChanged(\"height\"),\n map(({ height }) => {\n const main = getComponentElement(\"main\")\n const grid = getElement(\":scope > :first-child\", main)\n return height + 0.8 * (\n grid.offsetTop -\n main.offsetTop\n )\n }),\n share()\n )\n\n /* Compute partition of previous and next anchors */\n const partition$ = watchElementSize(document.body)\n .pipe(\n distinctUntilKeyChanged(\"height\"),\n\n /* Build index to map anchor paths to vertical offsets */\n switchMap(body => defer(() => {\n let path: HTMLAnchorElement[] = []\n return of([...table].reduce((index, [anchor, target]) => {\n while (path.length) {\n const last = table.get(path[path.length - 1])!\n if (last.tagName >= target.tagName) {\n path.pop()\n } else {\n break\n }\n }\n\n /* If the current anchor is hidden, continue with its parent */\n let offset = target.offsetTop\n while (!offset && target.parentElement) {\n target = target.parentElement\n offset = target.offsetTop\n }\n\n /* Map reversed anchor path to vertical offset */\n return index.set(\n [...path = [...path, anchor]].reverse(),\n offset\n )\n }, new Map()))\n })\n .pipe(\n\n /* Sort index by vertical offset (see https://bit.ly/30z6QSO) */\n map(index => new Map([...index].sort(([, a], [, b]) => a - b))),\n combineLatestWith(adjust$),\n\n /* Re-compute partition when viewport offset changes */\n switchMap(([index, adjust]) => viewport$\n .pipe(\n scan(([prev, next], { offset: { y }, size }) => {\n const last = y + size.height >= Math.floor(body.height)\n\n /* Look forward */\n while (next.length) {\n const [, offset] = next[0]\n if (offset - adjust < y || last) {\n prev = [...prev, next.shift()!]\n } else {\n break\n }\n }\n\n /* Look backward */\n while (prev.length) {\n const [, offset] = prev[prev.length - 1]\n if (offset - adjust >= y && !last) {\n next = [prev.pop()!, ...next]\n } else {\n break\n }\n }\n\n /* Return partition */\n return [prev, next]\n }, [[], [...index]]),\n distinctUntilChanged((a, b) => (\n a[0] === b[0] &&\n a[1] === b[1]\n ))\n )\n )\n )\n )\n )\n\n /* Compute and return anchor list migrations */\n return partition$\n .pipe(\n map(([prev, next]) => ({\n prev: prev.map(([path]) => path),\n next: next.map(([path]) => path)\n })),\n\n /* Extract anchor list migrations */\n startWith({ prev: [], next: [] }),\n bufferCount(2, 1),\n map(([a, b]) => {\n\n /* Moving down */\n if (a.prev.length < b.prev.length) {\n return {\n prev: b.prev.slice(Math.max(0, a.prev.length - 1), b.prev.length),\n next: []\n }\n\n /* Moving up */\n } else {\n return {\n prev: b.prev.slice(-1),\n next: b.next.slice(0, b.next.length - a.next.length)\n }\n }\n })\n )\n}\n\n/* ------------------------------------------------------------------------- */\n\n/**\n * Mount table of contents\n *\n * @param el - Table of contents element\n * @param options - Options\n *\n * @returns Table of contents component observable\n */\nexport function mountTableOfContents(\n el: HTMLElement, { viewport$, header$, target$ }: MountOptions\n): Observable> {\n return defer(() => {\n const push$ = new Subject()\n const done$ = push$.pipe(takeLast(1))\n push$.subscribe(({ prev, next }) => {\n\n /* Look forward */\n for (const [anchor] of next) {\n anchor.classList.remove(\"md-nav__link--passed\")\n anchor.classList.remove(\"md-nav__link--active\")\n }\n\n /* Look backward */\n for (const [index, [anchor]] of prev.entries()) {\n anchor.classList.add(\"md-nav__link--passed\")\n anchor.classList.toggle(\n \"md-nav__link--active\",\n index === prev.length - 1\n )\n }\n })\n\n /* Set up anchor tracking, if enabled */\n if (feature(\"navigation.tracking\"))\n viewport$\n .pipe(\n takeUntil(done$),\n distinctUntilKeyChanged(\"offset\"),\n debounceTime(250),\n skip(1),\n takeUntil(target$.pipe(skip(1))),\n repeat({ delay: 250 }),\n withLatestFrom(push$)\n )\n .subscribe(([, { prev }]) => {\n const url = getLocation()\n\n /* Set hash fragment to active anchor */\n const anchor = prev[prev.length - 1]\n if (anchor && anchor.length) {\n const [active] = anchor\n const { hash } = new URL(active.href)\n if (url.hash !== hash) {\n url.hash = hash\n history.replaceState({}, \"\", `${url}`)\n }\n\n /* Reset anchor when at the top */\n } else {\n url.hash = \"\"\n history.replaceState({}, \"\", `${url}`)\n }\n })\n\n /* Create and return component */\n return watchTableOfContents(el, { viewport$, header$ })\n .pipe(\n tap(state => push$.next(state)),\n finalize(() => push$.complete()),\n map(state => ({ ref: el, ...state }))\n )\n })\n}\n", "/*\n * Copyright (c) 2016-2022 Martin Donath \n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\nimport {\n Observable,\n Subject,\n bufferCount,\n combineLatest,\n distinctUntilChanged,\n distinctUntilKeyChanged,\n endWith,\n finalize,\n map,\n repeat,\n skip,\n takeLast,\n takeUntil,\n tap\n} from \"rxjs\"\n\nimport { Viewport } from \"~/browser\"\n\nimport { Component } from \"../_\"\nimport { Header } from \"../header\"\nimport { Main } from \"../main\"\n\n/* ----------------------------------------------------------------------------\n * Types\n * ------------------------------------------------------------------------- */\n\n/**\n * Back-to-top button\n */\nexport interface BackToTop {\n hidden: boolean /* Back-to-top button is hidden */\n}\n\n/* ----------------------------------------------------------------------------\n * Helper types\n * ------------------------------------------------------------------------- */\n\n/**\n * Watch options\n */\ninterface WatchOptions {\n viewport$: Observable /* Viewport observable */\n main$: Observable
    /* Main area observable */\n target$: Observable /* Location target observable */\n}\n\n/**\n * Mount options\n */\ninterface MountOptions {\n viewport$: Observable /* Viewport observable */\n header$: Observable
    /* Header observable */\n main$: Observable
    /* Main area observable */\n target$: Observable /* Location target observable */\n}\n\n/* ----------------------------------------------------------------------------\n * Functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Watch back-to-top\n *\n * @param _el - Back-to-top element\n * @param options - Options\n *\n * @returns Back-to-top observable\n */\nexport function watchBackToTop(\n _el: HTMLElement, { viewport$, main$, target$ }: WatchOptions\n): Observable {\n\n /* Compute direction */\n const direction$ = viewport$\n .pipe(\n map(({ offset: { y } }) => y),\n bufferCount(2, 1),\n map(([a, b]) => a > b && b > 0),\n distinctUntilChanged()\n )\n\n /* Compute whether main area is active */\n const active$ = main$\n .pipe(\n map(({ active }) => active)\n )\n\n /* Compute threshold for hiding */\n return combineLatest([active$, direction$])\n .pipe(\n map(([active, direction]) => !(active && direction)),\n distinctUntilChanged(),\n takeUntil(target$.pipe(skip(1))),\n endWith(true),\n repeat({ delay: 250 }),\n map(hidden => ({ hidden }))\n )\n}\n\n/* ------------------------------------------------------------------------- */\n\n/**\n * Mount back-to-top\n *\n * @param el - Back-to-top element\n * @param options - Options\n *\n * @returns Back-to-top component observable\n */\nexport function mountBackToTop(\n el: HTMLElement, { viewport$, header$, main$, target$ }: MountOptions\n): Observable> {\n const push$ = new Subject()\n const done$ = push$.pipe(takeLast(1))\n push$.subscribe({\n\n /* Handle emission */\n next({ hidden }) {\n el.hidden = hidden\n if (hidden) {\n el.setAttribute(\"tabindex\", \"-1\")\n el.blur()\n } else {\n el.removeAttribute(\"tabindex\")\n }\n },\n\n /* Handle complete */\n complete() {\n el.style.top = \"\"\n el.hidden = true\n el.removeAttribute(\"tabindex\")\n }\n })\n\n /* Watch header height */\n header$\n .pipe(\n takeUntil(done$),\n distinctUntilKeyChanged(\"height\")\n )\n .subscribe(({ height }) => {\n el.style.top = `${height + 16}px`\n })\n\n /* Create and return component */\n return watchBackToTop(el, { viewport$, main$, target$ })\n .pipe(\n tap(state => push$.next(state)),\n finalize(() => push$.complete()),\n map(state => ({ ref: el, ...state }))\n )\n}\n", "/*\n * Copyright (c) 2016-2022 Martin Donath \n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\nimport {\n Observable,\n fromEvent,\n map,\n mergeMap,\n switchMap,\n takeWhile,\n tap,\n withLatestFrom\n} from \"rxjs\"\n\nimport { getElements } from \"~/browser\"\n\n/* ----------------------------------------------------------------------------\n * Helper types\n * ------------------------------------------------------------------------- */\n\n/**\n * Patch options\n */\ninterface PatchOptions {\n document$: Observable /* Document observable */\n tablet$: Observable /* Media tablet observable */\n}\n\n/* ----------------------------------------------------------------------------\n * Functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Patch indeterminate checkboxes\n *\n * This function replaces the indeterminate \"pseudo state\" with the actual\n * indeterminate state, which is used to keep navigation always expanded.\n *\n * @param options - Options\n */\nexport function patchIndeterminate(\n { document$, tablet$ }: PatchOptions\n): void {\n document$\n .pipe(\n switchMap(() => getElements(\n // @todo `data-md-state` is deprecated and removed in v9\n \".md-toggle--indeterminate, [data-md-state=indeterminate]\"\n )),\n tap(el => {\n el.indeterminate = true\n el.checked = false\n }),\n mergeMap(el => fromEvent(el, \"change\")\n .pipe(\n takeWhile(() => el.classList.contains(\"md-toggle--indeterminate\")),\n map(() => el)\n )\n ),\n withLatestFrom(tablet$)\n )\n .subscribe(([el, tablet]) => {\n el.classList.remove(\"md-toggle--indeterminate\")\n if (tablet)\n el.checked = false\n })\n}\n", "/*\n * Copyright (c) 2016-2022 Martin Donath \n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\nimport {\n Observable,\n filter,\n fromEvent,\n map,\n mergeMap,\n switchMap,\n tap\n} from \"rxjs\"\n\nimport { getElements } from \"~/browser\"\n\n/* ----------------------------------------------------------------------------\n * Helper types\n * ------------------------------------------------------------------------- */\n\n/**\n * Patch options\n */\ninterface PatchOptions {\n document$: Observable /* Document observable */\n}\n\n/* ----------------------------------------------------------------------------\n * Helper functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Check whether the given device is an Apple device\n *\n * @returns Test result\n */\nfunction isAppleDevice(): boolean {\n return /(iPad|iPhone|iPod)/.test(navigator.userAgent)\n}\n\n/* ----------------------------------------------------------------------------\n * Functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Patch all elements with `data-md-scrollfix` attributes\n *\n * This is a year-old patch which ensures that overflow scrolling works at the\n * top and bottom of containers on iOS by ensuring a `1px` scroll offset upon\n * the start of a touch event.\n *\n * @see https://bit.ly/2SCtAOO - Original source\n *\n * @param options - Options\n */\nexport function patchScrollfix(\n { document$ }: PatchOptions\n): void {\n document$\n .pipe(\n switchMap(() => getElements(\"[data-md-scrollfix]\")),\n tap(el => el.removeAttribute(\"data-md-scrollfix\")),\n filter(isAppleDevice),\n mergeMap(el => fromEvent(el, \"touchstart\")\n .pipe(\n map(() => el)\n )\n )\n )\n .subscribe(el => {\n const top = el.scrollTop\n\n /* We're at the top of the container */\n if (top === 0) {\n el.scrollTop = 1\n\n /* We're at the bottom of the container */\n } else if (top + el.offsetHeight === el.scrollHeight) {\n el.scrollTop = top - 1\n }\n })\n}\n", "/*\n * Copyright (c) 2016-2022 Martin Donath \n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\nimport {\n Observable,\n combineLatest,\n delay,\n map,\n of,\n switchMap,\n withLatestFrom\n} from \"rxjs\"\n\nimport {\n Viewport,\n watchToggle\n} from \"~/browser\"\n\n/* ----------------------------------------------------------------------------\n * Helper types\n * ------------------------------------------------------------------------- */\n\n/**\n * Patch options\n */\ninterface PatchOptions {\n viewport$: Observable /* Viewport observable */\n tablet$: Observable /* Media tablet observable */\n}\n\n/* ----------------------------------------------------------------------------\n * Functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Patch the document body to lock when search is open\n *\n * For mobile and tablet viewports, the search is rendered full screen, which\n * leads to scroll leaking when at the top or bottom of the search result. This\n * function locks the body when the search is in full screen mode, and restores\n * the scroll position when leaving.\n *\n * @param options - Options\n */\nexport function patchScrolllock(\n { viewport$, tablet$ }: PatchOptions\n): void {\n combineLatest([watchToggle(\"search\"), tablet$])\n .pipe(\n map(([active, tablet]) => active && !tablet),\n switchMap(active => of(active)\n .pipe(\n delay(active ? 400 : 100)\n )\n ),\n withLatestFrom(viewport$)\n )\n .subscribe(([active, { offset: { y }}]) => {\n if (active) {\n document.body.setAttribute(\"data-md-scrolllock\", \"\")\n document.body.style.top = `-${y}px`\n } else {\n const value = -1 * parseInt(document.body.style.top, 10)\n document.body.removeAttribute(\"data-md-scrolllock\")\n document.body.style.top = \"\"\n if (value)\n window.scrollTo(0, value)\n }\n })\n}\n", "/*\n * Copyright (c) 2016-2022 Martin Donath \n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\n/* ----------------------------------------------------------------------------\n * Polyfills\n * ------------------------------------------------------------------------- */\n\n/* Polyfill `Object.entries` */\nif (!Object.entries)\n Object.entries = function (obj: object) {\n const data: [string, string][] = []\n for (const key of Object.keys(obj))\n // @ts-expect-error - ignore property access warning\n data.push([key, obj[key]])\n\n /* Return entries */\n return data\n }\n\n/* Polyfill `Object.values` */\nif (!Object.values)\n Object.values = function (obj: object) {\n const data: string[] = []\n for (const key of Object.keys(obj))\n // @ts-expect-error - ignore property access warning\n data.push(obj[key])\n\n /* Return values */\n return data\n }\n\n/* ------------------------------------------------------------------------- */\n\n/* Polyfills for `Element` */\nif (typeof Element !== \"undefined\") {\n\n /* Polyfill `Element.scrollTo` */\n if (!Element.prototype.scrollTo)\n Element.prototype.scrollTo = function (\n x?: ScrollToOptions | number, y?: number\n ): void {\n if (typeof x === \"object\") {\n this.scrollLeft = x.left!\n this.scrollTop = x.top!\n } else {\n this.scrollLeft = x!\n this.scrollTop = y!\n }\n }\n\n /* Polyfill `Element.replaceWith` */\n if (!Element.prototype.replaceWith)\n Element.prototype.replaceWith = function (\n ...nodes: Array\n ): void {\n const parent = this.parentNode\n if (parent) {\n if (nodes.length === 0)\n parent.removeChild(this)\n\n /* Replace children and create text nodes */\n for (let i = nodes.length - 1; i >= 0; i--) {\n let node = nodes[i]\n if (typeof node !== \"object\")\n node = document.createTextNode(node)\n else if (node.parentNode)\n node.parentNode.removeChild(node)\n\n /* Replace child or insert before previous sibling */\n if (!i)\n parent.replaceChild(node, this)\n else\n parent.insertBefore(this.previousSibling!, node)\n }\n }\n }\n}\n"], + "mappings": "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", + "names": [] +} diff --git a/develop/assets/javascripts/lunr/min/lunr.ar.min.js b/develop/assets/javascripts/lunr/min/lunr.ar.min.js new file mode 100644 index 0000000..248ddc5 --- /dev/null +++ b/develop/assets/javascripts/lunr/min/lunr.ar.min.js @@ -0,0 +1 @@ +!function(e,r){"function"==typeof define&&define.amd?define(r):"object"==typeof exports?module.exports=r():r()(e.lunr)}(this,function(){return function(e){if(void 0===e)throw new Error("Lunr is not present. Please include / require Lunr before this script.");if(void 0===e.stemmerSupport)throw new Error("Lunr stemmer support is not present. Please include / require Lunr stemmer support before this script.");e.ar=function(){this.pipeline.reset(),this.pipeline.add(e.ar.trimmer,e.ar.stopWordFilter,e.ar.stemmer),this.searchPipeline&&(this.searchPipeline.reset(),this.searchPipeline.add(e.ar.stemmer))},e.ar.wordCharacters="ء-ٛٱـ",e.ar.trimmer=e.trimmerSupport.generateTrimmer(e.ar.wordCharacters),e.Pipeline.registerFunction(e.ar.trimmer,"trimmer-ar"),e.ar.stemmer=function(){var e=this;return e.result=!1,e.preRemoved=!1,e.sufRemoved=!1,e.pre={pre1:"ف ك ب و س ل ن ا ي ت",pre2:"ال لل",pre3:"بال وال فال تال كال ولل",pre4:"فبال كبال وبال وكال"},e.suf={suf1:"ه ك ت ن ا ي",suf2:"نك نه ها وك يا اه ون ين تن تم نا وا ان كم كن ني نن ما هم هن تك ته ات يه",suf3:"تين كهم نيه نهم ونه وها يهم ونا ونك وني وهم تكم تنا تها تني تهم كما كها ناه نكم هنا تان يها",suf4:"كموه ناها ونني ونهم تكما تموه تكاه كماه ناكم ناهم نيها وننا"},e.patterns=JSON.parse('{"pt43":[{"pt":[{"c":"ا","l":1}]},{"pt":[{"c":"ا,ت,ن,ي","l":0}],"mPt":[{"c":"ف","l":0,"m":1},{"c":"ع","l":1,"m":2},{"c":"ل","l":2,"m":3}]},{"pt":[{"c":"و","l":2}],"mPt":[{"c":"ف","l":0,"m":0},{"c":"ع","l":1,"m":1},{"c":"ل","l":2,"m":3}]},{"pt":[{"c":"ا","l":2}]},{"pt":[{"c":"ي","l":2}],"mPt":[{"c":"ف","l":0,"m":0},{"c":"ع","l":1,"m":1},{"c":"ا","l":2},{"c":"ل","l":3,"m":3}]},{"pt":[{"c":"م","l":0}]}],"pt53":[{"pt":[{"c":"ت","l":0},{"c":"ا","l":2}]},{"pt":[{"c":"ا,ن,ت,ي","l":0},{"c":"ت","l":2}],"mPt":[{"c":"ا","l":0},{"c":"ف","l":1,"m":1},{"c":"ت","l":2},{"c":"ع","l":3,"m":3},{"c":"ا","l":4},{"c":"ل","l":5,"m":4}]},{"pt":[{"c":"ا","l":0},{"c":"ا","l":2}],"mPt":[{"c":"ا","l":0},{"c":"ف","l":1,"m":1},{"c":"ع","l":2,"m":3},{"c":"ل","l":3,"m":4},{"c":"ا","l":4},{"c":"ل","l":5,"m":4}]},{"pt":[{"c":"ا","l":0},{"c":"ا","l":3}],"mPt":[{"c":"ف","l":0,"m":1},{"c":"ع","l":1,"m":2},{"c":"ل","l":2,"m":4}]},{"pt":[{"c":"ا","l":3},{"c":"ن","l":4}]},{"pt":[{"c":"ت","l":0},{"c":"ي","l":3}]},{"pt":[{"c":"م","l":0},{"c":"و","l":3}]},{"pt":[{"c":"ا","l":1},{"c":"و","l":3}]},{"pt":[{"c":"و","l":1},{"c":"ا","l":2}]},{"pt":[{"c":"م","l":0},{"c":"ا","l":3}]},{"pt":[{"c":"م","l":0},{"c":"ي","l":3}]},{"pt":[{"c":"ا","l":2},{"c":"ن","l":3}]},{"pt":[{"c":"م","l":0},{"c":"ن","l":1}],"mPt":[{"c":"ا","l":0},{"c":"ن","l":1},{"c":"ف","l":2,"m":2},{"c":"ع","l":3,"m":3},{"c":"ا","l":4},{"c":"ل","l":5,"m":4}]},{"pt":[{"c":"م","l":0},{"c":"ت","l":2}],"mPt":[{"c":"ا","l":0},{"c":"ف","l":1,"m":1},{"c":"ت","l":2},{"c":"ع","l":3,"m":3},{"c":"ا","l":4},{"c":"ل","l":5,"m":4}]},{"pt":[{"c":"م","l":0},{"c":"ا","l":2}]},{"pt":[{"c":"م","l":1},{"c":"ا","l":3}]},{"pt":[{"c":"ي,ت,ا,ن","l":0},{"c":"ت","l":1}],"mPt":[{"c":"ف","l":0,"m":2},{"c":"ع","l":1,"m":3},{"c":"ا","l":2},{"c":"ل","l":3,"m":4}]},{"pt":[{"c":"ت,ي,ا,ن","l":0},{"c":"ت","l":2}],"mPt":[{"c":"ا","l":0},{"c":"ف","l":1,"m":1},{"c":"ت","l":2},{"c":"ع","l":3,"m":3},{"c":"ا","l":4},{"c":"ل","l":5,"m":4}]},{"pt":[{"c":"ا","l":2},{"c":"ي","l":3}]},{"pt":[{"c":"ا,ي,ت,ن","l":0},{"c":"ن","l":1}],"mPt":[{"c":"ا","l":0},{"c":"ن","l":1},{"c":"ف","l":2,"m":2},{"c":"ع","l":3,"m":3},{"c":"ا","l":4},{"c":"ل","l":5,"m":4}]},{"pt":[{"c":"ا","l":3},{"c":"ء","l":4}]}],"pt63":[{"pt":[{"c":"ا","l":0},{"c":"ت","l":2},{"c":"ا","l":4}]},{"pt":[{"c":"ا,ت,ن,ي","l":0},{"c":"س","l":1},{"c":"ت","l":2}],"mPt":[{"c":"ا","l":0},{"c":"س","l":1},{"c":"ت","l":2},{"c":"ف","l":3,"m":3},{"c":"ع","l":4,"m":4},{"c":"ا","l":5},{"c":"ل","l":6,"m":5}]},{"pt":[{"c":"ا,ن,ت,ي","l":0},{"c":"و","l":3}]},{"pt":[{"c":"م","l":0},{"c":"س","l":1},{"c":"ت","l":2}],"mPt":[{"c":"ا","l":0},{"c":"س","l":1},{"c":"ت","l":2},{"c":"ف","l":3,"m":3},{"c":"ع","l":4,"m":4},{"c":"ا","l":5},{"c":"ل","l":6,"m":5}]},{"pt":[{"c":"ي","l":1},{"c":"ي","l":3},{"c":"ا","l":4},{"c":"ء","l":5}]},{"pt":[{"c":"ا","l":0},{"c":"ن","l":1},{"c":"ا","l":4}]}],"pt54":[{"pt":[{"c":"ت","l":0}]},{"pt":[{"c":"ا,ي,ت,ن","l":0}],"mPt":[{"c":"ا","l":0},{"c":"ف","l":1,"m":1},{"c":"ع","l":2,"m":2},{"c":"ل","l":3,"m":3},{"c":"ر","l":4,"m":4},{"c":"ا","l":5},{"c":"ر","l":6,"m":4}]},{"pt":[{"c":"م","l":0}],"mPt":[{"c":"ا","l":0},{"c":"ف","l":1,"m":1},{"c":"ع","l":2,"m":2},{"c":"ل","l":3,"m":3},{"c":"ر","l":4,"m":4},{"c":"ا","l":5},{"c":"ر","l":6,"m":4}]},{"pt":[{"c":"ا","l":2}]},{"pt":[{"c":"ا","l":0},{"c":"ن","l":2}]}],"pt64":[{"pt":[{"c":"ا","l":0},{"c":"ا","l":4}]},{"pt":[{"c":"م","l":0},{"c":"ت","l":1}]}],"pt73":[{"pt":[{"c":"ا","l":0},{"c":"س","l":1},{"c":"ت","l":2},{"c":"ا","l":5}]}],"pt75":[{"pt":[{"c":"ا","l":0},{"c":"ا","l":5}]}]}'),e.execArray=["cleanWord","removeDiacritics","cleanAlef","removeStopWords","normalizeHamzaAndAlef","removeStartWaw","removePre432","removeEndTaa","wordCheck"],e.stem=function(){var r=0;for(e.result=!1,e.preRemoved=!1,e.sufRemoved=!1;r=0)return!0},e.normalizeHamzaAndAlef=function(){return e.word=e.word.replace("ؤ","ء"),e.word=e.word.replace("ئ","ء"),e.word=e.word.replace(/([\u0627])\1+/gi,"ا"),!1},e.removeEndTaa=function(){return!(e.word.length>2)||(e.word=e.word.replace(/[\u0627]$/,""),e.word=e.word.replace("ة",""),!1)},e.removeStartWaw=function(){return e.word.length>3&&"و"==e.word[0]&&"و"==e.word[1]&&(e.word=e.word.slice(1)),!1},e.removePre432=function(){var r=e.word;if(e.word.length>=7){var t=new RegExp("^("+e.pre.pre4.split(" ").join("|")+")");e.word=e.word.replace(t,"")}if(e.word==r&&e.word.length>=6){var c=new RegExp("^("+e.pre.pre3.split(" ").join("|")+")");e.word=e.word.replace(c,"")}if(e.word==r&&e.word.length>=5){var l=new RegExp("^("+e.pre.pre2.split(" ").join("|")+")");e.word=e.word.replace(l,"")}return r!=e.word&&(e.preRemoved=!0),!1},e.patternCheck=function(r){for(var t=0;t3){var t=new RegExp("^("+e.pre.pre1.split(" ").join("|")+")");e.word=e.word.replace(t,"")}return r!=e.word&&(e.preRemoved=!0),!1},e.removeSuf1=function(){var r=e.word;if(0==e.sufRemoved&&e.word.length>3){var t=new RegExp("("+e.suf.suf1.split(" ").join("|")+")$");e.word=e.word.replace(t,"")}return r!=e.word&&(e.sufRemoved=!0),!1},e.removeSuf432=function(){var r=e.word;if(e.word.length>=6){var t=new RegExp("("+e.suf.suf4.split(" ").join("|")+")$");e.word=e.word.replace(t,"")}if(e.word==r&&e.word.length>=5){var c=new RegExp("("+e.suf.suf3.split(" ").join("|")+")$");e.word=e.word.replace(c,"")}if(e.word==r&&e.word.length>=4){var l=new RegExp("("+e.suf.suf2.split(" ").join("|")+")$");e.word=e.word.replace(l,"")}return r!=e.word&&(e.sufRemoved=!0),!1},e.wordCheck=function(){for(var r=(e.word,[e.removeSuf432,e.removeSuf1,e.removePre1]),t=0,c=!1;e.word.length>=7&&!e.result&&t=f.limit)return;f.cursor++}for(;!f.out_grouping(w,97,248);){if(f.cursor>=f.limit)return;f.cursor++}d=f.cursor,d=d&&(r=f.limit_backward,f.limit_backward=d,f.ket=f.cursor,e=f.find_among_b(c,32),f.limit_backward=r,e))switch(f.bra=f.cursor,e){case 1:f.slice_del();break;case 2:f.in_grouping_b(p,97,229)&&f.slice_del()}}function t(){var e,r=f.limit-f.cursor;f.cursor>=d&&(e=f.limit_backward,f.limit_backward=d,f.ket=f.cursor,f.find_among_b(l,4)?(f.bra=f.cursor,f.limit_backward=e,f.cursor=f.limit-r,f.cursor>f.limit_backward&&(f.cursor--,f.bra=f.cursor,f.slice_del())):f.limit_backward=e)}function s(){var e,r,i,n=f.limit-f.cursor;if(f.ket=f.cursor,f.eq_s_b(2,"st")&&(f.bra=f.cursor,f.eq_s_b(2,"ig")&&f.slice_del()),f.cursor=f.limit-n,f.cursor>=d&&(r=f.limit_backward,f.limit_backward=d,f.ket=f.cursor,e=f.find_among_b(m,5),f.limit_backward=r,e))switch(f.bra=f.cursor,e){case 1:f.slice_del(),i=f.limit-f.cursor,t(),f.cursor=f.limit-i;break;case 2:f.slice_from("løs")}}function o(){var e;f.cursor>=d&&(e=f.limit_backward,f.limit_backward=d,f.ket=f.cursor,f.out_grouping_b(w,97,248)?(f.bra=f.cursor,u=f.slice_to(u),f.limit_backward=e,f.eq_v_b(u)&&f.slice_del()):f.limit_backward=e)}var a,d,u,c=[new r("hed",-1,1),new r("ethed",0,1),new r("ered",-1,1),new r("e",-1,1),new r("erede",3,1),new r("ende",3,1),new r("erende",5,1),new r("ene",3,1),new r("erne",3,1),new r("ere",3,1),new r("en",-1,1),new r("heden",10,1),new r("eren",10,1),new r("er",-1,1),new r("heder",13,1),new r("erer",13,1),new r("s",-1,2),new r("heds",16,1),new r("es",16,1),new r("endes",18,1),new r("erendes",19,1),new r("enes",18,1),new r("ernes",18,1),new r("eres",18,1),new r("ens",16,1),new r("hedens",24,1),new r("erens",24,1),new r("ers",16,1),new r("ets",16,1),new r("erets",28,1),new r("et",-1,1),new r("eret",30,1)],l=[new r("gd",-1,-1),new r("dt",-1,-1),new r("gt",-1,-1),new r("kt",-1,-1)],m=[new r("ig",-1,1),new r("lig",0,1),new r("elig",1,1),new r("els",-1,1),new r("løst",-1,2)],w=[17,65,16,1,0,0,0,0,0,0,0,0,0,0,0,0,48,0,128],p=[239,254,42,3,0,0,0,0,0,0,0,0,0,0,0,0,16],f=new i;this.setCurrent=function(e){f.setCurrent(e)},this.getCurrent=function(){return f.getCurrent()},this.stem=function(){var r=f.cursor;return e(),f.limit_backward=r,f.cursor=f.limit,n(),f.cursor=f.limit,t(),f.cursor=f.limit,s(),f.cursor=f.limit,o(),!0}};return function(e){return"function"==typeof e.update?e.update(function(e){return n.setCurrent(e),n.stem(),n.getCurrent()}):(n.setCurrent(e),n.stem(),n.getCurrent())}}(),e.Pipeline.registerFunction(e.da.stemmer,"stemmer-da"),e.da.stopWordFilter=e.generateStopWordFilter("ad af alle alt anden at blev blive bliver da de dem den denne der deres det dette dig din disse dog du efter eller en end er et for fra ham han hans har havde have hende hendes her hos hun hvad hvis hvor i ikke ind jeg jer jo kunne man mange med meget men mig min mine mit mod ned noget nogle nu når og også om op os over på selv sig sin sine sit skal skulle som sådan thi til ud under var vi vil ville vor være været".split(" ")),e.Pipeline.registerFunction(e.da.stopWordFilter,"stopWordFilter-da")}}); \ No newline at end of file diff --git a/develop/assets/javascripts/lunr/min/lunr.de.min.js b/develop/assets/javascripts/lunr/min/lunr.de.min.js new file mode 100644 index 0000000..f3b5c10 --- /dev/null +++ b/develop/assets/javascripts/lunr/min/lunr.de.min.js @@ -0,0 +1,18 @@ +/*! + * Lunr languages, `German` language + * https://github.com/MihaiValentin/lunr-languages + * + * Copyright 2014, Mihai Valentin + * http://www.mozilla.org/MPL/ + */ +/*! + * based on + * Snowball JavaScript Library v0.3 + * http://code.google.com/p/urim/ + * http://snowball.tartarus.org/ + * + * Copyright 2010, Oleg Mazko + * http://www.mozilla.org/MPL/ + */ + +!function(e,r){"function"==typeof define&&define.amd?define(r):"object"==typeof exports?module.exports=r():r()(e.lunr)}(this,function(){return function(e){if(void 0===e)throw new Error("Lunr is not present. Please include / require Lunr before this script.");if(void 0===e.stemmerSupport)throw new Error("Lunr stemmer support is not present. Please include / require Lunr stemmer support before this script.");e.de=function(){this.pipeline.reset(),this.pipeline.add(e.de.trimmer,e.de.stopWordFilter,e.de.stemmer),this.searchPipeline&&(this.searchPipeline.reset(),this.searchPipeline.add(e.de.stemmer))},e.de.wordCharacters="A-Za-zªºÀ-ÖØ-öø-ʸˠ-ˤᴀ-ᴥᴬ-ᵜᵢ-ᵥᵫ-ᵷᵹ-ᶾḀ-ỿⁱⁿₐ-ₜKÅℲⅎⅠ-ↈⱠ-ⱿꜢ-ꞇꞋ-ꞭꞰ-ꞷꟷ-ꟿꬰ-ꭚꭜ-ꭤff-stA-Za-z",e.de.trimmer=e.trimmerSupport.generateTrimmer(e.de.wordCharacters),e.Pipeline.registerFunction(e.de.trimmer,"trimmer-de"),e.de.stemmer=function(){var r=e.stemmerSupport.Among,n=e.stemmerSupport.SnowballProgram,i=new function(){function e(e,r,n){return!(!v.eq_s(1,e)||(v.ket=v.cursor,!v.in_grouping(p,97,252)))&&(v.slice_from(r),v.cursor=n,!0)}function i(){for(var r,n,i,s,t=v.cursor;;)if(r=v.cursor,v.bra=r,v.eq_s(1,"ß"))v.ket=v.cursor,v.slice_from("ss");else{if(r>=v.limit)break;v.cursor=r+1}for(v.cursor=t;;)for(n=v.cursor;;){if(i=v.cursor,v.in_grouping(p,97,252)){if(s=v.cursor,v.bra=s,e("u","U",i))break;if(v.cursor=s,e("y","Y",i))break}if(i>=v.limit)return void(v.cursor=n);v.cursor=i+1}}function s(){for(;!v.in_grouping(p,97,252);){if(v.cursor>=v.limit)return!0;v.cursor++}for(;!v.out_grouping(p,97,252);){if(v.cursor>=v.limit)return!0;v.cursor++}return!1}function t(){m=v.limit,l=m;var e=v.cursor+3;0<=e&&e<=v.limit&&(d=e,s()||(m=v.cursor,m=v.limit)return;v.cursor++}}}function c(){return m<=v.cursor}function u(){return l<=v.cursor}function a(){var e,r,n,i,s=v.limit-v.cursor;if(v.ket=v.cursor,(e=v.find_among_b(w,7))&&(v.bra=v.cursor,c()))switch(e){case 1:v.slice_del();break;case 2:v.slice_del(),v.ket=v.cursor,v.eq_s_b(1,"s")&&(v.bra=v.cursor,v.eq_s_b(3,"nis")&&v.slice_del());break;case 3:v.in_grouping_b(g,98,116)&&v.slice_del()}if(v.cursor=v.limit-s,v.ket=v.cursor,(e=v.find_among_b(f,4))&&(v.bra=v.cursor,c()))switch(e){case 1:v.slice_del();break;case 2:if(v.in_grouping_b(k,98,116)){var t=v.cursor-3;v.limit_backward<=t&&t<=v.limit&&(v.cursor=t,v.slice_del())}}if(v.cursor=v.limit-s,v.ket=v.cursor,(e=v.find_among_b(_,8))&&(v.bra=v.cursor,u()))switch(e){case 1:v.slice_del(),v.ket=v.cursor,v.eq_s_b(2,"ig")&&(v.bra=v.cursor,r=v.limit-v.cursor,v.eq_s_b(1,"e")||(v.cursor=v.limit-r,u()&&v.slice_del()));break;case 2:n=v.limit-v.cursor,v.eq_s_b(1,"e")||(v.cursor=v.limit-n,v.slice_del());break;case 3:if(v.slice_del(),v.ket=v.cursor,i=v.limit-v.cursor,!v.eq_s_b(2,"er")&&(v.cursor=v.limit-i,!v.eq_s_b(2,"en")))break;v.bra=v.cursor,c()&&v.slice_del();break;case 4:v.slice_del(),v.ket=v.cursor,e=v.find_among_b(b,2),e&&(v.bra=v.cursor,u()&&1==e&&v.slice_del())}}var d,l,m,h=[new r("",-1,6),new r("U",0,2),new r("Y",0,1),new r("ä",0,3),new r("ö",0,4),new r("ü",0,5)],w=[new r("e",-1,2),new r("em",-1,1),new r("en",-1,2),new r("ern",-1,1),new r("er",-1,1),new r("s",-1,3),new r("es",5,2)],f=[new r("en",-1,1),new r("er",-1,1),new r("st",-1,2),new r("est",2,1)],b=[new r("ig",-1,1),new r("lich",-1,1)],_=[new r("end",-1,1),new r("ig",-1,2),new r("ung",-1,1),new r("lich",-1,3),new r("isch",-1,2),new r("ik",-1,2),new r("heit",-1,3),new r("keit",-1,4)],p=[17,65,16,1,0,0,0,0,0,0,0,0,0,0,0,0,8,0,32,8],g=[117,30,5],k=[117,30,4],v=new n;this.setCurrent=function(e){v.setCurrent(e)},this.getCurrent=function(){return v.getCurrent()},this.stem=function(){var e=v.cursor;return i(),v.cursor=e,t(),v.limit_backward=e,v.cursor=v.limit,a(),v.cursor=v.limit_backward,o(),!0}};return function(e){return"function"==typeof e.update?e.update(function(e){return i.setCurrent(e),i.stem(),i.getCurrent()}):(i.setCurrent(e),i.stem(),i.getCurrent())}}(),e.Pipeline.registerFunction(e.de.stemmer,"stemmer-de"),e.de.stopWordFilter=e.generateStopWordFilter("aber alle allem allen aller alles als also am an ander andere anderem anderen anderer anderes anderm andern anderr anders auch auf aus bei bin bis bist da damit dann das dasselbe dazu daß dein deine deinem deinen deiner deines dem demselben den denn denselben der derer derselbe derselben des desselben dessen dich die dies diese dieselbe dieselben diesem diesen dieser dieses dir doch dort du durch ein eine einem einen einer eines einig einige einigem einigen einiger einiges einmal er es etwas euch euer eure eurem euren eurer eures für gegen gewesen hab habe haben hat hatte hatten hier hin hinter ich ihm ihn ihnen ihr ihre ihrem ihren ihrer ihres im in indem ins ist jede jedem jeden jeder jedes jene jenem jenen jener jenes jetzt kann kein keine keinem keinen keiner keines können könnte machen man manche manchem manchen mancher manches mein meine meinem meinen meiner meines mich mir mit muss musste nach nicht nichts noch nun nur ob oder ohne sehr sein seine seinem seinen seiner seines selbst sich sie sind so solche solchem solchen solcher solches soll sollte sondern sonst um und uns unse unsem unsen unser unses unter viel vom von vor war waren warst was weg weil weiter welche welchem welchen welcher welches wenn werde werden wie wieder will wir wird wirst wo wollen wollte während würde würden zu zum zur zwar zwischen über".split(" ")),e.Pipeline.registerFunction(e.de.stopWordFilter,"stopWordFilter-de")}}); \ No newline at end of file diff --git a/develop/assets/javascripts/lunr/min/lunr.du.min.js b/develop/assets/javascripts/lunr/min/lunr.du.min.js new file mode 100644 index 0000000..49a0f3f --- /dev/null +++ b/develop/assets/javascripts/lunr/min/lunr.du.min.js @@ -0,0 +1,18 @@ +/*! + * Lunr languages, `Dutch` language + * https://github.com/MihaiValentin/lunr-languages + * + * Copyright 2014, Mihai Valentin + * http://www.mozilla.org/MPL/ + */ +/*! + * based on + * Snowball JavaScript Library v0.3 + * http://code.google.com/p/urim/ + * http://snowball.tartarus.org/ + * + * Copyright 2010, Oleg Mazko + * http://www.mozilla.org/MPL/ + */ + +!function(e,r){"function"==typeof define&&define.amd?define(r):"object"==typeof exports?module.exports=r():r()(e.lunr)}(this,function(){return function(e){if(void 0===e)throw new Error("Lunr is not present. Please include / require Lunr before this script.");if(void 0===e.stemmerSupport)throw new Error("Lunr stemmer support is not present. Please include / require Lunr stemmer support before this script.");console.warn('[Lunr Languages] Please use the "nl" instead of the "du". The "nl" code is the standard code for Dutch language, and "du" will be removed in the next major versions.'),e.du=function(){this.pipeline.reset(),this.pipeline.add(e.du.trimmer,e.du.stopWordFilter,e.du.stemmer),this.searchPipeline&&(this.searchPipeline.reset(),this.searchPipeline.add(e.du.stemmer))},e.du.wordCharacters="A-Za-zªºÀ-ÖØ-öø-ʸˠ-ˤᴀ-ᴥᴬ-ᵜᵢ-ᵥᵫ-ᵷᵹ-ᶾḀ-ỿⁱⁿₐ-ₜKÅℲⅎⅠ-ↈⱠ-ⱿꜢ-ꞇꞋ-ꞭꞰ-ꞷꟷ-ꟿꬰ-ꭚꭜ-ꭤff-stA-Za-z",e.du.trimmer=e.trimmerSupport.generateTrimmer(e.du.wordCharacters),e.Pipeline.registerFunction(e.du.trimmer,"trimmer-du"),e.du.stemmer=function(){var r=e.stemmerSupport.Among,i=e.stemmerSupport.SnowballProgram,n=new function(){function e(){for(var e,r,i,o=C.cursor;;){if(C.bra=C.cursor,e=C.find_among(b,11))switch(C.ket=C.cursor,e){case 1:C.slice_from("a");continue;case 2:C.slice_from("e");continue;case 3:C.slice_from("i");continue;case 4:C.slice_from("o");continue;case 5:C.slice_from("u");continue;case 6:if(C.cursor>=C.limit)break;C.cursor++;continue}break}for(C.cursor=o,C.bra=o,C.eq_s(1,"y")?(C.ket=C.cursor,C.slice_from("Y")):C.cursor=o;;)if(r=C.cursor,C.in_grouping(q,97,232)){if(i=C.cursor,C.bra=i,C.eq_s(1,"i"))C.ket=C.cursor,C.in_grouping(q,97,232)&&(C.slice_from("I"),C.cursor=r);else if(C.cursor=i,C.eq_s(1,"y"))C.ket=C.cursor,C.slice_from("Y"),C.cursor=r;else if(n(r))break}else if(n(r))break}function n(e){return C.cursor=e,e>=C.limit||(C.cursor++,!1)}function o(){_=C.limit,f=_,t()||(_=C.cursor,_<3&&(_=3),t()||(f=C.cursor))}function t(){for(;!C.in_grouping(q,97,232);){if(C.cursor>=C.limit)return!0;C.cursor++}for(;!C.out_grouping(q,97,232);){if(C.cursor>=C.limit)return!0;C.cursor++}return!1}function s(){for(var e;;)if(C.bra=C.cursor,e=C.find_among(p,3))switch(C.ket=C.cursor,e){case 1:C.slice_from("y");break;case 2:C.slice_from("i");break;case 3:if(C.cursor>=C.limit)return;C.cursor++}}function u(){return _<=C.cursor}function c(){return f<=C.cursor}function a(){var e=C.limit-C.cursor;C.find_among_b(g,3)&&(C.cursor=C.limit-e,C.ket=C.cursor,C.cursor>C.limit_backward&&(C.cursor--,C.bra=C.cursor,C.slice_del()))}function l(){var e;w=!1,C.ket=C.cursor,C.eq_s_b(1,"e")&&(C.bra=C.cursor,u()&&(e=C.limit-C.cursor,C.out_grouping_b(q,97,232)&&(C.cursor=C.limit-e,C.slice_del(),w=!0,a())))}function m(){var e;u()&&(e=C.limit-C.cursor,C.out_grouping_b(q,97,232)&&(C.cursor=C.limit-e,C.eq_s_b(3,"gem")||(C.cursor=C.limit-e,C.slice_del(),a())))}function d(){var e,r,i,n,o,t,s=C.limit-C.cursor;if(C.ket=C.cursor,e=C.find_among_b(h,5))switch(C.bra=C.cursor,e){case 1:u()&&C.slice_from("heid");break;case 2:m();break;case 3:u()&&C.out_grouping_b(z,97,232)&&C.slice_del()}if(C.cursor=C.limit-s,l(),C.cursor=C.limit-s,C.ket=C.cursor,C.eq_s_b(4,"heid")&&(C.bra=C.cursor,c()&&(r=C.limit-C.cursor,C.eq_s_b(1,"c")||(C.cursor=C.limit-r,C.slice_del(),C.ket=C.cursor,C.eq_s_b(2,"en")&&(C.bra=C.cursor,m())))),C.cursor=C.limit-s,C.ket=C.cursor,e=C.find_among_b(k,6))switch(C.bra=C.cursor,e){case 1:if(c()){if(C.slice_del(),i=C.limit-C.cursor,C.ket=C.cursor,C.eq_s_b(2,"ig")&&(C.bra=C.cursor,c()&&(n=C.limit-C.cursor,!C.eq_s_b(1,"e")))){C.cursor=C.limit-n,C.slice_del();break}C.cursor=C.limit-i,a()}break;case 2:c()&&(o=C.limit-C.cursor,C.eq_s_b(1,"e")||(C.cursor=C.limit-o,C.slice_del()));break;case 3:c()&&(C.slice_del(),l());break;case 4:c()&&C.slice_del();break;case 5:c()&&w&&C.slice_del()}C.cursor=C.limit-s,C.out_grouping_b(j,73,232)&&(t=C.limit-C.cursor,C.find_among_b(v,4)&&C.out_grouping_b(q,97,232)&&(C.cursor=C.limit-t,C.ket=C.cursor,C.cursor>C.limit_backward&&(C.cursor--,C.bra=C.cursor,C.slice_del())))}var f,_,w,b=[new r("",-1,6),new r("á",0,1),new r("ä",0,1),new r("é",0,2),new r("ë",0,2),new r("í",0,3),new r("ï",0,3),new r("ó",0,4),new r("ö",0,4),new r("ú",0,5),new r("ü",0,5)],p=[new r("",-1,3),new r("I",0,2),new r("Y",0,1)],g=[new r("dd",-1,-1),new r("kk",-1,-1),new r("tt",-1,-1)],h=[new r("ene",-1,2),new r("se",-1,3),new r("en",-1,2),new r("heden",2,1),new r("s",-1,3)],k=[new r("end",-1,1),new r("ig",-1,2),new r("ing",-1,1),new r("lijk",-1,3),new r("baar",-1,4),new r("bar",-1,5)],v=[new r("aa",-1,-1),new r("ee",-1,-1),new r("oo",-1,-1),new r("uu",-1,-1)],q=[17,65,16,1,0,0,0,0,0,0,0,0,0,0,0,0,128],j=[1,0,0,17,65,16,1,0,0,0,0,0,0,0,0,0,0,0,0,128],z=[17,67,16,1,0,0,0,0,0,0,0,0,0,0,0,0,128],C=new i;this.setCurrent=function(e){C.setCurrent(e)},this.getCurrent=function(){return C.getCurrent()},this.stem=function(){var r=C.cursor;return e(),C.cursor=r,o(),C.limit_backward=r,C.cursor=C.limit,d(),C.cursor=C.limit_backward,s(),!0}};return function(e){return"function"==typeof e.update?e.update(function(e){return n.setCurrent(e),n.stem(),n.getCurrent()}):(n.setCurrent(e),n.stem(),n.getCurrent())}}(),e.Pipeline.registerFunction(e.du.stemmer,"stemmer-du"),e.du.stopWordFilter=e.generateStopWordFilter(" aan al alles als altijd andere ben bij daar dan dat de der deze die dit doch doen door dus een eens en er ge geen geweest haar had heb hebben heeft hem het hier hij hoe hun iemand iets ik in is ja je kan kon kunnen maar me meer men met mij mijn moet na naar niet niets nog nu of om omdat onder ons ook op over reeds te tegen toch toen tot u uit uw van veel voor want waren was wat werd wezen wie wil worden wordt zal ze zelf zich zij zijn zo zonder zou".split(" ")),e.Pipeline.registerFunction(e.du.stopWordFilter,"stopWordFilter-du")}}); \ No newline at end of file diff --git a/develop/assets/javascripts/lunr/min/lunr.es.min.js b/develop/assets/javascripts/lunr/min/lunr.es.min.js new file mode 100644 index 0000000..2989d34 --- /dev/null +++ b/develop/assets/javascripts/lunr/min/lunr.es.min.js @@ -0,0 +1,18 @@ +/*! + * Lunr languages, `Spanish` language + * https://github.com/MihaiValentin/lunr-languages + * + * Copyright 2014, Mihai Valentin + * http://www.mozilla.org/MPL/ + */ +/*! + * based on + * Snowball JavaScript Library v0.3 + * http://code.google.com/p/urim/ + * http://snowball.tartarus.org/ + * + * Copyright 2010, Oleg Mazko + * http://www.mozilla.org/MPL/ + */ + +!function(e,s){"function"==typeof define&&define.amd?define(s):"object"==typeof exports?module.exports=s():s()(e.lunr)}(this,function(){return function(e){if(void 0===e)throw new Error("Lunr is not present. Please include / require Lunr before this script.");if(void 0===e.stemmerSupport)throw new Error("Lunr stemmer support is not present. Please include / require Lunr stemmer support before this script.");e.es=function(){this.pipeline.reset(),this.pipeline.add(e.es.trimmer,e.es.stopWordFilter,e.es.stemmer),this.searchPipeline&&(this.searchPipeline.reset(),this.searchPipeline.add(e.es.stemmer))},e.es.wordCharacters="A-Za-zªºÀ-ÖØ-öø-ʸˠ-ˤᴀ-ᴥᴬ-ᵜᵢ-ᵥᵫ-ᵷᵹ-ᶾḀ-ỿⁱⁿₐ-ₜKÅℲⅎⅠ-ↈⱠ-ⱿꜢ-ꞇꞋ-ꞭꞰ-ꞷꟷ-ꟿꬰ-ꭚꭜ-ꭤff-stA-Za-z",e.es.trimmer=e.trimmerSupport.generateTrimmer(e.es.wordCharacters),e.Pipeline.registerFunction(e.es.trimmer,"trimmer-es"),e.es.stemmer=function(){var s=e.stemmerSupport.Among,r=e.stemmerSupport.SnowballProgram,n=new function(){function e(){if(A.out_grouping(x,97,252)){for(;!A.in_grouping(x,97,252);){if(A.cursor>=A.limit)return!0;A.cursor++}return!1}return!0}function n(){if(A.in_grouping(x,97,252)){var s=A.cursor;if(e()){if(A.cursor=s,!A.in_grouping(x,97,252))return!0;for(;!A.out_grouping(x,97,252);){if(A.cursor>=A.limit)return!0;A.cursor++}}return!1}return!0}function i(){var s,r=A.cursor;if(n()){if(A.cursor=r,!A.out_grouping(x,97,252))return;if(s=A.cursor,e()){if(A.cursor=s,!A.in_grouping(x,97,252)||A.cursor>=A.limit)return;A.cursor++}}g=A.cursor}function a(){for(;!A.in_grouping(x,97,252);){if(A.cursor>=A.limit)return!1;A.cursor++}for(;!A.out_grouping(x,97,252);){if(A.cursor>=A.limit)return!1;A.cursor++}return!0}function t(){var e=A.cursor;g=A.limit,p=g,v=g,i(),A.cursor=e,a()&&(p=A.cursor,a()&&(v=A.cursor))}function o(){for(var e;;){if(A.bra=A.cursor,e=A.find_among(k,6))switch(A.ket=A.cursor,e){case 1:A.slice_from("a");continue;case 2:A.slice_from("e");continue;case 3:A.slice_from("i");continue;case 4:A.slice_from("o");continue;case 5:A.slice_from("u");continue;case 6:if(A.cursor>=A.limit)break;A.cursor++;continue}break}}function u(){return g<=A.cursor}function w(){return p<=A.cursor}function c(){return v<=A.cursor}function m(){var e;if(A.ket=A.cursor,A.find_among_b(y,13)&&(A.bra=A.cursor,(e=A.find_among_b(q,11))&&u()))switch(e){case 1:A.bra=A.cursor,A.slice_from("iendo");break;case 2:A.bra=A.cursor,A.slice_from("ando");break;case 3:A.bra=A.cursor,A.slice_from("ar");break;case 4:A.bra=A.cursor,A.slice_from("er");break;case 5:A.bra=A.cursor,A.slice_from("ir");break;case 6:A.slice_del();break;case 7:A.eq_s_b(1,"u")&&A.slice_del()}}function l(e,s){if(!c())return!0;A.slice_del(),A.ket=A.cursor;var r=A.find_among_b(e,s);return r&&(A.bra=A.cursor,1==r&&c()&&A.slice_del()),!1}function d(e){return!c()||(A.slice_del(),A.ket=A.cursor,A.eq_s_b(2,e)&&(A.bra=A.cursor,c()&&A.slice_del()),!1)}function b(){var e;if(A.ket=A.cursor,e=A.find_among_b(S,46)){switch(A.bra=A.cursor,e){case 1:if(!c())return!1;A.slice_del();break;case 2:if(d("ic"))return!1;break;case 3:if(!c())return!1;A.slice_from("log");break;case 4:if(!c())return!1;A.slice_from("u");break;case 5:if(!c())return!1;A.slice_from("ente");break;case 6:if(!w())return!1;A.slice_del(),A.ket=A.cursor,e=A.find_among_b(C,4),e&&(A.bra=A.cursor,c()&&(A.slice_del(),1==e&&(A.ket=A.cursor,A.eq_s_b(2,"at")&&(A.bra=A.cursor,c()&&A.slice_del()))));break;case 7:if(l(P,3))return!1;break;case 8:if(l(F,3))return!1;break;case 9:if(d("at"))return!1}return!0}return!1}function f(){var e,s;if(A.cursor>=g&&(s=A.limit_backward,A.limit_backward=g,A.ket=A.cursor,e=A.find_among_b(W,12),A.limit_backward=s,e)){if(A.bra=A.cursor,1==e){if(!A.eq_s_b(1,"u"))return!1;A.slice_del()}return!0}return!1}function _(){var e,s,r,n;if(A.cursor>=g&&(s=A.limit_backward,A.limit_backward=g,A.ket=A.cursor,e=A.find_among_b(L,96),A.limit_backward=s,e))switch(A.bra=A.cursor,e){case 1:r=A.limit-A.cursor,A.eq_s_b(1,"u")?(n=A.limit-A.cursor,A.eq_s_b(1,"g")?A.cursor=A.limit-n:A.cursor=A.limit-r):A.cursor=A.limit-r,A.bra=A.cursor;case 2:A.slice_del()}}function h(){var e,s;if(A.ket=A.cursor,e=A.find_among_b(z,8))switch(A.bra=A.cursor,e){case 1:u()&&A.slice_del();break;case 2:u()&&(A.slice_del(),A.ket=A.cursor,A.eq_s_b(1,"u")&&(A.bra=A.cursor,s=A.limit-A.cursor,A.eq_s_b(1,"g")&&(A.cursor=A.limit-s,u()&&A.slice_del())))}}var v,p,g,k=[new s("",-1,6),new s("á",0,1),new s("é",0,2),new s("í",0,3),new s("ó",0,4),new s("ú",0,5)],y=[new s("la",-1,-1),new s("sela",0,-1),new s("le",-1,-1),new s("me",-1,-1),new s("se",-1,-1),new s("lo",-1,-1),new s("selo",5,-1),new s("las",-1,-1),new s("selas",7,-1),new s("les",-1,-1),new s("los",-1,-1),new s("selos",10,-1),new s("nos",-1,-1)],q=[new s("ando",-1,6),new s("iendo",-1,6),new s("yendo",-1,7),new s("ándo",-1,2),new s("iéndo",-1,1),new s("ar",-1,6),new s("er",-1,6),new s("ir",-1,6),new s("ár",-1,3),new s("ér",-1,4),new s("ír",-1,5)],C=[new s("ic",-1,-1),new s("ad",-1,-1),new s("os",-1,-1),new s("iv",-1,1)],P=[new s("able",-1,1),new s("ible",-1,1),new s("ante",-1,1)],F=[new s("ic",-1,1),new s("abil",-1,1),new s("iv",-1,1)],S=[new s("ica",-1,1),new s("ancia",-1,2),new s("encia",-1,5),new s("adora",-1,2),new s("osa",-1,1),new s("ista",-1,1),new s("iva",-1,9),new s("anza",-1,1),new s("logía",-1,3),new s("idad",-1,8),new s("able",-1,1),new s("ible",-1,1),new s("ante",-1,2),new s("mente",-1,7),new s("amente",13,6),new s("ación",-1,2),new s("ución",-1,4),new s("ico",-1,1),new s("ismo",-1,1),new s("oso",-1,1),new s("amiento",-1,1),new s("imiento",-1,1),new s("ivo",-1,9),new s("ador",-1,2),new s("icas",-1,1),new s("ancias",-1,2),new s("encias",-1,5),new s("adoras",-1,2),new s("osas",-1,1),new s("istas",-1,1),new s("ivas",-1,9),new s("anzas",-1,1),new s("logías",-1,3),new s("idades",-1,8),new s("ables",-1,1),new s("ibles",-1,1),new s("aciones",-1,2),new s("uciones",-1,4),new s("adores",-1,2),new s("antes",-1,2),new s("icos",-1,1),new s("ismos",-1,1),new s("osos",-1,1),new s("amientos",-1,1),new s("imientos",-1,1),new s("ivos",-1,9)],W=[new s("ya",-1,1),new s("ye",-1,1),new s("yan",-1,1),new s("yen",-1,1),new s("yeron",-1,1),new s("yendo",-1,1),new s("yo",-1,1),new s("yas",-1,1),new s("yes",-1,1),new s("yais",-1,1),new s("yamos",-1,1),new s("yó",-1,1)],L=[new s("aba",-1,2),new s("ada",-1,2),new s("ida",-1,2),new s("ara",-1,2),new s("iera",-1,2),new s("ía",-1,2),new s("aría",5,2),new s("ería",5,2),new s("iría",5,2),new s("ad",-1,2),new s("ed",-1,2),new s("id",-1,2),new s("ase",-1,2),new s("iese",-1,2),new s("aste",-1,2),new s("iste",-1,2),new s("an",-1,2),new s("aban",16,2),new s("aran",16,2),new s("ieran",16,2),new s("ían",16,2),new s("arían",20,2),new s("erían",20,2),new s("irían",20,2),new s("en",-1,1),new s("asen",24,2),new s("iesen",24,2),new s("aron",-1,2),new s("ieron",-1,2),new s("arán",-1,2),new s("erán",-1,2),new s("irán",-1,2),new s("ado",-1,2),new s("ido",-1,2),new s("ando",-1,2),new s("iendo",-1,2),new s("ar",-1,2),new s("er",-1,2),new s("ir",-1,2),new s("as",-1,2),new s("abas",39,2),new s("adas",39,2),new s("idas",39,2),new s("aras",39,2),new s("ieras",39,2),new s("ías",39,2),new s("arías",45,2),new s("erías",45,2),new s("irías",45,2),new s("es",-1,1),new s("ases",49,2),new s("ieses",49,2),new s("abais",-1,2),new s("arais",-1,2),new s("ierais",-1,2),new s("íais",-1,2),new s("aríais",55,2),new s("eríais",55,2),new s("iríais",55,2),new s("aseis",-1,2),new s("ieseis",-1,2),new s("asteis",-1,2),new s("isteis",-1,2),new s("áis",-1,2),new s("éis",-1,1),new s("aréis",64,2),new s("eréis",64,2),new s("iréis",64,2),new s("ados",-1,2),new s("idos",-1,2),new s("amos",-1,2),new s("ábamos",70,2),new s("áramos",70,2),new s("iéramos",70,2),new s("íamos",70,2),new s("aríamos",74,2),new s("eríamos",74,2),new s("iríamos",74,2),new s("emos",-1,1),new s("aremos",78,2),new s("eremos",78,2),new s("iremos",78,2),new s("ásemos",78,2),new s("iésemos",78,2),new s("imos",-1,2),new s("arás",-1,2),new s("erás",-1,2),new s("irás",-1,2),new s("ís",-1,2),new s("ará",-1,2),new s("erá",-1,2),new s("irá",-1,2),new s("aré",-1,2),new s("eré",-1,2),new s("iré",-1,2),new s("ió",-1,2)],z=[new s("a",-1,1),new s("e",-1,2),new s("o",-1,1),new s("os",-1,1),new s("á",-1,1),new s("é",-1,2),new s("í",-1,1),new s("ó",-1,1)],x=[17,65,16,0,0,0,0,0,0,0,0,0,0,0,0,0,1,17,4,10],A=new r;this.setCurrent=function(e){A.setCurrent(e)},this.getCurrent=function(){return A.getCurrent()},this.stem=function(){var e=A.cursor;return t(),A.limit_backward=e,A.cursor=A.limit,m(),A.cursor=A.limit,b()||(A.cursor=A.limit,f()||(A.cursor=A.limit,_())),A.cursor=A.limit,h(),A.cursor=A.limit_backward,o(),!0}};return function(e){return"function"==typeof e.update?e.update(function(e){return n.setCurrent(e),n.stem(),n.getCurrent()}):(n.setCurrent(e),n.stem(),n.getCurrent())}}(),e.Pipeline.registerFunction(e.es.stemmer,"stemmer-es"),e.es.stopWordFilter=e.generateStopWordFilter("a al algo algunas algunos ante antes como con contra cual cuando de del desde donde durante e el ella ellas ellos en entre era erais eran eras eres es esa esas ese eso esos esta estaba estabais estaban estabas estad estada estadas estado estados estamos estando estar estaremos estará estarán estarás estaré estaréis estaría estaríais estaríamos estarían estarías estas este estemos esto estos estoy estuve estuviera estuvierais estuvieran estuvieras estuvieron estuviese estuvieseis estuviesen estuvieses estuvimos estuviste estuvisteis estuviéramos estuviésemos estuvo está estábamos estáis están estás esté estéis estén estés fue fuera fuerais fueran fueras fueron fuese fueseis fuesen fueses fui fuimos fuiste fuisteis fuéramos fuésemos ha habida habidas habido habidos habiendo habremos habrá habrán habrás habré habréis habría habríais habríamos habrían habrías habéis había habíais habíamos habían habías han has hasta hay haya hayamos hayan hayas hayáis he hemos hube hubiera hubierais hubieran hubieras hubieron hubiese hubieseis hubiesen hubieses hubimos hubiste hubisteis hubiéramos hubiésemos hubo la las le les lo los me mi mis mucho muchos muy más mí mía mías mío míos nada ni no nos nosotras nosotros nuestra nuestras nuestro nuestros o os otra otras otro otros para pero poco por porque que quien quienes qué se sea seamos sean seas seremos será serán serás seré seréis sería seríais seríamos serían serías seáis sido siendo sin sobre sois somos son soy su sus suya suyas suyo suyos sí también tanto te tendremos tendrá tendrán tendrás tendré tendréis tendría tendríais tendríamos tendrían tendrías tened tenemos tenga tengamos tengan tengas tengo tengáis tenida tenidas tenido tenidos teniendo tenéis tenía teníais teníamos tenían tenías ti tiene tienen tienes todo todos tu tus tuve tuviera tuvierais tuvieran tuvieras tuvieron tuviese tuvieseis tuviesen tuvieses tuvimos tuviste tuvisteis tuviéramos tuviésemos tuvo tuya tuyas tuyo tuyos tú un una uno unos vosotras vosotros vuestra vuestras vuestro vuestros y ya yo él éramos".split(" ")),e.Pipeline.registerFunction(e.es.stopWordFilter,"stopWordFilter-es")}}); \ No newline at end of file diff --git a/develop/assets/javascripts/lunr/min/lunr.fi.min.js b/develop/assets/javascripts/lunr/min/lunr.fi.min.js new file mode 100644 index 0000000..29f5dfc --- /dev/null +++ b/develop/assets/javascripts/lunr/min/lunr.fi.min.js @@ -0,0 +1,18 @@ +/*! + * Lunr languages, `Finnish` language + * https://github.com/MihaiValentin/lunr-languages + * + * Copyright 2014, Mihai Valentin + * http://www.mozilla.org/MPL/ + */ +/*! + * based on + * Snowball JavaScript Library v0.3 + * http://code.google.com/p/urim/ + * http://snowball.tartarus.org/ + * + * Copyright 2010, Oleg Mazko + * http://www.mozilla.org/MPL/ + */ + +!function(i,e){"function"==typeof define&&define.amd?define(e):"object"==typeof exports?module.exports=e():e()(i.lunr)}(this,function(){return function(i){if(void 0===i)throw new Error("Lunr is not present. Please include / require Lunr before this script.");if(void 0===i.stemmerSupport)throw new Error("Lunr stemmer support is not present. Please include / require Lunr stemmer support before this script.");i.fi=function(){this.pipeline.reset(),this.pipeline.add(i.fi.trimmer,i.fi.stopWordFilter,i.fi.stemmer),this.searchPipeline&&(this.searchPipeline.reset(),this.searchPipeline.add(i.fi.stemmer))},i.fi.wordCharacters="A-Za-zªºÀ-ÖØ-öø-ʸˠ-ˤᴀ-ᴥᴬ-ᵜᵢ-ᵥᵫ-ᵷᵹ-ᶾḀ-ỿⁱⁿₐ-ₜKÅℲⅎⅠ-ↈⱠ-ⱿꜢ-ꞇꞋ-ꞭꞰ-ꞷꟷ-ꟿꬰ-ꭚꭜ-ꭤff-stA-Za-z",i.fi.trimmer=i.trimmerSupport.generateTrimmer(i.fi.wordCharacters),i.Pipeline.registerFunction(i.fi.trimmer,"trimmer-fi"),i.fi.stemmer=function(){var e=i.stemmerSupport.Among,r=i.stemmerSupport.SnowballProgram,n=new function(){function i(){f=A.limit,d=f,n()||(f=A.cursor,n()||(d=A.cursor))}function n(){for(var i;;){if(i=A.cursor,A.in_grouping(W,97,246))break;if(A.cursor=i,i>=A.limit)return!0;A.cursor++}for(A.cursor=i;!A.out_grouping(W,97,246);){if(A.cursor>=A.limit)return!0;A.cursor++}return!1}function t(){return d<=A.cursor}function s(){var i,e;if(A.cursor>=f)if(e=A.limit_backward,A.limit_backward=f,A.ket=A.cursor,i=A.find_among_b(h,10)){switch(A.bra=A.cursor,A.limit_backward=e,i){case 1:if(!A.in_grouping_b(x,97,246))return;break;case 2:if(!t())return}A.slice_del()}else A.limit_backward=e}function o(){var i,e,r;if(A.cursor>=f)if(e=A.limit_backward,A.limit_backward=f,A.ket=A.cursor,i=A.find_among_b(v,9))switch(A.bra=A.cursor,A.limit_backward=e,i){case 1:r=A.limit-A.cursor,A.eq_s_b(1,"k")||(A.cursor=A.limit-r,A.slice_del());break;case 2:A.slice_del(),A.ket=A.cursor,A.eq_s_b(3,"kse")&&(A.bra=A.cursor,A.slice_from("ksi"));break;case 3:A.slice_del();break;case 4:A.find_among_b(p,6)&&A.slice_del();break;case 5:A.find_among_b(g,6)&&A.slice_del();break;case 6:A.find_among_b(j,2)&&A.slice_del()}else A.limit_backward=e}function l(){return A.find_among_b(q,7)}function a(){return A.eq_s_b(1,"i")&&A.in_grouping_b(L,97,246)}function u(){var i,e,r;if(A.cursor>=f)if(e=A.limit_backward,A.limit_backward=f,A.ket=A.cursor,i=A.find_among_b(C,30)){switch(A.bra=A.cursor,A.limit_backward=e,i){case 1:if(!A.eq_s_b(1,"a"))return;break;case 2:case 9:if(!A.eq_s_b(1,"e"))return;break;case 3:if(!A.eq_s_b(1,"i"))return;break;case 4:if(!A.eq_s_b(1,"o"))return;break;case 5:if(!A.eq_s_b(1,"ä"))return;break;case 6:if(!A.eq_s_b(1,"ö"))return;break;case 7:if(r=A.limit-A.cursor,!l()&&(A.cursor=A.limit-r,!A.eq_s_b(2,"ie"))){A.cursor=A.limit-r;break}if(A.cursor=A.limit-r,A.cursor<=A.limit_backward){A.cursor=A.limit-r;break}A.cursor--,A.bra=A.cursor;break;case 8:if(!A.in_grouping_b(W,97,246)||!A.out_grouping_b(W,97,246))return}A.slice_del(),k=!0}else A.limit_backward=e}function c(){var i,e,r;if(A.cursor>=d)if(e=A.limit_backward,A.limit_backward=d,A.ket=A.cursor,i=A.find_among_b(P,14)){if(A.bra=A.cursor,A.limit_backward=e,1==i){if(r=A.limit-A.cursor,A.eq_s_b(2,"po"))return;A.cursor=A.limit-r}A.slice_del()}else A.limit_backward=e}function m(){var i;A.cursor>=f&&(i=A.limit_backward,A.limit_backward=f,A.ket=A.cursor,A.find_among_b(F,2)?(A.bra=A.cursor,A.limit_backward=i,A.slice_del()):A.limit_backward=i)}function w(){var i,e,r,n,t,s;if(A.cursor>=f){if(e=A.limit_backward,A.limit_backward=f,A.ket=A.cursor,A.eq_s_b(1,"t")&&(A.bra=A.cursor,r=A.limit-A.cursor,A.in_grouping_b(W,97,246)&&(A.cursor=A.limit-r,A.slice_del(),A.limit_backward=e,n=A.limit-A.cursor,A.cursor>=d&&(A.cursor=d,t=A.limit_backward,A.limit_backward=A.cursor,A.cursor=A.limit-n,A.ket=A.cursor,i=A.find_among_b(S,2))))){if(A.bra=A.cursor,A.limit_backward=t,1==i){if(s=A.limit-A.cursor,A.eq_s_b(2,"po"))return;A.cursor=A.limit-s}return void A.slice_del()}A.limit_backward=e}}function _(){var i,e,r,n;if(A.cursor>=f){for(i=A.limit_backward,A.limit_backward=f,e=A.limit-A.cursor,l()&&(A.cursor=A.limit-e,A.ket=A.cursor,A.cursor>A.limit_backward&&(A.cursor--,A.bra=A.cursor,A.slice_del())),A.cursor=A.limit-e,A.ket=A.cursor,A.in_grouping_b(y,97,228)&&(A.bra=A.cursor,A.out_grouping_b(W,97,246)&&A.slice_del()),A.cursor=A.limit-e,A.ket=A.cursor,A.eq_s_b(1,"j")&&(A.bra=A.cursor,r=A.limit-A.cursor,A.eq_s_b(1,"o")?A.slice_del():(A.cursor=A.limit-r,A.eq_s_b(1,"u")&&A.slice_del())),A.cursor=A.limit-e,A.ket=A.cursor,A.eq_s_b(1,"o")&&(A.bra=A.cursor,A.eq_s_b(1,"j")&&A.slice_del()),A.cursor=A.limit-e,A.limit_backward=i;;){if(n=A.limit-A.cursor,A.out_grouping_b(W,97,246)){A.cursor=A.limit-n;break}if(A.cursor=A.limit-n,A.cursor<=A.limit_backward)return;A.cursor--}A.ket=A.cursor,A.cursor>A.limit_backward&&(A.cursor--,A.bra=A.cursor,b=A.slice_to(),A.eq_v_b(b)&&A.slice_del())}}var k,b,d,f,h=[new e("pa",-1,1),new e("sti",-1,2),new e("kaan",-1,1),new e("han",-1,1),new e("kin",-1,1),new e("hän",-1,1),new e("kään",-1,1),new e("ko",-1,1),new e("pä",-1,1),new e("kö",-1,1)],p=[new e("lla",-1,-1),new e("na",-1,-1),new e("ssa",-1,-1),new e("ta",-1,-1),new e("lta",3,-1),new e("sta",3,-1)],g=[new e("llä",-1,-1),new e("nä",-1,-1),new e("ssä",-1,-1),new e("tä",-1,-1),new e("ltä",3,-1),new e("stä",3,-1)],j=[new e("lle",-1,-1),new e("ine",-1,-1)],v=[new e("nsa",-1,3),new e("mme",-1,3),new e("nne",-1,3),new e("ni",-1,2),new e("si",-1,1),new e("an",-1,4),new e("en",-1,6),new e("än",-1,5),new e("nsä",-1,3)],q=[new e("aa",-1,-1),new e("ee",-1,-1),new e("ii",-1,-1),new e("oo",-1,-1),new e("uu",-1,-1),new e("ää",-1,-1),new e("öö",-1,-1)],C=[new e("a",-1,8),new e("lla",0,-1),new e("na",0,-1),new e("ssa",0,-1),new e("ta",0,-1),new e("lta",4,-1),new e("sta",4,-1),new e("tta",4,9),new e("lle",-1,-1),new e("ine",-1,-1),new e("ksi",-1,-1),new e("n",-1,7),new e("han",11,1),new e("den",11,-1,a),new e("seen",11,-1,l),new e("hen",11,2),new e("tten",11,-1,a),new e("hin",11,3),new e("siin",11,-1,a),new e("hon",11,4),new e("hän",11,5),new e("hön",11,6),new e("ä",-1,8),new e("llä",22,-1),new e("nä",22,-1),new e("ssä",22,-1),new e("tä",22,-1),new e("ltä",26,-1),new e("stä",26,-1),new e("ttä",26,9)],P=[new e("eja",-1,-1),new e("mma",-1,1),new e("imma",1,-1),new e("mpa",-1,1),new e("impa",3,-1),new e("mmi",-1,1),new e("immi",5,-1),new e("mpi",-1,1),new e("impi",7,-1),new e("ejä",-1,-1),new e("mmä",-1,1),new e("immä",10,-1),new e("mpä",-1,1),new e("impä",12,-1)],F=[new e("i",-1,-1),new e("j",-1,-1)],S=[new e("mma",-1,1),new e("imma",0,-1)],y=[17,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,8],W=[17,65,16,1,0,0,0,0,0,0,0,0,0,0,0,0,8,0,32],L=[17,65,16,0,0,0,0,0,0,0,0,0,0,0,0,0,8,0,32],x=[17,97,24,1,0,0,0,0,0,0,0,0,0,0,0,0,8,0,32],A=new r;this.setCurrent=function(i){A.setCurrent(i)},this.getCurrent=function(){return A.getCurrent()},this.stem=function(){var e=A.cursor;return i(),k=!1,A.limit_backward=e,A.cursor=A.limit,s(),A.cursor=A.limit,o(),A.cursor=A.limit,u(),A.cursor=A.limit,c(),A.cursor=A.limit,k?(m(),A.cursor=A.limit):(A.cursor=A.limit,w(),A.cursor=A.limit),_(),!0}};return function(i){return"function"==typeof i.update?i.update(function(i){return n.setCurrent(i),n.stem(),n.getCurrent()}):(n.setCurrent(i),n.stem(),n.getCurrent())}}(),i.Pipeline.registerFunction(i.fi.stemmer,"stemmer-fi"),i.fi.stopWordFilter=i.generateStopWordFilter("ei eivät emme en et ette että he heidän heidät heihin heille heillä heiltä heissä heistä heitä hän häneen hänelle hänellä häneltä hänen hänessä hänestä hänet häntä itse ja johon joiden joihin joiksi joilla joille joilta joina joissa joista joita joka joksi jolla jolle jolta jona jonka jos jossa josta jota jotka kanssa keiden keihin keiksi keille keillä keiltä keinä keissä keistä keitä keneen keneksi kenelle kenellä keneltä kenen kenenä kenessä kenestä kenet ketkä ketkä ketä koska kuin kuka kun me meidän meidät meihin meille meillä meiltä meissä meistä meitä mihin miksi mikä mille millä miltä minkä minkä minua minulla minulle minulta minun minussa minusta minut minuun minä minä missä mistä mitkä mitä mukaan mutta ne niiden niihin niiksi niille niillä niiltä niin niin niinä niissä niistä niitä noiden noihin noiksi noilla noille noilta noin noina noissa noista noita nuo nyt näiden näihin näiksi näille näillä näiltä näinä näissä näistä näitä nämä ole olemme olen olet olette oli olimme olin olisi olisimme olisin olisit olisitte olisivat olit olitte olivat olla olleet ollut on ovat poikki se sekä sen siihen siinä siitä siksi sille sillä sillä siltä sinua sinulla sinulle sinulta sinun sinussa sinusta sinut sinuun sinä sinä sitä tai te teidän teidät teihin teille teillä teiltä teissä teistä teitä tuo tuohon tuoksi tuolla tuolle tuolta tuon tuona tuossa tuosta tuota tähän täksi tälle tällä tältä tämä tämän tänä tässä tästä tätä vaan vai vaikka yli".split(" ")),i.Pipeline.registerFunction(i.fi.stopWordFilter,"stopWordFilter-fi")}}); \ No newline at end of file diff --git a/develop/assets/javascripts/lunr/min/lunr.fr.min.js b/develop/assets/javascripts/lunr/min/lunr.fr.min.js new file mode 100644 index 0000000..68cd009 --- /dev/null +++ b/develop/assets/javascripts/lunr/min/lunr.fr.min.js @@ -0,0 +1,18 @@ +/*! + * Lunr languages, `French` language + * https://github.com/MihaiValentin/lunr-languages + * + * Copyright 2014, Mihai Valentin + * http://www.mozilla.org/MPL/ + */ +/*! + * based on + * Snowball JavaScript Library v0.3 + * http://code.google.com/p/urim/ + * http://snowball.tartarus.org/ + * + * Copyright 2010, Oleg Mazko + * http://www.mozilla.org/MPL/ + */ + +!function(e,r){"function"==typeof define&&define.amd?define(r):"object"==typeof exports?module.exports=r():r()(e.lunr)}(this,function(){return function(e){if(void 0===e)throw new Error("Lunr is not present. Please include / require Lunr before this script.");if(void 0===e.stemmerSupport)throw new Error("Lunr stemmer support is not present. Please include / require Lunr stemmer support before this script.");e.fr=function(){this.pipeline.reset(),this.pipeline.add(e.fr.trimmer,e.fr.stopWordFilter,e.fr.stemmer),this.searchPipeline&&(this.searchPipeline.reset(),this.searchPipeline.add(e.fr.stemmer))},e.fr.wordCharacters="A-Za-zªºÀ-ÖØ-öø-ʸˠ-ˤᴀ-ᴥᴬ-ᵜᵢ-ᵥᵫ-ᵷᵹ-ᶾḀ-ỿⁱⁿₐ-ₜKÅℲⅎⅠ-ↈⱠ-ⱿꜢ-ꞇꞋ-ꞭꞰ-ꞷꟷ-ꟿꬰ-ꭚꭜ-ꭤff-stA-Za-z",e.fr.trimmer=e.trimmerSupport.generateTrimmer(e.fr.wordCharacters),e.Pipeline.registerFunction(e.fr.trimmer,"trimmer-fr"),e.fr.stemmer=function(){var r=e.stemmerSupport.Among,s=e.stemmerSupport.SnowballProgram,i=new function(){function e(e,r,s){return!(!W.eq_s(1,e)||(W.ket=W.cursor,!W.in_grouping(F,97,251)))&&(W.slice_from(r),W.cursor=s,!0)}function i(e,r,s){return!!W.eq_s(1,e)&&(W.ket=W.cursor,W.slice_from(r),W.cursor=s,!0)}function n(){for(var r,s;;){if(r=W.cursor,W.in_grouping(F,97,251)){if(W.bra=W.cursor,s=W.cursor,e("u","U",r))continue;if(W.cursor=s,e("i","I",r))continue;if(W.cursor=s,i("y","Y",r))continue}if(W.cursor=r,W.bra=r,!e("y","Y",r)){if(W.cursor=r,W.eq_s(1,"q")&&(W.bra=W.cursor,i("u","U",r)))continue;if(W.cursor=r,r>=W.limit)return;W.cursor++}}}function t(){for(;!W.in_grouping(F,97,251);){if(W.cursor>=W.limit)return!0;W.cursor++}for(;!W.out_grouping(F,97,251);){if(W.cursor>=W.limit)return!0;W.cursor++}return!1}function u(){var e=W.cursor;if(q=W.limit,g=q,p=q,W.in_grouping(F,97,251)&&W.in_grouping(F,97,251)&&W.cursor=W.limit){W.cursor=q;break}W.cursor++}while(!W.in_grouping(F,97,251))}q=W.cursor,W.cursor=e,t()||(g=W.cursor,t()||(p=W.cursor))}function o(){for(var e,r;;){if(r=W.cursor,W.bra=r,!(e=W.find_among(h,4)))break;switch(W.ket=W.cursor,e){case 1:W.slice_from("i");break;case 2:W.slice_from("u");break;case 3:W.slice_from("y");break;case 4:if(W.cursor>=W.limit)return;W.cursor++}}}function c(){return q<=W.cursor}function a(){return g<=W.cursor}function l(){return p<=W.cursor}function w(){var e,r;if(W.ket=W.cursor,e=W.find_among_b(C,43)){switch(W.bra=W.cursor,e){case 1:if(!l())return!1;W.slice_del();break;case 2:if(!l())return!1;W.slice_del(),W.ket=W.cursor,W.eq_s_b(2,"ic")&&(W.bra=W.cursor,l()?W.slice_del():W.slice_from("iqU"));break;case 3:if(!l())return!1;W.slice_from("log");break;case 4:if(!l())return!1;W.slice_from("u");break;case 5:if(!l())return!1;W.slice_from("ent");break;case 6:if(!c())return!1;if(W.slice_del(),W.ket=W.cursor,e=W.find_among_b(z,6))switch(W.bra=W.cursor,e){case 1:l()&&(W.slice_del(),W.ket=W.cursor,W.eq_s_b(2,"at")&&(W.bra=W.cursor,l()&&W.slice_del()));break;case 2:l()?W.slice_del():a()&&W.slice_from("eux");break;case 3:l()&&W.slice_del();break;case 4:c()&&W.slice_from("i")}break;case 7:if(!l())return!1;if(W.slice_del(),W.ket=W.cursor,e=W.find_among_b(y,3))switch(W.bra=W.cursor,e){case 1:l()?W.slice_del():W.slice_from("abl");break;case 2:l()?W.slice_del():W.slice_from("iqU");break;case 3:l()&&W.slice_del()}break;case 8:if(!l())return!1;if(W.slice_del(),W.ket=W.cursor,W.eq_s_b(2,"at")&&(W.bra=W.cursor,l()&&(W.slice_del(),W.ket=W.cursor,W.eq_s_b(2,"ic")))){W.bra=W.cursor,l()?W.slice_del():W.slice_from("iqU");break}break;case 9:W.slice_from("eau");break;case 10:if(!a())return!1;W.slice_from("al");break;case 11:if(l())W.slice_del();else{if(!a())return!1;W.slice_from("eux")}break;case 12:if(!a()||!W.out_grouping_b(F,97,251))return!1;W.slice_del();break;case 13:return c()&&W.slice_from("ant"),!1;case 14:return c()&&W.slice_from("ent"),!1;case 15:return r=W.limit-W.cursor,W.in_grouping_b(F,97,251)&&c()&&(W.cursor=W.limit-r,W.slice_del()),!1}return!0}return!1}function f(){var e,r;if(W.cursor=q){if(s=W.limit_backward,W.limit_backward=q,W.ket=W.cursor,e=W.find_among_b(P,7))switch(W.bra=W.cursor,e){case 1:if(l()){if(i=W.limit-W.cursor,!W.eq_s_b(1,"s")&&(W.cursor=W.limit-i,!W.eq_s_b(1,"t")))break;W.slice_del()}break;case 2:W.slice_from("i");break;case 3:W.slice_del();break;case 4:W.eq_s_b(2,"gu")&&W.slice_del()}W.limit_backward=s}}function b(){var e=W.limit-W.cursor;W.find_among_b(U,5)&&(W.cursor=W.limit-e,W.ket=W.cursor,W.cursor>W.limit_backward&&(W.cursor--,W.bra=W.cursor,W.slice_del()))}function d(){for(var e,r=1;W.out_grouping_b(F,97,251);)r--;if(r<=0){if(W.ket=W.cursor,e=W.limit-W.cursor,!W.eq_s_b(1,"é")&&(W.cursor=W.limit-e,!W.eq_s_b(1,"è")))return;W.bra=W.cursor,W.slice_from("e")}}function k(){if(!w()&&(W.cursor=W.limit,!f()&&(W.cursor=W.limit,!m())))return W.cursor=W.limit,void _();W.cursor=W.limit,W.ket=W.cursor,W.eq_s_b(1,"Y")?(W.bra=W.cursor,W.slice_from("i")):(W.cursor=W.limit,W.eq_s_b(1,"ç")&&(W.bra=W.cursor,W.slice_from("c")))}var p,g,q,v=[new r("col",-1,-1),new r("par",-1,-1),new r("tap",-1,-1)],h=[new r("",-1,4),new r("I",0,1),new r("U",0,2),new r("Y",0,3)],z=[new r("iqU",-1,3),new r("abl",-1,3),new r("Ièr",-1,4),new r("ièr",-1,4),new r("eus",-1,2),new r("iv",-1,1)],y=[new r("ic",-1,2),new r("abil",-1,1),new r("iv",-1,3)],C=[new r("iqUe",-1,1),new r("atrice",-1,2),new r("ance",-1,1),new r("ence",-1,5),new r("logie",-1,3),new r("able",-1,1),new r("isme",-1,1),new r("euse",-1,11),new r("iste",-1,1),new r("ive",-1,8),new r("if",-1,8),new r("usion",-1,4),new r("ation",-1,2),new r("ution",-1,4),new r("ateur",-1,2),new r("iqUes",-1,1),new r("atrices",-1,2),new r("ances",-1,1),new r("ences",-1,5),new r("logies",-1,3),new r("ables",-1,1),new r("ismes",-1,1),new r("euses",-1,11),new r("istes",-1,1),new r("ives",-1,8),new r("ifs",-1,8),new r("usions",-1,4),new r("ations",-1,2),new r("utions",-1,4),new r("ateurs",-1,2),new r("ments",-1,15),new r("ements",30,6),new r("issements",31,12),new r("ités",-1,7),new r("ment",-1,15),new r("ement",34,6),new r("issement",35,12),new r("amment",34,13),new r("emment",34,14),new r("aux",-1,10),new r("eaux",39,9),new r("eux",-1,1),new r("ité",-1,7)],x=[new r("ira",-1,1),new r("ie",-1,1),new r("isse",-1,1),new r("issante",-1,1),new r("i",-1,1),new r("irai",4,1),new r("ir",-1,1),new r("iras",-1,1),new r("ies",-1,1),new r("îmes",-1,1),new r("isses",-1,1),new r("issantes",-1,1),new r("îtes",-1,1),new r("is",-1,1),new r("irais",13,1),new r("issais",13,1),new r("irions",-1,1),new r("issions",-1,1),new r("irons",-1,1),new r("issons",-1,1),new r("issants",-1,1),new r("it",-1,1),new r("irait",21,1),new r("issait",21,1),new r("issant",-1,1),new r("iraIent",-1,1),new r("issaIent",-1,1),new r("irent",-1,1),new r("issent",-1,1),new r("iront",-1,1),new r("ît",-1,1),new r("iriez",-1,1),new r("issiez",-1,1),new r("irez",-1,1),new r("issez",-1,1)],I=[new r("a",-1,3),new r("era",0,2),new r("asse",-1,3),new r("ante",-1,3),new r("ée",-1,2),new r("ai",-1,3),new r("erai",5,2),new r("er",-1,2),new r("as",-1,3),new r("eras",8,2),new r("âmes",-1,3),new r("asses",-1,3),new r("antes",-1,3),new r("âtes",-1,3),new r("ées",-1,2),new r("ais",-1,3),new r("erais",15,2),new r("ions",-1,1),new r("erions",17,2),new r("assions",17,3),new r("erons",-1,2),new r("ants",-1,3),new r("és",-1,2),new r("ait",-1,3),new r("erait",23,2),new r("ant",-1,3),new r("aIent",-1,3),new r("eraIent",26,2),new r("èrent",-1,2),new r("assent",-1,3),new r("eront",-1,2),new r("ât",-1,3),new r("ez",-1,2),new r("iez",32,2),new r("eriez",33,2),new r("assiez",33,3),new r("erez",32,2),new r("é",-1,2)],P=[new r("e",-1,3),new r("Ière",0,2),new r("ière",0,2),new r("ion",-1,1),new r("Ier",-1,2),new r("ier",-1,2),new r("ë",-1,4)],U=[new r("ell",-1,-1),new r("eill",-1,-1),new r("enn",-1,-1),new r("onn",-1,-1),new r("ett",-1,-1)],F=[17,65,16,1,0,0,0,0,0,0,0,0,0,0,0,128,130,103,8,5],S=[1,65,20,0,0,0,0,0,0,0,0,0,0,0,0,0,128],W=new s;this.setCurrent=function(e){W.setCurrent(e)},this.getCurrent=function(){return W.getCurrent()},this.stem=function(){var e=W.cursor;return n(),W.cursor=e,u(),W.limit_backward=e,W.cursor=W.limit,k(),W.cursor=W.limit,b(),W.cursor=W.limit,d(),W.cursor=W.limit_backward,o(),!0}};return function(e){return"function"==typeof e.update?e.update(function(e){return i.setCurrent(e),i.stem(),i.getCurrent()}):(i.setCurrent(e),i.stem(),i.getCurrent())}}(),e.Pipeline.registerFunction(e.fr.stemmer,"stemmer-fr"),e.fr.stopWordFilter=e.generateStopWordFilter("ai aie aient aies ait as au aura aurai auraient aurais aurait auras aurez auriez aurions aurons auront aux avaient avais avait avec avez aviez avions avons ayant ayez ayons c ce ceci celà ces cet cette d dans de des du elle en es est et eu eue eues eurent eus eusse eussent eusses eussiez eussions eut eux eûmes eût eûtes furent fus fusse fussent fusses fussiez fussions fut fûmes fût fûtes ici il ils j je l la le les leur leurs lui m ma mais me mes moi mon même n ne nos notre nous on ont ou par pas pour qu que quel quelle quelles quels qui s sa sans se sera serai seraient serais serait seras serez seriez serions serons seront ses soi soient sois soit sommes son sont soyez soyons suis sur t ta te tes toi ton tu un une vos votre vous y à étaient étais était étant étiez étions été étée étées étés êtes".split(" ")),e.Pipeline.registerFunction(e.fr.stopWordFilter,"stopWordFilter-fr")}}); \ No newline at end of file diff --git a/develop/assets/javascripts/lunr/min/lunr.hi.min.js b/develop/assets/javascripts/lunr/min/lunr.hi.min.js new file mode 100644 index 0000000..7dbc414 --- /dev/null +++ b/develop/assets/javascripts/lunr/min/lunr.hi.min.js @@ -0,0 +1 @@ +!function(e,r){"function"==typeof define&&define.amd?define(r):"object"==typeof exports?module.exports=r():r()(e.lunr)}(this,function(){return function(e){if(void 0===e)throw new Error("Lunr is not present. Please include / require Lunr before this script.");if(void 0===e.stemmerSupport)throw new Error("Lunr stemmer support is not present. Please include / require Lunr stemmer support before this script.");e.hi=function(){this.pipeline.reset(),this.pipeline.add(e.hi.trimmer,e.hi.stopWordFilter,e.hi.stemmer),this.searchPipeline&&(this.searchPipeline.reset(),this.searchPipeline.add(e.hi.stemmer))},e.hi.wordCharacters="ऀ-ःऄ-एऐ-टठ-यर-िी-ॏॐ-य़ॠ-९॰-ॿa-zA-Za-zA-Z0-90-9",e.hi.trimmer=e.trimmerSupport.generateTrimmer(e.hi.wordCharacters),e.Pipeline.registerFunction(e.hi.trimmer,"trimmer-hi"),e.hi.stopWordFilter=e.generateStopWordFilter("अत अपना अपनी अपने अभी अंदर आदि आप इत्यादि इन इनका इन्हीं इन्हें इन्हों इस इसका इसकी इसके इसमें इसी इसे उन उनका उनकी उनके उनको उन्हीं उन्हें उन्हों उस उसके उसी उसे एक एवं एस ऐसे और कई कर करता करते करना करने करें कहते कहा का काफ़ी कि कितना किन्हें किन्हों किया किर किस किसी किसे की कुछ कुल के को कोई कौन कौनसा गया घर जब जहाँ जा जितना जिन जिन्हें जिन्हों जिस जिसे जीधर जैसा जैसे जो तक तब तरह तिन तिन्हें तिन्हों तिस तिसे तो था थी थे दबारा दिया दुसरा दूसरे दो द्वारा न नके नहीं ना निहायत नीचे ने पर पहले पूरा पे फिर बनी बही बहुत बाद बाला बिलकुल भी भीतर मगर मानो मे में यदि यह यहाँ यही या यिह ये रखें रहा रहे ऱ्वासा लिए लिये लेकिन व वग़ैरह वर्ग वह वहाँ वहीं वाले वुह वे वो सकता सकते सबसे सभी साथ साबुत साभ सारा से सो संग ही हुआ हुई हुए है हैं हो होता होती होते होना होने".split(" ")),e.hi.stemmer=function(){return function(e){return"function"==typeof e.update?e.update(function(e){return e}):e}}();var r=e.wordcut;r.init(),e.hi.tokenizer=function(i){if(!arguments.length||null==i||void 0==i)return[];if(Array.isArray(i))return i.map(function(r){return isLunr2?new e.Token(r.toLowerCase()):r.toLowerCase()});var t=i.toString().toLowerCase().replace(/^\s+/,"");return r.cut(t).split("|")},e.Pipeline.registerFunction(e.hi.stemmer,"stemmer-hi"),e.Pipeline.registerFunction(e.hi.stopWordFilter,"stopWordFilter-hi")}}); \ No newline at end of file diff --git a/develop/assets/javascripts/lunr/min/lunr.hu.min.js b/develop/assets/javascripts/lunr/min/lunr.hu.min.js new file mode 100644 index 0000000..ed9d909 --- /dev/null +++ b/develop/assets/javascripts/lunr/min/lunr.hu.min.js @@ -0,0 +1,18 @@ +/*! + * Lunr languages, `Hungarian` language + * https://github.com/MihaiValentin/lunr-languages + * + * Copyright 2014, Mihai Valentin + * http://www.mozilla.org/MPL/ + */ +/*! + * based on + * Snowball JavaScript Library v0.3 + * http://code.google.com/p/urim/ + * http://snowball.tartarus.org/ + * + * Copyright 2010, Oleg Mazko + * http://www.mozilla.org/MPL/ + */ + +!function(e,n){"function"==typeof define&&define.amd?define(n):"object"==typeof exports?module.exports=n():n()(e.lunr)}(this,function(){return function(e){if(void 0===e)throw new Error("Lunr is not present. Please include / require Lunr before this script.");if(void 0===e.stemmerSupport)throw new Error("Lunr stemmer support is not present. Please include / require Lunr stemmer support before this script.");e.hu=function(){this.pipeline.reset(),this.pipeline.add(e.hu.trimmer,e.hu.stopWordFilter,e.hu.stemmer),this.searchPipeline&&(this.searchPipeline.reset(),this.searchPipeline.add(e.hu.stemmer))},e.hu.wordCharacters="A-Za-zªºÀ-ÖØ-öø-ʸˠ-ˤᴀ-ᴥᴬ-ᵜᵢ-ᵥᵫ-ᵷᵹ-ᶾḀ-ỿⁱⁿₐ-ₜKÅℲⅎⅠ-ↈⱠ-ⱿꜢ-ꞇꞋ-ꞭꞰ-ꞷꟷ-ꟿꬰ-ꭚꭜ-ꭤff-stA-Za-z",e.hu.trimmer=e.trimmerSupport.generateTrimmer(e.hu.wordCharacters),e.Pipeline.registerFunction(e.hu.trimmer,"trimmer-hu"),e.hu.stemmer=function(){var n=e.stemmerSupport.Among,r=e.stemmerSupport.SnowballProgram,i=new function(){function e(){var e,n=L.cursor;if(d=L.limit,L.in_grouping(W,97,252))for(;;){if(e=L.cursor,L.out_grouping(W,97,252))return L.cursor=e,L.find_among(g,8)||(L.cursor=e,e=L.limit)return void(d=e);L.cursor++}if(L.cursor=n,L.out_grouping(W,97,252)){for(;!L.in_grouping(W,97,252);){if(L.cursor>=L.limit)return;L.cursor++}d=L.cursor}}function i(){return d<=L.cursor}function a(){var e;if(L.ket=L.cursor,(e=L.find_among_b(h,2))&&(L.bra=L.cursor,i()))switch(e){case 1:L.slice_from("a");break;case 2:L.slice_from("e")}}function t(){var e=L.limit-L.cursor;return!!L.find_among_b(p,23)&&(L.cursor=L.limit-e,!0)}function s(){if(L.cursor>L.limit_backward){L.cursor--,L.ket=L.cursor;var e=L.cursor-1;L.limit_backward<=e&&e<=L.limit&&(L.cursor=e,L.bra=e,L.slice_del())}}function c(){var e;if(L.ket=L.cursor,(e=L.find_among_b(_,2))&&(L.bra=L.cursor,i())){if((1==e||2==e)&&!t())return;L.slice_del(),s()}}function o(){L.ket=L.cursor,L.find_among_b(v,44)&&(L.bra=L.cursor,i()&&(L.slice_del(),a()))}function w(){var e;if(L.ket=L.cursor,(e=L.find_among_b(z,3))&&(L.bra=L.cursor,i()))switch(e){case 1:L.slice_from("e");break;case 2:case 3:L.slice_from("a")}}function l(){var e;if(L.ket=L.cursor,(e=L.find_among_b(y,6))&&(L.bra=L.cursor,i()))switch(e){case 1:case 2:L.slice_del();break;case 3:L.slice_from("a");break;case 4:L.slice_from("e")}}function u(){var e;if(L.ket=L.cursor,(e=L.find_among_b(j,2))&&(L.bra=L.cursor,i())){if((1==e||2==e)&&!t())return;L.slice_del(),s()}}function m(){var e;if(L.ket=L.cursor,(e=L.find_among_b(C,7))&&(L.bra=L.cursor,i()))switch(e){case 1:L.slice_from("a");break;case 2:L.slice_from("e");break;case 3:case 4:case 5:case 6:case 7:L.slice_del()}}function k(){var e;if(L.ket=L.cursor,(e=L.find_among_b(P,12))&&(L.bra=L.cursor,i()))switch(e){case 1:case 4:case 7:case 9:L.slice_del();break;case 2:case 5:case 8:L.slice_from("e");break;case 3:case 6:L.slice_from("a")}}function f(){var e;if(L.ket=L.cursor,(e=L.find_among_b(F,31))&&(L.bra=L.cursor,i()))switch(e){case 1:case 4:case 7:case 8:case 9:case 12:case 13:case 16:case 17:case 18:L.slice_del();break;case 2:case 5:case 10:case 14:case 19:L.slice_from("a");break;case 3:case 6:case 11:case 15:case 20:L.slice_from("e")}}function b(){var e;if(L.ket=L.cursor,(e=L.find_among_b(S,42))&&(L.bra=L.cursor,i()))switch(e){case 1:case 4:case 5:case 6:case 9:case 10:case 11:case 14:case 15:case 16:case 17:case 20:case 21:case 24:case 25:case 26:case 29:L.slice_del();break;case 2:case 7:case 12:case 18:case 22:case 27:L.slice_from("a");break;case 3:case 8:case 13:case 19:case 23:case 28:L.slice_from("e")}}var d,g=[new n("cs",-1,-1),new n("dzs",-1,-1),new n("gy",-1,-1),new n("ly",-1,-1),new n("ny",-1,-1),new n("sz",-1,-1),new n("ty",-1,-1),new n("zs",-1,-1)],h=[new n("á",-1,1),new n("é",-1,2)],p=[new n("bb",-1,-1),new n("cc",-1,-1),new n("dd",-1,-1),new n("ff",-1,-1),new n("gg",-1,-1),new n("jj",-1,-1),new n("kk",-1,-1),new n("ll",-1,-1),new n("mm",-1,-1),new n("nn",-1,-1),new n("pp",-1,-1),new n("rr",-1,-1),new n("ccs",-1,-1),new n("ss",-1,-1),new n("zzs",-1,-1),new n("tt",-1,-1),new n("vv",-1,-1),new n("ggy",-1,-1),new n("lly",-1,-1),new n("nny",-1,-1),new n("tty",-1,-1),new n("ssz",-1,-1),new n("zz",-1,-1)],_=[new n("al",-1,1),new n("el",-1,2)],v=[new n("ba",-1,-1),new n("ra",-1,-1),new n("be",-1,-1),new n("re",-1,-1),new n("ig",-1,-1),new n("nak",-1,-1),new n("nek",-1,-1),new n("val",-1,-1),new n("vel",-1,-1),new n("ul",-1,-1),new n("nál",-1,-1),new n("nél",-1,-1),new n("ból",-1,-1),new n("ról",-1,-1),new n("tól",-1,-1),new n("bõl",-1,-1),new n("rõl",-1,-1),new n("tõl",-1,-1),new n("ül",-1,-1),new n("n",-1,-1),new n("an",19,-1),new n("ban",20,-1),new n("en",19,-1),new n("ben",22,-1),new n("képpen",22,-1),new n("on",19,-1),new n("ön",19,-1),new n("képp",-1,-1),new n("kor",-1,-1),new n("t",-1,-1),new n("at",29,-1),new n("et",29,-1),new n("ként",29,-1),new n("anként",32,-1),new n("enként",32,-1),new n("onként",32,-1),new n("ot",29,-1),new n("ért",29,-1),new n("öt",29,-1),new n("hez",-1,-1),new n("hoz",-1,-1),new n("höz",-1,-1),new n("vá",-1,-1),new n("vé",-1,-1)],z=[new n("án",-1,2),new n("én",-1,1),new n("ánként",-1,3)],y=[new n("stul",-1,2),new n("astul",0,1),new n("ástul",0,3),new n("stül",-1,2),new n("estül",3,1),new n("éstül",3,4)],j=[new n("á",-1,1),new n("é",-1,2)],C=[new n("k",-1,7),new n("ak",0,4),new n("ek",0,6),new n("ok",0,5),new n("ák",0,1),new n("ék",0,2),new n("ök",0,3)],P=[new n("éi",-1,7),new n("áéi",0,6),new n("ééi",0,5),new n("é",-1,9),new n("ké",3,4),new n("aké",4,1),new n("eké",4,1),new n("oké",4,1),new n("áké",4,3),new n("éké",4,2),new n("öké",4,1),new n("éé",3,8)],F=[new n("a",-1,18),new n("ja",0,17),new n("d",-1,16),new n("ad",2,13),new n("ed",2,13),new n("od",2,13),new n("ád",2,14),new n("éd",2,15),new n("öd",2,13),new n("e",-1,18),new n("je",9,17),new n("nk",-1,4),new n("unk",11,1),new n("ánk",11,2),new n("énk",11,3),new n("ünk",11,1),new n("uk",-1,8),new n("juk",16,7),new n("ájuk",17,5),new n("ük",-1,8),new n("jük",19,7),new n("éjük",20,6),new n("m",-1,12),new n("am",22,9),new n("em",22,9),new n("om",22,9),new n("ám",22,10),new n("ém",22,11),new n("o",-1,18),new n("á",-1,19),new n("é",-1,20)],S=[new n("id",-1,10),new n("aid",0,9),new n("jaid",1,6),new n("eid",0,9),new n("jeid",3,6),new n("áid",0,7),new n("éid",0,8),new n("i",-1,15),new n("ai",7,14),new n("jai",8,11),new n("ei",7,14),new n("jei",10,11),new n("ái",7,12),new n("éi",7,13),new n("itek",-1,24),new n("eitek",14,21),new n("jeitek",15,20),new n("éitek",14,23),new n("ik",-1,29),new n("aik",18,26),new n("jaik",19,25),new n("eik",18,26),new n("jeik",21,25),new n("áik",18,27),new n("éik",18,28),new n("ink",-1,20),new n("aink",25,17),new n("jaink",26,16),new n("eink",25,17),new n("jeink",28,16),new n("áink",25,18),new n("éink",25,19),new n("aitok",-1,21),new n("jaitok",32,20),new n("áitok",-1,22),new n("im",-1,5),new n("aim",35,4),new n("jaim",36,1),new n("eim",35,4),new n("jeim",38,1),new n("áim",35,2),new n("éim",35,3)],W=[17,65,16,0,0,0,0,0,0,0,0,0,0,0,0,0,1,17,52,14],L=new r;this.setCurrent=function(e){L.setCurrent(e)},this.getCurrent=function(){return L.getCurrent()},this.stem=function(){var n=L.cursor;return e(),L.limit_backward=n,L.cursor=L.limit,c(),L.cursor=L.limit,o(),L.cursor=L.limit,w(),L.cursor=L.limit,l(),L.cursor=L.limit,u(),L.cursor=L.limit,k(),L.cursor=L.limit,f(),L.cursor=L.limit,b(),L.cursor=L.limit,m(),!0}};return function(e){return"function"==typeof e.update?e.update(function(e){return i.setCurrent(e),i.stem(),i.getCurrent()}):(i.setCurrent(e),i.stem(),i.getCurrent())}}(),e.Pipeline.registerFunction(e.hu.stemmer,"stemmer-hu"),e.hu.stopWordFilter=e.generateStopWordFilter("a abban ahhoz ahogy ahol aki akik akkor alatt amely amelyek amelyekben amelyeket amelyet amelynek ami amikor amit amolyan amíg annak arra arról az azok azon azonban azt aztán azután azzal azért be belül benne bár cikk cikkek cikkeket csak de e ebben eddig egy egyes egyetlen egyik egyre egyéb egész ehhez ekkor el ellen elsõ elég elõ elõször elõtt emilyen ennek erre ez ezek ezen ezt ezzel ezért fel felé hanem hiszen hogy hogyan igen ill ill. illetve ilyen ilyenkor ismét ison itt jobban jó jól kell kellett keressünk keresztül ki kívül között közül legalább legyen lehet lehetett lenne lenni lesz lett maga magát majd majd meg mellett mely melyek mert mi mikor milyen minden mindenki mindent mindig mint mintha mit mivel miért most már más másik még míg nagy nagyobb nagyon ne nekem neki nem nincs néha néhány nélkül olyan ott pedig persze rá s saját sem semmi sok sokat sokkal szemben szerint szinte számára talán tehát teljes tovább továbbá több ugyanis utolsó után utána vagy vagyis vagyok valaki valami valamint való van vannak vele vissza viszont volna volt voltak voltam voltunk által általában át én éppen és így õ õk õket össze úgy új újabb újra".split(" ")),e.Pipeline.registerFunction(e.hu.stopWordFilter,"stopWordFilter-hu")}}); \ No newline at end of file diff --git a/develop/assets/javascripts/lunr/min/lunr.it.min.js b/develop/assets/javascripts/lunr/min/lunr.it.min.js new file mode 100644 index 0000000..344b6a3 --- /dev/null +++ b/develop/assets/javascripts/lunr/min/lunr.it.min.js @@ -0,0 +1,18 @@ +/*! + * Lunr languages, `Italian` language + * https://github.com/MihaiValentin/lunr-languages + * + * Copyright 2014, Mihai Valentin + * http://www.mozilla.org/MPL/ + */ +/*! + * based on + * Snowball JavaScript Library v0.3 + * http://code.google.com/p/urim/ + * http://snowball.tartarus.org/ + * + * Copyright 2010, Oleg Mazko + * http://www.mozilla.org/MPL/ + */ + +!function(e,r){"function"==typeof define&&define.amd?define(r):"object"==typeof exports?module.exports=r():r()(e.lunr)}(this,function(){return function(e){if(void 0===e)throw new Error("Lunr is not present. Please include / require Lunr before this script.");if(void 0===e.stemmerSupport)throw new Error("Lunr stemmer support is not present. Please include / require Lunr stemmer support before this script.");e.it=function(){this.pipeline.reset(),this.pipeline.add(e.it.trimmer,e.it.stopWordFilter,e.it.stemmer),this.searchPipeline&&(this.searchPipeline.reset(),this.searchPipeline.add(e.it.stemmer))},e.it.wordCharacters="A-Za-zªºÀ-ÖØ-öø-ʸˠ-ˤᴀ-ᴥᴬ-ᵜᵢ-ᵥᵫ-ᵷᵹ-ᶾḀ-ỿⁱⁿₐ-ₜKÅℲⅎⅠ-ↈⱠ-ⱿꜢ-ꞇꞋ-ꞭꞰ-ꞷꟷ-ꟿꬰ-ꭚꭜ-ꭤff-stA-Za-z",e.it.trimmer=e.trimmerSupport.generateTrimmer(e.it.wordCharacters),e.Pipeline.registerFunction(e.it.trimmer,"trimmer-it"),e.it.stemmer=function(){var r=e.stemmerSupport.Among,n=e.stemmerSupport.SnowballProgram,i=new function(){function e(e,r,n){return!(!x.eq_s(1,e)||(x.ket=x.cursor,!x.in_grouping(L,97,249)))&&(x.slice_from(r),x.cursor=n,!0)}function i(){for(var r,n,i,o,t=x.cursor;;){if(x.bra=x.cursor,r=x.find_among(h,7))switch(x.ket=x.cursor,r){case 1:x.slice_from("à");continue;case 2:x.slice_from("è");continue;case 3:x.slice_from("ì");continue;case 4:x.slice_from("ò");continue;case 5:x.slice_from("ù");continue;case 6:x.slice_from("qU");continue;case 7:if(x.cursor>=x.limit)break;x.cursor++;continue}break}for(x.cursor=t;;)for(n=x.cursor;;){if(i=x.cursor,x.in_grouping(L,97,249)){if(x.bra=x.cursor,o=x.cursor,e("u","U",i))break;if(x.cursor=o,e("i","I",i))break}if(x.cursor=i,x.cursor>=x.limit)return void(x.cursor=n);x.cursor++}}function o(e){if(x.cursor=e,!x.in_grouping(L,97,249))return!1;for(;!x.out_grouping(L,97,249);){if(x.cursor>=x.limit)return!1;x.cursor++}return!0}function t(){if(x.in_grouping(L,97,249)){var e=x.cursor;if(x.out_grouping(L,97,249)){for(;!x.in_grouping(L,97,249);){if(x.cursor>=x.limit)return o(e);x.cursor++}return!0}return o(e)}return!1}function s(){var e,r=x.cursor;if(!t()){if(x.cursor=r,!x.out_grouping(L,97,249))return;if(e=x.cursor,x.out_grouping(L,97,249)){for(;!x.in_grouping(L,97,249);){if(x.cursor>=x.limit)return x.cursor=e,void(x.in_grouping(L,97,249)&&x.cursor=x.limit)return;x.cursor++}k=x.cursor}function a(){for(;!x.in_grouping(L,97,249);){if(x.cursor>=x.limit)return!1;x.cursor++}for(;!x.out_grouping(L,97,249);){if(x.cursor>=x.limit)return!1;x.cursor++}return!0}function u(){var e=x.cursor;k=x.limit,p=k,g=k,s(),x.cursor=e,a()&&(p=x.cursor,a()&&(g=x.cursor))}function c(){for(var e;;){if(x.bra=x.cursor,!(e=x.find_among(q,3)))break;switch(x.ket=x.cursor,e){case 1:x.slice_from("i");break;case 2:x.slice_from("u");break;case 3:if(x.cursor>=x.limit)return;x.cursor++}}}function w(){return k<=x.cursor}function l(){return p<=x.cursor}function m(){return g<=x.cursor}function f(){var e;if(x.ket=x.cursor,x.find_among_b(C,37)&&(x.bra=x.cursor,(e=x.find_among_b(z,5))&&w()))switch(e){case 1:x.slice_del();break;case 2:x.slice_from("e")}}function v(){var e;if(x.ket=x.cursor,!(e=x.find_among_b(S,51)))return!1;switch(x.bra=x.cursor,e){case 1:if(!m())return!1;x.slice_del();break;case 2:if(!m())return!1;x.slice_del(),x.ket=x.cursor,x.eq_s_b(2,"ic")&&(x.bra=x.cursor,m()&&x.slice_del());break;case 3:if(!m())return!1;x.slice_from("log");break;case 4:if(!m())return!1;x.slice_from("u");break;case 5:if(!m())return!1;x.slice_from("ente");break;case 6:if(!w())return!1;x.slice_del();break;case 7:if(!l())return!1;x.slice_del(),x.ket=x.cursor,e=x.find_among_b(P,4),e&&(x.bra=x.cursor,m()&&(x.slice_del(),1==e&&(x.ket=x.cursor,x.eq_s_b(2,"at")&&(x.bra=x.cursor,m()&&x.slice_del()))));break;case 8:if(!m())return!1;x.slice_del(),x.ket=x.cursor,e=x.find_among_b(F,3),e&&(x.bra=x.cursor,1==e&&m()&&x.slice_del());break;case 9:if(!m())return!1;x.slice_del(),x.ket=x.cursor,x.eq_s_b(2,"at")&&(x.bra=x.cursor,m()&&(x.slice_del(),x.ket=x.cursor,x.eq_s_b(2,"ic")&&(x.bra=x.cursor,m()&&x.slice_del())))}return!0}function b(){var e,r;x.cursor>=k&&(r=x.limit_backward,x.limit_backward=k,x.ket=x.cursor,e=x.find_among_b(W,87),e&&(x.bra=x.cursor,1==e&&x.slice_del()),x.limit_backward=r)}function d(){var e=x.limit-x.cursor;if(x.ket=x.cursor,x.in_grouping_b(y,97,242)&&(x.bra=x.cursor,w()&&(x.slice_del(),x.ket=x.cursor,x.eq_s_b(1,"i")&&(x.bra=x.cursor,w()))))return void x.slice_del();x.cursor=x.limit-e}function _(){d(),x.ket=x.cursor,x.eq_s_b(1,"h")&&(x.bra=x.cursor,x.in_grouping_b(U,99,103)&&w()&&x.slice_del())}var g,p,k,h=[new r("",-1,7),new r("qu",0,6),new r("á",0,1),new r("é",0,2),new r("í",0,3),new r("ó",0,4),new r("ú",0,5)],q=[new r("",-1,3),new r("I",0,1),new r("U",0,2)],C=[new r("la",-1,-1),new r("cela",0,-1),new r("gliela",0,-1),new r("mela",0,-1),new r("tela",0,-1),new r("vela",0,-1),new r("le",-1,-1),new r("cele",6,-1),new r("gliele",6,-1),new r("mele",6,-1),new r("tele",6,-1),new r("vele",6,-1),new r("ne",-1,-1),new r("cene",12,-1),new r("gliene",12,-1),new r("mene",12,-1),new r("sene",12,-1),new r("tene",12,-1),new r("vene",12,-1),new r("ci",-1,-1),new r("li",-1,-1),new r("celi",20,-1),new r("glieli",20,-1),new r("meli",20,-1),new r("teli",20,-1),new r("veli",20,-1),new r("gli",20,-1),new r("mi",-1,-1),new r("si",-1,-1),new r("ti",-1,-1),new r("vi",-1,-1),new r("lo",-1,-1),new r("celo",31,-1),new r("glielo",31,-1),new r("melo",31,-1),new r("telo",31,-1),new r("velo",31,-1)],z=[new r("ando",-1,1),new r("endo",-1,1),new r("ar",-1,2),new r("er",-1,2),new r("ir",-1,2)],P=[new r("ic",-1,-1),new r("abil",-1,-1),new r("os",-1,-1),new r("iv",-1,1)],F=[new r("ic",-1,1),new r("abil",-1,1),new r("iv",-1,1)],S=[new r("ica",-1,1),new r("logia",-1,3),new r("osa",-1,1),new r("ista",-1,1),new r("iva",-1,9),new r("anza",-1,1),new r("enza",-1,5),new r("ice",-1,1),new r("atrice",7,1),new r("iche",-1,1),new r("logie",-1,3),new r("abile",-1,1),new r("ibile",-1,1),new r("usione",-1,4),new r("azione",-1,2),new r("uzione",-1,4),new r("atore",-1,2),new r("ose",-1,1),new r("ante",-1,1),new r("mente",-1,1),new r("amente",19,7),new r("iste",-1,1),new r("ive",-1,9),new r("anze",-1,1),new r("enze",-1,5),new r("ici",-1,1),new r("atrici",25,1),new r("ichi",-1,1),new r("abili",-1,1),new r("ibili",-1,1),new r("ismi",-1,1),new r("usioni",-1,4),new r("azioni",-1,2),new r("uzioni",-1,4),new r("atori",-1,2),new r("osi",-1,1),new r("anti",-1,1),new r("amenti",-1,6),new r("imenti",-1,6),new r("isti",-1,1),new r("ivi",-1,9),new r("ico",-1,1),new r("ismo",-1,1),new r("oso",-1,1),new r("amento",-1,6),new r("imento",-1,6),new r("ivo",-1,9),new r("ità",-1,8),new r("istà",-1,1),new r("istè",-1,1),new r("istì",-1,1)],W=[new r("isca",-1,1),new r("enda",-1,1),new r("ata",-1,1),new r("ita",-1,1),new r("uta",-1,1),new r("ava",-1,1),new r("eva",-1,1),new r("iva",-1,1),new r("erebbe",-1,1),new r("irebbe",-1,1),new r("isce",-1,1),new r("ende",-1,1),new r("are",-1,1),new r("ere",-1,1),new r("ire",-1,1),new r("asse",-1,1),new r("ate",-1,1),new r("avate",16,1),new r("evate",16,1),new r("ivate",16,1),new r("ete",-1,1),new r("erete",20,1),new r("irete",20,1),new r("ite",-1,1),new r("ereste",-1,1),new r("ireste",-1,1),new r("ute",-1,1),new r("erai",-1,1),new r("irai",-1,1),new r("isci",-1,1),new r("endi",-1,1),new r("erei",-1,1),new r("irei",-1,1),new r("assi",-1,1),new r("ati",-1,1),new r("iti",-1,1),new r("eresti",-1,1),new r("iresti",-1,1),new r("uti",-1,1),new r("avi",-1,1),new r("evi",-1,1),new r("ivi",-1,1),new r("isco",-1,1),new r("ando",-1,1),new r("endo",-1,1),new r("Yamo",-1,1),new r("iamo",-1,1),new r("avamo",-1,1),new r("evamo",-1,1),new r("ivamo",-1,1),new r("eremo",-1,1),new r("iremo",-1,1),new r("assimo",-1,1),new r("ammo",-1,1),new r("emmo",-1,1),new r("eremmo",54,1),new r("iremmo",54,1),new r("immo",-1,1),new r("ano",-1,1),new r("iscano",58,1),new r("avano",58,1),new r("evano",58,1),new r("ivano",58,1),new r("eranno",-1,1),new r("iranno",-1,1),new r("ono",-1,1),new r("iscono",65,1),new r("arono",65,1),new r("erono",65,1),new r("irono",65,1),new r("erebbero",-1,1),new r("irebbero",-1,1),new r("assero",-1,1),new r("essero",-1,1),new r("issero",-1,1),new r("ato",-1,1),new r("ito",-1,1),new r("uto",-1,1),new r("avo",-1,1),new r("evo",-1,1),new r("ivo",-1,1),new r("ar",-1,1),new r("ir",-1,1),new r("erà",-1,1),new r("irà",-1,1),new r("erò",-1,1),new r("irò",-1,1)],L=[17,65,16,0,0,0,0,0,0,0,0,0,0,0,0,128,128,8,2,1],y=[17,65,0,0,0,0,0,0,0,0,0,0,0,0,0,128,128,8,2],U=[17],x=new n;this.setCurrent=function(e){x.setCurrent(e)},this.getCurrent=function(){return x.getCurrent()},this.stem=function(){var e=x.cursor;return i(),x.cursor=e,u(),x.limit_backward=e,x.cursor=x.limit,f(),x.cursor=x.limit,v()||(x.cursor=x.limit,b()),x.cursor=x.limit,_(),x.cursor=x.limit_backward,c(),!0}};return function(e){return"function"==typeof e.update?e.update(function(e){return i.setCurrent(e),i.stem(),i.getCurrent()}):(i.setCurrent(e),i.stem(),i.getCurrent())}}(),e.Pipeline.registerFunction(e.it.stemmer,"stemmer-it"),e.it.stopWordFilter=e.generateStopWordFilter("a abbia abbiamo abbiano abbiate ad agl agli ai al all alla alle allo anche avemmo avendo avesse avessero avessi avessimo aveste avesti avete aveva avevamo avevano avevate avevi avevo avrai avranno avrebbe avrebbero avrei avremmo avremo avreste avresti avrete avrà avrò avuta avute avuti avuto c che chi ci coi col come con contro cui da dagl dagli dai dal dall dalla dalle dallo degl degli dei del dell della delle dello di dov dove e ebbe ebbero ebbi ed era erano eravamo eravate eri ero essendo faccia facciamo facciano facciate faccio facemmo facendo facesse facessero facessi facessimo faceste facesti faceva facevamo facevano facevate facevi facevo fai fanno farai faranno farebbe farebbero farei faremmo faremo fareste faresti farete farà farò fece fecero feci fosse fossero fossi fossimo foste fosti fu fui fummo furono gli ha hai hanno ho i il in io l la le lei li lo loro lui ma mi mia mie miei mio ne negl negli nei nel nell nella nelle nello noi non nostra nostre nostri nostro o per perché più quale quanta quante quanti quanto quella quelle quelli quello questa queste questi questo sarai saranno sarebbe sarebbero sarei saremmo saremo sareste saresti sarete sarà sarò se sei si sia siamo siano siate siete sono sta stai stando stanno starai staranno starebbe starebbero starei staremmo staremo stareste staresti starete starà starò stava stavamo stavano stavate stavi stavo stemmo stesse stessero stessi stessimo steste stesti stette stettero stetti stia stiamo stiano stiate sto su sua sue sugl sugli sui sul sull sulla sulle sullo suo suoi ti tra tu tua tue tuo tuoi tutti tutto un una uno vi voi vostra vostre vostri vostro è".split(" ")),e.Pipeline.registerFunction(e.it.stopWordFilter,"stopWordFilter-it")}}); \ No newline at end of file diff --git a/develop/assets/javascripts/lunr/min/lunr.ja.min.js b/develop/assets/javascripts/lunr/min/lunr.ja.min.js new file mode 100644 index 0000000..5f254eb --- /dev/null +++ b/develop/assets/javascripts/lunr/min/lunr.ja.min.js @@ -0,0 +1 @@ +!function(e,r){"function"==typeof define&&define.amd?define(r):"object"==typeof exports?module.exports=r():r()(e.lunr)}(this,function(){return function(e){if(void 0===e)throw new Error("Lunr is not present. Please include / require Lunr before this script.");if(void 0===e.stemmerSupport)throw new Error("Lunr stemmer support is not present. Please include / require Lunr stemmer support before this script.");var r="2"==e.version[0];e.ja=function(){this.pipeline.reset(),this.pipeline.add(e.ja.trimmer,e.ja.stopWordFilter,e.ja.stemmer),r?this.tokenizer=e.ja.tokenizer:(e.tokenizer&&(e.tokenizer=e.ja.tokenizer),this.tokenizerFn&&(this.tokenizerFn=e.ja.tokenizer))};var t=new e.TinySegmenter;e.ja.tokenizer=function(i){var n,o,s,p,a,u,m,l,c,f;if(!arguments.length||null==i||void 0==i)return[];if(Array.isArray(i))return i.map(function(t){return r?new e.Token(t.toLowerCase()):t.toLowerCase()});for(o=i.toString().toLowerCase().replace(/^\s+/,""),n=o.length-1;n>=0;n--)if(/\S/.test(o.charAt(n))){o=o.substring(0,n+1);break}for(a=[],s=o.length,c=0,l=0;c<=s;c++)if(u=o.charAt(c),m=c-l,u.match(/\s/)||c==s){if(m>0)for(p=t.segment(o.slice(l,c)).filter(function(e){return!!e}),f=l,n=0;n=C.limit)break;C.cursor++;continue}break}for(C.cursor=o,C.bra=o,C.eq_s(1,"y")?(C.ket=C.cursor,C.slice_from("Y")):C.cursor=o;;)if(e=C.cursor,C.in_grouping(q,97,232)){if(i=C.cursor,C.bra=i,C.eq_s(1,"i"))C.ket=C.cursor,C.in_grouping(q,97,232)&&(C.slice_from("I"),C.cursor=e);else if(C.cursor=i,C.eq_s(1,"y"))C.ket=C.cursor,C.slice_from("Y"),C.cursor=e;else if(n(e))break}else if(n(e))break}function n(r){return C.cursor=r,r>=C.limit||(C.cursor++,!1)}function o(){_=C.limit,d=_,t()||(_=C.cursor,_<3&&(_=3),t()||(d=C.cursor))}function t(){for(;!C.in_grouping(q,97,232);){if(C.cursor>=C.limit)return!0;C.cursor++}for(;!C.out_grouping(q,97,232);){if(C.cursor>=C.limit)return!0;C.cursor++}return!1}function s(){for(var r;;)if(C.bra=C.cursor,r=C.find_among(p,3))switch(C.ket=C.cursor,r){case 1:C.slice_from("y");break;case 2:C.slice_from("i");break;case 3:if(C.cursor>=C.limit)return;C.cursor++}}function u(){return _<=C.cursor}function c(){return d<=C.cursor}function a(){var r=C.limit-C.cursor;C.find_among_b(g,3)&&(C.cursor=C.limit-r,C.ket=C.cursor,C.cursor>C.limit_backward&&(C.cursor--,C.bra=C.cursor,C.slice_del()))}function l(){var r;w=!1,C.ket=C.cursor,C.eq_s_b(1,"e")&&(C.bra=C.cursor,u()&&(r=C.limit-C.cursor,C.out_grouping_b(q,97,232)&&(C.cursor=C.limit-r,C.slice_del(),w=!0,a())))}function m(){var r;u()&&(r=C.limit-C.cursor,C.out_grouping_b(q,97,232)&&(C.cursor=C.limit-r,C.eq_s_b(3,"gem")||(C.cursor=C.limit-r,C.slice_del(),a())))}function f(){var r,e,i,n,o,t,s=C.limit-C.cursor;if(C.ket=C.cursor,r=C.find_among_b(h,5))switch(C.bra=C.cursor,r){case 1:u()&&C.slice_from("heid");break;case 2:m();break;case 3:u()&&C.out_grouping_b(j,97,232)&&C.slice_del()}if(C.cursor=C.limit-s,l(),C.cursor=C.limit-s,C.ket=C.cursor,C.eq_s_b(4,"heid")&&(C.bra=C.cursor,c()&&(e=C.limit-C.cursor,C.eq_s_b(1,"c")||(C.cursor=C.limit-e,C.slice_del(),C.ket=C.cursor,C.eq_s_b(2,"en")&&(C.bra=C.cursor,m())))),C.cursor=C.limit-s,C.ket=C.cursor,r=C.find_among_b(k,6))switch(C.bra=C.cursor,r){case 1:if(c()){if(C.slice_del(),i=C.limit-C.cursor,C.ket=C.cursor,C.eq_s_b(2,"ig")&&(C.bra=C.cursor,c()&&(n=C.limit-C.cursor,!C.eq_s_b(1,"e")))){C.cursor=C.limit-n,C.slice_del();break}C.cursor=C.limit-i,a()}break;case 2:c()&&(o=C.limit-C.cursor,C.eq_s_b(1,"e")||(C.cursor=C.limit-o,C.slice_del()));break;case 3:c()&&(C.slice_del(),l());break;case 4:c()&&C.slice_del();break;case 5:c()&&w&&C.slice_del()}C.cursor=C.limit-s,C.out_grouping_b(z,73,232)&&(t=C.limit-C.cursor,C.find_among_b(v,4)&&C.out_grouping_b(q,97,232)&&(C.cursor=C.limit-t,C.ket=C.cursor,C.cursor>C.limit_backward&&(C.cursor--,C.bra=C.cursor,C.slice_del())))}var d,_,w,b=[new e("",-1,6),new e("á",0,1),new e("ä",0,1),new e("é",0,2),new e("ë",0,2),new e("í",0,3),new e("ï",0,3),new e("ó",0,4),new e("ö",0,4),new e("ú",0,5),new e("ü",0,5)],p=[new e("",-1,3),new e("I",0,2),new e("Y",0,1)],g=[new e("dd",-1,-1),new e("kk",-1,-1),new e("tt",-1,-1)],h=[new e("ene",-1,2),new e("se",-1,3),new e("en",-1,2),new e("heden",2,1),new e("s",-1,3)],k=[new e("end",-1,1),new e("ig",-1,2),new e("ing",-1,1),new e("lijk",-1,3),new e("baar",-1,4),new e("bar",-1,5)],v=[new e("aa",-1,-1),new e("ee",-1,-1),new e("oo",-1,-1),new e("uu",-1,-1)],q=[17,65,16,1,0,0,0,0,0,0,0,0,0,0,0,0,128],z=[1,0,0,17,65,16,1,0,0,0,0,0,0,0,0,0,0,0,0,128],j=[17,67,16,1,0,0,0,0,0,0,0,0,0,0,0,0,128],C=new i;this.setCurrent=function(r){C.setCurrent(r)},this.getCurrent=function(){return C.getCurrent()},this.stem=function(){var e=C.cursor;return r(),C.cursor=e,o(),C.limit_backward=e,C.cursor=C.limit,f(),C.cursor=C.limit_backward,s(),!0}};return function(r){return"function"==typeof r.update?r.update(function(r){return n.setCurrent(r),n.stem(),n.getCurrent()}):(n.setCurrent(r),n.stem(),n.getCurrent())}}(),r.Pipeline.registerFunction(r.nl.stemmer,"stemmer-nl"),r.nl.stopWordFilter=r.generateStopWordFilter(" aan al alles als altijd andere ben bij daar dan dat de der deze die dit doch doen door dus een eens en er ge geen geweest haar had heb hebben heeft hem het hier hij hoe hun iemand iets ik in is ja je kan kon kunnen maar me meer men met mij mijn moet na naar niet niets nog nu of om omdat onder ons ook op over reeds te tegen toch toen tot u uit uw van veel voor want waren was wat werd wezen wie wil worden wordt zal ze zelf zich zij zijn zo zonder zou".split(" ")),r.Pipeline.registerFunction(r.nl.stopWordFilter,"stopWordFilter-nl")}}); \ No newline at end of file diff --git a/develop/assets/javascripts/lunr/min/lunr.no.min.js b/develop/assets/javascripts/lunr/min/lunr.no.min.js new file mode 100644 index 0000000..92bc7e4 --- /dev/null +++ b/develop/assets/javascripts/lunr/min/lunr.no.min.js @@ -0,0 +1,18 @@ +/*! + * Lunr languages, `Norwegian` language + * https://github.com/MihaiValentin/lunr-languages + * + * Copyright 2014, Mihai Valentin + * http://www.mozilla.org/MPL/ + */ +/*! + * based on + * Snowball JavaScript Library v0.3 + * http://code.google.com/p/urim/ + * http://snowball.tartarus.org/ + * + * Copyright 2010, Oleg Mazko + * http://www.mozilla.org/MPL/ + */ + +!function(e,r){"function"==typeof define&&define.amd?define(r):"object"==typeof exports?module.exports=r():r()(e.lunr)}(this,function(){return function(e){if(void 0===e)throw new Error("Lunr is not present. Please include / require Lunr before this script.");if(void 0===e.stemmerSupport)throw new Error("Lunr stemmer support is not present. Please include / require Lunr stemmer support before this script.");e.no=function(){this.pipeline.reset(),this.pipeline.add(e.no.trimmer,e.no.stopWordFilter,e.no.stemmer),this.searchPipeline&&(this.searchPipeline.reset(),this.searchPipeline.add(e.no.stemmer))},e.no.wordCharacters="A-Za-zªºÀ-ÖØ-öø-ʸˠ-ˤᴀ-ᴥᴬ-ᵜᵢ-ᵥᵫ-ᵷᵹ-ᶾḀ-ỿⁱⁿₐ-ₜKÅℲⅎⅠ-ↈⱠ-ⱿꜢ-ꞇꞋ-ꞭꞰ-ꞷꟷ-ꟿꬰ-ꭚꭜ-ꭤff-stA-Za-z",e.no.trimmer=e.trimmerSupport.generateTrimmer(e.no.wordCharacters),e.Pipeline.registerFunction(e.no.trimmer,"trimmer-no"),e.no.stemmer=function(){var r=e.stemmerSupport.Among,n=e.stemmerSupport.SnowballProgram,i=new function(){function e(){var e,r=w.cursor+3;if(a=w.limit,0<=r||r<=w.limit){for(s=r;;){if(e=w.cursor,w.in_grouping(d,97,248)){w.cursor=e;break}if(e>=w.limit)return;w.cursor=e+1}for(;!w.out_grouping(d,97,248);){if(w.cursor>=w.limit)return;w.cursor++}a=w.cursor,a=a&&(r=w.limit_backward,w.limit_backward=a,w.ket=w.cursor,e=w.find_among_b(m,29),w.limit_backward=r,e))switch(w.bra=w.cursor,e){case 1:w.slice_del();break;case 2:n=w.limit-w.cursor,w.in_grouping_b(c,98,122)?w.slice_del():(w.cursor=w.limit-n,w.eq_s_b(1,"k")&&w.out_grouping_b(d,97,248)&&w.slice_del());break;case 3:w.slice_from("er")}}function t(){var e,r=w.limit-w.cursor;w.cursor>=a&&(e=w.limit_backward,w.limit_backward=a,w.ket=w.cursor,w.find_among_b(u,2)?(w.bra=w.cursor,w.limit_backward=e,w.cursor=w.limit-r,w.cursor>w.limit_backward&&(w.cursor--,w.bra=w.cursor,w.slice_del())):w.limit_backward=e)}function o(){var e,r;w.cursor>=a&&(r=w.limit_backward,w.limit_backward=a,w.ket=w.cursor,e=w.find_among_b(l,11),e?(w.bra=w.cursor,w.limit_backward=r,1==e&&w.slice_del()):w.limit_backward=r)}var s,a,m=[new r("a",-1,1),new r("e",-1,1),new r("ede",1,1),new r("ande",1,1),new r("ende",1,1),new r("ane",1,1),new r("ene",1,1),new r("hetene",6,1),new r("erte",1,3),new r("en",-1,1),new r("heten",9,1),new r("ar",-1,1),new r("er",-1,1),new r("heter",12,1),new r("s",-1,2),new r("as",14,1),new r("es",14,1),new r("edes",16,1),new r("endes",16,1),new r("enes",16,1),new r("hetenes",19,1),new r("ens",14,1),new r("hetens",21,1),new r("ers",14,1),new r("ets",14,1),new r("et",-1,1),new r("het",25,1),new r("ert",-1,3),new r("ast",-1,1)],u=[new r("dt",-1,-1),new r("vt",-1,-1)],l=[new r("leg",-1,1),new r("eleg",0,1),new r("ig",-1,1),new r("eig",2,1),new r("lig",2,1),new r("elig",4,1),new r("els",-1,1),new r("lov",-1,1),new r("elov",7,1),new r("slov",7,1),new r("hetslov",9,1)],d=[17,65,16,1,0,0,0,0,0,0,0,0,0,0,0,0,48,0,128],c=[119,125,149,1],w=new n;this.setCurrent=function(e){w.setCurrent(e)},this.getCurrent=function(){return w.getCurrent()},this.stem=function(){var r=w.cursor;return e(),w.limit_backward=r,w.cursor=w.limit,i(),w.cursor=w.limit,t(),w.cursor=w.limit,o(),!0}};return function(e){return"function"==typeof e.update?e.update(function(e){return i.setCurrent(e),i.stem(),i.getCurrent()}):(i.setCurrent(e),i.stem(),i.getCurrent())}}(),e.Pipeline.registerFunction(e.no.stemmer,"stemmer-no"),e.no.stopWordFilter=e.generateStopWordFilter("alle at av bare begge ble blei bli blir blitt både båe da de deg dei deim deira deires dem den denne der dere deres det dette di din disse ditt du dykk dykkar då eg ein eit eitt eller elles en enn er et ett etter for fordi fra før ha hadde han hans har hennar henne hennes her hjå ho hoe honom hoss hossen hun hva hvem hver hvilke hvilken hvis hvor hvordan hvorfor i ikke ikkje ikkje ingen ingi inkje inn inni ja jeg kan kom korleis korso kun kunne kva kvar kvarhelst kven kvi kvifor man mange me med medan meg meget mellom men mi min mine mitt mot mykje ned no noe noen noka noko nokon nokor nokre nå når og også om opp oss over på samme seg selv si si sia sidan siden sin sine sitt sjøl skal skulle slik so som som somme somt så sånn til um upp ut uten var vart varte ved vere verte vi vil ville vore vors vort vår være være vært å".split(" ")),e.Pipeline.registerFunction(e.no.stopWordFilter,"stopWordFilter-no")}}); \ No newline at end of file diff --git a/develop/assets/javascripts/lunr/min/lunr.pt.min.js b/develop/assets/javascripts/lunr/min/lunr.pt.min.js new file mode 100644 index 0000000..6c16996 --- /dev/null +++ b/develop/assets/javascripts/lunr/min/lunr.pt.min.js @@ -0,0 +1,18 @@ +/*! + * Lunr languages, `Portuguese` language + * https://github.com/MihaiValentin/lunr-languages + * + * Copyright 2014, Mihai Valentin + * http://www.mozilla.org/MPL/ + */ +/*! + * based on + * Snowball JavaScript Library v0.3 + * http://code.google.com/p/urim/ + * http://snowball.tartarus.org/ + * + * Copyright 2010, Oleg Mazko + * http://www.mozilla.org/MPL/ + */ + +!function(e,r){"function"==typeof define&&define.amd?define(r):"object"==typeof exports?module.exports=r():r()(e.lunr)}(this,function(){return function(e){if(void 0===e)throw new Error("Lunr is not present. Please include / require Lunr before this script.");if(void 0===e.stemmerSupport)throw new Error("Lunr stemmer support is not present. Please include / require Lunr stemmer support before this script.");e.pt=function(){this.pipeline.reset(),this.pipeline.add(e.pt.trimmer,e.pt.stopWordFilter,e.pt.stemmer),this.searchPipeline&&(this.searchPipeline.reset(),this.searchPipeline.add(e.pt.stemmer))},e.pt.wordCharacters="A-Za-zªºÀ-ÖØ-öø-ʸˠ-ˤᴀ-ᴥᴬ-ᵜᵢ-ᵥᵫ-ᵷᵹ-ᶾḀ-ỿⁱⁿₐ-ₜKÅℲⅎⅠ-ↈⱠ-ⱿꜢ-ꞇꞋ-ꞭꞰ-ꞷꟷ-ꟿꬰ-ꭚꭜ-ꭤff-stA-Za-z",e.pt.trimmer=e.trimmerSupport.generateTrimmer(e.pt.wordCharacters),e.Pipeline.registerFunction(e.pt.trimmer,"trimmer-pt"),e.pt.stemmer=function(){var r=e.stemmerSupport.Among,s=e.stemmerSupport.SnowballProgram,n=new function(){function e(){for(var e;;){if(z.bra=z.cursor,e=z.find_among(k,3))switch(z.ket=z.cursor,e){case 1:z.slice_from("a~");continue;case 2:z.slice_from("o~");continue;case 3:if(z.cursor>=z.limit)break;z.cursor++;continue}break}}function n(){if(z.out_grouping(y,97,250)){for(;!z.in_grouping(y,97,250);){if(z.cursor>=z.limit)return!0;z.cursor++}return!1}return!0}function i(){if(z.in_grouping(y,97,250))for(;!z.out_grouping(y,97,250);){if(z.cursor>=z.limit)return!1;z.cursor++}return g=z.cursor,!0}function o(){var e,r,s=z.cursor;if(z.in_grouping(y,97,250))if(e=z.cursor,n()){if(z.cursor=e,i())return}else g=z.cursor;if(z.cursor=s,z.out_grouping(y,97,250)){if(r=z.cursor,n()){if(z.cursor=r,!z.in_grouping(y,97,250)||z.cursor>=z.limit)return;z.cursor++}g=z.cursor}}function t(){for(;!z.in_grouping(y,97,250);){if(z.cursor>=z.limit)return!1;z.cursor++}for(;!z.out_grouping(y,97,250);){if(z.cursor>=z.limit)return!1;z.cursor++}return!0}function a(){var e=z.cursor;g=z.limit,b=g,h=g,o(),z.cursor=e,t()&&(b=z.cursor,t()&&(h=z.cursor))}function u(){for(var e;;){if(z.bra=z.cursor,e=z.find_among(q,3))switch(z.ket=z.cursor,e){case 1:z.slice_from("ã");continue;case 2:z.slice_from("õ");continue;case 3:if(z.cursor>=z.limit)break;z.cursor++;continue}break}}function w(){return g<=z.cursor}function m(){return b<=z.cursor}function c(){return h<=z.cursor}function l(){var e;if(z.ket=z.cursor,!(e=z.find_among_b(F,45)))return!1;switch(z.bra=z.cursor,e){case 1:if(!c())return!1;z.slice_del();break;case 2:if(!c())return!1;z.slice_from("log");break;case 3:if(!c())return!1;z.slice_from("u");break;case 4:if(!c())return!1;z.slice_from("ente");break;case 5:if(!m())return!1;z.slice_del(),z.ket=z.cursor,e=z.find_among_b(j,4),e&&(z.bra=z.cursor,c()&&(z.slice_del(),1==e&&(z.ket=z.cursor,z.eq_s_b(2,"at")&&(z.bra=z.cursor,c()&&z.slice_del()))));break;case 6:if(!c())return!1;z.slice_del(),z.ket=z.cursor,e=z.find_among_b(C,3),e&&(z.bra=z.cursor,1==e&&c()&&z.slice_del());break;case 7:if(!c())return!1;z.slice_del(),z.ket=z.cursor,e=z.find_among_b(P,3),e&&(z.bra=z.cursor,1==e&&c()&&z.slice_del());break;case 8:if(!c())return!1;z.slice_del(),z.ket=z.cursor,z.eq_s_b(2,"at")&&(z.bra=z.cursor,c()&&z.slice_del());break;case 9:if(!w()||!z.eq_s_b(1,"e"))return!1;z.slice_from("ir")}return!0}function f(){var e,r;if(z.cursor>=g){if(r=z.limit_backward,z.limit_backward=g,z.ket=z.cursor,e=z.find_among_b(S,120))return z.bra=z.cursor,1==e&&z.slice_del(),z.limit_backward=r,!0;z.limit_backward=r}return!1}function d(){var e;z.ket=z.cursor,(e=z.find_among_b(W,7))&&(z.bra=z.cursor,1==e&&w()&&z.slice_del())}function v(e,r){if(z.eq_s_b(1,e)){z.bra=z.cursor;var s=z.limit-z.cursor;if(z.eq_s_b(1,r))return z.cursor=z.limit-s,w()&&z.slice_del(),!1}return!0}function p(){var e;if(z.ket=z.cursor,e=z.find_among_b(L,4))switch(z.bra=z.cursor,e){case 1:w()&&(z.slice_del(),z.ket=z.cursor,z.limit-z.cursor,v("u","g")&&v("i","c"));break;case 2:z.slice_from("c")}}function _(){if(!l()&&(z.cursor=z.limit,!f()))return z.cursor=z.limit,void d();z.cursor=z.limit,z.ket=z.cursor,z.eq_s_b(1,"i")&&(z.bra=z.cursor,z.eq_s_b(1,"c")&&(z.cursor=z.limit,w()&&z.slice_del()))}var h,b,g,k=[new r("",-1,3),new r("ã",0,1),new r("õ",0,2)],q=[new r("",-1,3),new r("a~",0,1),new r("o~",0,2)],j=[new r("ic",-1,-1),new r("ad",-1,-1),new r("os",-1,-1),new r("iv",-1,1)],C=[new r("ante",-1,1),new r("avel",-1,1),new r("ível",-1,1)],P=[new r("ic",-1,1),new r("abil",-1,1),new r("iv",-1,1)],F=[new r("ica",-1,1),new r("ância",-1,1),new r("ência",-1,4),new r("ira",-1,9),new r("adora",-1,1),new r("osa",-1,1),new r("ista",-1,1),new r("iva",-1,8),new r("eza",-1,1),new r("logía",-1,2),new r("idade",-1,7),new r("ante",-1,1),new r("mente",-1,6),new r("amente",12,5),new r("ável",-1,1),new r("ível",-1,1),new r("ución",-1,3),new r("ico",-1,1),new r("ismo",-1,1),new r("oso",-1,1),new r("amento",-1,1),new r("imento",-1,1),new r("ivo",-1,8),new r("aça~o",-1,1),new r("ador",-1,1),new r("icas",-1,1),new r("ências",-1,4),new r("iras",-1,9),new r("adoras",-1,1),new r("osas",-1,1),new r("istas",-1,1),new r("ivas",-1,8),new r("ezas",-1,1),new r("logías",-1,2),new r("idades",-1,7),new r("uciones",-1,3),new r("adores",-1,1),new r("antes",-1,1),new r("aço~es",-1,1),new r("icos",-1,1),new r("ismos",-1,1),new r("osos",-1,1),new r("amentos",-1,1),new r("imentos",-1,1),new r("ivos",-1,8)],S=[new r("ada",-1,1),new r("ida",-1,1),new r("ia",-1,1),new r("aria",2,1),new r("eria",2,1),new r("iria",2,1),new r("ara",-1,1),new r("era",-1,1),new r("ira",-1,1),new r("ava",-1,1),new r("asse",-1,1),new r("esse",-1,1),new r("isse",-1,1),new r("aste",-1,1),new r("este",-1,1),new r("iste",-1,1),new r("ei",-1,1),new r("arei",16,1),new r("erei",16,1),new r("irei",16,1),new r("am",-1,1),new r("iam",20,1),new r("ariam",21,1),new r("eriam",21,1),new r("iriam",21,1),new r("aram",20,1),new r("eram",20,1),new r("iram",20,1),new r("avam",20,1),new r("em",-1,1),new r("arem",29,1),new r("erem",29,1),new r("irem",29,1),new r("assem",29,1),new r("essem",29,1),new r("issem",29,1),new r("ado",-1,1),new r("ido",-1,1),new r("ando",-1,1),new r("endo",-1,1),new r("indo",-1,1),new r("ara~o",-1,1),new r("era~o",-1,1),new r("ira~o",-1,1),new r("ar",-1,1),new r("er",-1,1),new r("ir",-1,1),new r("as",-1,1),new r("adas",47,1),new r("idas",47,1),new r("ias",47,1),new r("arias",50,1),new r("erias",50,1),new r("irias",50,1),new r("aras",47,1),new r("eras",47,1),new r("iras",47,1),new r("avas",47,1),new r("es",-1,1),new r("ardes",58,1),new r("erdes",58,1),new r("irdes",58,1),new r("ares",58,1),new r("eres",58,1),new r("ires",58,1),new r("asses",58,1),new r("esses",58,1),new r("isses",58,1),new r("astes",58,1),new r("estes",58,1),new r("istes",58,1),new r("is",-1,1),new r("ais",71,1),new r("eis",71,1),new r("areis",73,1),new r("ereis",73,1),new r("ireis",73,1),new r("áreis",73,1),new r("éreis",73,1),new r("íreis",73,1),new r("ásseis",73,1),new r("ésseis",73,1),new r("ísseis",73,1),new r("áveis",73,1),new r("íeis",73,1),new r("aríeis",84,1),new r("eríeis",84,1),new r("iríeis",84,1),new r("ados",-1,1),new r("idos",-1,1),new r("amos",-1,1),new r("áramos",90,1),new r("éramos",90,1),new r("íramos",90,1),new r("ávamos",90,1),new r("íamos",90,1),new r("aríamos",95,1),new r("eríamos",95,1),new r("iríamos",95,1),new r("emos",-1,1),new r("aremos",99,1),new r("eremos",99,1),new r("iremos",99,1),new r("ássemos",99,1),new r("êssemos",99,1),new r("íssemos",99,1),new r("imos",-1,1),new r("armos",-1,1),new r("ermos",-1,1),new r("irmos",-1,1),new r("ámos",-1,1),new r("arás",-1,1),new r("erás",-1,1),new r("irás",-1,1),new r("eu",-1,1),new r("iu",-1,1),new r("ou",-1,1),new r("ará",-1,1),new r("erá",-1,1),new r("irá",-1,1)],W=[new r("a",-1,1),new r("i",-1,1),new r("o",-1,1),new r("os",-1,1),new r("á",-1,1),new r("í",-1,1),new r("ó",-1,1)],L=[new r("e",-1,1),new r("ç",-1,2),new r("é",-1,1),new r("ê",-1,1)],y=[17,65,16,0,0,0,0,0,0,0,0,0,0,0,0,0,3,19,12,2],z=new s;this.setCurrent=function(e){z.setCurrent(e)},this.getCurrent=function(){return z.getCurrent()},this.stem=function(){var r=z.cursor;return e(),z.cursor=r,a(),z.limit_backward=r,z.cursor=z.limit,_(),z.cursor=z.limit,p(),z.cursor=z.limit_backward,u(),!0}};return function(e){return"function"==typeof e.update?e.update(function(e){return n.setCurrent(e),n.stem(),n.getCurrent()}):(n.setCurrent(e),n.stem(),n.getCurrent())}}(),e.Pipeline.registerFunction(e.pt.stemmer,"stemmer-pt"),e.pt.stopWordFilter=e.generateStopWordFilter("a ao aos aquela aquelas aquele aqueles aquilo as até com como da das de dela delas dele deles depois do dos e ela elas ele eles em entre era eram essa essas esse esses esta estamos estas estava estavam este esteja estejam estejamos estes esteve estive estivemos estiver estivera estiveram estiverem estivermos estivesse estivessem estivéramos estivéssemos estou está estávamos estão eu foi fomos for fora foram forem formos fosse fossem fui fôramos fôssemos haja hajam hajamos havemos hei houve houvemos houver houvera houveram houverei houverem houveremos houveria houveriam houvermos houverá houverão houveríamos houvesse houvessem houvéramos houvéssemos há hão isso isto já lhe lhes mais mas me mesmo meu meus minha minhas muito na nas nem no nos nossa nossas nosso nossos num numa não nós o os ou para pela pelas pelo pelos por qual quando que quem se seja sejam sejamos sem serei seremos seria seriam será serão seríamos seu seus somos sou sua suas são só também te tem temos tenha tenham tenhamos tenho terei teremos teria teriam terá terão teríamos teu teus teve tinha tinham tive tivemos tiver tivera tiveram tiverem tivermos tivesse tivessem tivéramos tivéssemos tu tua tuas tém tínhamos um uma você vocês vos à às éramos".split(" ")),e.Pipeline.registerFunction(e.pt.stopWordFilter,"stopWordFilter-pt")}}); \ No newline at end of file diff --git a/develop/assets/javascripts/lunr/min/lunr.ro.min.js b/develop/assets/javascripts/lunr/min/lunr.ro.min.js new file mode 100644 index 0000000..7277140 --- /dev/null +++ b/develop/assets/javascripts/lunr/min/lunr.ro.min.js @@ -0,0 +1,18 @@ +/*! + * Lunr languages, `Romanian` language + * https://github.com/MihaiValentin/lunr-languages + * + * Copyright 2014, Mihai Valentin + * http://www.mozilla.org/MPL/ + */ +/*! + * based on + * Snowball JavaScript Library v0.3 + * http://code.google.com/p/urim/ + * http://snowball.tartarus.org/ + * + * Copyright 2010, Oleg Mazko + * http://www.mozilla.org/MPL/ + */ + +!function(e,i){"function"==typeof define&&define.amd?define(i):"object"==typeof exports?module.exports=i():i()(e.lunr)}(this,function(){return function(e){if(void 0===e)throw new Error("Lunr is not present. Please include / require Lunr before this script.");if(void 0===e.stemmerSupport)throw new Error("Lunr stemmer support is not present. Please include / require Lunr stemmer support before this script.");e.ro=function(){this.pipeline.reset(),this.pipeline.add(e.ro.trimmer,e.ro.stopWordFilter,e.ro.stemmer),this.searchPipeline&&(this.searchPipeline.reset(),this.searchPipeline.add(e.ro.stemmer))},e.ro.wordCharacters="A-Za-zªºÀ-ÖØ-öø-ʸˠ-ˤᴀ-ᴥᴬ-ᵜᵢ-ᵥᵫ-ᵷᵹ-ᶾḀ-ỿⁱⁿₐ-ₜKÅℲⅎⅠ-ↈⱠ-ⱿꜢ-ꞇꞋ-ꞭꞰ-ꞷꟷ-ꟿꬰ-ꭚꭜ-ꭤff-stA-Za-z",e.ro.trimmer=e.trimmerSupport.generateTrimmer(e.ro.wordCharacters),e.Pipeline.registerFunction(e.ro.trimmer,"trimmer-ro"),e.ro.stemmer=function(){var i=e.stemmerSupport.Among,r=e.stemmerSupport.SnowballProgram,n=new function(){function e(e,i){L.eq_s(1,e)&&(L.ket=L.cursor,L.in_grouping(W,97,259)&&L.slice_from(i))}function n(){for(var i,r;;){if(i=L.cursor,L.in_grouping(W,97,259)&&(r=L.cursor,L.bra=r,e("u","U"),L.cursor=r,e("i","I")),L.cursor=i,L.cursor>=L.limit)break;L.cursor++}}function t(){if(L.out_grouping(W,97,259)){for(;!L.in_grouping(W,97,259);){if(L.cursor>=L.limit)return!0;L.cursor++}return!1}return!0}function a(){if(L.in_grouping(W,97,259))for(;!L.out_grouping(W,97,259);){if(L.cursor>=L.limit)return!0;L.cursor++}return!1}function o(){var e,i,r=L.cursor;if(L.in_grouping(W,97,259)){if(e=L.cursor,!t())return void(h=L.cursor);if(L.cursor=e,!a())return void(h=L.cursor)}L.cursor=r,L.out_grouping(W,97,259)&&(i=L.cursor,t()&&(L.cursor=i,L.in_grouping(W,97,259)&&L.cursor=L.limit)return!1;L.cursor++}for(;!L.out_grouping(W,97,259);){if(L.cursor>=L.limit)return!1;L.cursor++}return!0}function c(){var e=L.cursor;h=L.limit,k=h,g=h,o(),L.cursor=e,u()&&(k=L.cursor,u()&&(g=L.cursor))}function s(){for(var e;;){if(L.bra=L.cursor,e=L.find_among(z,3))switch(L.ket=L.cursor,e){case 1:L.slice_from("i");continue;case 2:L.slice_from("u");continue;case 3:if(L.cursor>=L.limit)break;L.cursor++;continue}break}}function w(){return h<=L.cursor}function m(){return k<=L.cursor}function l(){return g<=L.cursor}function f(){var e,i;if(L.ket=L.cursor,(e=L.find_among_b(C,16))&&(L.bra=L.cursor,m()))switch(e){case 1:L.slice_del();break;case 2:L.slice_from("a");break;case 3:L.slice_from("e");break;case 4:L.slice_from("i");break;case 5:i=L.limit-L.cursor,L.eq_s_b(2,"ab")||(L.cursor=L.limit-i,L.slice_from("i"));break;case 6:L.slice_from("at");break;case 7:L.slice_from("aţi")}}function p(){var e,i=L.limit-L.cursor;if(L.ket=L.cursor,(e=L.find_among_b(P,46))&&(L.bra=L.cursor,m())){switch(e){case 1:L.slice_from("abil");break;case 2:L.slice_from("ibil");break;case 3:L.slice_from("iv");break;case 4:L.slice_from("ic");break;case 5:L.slice_from("at");break;case 6:L.slice_from("it")}return _=!0,L.cursor=L.limit-i,!0}return!1}function d(){var e,i;for(_=!1;;)if(i=L.limit-L.cursor,!p()){L.cursor=L.limit-i;break}if(L.ket=L.cursor,(e=L.find_among_b(F,62))&&(L.bra=L.cursor,l())){switch(e){case 1:L.slice_del();break;case 2:L.eq_s_b(1,"ţ")&&(L.bra=L.cursor,L.slice_from("t"));break;case 3:L.slice_from("ist")}_=!0}}function b(){var e,i,r;if(L.cursor>=h){if(i=L.limit_backward,L.limit_backward=h,L.ket=L.cursor,e=L.find_among_b(q,94))switch(L.bra=L.cursor,e){case 1:if(r=L.limit-L.cursor,!L.out_grouping_b(W,97,259)&&(L.cursor=L.limit-r,!L.eq_s_b(1,"u")))break;case 2:L.slice_del()}L.limit_backward=i}}function v(){var e;L.ket=L.cursor,(e=L.find_among_b(S,5))&&(L.bra=L.cursor,w()&&1==e&&L.slice_del())}var _,g,k,h,z=[new i("",-1,3),new i("I",0,1),new i("U",0,2)],C=[new i("ea",-1,3),new i("aţia",-1,7),new i("aua",-1,2),new i("iua",-1,4),new i("aţie",-1,7),new i("ele",-1,3),new i("ile",-1,5),new i("iile",6,4),new i("iei",-1,4),new i("atei",-1,6),new i("ii",-1,4),new i("ului",-1,1),new i("ul",-1,1),new i("elor",-1,3),new i("ilor",-1,4),new i("iilor",14,4)],P=[new i("icala",-1,4),new i("iciva",-1,4),new i("ativa",-1,5),new i("itiva",-1,6),new i("icale",-1,4),new i("aţiune",-1,5),new i("iţiune",-1,6),new i("atoare",-1,5),new i("itoare",-1,6),new i("ătoare",-1,5),new i("icitate",-1,4),new i("abilitate",-1,1),new i("ibilitate",-1,2),new i("ivitate",-1,3),new i("icive",-1,4),new i("ative",-1,5),new i("itive",-1,6),new i("icali",-1,4),new i("atori",-1,5),new i("icatori",18,4),new i("itori",-1,6),new i("ători",-1,5),new i("icitati",-1,4),new i("abilitati",-1,1),new i("ivitati",-1,3),new i("icivi",-1,4),new i("ativi",-1,5),new i("itivi",-1,6),new i("icităi",-1,4),new i("abilităi",-1,1),new i("ivităi",-1,3),new i("icităţi",-1,4),new i("abilităţi",-1,1),new i("ivităţi",-1,3),new i("ical",-1,4),new i("ator",-1,5),new i("icator",35,4),new i("itor",-1,6),new i("ător",-1,5),new i("iciv",-1,4),new i("ativ",-1,5),new i("itiv",-1,6),new i("icală",-1,4),new i("icivă",-1,4),new i("ativă",-1,5),new i("itivă",-1,6)],F=[new i("ica",-1,1),new i("abila",-1,1),new i("ibila",-1,1),new i("oasa",-1,1),new i("ata",-1,1),new i("ita",-1,1),new i("anta",-1,1),new i("ista",-1,3),new i("uta",-1,1),new i("iva",-1,1),new i("ic",-1,1),new i("ice",-1,1),new i("abile",-1,1),new i("ibile",-1,1),new i("isme",-1,3),new i("iune",-1,2),new i("oase",-1,1),new i("ate",-1,1),new i("itate",17,1),new i("ite",-1,1),new i("ante",-1,1),new i("iste",-1,3),new i("ute",-1,1),new i("ive",-1,1),new i("ici",-1,1),new i("abili",-1,1),new i("ibili",-1,1),new i("iuni",-1,2),new i("atori",-1,1),new i("osi",-1,1),new i("ati",-1,1),new i("itati",30,1),new i("iti",-1,1),new i("anti",-1,1),new i("isti",-1,3),new i("uti",-1,1),new i("işti",-1,3),new i("ivi",-1,1),new i("ităi",-1,1),new i("oşi",-1,1),new i("ităţi",-1,1),new i("abil",-1,1),new i("ibil",-1,1),new i("ism",-1,3),new i("ator",-1,1),new i("os",-1,1),new i("at",-1,1),new i("it",-1,1),new i("ant",-1,1),new i("ist",-1,3),new i("ut",-1,1),new i("iv",-1,1),new i("ică",-1,1),new i("abilă",-1,1),new i("ibilă",-1,1),new i("oasă",-1,1),new i("ată",-1,1),new i("ită",-1,1),new i("antă",-1,1),new i("istă",-1,3),new i("ută",-1,1),new i("ivă",-1,1)],q=[new i("ea",-1,1),new i("ia",-1,1),new i("esc",-1,1),new i("ăsc",-1,1),new i("ind",-1,1),new i("ând",-1,1),new i("are",-1,1),new i("ere",-1,1),new i("ire",-1,1),new i("âre",-1,1),new i("se",-1,2),new i("ase",10,1),new i("sese",10,2),new i("ise",10,1),new i("use",10,1),new i("âse",10,1),new i("eşte",-1,1),new i("ăşte",-1,1),new i("eze",-1,1),new i("ai",-1,1),new i("eai",19,1),new i("iai",19,1),new i("sei",-1,2),new i("eşti",-1,1),new i("ăşti",-1,1),new i("ui",-1,1),new i("ezi",-1,1),new i("âi",-1,1),new i("aşi",-1,1),new i("seşi",-1,2),new i("aseşi",29,1),new i("seseşi",29,2),new i("iseşi",29,1),new i("useşi",29,1),new i("âseşi",29,1),new i("işi",-1,1),new i("uşi",-1,1),new i("âşi",-1,1),new i("aţi",-1,2),new i("eaţi",38,1),new i("iaţi",38,1),new i("eţi",-1,2),new i("iţi",-1,2),new i("âţi",-1,2),new i("arăţi",-1,1),new i("serăţi",-1,2),new i("aserăţi",45,1),new i("seserăţi",45,2),new i("iserăţi",45,1),new i("userăţi",45,1),new i("âserăţi",45,1),new i("irăţi",-1,1),new i("urăţi",-1,1),new i("ârăţi",-1,1),new i("am",-1,1),new i("eam",54,1),new i("iam",54,1),new i("em",-1,2),new i("asem",57,1),new i("sesem",57,2),new i("isem",57,1),new i("usem",57,1),new i("âsem",57,1),new i("im",-1,2),new i("âm",-1,2),new i("ăm",-1,2),new i("arăm",65,1),new i("serăm",65,2),new i("aserăm",67,1),new i("seserăm",67,2),new i("iserăm",67,1),new i("userăm",67,1),new i("âserăm",67,1),new i("irăm",65,1),new i("urăm",65,1),new i("ârăm",65,1),new i("au",-1,1),new i("eau",76,1),new i("iau",76,1),new i("indu",-1,1),new i("ându",-1,1),new i("ez",-1,1),new i("ească",-1,1),new i("ară",-1,1),new i("seră",-1,2),new i("aseră",84,1),new i("seseră",84,2),new i("iseră",84,1),new i("useră",84,1),new i("âseră",84,1),new i("iră",-1,1),new i("ură",-1,1),new i("âră",-1,1),new i("ează",-1,1)],S=[new i("a",-1,1),new i("e",-1,1),new i("ie",1,1),new i("i",-1,1),new i("ă",-1,1)],W=[17,65,16,0,0,0,0,0,0,0,0,0,0,0,0,0,2,32,0,0,4],L=new r;this.setCurrent=function(e){L.setCurrent(e)},this.getCurrent=function(){return L.getCurrent()},this.stem=function(){var e=L.cursor;return n(),L.cursor=e,c(),L.limit_backward=e,L.cursor=L.limit,f(),L.cursor=L.limit,d(),L.cursor=L.limit,_||(L.cursor=L.limit,b(),L.cursor=L.limit),v(),L.cursor=L.limit_backward,s(),!0}};return function(e){return"function"==typeof e.update?e.update(function(e){return n.setCurrent(e),n.stem(),n.getCurrent()}):(n.setCurrent(e),n.stem(),n.getCurrent())}}(),e.Pipeline.registerFunction(e.ro.stemmer,"stemmer-ro"),e.ro.stopWordFilter=e.generateStopWordFilter("acea aceasta această aceea acei aceia acel acela acele acelea acest acesta aceste acestea aceşti aceştia acolo acord acum ai aia aibă aici al ale alea altceva altcineva am ar are asemenea asta astea astăzi asupra au avea avem aveţi azi aş aşadar aţi bine bucur bună ca care caut ce cel ceva chiar cinci cine cineva contra cu cum cumva curând curînd când cât câte câtva câţi cînd cît cîte cîtva cîţi că căci cărei căror cărui către da dacă dar datorită dată dau de deci deja deoarece departe deşi din dinaintea dintr- dintre doi doilea două drept după dă ea ei el ele eram este eu eşti face fata fi fie fiecare fii fim fiu fiţi frumos fără graţie halbă iar ieri la le li lor lui lângă lîngă mai mea mei mele mereu meu mi mie mine mult multă mulţi mulţumesc mâine mîine mă ne nevoie nici nicăieri nimeni nimeri nimic nişte noastre noastră noi noroc nostru nouă noştri nu opt ori oricare orice oricine oricum oricând oricât oricînd oricît oriunde patra patru patrulea pe pentru peste pic poate pot prea prima primul prin puţin puţina puţină până pînă rog sa sale sau se spate spre sub sunt suntem sunteţi sută sînt sîntem sînteţi să săi său ta tale te timp tine toate toată tot totuşi toţi trei treia treilea tu tăi tău un una unde undeva unei uneia unele uneori unii unor unora unu unui unuia unul vi voastre voastră voi vostru vouă voştri vreme vreo vreun vă zece zero zi zice îi îl îmi împotriva în înainte înaintea încotro încât încît între întrucât întrucît îţi ăla ălea ăsta ăstea ăştia şapte şase şi ştiu ţi ţie".split(" ")),e.Pipeline.registerFunction(e.ro.stopWordFilter,"stopWordFilter-ro")}}); \ No newline at end of file diff --git a/develop/assets/javascripts/lunr/min/lunr.ru.min.js b/develop/assets/javascripts/lunr/min/lunr.ru.min.js new file mode 100644 index 0000000..186cc48 --- /dev/null +++ b/develop/assets/javascripts/lunr/min/lunr.ru.min.js @@ -0,0 +1,18 @@ +/*! + * Lunr languages, `Russian` language + * https://github.com/MihaiValentin/lunr-languages + * + * Copyright 2014, Mihai Valentin + * http://www.mozilla.org/MPL/ + */ +/*! + * based on + * Snowball JavaScript Library v0.3 + * http://code.google.com/p/urim/ + * http://snowball.tartarus.org/ + * + * Copyright 2010, Oleg Mazko + * http://www.mozilla.org/MPL/ + */ + +!function(e,n){"function"==typeof define&&define.amd?define(n):"object"==typeof exports?module.exports=n():n()(e.lunr)}(this,function(){return function(e){if(void 0===e)throw new Error("Lunr is not present. Please include / require Lunr before this script.");if(void 0===e.stemmerSupport)throw new Error("Lunr stemmer support is not present. Please include / require Lunr stemmer support before this script.");e.ru=function(){this.pipeline.reset(),this.pipeline.add(e.ru.trimmer,e.ru.stopWordFilter,e.ru.stemmer),this.searchPipeline&&(this.searchPipeline.reset(),this.searchPipeline.add(e.ru.stemmer))},e.ru.wordCharacters="Ѐ-҄҇-ԯᴫᵸⷠ-ⷿꙀ-ꚟ︮︯",e.ru.trimmer=e.trimmerSupport.generateTrimmer(e.ru.wordCharacters),e.Pipeline.registerFunction(e.ru.trimmer,"trimmer-ru"),e.ru.stemmer=function(){var n=e.stemmerSupport.Among,r=e.stemmerSupport.SnowballProgram,t=new function(){function e(){for(;!W.in_grouping(S,1072,1103);){if(W.cursor>=W.limit)return!1;W.cursor++}return!0}function t(){for(;!W.out_grouping(S,1072,1103);){if(W.cursor>=W.limit)return!1;W.cursor++}return!0}function w(){b=W.limit,_=b,e()&&(b=W.cursor,t()&&e()&&t()&&(_=W.cursor))}function i(){return _<=W.cursor}function u(e,n){var r,t;if(W.ket=W.cursor,r=W.find_among_b(e,n)){switch(W.bra=W.cursor,r){case 1:if(t=W.limit-W.cursor,!W.eq_s_b(1,"а")&&(W.cursor=W.limit-t,!W.eq_s_b(1,"я")))return!1;case 2:W.slice_del()}return!0}return!1}function o(){return u(h,9)}function s(e,n){var r;return W.ket=W.cursor,!!(r=W.find_among_b(e,n))&&(W.bra=W.cursor,1==r&&W.slice_del(),!0)}function c(){return s(g,26)}function m(){return!!c()&&(u(C,8),!0)}function f(){return s(k,2)}function l(){return u(P,46)}function a(){s(v,36)}function p(){var e;W.ket=W.cursor,(e=W.find_among_b(F,2))&&(W.bra=W.cursor,i()&&1==e&&W.slice_del())}function d(){var e;if(W.ket=W.cursor,e=W.find_among_b(q,4))switch(W.bra=W.cursor,e){case 1:if(W.slice_del(),W.ket=W.cursor,!W.eq_s_b(1,"н"))break;W.bra=W.cursor;case 2:if(!W.eq_s_b(1,"н"))break;case 3:W.slice_del()}}var _,b,h=[new n("в",-1,1),new n("ив",0,2),new n("ыв",0,2),new n("вши",-1,1),new n("ивши",3,2),new n("ывши",3,2),new n("вшись",-1,1),new n("ившись",6,2),new n("ывшись",6,2)],g=[new n("ее",-1,1),new n("ие",-1,1),new n("ое",-1,1),new n("ые",-1,1),new n("ими",-1,1),new n("ыми",-1,1),new n("ей",-1,1),new n("ий",-1,1),new n("ой",-1,1),new n("ый",-1,1),new n("ем",-1,1),new n("им",-1,1),new n("ом",-1,1),new n("ым",-1,1),new n("его",-1,1),new n("ого",-1,1),new n("ему",-1,1),new n("ому",-1,1),new n("их",-1,1),new n("ых",-1,1),new n("ею",-1,1),new n("ою",-1,1),new n("ую",-1,1),new n("юю",-1,1),new n("ая",-1,1),new n("яя",-1,1)],C=[new n("ем",-1,1),new n("нн",-1,1),new n("вш",-1,1),new n("ивш",2,2),new n("ывш",2,2),new n("щ",-1,1),new n("ющ",5,1),new n("ующ",6,2)],k=[new n("сь",-1,1),new n("ся",-1,1)],P=[new n("ла",-1,1),new n("ила",0,2),new n("ыла",0,2),new n("на",-1,1),new n("ена",3,2),new n("ете",-1,1),new n("ите",-1,2),new n("йте",-1,1),new n("ейте",7,2),new n("уйте",7,2),new n("ли",-1,1),new n("или",10,2),new n("ыли",10,2),new n("й",-1,1),new n("ей",13,2),new n("уй",13,2),new n("л",-1,1),new n("ил",16,2),new n("ыл",16,2),new n("ем",-1,1),new n("им",-1,2),new n("ым",-1,2),new n("н",-1,1),new n("ен",22,2),new n("ло",-1,1),new n("ило",24,2),new n("ыло",24,2),new n("но",-1,1),new n("ено",27,2),new n("нно",27,1),new n("ет",-1,1),new n("ует",30,2),new n("ит",-1,2),new n("ыт",-1,2),new n("ют",-1,1),new n("уют",34,2),new n("ят",-1,2),new n("ны",-1,1),new n("ены",37,2),new n("ть",-1,1),new n("ить",39,2),new n("ыть",39,2),new n("ешь",-1,1),new n("ишь",-1,2),new n("ю",-1,2),new n("ую",44,2)],v=[new n("а",-1,1),new n("ев",-1,1),new n("ов",-1,1),new n("е",-1,1),new n("ие",3,1),new n("ье",3,1),new n("и",-1,1),new n("еи",6,1),new n("ии",6,1),new n("ами",6,1),new n("ями",6,1),new n("иями",10,1),new n("й",-1,1),new n("ей",12,1),new n("ией",13,1),new n("ий",12,1),new n("ой",12,1),new n("ам",-1,1),new n("ем",-1,1),new n("ием",18,1),new n("ом",-1,1),new n("ям",-1,1),new n("иям",21,1),new n("о",-1,1),new n("у",-1,1),new n("ах",-1,1),new n("ях",-1,1),new n("иях",26,1),new n("ы",-1,1),new n("ь",-1,1),new n("ю",-1,1),new n("ию",30,1),new n("ью",30,1),new n("я",-1,1),new n("ия",33,1),new n("ья",33,1)],F=[new n("ост",-1,1),new n("ость",-1,1)],q=[new n("ейше",-1,1),new n("н",-1,2),new n("ейш",-1,1),new n("ь",-1,3)],S=[33,65,8,232],W=new r;this.setCurrent=function(e){W.setCurrent(e)},this.getCurrent=function(){return W.getCurrent()},this.stem=function(){return w(),W.cursor=W.limit,!(W.cursor=i&&(e-=i,t[e>>3]&1<<(7&e)))return this.cursor++,!0}return!1},in_grouping_b:function(t,i,s){if(this.cursor>this.limit_backward){var e=r.charCodeAt(this.cursor-1);if(e<=s&&e>=i&&(e-=i,t[e>>3]&1<<(7&e)))return this.cursor--,!0}return!1},out_grouping:function(t,i,s){if(this.cursors||e>3]&1<<(7&e)))return this.cursor++,!0}return!1},out_grouping_b:function(t,i,s){if(this.cursor>this.limit_backward){var e=r.charCodeAt(this.cursor-1);if(e>s||e>3]&1<<(7&e)))return this.cursor--,!0}return!1},eq_s:function(t,i){if(this.limit-this.cursor>1),f=0,l=o0||e==s||c)break;c=!0}}for(;;){var _=t[s];if(o>=_.s_size){if(this.cursor=n+_.s_size,!_.method)return _.result;var b=_.method();if(this.cursor=n+_.s_size,b)return _.result}if((s=_.substring_i)<0)return 0}},find_among_b:function(t,i){for(var s=0,e=i,n=this.cursor,u=this.limit_backward,o=0,h=0,c=!1;;){for(var a=s+(e-s>>1),f=0,l=o=0;m--){if(n-l==u){f=-1;break}if(f=r.charCodeAt(n-1-l)-_.s[m])break;l++}if(f<0?(e=a,h=l):(s=a,o=l),e-s<=1){if(s>0||e==s||c)break;c=!0}}for(;;){var _=t[s];if(o>=_.s_size){if(this.cursor=n-_.s_size,!_.method)return _.result;var b=_.method();if(this.cursor=n-_.s_size,b)return _.result}if((s=_.substring_i)<0)return 0}},replace_s:function(t,i,s){var e=s.length-(i-t),n=r.substring(0,t),u=r.substring(i);return r=n+s+u,this.limit+=e,this.cursor>=i?this.cursor+=e:this.cursor>t&&(this.cursor=t),e},slice_check:function(){if(this.bra<0||this.bra>this.ket||this.ket>this.limit||this.limit>r.length)throw"faulty slice operation"},slice_from:function(r){this.slice_check(),this.replace_s(this.bra,this.ket,r)},slice_del:function(){this.slice_from("")},insert:function(r,t,i){var s=this.replace_s(r,t,i);r<=this.bra&&(this.bra+=s),r<=this.ket&&(this.ket+=s)},slice_to:function(){return this.slice_check(),r.substring(this.bra,this.ket)},eq_v_b:function(r){return this.eq_s_b(r.length,r)}}}},r.trimmerSupport={generateTrimmer:function(r){var t=new RegExp("^[^"+r+"]+"),i=new RegExp("[^"+r+"]+$");return function(r){return"function"==typeof r.update?r.update(function(r){return r.replace(t,"").replace(i,"")}):r.replace(t,"").replace(i,"")}}}}}); \ No newline at end of file diff --git a/develop/assets/javascripts/lunr/min/lunr.sv.min.js b/develop/assets/javascripts/lunr/min/lunr.sv.min.js new file mode 100644 index 0000000..3e5eb64 --- /dev/null +++ b/develop/assets/javascripts/lunr/min/lunr.sv.min.js @@ -0,0 +1,18 @@ +/*! + * Lunr languages, `Swedish` language + * https://github.com/MihaiValentin/lunr-languages + * + * Copyright 2014, Mihai Valentin + * http://www.mozilla.org/MPL/ + */ +/*! + * based on + * Snowball JavaScript Library v0.3 + * http://code.google.com/p/urim/ + * http://snowball.tartarus.org/ + * + * Copyright 2010, Oleg Mazko + * http://www.mozilla.org/MPL/ + */ + +!function(e,r){"function"==typeof define&&define.amd?define(r):"object"==typeof exports?module.exports=r():r()(e.lunr)}(this,function(){return function(e){if(void 0===e)throw new Error("Lunr is not present. Please include / require Lunr before this script.");if(void 0===e.stemmerSupport)throw new Error("Lunr stemmer support is not present. Please include / require Lunr stemmer support before this script.");e.sv=function(){this.pipeline.reset(),this.pipeline.add(e.sv.trimmer,e.sv.stopWordFilter,e.sv.stemmer),this.searchPipeline&&(this.searchPipeline.reset(),this.searchPipeline.add(e.sv.stemmer))},e.sv.wordCharacters="A-Za-zªºÀ-ÖØ-öø-ʸˠ-ˤᴀ-ᴥᴬ-ᵜᵢ-ᵥᵫ-ᵷᵹ-ᶾḀ-ỿⁱⁿₐ-ₜKÅℲⅎⅠ-ↈⱠ-ⱿꜢ-ꞇꞋ-ꞭꞰ-ꞷꟷ-ꟿꬰ-ꭚꭜ-ꭤff-stA-Za-z",e.sv.trimmer=e.trimmerSupport.generateTrimmer(e.sv.wordCharacters),e.Pipeline.registerFunction(e.sv.trimmer,"trimmer-sv"),e.sv.stemmer=function(){var r=e.stemmerSupport.Among,n=e.stemmerSupport.SnowballProgram,t=new function(){function e(){var e,r=w.cursor+3;if(o=w.limit,0<=r||r<=w.limit){for(a=r;;){if(e=w.cursor,w.in_grouping(l,97,246)){w.cursor=e;break}if(w.cursor=e,w.cursor>=w.limit)return;w.cursor++}for(;!w.out_grouping(l,97,246);){if(w.cursor>=w.limit)return;w.cursor++}o=w.cursor,o=o&&(w.limit_backward=o,w.cursor=w.limit,w.ket=w.cursor,e=w.find_among_b(u,37),w.limit_backward=r,e))switch(w.bra=w.cursor,e){case 1:w.slice_del();break;case 2:w.in_grouping_b(d,98,121)&&w.slice_del()}}function i(){var e=w.limit_backward;w.cursor>=o&&(w.limit_backward=o,w.cursor=w.limit,w.find_among_b(c,7)&&(w.cursor=w.limit,w.ket=w.cursor,w.cursor>w.limit_backward&&(w.bra=--w.cursor,w.slice_del())),w.limit_backward=e)}function s(){var e,r;if(w.cursor>=o){if(r=w.limit_backward,w.limit_backward=o,w.cursor=w.limit,w.ket=w.cursor,e=w.find_among_b(m,5))switch(w.bra=w.cursor,e){case 1:w.slice_del();break;case 2:w.slice_from("lös");break;case 3:w.slice_from("full")}w.limit_backward=r}}var a,o,u=[new r("a",-1,1),new r("arna",0,1),new r("erna",0,1),new r("heterna",2,1),new r("orna",0,1),new r("ad",-1,1),new r("e",-1,1),new r("ade",6,1),new r("ande",6,1),new r("arne",6,1),new r("are",6,1),new r("aste",6,1),new r("en",-1,1),new r("anden",12,1),new r("aren",12,1),new r("heten",12,1),new r("ern",-1,1),new r("ar",-1,1),new r("er",-1,1),new r("heter",18,1),new r("or",-1,1),new r("s",-1,2),new r("as",21,1),new r("arnas",22,1),new r("ernas",22,1),new r("ornas",22,1),new r("es",21,1),new r("ades",26,1),new r("andes",26,1),new r("ens",21,1),new r("arens",29,1),new r("hetens",29,1),new r("erns",21,1),new r("at",-1,1),new r("andet",-1,1),new r("het",-1,1),new r("ast",-1,1)],c=[new r("dd",-1,-1),new r("gd",-1,-1),new r("nn",-1,-1),new r("dt",-1,-1),new r("gt",-1,-1),new r("kt",-1,-1),new r("tt",-1,-1)],m=[new r("ig",-1,1),new r("lig",0,1),new r("els",-1,1),new r("fullt",-1,3),new r("löst",-1,2)],l=[17,65,16,1,0,0,0,0,0,0,0,0,0,0,0,0,24,0,32],d=[119,127,149],w=new n;this.setCurrent=function(e){w.setCurrent(e)},this.getCurrent=function(){return w.getCurrent()},this.stem=function(){var r=w.cursor;return e(),w.limit_backward=r,w.cursor=w.limit,t(),w.cursor=w.limit,i(),w.cursor=w.limit,s(),!0}};return function(e){return"function"==typeof e.update?e.update(function(e){return t.setCurrent(e),t.stem(),t.getCurrent()}):(t.setCurrent(e),t.stem(),t.getCurrent())}}(),e.Pipeline.registerFunction(e.sv.stemmer,"stemmer-sv"),e.sv.stopWordFilter=e.generateStopWordFilter("alla allt att av blev bli blir blivit de dem den denna deras dess dessa det detta dig din dina ditt du där då efter ej eller en er era ert ett från för ha hade han hans har henne hennes hon honom hur här i icke ingen inom inte jag ju kan kunde man med mellan men mig min mina mitt mot mycket ni nu när någon något några och om oss på samma sedan sig sin sina sitta själv skulle som så sådan sådana sådant till under upp ut utan vad var vara varför varit varje vars vart vem vi vid vilka vilkas vilken vilket vår våra vårt än är åt över".split(" ")),e.Pipeline.registerFunction(e.sv.stopWordFilter,"stopWordFilter-sv")}}); \ No newline at end of file diff --git a/develop/assets/javascripts/lunr/min/lunr.th.min.js b/develop/assets/javascripts/lunr/min/lunr.th.min.js new file mode 100644 index 0000000..dee3aac --- /dev/null +++ b/develop/assets/javascripts/lunr/min/lunr.th.min.js @@ -0,0 +1 @@ +!function(e,r){"function"==typeof define&&define.amd?define(r):"object"==typeof exports?module.exports=r():r()(e.lunr)}(this,function(){return function(e){if(void 0===e)throw new Error("Lunr is not present. Please include / require Lunr before this script.");if(void 0===e.stemmerSupport)throw new Error("Lunr stemmer support is not present. Please include / require Lunr stemmer support before this script.");var r="2"==e.version[0];e.th=function(){this.pipeline.reset(),this.pipeline.add(e.th.trimmer),r?this.tokenizer=e.th.tokenizer:(e.tokenizer&&(e.tokenizer=e.th.tokenizer),this.tokenizerFn&&(this.tokenizerFn=e.th.tokenizer))},e.th.wordCharacters="[฀-๿]",e.th.trimmer=e.trimmerSupport.generateTrimmer(e.th.wordCharacters),e.Pipeline.registerFunction(e.th.trimmer,"trimmer-th");var t=e.wordcut;t.init(),e.th.tokenizer=function(i){if(!arguments.length||null==i||void 0==i)return[];if(Array.isArray(i))return i.map(function(t){return r?new e.Token(t):t});var n=i.toString().replace(/^\s+/,"");return t.cut(n).split("|")}}}); \ No newline at end of file diff --git a/develop/assets/javascripts/lunr/min/lunr.tr.min.js b/develop/assets/javascripts/lunr/min/lunr.tr.min.js new file mode 100644 index 0000000..563f6ec --- /dev/null +++ b/develop/assets/javascripts/lunr/min/lunr.tr.min.js @@ -0,0 +1,18 @@ +/*! + * Lunr languages, `Turkish` language + * https://github.com/MihaiValentin/lunr-languages + * + * Copyright 2014, Mihai Valentin + * http://www.mozilla.org/MPL/ + */ +/*! + * based on + * Snowball JavaScript Library v0.3 + * http://code.google.com/p/urim/ + * http://snowball.tartarus.org/ + * + * Copyright 2010, Oleg Mazko + * http://www.mozilla.org/MPL/ + */ + +!function(r,i){"function"==typeof define&&define.amd?define(i):"object"==typeof exports?module.exports=i():i()(r.lunr)}(this,function(){return function(r){if(void 0===r)throw new Error("Lunr is not present. Please include / require Lunr before this script.");if(void 0===r.stemmerSupport)throw new Error("Lunr stemmer support is not present. Please include / require Lunr stemmer support before this script.");r.tr=function(){this.pipeline.reset(),this.pipeline.add(r.tr.trimmer,r.tr.stopWordFilter,r.tr.stemmer),this.searchPipeline&&(this.searchPipeline.reset(),this.searchPipeline.add(r.tr.stemmer))},r.tr.wordCharacters="A-Za-zªºÀ-ÖØ-öø-ʸˠ-ˤᴀ-ᴥᴬ-ᵜᵢ-ᵥᵫ-ᵷᵹ-ᶾḀ-ỿⁱⁿₐ-ₜKÅℲⅎⅠ-ↈⱠ-ⱿꜢ-ꞇꞋ-ꞭꞰ-ꞷꟷ-ꟿꬰ-ꭚꭜ-ꭤff-stA-Za-z",r.tr.trimmer=r.trimmerSupport.generateTrimmer(r.tr.wordCharacters),r.Pipeline.registerFunction(r.tr.trimmer,"trimmer-tr"),r.tr.stemmer=function(){var i=r.stemmerSupport.Among,e=r.stemmerSupport.SnowballProgram,n=new function(){function r(r,i,e){for(;;){var n=Dr.limit-Dr.cursor;if(Dr.in_grouping_b(r,i,e)){Dr.cursor=Dr.limit-n;break}if(Dr.cursor=Dr.limit-n,Dr.cursor<=Dr.limit_backward)return!1;Dr.cursor--}return!0}function n(){var i,e;i=Dr.limit-Dr.cursor,r(Wr,97,305);for(var n=0;nDr.limit_backward&&(Dr.cursor--,e=Dr.limit-Dr.cursor,i()))?(Dr.cursor=Dr.limit-e,!0):(Dr.cursor=Dr.limit-n,r()?(Dr.cursor=Dr.limit-n,!1):(Dr.cursor=Dr.limit-n,!(Dr.cursor<=Dr.limit_backward)&&(Dr.cursor--,!!i()&&(Dr.cursor=Dr.limit-n,!0))))}function u(r){return t(r,function(){return Dr.in_grouping_b(Wr,97,305)})}function o(){return u(function(){return Dr.eq_s_b(1,"n")})}function s(){return u(function(){return Dr.eq_s_b(1,"s")})}function c(){return u(function(){return Dr.eq_s_b(1,"y")})}function l(){return t(function(){return Dr.in_grouping_b(Lr,105,305)},function(){return Dr.out_grouping_b(Wr,97,305)})}function a(){return Dr.find_among_b(ur,10)&&l()}function m(){return n()&&Dr.in_grouping_b(Lr,105,305)&&s()}function d(){return Dr.find_among_b(or,2)}function f(){return n()&&Dr.in_grouping_b(Lr,105,305)&&c()}function b(){return n()&&Dr.find_among_b(sr,4)}function w(){return n()&&Dr.find_among_b(cr,4)&&o()}function _(){return n()&&Dr.find_among_b(lr,2)&&c()}function k(){return n()&&Dr.find_among_b(ar,2)}function p(){return n()&&Dr.find_among_b(mr,4)}function g(){return n()&&Dr.find_among_b(dr,2)}function y(){return n()&&Dr.find_among_b(fr,4)}function z(){return n()&&Dr.find_among_b(br,2)}function v(){return n()&&Dr.find_among_b(wr,2)&&c()}function h(){return Dr.eq_s_b(2,"ki")}function q(){return n()&&Dr.find_among_b(_r,2)&&o()}function C(){return n()&&Dr.find_among_b(kr,4)&&c()}function P(){return n()&&Dr.find_among_b(pr,4)}function F(){return n()&&Dr.find_among_b(gr,4)&&c()}function S(){return Dr.find_among_b(yr,4)}function W(){return n()&&Dr.find_among_b(zr,2)}function L(){return n()&&Dr.find_among_b(vr,4)}function x(){return n()&&Dr.find_among_b(hr,8)}function A(){return Dr.find_among_b(qr,2)}function E(){return n()&&Dr.find_among_b(Cr,32)&&c()}function j(){return Dr.find_among_b(Pr,8)&&c()}function T(){return n()&&Dr.find_among_b(Fr,4)&&c()}function Z(){return Dr.eq_s_b(3,"ken")&&c()}function B(){var r=Dr.limit-Dr.cursor;return!(T()||(Dr.cursor=Dr.limit-r,E()||(Dr.cursor=Dr.limit-r,j()||(Dr.cursor=Dr.limit-r,Z()))))}function D(){if(A()){var r=Dr.limit-Dr.cursor;if(S()||(Dr.cursor=Dr.limit-r,W()||(Dr.cursor=Dr.limit-r,C()||(Dr.cursor=Dr.limit-r,P()||(Dr.cursor=Dr.limit-r,F()||(Dr.cursor=Dr.limit-r))))),T())return!1}return!0}function G(){if(W()){Dr.bra=Dr.cursor,Dr.slice_del();var r=Dr.limit-Dr.cursor;return Dr.ket=Dr.cursor,x()||(Dr.cursor=Dr.limit-r,E()||(Dr.cursor=Dr.limit-r,j()||(Dr.cursor=Dr.limit-r,T()||(Dr.cursor=Dr.limit-r)))),nr=!1,!1}return!0}function H(){if(!L())return!0;var r=Dr.limit-Dr.cursor;return!E()&&(Dr.cursor=Dr.limit-r,!j())}function I(){var r,i=Dr.limit-Dr.cursor;return!(S()||(Dr.cursor=Dr.limit-i,F()||(Dr.cursor=Dr.limit-i,P()||(Dr.cursor=Dr.limit-i,C()))))||(Dr.bra=Dr.cursor,Dr.slice_del(),r=Dr.limit-Dr.cursor,Dr.ket=Dr.cursor,T()||(Dr.cursor=Dr.limit-r),!1)}function J(){var r,i=Dr.limit-Dr.cursor;if(Dr.ket=Dr.cursor,nr=!0,B()&&(Dr.cursor=Dr.limit-i,D()&&(Dr.cursor=Dr.limit-i,G()&&(Dr.cursor=Dr.limit-i,H()&&(Dr.cursor=Dr.limit-i,I()))))){if(Dr.cursor=Dr.limit-i,!x())return;Dr.bra=Dr.cursor,Dr.slice_del(),Dr.ket=Dr.cursor,r=Dr.limit-Dr.cursor,S()||(Dr.cursor=Dr.limit-r,W()||(Dr.cursor=Dr.limit-r,C()||(Dr.cursor=Dr.limit-r,P()||(Dr.cursor=Dr.limit-r,F()||(Dr.cursor=Dr.limit-r))))),T()||(Dr.cursor=Dr.limit-r)}Dr.bra=Dr.cursor,Dr.slice_del()}function K(){var r,i,e,n;if(Dr.ket=Dr.cursor,h()){if(r=Dr.limit-Dr.cursor,p())return Dr.bra=Dr.cursor,Dr.slice_del(),i=Dr.limit-Dr.cursor,Dr.ket=Dr.cursor,W()?(Dr.bra=Dr.cursor,Dr.slice_del(),K()):(Dr.cursor=Dr.limit-i,a()&&(Dr.bra=Dr.cursor,Dr.slice_del(),Dr.ket=Dr.cursor,W()&&(Dr.bra=Dr.cursor,Dr.slice_del(),K()))),!0;if(Dr.cursor=Dr.limit-r,w()){if(Dr.bra=Dr.cursor,Dr.slice_del(),Dr.ket=Dr.cursor,e=Dr.limit-Dr.cursor,d())Dr.bra=Dr.cursor,Dr.slice_del();else{if(Dr.cursor=Dr.limit-e,Dr.ket=Dr.cursor,!a()&&(Dr.cursor=Dr.limit-e,!m()&&(Dr.cursor=Dr.limit-e,!K())))return!0;Dr.bra=Dr.cursor,Dr.slice_del(),Dr.ket=Dr.cursor,W()&&(Dr.bra=Dr.cursor,Dr.slice_del(),K())}return!0}if(Dr.cursor=Dr.limit-r,g()){if(n=Dr.limit-Dr.cursor,d())Dr.bra=Dr.cursor,Dr.slice_del();else if(Dr.cursor=Dr.limit-n,m())Dr.bra=Dr.cursor,Dr.slice_del(),Dr.ket=Dr.cursor,W()&&(Dr.bra=Dr.cursor,Dr.slice_del(),K());else if(Dr.cursor=Dr.limit-n,!K())return!1;return!0}}return!1}function M(r){if(Dr.ket=Dr.cursor,!g()&&(Dr.cursor=Dr.limit-r,!k()))return!1;var i=Dr.limit-Dr.cursor;if(d())Dr.bra=Dr.cursor,Dr.slice_del();else if(Dr.cursor=Dr.limit-i,m())Dr.bra=Dr.cursor,Dr.slice_del(),Dr.ket=Dr.cursor,W()&&(Dr.bra=Dr.cursor,Dr.slice_del(),K());else if(Dr.cursor=Dr.limit-i,!K())return!1;return!0}function N(r){if(Dr.ket=Dr.cursor,!z()&&(Dr.cursor=Dr.limit-r,!b()))return!1;var i=Dr.limit-Dr.cursor;return!(!m()&&(Dr.cursor=Dr.limit-i,!d()))&&(Dr.bra=Dr.cursor,Dr.slice_del(),Dr.ket=Dr.cursor,W()&&(Dr.bra=Dr.cursor,Dr.slice_del(),K()),!0)}function O(){var r,i=Dr.limit-Dr.cursor;return Dr.ket=Dr.cursor,!(!w()&&(Dr.cursor=Dr.limit-i,!v()))&&(Dr.bra=Dr.cursor,Dr.slice_del(),r=Dr.limit-Dr.cursor,Dr.ket=Dr.cursor,!(!W()||(Dr.bra=Dr.cursor,Dr.slice_del(),!K()))||(Dr.cursor=Dr.limit-r,Dr.ket=Dr.cursor,!(a()||(Dr.cursor=Dr.limit-r,m()||(Dr.cursor=Dr.limit-r,K())))||(Dr.bra=Dr.cursor,Dr.slice_del(),Dr.ket=Dr.cursor,W()&&(Dr.bra=Dr.cursor,Dr.slice_del(),K()),!0)))}function Q(){var r,i,e=Dr.limit-Dr.cursor;if(Dr.ket=Dr.cursor,!p()&&(Dr.cursor=Dr.limit-e,!f()&&(Dr.cursor=Dr.limit-e,!_())))return!1;if(Dr.bra=Dr.cursor,Dr.slice_del(),Dr.ket=Dr.cursor,r=Dr.limit-Dr.cursor,a())Dr.bra=Dr.cursor,Dr.slice_del(),i=Dr.limit-Dr.cursor,Dr.ket=Dr.cursor,W()||(Dr.cursor=Dr.limit-i);else if(Dr.cursor=Dr.limit-r,!W())return!0;return Dr.bra=Dr.cursor,Dr.slice_del(),Dr.ket=Dr.cursor,K(),!0}function R(){var r,i,e=Dr.limit-Dr.cursor;if(Dr.ket=Dr.cursor,W())return Dr.bra=Dr.cursor,Dr.slice_del(),void K();if(Dr.cursor=Dr.limit-e,Dr.ket=Dr.cursor,q())if(Dr.bra=Dr.cursor,Dr.slice_del(),r=Dr.limit-Dr.cursor,Dr.ket=Dr.cursor,d())Dr.bra=Dr.cursor,Dr.slice_del();else{if(Dr.cursor=Dr.limit-r,Dr.ket=Dr.cursor,!a()&&(Dr.cursor=Dr.limit-r,!m())){if(Dr.cursor=Dr.limit-r,Dr.ket=Dr.cursor,!W())return;if(Dr.bra=Dr.cursor,Dr.slice_del(),!K())return}Dr.bra=Dr.cursor,Dr.slice_del(),Dr.ket=Dr.cursor,W()&&(Dr.bra=Dr.cursor,Dr.slice_del(),K())}else if(Dr.cursor=Dr.limit-e,!M(e)&&(Dr.cursor=Dr.limit-e,!N(e))){if(Dr.cursor=Dr.limit-e,Dr.ket=Dr.cursor,y())return Dr.bra=Dr.cursor,Dr.slice_del(),Dr.ket=Dr.cursor,i=Dr.limit-Dr.cursor,void(a()?(Dr.bra=Dr.cursor,Dr.slice_del(),Dr.ket=Dr.cursor,W()&&(Dr.bra=Dr.cursor,Dr.slice_del(),K())):(Dr.cursor=Dr.limit-i,W()?(Dr.bra=Dr.cursor,Dr.slice_del(),K()):(Dr.cursor=Dr.limit-i,K())));if(Dr.cursor=Dr.limit-e,!O()){if(Dr.cursor=Dr.limit-e,d())return Dr.bra=Dr.cursor,void Dr.slice_del();Dr.cursor=Dr.limit-e,K()||(Dr.cursor=Dr.limit-e,Q()||(Dr.cursor=Dr.limit-e,Dr.ket=Dr.cursor,(a()||(Dr.cursor=Dr.limit-e,m()))&&(Dr.bra=Dr.cursor,Dr.slice_del(),Dr.ket=Dr.cursor,W()&&(Dr.bra=Dr.cursor,Dr.slice_del(),K()))))}}}function U(){var r;if(Dr.ket=Dr.cursor,r=Dr.find_among_b(Sr,4))switch(Dr.bra=Dr.cursor,r){case 1:Dr.slice_from("p");break;case 2:Dr.slice_from("ç");break;case 3:Dr.slice_from("t");break;case 4:Dr.slice_from("k")}}function V(){for(;;){var r=Dr.limit-Dr.cursor;if(Dr.in_grouping_b(Wr,97,305)){Dr.cursor=Dr.limit-r;break}if(Dr.cursor=Dr.limit-r,Dr.cursor<=Dr.limit_backward)return!1;Dr.cursor--}return!0}function X(r,i,e){if(Dr.cursor=Dr.limit-r,V()){var n=Dr.limit-Dr.cursor;if(!Dr.eq_s_b(1,i)&&(Dr.cursor=Dr.limit-n,!Dr.eq_s_b(1,e)))return!0;Dr.cursor=Dr.limit-r;var t=Dr.cursor;return Dr.insert(Dr.cursor,Dr.cursor,e),Dr.cursor=t,!1}return!0}function Y(){var r=Dr.limit-Dr.cursor;(Dr.eq_s_b(1,"d")||(Dr.cursor=Dr.limit-r,Dr.eq_s_b(1,"g")))&&X(r,"a","ı")&&X(r,"e","i")&&X(r,"o","u")&&X(r,"ö","ü")}function $(){for(var r,i=Dr.cursor,e=2;;){for(r=Dr.cursor;!Dr.in_grouping(Wr,97,305);){if(Dr.cursor>=Dr.limit)return Dr.cursor=r,!(e>0)&&(Dr.cursor=i,!0);Dr.cursor++}e--}}function rr(r,i,e){for(;!Dr.eq_s(i,e);){if(Dr.cursor>=Dr.limit)return!0;Dr.cursor++}return(tr=i)!=Dr.limit||(Dr.cursor=r,!1)}function ir(){var r=Dr.cursor;return!rr(r,2,"ad")||(Dr.cursor=r,!rr(r,5,"soyad"))}function er(){var r=Dr.cursor;return!ir()&&(Dr.limit_backward=r,Dr.cursor=Dr.limit,Y(),Dr.cursor=Dr.limit,U(),!0)}var nr,tr,ur=[new i("m",-1,-1),new i("n",-1,-1),new i("miz",-1,-1),new i("niz",-1,-1),new i("muz",-1,-1),new i("nuz",-1,-1),new i("müz",-1,-1),new i("nüz",-1,-1),new i("mız",-1,-1),new i("nız",-1,-1)],or=[new i("leri",-1,-1),new i("ları",-1,-1)],sr=[new i("ni",-1,-1),new i("nu",-1,-1),new i("nü",-1,-1),new i("nı",-1,-1)],cr=[new i("in",-1,-1),new i("un",-1,-1),new i("ün",-1,-1),new i("ın",-1,-1)],lr=[new i("a",-1,-1),new i("e",-1,-1)],ar=[new i("na",-1,-1),new i("ne",-1,-1)],mr=[new i("da",-1,-1),new i("ta",-1,-1),new i("de",-1,-1),new i("te",-1,-1)],dr=[new i("nda",-1,-1),new i("nde",-1,-1)],fr=[new i("dan",-1,-1),new i("tan",-1,-1),new i("den",-1,-1),new i("ten",-1,-1)],br=[new i("ndan",-1,-1),new i("nden",-1,-1)],wr=[new i("la",-1,-1),new i("le",-1,-1)],_r=[new i("ca",-1,-1),new i("ce",-1,-1)],kr=[new i("im",-1,-1),new i("um",-1,-1),new i("üm",-1,-1),new i("ım",-1,-1)],pr=[new i("sin",-1,-1),new i("sun",-1,-1),new i("sün",-1,-1),new i("sın",-1,-1)],gr=[new i("iz",-1,-1),new i("uz",-1,-1),new i("üz",-1,-1),new i("ız",-1,-1)],yr=[new i("siniz",-1,-1),new i("sunuz",-1,-1),new i("sünüz",-1,-1),new i("sınız",-1,-1)],zr=[new i("lar",-1,-1),new i("ler",-1,-1)],vr=[new i("niz",-1,-1),new i("nuz",-1,-1),new i("nüz",-1,-1),new i("nız",-1,-1)],hr=[new i("dir",-1,-1),new i("tir",-1,-1),new i("dur",-1,-1),new i("tur",-1,-1),new i("dür",-1,-1),new i("tür",-1,-1),new i("dır",-1,-1),new i("tır",-1,-1)],qr=[new i("casına",-1,-1),new i("cesine",-1,-1)],Cr=[new i("di",-1,-1),new i("ti",-1,-1),new i("dik",-1,-1),new i("tik",-1,-1),new i("duk",-1,-1),new i("tuk",-1,-1),new i("dük",-1,-1),new i("tük",-1,-1),new i("dık",-1,-1),new i("tık",-1,-1),new i("dim",-1,-1),new i("tim",-1,-1),new i("dum",-1,-1),new i("tum",-1,-1),new i("düm",-1,-1),new i("tüm",-1,-1),new i("dım",-1,-1),new i("tım",-1,-1),new i("din",-1,-1),new i("tin",-1,-1),new i("dun",-1,-1),new i("tun",-1,-1),new i("dün",-1,-1),new i("tün",-1,-1),new i("dın",-1,-1),new i("tın",-1,-1),new i("du",-1,-1),new i("tu",-1,-1),new i("dü",-1,-1),new i("tü",-1,-1),new i("dı",-1,-1),new i("tı",-1,-1)],Pr=[new i("sa",-1,-1),new i("se",-1,-1),new i("sak",-1,-1),new i("sek",-1,-1),new i("sam",-1,-1),new i("sem",-1,-1),new i("san",-1,-1),new i("sen",-1,-1)],Fr=[new i("miş",-1,-1),new i("muş",-1,-1),new i("müş",-1,-1),new i("mış",-1,-1)],Sr=[new i("b",-1,1),new i("c",-1,2),new i("d",-1,3),new i("ğ",-1,4)],Wr=[17,65,16,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,32,8,0,0,0,0,0,0,1],Lr=[1,16,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,8,0,0,0,0,0,0,1],xr=[1,64,16,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1],Ar=[17,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,130],Er=[1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1],jr=[17],Tr=[65],Zr=[65],Br=[["a",xr,97,305],["e",Ar,101,252],["ı",Er,97,305],["i",jr,101,105],["o",Tr,111,117],["ö",Zr,246,252],["u",Tr,111,117]],Dr=new e;this.setCurrent=function(r){Dr.setCurrent(r)},this.getCurrent=function(){return Dr.getCurrent()},this.stem=function(){return!!($()&&(Dr.limit_backward=Dr.cursor,Dr.cursor=Dr.limit,J(),Dr.cursor=Dr.limit,nr&&(R(),Dr.cursor=Dr.limit_backward,er())))}};return function(r){return"function"==typeof r.update?r.update(function(r){return n.setCurrent(r),n.stem(),n.getCurrent()}):(n.setCurrent(r),n.stem(),n.getCurrent())}}(),r.Pipeline.registerFunction(r.tr.stemmer,"stemmer-tr"),r.tr.stopWordFilter=r.generateStopWordFilter("acaba altmış altı ama ancak arada aslında ayrıca bana bazı belki ben benden beni benim beri beş bile bin bir biri birkaç birkez birçok birşey birşeyi biz bizden bize bizi bizim bu buna bunda bundan bunlar bunları bunların bunu bunun burada böyle böylece da daha dahi de defa değil diye diğer doksan dokuz dolayı dolayısıyla dört edecek eden ederek edilecek ediliyor edilmesi ediyor elli en etmesi etti ettiği ettiğini eğer gibi göre halen hangi hatta hem henüz hep hepsi her herhangi herkesin hiç hiçbir iki ile ilgili ise itibaren itibariyle için işte kadar karşın katrilyon kendi kendilerine kendini kendisi kendisine kendisini kez ki kim kimden kime kimi kimse kırk milyar milyon mu mü mı nasıl ne neden nedenle nerde nerede nereye niye niçin o olan olarak oldu olduklarını olduğu olduğunu olmadı olmadığı olmak olması olmayan olmaz olsa olsun olup olur olursa oluyor on ona ondan onlar onlardan onları onların onu onun otuz oysa pek rağmen sadece sanki sekiz seksen sen senden seni senin siz sizden sizi sizin tarafından trilyon tüm var vardı ve veya ya yani yapacak yapmak yaptı yaptıkları yaptığı yaptığını yapılan yapılması yapıyor yedi yerine yetmiş yine yirmi yoksa yüz zaten çok çünkü öyle üzere üç şey şeyden şeyi şeyler şu şuna şunda şundan şunları şunu şöyle".split(" ")),r.Pipeline.registerFunction(r.tr.stopWordFilter,"stopWordFilter-tr")}}); \ No newline at end of file diff --git a/develop/assets/javascripts/lunr/min/lunr.vi.min.js b/develop/assets/javascripts/lunr/min/lunr.vi.min.js new file mode 100644 index 0000000..22aed28 --- /dev/null +++ b/develop/assets/javascripts/lunr/min/lunr.vi.min.js @@ -0,0 +1 @@ +!function(e,r){"function"==typeof define&&define.amd?define(r):"object"==typeof exports?module.exports=r():r()(e.lunr)}(this,function(){return function(e){if(void 0===e)throw new Error("Lunr is not present. Please include / require Lunr before this script.");if(void 0===e.stemmerSupport)throw new Error("Lunr stemmer support is not present. Please include / require Lunr stemmer support before this script.");e.vi=function(){this.pipeline.reset(),this.pipeline.add(e.vi.stopWordFilter,e.vi.trimmer)},e.vi.wordCharacters="[A-Za-ẓ̀͐́͑̉̃̓ÂâÊêÔôĂ-ăĐ-đƠ-ơƯ-ư]",e.vi.trimmer=e.trimmerSupport.generateTrimmer(e.vi.wordCharacters),e.Pipeline.registerFunction(e.vi.trimmer,"trimmer-vi"),e.vi.stopWordFilter=e.generateStopWordFilter("là cái nhưng mà".split(" "))}}); \ No newline at end of file diff --git a/develop/assets/javascripts/lunr/min/lunr.zh.min.js b/develop/assets/javascripts/lunr/min/lunr.zh.min.js new file mode 100644 index 0000000..7727bbe --- /dev/null +++ b/develop/assets/javascripts/lunr/min/lunr.zh.min.js @@ -0,0 +1 @@ +!function(e,r){"function"==typeof define&&define.amd?define(r):"object"==typeof exports?module.exports=r(require("nodejieba")):r()(e.lunr)}(this,function(e){return function(r,t){if(void 0===r)throw new Error("Lunr is not present. Please include / require Lunr before this script.");if(void 0===r.stemmerSupport)throw new Error("Lunr stemmer support is not present. Please include / require Lunr stemmer support before this script.");var i="2"==r.version[0];r.zh=function(){this.pipeline.reset(),this.pipeline.add(r.zh.trimmer,r.zh.stopWordFilter,r.zh.stemmer),i?this.tokenizer=r.zh.tokenizer:(r.tokenizer&&(r.tokenizer=r.zh.tokenizer),this.tokenizerFn&&(this.tokenizerFn=r.zh.tokenizer))},r.zh.tokenizer=function(n){if(!arguments.length||null==n||void 0==n)return[];if(Array.isArray(n))return n.map(function(e){return i?new r.Token(e.toLowerCase()):e.toLowerCase()});t&&e.load(t);var o=n.toString().trim().toLowerCase(),s=[];e.cut(o,!0).forEach(function(e){s=s.concat(e.split(" "))}),s=s.filter(function(e){return!!e});var u=0;return s.map(function(e,t){if(i){var n=o.indexOf(e,u),s={};return s.position=[n,e.length],s.index=t,u=n,new r.Token(e,s)}return e})},r.zh.wordCharacters="\\w一-龥",r.zh.trimmer=r.trimmerSupport.generateTrimmer(r.zh.wordCharacters),r.Pipeline.registerFunction(r.zh.trimmer,"trimmer-zh"),r.zh.stemmer=function(){return function(e){return e}}(),r.Pipeline.registerFunction(r.zh.stemmer,"stemmer-zh"),r.zh.stopWordFilter=r.generateStopWordFilter("的 一 不 在 人 有 是 为 以 于 上 他 而 后 之 来 及 了 因 下 可 到 由 这 与 也 此 但 并 个 其 已 无 小 我 们 起 最 再 今 去 好 只 又 或 很 亦 某 把 那 你 乃 它 吧 被 比 别 趁 当 从 到 得 打 凡 儿 尔 该 各 给 跟 和 何 还 即 几 既 看 据 距 靠 啦 了 另 么 每 们 嘛 拿 哪 那 您 凭 且 却 让 仍 啥 如 若 使 谁 虽 随 同 所 她 哇 嗡 往 哪 些 向 沿 哟 用 于 咱 则 怎 曾 至 致 着 诸 自".split(" ")),r.Pipeline.registerFunction(r.zh.stopWordFilter,"stopWordFilter-zh")}}); \ No newline at end of file diff --git a/develop/assets/javascripts/lunr/tinyseg.js b/develop/assets/javascripts/lunr/tinyseg.js new file mode 100644 index 0000000..167fa6d --- /dev/null +++ b/develop/assets/javascripts/lunr/tinyseg.js @@ -0,0 +1,206 @@ +/** + * export the module via AMD, CommonJS or as a browser global + * Export code from https://github.com/umdjs/umd/blob/master/returnExports.js + */ +;(function (root, factory) { + if (typeof define === 'function' && define.amd) { + // AMD. Register as an anonymous module. + define(factory) + } else if (typeof exports === 'object') { + /** + * Node. Does not work with strict CommonJS, but + * only CommonJS-like environments that support module.exports, + * like Node. + */ + module.exports = factory() + } else { + // Browser globals (root is window) + factory()(root.lunr); + } +}(this, function () { + /** + * Just return a value to define the module export. + * This example returns an object, but the module + * can return a function as the exported value. + */ + + return function(lunr) { + // TinySegmenter 0.1 -- Super compact Japanese tokenizer in Javascript + // (c) 2008 Taku Kudo + // TinySegmenter is freely distributable under the terms of a new BSD licence. + // For details, see http://chasen.org/~taku/software/TinySegmenter/LICENCE.txt + + function TinySegmenter() { + var patterns = { + "[一二三四五六七八九十百千万億兆]":"M", + "[一-龠々〆ヵヶ]":"H", + "[ぁ-ん]":"I", + "[ァ-ヴーア-ン゙ー]":"K", + "[a-zA-Za-zA-Z]":"A", + "[0-90-9]":"N" + } + this.chartype_ = []; + for (var i in patterns) { + var regexp = new RegExp(i); + this.chartype_.push([regexp, patterns[i]]); + } + + this.BIAS__ = -332 + this.BC1__ = {"HH":6,"II":2461,"KH":406,"OH":-1378}; + this.BC2__ = {"AA":-3267,"AI":2744,"AN":-878,"HH":-4070,"HM":-1711,"HN":4012,"HO":3761,"IA":1327,"IH":-1184,"II":-1332,"IK":1721,"IO":5492,"KI":3831,"KK":-8741,"MH":-3132,"MK":3334,"OO":-2920}; + this.BC3__ = {"HH":996,"HI":626,"HK":-721,"HN":-1307,"HO":-836,"IH":-301,"KK":2762,"MK":1079,"MM":4034,"OA":-1652,"OH":266}; + this.BP1__ = {"BB":295,"OB":304,"OO":-125,"UB":352}; + this.BP2__ = {"BO":60,"OO":-1762}; + this.BQ1__ = {"BHH":1150,"BHM":1521,"BII":-1158,"BIM":886,"BMH":1208,"BNH":449,"BOH":-91,"BOO":-2597,"OHI":451,"OIH":-296,"OKA":1851,"OKH":-1020,"OKK":904,"OOO":2965}; + this.BQ2__ = {"BHH":118,"BHI":-1159,"BHM":466,"BIH":-919,"BKK":-1720,"BKO":864,"OHH":-1139,"OHM":-181,"OIH":153,"UHI":-1146}; + this.BQ3__ = {"BHH":-792,"BHI":2664,"BII":-299,"BKI":419,"BMH":937,"BMM":8335,"BNN":998,"BOH":775,"OHH":2174,"OHM":439,"OII":280,"OKH":1798,"OKI":-793,"OKO":-2242,"OMH":-2402,"OOO":11699}; + this.BQ4__ = {"BHH":-3895,"BIH":3761,"BII":-4654,"BIK":1348,"BKK":-1806,"BMI":-3385,"BOO":-12396,"OAH":926,"OHH":266,"OHK":-2036,"ONN":-973}; + this.BW1__ = {",と":660,",同":727,"B1あ":1404,"B1同":542,"、と":660,"、同":727,"」と":1682,"あっ":1505,"いう":1743,"いっ":-2055,"いる":672,"うし":-4817,"うん":665,"から":3472,"がら":600,"こう":-790,"こと":2083,"こん":-1262,"さら":-4143,"さん":4573,"した":2641,"して":1104,"すで":-3399,"そこ":1977,"それ":-871,"たち":1122,"ため":601,"った":3463,"つい":-802,"てい":805,"てき":1249,"でき":1127,"です":3445,"では":844,"とい":-4915,"とみ":1922,"どこ":3887,"ない":5713,"なっ":3015,"など":7379,"なん":-1113,"にし":2468,"には":1498,"にも":1671,"に対":-912,"の一":-501,"の中":741,"ませ":2448,"まで":1711,"まま":2600,"まる":-2155,"やむ":-1947,"よっ":-2565,"れた":2369,"れで":-913,"をし":1860,"を見":731,"亡く":-1886,"京都":2558,"取り":-2784,"大き":-2604,"大阪":1497,"平方":-2314,"引き":-1336,"日本":-195,"本当":-2423,"毎日":-2113,"目指":-724,"B1あ":1404,"B1同":542,"」と":1682}; + this.BW2__ = {"..":-11822,"11":-669,"――":-5730,"−−":-13175,"いう":-1609,"うか":2490,"かし":-1350,"かも":-602,"から":-7194,"かれ":4612,"がい":853,"がら":-3198,"きた":1941,"くな":-1597,"こと":-8392,"この":-4193,"させ":4533,"され":13168,"さん":-3977,"しい":-1819,"しか":-545,"した":5078,"して":972,"しな":939,"その":-3744,"たい":-1253,"たた":-662,"ただ":-3857,"たち":-786,"たと":1224,"たは":-939,"った":4589,"って":1647,"っと":-2094,"てい":6144,"てき":3640,"てく":2551,"ては":-3110,"ても":-3065,"でい":2666,"でき":-1528,"でし":-3828,"です":-4761,"でも":-4203,"とい":1890,"とこ":-1746,"とと":-2279,"との":720,"とみ":5168,"とも":-3941,"ない":-2488,"なが":-1313,"など":-6509,"なの":2614,"なん":3099,"にお":-1615,"にし":2748,"にな":2454,"によ":-7236,"に対":-14943,"に従":-4688,"に関":-11388,"のか":2093,"ので":-7059,"のに":-6041,"のの":-6125,"はい":1073,"はが":-1033,"はず":-2532,"ばれ":1813,"まし":-1316,"まで":-6621,"まれ":5409,"めて":-3153,"もい":2230,"もの":-10713,"らか":-944,"らし":-1611,"らに":-1897,"りし":651,"りま":1620,"れた":4270,"れて":849,"れば":4114,"ろう":6067,"われ":7901,"を通":-11877,"んだ":728,"んな":-4115,"一人":602,"一方":-1375,"一日":970,"一部":-1051,"上が":-4479,"会社":-1116,"出て":2163,"分の":-7758,"同党":970,"同日":-913,"大阪":-2471,"委員":-1250,"少な":-1050,"年度":-8669,"年間":-1626,"府県":-2363,"手権":-1982,"新聞":-4066,"日新":-722,"日本":-7068,"日米":3372,"曜日":-601,"朝鮮":-2355,"本人":-2697,"東京":-1543,"然と":-1384,"社会":-1276,"立て":-990,"第に":-1612,"米国":-4268,"11":-669}; + this.BW3__ = {"あた":-2194,"あり":719,"ある":3846,"い.":-1185,"い。":-1185,"いい":5308,"いえ":2079,"いく":3029,"いた":2056,"いっ":1883,"いる":5600,"いわ":1527,"うち":1117,"うと":4798,"えと":1454,"か.":2857,"か。":2857,"かけ":-743,"かっ":-4098,"かに":-669,"から":6520,"かり":-2670,"が,":1816,"が、":1816,"がき":-4855,"がけ":-1127,"がっ":-913,"がら":-4977,"がり":-2064,"きた":1645,"けど":1374,"こと":7397,"この":1542,"ころ":-2757,"さい":-714,"さを":976,"し,":1557,"し、":1557,"しい":-3714,"した":3562,"して":1449,"しな":2608,"しま":1200,"す.":-1310,"す。":-1310,"する":6521,"ず,":3426,"ず、":3426,"ずに":841,"そう":428,"た.":8875,"た。":8875,"たい":-594,"たの":812,"たり":-1183,"たる":-853,"だ.":4098,"だ。":4098,"だっ":1004,"った":-4748,"って":300,"てい":6240,"てお":855,"ても":302,"です":1437,"でに":-1482,"では":2295,"とう":-1387,"とし":2266,"との":541,"とも":-3543,"どう":4664,"ない":1796,"なく":-903,"など":2135,"に,":-1021,"に、":-1021,"にし":1771,"にな":1906,"には":2644,"の,":-724,"の、":-724,"の子":-1000,"は,":1337,"は、":1337,"べき":2181,"まし":1113,"ます":6943,"まっ":-1549,"まで":6154,"まれ":-793,"らし":1479,"られ":6820,"るる":3818,"れ,":854,"れ、":854,"れた":1850,"れて":1375,"れば":-3246,"れる":1091,"われ":-605,"んだ":606,"んで":798,"カ月":990,"会議":860,"入り":1232,"大会":2217,"始め":1681,"市":965,"新聞":-5055,"日,":974,"日、":974,"社会":2024,"カ月":990}; + this.TC1__ = {"AAA":1093,"HHH":1029,"HHM":580,"HII":998,"HOH":-390,"HOM":-331,"IHI":1169,"IOH":-142,"IOI":-1015,"IOM":467,"MMH":187,"OOI":-1832}; + this.TC2__ = {"HHO":2088,"HII":-1023,"HMM":-1154,"IHI":-1965,"KKH":703,"OII":-2649}; + this.TC3__ = {"AAA":-294,"HHH":346,"HHI":-341,"HII":-1088,"HIK":731,"HOH":-1486,"IHH":128,"IHI":-3041,"IHO":-1935,"IIH":-825,"IIM":-1035,"IOI":-542,"KHH":-1216,"KKA":491,"KKH":-1217,"KOK":-1009,"MHH":-2694,"MHM":-457,"MHO":123,"MMH":-471,"NNH":-1689,"NNO":662,"OHO":-3393}; + this.TC4__ = {"HHH":-203,"HHI":1344,"HHK":365,"HHM":-122,"HHN":182,"HHO":669,"HIH":804,"HII":679,"HOH":446,"IHH":695,"IHO":-2324,"IIH":321,"III":1497,"IIO":656,"IOO":54,"KAK":4845,"KKA":3386,"KKK":3065,"MHH":-405,"MHI":201,"MMH":-241,"MMM":661,"MOM":841}; + this.TQ1__ = {"BHHH":-227,"BHHI":316,"BHIH":-132,"BIHH":60,"BIII":1595,"BNHH":-744,"BOHH":225,"BOOO":-908,"OAKK":482,"OHHH":281,"OHIH":249,"OIHI":200,"OIIH":-68}; + this.TQ2__ = {"BIHH":-1401,"BIII":-1033,"BKAK":-543,"BOOO":-5591}; + this.TQ3__ = {"BHHH":478,"BHHM":-1073,"BHIH":222,"BHII":-504,"BIIH":-116,"BIII":-105,"BMHI":-863,"BMHM":-464,"BOMH":620,"OHHH":346,"OHHI":1729,"OHII":997,"OHMH":481,"OIHH":623,"OIIH":1344,"OKAK":2792,"OKHH":587,"OKKA":679,"OOHH":110,"OOII":-685}; + this.TQ4__ = {"BHHH":-721,"BHHM":-3604,"BHII":-966,"BIIH":-607,"BIII":-2181,"OAAA":-2763,"OAKK":180,"OHHH":-294,"OHHI":2446,"OHHO":480,"OHIH":-1573,"OIHH":1935,"OIHI":-493,"OIIH":626,"OIII":-4007,"OKAK":-8156}; + this.TW1__ = {"につい":-4681,"東京都":2026}; + this.TW2__ = {"ある程":-2049,"いった":-1256,"ころが":-2434,"しょう":3873,"その後":-4430,"だって":-1049,"ていた":1833,"として":-4657,"ともに":-4517,"もので":1882,"一気に":-792,"初めて":-1512,"同時に":-8097,"大きな":-1255,"対して":-2721,"社会党":-3216}; + this.TW3__ = {"いただ":-1734,"してい":1314,"として":-4314,"につい":-5483,"にとっ":-5989,"に当た":-6247,"ので,":-727,"ので、":-727,"のもの":-600,"れから":-3752,"十二月":-2287}; + this.TW4__ = {"いう.":8576,"いう。":8576,"からな":-2348,"してい":2958,"たが,":1516,"たが、":1516,"ている":1538,"という":1349,"ました":5543,"ません":1097,"ようと":-4258,"よると":5865}; + this.UC1__ = {"A":484,"K":93,"M":645,"O":-505}; + this.UC2__ = {"A":819,"H":1059,"I":409,"M":3987,"N":5775,"O":646}; + this.UC3__ = {"A":-1370,"I":2311}; + this.UC4__ = {"A":-2643,"H":1809,"I":-1032,"K":-3450,"M":3565,"N":3876,"O":6646}; + this.UC5__ = {"H":313,"I":-1238,"K":-799,"M":539,"O":-831}; + this.UC6__ = {"H":-506,"I":-253,"K":87,"M":247,"O":-387}; + this.UP1__ = {"O":-214}; + this.UP2__ = {"B":69,"O":935}; + this.UP3__ = {"B":189}; + this.UQ1__ = {"BH":21,"BI":-12,"BK":-99,"BN":142,"BO":-56,"OH":-95,"OI":477,"OK":410,"OO":-2422}; + this.UQ2__ = {"BH":216,"BI":113,"OK":1759}; + this.UQ3__ = {"BA":-479,"BH":42,"BI":1913,"BK":-7198,"BM":3160,"BN":6427,"BO":14761,"OI":-827,"ON":-3212}; + this.UW1__ = {",":156,"、":156,"「":-463,"あ":-941,"う":-127,"が":-553,"き":121,"こ":505,"で":-201,"と":-547,"ど":-123,"に":-789,"の":-185,"は":-847,"も":-466,"や":-470,"よ":182,"ら":-292,"り":208,"れ":169,"を":-446,"ん":-137,"・":-135,"主":-402,"京":-268,"区":-912,"午":871,"国":-460,"大":561,"委":729,"市":-411,"日":-141,"理":361,"生":-408,"県":-386,"都":-718,"「":-463,"・":-135}; + this.UW2__ = {",":-829,"、":-829,"〇":892,"「":-645,"」":3145,"あ":-538,"い":505,"う":134,"お":-502,"か":1454,"が":-856,"く":-412,"こ":1141,"さ":878,"ざ":540,"し":1529,"す":-675,"せ":300,"そ":-1011,"た":188,"だ":1837,"つ":-949,"て":-291,"で":-268,"と":-981,"ど":1273,"な":1063,"に":-1764,"の":130,"は":-409,"ひ":-1273,"べ":1261,"ま":600,"も":-1263,"や":-402,"よ":1639,"り":-579,"る":-694,"れ":571,"を":-2516,"ん":2095,"ア":-587,"カ":306,"キ":568,"ッ":831,"三":-758,"不":-2150,"世":-302,"中":-968,"主":-861,"事":492,"人":-123,"会":978,"保":362,"入":548,"初":-3025,"副":-1566,"北":-3414,"区":-422,"大":-1769,"天":-865,"太":-483,"子":-1519,"学":760,"実":1023,"小":-2009,"市":-813,"年":-1060,"強":1067,"手":-1519,"揺":-1033,"政":1522,"文":-1355,"新":-1682,"日":-1815,"明":-1462,"最":-630,"朝":-1843,"本":-1650,"東":-931,"果":-665,"次":-2378,"民":-180,"気":-1740,"理":752,"発":529,"目":-1584,"相":-242,"県":-1165,"立":-763,"第":810,"米":509,"自":-1353,"行":838,"西":-744,"見":-3874,"調":1010,"議":1198,"込":3041,"開":1758,"間":-1257,"「":-645,"」":3145,"ッ":831,"ア":-587,"カ":306,"キ":568}; + this.UW3__ = {",":4889,"1":-800,"−":-1723,"、":4889,"々":-2311,"〇":5827,"」":2670,"〓":-3573,"あ":-2696,"い":1006,"う":2342,"え":1983,"お":-4864,"か":-1163,"が":3271,"く":1004,"け":388,"げ":401,"こ":-3552,"ご":-3116,"さ":-1058,"し":-395,"す":584,"せ":3685,"そ":-5228,"た":842,"ち":-521,"っ":-1444,"つ":-1081,"て":6167,"で":2318,"と":1691,"ど":-899,"な":-2788,"に":2745,"の":4056,"は":4555,"ひ":-2171,"ふ":-1798,"へ":1199,"ほ":-5516,"ま":-4384,"み":-120,"め":1205,"も":2323,"や":-788,"よ":-202,"ら":727,"り":649,"る":5905,"れ":2773,"わ":-1207,"を":6620,"ん":-518,"ア":551,"グ":1319,"ス":874,"ッ":-1350,"ト":521,"ム":1109,"ル":1591,"ロ":2201,"ン":278,"・":-3794,"一":-1619,"下":-1759,"世":-2087,"両":3815,"中":653,"主":-758,"予":-1193,"二":974,"人":2742,"今":792,"他":1889,"以":-1368,"低":811,"何":4265,"作":-361,"保":-2439,"元":4858,"党":3593,"全":1574,"公":-3030,"六":755,"共":-1880,"円":5807,"再":3095,"分":457,"初":2475,"別":1129,"前":2286,"副":4437,"力":365,"動":-949,"務":-1872,"化":1327,"北":-1038,"区":4646,"千":-2309,"午":-783,"協":-1006,"口":483,"右":1233,"各":3588,"合":-241,"同":3906,"和":-837,"員":4513,"国":642,"型":1389,"場":1219,"外":-241,"妻":2016,"学":-1356,"安":-423,"実":-1008,"家":1078,"小":-513,"少":-3102,"州":1155,"市":3197,"平":-1804,"年":2416,"広":-1030,"府":1605,"度":1452,"建":-2352,"当":-3885,"得":1905,"思":-1291,"性":1822,"戸":-488,"指":-3973,"政":-2013,"教":-1479,"数":3222,"文":-1489,"新":1764,"日":2099,"旧":5792,"昨":-661,"時":-1248,"曜":-951,"最":-937,"月":4125,"期":360,"李":3094,"村":364,"東":-805,"核":5156,"森":2438,"業":484,"氏":2613,"民":-1694,"決":-1073,"法":1868,"海":-495,"無":979,"物":461,"特":-3850,"生":-273,"用":914,"町":1215,"的":7313,"直":-1835,"省":792,"県":6293,"知":-1528,"私":4231,"税":401,"立":-960,"第":1201,"米":7767,"系":3066,"約":3663,"級":1384,"統":-4229,"総":1163,"線":1255,"者":6457,"能":725,"自":-2869,"英":785,"見":1044,"調":-562,"財":-733,"費":1777,"車":1835,"軍":1375,"込":-1504,"通":-1136,"選":-681,"郎":1026,"郡":4404,"部":1200,"金":2163,"長":421,"開":-1432,"間":1302,"関":-1282,"雨":2009,"電":-1045,"非":2066,"駅":1620,"1":-800,"」":2670,"・":-3794,"ッ":-1350,"ア":551,"グ":1319,"ス":874,"ト":521,"ム":1109,"ル":1591,"ロ":2201,"ン":278}; + this.UW4__ = {",":3930,".":3508,"―":-4841,"、":3930,"。":3508,"〇":4999,"「":1895,"」":3798,"〓":-5156,"あ":4752,"い":-3435,"う":-640,"え":-2514,"お":2405,"か":530,"が":6006,"き":-4482,"ぎ":-3821,"く":-3788,"け":-4376,"げ":-4734,"こ":2255,"ご":1979,"さ":2864,"し":-843,"じ":-2506,"す":-731,"ず":1251,"せ":181,"そ":4091,"た":5034,"だ":5408,"ち":-3654,"っ":-5882,"つ":-1659,"て":3994,"で":7410,"と":4547,"な":5433,"に":6499,"ぬ":1853,"ね":1413,"の":7396,"は":8578,"ば":1940,"ひ":4249,"び":-4134,"ふ":1345,"へ":6665,"べ":-744,"ほ":1464,"ま":1051,"み":-2082,"む":-882,"め":-5046,"も":4169,"ゃ":-2666,"や":2795,"ょ":-1544,"よ":3351,"ら":-2922,"り":-9726,"る":-14896,"れ":-2613,"ろ":-4570,"わ":-1783,"を":13150,"ん":-2352,"カ":2145,"コ":1789,"セ":1287,"ッ":-724,"ト":-403,"メ":-1635,"ラ":-881,"リ":-541,"ル":-856,"ン":-3637,"・":-4371,"ー":-11870,"一":-2069,"中":2210,"予":782,"事":-190,"井":-1768,"人":1036,"以":544,"会":950,"体":-1286,"作":530,"側":4292,"先":601,"党":-2006,"共":-1212,"内":584,"円":788,"初":1347,"前":1623,"副":3879,"力":-302,"動":-740,"務":-2715,"化":776,"区":4517,"協":1013,"参":1555,"合":-1834,"和":-681,"員":-910,"器":-851,"回":1500,"国":-619,"園":-1200,"地":866,"場":-1410,"塁":-2094,"士":-1413,"多":1067,"大":571,"子":-4802,"学":-1397,"定":-1057,"寺":-809,"小":1910,"屋":-1328,"山":-1500,"島":-2056,"川":-2667,"市":2771,"年":374,"庁":-4556,"後":456,"性":553,"感":916,"所":-1566,"支":856,"改":787,"政":2182,"教":704,"文":522,"方":-856,"日":1798,"時":1829,"最":845,"月":-9066,"木":-485,"来":-442,"校":-360,"業":-1043,"氏":5388,"民":-2716,"気":-910,"沢":-939,"済":-543,"物":-735,"率":672,"球":-1267,"生":-1286,"産":-1101,"田":-2900,"町":1826,"的":2586,"目":922,"省":-3485,"県":2997,"空":-867,"立":-2112,"第":788,"米":2937,"系":786,"約":2171,"経":1146,"統":-1169,"総":940,"線":-994,"署":749,"者":2145,"能":-730,"般":-852,"行":-792,"規":792,"警":-1184,"議":-244,"谷":-1000,"賞":730,"車":-1481,"軍":1158,"輪":-1433,"込":-3370,"近":929,"道":-1291,"選":2596,"郎":-4866,"都":1192,"野":-1100,"銀":-2213,"長":357,"間":-2344,"院":-2297,"際":-2604,"電":-878,"領":-1659,"題":-792,"館":-1984,"首":1749,"高":2120,"「":1895,"」":3798,"・":-4371,"ッ":-724,"ー":-11870,"カ":2145,"コ":1789,"セ":1287,"ト":-403,"メ":-1635,"ラ":-881,"リ":-541,"ル":-856,"ン":-3637}; + this.UW5__ = {",":465,".":-299,"1":-514,"E2":-32768,"]":-2762,"、":465,"。":-299,"「":363,"あ":1655,"い":331,"う":-503,"え":1199,"お":527,"か":647,"が":-421,"き":1624,"ぎ":1971,"く":312,"げ":-983,"さ":-1537,"し":-1371,"す":-852,"だ":-1186,"ち":1093,"っ":52,"つ":921,"て":-18,"で":-850,"と":-127,"ど":1682,"な":-787,"に":-1224,"の":-635,"は":-578,"べ":1001,"み":502,"め":865,"ゃ":3350,"ょ":854,"り":-208,"る":429,"れ":504,"わ":419,"を":-1264,"ん":327,"イ":241,"ル":451,"ン":-343,"中":-871,"京":722,"会":-1153,"党":-654,"務":3519,"区":-901,"告":848,"員":2104,"大":-1296,"学":-548,"定":1785,"嵐":-1304,"市":-2991,"席":921,"年":1763,"思":872,"所":-814,"挙":1618,"新":-1682,"日":218,"月":-4353,"査":932,"格":1356,"機":-1508,"氏":-1347,"田":240,"町":-3912,"的":-3149,"相":1319,"省":-1052,"県":-4003,"研":-997,"社":-278,"空":-813,"統":1955,"者":-2233,"表":663,"語":-1073,"議":1219,"選":-1018,"郎":-368,"長":786,"間":1191,"題":2368,"館":-689,"1":-514,"E2":-32768,"「":363,"イ":241,"ル":451,"ン":-343}; + this.UW6__ = {",":227,".":808,"1":-270,"E1":306,"、":227,"。":808,"あ":-307,"う":189,"か":241,"が":-73,"く":-121,"こ":-200,"じ":1782,"す":383,"た":-428,"っ":573,"て":-1014,"で":101,"と":-105,"な":-253,"に":-149,"の":-417,"は":-236,"も":-206,"り":187,"る":-135,"を":195,"ル":-673,"ン":-496,"一":-277,"中":201,"件":-800,"会":624,"前":302,"区":1792,"員":-1212,"委":798,"学":-960,"市":887,"広":-695,"後":535,"業":-697,"相":753,"社":-507,"福":974,"空":-822,"者":1811,"連":463,"郎":1082,"1":-270,"E1":306,"ル":-673,"ン":-496}; + + return this; + } + TinySegmenter.prototype.ctype_ = function(str) { + for (var i in this.chartype_) { + if (str.match(this.chartype_[i][0])) { + return this.chartype_[i][1]; + } + } + return "O"; + } + + TinySegmenter.prototype.ts_ = function(v) { + if (v) { return v; } + return 0; + } + + TinySegmenter.prototype.segment = function(input) { + if (input == null || input == undefined || input == "") { + return []; + } + var result = []; + var seg = ["B3","B2","B1"]; + var ctype = ["O","O","O"]; + var o = input.split(""); + for (i = 0; i < o.length; ++i) { + seg.push(o[i]); + ctype.push(this.ctype_(o[i])) + } + seg.push("E1"); + seg.push("E2"); + seg.push("E3"); + ctype.push("O"); + ctype.push("O"); + ctype.push("O"); + var word = seg[3]; + var p1 = "U"; + var p2 = "U"; + var p3 = "U"; + for (var i = 4; i < seg.length - 3; ++i) { + var score = this.BIAS__; + var w1 = seg[i-3]; + var w2 = seg[i-2]; + var w3 = seg[i-1]; + var w4 = seg[i]; + var w5 = seg[i+1]; + var w6 = seg[i+2]; + var c1 = ctype[i-3]; + var c2 = ctype[i-2]; + var c3 = ctype[i-1]; + var c4 = ctype[i]; + var c5 = ctype[i+1]; + var c6 = ctype[i+2]; + score += this.ts_(this.UP1__[p1]); + score += this.ts_(this.UP2__[p2]); + score += this.ts_(this.UP3__[p3]); + score += this.ts_(this.BP1__[p1 + p2]); + score += this.ts_(this.BP2__[p2 + p3]); + score += this.ts_(this.UW1__[w1]); + score += this.ts_(this.UW2__[w2]); + score += this.ts_(this.UW3__[w3]); + score += this.ts_(this.UW4__[w4]); + score += this.ts_(this.UW5__[w5]); + score += this.ts_(this.UW6__[w6]); + score += this.ts_(this.BW1__[w2 + w3]); + score += this.ts_(this.BW2__[w3 + w4]); + score += this.ts_(this.BW3__[w4 + w5]); + score += this.ts_(this.TW1__[w1 + w2 + w3]); + score += this.ts_(this.TW2__[w2 + w3 + w4]); + score += this.ts_(this.TW3__[w3 + w4 + w5]); + score += this.ts_(this.TW4__[w4 + w5 + w6]); + score += this.ts_(this.UC1__[c1]); + score += this.ts_(this.UC2__[c2]); + score += this.ts_(this.UC3__[c3]); + score += this.ts_(this.UC4__[c4]); + score += this.ts_(this.UC5__[c5]); + score += this.ts_(this.UC6__[c6]); + score += this.ts_(this.BC1__[c2 + c3]); + score += this.ts_(this.BC2__[c3 + c4]); + score += this.ts_(this.BC3__[c4 + c5]); + score += this.ts_(this.TC1__[c1 + c2 + c3]); + score += this.ts_(this.TC2__[c2 + c3 + c4]); + score += this.ts_(this.TC3__[c3 + c4 + c5]); + score += this.ts_(this.TC4__[c4 + c5 + c6]); + // score += this.ts_(this.TC5__[c4 + c5 + c6]); + score += this.ts_(this.UQ1__[p1 + c1]); + score += this.ts_(this.UQ2__[p2 + c2]); + score += this.ts_(this.UQ3__[p3 + c3]); + score += this.ts_(this.BQ1__[p2 + c2 + c3]); + score += this.ts_(this.BQ2__[p2 + c3 + c4]); + score += this.ts_(this.BQ3__[p3 + c2 + c3]); + score += this.ts_(this.BQ4__[p3 + c3 + c4]); + score += this.ts_(this.TQ1__[p2 + c1 + c2 + c3]); + score += this.ts_(this.TQ2__[p2 + c2 + c3 + c4]); + score += this.ts_(this.TQ3__[p3 + c1 + c2 + c3]); + score += this.ts_(this.TQ4__[p3 + c2 + c3 + c4]); + var p = "O"; + if (score > 0) { + result.push(word); + word = ""; + p = "B"; + } + p1 = p2; + p2 = p3; + p3 = p; + word += seg[i]; + } + result.push(word); + + return result; + } + + lunr.TinySegmenter = TinySegmenter; + }; + +})); \ No newline at end of file diff --git a/develop/assets/javascripts/lunr/wordcut.js b/develop/assets/javascripts/lunr/wordcut.js new file mode 100644 index 0000000..146f4b4 --- /dev/null +++ b/develop/assets/javascripts/lunr/wordcut.js @@ -0,0 +1,6708 @@ +(function(f){if(typeof exports==="object"&&typeof module!=="undefined"){module.exports=f()}else if(typeof define==="function"&&define.amd){define([],f)}else{var g;if(typeof window!=="undefined"){g=window}else if(typeof global!=="undefined"){g=global}else if(typeof self!=="undefined"){g=self}else{g=this}(g.lunr || (g.lunr = {})).wordcut = f()}})(function(){var define,module,exports;return (function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o 1; + }) + this.addWords(words, false) + } + if(finalize){ + this.finalizeDict(); + } + }, + + dictSeek: function (l, r, ch, strOffset, pos) { + var ans = null; + while (l <= r) { + var m = Math.floor((l + r) / 2), + dict_item = this.dict[m], + len = dict_item.length; + if (len <= strOffset) { + l = m + 1; + } else { + var ch_ = dict_item[strOffset]; + if (ch_ < ch) { + l = m + 1; + } else if (ch_ > ch) { + r = m - 1; + } else { + ans = m; + if (pos == LEFT) { + r = m - 1; + } else { + l = m + 1; + } + } + } + } + return ans; + }, + + isFinal: function (acceptor) { + return this.dict[acceptor.l].length == acceptor.strOffset; + }, + + createAcceptor: function () { + return { + l: 0, + r: this.dict.length - 1, + strOffset: 0, + isFinal: false, + dict: this, + transit: function (ch) { + return this.dict.transit(this, ch); + }, + isError: false, + tag: "DICT", + w: 1, + type: "DICT" + }; + }, + + transit: function (acceptor, ch) { + var l = this.dictSeek(acceptor.l, + acceptor.r, + ch, + acceptor.strOffset, + LEFT); + if (l !== null) { + var r = this.dictSeek(l, + acceptor.r, + ch, + acceptor.strOffset, + RIGHT); + acceptor.l = l; + acceptor.r = r; + acceptor.strOffset++; + acceptor.isFinal = this.isFinal(acceptor); + } else { + acceptor.isError = true; + } + return acceptor; + }, + + sortuniq: function(a){ + return a.sort().filter(function(item, pos, arr){ + return !pos || item != arr[pos - 1]; + }) + }, + + flatten: function(a){ + //[[1,2],[3]] -> [1,2,3] + return [].concat.apply([], a); + } +}; +module.exports = WordcutDict; + +}).call(this,"/dist/tmp") +},{"glob":16,"path":22}],3:[function(require,module,exports){ +var WordRule = { + createAcceptor: function(tag) { + if (tag["WORD_RULE"]) + return null; + + return {strOffset: 0, + isFinal: false, + transit: function(ch) { + var lch = ch.toLowerCase(); + if (lch >= "a" && lch <= "z") { + this.isFinal = true; + this.strOffset++; + } else { + this.isError = true; + } + return this; + }, + isError: false, + tag: "WORD_RULE", + type: "WORD_RULE", + w: 1}; + } +}; + +var NumberRule = { + createAcceptor: function(tag) { + if (tag["NUMBER_RULE"]) + return null; + + return {strOffset: 0, + isFinal: false, + transit: function(ch) { + if (ch >= "0" && ch <= "9") { + this.isFinal = true; + this.strOffset++; + } else { + this.isError = true; + } + return this; + }, + isError: false, + tag: "NUMBER_RULE", + type: "NUMBER_RULE", + w: 1}; + } +}; + +var SpaceRule = { + tag: "SPACE_RULE", + createAcceptor: function(tag) { + + if (tag["SPACE_RULE"]) + return null; + + return {strOffset: 0, + isFinal: false, + transit: function(ch) { + if (ch == " " || ch == "\t" || ch == "\r" || ch == "\n" || + ch == "\u00A0" || ch=="\u2003"//nbsp and emsp + ) { + this.isFinal = true; + this.strOffset++; + } else { + this.isError = true; + } + return this; + }, + isError: false, + tag: SpaceRule.tag, + w: 1, + type: "SPACE_RULE"}; + } +} + +var SingleSymbolRule = { + tag: "SINSYM", + createAcceptor: function(tag) { + return {strOffset: 0, + isFinal: false, + transit: function(ch) { + if (this.strOffset == 0 && ch.match(/^[\@\(\)\/\,\-\."`]$/)) { + this.isFinal = true; + this.strOffset++; + } else { + this.isError = true; + } + return this; + }, + isError: false, + tag: "SINSYM", + w: 1, + type: "SINSYM"}; + } +} + + +var LatinRules = [WordRule, SpaceRule, SingleSymbolRule, NumberRule]; + +module.exports = LatinRules; + +},{}],4:[function(require,module,exports){ +var _ = require("underscore") + , WordcutCore = require("./wordcut_core"); +var PathInfoBuilder = { + + /* + buildByPartAcceptors: function(path, acceptors, i) { + var + var genInfos = partAcceptors.reduce(function(genInfos, acceptor) { + + }, []); + + return genInfos; + } + */ + + buildByAcceptors: function(path, finalAcceptors, i) { + var self = this; + var infos = finalAcceptors.map(function(acceptor) { + var p = i - acceptor.strOffset + 1 + , _info = path[p]; + + var info = {p: p, + mw: _info.mw + (acceptor.mw === undefined ? 0 : acceptor.mw), + w: acceptor.w + _info.w, + unk: (acceptor.unk ? acceptor.unk : 0) + _info.unk, + type: acceptor.type}; + + if (acceptor.type == "PART") { + for(var j = p + 1; j <= i; j++) { + path[j].merge = p; + } + info.merge = p; + } + + return info; + }); + return infos.filter(function(info) { return info; }); + }, + + fallback: function(path, leftBoundary, text, i) { + var _info = path[leftBoundary]; + if (text[i].match(/[\u0E48-\u0E4E]/)) { + if (leftBoundary != 0) + leftBoundary = path[leftBoundary].p; + return {p: leftBoundary, + mw: 0, + w: 1 + _info.w, + unk: 1 + _info.unk, + type: "UNK"}; +/* } else if(leftBoundary > 0 && path[leftBoundary].type !== "UNK") { + leftBoundary = path[leftBoundary].p; + return {p: leftBoundary, + w: 1 + _info.w, + unk: 1 + _info.unk, + type: "UNK"}; */ + } else { + return {p: leftBoundary, + mw: _info.mw, + w: 1 + _info.w, + unk: 1 + _info.unk, + type: "UNK"}; + } + }, + + build: function(path, finalAcceptors, i, leftBoundary, text) { + var basicPathInfos = this.buildByAcceptors(path, finalAcceptors, i); + if (basicPathInfos.length > 0) { + return basicPathInfos; + } else { + return [this.fallback(path, leftBoundary, text, i)]; + } + } +}; + +module.exports = function() { + return _.clone(PathInfoBuilder); +} + +},{"./wordcut_core":8,"underscore":25}],5:[function(require,module,exports){ +var _ = require("underscore"); + + +var PathSelector = { + selectPath: function(paths) { + var path = paths.reduce(function(selectedPath, path) { + if (selectedPath == null) { + return path; + } else { + if (path.unk < selectedPath.unk) + return path; + if (path.unk == selectedPath.unk) { + if (path.mw < selectedPath.mw) + return path + if (path.mw == selectedPath.mw) { + if (path.w < selectedPath.w) + return path; + } + } + return selectedPath; + } + }, null); + return path; + }, + + createPath: function() { + return [{p:null, w:0, unk:0, type: "INIT", mw:0}]; + } +}; + +module.exports = function() { + return _.clone(PathSelector); +}; + +},{"underscore":25}],6:[function(require,module,exports){ +function isMatch(pat, offset, ch) { + if (pat.length <= offset) + return false; + var _ch = pat[offset]; + return _ch == ch || + (_ch.match(/[กข]/) && ch.match(/[ก-ฮ]/)) || + (_ch.match(/[มบ]/) && ch.match(/[ก-ฮ]/)) || + (_ch.match(/\u0E49/) && ch.match(/[\u0E48-\u0E4B]/)); +} + +var Rule0 = { + pat: "เหก็ม", + createAcceptor: function(tag) { + return {strOffset: 0, + isFinal: false, + transit: function(ch) { + if (isMatch(Rule0.pat, this.strOffset,ch)) { + this.isFinal = (this.strOffset + 1 == Rule0.pat.length); + this.strOffset++; + } else { + this.isError = true; + } + return this; + }, + isError: false, + tag: "THAI_RULE", + type: "THAI_RULE", + w: 1}; + } +}; + +var PartRule = { + createAcceptor: function(tag) { + return {strOffset: 0, + patterns: [ + "แก", "เก", "ก้", "กก์", "กา", "กี", "กิ", "กืก" + ], + isFinal: false, + transit: function(ch) { + var offset = this.strOffset; + this.patterns = this.patterns.filter(function(pat) { + return isMatch(pat, offset, ch); + }); + + if (this.patterns.length > 0) { + var len = 1 + offset; + this.isFinal = this.patterns.some(function(pat) { + return pat.length == len; + }); + this.strOffset++; + } else { + this.isError = true; + } + return this; + }, + isError: false, + tag: "PART", + type: "PART", + unk: 1, + w: 1}; + } +}; + +var ThaiRules = [Rule0, PartRule]; + +module.exports = ThaiRules; + +},{}],7:[function(require,module,exports){ +var sys = require("sys") + , WordcutDict = require("./dict") + , WordcutCore = require("./wordcut_core") + , PathInfoBuilder = require("./path_info_builder") + , PathSelector = require("./path_selector") + , Acceptors = require("./acceptors") + , latinRules = require("./latin_rules") + , thaiRules = require("./thai_rules") + , _ = require("underscore"); + + +var Wordcut = Object.create(WordcutCore); +Wordcut.defaultPathInfoBuilder = PathInfoBuilder; +Wordcut.defaultPathSelector = PathSelector; +Wordcut.defaultAcceptors = Acceptors; +Wordcut.defaultLatinRules = latinRules; +Wordcut.defaultThaiRules = thaiRules; +Wordcut.defaultDict = WordcutDict; + + +Wordcut.initNoDict = function(dict_path) { + var self = this; + self.pathInfoBuilder = new self.defaultPathInfoBuilder; + self.pathSelector = new self.defaultPathSelector; + self.acceptors = new self.defaultAcceptors; + self.defaultLatinRules.forEach(function(rule) { + self.acceptors.creators.push(rule); + }); + self.defaultThaiRules.forEach(function(rule) { + self.acceptors.creators.push(rule); + }); +}; + +Wordcut.init = function(dict_path, withDefault, additionalWords) { + withDefault = withDefault || false; + this.initNoDict(); + var dict = _.clone(this.defaultDict); + dict.init(dict_path, withDefault, additionalWords); + this.acceptors.creators.push(dict); +}; + +module.exports = Wordcut; + +},{"./acceptors":1,"./dict":2,"./latin_rules":3,"./path_info_builder":4,"./path_selector":5,"./thai_rules":6,"./wordcut_core":8,"sys":28,"underscore":25}],8:[function(require,module,exports){ +var WordcutCore = { + + buildPath: function(text) { + var self = this + , path = self.pathSelector.createPath() + , leftBoundary = 0; + self.acceptors.reset(); + for (var i = 0; i < text.length; i++) { + var ch = text[i]; + self.acceptors.transit(ch); + + var possiblePathInfos = self + .pathInfoBuilder + .build(path, + self.acceptors.getFinalAcceptors(), + i, + leftBoundary, + text); + var selectedPath = self.pathSelector.selectPath(possiblePathInfos) + + path.push(selectedPath); + if (selectedPath.type !== "UNK") { + leftBoundary = i; + } + } + return path; + }, + + pathToRanges: function(path) { + var e = path.length - 1 + , ranges = []; + + while (e > 0) { + var info = path[e] + , s = info.p; + + if (info.merge !== undefined && ranges.length > 0) { + var r = ranges[ranges.length - 1]; + r.s = info.merge; + s = r.s; + } else { + ranges.push({s:s, e:e}); + } + e = s; + } + return ranges.reverse(); + }, + + rangesToText: function(text, ranges, delimiter) { + return ranges.map(function(r) { + return text.substring(r.s, r.e); + }).join(delimiter); + }, + + cut: function(text, delimiter) { + var path = this.buildPath(text) + , ranges = this.pathToRanges(path); + return this + .rangesToText(text, ranges, + (delimiter === undefined ? "|" : delimiter)); + }, + + cutIntoRanges: function(text, noText) { + var path = this.buildPath(text) + , ranges = this.pathToRanges(path); + + if (!noText) { + ranges.forEach(function(r) { + r.text = text.substring(r.s, r.e); + }); + } + return ranges; + }, + + cutIntoArray: function(text) { + var path = this.buildPath(text) + , ranges = this.pathToRanges(path); + + return ranges.map(function(r) { + return text.substring(r.s, r.e) + }); + } +}; + +module.exports = WordcutCore; + +},{}],9:[function(require,module,exports){ +// http://wiki.commonjs.org/wiki/Unit_Testing/1.0 +// +// THIS IS NOT TESTED NOR LIKELY TO WORK OUTSIDE V8! +// +// Originally from narwhal.js (http://narwhaljs.org) +// Copyright (c) 2009 Thomas Robinson <280north.com> +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the 'Software'), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN +// ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +// when used in node, this will actually load the util module we depend on +// versus loading the builtin util module as happens otherwise +// this is a bug in node module loading as far as I am concerned +var util = require('util/'); + +var pSlice = Array.prototype.slice; +var hasOwn = Object.prototype.hasOwnProperty; + +// 1. The assert module provides functions that throw +// AssertionError's when particular conditions are not met. The +// assert module must conform to the following interface. + +var assert = module.exports = ok; + +// 2. The AssertionError is defined in assert. +// new assert.AssertionError({ message: message, +// actual: actual, +// expected: expected }) + +assert.AssertionError = function AssertionError(options) { + this.name = 'AssertionError'; + this.actual = options.actual; + this.expected = options.expected; + this.operator = options.operator; + if (options.message) { + this.message = options.message; + this.generatedMessage = false; + } else { + this.message = getMessage(this); + this.generatedMessage = true; + } + var stackStartFunction = options.stackStartFunction || fail; + + if (Error.captureStackTrace) { + Error.captureStackTrace(this, stackStartFunction); + } + else { + // non v8 browsers so we can have a stacktrace + var err = new Error(); + if (err.stack) { + var out = err.stack; + + // try to strip useless frames + var fn_name = stackStartFunction.name; + var idx = out.indexOf('\n' + fn_name); + if (idx >= 0) { + // once we have located the function frame + // we need to strip out everything before it (and its line) + var next_line = out.indexOf('\n', idx + 1); + out = out.substring(next_line + 1); + } + + this.stack = out; + } + } +}; + +// assert.AssertionError instanceof Error +util.inherits(assert.AssertionError, Error); + +function replacer(key, value) { + if (util.isUndefined(value)) { + return '' + value; + } + if (util.isNumber(value) && !isFinite(value)) { + return value.toString(); + } + if (util.isFunction(value) || util.isRegExp(value)) { + return value.toString(); + } + return value; +} + +function truncate(s, n) { + if (util.isString(s)) { + return s.length < n ? s : s.slice(0, n); + } else { + return s; + } +} + +function getMessage(self) { + return truncate(JSON.stringify(self.actual, replacer), 128) + ' ' + + self.operator + ' ' + + truncate(JSON.stringify(self.expected, replacer), 128); +} + +// At present only the three keys mentioned above are used and +// understood by the spec. Implementations or sub modules can pass +// other keys to the AssertionError's constructor - they will be +// ignored. + +// 3. All of the following functions must throw an AssertionError +// when a corresponding condition is not met, with a message that +// may be undefined if not provided. All assertion methods provide +// both the actual and expected values to the assertion error for +// display purposes. + +function fail(actual, expected, message, operator, stackStartFunction) { + throw new assert.AssertionError({ + message: message, + actual: actual, + expected: expected, + operator: operator, + stackStartFunction: stackStartFunction + }); +} + +// EXTENSION! allows for well behaved errors defined elsewhere. +assert.fail = fail; + +// 4. Pure assertion tests whether a value is truthy, as determined +// by !!guard. +// assert.ok(guard, message_opt); +// This statement is equivalent to assert.equal(true, !!guard, +// message_opt);. To test strictly for the value true, use +// assert.strictEqual(true, guard, message_opt);. + +function ok(value, message) { + if (!value) fail(value, true, message, '==', assert.ok); +} +assert.ok = ok; + +// 5. The equality assertion tests shallow, coercive equality with +// ==. +// assert.equal(actual, expected, message_opt); + +assert.equal = function equal(actual, expected, message) { + if (actual != expected) fail(actual, expected, message, '==', assert.equal); +}; + +// 6. The non-equality assertion tests for whether two objects are not equal +// with != assert.notEqual(actual, expected, message_opt); + +assert.notEqual = function notEqual(actual, expected, message) { + if (actual == expected) { + fail(actual, expected, message, '!=', assert.notEqual); + } +}; + +// 7. The equivalence assertion tests a deep equality relation. +// assert.deepEqual(actual, expected, message_opt); + +assert.deepEqual = function deepEqual(actual, expected, message) { + if (!_deepEqual(actual, expected)) { + fail(actual, expected, message, 'deepEqual', assert.deepEqual); + } +}; + +function _deepEqual(actual, expected) { + // 7.1. All identical values are equivalent, as determined by ===. + if (actual === expected) { + return true; + + } else if (util.isBuffer(actual) && util.isBuffer(expected)) { + if (actual.length != expected.length) return false; + + for (var i = 0; i < actual.length; i++) { + if (actual[i] !== expected[i]) return false; + } + + return true; + + // 7.2. If the expected value is a Date object, the actual value is + // equivalent if it is also a Date object that refers to the same time. + } else if (util.isDate(actual) && util.isDate(expected)) { + return actual.getTime() === expected.getTime(); + + // 7.3 If the expected value is a RegExp object, the actual value is + // equivalent if it is also a RegExp object with the same source and + // properties (`global`, `multiline`, `lastIndex`, `ignoreCase`). + } else if (util.isRegExp(actual) && util.isRegExp(expected)) { + return actual.source === expected.source && + actual.global === expected.global && + actual.multiline === expected.multiline && + actual.lastIndex === expected.lastIndex && + actual.ignoreCase === expected.ignoreCase; + + // 7.4. Other pairs that do not both pass typeof value == 'object', + // equivalence is determined by ==. + } else if (!util.isObject(actual) && !util.isObject(expected)) { + return actual == expected; + + // 7.5 For all other Object pairs, including Array objects, equivalence is + // determined by having the same number of owned properties (as verified + // with Object.prototype.hasOwnProperty.call), the same set of keys + // (although not necessarily the same order), equivalent values for every + // corresponding key, and an identical 'prototype' property. Note: this + // accounts for both named and indexed properties on Arrays. + } else { + return objEquiv(actual, expected); + } +} + +function isArguments(object) { + return Object.prototype.toString.call(object) == '[object Arguments]'; +} + +function objEquiv(a, b) { + if (util.isNullOrUndefined(a) || util.isNullOrUndefined(b)) + return false; + // an identical 'prototype' property. + if (a.prototype !== b.prototype) return false; + // if one is a primitive, the other must be same + if (util.isPrimitive(a) || util.isPrimitive(b)) { + return a === b; + } + var aIsArgs = isArguments(a), + bIsArgs = isArguments(b); + if ((aIsArgs && !bIsArgs) || (!aIsArgs && bIsArgs)) + return false; + if (aIsArgs) { + a = pSlice.call(a); + b = pSlice.call(b); + return _deepEqual(a, b); + } + var ka = objectKeys(a), + kb = objectKeys(b), + key, i; + // having the same number of owned properties (keys incorporates + // hasOwnProperty) + if (ka.length != kb.length) + return false; + //the same set of keys (although not necessarily the same order), + ka.sort(); + kb.sort(); + //~~~cheap key test + for (i = ka.length - 1; i >= 0; i--) { + if (ka[i] != kb[i]) + return false; + } + //equivalent values for every corresponding key, and + //~~~possibly expensive deep test + for (i = ka.length - 1; i >= 0; i--) { + key = ka[i]; + if (!_deepEqual(a[key], b[key])) return false; + } + return true; +} + +// 8. The non-equivalence assertion tests for any deep inequality. +// assert.notDeepEqual(actual, expected, message_opt); + +assert.notDeepEqual = function notDeepEqual(actual, expected, message) { + if (_deepEqual(actual, expected)) { + fail(actual, expected, message, 'notDeepEqual', assert.notDeepEqual); + } +}; + +// 9. The strict equality assertion tests strict equality, as determined by ===. +// assert.strictEqual(actual, expected, message_opt); + +assert.strictEqual = function strictEqual(actual, expected, message) { + if (actual !== expected) { + fail(actual, expected, message, '===', assert.strictEqual); + } +}; + +// 10. The strict non-equality assertion tests for strict inequality, as +// determined by !==. assert.notStrictEqual(actual, expected, message_opt); + +assert.notStrictEqual = function notStrictEqual(actual, expected, message) { + if (actual === expected) { + fail(actual, expected, message, '!==', assert.notStrictEqual); + } +}; + +function expectedException(actual, expected) { + if (!actual || !expected) { + return false; + } + + if (Object.prototype.toString.call(expected) == '[object RegExp]') { + return expected.test(actual); + } else if (actual instanceof expected) { + return true; + } else if (expected.call({}, actual) === true) { + return true; + } + + return false; +} + +function _throws(shouldThrow, block, expected, message) { + var actual; + + if (util.isString(expected)) { + message = expected; + expected = null; + } + + try { + block(); + } catch (e) { + actual = e; + } + + message = (expected && expected.name ? ' (' + expected.name + ').' : '.') + + (message ? ' ' + message : '.'); + + if (shouldThrow && !actual) { + fail(actual, expected, 'Missing expected exception' + message); + } + + if (!shouldThrow && expectedException(actual, expected)) { + fail(actual, expected, 'Got unwanted exception' + message); + } + + if ((shouldThrow && actual && expected && + !expectedException(actual, expected)) || (!shouldThrow && actual)) { + throw actual; + } +} + +// 11. Expected to throw an error: +// assert.throws(block, Error_opt, message_opt); + +assert.throws = function(block, /*optional*/error, /*optional*/message) { + _throws.apply(this, [true].concat(pSlice.call(arguments))); +}; + +// EXTENSION! This is annoying to write outside this module. +assert.doesNotThrow = function(block, /*optional*/message) { + _throws.apply(this, [false].concat(pSlice.call(arguments))); +}; + +assert.ifError = function(err) { if (err) {throw err;}}; + +var objectKeys = Object.keys || function (obj) { + var keys = []; + for (var key in obj) { + if (hasOwn.call(obj, key)) keys.push(key); + } + return keys; +}; + +},{"util/":28}],10:[function(require,module,exports){ +'use strict'; +module.exports = balanced; +function balanced(a, b, str) { + if (a instanceof RegExp) a = maybeMatch(a, str); + if (b instanceof RegExp) b = maybeMatch(b, str); + + var r = range(a, b, str); + + return r && { + start: r[0], + end: r[1], + pre: str.slice(0, r[0]), + body: str.slice(r[0] + a.length, r[1]), + post: str.slice(r[1] + b.length) + }; +} + +function maybeMatch(reg, str) { + var m = str.match(reg); + return m ? m[0] : null; +} + +balanced.range = range; +function range(a, b, str) { + var begs, beg, left, right, result; + var ai = str.indexOf(a); + var bi = str.indexOf(b, ai + 1); + var i = ai; + + if (ai >= 0 && bi > 0) { + begs = []; + left = str.length; + + while (i >= 0 && !result) { + if (i == ai) { + begs.push(i); + ai = str.indexOf(a, i + 1); + } else if (begs.length == 1) { + result = [ begs.pop(), bi ]; + } else { + beg = begs.pop(); + if (beg < left) { + left = beg; + right = bi; + } + + bi = str.indexOf(b, i + 1); + } + + i = ai < bi && ai >= 0 ? ai : bi; + } + + if (begs.length) { + result = [ left, right ]; + } + } + + return result; +} + +},{}],11:[function(require,module,exports){ +var concatMap = require('concat-map'); +var balanced = require('balanced-match'); + +module.exports = expandTop; + +var escSlash = '\0SLASH'+Math.random()+'\0'; +var escOpen = '\0OPEN'+Math.random()+'\0'; +var escClose = '\0CLOSE'+Math.random()+'\0'; +var escComma = '\0COMMA'+Math.random()+'\0'; +var escPeriod = '\0PERIOD'+Math.random()+'\0'; + +function numeric(str) { + return parseInt(str, 10) == str + ? parseInt(str, 10) + : str.charCodeAt(0); +} + +function escapeBraces(str) { + return str.split('\\\\').join(escSlash) + .split('\\{').join(escOpen) + .split('\\}').join(escClose) + .split('\\,').join(escComma) + .split('\\.').join(escPeriod); +} + +function unescapeBraces(str) { + return str.split(escSlash).join('\\') + .split(escOpen).join('{') + .split(escClose).join('}') + .split(escComma).join(',') + .split(escPeriod).join('.'); +} + + +// Basically just str.split(","), but handling cases +// where we have nested braced sections, which should be +// treated as individual members, like {a,{b,c},d} +function parseCommaParts(str) { + if (!str) + return ['']; + + var parts = []; + var m = balanced('{', '}', str); + + if (!m) + return str.split(','); + + var pre = m.pre; + var body = m.body; + var post = m.post; + var p = pre.split(','); + + p[p.length-1] += '{' + body + '}'; + var postParts = parseCommaParts(post); + if (post.length) { + p[p.length-1] += postParts.shift(); + p.push.apply(p, postParts); + } + + parts.push.apply(parts, p); + + return parts; +} + +function expandTop(str) { + if (!str) + return []; + + // I don't know why Bash 4.3 does this, but it does. + // Anything starting with {} will have the first two bytes preserved + // but *only* at the top level, so {},a}b will not expand to anything, + // but a{},b}c will be expanded to [a}c,abc]. + // One could argue that this is a bug in Bash, but since the goal of + // this module is to match Bash's rules, we escape a leading {} + if (str.substr(0, 2) === '{}') { + str = '\\{\\}' + str.substr(2); + } + + return expand(escapeBraces(str), true).map(unescapeBraces); +} + +function identity(e) { + return e; +} + +function embrace(str) { + return '{' + str + '}'; +} +function isPadded(el) { + return /^-?0\d/.test(el); +} + +function lte(i, y) { + return i <= y; +} +function gte(i, y) { + return i >= y; +} + +function expand(str, isTop) { + var expansions = []; + + var m = balanced('{', '}', str); + if (!m || /\$$/.test(m.pre)) return [str]; + + var isNumericSequence = /^-?\d+\.\.-?\d+(?:\.\.-?\d+)?$/.test(m.body); + var isAlphaSequence = /^[a-zA-Z]\.\.[a-zA-Z](?:\.\.-?\d+)?$/.test(m.body); + var isSequence = isNumericSequence || isAlphaSequence; + var isOptions = m.body.indexOf(',') >= 0; + if (!isSequence && !isOptions) { + // {a},b} + if (m.post.match(/,.*\}/)) { + str = m.pre + '{' + m.body + escClose + m.post; + return expand(str); + } + return [str]; + } + + var n; + if (isSequence) { + n = m.body.split(/\.\./); + } else { + n = parseCommaParts(m.body); + if (n.length === 1) { + // x{{a,b}}y ==> x{a}y x{b}y + n = expand(n[0], false).map(embrace); + if (n.length === 1) { + var post = m.post.length + ? expand(m.post, false) + : ['']; + return post.map(function(p) { + return m.pre + n[0] + p; + }); + } + } + } + + // at this point, n is the parts, and we know it's not a comma set + // with a single entry. + + // no need to expand pre, since it is guaranteed to be free of brace-sets + var pre = m.pre; + var post = m.post.length + ? expand(m.post, false) + : ['']; + + var N; + + if (isSequence) { + var x = numeric(n[0]); + var y = numeric(n[1]); + var width = Math.max(n[0].length, n[1].length) + var incr = n.length == 3 + ? Math.abs(numeric(n[2])) + : 1; + var test = lte; + var reverse = y < x; + if (reverse) { + incr *= -1; + test = gte; + } + var pad = n.some(isPadded); + + N = []; + + for (var i = x; test(i, y); i += incr) { + var c; + if (isAlphaSequence) { + c = String.fromCharCode(i); + if (c === '\\') + c = ''; + } else { + c = String(i); + if (pad) { + var need = width - c.length; + if (need > 0) { + var z = new Array(need + 1).join('0'); + if (i < 0) + c = '-' + z + c.slice(1); + else + c = z + c; + } + } + } + N.push(c); + } + } else { + N = concatMap(n, function(el) { return expand(el, false) }); + } + + for (var j = 0; j < N.length; j++) { + for (var k = 0; k < post.length; k++) { + var expansion = pre + N[j] + post[k]; + if (!isTop || isSequence || expansion) + expansions.push(expansion); + } + } + + return expansions; +} + + +},{"balanced-match":10,"concat-map":13}],12:[function(require,module,exports){ + +},{}],13:[function(require,module,exports){ +module.exports = function (xs, fn) { + var res = []; + for (var i = 0; i < xs.length; i++) { + var x = fn(xs[i], i); + if (isArray(x)) res.push.apply(res, x); + else res.push(x); + } + return res; +}; + +var isArray = Array.isArray || function (xs) { + return Object.prototype.toString.call(xs) === '[object Array]'; +}; + +},{}],14:[function(require,module,exports){ +// Copyright Joyent, Inc. and other Node contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to permit +// persons to whom the Software is furnished to do so, subject to the +// following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +// USE OR OTHER DEALINGS IN THE SOFTWARE. + +function EventEmitter() { + this._events = this._events || {}; + this._maxListeners = this._maxListeners || undefined; +} +module.exports = EventEmitter; + +// Backwards-compat with node 0.10.x +EventEmitter.EventEmitter = EventEmitter; + +EventEmitter.prototype._events = undefined; +EventEmitter.prototype._maxListeners = undefined; + +// By default EventEmitters will print a warning if more than 10 listeners are +// added to it. This is a useful default which helps finding memory leaks. +EventEmitter.defaultMaxListeners = 10; + +// Obviously not all Emitters should be limited to 10. This function allows +// that to be increased. Set to zero for unlimited. +EventEmitter.prototype.setMaxListeners = function(n) { + if (!isNumber(n) || n < 0 || isNaN(n)) + throw TypeError('n must be a positive number'); + this._maxListeners = n; + return this; +}; + +EventEmitter.prototype.emit = function(type) { + var er, handler, len, args, i, listeners; + + if (!this._events) + this._events = {}; + + // If there is no 'error' event listener then throw. + if (type === 'error') { + if (!this._events.error || + (isObject(this._events.error) && !this._events.error.length)) { + er = arguments[1]; + if (er instanceof Error) { + throw er; // Unhandled 'error' event + } + throw TypeError('Uncaught, unspecified "error" event.'); + } + } + + handler = this._events[type]; + + if (isUndefined(handler)) + return false; + + if (isFunction(handler)) { + switch (arguments.length) { + // fast cases + case 1: + handler.call(this); + break; + case 2: + handler.call(this, arguments[1]); + break; + case 3: + handler.call(this, arguments[1], arguments[2]); + break; + // slower + default: + len = arguments.length; + args = new Array(len - 1); + for (i = 1; i < len; i++) + args[i - 1] = arguments[i]; + handler.apply(this, args); + } + } else if (isObject(handler)) { + len = arguments.length; + args = new Array(len - 1); + for (i = 1; i < len; i++) + args[i - 1] = arguments[i]; + + listeners = handler.slice(); + len = listeners.length; + for (i = 0; i < len; i++) + listeners[i].apply(this, args); + } + + return true; +}; + +EventEmitter.prototype.addListener = function(type, listener) { + var m; + + if (!isFunction(listener)) + throw TypeError('listener must be a function'); + + if (!this._events) + this._events = {}; + + // To avoid recursion in the case that type === "newListener"! Before + // adding it to the listeners, first emit "newListener". + if (this._events.newListener) + this.emit('newListener', type, + isFunction(listener.listener) ? + listener.listener : listener); + + if (!this._events[type]) + // Optimize the case of one listener. Don't need the extra array object. + this._events[type] = listener; + else if (isObject(this._events[type])) + // If we've already got an array, just append. + this._events[type].push(listener); + else + // Adding the second element, need to change to array. + this._events[type] = [this._events[type], listener]; + + // Check for listener leak + if (isObject(this._events[type]) && !this._events[type].warned) { + var m; + if (!isUndefined(this._maxListeners)) { + m = this._maxListeners; + } else { + m = EventEmitter.defaultMaxListeners; + } + + if (m && m > 0 && this._events[type].length > m) { + this._events[type].warned = true; + console.error('(node) warning: possible EventEmitter memory ' + + 'leak detected. %d listeners added. ' + + 'Use emitter.setMaxListeners() to increase limit.', + this._events[type].length); + if (typeof console.trace === 'function') { + // not supported in IE 10 + console.trace(); + } + } + } + + return this; +}; + +EventEmitter.prototype.on = EventEmitter.prototype.addListener; + +EventEmitter.prototype.once = function(type, listener) { + if (!isFunction(listener)) + throw TypeError('listener must be a function'); + + var fired = false; + + function g() { + this.removeListener(type, g); + + if (!fired) { + fired = true; + listener.apply(this, arguments); + } + } + + g.listener = listener; + this.on(type, g); + + return this; +}; + +// emits a 'removeListener' event iff the listener was removed +EventEmitter.prototype.removeListener = function(type, listener) { + var list, position, length, i; + + if (!isFunction(listener)) + throw TypeError('listener must be a function'); + + if (!this._events || !this._events[type]) + return this; + + list = this._events[type]; + length = list.length; + position = -1; + + if (list === listener || + (isFunction(list.listener) && list.listener === listener)) { + delete this._events[type]; + if (this._events.removeListener) + this.emit('removeListener', type, listener); + + } else if (isObject(list)) { + for (i = length; i-- > 0;) { + if (list[i] === listener || + (list[i].listener && list[i].listener === listener)) { + position = i; + break; + } + } + + if (position < 0) + return this; + + if (list.length === 1) { + list.length = 0; + delete this._events[type]; + } else { + list.splice(position, 1); + } + + if (this._events.removeListener) + this.emit('removeListener', type, listener); + } + + return this; +}; + +EventEmitter.prototype.removeAllListeners = function(type) { + var key, listeners; + + if (!this._events) + return this; + + // not listening for removeListener, no need to emit + if (!this._events.removeListener) { + if (arguments.length === 0) + this._events = {}; + else if (this._events[type]) + delete this._events[type]; + return this; + } + + // emit removeListener for all listeners on all events + if (arguments.length === 0) { + for (key in this._events) { + if (key === 'removeListener') continue; + this.removeAllListeners(key); + } + this.removeAllListeners('removeListener'); + this._events = {}; + return this; + } + + listeners = this._events[type]; + + if (isFunction(listeners)) { + this.removeListener(type, listeners); + } else { + // LIFO order + while (listeners.length) + this.removeListener(type, listeners[listeners.length - 1]); + } + delete this._events[type]; + + return this; +}; + +EventEmitter.prototype.listeners = function(type) { + var ret; + if (!this._events || !this._events[type]) + ret = []; + else if (isFunction(this._events[type])) + ret = [this._events[type]]; + else + ret = this._events[type].slice(); + return ret; +}; + +EventEmitter.listenerCount = function(emitter, type) { + var ret; + if (!emitter._events || !emitter._events[type]) + ret = 0; + else if (isFunction(emitter._events[type])) + ret = 1; + else + ret = emitter._events[type].length; + return ret; +}; + +function isFunction(arg) { + return typeof arg === 'function'; +} + +function isNumber(arg) { + return typeof arg === 'number'; +} + +function isObject(arg) { + return typeof arg === 'object' && arg !== null; +} + +function isUndefined(arg) { + return arg === void 0; +} + +},{}],15:[function(require,module,exports){ +(function (process){ +exports.alphasort = alphasort +exports.alphasorti = alphasorti +exports.setopts = setopts +exports.ownProp = ownProp +exports.makeAbs = makeAbs +exports.finish = finish +exports.mark = mark +exports.isIgnored = isIgnored +exports.childrenIgnored = childrenIgnored + +function ownProp (obj, field) { + return Object.prototype.hasOwnProperty.call(obj, field) +} + +var path = require("path") +var minimatch = require("minimatch") +var isAbsolute = require("path-is-absolute") +var Minimatch = minimatch.Minimatch + +function alphasorti (a, b) { + return a.toLowerCase().localeCompare(b.toLowerCase()) +} + +function alphasort (a, b) { + return a.localeCompare(b) +} + +function setupIgnores (self, options) { + self.ignore = options.ignore || [] + + if (!Array.isArray(self.ignore)) + self.ignore = [self.ignore] + + if (self.ignore.length) { + self.ignore = self.ignore.map(ignoreMap) + } +} + +function ignoreMap (pattern) { + var gmatcher = null + if (pattern.slice(-3) === '/**') { + var gpattern = pattern.replace(/(\/\*\*)+$/, '') + gmatcher = new Minimatch(gpattern) + } + + return { + matcher: new Minimatch(pattern), + gmatcher: gmatcher + } +} + +function setopts (self, pattern, options) { + if (!options) + options = {} + + // base-matching: just use globstar for that. + if (options.matchBase && -1 === pattern.indexOf("/")) { + if (options.noglobstar) { + throw new Error("base matching requires globstar") + } + pattern = "**/" + pattern + } + + self.silent = !!options.silent + self.pattern = pattern + self.strict = options.strict !== false + self.realpath = !!options.realpath + self.realpathCache = options.realpathCache || Object.create(null) + self.follow = !!options.follow + self.dot = !!options.dot + self.mark = !!options.mark + self.nodir = !!options.nodir + if (self.nodir) + self.mark = true + self.sync = !!options.sync + self.nounique = !!options.nounique + self.nonull = !!options.nonull + self.nosort = !!options.nosort + self.nocase = !!options.nocase + self.stat = !!options.stat + self.noprocess = !!options.noprocess + + self.maxLength = options.maxLength || Infinity + self.cache = options.cache || Object.create(null) + self.statCache = options.statCache || Object.create(null) + self.symlinks = options.symlinks || Object.create(null) + + setupIgnores(self, options) + + self.changedCwd = false + var cwd = process.cwd() + if (!ownProp(options, "cwd")) + self.cwd = cwd + else { + self.cwd = options.cwd + self.changedCwd = path.resolve(options.cwd) !== cwd + } + + self.root = options.root || path.resolve(self.cwd, "/") + self.root = path.resolve(self.root) + if (process.platform === "win32") + self.root = self.root.replace(/\\/g, "/") + + self.nomount = !!options.nomount + + // disable comments and negation unless the user explicitly + // passes in false as the option. + options.nonegate = options.nonegate === false ? false : true + options.nocomment = options.nocomment === false ? false : true + deprecationWarning(options) + + self.minimatch = new Minimatch(pattern, options) + self.options = self.minimatch.options +} + +// TODO(isaacs): remove entirely in v6 +// exported to reset in tests +exports.deprecationWarned +function deprecationWarning(options) { + if (!options.nonegate || !options.nocomment) { + if (process.noDeprecation !== true && !exports.deprecationWarned) { + var msg = 'glob WARNING: comments and negation will be disabled in v6' + if (process.throwDeprecation) + throw new Error(msg) + else if (process.traceDeprecation) + console.trace(msg) + else + console.error(msg) + + exports.deprecationWarned = true + } + } +} + +function finish (self) { + var nou = self.nounique + var all = nou ? [] : Object.create(null) + + for (var i = 0, l = self.matches.length; i < l; i ++) { + var matches = self.matches[i] + if (!matches || Object.keys(matches).length === 0) { + if (self.nonull) { + // do like the shell, and spit out the literal glob + var literal = self.minimatch.globSet[i] + if (nou) + all.push(literal) + else + all[literal] = true + } + } else { + // had matches + var m = Object.keys(matches) + if (nou) + all.push.apply(all, m) + else + m.forEach(function (m) { + all[m] = true + }) + } + } + + if (!nou) + all = Object.keys(all) + + if (!self.nosort) + all = all.sort(self.nocase ? alphasorti : alphasort) + + // at *some* point we statted all of these + if (self.mark) { + for (var i = 0; i < all.length; i++) { + all[i] = self._mark(all[i]) + } + if (self.nodir) { + all = all.filter(function (e) { + return !(/\/$/.test(e)) + }) + } + } + + if (self.ignore.length) + all = all.filter(function(m) { + return !isIgnored(self, m) + }) + + self.found = all +} + +function mark (self, p) { + var abs = makeAbs(self, p) + var c = self.cache[abs] + var m = p + if (c) { + var isDir = c === 'DIR' || Array.isArray(c) + var slash = p.slice(-1) === '/' + + if (isDir && !slash) + m += '/' + else if (!isDir && slash) + m = m.slice(0, -1) + + if (m !== p) { + var mabs = makeAbs(self, m) + self.statCache[mabs] = self.statCache[abs] + self.cache[mabs] = self.cache[abs] + } + } + + return m +} + +// lotta situps... +function makeAbs (self, f) { + var abs = f + if (f.charAt(0) === '/') { + abs = path.join(self.root, f) + } else if (isAbsolute(f) || f === '') { + abs = f + } else if (self.changedCwd) { + abs = path.resolve(self.cwd, f) + } else { + abs = path.resolve(f) + } + return abs +} + + +// Return true, if pattern ends with globstar '**', for the accompanying parent directory. +// Ex:- If node_modules/** is the pattern, add 'node_modules' to ignore list along with it's contents +function isIgnored (self, path) { + if (!self.ignore.length) + return false + + return self.ignore.some(function(item) { + return item.matcher.match(path) || !!(item.gmatcher && item.gmatcher.match(path)) + }) +} + +function childrenIgnored (self, path) { + if (!self.ignore.length) + return false + + return self.ignore.some(function(item) { + return !!(item.gmatcher && item.gmatcher.match(path)) + }) +} + +}).call(this,require('_process')) +},{"_process":24,"minimatch":20,"path":22,"path-is-absolute":23}],16:[function(require,module,exports){ +(function (process){ +// Approach: +// +// 1. Get the minimatch set +// 2. For each pattern in the set, PROCESS(pattern, false) +// 3. Store matches per-set, then uniq them +// +// PROCESS(pattern, inGlobStar) +// Get the first [n] items from pattern that are all strings +// Join these together. This is PREFIX. +// If there is no more remaining, then stat(PREFIX) and +// add to matches if it succeeds. END. +// +// If inGlobStar and PREFIX is symlink and points to dir +// set ENTRIES = [] +// else readdir(PREFIX) as ENTRIES +// If fail, END +// +// with ENTRIES +// If pattern[n] is GLOBSTAR +// // handle the case where the globstar match is empty +// // by pruning it out, and testing the resulting pattern +// PROCESS(pattern[0..n] + pattern[n+1 .. $], false) +// // handle other cases. +// for ENTRY in ENTRIES (not dotfiles) +// // attach globstar + tail onto the entry +// // Mark that this entry is a globstar match +// PROCESS(pattern[0..n] + ENTRY + pattern[n .. $], true) +// +// else // not globstar +// for ENTRY in ENTRIES (not dotfiles, unless pattern[n] is dot) +// Test ENTRY against pattern[n] +// If fails, continue +// If passes, PROCESS(pattern[0..n] + item + pattern[n+1 .. $]) +// +// Caveat: +// Cache all stats and readdirs results to minimize syscall. Since all +// we ever care about is existence and directory-ness, we can just keep +// `true` for files, and [children,...] for directories, or `false` for +// things that don't exist. + +module.exports = glob + +var fs = require('fs') +var minimatch = require('minimatch') +var Minimatch = minimatch.Minimatch +var inherits = require('inherits') +var EE = require('events').EventEmitter +var path = require('path') +var assert = require('assert') +var isAbsolute = require('path-is-absolute') +var globSync = require('./sync.js') +var common = require('./common.js') +var alphasort = common.alphasort +var alphasorti = common.alphasorti +var setopts = common.setopts +var ownProp = common.ownProp +var inflight = require('inflight') +var util = require('util') +var childrenIgnored = common.childrenIgnored +var isIgnored = common.isIgnored + +var once = require('once') + +function glob (pattern, options, cb) { + if (typeof options === 'function') cb = options, options = {} + if (!options) options = {} + + if (options.sync) { + if (cb) + throw new TypeError('callback provided to sync glob') + return globSync(pattern, options) + } + + return new Glob(pattern, options, cb) +} + +glob.sync = globSync +var GlobSync = glob.GlobSync = globSync.GlobSync + +// old api surface +glob.glob = glob + +glob.hasMagic = function (pattern, options_) { + var options = util._extend({}, options_) + options.noprocess = true + + var g = new Glob(pattern, options) + var set = g.minimatch.set + if (set.length > 1) + return true + + for (var j = 0; j < set[0].length; j++) { + if (typeof set[0][j] !== 'string') + return true + } + + return false +} + +glob.Glob = Glob +inherits(Glob, EE) +function Glob (pattern, options, cb) { + if (typeof options === 'function') { + cb = options + options = null + } + + if (options && options.sync) { + if (cb) + throw new TypeError('callback provided to sync glob') + return new GlobSync(pattern, options) + } + + if (!(this instanceof Glob)) + return new Glob(pattern, options, cb) + + setopts(this, pattern, options) + this._didRealPath = false + + // process each pattern in the minimatch set + var n = this.minimatch.set.length + + // The matches are stored as {: true,...} so that + // duplicates are automagically pruned. + // Later, we do an Object.keys() on these. + // Keep them as a list so we can fill in when nonull is set. + this.matches = new Array(n) + + if (typeof cb === 'function') { + cb = once(cb) + this.on('error', cb) + this.on('end', function (matches) { + cb(null, matches) + }) + } + + var self = this + var n = this.minimatch.set.length + this._processing = 0 + this.matches = new Array(n) + + this._emitQueue = [] + this._processQueue = [] + this.paused = false + + if (this.noprocess) + return this + + if (n === 0) + return done() + + for (var i = 0; i < n; i ++) { + this._process(this.minimatch.set[i], i, false, done) + } + + function done () { + --self._processing + if (self._processing <= 0) + self._finish() + } +} + +Glob.prototype._finish = function () { + assert(this instanceof Glob) + if (this.aborted) + return + + if (this.realpath && !this._didRealpath) + return this._realpath() + + common.finish(this) + this.emit('end', this.found) +} + +Glob.prototype._realpath = function () { + if (this._didRealpath) + return + + this._didRealpath = true + + var n = this.matches.length + if (n === 0) + return this._finish() + + var self = this + for (var i = 0; i < this.matches.length; i++) + this._realpathSet(i, next) + + function next () { + if (--n === 0) + self._finish() + } +} + +Glob.prototype._realpathSet = function (index, cb) { + var matchset = this.matches[index] + if (!matchset) + return cb() + + var found = Object.keys(matchset) + var self = this + var n = found.length + + if (n === 0) + return cb() + + var set = this.matches[index] = Object.create(null) + found.forEach(function (p, i) { + // If there's a problem with the stat, then it means that + // one or more of the links in the realpath couldn't be + // resolved. just return the abs value in that case. + p = self._makeAbs(p) + fs.realpath(p, self.realpathCache, function (er, real) { + if (!er) + set[real] = true + else if (er.syscall === 'stat') + set[p] = true + else + self.emit('error', er) // srsly wtf right here + + if (--n === 0) { + self.matches[index] = set + cb() + } + }) + }) +} + +Glob.prototype._mark = function (p) { + return common.mark(this, p) +} + +Glob.prototype._makeAbs = function (f) { + return common.makeAbs(this, f) +} + +Glob.prototype.abort = function () { + this.aborted = true + this.emit('abort') +} + +Glob.prototype.pause = function () { + if (!this.paused) { + this.paused = true + this.emit('pause') + } +} + +Glob.prototype.resume = function () { + if (this.paused) { + this.emit('resume') + this.paused = false + if (this._emitQueue.length) { + var eq = this._emitQueue.slice(0) + this._emitQueue.length = 0 + for (var i = 0; i < eq.length; i ++) { + var e = eq[i] + this._emitMatch(e[0], e[1]) + } + } + if (this._processQueue.length) { + var pq = this._processQueue.slice(0) + this._processQueue.length = 0 + for (var i = 0; i < pq.length; i ++) { + var p = pq[i] + this._processing-- + this._process(p[0], p[1], p[2], p[3]) + } + } + } +} + +Glob.prototype._process = function (pattern, index, inGlobStar, cb) { + assert(this instanceof Glob) + assert(typeof cb === 'function') + + if (this.aborted) + return + + this._processing++ + if (this.paused) { + this._processQueue.push([pattern, index, inGlobStar, cb]) + return + } + + //console.error('PROCESS %d', this._processing, pattern) + + // Get the first [n] parts of pattern that are all strings. + var n = 0 + while (typeof pattern[n] === 'string') { + n ++ + } + // now n is the index of the first one that is *not* a string. + + // see if there's anything else + var prefix + switch (n) { + // if not, then this is rather simple + case pattern.length: + this._processSimple(pattern.join('/'), index, cb) + return + + case 0: + // pattern *starts* with some non-trivial item. + // going to readdir(cwd), but not include the prefix in matches. + prefix = null + break + + default: + // pattern has some string bits in the front. + // whatever it starts with, whether that's 'absolute' like /foo/bar, + // or 'relative' like '../baz' + prefix = pattern.slice(0, n).join('/') + break + } + + var remain = pattern.slice(n) + + // get the list of entries. + var read + if (prefix === null) + read = '.' + else if (isAbsolute(prefix) || isAbsolute(pattern.join('/'))) { + if (!prefix || !isAbsolute(prefix)) + prefix = '/' + prefix + read = prefix + } else + read = prefix + + var abs = this._makeAbs(read) + + //if ignored, skip _processing + if (childrenIgnored(this, read)) + return cb() + + var isGlobStar = remain[0] === minimatch.GLOBSTAR + if (isGlobStar) + this._processGlobStar(prefix, read, abs, remain, index, inGlobStar, cb) + else + this._processReaddir(prefix, read, abs, remain, index, inGlobStar, cb) +} + +Glob.prototype._processReaddir = function (prefix, read, abs, remain, index, inGlobStar, cb) { + var self = this + this._readdir(abs, inGlobStar, function (er, entries) { + return self._processReaddir2(prefix, read, abs, remain, index, inGlobStar, entries, cb) + }) +} + +Glob.prototype._processReaddir2 = function (prefix, read, abs, remain, index, inGlobStar, entries, cb) { + + // if the abs isn't a dir, then nothing can match! + if (!entries) + return cb() + + // It will only match dot entries if it starts with a dot, or if + // dot is set. Stuff like @(.foo|.bar) isn't allowed. + var pn = remain[0] + var negate = !!this.minimatch.negate + var rawGlob = pn._glob + var dotOk = this.dot || rawGlob.charAt(0) === '.' + + var matchedEntries = [] + for (var i = 0; i < entries.length; i++) { + var e = entries[i] + if (e.charAt(0) !== '.' || dotOk) { + var m + if (negate && !prefix) { + m = !e.match(pn) + } else { + m = e.match(pn) + } + if (m) + matchedEntries.push(e) + } + } + + //console.error('prd2', prefix, entries, remain[0]._glob, matchedEntries) + + var len = matchedEntries.length + // If there are no matched entries, then nothing matches. + if (len === 0) + return cb() + + // if this is the last remaining pattern bit, then no need for + // an additional stat *unless* the user has specified mark or + // stat explicitly. We know they exist, since readdir returned + // them. + + if (remain.length === 1 && !this.mark && !this.stat) { + if (!this.matches[index]) + this.matches[index] = Object.create(null) + + for (var i = 0; i < len; i ++) { + var e = matchedEntries[i] + if (prefix) { + if (prefix !== '/') + e = prefix + '/' + e + else + e = prefix + e + } + + if (e.charAt(0) === '/' && !this.nomount) { + e = path.join(this.root, e) + } + this._emitMatch(index, e) + } + // This was the last one, and no stats were needed + return cb() + } + + // now test all matched entries as stand-ins for that part + // of the pattern. + remain.shift() + for (var i = 0; i < len; i ++) { + var e = matchedEntries[i] + var newPattern + if (prefix) { + if (prefix !== '/') + e = prefix + '/' + e + else + e = prefix + e + } + this._process([e].concat(remain), index, inGlobStar, cb) + } + cb() +} + +Glob.prototype._emitMatch = function (index, e) { + if (this.aborted) + return + + if (this.matches[index][e]) + return + + if (isIgnored(this, e)) + return + + if (this.paused) { + this._emitQueue.push([index, e]) + return + } + + var abs = this._makeAbs(e) + + if (this.nodir) { + var c = this.cache[abs] + if (c === 'DIR' || Array.isArray(c)) + return + } + + if (this.mark) + e = this._mark(e) + + this.matches[index][e] = true + + var st = this.statCache[abs] + if (st) + this.emit('stat', e, st) + + this.emit('match', e) +} + +Glob.prototype._readdirInGlobStar = function (abs, cb) { + if (this.aborted) + return + + // follow all symlinked directories forever + // just proceed as if this is a non-globstar situation + if (this.follow) + return this._readdir(abs, false, cb) + + var lstatkey = 'lstat\0' + abs + var self = this + var lstatcb = inflight(lstatkey, lstatcb_) + + if (lstatcb) + fs.lstat(abs, lstatcb) + + function lstatcb_ (er, lstat) { + if (er) + return cb() + + var isSym = lstat.isSymbolicLink() + self.symlinks[abs] = isSym + + // If it's not a symlink or a dir, then it's definitely a regular file. + // don't bother doing a readdir in that case. + if (!isSym && !lstat.isDirectory()) { + self.cache[abs] = 'FILE' + cb() + } else + self._readdir(abs, false, cb) + } +} + +Glob.prototype._readdir = function (abs, inGlobStar, cb) { + if (this.aborted) + return + + cb = inflight('readdir\0'+abs+'\0'+inGlobStar, cb) + if (!cb) + return + + //console.error('RD %j %j', +inGlobStar, abs) + if (inGlobStar && !ownProp(this.symlinks, abs)) + return this._readdirInGlobStar(abs, cb) + + if (ownProp(this.cache, abs)) { + var c = this.cache[abs] + if (!c || c === 'FILE') + return cb() + + if (Array.isArray(c)) + return cb(null, c) + } + + var self = this + fs.readdir(abs, readdirCb(this, abs, cb)) +} + +function readdirCb (self, abs, cb) { + return function (er, entries) { + if (er) + self._readdirError(abs, er, cb) + else + self._readdirEntries(abs, entries, cb) + } +} + +Glob.prototype._readdirEntries = function (abs, entries, cb) { + if (this.aborted) + return + + // if we haven't asked to stat everything, then just + // assume that everything in there exists, so we can avoid + // having to stat it a second time. + if (!this.mark && !this.stat) { + for (var i = 0; i < entries.length; i ++) { + var e = entries[i] + if (abs === '/') + e = abs + e + else + e = abs + '/' + e + this.cache[e] = true + } + } + + this.cache[abs] = entries + return cb(null, entries) +} + +Glob.prototype._readdirError = function (f, er, cb) { + if (this.aborted) + return + + // handle errors, and cache the information + switch (er.code) { + case 'ENOTSUP': // https://github.com/isaacs/node-glob/issues/205 + case 'ENOTDIR': // totally normal. means it *does* exist. + this.cache[this._makeAbs(f)] = 'FILE' + break + + case 'ENOENT': // not terribly unusual + case 'ELOOP': + case 'ENAMETOOLONG': + case 'UNKNOWN': + this.cache[this._makeAbs(f)] = false + break + + default: // some unusual error. Treat as failure. + this.cache[this._makeAbs(f)] = false + if (this.strict) { + this.emit('error', er) + // If the error is handled, then we abort + // if not, we threw out of here + this.abort() + } + if (!this.silent) + console.error('glob error', er) + break + } + + return cb() +} + +Glob.prototype._processGlobStar = function (prefix, read, abs, remain, index, inGlobStar, cb) { + var self = this + this._readdir(abs, inGlobStar, function (er, entries) { + self._processGlobStar2(prefix, read, abs, remain, index, inGlobStar, entries, cb) + }) +} + + +Glob.prototype._processGlobStar2 = function (prefix, read, abs, remain, index, inGlobStar, entries, cb) { + //console.error('pgs2', prefix, remain[0], entries) + + // no entries means not a dir, so it can never have matches + // foo.txt/** doesn't match foo.txt + if (!entries) + return cb() + + // test without the globstar, and with every child both below + // and replacing the globstar. + var remainWithoutGlobStar = remain.slice(1) + var gspref = prefix ? [ prefix ] : [] + var noGlobStar = gspref.concat(remainWithoutGlobStar) + + // the noGlobStar pattern exits the inGlobStar state + this._process(noGlobStar, index, false, cb) + + var isSym = this.symlinks[abs] + var len = entries.length + + // If it's a symlink, and we're in a globstar, then stop + if (isSym && inGlobStar) + return cb() + + for (var i = 0; i < len; i++) { + var e = entries[i] + if (e.charAt(0) === '.' && !this.dot) + continue + + // these two cases enter the inGlobStar state + var instead = gspref.concat(entries[i], remainWithoutGlobStar) + this._process(instead, index, true, cb) + + var below = gspref.concat(entries[i], remain) + this._process(below, index, true, cb) + } + + cb() +} + +Glob.prototype._processSimple = function (prefix, index, cb) { + // XXX review this. Shouldn't it be doing the mounting etc + // before doing stat? kinda weird? + var self = this + this._stat(prefix, function (er, exists) { + self._processSimple2(prefix, index, er, exists, cb) + }) +} +Glob.prototype._processSimple2 = function (prefix, index, er, exists, cb) { + + //console.error('ps2', prefix, exists) + + if (!this.matches[index]) + this.matches[index] = Object.create(null) + + // If it doesn't exist, then just mark the lack of results + if (!exists) + return cb() + + if (prefix && isAbsolute(prefix) && !this.nomount) { + var trail = /[\/\\]$/.test(prefix) + if (prefix.charAt(0) === '/') { + prefix = path.join(this.root, prefix) + } else { + prefix = path.resolve(this.root, prefix) + if (trail) + prefix += '/' + } + } + + if (process.platform === 'win32') + prefix = prefix.replace(/\\/g, '/') + + // Mark this as a match + this._emitMatch(index, prefix) + cb() +} + +// Returns either 'DIR', 'FILE', or false +Glob.prototype._stat = function (f, cb) { + var abs = this._makeAbs(f) + var needDir = f.slice(-1) === '/' + + if (f.length > this.maxLength) + return cb() + + if (!this.stat && ownProp(this.cache, abs)) { + var c = this.cache[abs] + + if (Array.isArray(c)) + c = 'DIR' + + // It exists, but maybe not how we need it + if (!needDir || c === 'DIR') + return cb(null, c) + + if (needDir && c === 'FILE') + return cb() + + // otherwise we have to stat, because maybe c=true + // if we know it exists, but not what it is. + } + + var exists + var stat = this.statCache[abs] + if (stat !== undefined) { + if (stat === false) + return cb(null, stat) + else { + var type = stat.isDirectory() ? 'DIR' : 'FILE' + if (needDir && type === 'FILE') + return cb() + else + return cb(null, type, stat) + } + } + + var self = this + var statcb = inflight('stat\0' + abs, lstatcb_) + if (statcb) + fs.lstat(abs, statcb) + + function lstatcb_ (er, lstat) { + if (lstat && lstat.isSymbolicLink()) { + // If it's a symlink, then treat it as the target, unless + // the target does not exist, then treat it as a file. + return fs.stat(abs, function (er, stat) { + if (er) + self._stat2(f, abs, null, lstat, cb) + else + self._stat2(f, abs, er, stat, cb) + }) + } else { + self._stat2(f, abs, er, lstat, cb) + } + } +} + +Glob.prototype._stat2 = function (f, abs, er, stat, cb) { + if (er) { + this.statCache[abs] = false + return cb() + } + + var needDir = f.slice(-1) === '/' + this.statCache[abs] = stat + + if (abs.slice(-1) === '/' && !stat.isDirectory()) + return cb(null, false, stat) + + var c = stat.isDirectory() ? 'DIR' : 'FILE' + this.cache[abs] = this.cache[abs] || c + + if (needDir && c !== 'DIR') + return cb() + + return cb(null, c, stat) +} + +}).call(this,require('_process')) +},{"./common.js":15,"./sync.js":17,"_process":24,"assert":9,"events":14,"fs":12,"inflight":18,"inherits":19,"minimatch":20,"once":21,"path":22,"path-is-absolute":23,"util":28}],17:[function(require,module,exports){ +(function (process){ +module.exports = globSync +globSync.GlobSync = GlobSync + +var fs = require('fs') +var minimatch = require('minimatch') +var Minimatch = minimatch.Minimatch +var Glob = require('./glob.js').Glob +var util = require('util') +var path = require('path') +var assert = require('assert') +var isAbsolute = require('path-is-absolute') +var common = require('./common.js') +var alphasort = common.alphasort +var alphasorti = common.alphasorti +var setopts = common.setopts +var ownProp = common.ownProp +var childrenIgnored = common.childrenIgnored + +function globSync (pattern, options) { + if (typeof options === 'function' || arguments.length === 3) + throw new TypeError('callback provided to sync glob\n'+ + 'See: https://github.com/isaacs/node-glob/issues/167') + + return new GlobSync(pattern, options).found +} + +function GlobSync (pattern, options) { + if (!pattern) + throw new Error('must provide pattern') + + if (typeof options === 'function' || arguments.length === 3) + throw new TypeError('callback provided to sync glob\n'+ + 'See: https://github.com/isaacs/node-glob/issues/167') + + if (!(this instanceof GlobSync)) + return new GlobSync(pattern, options) + + setopts(this, pattern, options) + + if (this.noprocess) + return this + + var n = this.minimatch.set.length + this.matches = new Array(n) + for (var i = 0; i < n; i ++) { + this._process(this.minimatch.set[i], i, false) + } + this._finish() +} + +GlobSync.prototype._finish = function () { + assert(this instanceof GlobSync) + if (this.realpath) { + var self = this + this.matches.forEach(function (matchset, index) { + var set = self.matches[index] = Object.create(null) + for (var p in matchset) { + try { + p = self._makeAbs(p) + var real = fs.realpathSync(p, self.realpathCache) + set[real] = true + } catch (er) { + if (er.syscall === 'stat') + set[self._makeAbs(p)] = true + else + throw er + } + } + }) + } + common.finish(this) +} + + +GlobSync.prototype._process = function (pattern, index, inGlobStar) { + assert(this instanceof GlobSync) + + // Get the first [n] parts of pattern that are all strings. + var n = 0 + while (typeof pattern[n] === 'string') { + n ++ + } + // now n is the index of the first one that is *not* a string. + + // See if there's anything else + var prefix + switch (n) { + // if not, then this is rather simple + case pattern.length: + this._processSimple(pattern.join('/'), index) + return + + case 0: + // pattern *starts* with some non-trivial item. + // going to readdir(cwd), but not include the prefix in matches. + prefix = null + break + + default: + // pattern has some string bits in the front. + // whatever it starts with, whether that's 'absolute' like /foo/bar, + // or 'relative' like '../baz' + prefix = pattern.slice(0, n).join('/') + break + } + + var remain = pattern.slice(n) + + // get the list of entries. + var read + if (prefix === null) + read = '.' + else if (isAbsolute(prefix) || isAbsolute(pattern.join('/'))) { + if (!prefix || !isAbsolute(prefix)) + prefix = '/' + prefix + read = prefix + } else + read = prefix + + var abs = this._makeAbs(read) + + //if ignored, skip processing + if (childrenIgnored(this, read)) + return + + var isGlobStar = remain[0] === minimatch.GLOBSTAR + if (isGlobStar) + this._processGlobStar(prefix, read, abs, remain, index, inGlobStar) + else + this._processReaddir(prefix, read, abs, remain, index, inGlobStar) +} + + +GlobSync.prototype._processReaddir = function (prefix, read, abs, remain, index, inGlobStar) { + var entries = this._readdir(abs, inGlobStar) + + // if the abs isn't a dir, then nothing can match! + if (!entries) + return + + // It will only match dot entries if it starts with a dot, or if + // dot is set. Stuff like @(.foo|.bar) isn't allowed. + var pn = remain[0] + var negate = !!this.minimatch.negate + var rawGlob = pn._glob + var dotOk = this.dot || rawGlob.charAt(0) === '.' + + var matchedEntries = [] + for (var i = 0; i < entries.length; i++) { + var e = entries[i] + if (e.charAt(0) !== '.' || dotOk) { + var m + if (negate && !prefix) { + m = !e.match(pn) + } else { + m = e.match(pn) + } + if (m) + matchedEntries.push(e) + } + } + + var len = matchedEntries.length + // If there are no matched entries, then nothing matches. + if (len === 0) + return + + // if this is the last remaining pattern bit, then no need for + // an additional stat *unless* the user has specified mark or + // stat explicitly. We know they exist, since readdir returned + // them. + + if (remain.length === 1 && !this.mark && !this.stat) { + if (!this.matches[index]) + this.matches[index] = Object.create(null) + + for (var i = 0; i < len; i ++) { + var e = matchedEntries[i] + if (prefix) { + if (prefix.slice(-1) !== '/') + e = prefix + '/' + e + else + e = prefix + e + } + + if (e.charAt(0) === '/' && !this.nomount) { + e = path.join(this.root, e) + } + this.matches[index][e] = true + } + // This was the last one, and no stats were needed + return + } + + // now test all matched entries as stand-ins for that part + // of the pattern. + remain.shift() + for (var i = 0; i < len; i ++) { + var e = matchedEntries[i] + var newPattern + if (prefix) + newPattern = [prefix, e] + else + newPattern = [e] + this._process(newPattern.concat(remain), index, inGlobStar) + } +} + + +GlobSync.prototype._emitMatch = function (index, e) { + var abs = this._makeAbs(e) + if (this.mark) + e = this._mark(e) + + if (this.matches[index][e]) + return + + if (this.nodir) { + var c = this.cache[this._makeAbs(e)] + if (c === 'DIR' || Array.isArray(c)) + return + } + + this.matches[index][e] = true + if (this.stat) + this._stat(e) +} + + +GlobSync.prototype._readdirInGlobStar = function (abs) { + // follow all symlinked directories forever + // just proceed as if this is a non-globstar situation + if (this.follow) + return this._readdir(abs, false) + + var entries + var lstat + var stat + try { + lstat = fs.lstatSync(abs) + } catch (er) { + // lstat failed, doesn't exist + return null + } + + var isSym = lstat.isSymbolicLink() + this.symlinks[abs] = isSym + + // If it's not a symlink or a dir, then it's definitely a regular file. + // don't bother doing a readdir in that case. + if (!isSym && !lstat.isDirectory()) + this.cache[abs] = 'FILE' + else + entries = this._readdir(abs, false) + + return entries +} + +GlobSync.prototype._readdir = function (abs, inGlobStar) { + var entries + + if (inGlobStar && !ownProp(this.symlinks, abs)) + return this._readdirInGlobStar(abs) + + if (ownProp(this.cache, abs)) { + var c = this.cache[abs] + if (!c || c === 'FILE') + return null + + if (Array.isArray(c)) + return c + } + + try { + return this._readdirEntries(abs, fs.readdirSync(abs)) + } catch (er) { + this._readdirError(abs, er) + return null + } +} + +GlobSync.prototype._readdirEntries = function (abs, entries) { + // if we haven't asked to stat everything, then just + // assume that everything in there exists, so we can avoid + // having to stat it a second time. + if (!this.mark && !this.stat) { + for (var i = 0; i < entries.length; i ++) { + var e = entries[i] + if (abs === '/') + e = abs + e + else + e = abs + '/' + e + this.cache[e] = true + } + } + + this.cache[abs] = entries + + // mark and cache dir-ness + return entries +} + +GlobSync.prototype._readdirError = function (f, er) { + // handle errors, and cache the information + switch (er.code) { + case 'ENOTSUP': // https://github.com/isaacs/node-glob/issues/205 + case 'ENOTDIR': // totally normal. means it *does* exist. + this.cache[this._makeAbs(f)] = 'FILE' + break + + case 'ENOENT': // not terribly unusual + case 'ELOOP': + case 'ENAMETOOLONG': + case 'UNKNOWN': + this.cache[this._makeAbs(f)] = false + break + + default: // some unusual error. Treat as failure. + this.cache[this._makeAbs(f)] = false + if (this.strict) + throw er + if (!this.silent) + console.error('glob error', er) + break + } +} + +GlobSync.prototype._processGlobStar = function (prefix, read, abs, remain, index, inGlobStar) { + + var entries = this._readdir(abs, inGlobStar) + + // no entries means not a dir, so it can never have matches + // foo.txt/** doesn't match foo.txt + if (!entries) + return + + // test without the globstar, and with every child both below + // and replacing the globstar. + var remainWithoutGlobStar = remain.slice(1) + var gspref = prefix ? [ prefix ] : [] + var noGlobStar = gspref.concat(remainWithoutGlobStar) + + // the noGlobStar pattern exits the inGlobStar state + this._process(noGlobStar, index, false) + + var len = entries.length + var isSym = this.symlinks[abs] + + // If it's a symlink, and we're in a globstar, then stop + if (isSym && inGlobStar) + return + + for (var i = 0; i < len; i++) { + var e = entries[i] + if (e.charAt(0) === '.' && !this.dot) + continue + + // these two cases enter the inGlobStar state + var instead = gspref.concat(entries[i], remainWithoutGlobStar) + this._process(instead, index, true) + + var below = gspref.concat(entries[i], remain) + this._process(below, index, true) + } +} + +GlobSync.prototype._processSimple = function (prefix, index) { + // XXX review this. Shouldn't it be doing the mounting etc + // before doing stat? kinda weird? + var exists = this._stat(prefix) + + if (!this.matches[index]) + this.matches[index] = Object.create(null) + + // If it doesn't exist, then just mark the lack of results + if (!exists) + return + + if (prefix && isAbsolute(prefix) && !this.nomount) { + var trail = /[\/\\]$/.test(prefix) + if (prefix.charAt(0) === '/') { + prefix = path.join(this.root, prefix) + } else { + prefix = path.resolve(this.root, prefix) + if (trail) + prefix += '/' + } + } + + if (process.platform === 'win32') + prefix = prefix.replace(/\\/g, '/') + + // Mark this as a match + this.matches[index][prefix] = true +} + +// Returns either 'DIR', 'FILE', or false +GlobSync.prototype._stat = function (f) { + var abs = this._makeAbs(f) + var needDir = f.slice(-1) === '/' + + if (f.length > this.maxLength) + return false + + if (!this.stat && ownProp(this.cache, abs)) { + var c = this.cache[abs] + + if (Array.isArray(c)) + c = 'DIR' + + // It exists, but maybe not how we need it + if (!needDir || c === 'DIR') + return c + + if (needDir && c === 'FILE') + return false + + // otherwise we have to stat, because maybe c=true + // if we know it exists, but not what it is. + } + + var exists + var stat = this.statCache[abs] + if (!stat) { + var lstat + try { + lstat = fs.lstatSync(abs) + } catch (er) { + return false + } + + if (lstat.isSymbolicLink()) { + try { + stat = fs.statSync(abs) + } catch (er) { + stat = lstat + } + } else { + stat = lstat + } + } + + this.statCache[abs] = stat + + var c = stat.isDirectory() ? 'DIR' : 'FILE' + this.cache[abs] = this.cache[abs] || c + + if (needDir && c !== 'DIR') + return false + + return c +} + +GlobSync.prototype._mark = function (p) { + return common.mark(this, p) +} + +GlobSync.prototype._makeAbs = function (f) { + return common.makeAbs(this, f) +} + +}).call(this,require('_process')) +},{"./common.js":15,"./glob.js":16,"_process":24,"assert":9,"fs":12,"minimatch":20,"path":22,"path-is-absolute":23,"util":28}],18:[function(require,module,exports){ +(function (process){ +var wrappy = require('wrappy') +var reqs = Object.create(null) +var once = require('once') + +module.exports = wrappy(inflight) + +function inflight (key, cb) { + if (reqs[key]) { + reqs[key].push(cb) + return null + } else { + reqs[key] = [cb] + return makeres(key) + } +} + +function makeres (key) { + return once(function RES () { + var cbs = reqs[key] + var len = cbs.length + var args = slice(arguments) + + // XXX It's somewhat ambiguous whether a new callback added in this + // pass should be queued for later execution if something in the + // list of callbacks throws, or if it should just be discarded. + // However, it's such an edge case that it hardly matters, and either + // choice is likely as surprising as the other. + // As it happens, we do go ahead and schedule it for later execution. + try { + for (var i = 0; i < len; i++) { + cbs[i].apply(null, args) + } + } finally { + if (cbs.length > len) { + // added more in the interim. + // de-zalgo, just in case, but don't call again. + cbs.splice(0, len) + process.nextTick(function () { + RES.apply(null, args) + }) + } else { + delete reqs[key] + } + } + }) +} + +function slice (args) { + var length = args.length + var array = [] + + for (var i = 0; i < length; i++) array[i] = args[i] + return array +} + +}).call(this,require('_process')) +},{"_process":24,"once":21,"wrappy":29}],19:[function(require,module,exports){ +if (typeof Object.create === 'function') { + // implementation from standard node.js 'util' module + module.exports = function inherits(ctor, superCtor) { + ctor.super_ = superCtor + ctor.prototype = Object.create(superCtor.prototype, { + constructor: { + value: ctor, + enumerable: false, + writable: true, + configurable: true + } + }); + }; +} else { + // old school shim for old browsers + module.exports = function inherits(ctor, superCtor) { + ctor.super_ = superCtor + var TempCtor = function () {} + TempCtor.prototype = superCtor.prototype + ctor.prototype = new TempCtor() + ctor.prototype.constructor = ctor + } +} + +},{}],20:[function(require,module,exports){ +module.exports = minimatch +minimatch.Minimatch = Minimatch + +var path = { sep: '/' } +try { + path = require('path') +} catch (er) {} + +var GLOBSTAR = minimatch.GLOBSTAR = Minimatch.GLOBSTAR = {} +var expand = require('brace-expansion') + +var plTypes = { + '!': { open: '(?:(?!(?:', close: '))[^/]*?)'}, + '?': { open: '(?:', close: ')?' }, + '+': { open: '(?:', close: ')+' }, + '*': { open: '(?:', close: ')*' }, + '@': { open: '(?:', close: ')' } +} + +// any single thing other than / +// don't need to escape / when using new RegExp() +var qmark = '[^/]' + +// * => any number of characters +var star = qmark + '*?' + +// ** when dots are allowed. Anything goes, except .. and . +// not (^ or / followed by one or two dots followed by $ or /), +// followed by anything, any number of times. +var twoStarDot = '(?:(?!(?:\\\/|^)(?:\\.{1,2})($|\\\/)).)*?' + +// not a ^ or / followed by a dot, +// followed by anything, any number of times. +var twoStarNoDot = '(?:(?!(?:\\\/|^)\\.).)*?' + +// characters that need to be escaped in RegExp. +var reSpecials = charSet('().*{}+?[]^$\\!') + +// "abc" -> { a:true, b:true, c:true } +function charSet (s) { + return s.split('').reduce(function (set, c) { + set[c] = true + return set + }, {}) +} + +// normalizes slashes. +var slashSplit = /\/+/ + +minimatch.filter = filter +function filter (pattern, options) { + options = options || {} + return function (p, i, list) { + return minimatch(p, pattern, options) + } +} + +function ext (a, b) { + a = a || {} + b = b || {} + var t = {} + Object.keys(b).forEach(function (k) { + t[k] = b[k] + }) + Object.keys(a).forEach(function (k) { + t[k] = a[k] + }) + return t +} + +minimatch.defaults = function (def) { + if (!def || !Object.keys(def).length) return minimatch + + var orig = minimatch + + var m = function minimatch (p, pattern, options) { + return orig.minimatch(p, pattern, ext(def, options)) + } + + m.Minimatch = function Minimatch (pattern, options) { + return new orig.Minimatch(pattern, ext(def, options)) + } + + return m +} + +Minimatch.defaults = function (def) { + if (!def || !Object.keys(def).length) return Minimatch + return minimatch.defaults(def).Minimatch +} + +function minimatch (p, pattern, options) { + if (typeof pattern !== 'string') { + throw new TypeError('glob pattern string required') + } + + if (!options) options = {} + + // shortcut: comments match nothing. + if (!options.nocomment && pattern.charAt(0) === '#') { + return false + } + + // "" only matches "" + if (pattern.trim() === '') return p === '' + + return new Minimatch(pattern, options).match(p) +} + +function Minimatch (pattern, options) { + if (!(this instanceof Minimatch)) { + return new Minimatch(pattern, options) + } + + if (typeof pattern !== 'string') { + throw new TypeError('glob pattern string required') + } + + if (!options) options = {} + pattern = pattern.trim() + + // windows support: need to use /, not \ + if (path.sep !== '/') { + pattern = pattern.split(path.sep).join('/') + } + + this.options = options + this.set = [] + this.pattern = pattern + this.regexp = null + this.negate = false + this.comment = false + this.empty = false + + // make the set of regexps etc. + this.make() +} + +Minimatch.prototype.debug = function () {} + +Minimatch.prototype.make = make +function make () { + // don't do it more than once. + if (this._made) return + + var pattern = this.pattern + var options = this.options + + // empty patterns and comments match nothing. + if (!options.nocomment && pattern.charAt(0) === '#') { + this.comment = true + return + } + if (!pattern) { + this.empty = true + return + } + + // step 1: figure out negation, etc. + this.parseNegate() + + // step 2: expand braces + var set = this.globSet = this.braceExpand() + + if (options.debug) this.debug = console.error + + this.debug(this.pattern, set) + + // step 3: now we have a set, so turn each one into a series of path-portion + // matching patterns. + // These will be regexps, except in the case of "**", which is + // set to the GLOBSTAR object for globstar behavior, + // and will not contain any / characters + set = this.globParts = set.map(function (s) { + return s.split(slashSplit) + }) + + this.debug(this.pattern, set) + + // glob --> regexps + set = set.map(function (s, si, set) { + return s.map(this.parse, this) + }, this) + + this.debug(this.pattern, set) + + // filter out everything that didn't compile properly. + set = set.filter(function (s) { + return s.indexOf(false) === -1 + }) + + this.debug(this.pattern, set) + + this.set = set +} + +Minimatch.prototype.parseNegate = parseNegate +function parseNegate () { + var pattern = this.pattern + var negate = false + var options = this.options + var negateOffset = 0 + + if (options.nonegate) return + + for (var i = 0, l = pattern.length + ; i < l && pattern.charAt(i) === '!' + ; i++) { + negate = !negate + negateOffset++ + } + + if (negateOffset) this.pattern = pattern.substr(negateOffset) + this.negate = negate +} + +// Brace expansion: +// a{b,c}d -> abd acd +// a{b,}c -> abc ac +// a{0..3}d -> a0d a1d a2d a3d +// a{b,c{d,e}f}g -> abg acdfg acefg +// a{b,c}d{e,f}g -> abdeg acdeg abdeg abdfg +// +// Invalid sets are not expanded. +// a{2..}b -> a{2..}b +// a{b}c -> a{b}c +minimatch.braceExpand = function (pattern, options) { + return braceExpand(pattern, options) +} + +Minimatch.prototype.braceExpand = braceExpand + +function braceExpand (pattern, options) { + if (!options) { + if (this instanceof Minimatch) { + options = this.options + } else { + options = {} + } + } + + pattern = typeof pattern === 'undefined' + ? this.pattern : pattern + + if (typeof pattern === 'undefined') { + throw new TypeError('undefined pattern') + } + + if (options.nobrace || + !pattern.match(/\{.*\}/)) { + // shortcut. no need to expand. + return [pattern] + } + + return expand(pattern) +} + +// parse a component of the expanded set. +// At this point, no pattern may contain "/" in it +// so we're going to return a 2d array, where each entry is the full +// pattern, split on '/', and then turned into a regular expression. +// A regexp is made at the end which joins each array with an +// escaped /, and another full one which joins each regexp with |. +// +// Following the lead of Bash 4.1, note that "**" only has special meaning +// when it is the *only* thing in a path portion. Otherwise, any series +// of * is equivalent to a single *. Globstar behavior is enabled by +// default, and can be disabled by setting options.noglobstar. +Minimatch.prototype.parse = parse +var SUBPARSE = {} +function parse (pattern, isSub) { + if (pattern.length > 1024 * 64) { + throw new TypeError('pattern is too long') + } + + var options = this.options + + // shortcuts + if (!options.noglobstar && pattern === '**') return GLOBSTAR + if (pattern === '') return '' + + var re = '' + var hasMagic = !!options.nocase + var escaping = false + // ? => one single character + var patternListStack = [] + var negativeLists = [] + var stateChar + var inClass = false + var reClassStart = -1 + var classStart = -1 + // . and .. never match anything that doesn't start with ., + // even when options.dot is set. + var patternStart = pattern.charAt(0) === '.' ? '' // anything + // not (start or / followed by . or .. followed by / or end) + : options.dot ? '(?!(?:^|\\\/)\\.{1,2}(?:$|\\\/))' + : '(?!\\.)' + var self = this + + function clearStateChar () { + if (stateChar) { + // we had some state-tracking character + // that wasn't consumed by this pass. + switch (stateChar) { + case '*': + re += star + hasMagic = true + break + case '?': + re += qmark + hasMagic = true + break + default: + re += '\\' + stateChar + break + } + self.debug('clearStateChar %j %j', stateChar, re) + stateChar = false + } + } + + for (var i = 0, len = pattern.length, c + ; (i < len) && (c = pattern.charAt(i)) + ; i++) { + this.debug('%s\t%s %s %j', pattern, i, re, c) + + // skip over any that are escaped. + if (escaping && reSpecials[c]) { + re += '\\' + c + escaping = false + continue + } + + switch (c) { + case '/': + // completely not allowed, even escaped. + // Should already be path-split by now. + return false + + case '\\': + clearStateChar() + escaping = true + continue + + // the various stateChar values + // for the "extglob" stuff. + case '?': + case '*': + case '+': + case '@': + case '!': + this.debug('%s\t%s %s %j <-- stateChar', pattern, i, re, c) + + // all of those are literals inside a class, except that + // the glob [!a] means [^a] in regexp + if (inClass) { + this.debug(' in class') + if (c === '!' && i === classStart + 1) c = '^' + re += c + continue + } + + // if we already have a stateChar, then it means + // that there was something like ** or +? in there. + // Handle the stateChar, then proceed with this one. + self.debug('call clearStateChar %j', stateChar) + clearStateChar() + stateChar = c + // if extglob is disabled, then +(asdf|foo) isn't a thing. + // just clear the statechar *now*, rather than even diving into + // the patternList stuff. + if (options.noext) clearStateChar() + continue + + case '(': + if (inClass) { + re += '(' + continue + } + + if (!stateChar) { + re += '\\(' + continue + } + + patternListStack.push({ + type: stateChar, + start: i - 1, + reStart: re.length, + open: plTypes[stateChar].open, + close: plTypes[stateChar].close + }) + // negation is (?:(?!js)[^/]*) + re += stateChar === '!' ? '(?:(?!(?:' : '(?:' + this.debug('plType %j %j', stateChar, re) + stateChar = false + continue + + case ')': + if (inClass || !patternListStack.length) { + re += '\\)' + continue + } + + clearStateChar() + hasMagic = true + var pl = patternListStack.pop() + // negation is (?:(?!js)[^/]*) + // The others are (?:) + re += pl.close + if (pl.type === '!') { + negativeLists.push(pl) + } + pl.reEnd = re.length + continue + + case '|': + if (inClass || !patternListStack.length || escaping) { + re += '\\|' + escaping = false + continue + } + + clearStateChar() + re += '|' + continue + + // these are mostly the same in regexp and glob + case '[': + // swallow any state-tracking char before the [ + clearStateChar() + + if (inClass) { + re += '\\' + c + continue + } + + inClass = true + classStart = i + reClassStart = re.length + re += c + continue + + case ']': + // a right bracket shall lose its special + // meaning and represent itself in + // a bracket expression if it occurs + // first in the list. -- POSIX.2 2.8.3.2 + if (i === classStart + 1 || !inClass) { + re += '\\' + c + escaping = false + continue + } + + // handle the case where we left a class open. + // "[z-a]" is valid, equivalent to "\[z-a\]" + if (inClass) { + // split where the last [ was, make sure we don't have + // an invalid re. if so, re-walk the contents of the + // would-be class to re-translate any characters that + // were passed through as-is + // TODO: It would probably be faster to determine this + // without a try/catch and a new RegExp, but it's tricky + // to do safely. For now, this is safe and works. + var cs = pattern.substring(classStart + 1, i) + try { + RegExp('[' + cs + ']') + } catch (er) { + // not a valid class! + var sp = this.parse(cs, SUBPARSE) + re = re.substr(0, reClassStart) + '\\[' + sp[0] + '\\]' + hasMagic = hasMagic || sp[1] + inClass = false + continue + } + } + + // finish up the class. + hasMagic = true + inClass = false + re += c + continue + + default: + // swallow any state char that wasn't consumed + clearStateChar() + + if (escaping) { + // no need + escaping = false + } else if (reSpecials[c] + && !(c === '^' && inClass)) { + re += '\\' + } + + re += c + + } // switch + } // for + + // handle the case where we left a class open. + // "[abc" is valid, equivalent to "\[abc" + if (inClass) { + // split where the last [ was, and escape it + // this is a huge pita. We now have to re-walk + // the contents of the would-be class to re-translate + // any characters that were passed through as-is + cs = pattern.substr(classStart + 1) + sp = this.parse(cs, SUBPARSE) + re = re.substr(0, reClassStart) + '\\[' + sp[0] + hasMagic = hasMagic || sp[1] + } + + // handle the case where we had a +( thing at the *end* + // of the pattern. + // each pattern list stack adds 3 chars, and we need to go through + // and escape any | chars that were passed through as-is for the regexp. + // Go through and escape them, taking care not to double-escape any + // | chars that were already escaped. + for (pl = patternListStack.pop(); pl; pl = patternListStack.pop()) { + var tail = re.slice(pl.reStart + pl.open.length) + this.debug('setting tail', re, pl) + // maybe some even number of \, then maybe 1 \, followed by a | + tail = tail.replace(/((?:\\{2}){0,64})(\\?)\|/g, function (_, $1, $2) { + if (!$2) { + // the | isn't already escaped, so escape it. + $2 = '\\' + } + + // need to escape all those slashes *again*, without escaping the + // one that we need for escaping the | character. As it works out, + // escaping an even number of slashes can be done by simply repeating + // it exactly after itself. That's why this trick works. + // + // I am sorry that you have to see this. + return $1 + $1 + $2 + '|' + }) + + this.debug('tail=%j\n %s', tail, tail, pl, re) + var t = pl.type === '*' ? star + : pl.type === '?' ? qmark + : '\\' + pl.type + + hasMagic = true + re = re.slice(0, pl.reStart) + t + '\\(' + tail + } + + // handle trailing things that only matter at the very end. + clearStateChar() + if (escaping) { + // trailing \\ + re += '\\\\' + } + + // only need to apply the nodot start if the re starts with + // something that could conceivably capture a dot + var addPatternStart = false + switch (re.charAt(0)) { + case '.': + case '[': + case '(': addPatternStart = true + } + + // Hack to work around lack of negative lookbehind in JS + // A pattern like: *.!(x).!(y|z) needs to ensure that a name + // like 'a.xyz.yz' doesn't match. So, the first negative + // lookahead, has to look ALL the way ahead, to the end of + // the pattern. + for (var n = negativeLists.length - 1; n > -1; n--) { + var nl = negativeLists[n] + + var nlBefore = re.slice(0, nl.reStart) + var nlFirst = re.slice(nl.reStart, nl.reEnd - 8) + var nlLast = re.slice(nl.reEnd - 8, nl.reEnd) + var nlAfter = re.slice(nl.reEnd) + + nlLast += nlAfter + + // Handle nested stuff like *(*.js|!(*.json)), where open parens + // mean that we should *not* include the ) in the bit that is considered + // "after" the negated section. + var openParensBefore = nlBefore.split('(').length - 1 + var cleanAfter = nlAfter + for (i = 0; i < openParensBefore; i++) { + cleanAfter = cleanAfter.replace(/\)[+*?]?/, '') + } + nlAfter = cleanAfter + + var dollar = '' + if (nlAfter === '' && isSub !== SUBPARSE) { + dollar = '$' + } + var newRe = nlBefore + nlFirst + nlAfter + dollar + nlLast + re = newRe + } + + // if the re is not "" at this point, then we need to make sure + // it doesn't match against an empty path part. + // Otherwise a/* will match a/, which it should not. + if (re !== '' && hasMagic) { + re = '(?=.)' + re + } + + if (addPatternStart) { + re = patternStart + re + } + + // parsing just a piece of a larger pattern. + if (isSub === SUBPARSE) { + return [re, hasMagic] + } + + // skip the regexp for non-magical patterns + // unescape anything in it, though, so that it'll be + // an exact match against a file etc. + if (!hasMagic) { + return globUnescape(pattern) + } + + var flags = options.nocase ? 'i' : '' + try { + var regExp = new RegExp('^' + re + '$', flags) + } catch (er) { + // If it was an invalid regular expression, then it can't match + // anything. This trick looks for a character after the end of + // the string, which is of course impossible, except in multi-line + // mode, but it's not a /m regex. + return new RegExp('$.') + } + + regExp._glob = pattern + regExp._src = re + + return regExp +} + +minimatch.makeRe = function (pattern, options) { + return new Minimatch(pattern, options || {}).makeRe() +} + +Minimatch.prototype.makeRe = makeRe +function makeRe () { + if (this.regexp || this.regexp === false) return this.regexp + + // at this point, this.set is a 2d array of partial + // pattern strings, or "**". + // + // It's better to use .match(). This function shouldn't + // be used, really, but it's pretty convenient sometimes, + // when you just want to work with a regex. + var set = this.set + + if (!set.length) { + this.regexp = false + return this.regexp + } + var options = this.options + + var twoStar = options.noglobstar ? star + : options.dot ? twoStarDot + : twoStarNoDot + var flags = options.nocase ? 'i' : '' + + var re = set.map(function (pattern) { + return pattern.map(function (p) { + return (p === GLOBSTAR) ? twoStar + : (typeof p === 'string') ? regExpEscape(p) + : p._src + }).join('\\\/') + }).join('|') + + // must match entire pattern + // ending in a * or ** will make it less strict. + re = '^(?:' + re + ')$' + + // can match anything, as long as it's not this. + if (this.negate) re = '^(?!' + re + ').*$' + + try { + this.regexp = new RegExp(re, flags) + } catch (ex) { + this.regexp = false + } + return this.regexp +} + +minimatch.match = function (list, pattern, options) { + options = options || {} + var mm = new Minimatch(pattern, options) + list = list.filter(function (f) { + return mm.match(f) + }) + if (mm.options.nonull && !list.length) { + list.push(pattern) + } + return list +} + +Minimatch.prototype.match = match +function match (f, partial) { + this.debug('match', f, this.pattern) + // short-circuit in the case of busted things. + // comments, etc. + if (this.comment) return false + if (this.empty) return f === '' + + if (f === '/' && partial) return true + + var options = this.options + + // windows: need to use /, not \ + if (path.sep !== '/') { + f = f.split(path.sep).join('/') + } + + // treat the test path as a set of pathparts. + f = f.split(slashSplit) + this.debug(this.pattern, 'split', f) + + // just ONE of the pattern sets in this.set needs to match + // in order for it to be valid. If negating, then just one + // match means that we have failed. + // Either way, return on the first hit. + + var set = this.set + this.debug(this.pattern, 'set', set) + + // Find the basename of the path by looking for the last non-empty segment + var filename + var i + for (i = f.length - 1; i >= 0; i--) { + filename = f[i] + if (filename) break + } + + for (i = 0; i < set.length; i++) { + var pattern = set[i] + var file = f + if (options.matchBase && pattern.length === 1) { + file = [filename] + } + var hit = this.matchOne(file, pattern, partial) + if (hit) { + if (options.flipNegate) return true + return !this.negate + } + } + + // didn't get any hits. this is success if it's a negative + // pattern, failure otherwise. + if (options.flipNegate) return false + return this.negate +} + +// set partial to true to test if, for example, +// "/a/b" matches the start of "/*/b/*/d" +// Partial means, if you run out of file before you run +// out of pattern, then that's fine, as long as all +// the parts match. +Minimatch.prototype.matchOne = function (file, pattern, partial) { + var options = this.options + + this.debug('matchOne', + { 'this': this, file: file, pattern: pattern }) + + this.debug('matchOne', file.length, pattern.length) + + for (var fi = 0, + pi = 0, + fl = file.length, + pl = pattern.length + ; (fi < fl) && (pi < pl) + ; fi++, pi++) { + this.debug('matchOne loop') + var p = pattern[pi] + var f = file[fi] + + this.debug(pattern, p, f) + + // should be impossible. + // some invalid regexp stuff in the set. + if (p === false) return false + + if (p === GLOBSTAR) { + this.debug('GLOBSTAR', [pattern, p, f]) + + // "**" + // a/**/b/**/c would match the following: + // a/b/x/y/z/c + // a/x/y/z/b/c + // a/b/x/b/x/c + // a/b/c + // To do this, take the rest of the pattern after + // the **, and see if it would match the file remainder. + // If so, return success. + // If not, the ** "swallows" a segment, and try again. + // This is recursively awful. + // + // a/**/b/**/c matching a/b/x/y/z/c + // - a matches a + // - doublestar + // - matchOne(b/x/y/z/c, b/**/c) + // - b matches b + // - doublestar + // - matchOne(x/y/z/c, c) -> no + // - matchOne(y/z/c, c) -> no + // - matchOne(z/c, c) -> no + // - matchOne(c, c) yes, hit + var fr = fi + var pr = pi + 1 + if (pr === pl) { + this.debug('** at the end') + // a ** at the end will just swallow the rest. + // We have found a match. + // however, it will not swallow /.x, unless + // options.dot is set. + // . and .. are *never* matched by **, for explosively + // exponential reasons. + for (; fi < fl; fi++) { + if (file[fi] === '.' || file[fi] === '..' || + (!options.dot && file[fi].charAt(0) === '.')) return false + } + return true + } + + // ok, let's see if we can swallow whatever we can. + while (fr < fl) { + var swallowee = file[fr] + + this.debug('\nglobstar while', file, fr, pattern, pr, swallowee) + + // XXX remove this slice. Just pass the start index. + if (this.matchOne(file.slice(fr), pattern.slice(pr), partial)) { + this.debug('globstar found match!', fr, fl, swallowee) + // found a match. + return true + } else { + // can't swallow "." or ".." ever. + // can only swallow ".foo" when explicitly asked. + if (swallowee === '.' || swallowee === '..' || + (!options.dot && swallowee.charAt(0) === '.')) { + this.debug('dot detected!', file, fr, pattern, pr) + break + } + + // ** swallows a segment, and continue. + this.debug('globstar swallow a segment, and continue') + fr++ + } + } + + // no match was found. + // However, in partial mode, we can't say this is necessarily over. + // If there's more *pattern* left, then + if (partial) { + // ran out of file + this.debug('\n>>> no match, partial?', file, fr, pattern, pr) + if (fr === fl) return true + } + return false + } + + // something other than ** + // non-magic patterns just have to match exactly + // patterns with magic have been turned into regexps. + var hit + if (typeof p === 'string') { + if (options.nocase) { + hit = f.toLowerCase() === p.toLowerCase() + } else { + hit = f === p + } + this.debug('string match', p, f, hit) + } else { + hit = f.match(p) + this.debug('pattern match', p, f, hit) + } + + if (!hit) return false + } + + // Note: ending in / means that we'll get a final "" + // at the end of the pattern. This can only match a + // corresponding "" at the end of the file. + // If the file ends in /, then it can only match a + // a pattern that ends in /, unless the pattern just + // doesn't have any more for it. But, a/b/ should *not* + // match "a/b/*", even though "" matches against the + // [^/]*? pattern, except in partial mode, where it might + // simply not be reached yet. + // However, a/b/ should still satisfy a/* + + // now either we fell off the end of the pattern, or we're done. + if (fi === fl && pi === pl) { + // ran out of pattern and filename at the same time. + // an exact hit! + return true + } else if (fi === fl) { + // ran out of file, but still had pattern left. + // this is ok if we're doing the match as part of + // a glob fs traversal. + return partial + } else if (pi === pl) { + // ran out of pattern, still have file left. + // this is only acceptable if we're on the very last + // empty segment of a file with a trailing slash. + // a/* should match a/b/ + var emptyFileEnd = (fi === fl - 1) && (file[fi] === '') + return emptyFileEnd + } + + // should be unreachable. + throw new Error('wtf?') +} + +// replace stuff like \* with * +function globUnescape (s) { + return s.replace(/\\(.)/g, '$1') +} + +function regExpEscape (s) { + return s.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, '\\$&') +} + +},{"brace-expansion":11,"path":22}],21:[function(require,module,exports){ +var wrappy = require('wrappy') +module.exports = wrappy(once) +module.exports.strict = wrappy(onceStrict) + +once.proto = once(function () { + Object.defineProperty(Function.prototype, 'once', { + value: function () { + return once(this) + }, + configurable: true + }) + + Object.defineProperty(Function.prototype, 'onceStrict', { + value: function () { + return onceStrict(this) + }, + configurable: true + }) +}) + +function once (fn) { + var f = function () { + if (f.called) return f.value + f.called = true + return f.value = fn.apply(this, arguments) + } + f.called = false + return f +} + +function onceStrict (fn) { + var f = function () { + if (f.called) + throw new Error(f.onceError) + f.called = true + return f.value = fn.apply(this, arguments) + } + var name = fn.name || 'Function wrapped with `once`' + f.onceError = name + " shouldn't be called more than once" + f.called = false + return f +} + +},{"wrappy":29}],22:[function(require,module,exports){ +(function (process){ +// Copyright Joyent, Inc. and other Node contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to permit +// persons to whom the Software is furnished to do so, subject to the +// following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +// USE OR OTHER DEALINGS IN THE SOFTWARE. + +// resolves . and .. elements in a path array with directory names there +// must be no slashes, empty elements, or device names (c:\) in the array +// (so also no leading and trailing slashes - it does not distinguish +// relative and absolute paths) +function normalizeArray(parts, allowAboveRoot) { + // if the path tries to go above the root, `up` ends up > 0 + var up = 0; + for (var i = parts.length - 1; i >= 0; i--) { + var last = parts[i]; + if (last === '.') { + parts.splice(i, 1); + } else if (last === '..') { + parts.splice(i, 1); + up++; + } else if (up) { + parts.splice(i, 1); + up--; + } + } + + // if the path is allowed to go above the root, restore leading ..s + if (allowAboveRoot) { + for (; up--; up) { + parts.unshift('..'); + } + } + + return parts; +} + +// Split a filename into [root, dir, basename, ext], unix version +// 'root' is just a slash, or nothing. +var splitPathRe = + /^(\/?|)([\s\S]*?)((?:\.{1,2}|[^\/]+?|)(\.[^.\/]*|))(?:[\/]*)$/; +var splitPath = function(filename) { + return splitPathRe.exec(filename).slice(1); +}; + +// path.resolve([from ...], to) +// posix version +exports.resolve = function() { + var resolvedPath = '', + resolvedAbsolute = false; + + for (var i = arguments.length - 1; i >= -1 && !resolvedAbsolute; i--) { + var path = (i >= 0) ? arguments[i] : process.cwd(); + + // Skip empty and invalid entries + if (typeof path !== 'string') { + throw new TypeError('Arguments to path.resolve must be strings'); + } else if (!path) { + continue; + } + + resolvedPath = path + '/' + resolvedPath; + resolvedAbsolute = path.charAt(0) === '/'; + } + + // At this point the path should be resolved to a full absolute path, but + // handle relative paths to be safe (might happen when process.cwd() fails) + + // Normalize the path + resolvedPath = normalizeArray(filter(resolvedPath.split('/'), function(p) { + return !!p; + }), !resolvedAbsolute).join('/'); + + return ((resolvedAbsolute ? '/' : '') + resolvedPath) || '.'; +}; + +// path.normalize(path) +// posix version +exports.normalize = function(path) { + var isAbsolute = exports.isAbsolute(path), + trailingSlash = substr(path, -1) === '/'; + + // Normalize the path + path = normalizeArray(filter(path.split('/'), function(p) { + return !!p; + }), !isAbsolute).join('/'); + + if (!path && !isAbsolute) { + path = '.'; + } + if (path && trailingSlash) { + path += '/'; + } + + return (isAbsolute ? '/' : '') + path; +}; + +// posix version +exports.isAbsolute = function(path) { + return path.charAt(0) === '/'; +}; + +// posix version +exports.join = function() { + var paths = Array.prototype.slice.call(arguments, 0); + return exports.normalize(filter(paths, function(p, index) { + if (typeof p !== 'string') { + throw new TypeError('Arguments to path.join must be strings'); + } + return p; + }).join('/')); +}; + + +// path.relative(from, to) +// posix version +exports.relative = function(from, to) { + from = exports.resolve(from).substr(1); + to = exports.resolve(to).substr(1); + + function trim(arr) { + var start = 0; + for (; start < arr.length; start++) { + if (arr[start] !== '') break; + } + + var end = arr.length - 1; + for (; end >= 0; end--) { + if (arr[end] !== '') break; + } + + if (start > end) return []; + return arr.slice(start, end - start + 1); + } + + var fromParts = trim(from.split('/')); + var toParts = trim(to.split('/')); + + var length = Math.min(fromParts.length, toParts.length); + var samePartsLength = length; + for (var i = 0; i < length; i++) { + if (fromParts[i] !== toParts[i]) { + samePartsLength = i; + break; + } + } + + var outputParts = []; + for (var i = samePartsLength; i < fromParts.length; i++) { + outputParts.push('..'); + } + + outputParts = outputParts.concat(toParts.slice(samePartsLength)); + + return outputParts.join('/'); +}; + +exports.sep = '/'; +exports.delimiter = ':'; + +exports.dirname = function(path) { + var result = splitPath(path), + root = result[0], + dir = result[1]; + + if (!root && !dir) { + // No dirname whatsoever + return '.'; + } + + if (dir) { + // It has a dirname, strip trailing slash + dir = dir.substr(0, dir.length - 1); + } + + return root + dir; +}; + + +exports.basename = function(path, ext) { + var f = splitPath(path)[2]; + // TODO: make this comparison case-insensitive on windows? + if (ext && f.substr(-1 * ext.length) === ext) { + f = f.substr(0, f.length - ext.length); + } + return f; +}; + + +exports.extname = function(path) { + return splitPath(path)[3]; +}; + +function filter (xs, f) { + if (xs.filter) return xs.filter(f); + var res = []; + for (var i = 0; i < xs.length; i++) { + if (f(xs[i], i, xs)) res.push(xs[i]); + } + return res; +} + +// String.prototype.substr - negative index don't work in IE8 +var substr = 'ab'.substr(-1) === 'b' + ? function (str, start, len) { return str.substr(start, len) } + : function (str, start, len) { + if (start < 0) start = str.length + start; + return str.substr(start, len); + } +; + +}).call(this,require('_process')) +},{"_process":24}],23:[function(require,module,exports){ +(function (process){ +'use strict'; + +function posix(path) { + return path.charAt(0) === '/'; +} + +function win32(path) { + // https://github.com/nodejs/node/blob/b3fcc245fb25539909ef1d5eaa01dbf92e168633/lib/path.js#L56 + var splitDeviceRe = /^([a-zA-Z]:|[\\\/]{2}[^\\\/]+[\\\/]+[^\\\/]+)?([\\\/])?([\s\S]*?)$/; + var result = splitDeviceRe.exec(path); + var device = result[1] || ''; + var isUnc = Boolean(device && device.charAt(1) !== ':'); + + // UNC paths are always absolute + return Boolean(result[2] || isUnc); +} + +module.exports = process.platform === 'win32' ? win32 : posix; +module.exports.posix = posix; +module.exports.win32 = win32; + +}).call(this,require('_process')) +},{"_process":24}],24:[function(require,module,exports){ +// shim for using process in browser +var process = module.exports = {}; + +// cached from whatever global is present so that test runners that stub it +// don't break things. But we need to wrap it in a try catch in case it is +// wrapped in strict mode code which doesn't define any globals. It's inside a +// function because try/catches deoptimize in certain engines. + +var cachedSetTimeout; +var cachedClearTimeout; + +function defaultSetTimout() { + throw new Error('setTimeout has not been defined'); +} +function defaultClearTimeout () { + throw new Error('clearTimeout has not been defined'); +} +(function () { + try { + if (typeof setTimeout === 'function') { + cachedSetTimeout = setTimeout; + } else { + cachedSetTimeout = defaultSetTimout; + } + } catch (e) { + cachedSetTimeout = defaultSetTimout; + } + try { + if (typeof clearTimeout === 'function') { + cachedClearTimeout = clearTimeout; + } else { + cachedClearTimeout = defaultClearTimeout; + } + } catch (e) { + cachedClearTimeout = defaultClearTimeout; + } +} ()) +function runTimeout(fun) { + if (cachedSetTimeout === setTimeout) { + //normal enviroments in sane situations + return setTimeout(fun, 0); + } + // if setTimeout wasn't available but was latter defined + if ((cachedSetTimeout === defaultSetTimout || !cachedSetTimeout) && setTimeout) { + cachedSetTimeout = setTimeout; + return setTimeout(fun, 0); + } + try { + // when when somebody has screwed with setTimeout but no I.E. maddness + return cachedSetTimeout(fun, 0); + } catch(e){ + try { + // When we are in I.E. but the script has been evaled so I.E. doesn't trust the global object when called normally + return cachedSetTimeout.call(null, fun, 0); + } catch(e){ + // same as above but when it's a version of I.E. that must have the global object for 'this', hopfully our context correct otherwise it will throw a global error + return cachedSetTimeout.call(this, fun, 0); + } + } + + +} +function runClearTimeout(marker) { + if (cachedClearTimeout === clearTimeout) { + //normal enviroments in sane situations + return clearTimeout(marker); + } + // if clearTimeout wasn't available but was latter defined + if ((cachedClearTimeout === defaultClearTimeout || !cachedClearTimeout) && clearTimeout) { + cachedClearTimeout = clearTimeout; + return clearTimeout(marker); + } + try { + // when when somebody has screwed with setTimeout but no I.E. maddness + return cachedClearTimeout(marker); + } catch (e){ + try { + // When we are in I.E. but the script has been evaled so I.E. doesn't trust the global object when called normally + return cachedClearTimeout.call(null, marker); + } catch (e){ + // same as above but when it's a version of I.E. that must have the global object for 'this', hopfully our context correct otherwise it will throw a global error. + // Some versions of I.E. have different rules for clearTimeout vs setTimeout + return cachedClearTimeout.call(this, marker); + } + } + + + +} +var queue = []; +var draining = false; +var currentQueue; +var queueIndex = -1; + +function cleanUpNextTick() { + if (!draining || !currentQueue) { + return; + } + draining = false; + if (currentQueue.length) { + queue = currentQueue.concat(queue); + } else { + queueIndex = -1; + } + if (queue.length) { + drainQueue(); + } +} + +function drainQueue() { + if (draining) { + return; + } + var timeout = runTimeout(cleanUpNextTick); + draining = true; + + var len = queue.length; + while(len) { + currentQueue = queue; + queue = []; + while (++queueIndex < len) { + if (currentQueue) { + currentQueue[queueIndex].run(); + } + } + queueIndex = -1; + len = queue.length; + } + currentQueue = null; + draining = false; + runClearTimeout(timeout); +} + +process.nextTick = function (fun) { + var args = new Array(arguments.length - 1); + if (arguments.length > 1) { + for (var i = 1; i < arguments.length; i++) { + args[i - 1] = arguments[i]; + } + } + queue.push(new Item(fun, args)); + if (queue.length === 1 && !draining) { + runTimeout(drainQueue); + } +}; + +// v8 likes predictible objects +function Item(fun, array) { + this.fun = fun; + this.array = array; +} +Item.prototype.run = function () { + this.fun.apply(null, this.array); +}; +process.title = 'browser'; +process.browser = true; +process.env = {}; +process.argv = []; +process.version = ''; // empty string to avoid regexp issues +process.versions = {}; + +function noop() {} + +process.on = noop; +process.addListener = noop; +process.once = noop; +process.off = noop; +process.removeListener = noop; +process.removeAllListeners = noop; +process.emit = noop; +process.prependListener = noop; +process.prependOnceListener = noop; + +process.listeners = function (name) { return [] } + +process.binding = function (name) { + throw new Error('process.binding is not supported'); +}; + +process.cwd = function () { return '/' }; +process.chdir = function (dir) { + throw new Error('process.chdir is not supported'); +}; +process.umask = function() { return 0; }; + +},{}],25:[function(require,module,exports){ +// Underscore.js 1.8.3 +// http://underscorejs.org +// (c) 2009-2015 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors +// Underscore may be freely distributed under the MIT license. + +(function() { + + // Baseline setup + // -------------- + + // Establish the root object, `window` in the browser, or `exports` on the server. + var root = this; + + // Save the previous value of the `_` variable. + var previousUnderscore = root._; + + // Save bytes in the minified (but not gzipped) version: + var ArrayProto = Array.prototype, ObjProto = Object.prototype, FuncProto = Function.prototype; + + // Create quick reference variables for speed access to core prototypes. + var + push = ArrayProto.push, + slice = ArrayProto.slice, + toString = ObjProto.toString, + hasOwnProperty = ObjProto.hasOwnProperty; + + // All **ECMAScript 5** native function implementations that we hope to use + // are declared here. + var + nativeIsArray = Array.isArray, + nativeKeys = Object.keys, + nativeBind = FuncProto.bind, + nativeCreate = Object.create; + + // Naked function reference for surrogate-prototype-swapping. + var Ctor = function(){}; + + // Create a safe reference to the Underscore object for use below. + var _ = function(obj) { + if (obj instanceof _) return obj; + if (!(this instanceof _)) return new _(obj); + this._wrapped = obj; + }; + + // Export the Underscore object for **Node.js**, with + // backwards-compatibility for the old `require()` API. If we're in + // the browser, add `_` as a global object. + if (typeof exports !== 'undefined') { + if (typeof module !== 'undefined' && module.exports) { + exports = module.exports = _; + } + exports._ = _; + } else { + root._ = _; + } + + // Current version. + _.VERSION = '1.8.3'; + + // Internal function that returns an efficient (for current engines) version + // of the passed-in callback, to be repeatedly applied in other Underscore + // functions. + var optimizeCb = function(func, context, argCount) { + if (context === void 0) return func; + switch (argCount == null ? 3 : argCount) { + case 1: return function(value) { + return func.call(context, value); + }; + case 2: return function(value, other) { + return func.call(context, value, other); + }; + case 3: return function(value, index, collection) { + return func.call(context, value, index, collection); + }; + case 4: return function(accumulator, value, index, collection) { + return func.call(context, accumulator, value, index, collection); + }; + } + return function() { + return func.apply(context, arguments); + }; + }; + + // A mostly-internal function to generate callbacks that can be applied + // to each element in a collection, returning the desired result — either + // identity, an arbitrary callback, a property matcher, or a property accessor. + var cb = function(value, context, argCount) { + if (value == null) return _.identity; + if (_.isFunction(value)) return optimizeCb(value, context, argCount); + if (_.isObject(value)) return _.matcher(value); + return _.property(value); + }; + _.iteratee = function(value, context) { + return cb(value, context, Infinity); + }; + + // An internal function for creating assigner functions. + var createAssigner = function(keysFunc, undefinedOnly) { + return function(obj) { + var length = arguments.length; + if (length < 2 || obj == null) return obj; + for (var index = 1; index < length; index++) { + var source = arguments[index], + keys = keysFunc(source), + l = keys.length; + for (var i = 0; i < l; i++) { + var key = keys[i]; + if (!undefinedOnly || obj[key] === void 0) obj[key] = source[key]; + } + } + return obj; + }; + }; + + // An internal function for creating a new object that inherits from another. + var baseCreate = function(prototype) { + if (!_.isObject(prototype)) return {}; + if (nativeCreate) return nativeCreate(prototype); + Ctor.prototype = prototype; + var result = new Ctor; + Ctor.prototype = null; + return result; + }; + + var property = function(key) { + return function(obj) { + return obj == null ? void 0 : obj[key]; + }; + }; + + // Helper for collection methods to determine whether a collection + // should be iterated as an array or as an object + // Related: http://people.mozilla.org/~jorendorff/es6-draft.html#sec-tolength + // Avoids a very nasty iOS 8 JIT bug on ARM-64. #2094 + var MAX_ARRAY_INDEX = Math.pow(2, 53) - 1; + var getLength = property('length'); + var isArrayLike = function(collection) { + var length = getLength(collection); + return typeof length == 'number' && length >= 0 && length <= MAX_ARRAY_INDEX; + }; + + // Collection Functions + // -------------------- + + // The cornerstone, an `each` implementation, aka `forEach`. + // Handles raw objects in addition to array-likes. Treats all + // sparse array-likes as if they were dense. + _.each = _.forEach = function(obj, iteratee, context) { + iteratee = optimizeCb(iteratee, context); + var i, length; + if (isArrayLike(obj)) { + for (i = 0, length = obj.length; i < length; i++) { + iteratee(obj[i], i, obj); + } + } else { + var keys = _.keys(obj); + for (i = 0, length = keys.length; i < length; i++) { + iteratee(obj[keys[i]], keys[i], obj); + } + } + return obj; + }; + + // Return the results of applying the iteratee to each element. + _.map = _.collect = function(obj, iteratee, context) { + iteratee = cb(iteratee, context); + var keys = !isArrayLike(obj) && _.keys(obj), + length = (keys || obj).length, + results = Array(length); + for (var index = 0; index < length; index++) { + var currentKey = keys ? keys[index] : index; + results[index] = iteratee(obj[currentKey], currentKey, obj); + } + return results; + }; + + // Create a reducing function iterating left or right. + function createReduce(dir) { + // Optimized iterator function as using arguments.length + // in the main function will deoptimize the, see #1991. + function iterator(obj, iteratee, memo, keys, index, length) { + for (; index >= 0 && index < length; index += dir) { + var currentKey = keys ? keys[index] : index; + memo = iteratee(memo, obj[currentKey], currentKey, obj); + } + return memo; + } + + return function(obj, iteratee, memo, context) { + iteratee = optimizeCb(iteratee, context, 4); + var keys = !isArrayLike(obj) && _.keys(obj), + length = (keys || obj).length, + index = dir > 0 ? 0 : length - 1; + // Determine the initial value if none is provided. + if (arguments.length < 3) { + memo = obj[keys ? keys[index] : index]; + index += dir; + } + return iterator(obj, iteratee, memo, keys, index, length); + }; + } + + // **Reduce** builds up a single result from a list of values, aka `inject`, + // or `foldl`. + _.reduce = _.foldl = _.inject = createReduce(1); + + // The right-associative version of reduce, also known as `foldr`. + _.reduceRight = _.foldr = createReduce(-1); + + // Return the first value which passes a truth test. Aliased as `detect`. + _.find = _.detect = function(obj, predicate, context) { + var key; + if (isArrayLike(obj)) { + key = _.findIndex(obj, predicate, context); + } else { + key = _.findKey(obj, predicate, context); + } + if (key !== void 0 && key !== -1) return obj[key]; + }; + + // Return all the elements that pass a truth test. + // Aliased as `select`. + _.filter = _.select = function(obj, predicate, context) { + var results = []; + predicate = cb(predicate, context); + _.each(obj, function(value, index, list) { + if (predicate(value, index, list)) results.push(value); + }); + return results; + }; + + // Return all the elements for which a truth test fails. + _.reject = function(obj, predicate, context) { + return _.filter(obj, _.negate(cb(predicate)), context); + }; + + // Determine whether all of the elements match a truth test. + // Aliased as `all`. + _.every = _.all = function(obj, predicate, context) { + predicate = cb(predicate, context); + var keys = !isArrayLike(obj) && _.keys(obj), + length = (keys || obj).length; + for (var index = 0; index < length; index++) { + var currentKey = keys ? keys[index] : index; + if (!predicate(obj[currentKey], currentKey, obj)) return false; + } + return true; + }; + + // Determine if at least one element in the object matches a truth test. + // Aliased as `any`. + _.some = _.any = function(obj, predicate, context) { + predicate = cb(predicate, context); + var keys = !isArrayLike(obj) && _.keys(obj), + length = (keys || obj).length; + for (var index = 0; index < length; index++) { + var currentKey = keys ? keys[index] : index; + if (predicate(obj[currentKey], currentKey, obj)) return true; + } + return false; + }; + + // Determine if the array or object contains a given item (using `===`). + // Aliased as `includes` and `include`. + _.contains = _.includes = _.include = function(obj, item, fromIndex, guard) { + if (!isArrayLike(obj)) obj = _.values(obj); + if (typeof fromIndex != 'number' || guard) fromIndex = 0; + return _.indexOf(obj, item, fromIndex) >= 0; + }; + + // Invoke a method (with arguments) on every item in a collection. + _.invoke = function(obj, method) { + var args = slice.call(arguments, 2); + var isFunc = _.isFunction(method); + return _.map(obj, function(value) { + var func = isFunc ? method : value[method]; + return func == null ? func : func.apply(value, args); + }); + }; + + // Convenience version of a common use case of `map`: fetching a property. + _.pluck = function(obj, key) { + return _.map(obj, _.property(key)); + }; + + // Convenience version of a common use case of `filter`: selecting only objects + // containing specific `key:value` pairs. + _.where = function(obj, attrs) { + return _.filter(obj, _.matcher(attrs)); + }; + + // Convenience version of a common use case of `find`: getting the first object + // containing specific `key:value` pairs. + _.findWhere = function(obj, attrs) { + return _.find(obj, _.matcher(attrs)); + }; + + // Return the maximum element (or element-based computation). + _.max = function(obj, iteratee, context) { + var result = -Infinity, lastComputed = -Infinity, + value, computed; + if (iteratee == null && obj != null) { + obj = isArrayLike(obj) ? obj : _.values(obj); + for (var i = 0, length = obj.length; i < length; i++) { + value = obj[i]; + if (value > result) { + result = value; + } + } + } else { + iteratee = cb(iteratee, context); + _.each(obj, function(value, index, list) { + computed = iteratee(value, index, list); + if (computed > lastComputed || computed === -Infinity && result === -Infinity) { + result = value; + lastComputed = computed; + } + }); + } + return result; + }; + + // Return the minimum element (or element-based computation). + _.min = function(obj, iteratee, context) { + var result = Infinity, lastComputed = Infinity, + value, computed; + if (iteratee == null && obj != null) { + obj = isArrayLike(obj) ? obj : _.values(obj); + for (var i = 0, length = obj.length; i < length; i++) { + value = obj[i]; + if (value < result) { + result = value; + } + } + } else { + iteratee = cb(iteratee, context); + _.each(obj, function(value, index, list) { + computed = iteratee(value, index, list); + if (computed < lastComputed || computed === Infinity && result === Infinity) { + result = value; + lastComputed = computed; + } + }); + } + return result; + }; + + // Shuffle a collection, using the modern version of the + // [Fisher-Yates shuffle](http://en.wikipedia.org/wiki/Fisher–Yates_shuffle). + _.shuffle = function(obj) { + var set = isArrayLike(obj) ? obj : _.values(obj); + var length = set.length; + var shuffled = Array(length); + for (var index = 0, rand; index < length; index++) { + rand = _.random(0, index); + if (rand !== index) shuffled[index] = shuffled[rand]; + shuffled[rand] = set[index]; + } + return shuffled; + }; + + // Sample **n** random values from a collection. + // If **n** is not specified, returns a single random element. + // The internal `guard` argument allows it to work with `map`. + _.sample = function(obj, n, guard) { + if (n == null || guard) { + if (!isArrayLike(obj)) obj = _.values(obj); + return obj[_.random(obj.length - 1)]; + } + return _.shuffle(obj).slice(0, Math.max(0, n)); + }; + + // Sort the object's values by a criterion produced by an iteratee. + _.sortBy = function(obj, iteratee, context) { + iteratee = cb(iteratee, context); + return _.pluck(_.map(obj, function(value, index, list) { + return { + value: value, + index: index, + criteria: iteratee(value, index, list) + }; + }).sort(function(left, right) { + var a = left.criteria; + var b = right.criteria; + if (a !== b) { + if (a > b || a === void 0) return 1; + if (a < b || b === void 0) return -1; + } + return left.index - right.index; + }), 'value'); + }; + + // An internal function used for aggregate "group by" operations. + var group = function(behavior) { + return function(obj, iteratee, context) { + var result = {}; + iteratee = cb(iteratee, context); + _.each(obj, function(value, index) { + var key = iteratee(value, index, obj); + behavior(result, value, key); + }); + return result; + }; + }; + + // Groups the object's values by a criterion. Pass either a string attribute + // to group by, or a function that returns the criterion. + _.groupBy = group(function(result, value, key) { + if (_.has(result, key)) result[key].push(value); else result[key] = [value]; + }); + + // Indexes the object's values by a criterion, similar to `groupBy`, but for + // when you know that your index values will be unique. + _.indexBy = group(function(result, value, key) { + result[key] = value; + }); + + // Counts instances of an object that group by a certain criterion. Pass + // either a string attribute to count by, or a function that returns the + // criterion. + _.countBy = group(function(result, value, key) { + if (_.has(result, key)) result[key]++; else result[key] = 1; + }); + + // Safely create a real, live array from anything iterable. + _.toArray = function(obj) { + if (!obj) return []; + if (_.isArray(obj)) return slice.call(obj); + if (isArrayLike(obj)) return _.map(obj, _.identity); + return _.values(obj); + }; + + // Return the number of elements in an object. + _.size = function(obj) { + if (obj == null) return 0; + return isArrayLike(obj) ? obj.length : _.keys(obj).length; + }; + + // Split a collection into two arrays: one whose elements all satisfy the given + // predicate, and one whose elements all do not satisfy the predicate. + _.partition = function(obj, predicate, context) { + predicate = cb(predicate, context); + var pass = [], fail = []; + _.each(obj, function(value, key, obj) { + (predicate(value, key, obj) ? pass : fail).push(value); + }); + return [pass, fail]; + }; + + // Array Functions + // --------------- + + // Get the first element of an array. Passing **n** will return the first N + // values in the array. Aliased as `head` and `take`. The **guard** check + // allows it to work with `_.map`. + _.first = _.head = _.take = function(array, n, guard) { + if (array == null) return void 0; + if (n == null || guard) return array[0]; + return _.initial(array, array.length - n); + }; + + // Returns everything but the last entry of the array. Especially useful on + // the arguments object. Passing **n** will return all the values in + // the array, excluding the last N. + _.initial = function(array, n, guard) { + return slice.call(array, 0, Math.max(0, array.length - (n == null || guard ? 1 : n))); + }; + + // Get the last element of an array. Passing **n** will return the last N + // values in the array. + _.last = function(array, n, guard) { + if (array == null) return void 0; + if (n == null || guard) return array[array.length - 1]; + return _.rest(array, Math.max(0, array.length - n)); + }; + + // Returns everything but the first entry of the array. Aliased as `tail` and `drop`. + // Especially useful on the arguments object. Passing an **n** will return + // the rest N values in the array. + _.rest = _.tail = _.drop = function(array, n, guard) { + return slice.call(array, n == null || guard ? 1 : n); + }; + + // Trim out all falsy values from an array. + _.compact = function(array) { + return _.filter(array, _.identity); + }; + + // Internal implementation of a recursive `flatten` function. + var flatten = function(input, shallow, strict, startIndex) { + var output = [], idx = 0; + for (var i = startIndex || 0, length = getLength(input); i < length; i++) { + var value = input[i]; + if (isArrayLike(value) && (_.isArray(value) || _.isArguments(value))) { + //flatten current level of array or arguments object + if (!shallow) value = flatten(value, shallow, strict); + var j = 0, len = value.length; + output.length += len; + while (j < len) { + output[idx++] = value[j++]; + } + } else if (!strict) { + output[idx++] = value; + } + } + return output; + }; + + // Flatten out an array, either recursively (by default), or just one level. + _.flatten = function(array, shallow) { + return flatten(array, shallow, false); + }; + + // Return a version of the array that does not contain the specified value(s). + _.without = function(array) { + return _.difference(array, slice.call(arguments, 1)); + }; + + // Produce a duplicate-free version of the array. If the array has already + // been sorted, you have the option of using a faster algorithm. + // Aliased as `unique`. + _.uniq = _.unique = function(array, isSorted, iteratee, context) { + if (!_.isBoolean(isSorted)) { + context = iteratee; + iteratee = isSorted; + isSorted = false; + } + if (iteratee != null) iteratee = cb(iteratee, context); + var result = []; + var seen = []; + for (var i = 0, length = getLength(array); i < length; i++) { + var value = array[i], + computed = iteratee ? iteratee(value, i, array) : value; + if (isSorted) { + if (!i || seen !== computed) result.push(value); + seen = computed; + } else if (iteratee) { + if (!_.contains(seen, computed)) { + seen.push(computed); + result.push(value); + } + } else if (!_.contains(result, value)) { + result.push(value); + } + } + return result; + }; + + // Produce an array that contains the union: each distinct element from all of + // the passed-in arrays. + _.union = function() { + return _.uniq(flatten(arguments, true, true)); + }; + + // Produce an array that contains every item shared between all the + // passed-in arrays. + _.intersection = function(array) { + var result = []; + var argsLength = arguments.length; + for (var i = 0, length = getLength(array); i < length; i++) { + var item = array[i]; + if (_.contains(result, item)) continue; + for (var j = 1; j < argsLength; j++) { + if (!_.contains(arguments[j], item)) break; + } + if (j === argsLength) result.push(item); + } + return result; + }; + + // Take the difference between one array and a number of other arrays. + // Only the elements present in just the first array will remain. + _.difference = function(array) { + var rest = flatten(arguments, true, true, 1); + return _.filter(array, function(value){ + return !_.contains(rest, value); + }); + }; + + // Zip together multiple lists into a single array -- elements that share + // an index go together. + _.zip = function() { + return _.unzip(arguments); + }; + + // Complement of _.zip. Unzip accepts an array of arrays and groups + // each array's elements on shared indices + _.unzip = function(array) { + var length = array && _.max(array, getLength).length || 0; + var result = Array(length); + + for (var index = 0; index < length; index++) { + result[index] = _.pluck(array, index); + } + return result; + }; + + // Converts lists into objects. Pass either a single array of `[key, value]` + // pairs, or two parallel arrays of the same length -- one of keys, and one of + // the corresponding values. + _.object = function(list, values) { + var result = {}; + for (var i = 0, length = getLength(list); i < length; i++) { + if (values) { + result[list[i]] = values[i]; + } else { + result[list[i][0]] = list[i][1]; + } + } + return result; + }; + + // Generator function to create the findIndex and findLastIndex functions + function createPredicateIndexFinder(dir) { + return function(array, predicate, context) { + predicate = cb(predicate, context); + var length = getLength(array); + var index = dir > 0 ? 0 : length - 1; + for (; index >= 0 && index < length; index += dir) { + if (predicate(array[index], index, array)) return index; + } + return -1; + }; + } + + // Returns the first index on an array-like that passes a predicate test + _.findIndex = createPredicateIndexFinder(1); + _.findLastIndex = createPredicateIndexFinder(-1); + + // Use a comparator function to figure out the smallest index at which + // an object should be inserted so as to maintain order. Uses binary search. + _.sortedIndex = function(array, obj, iteratee, context) { + iteratee = cb(iteratee, context, 1); + var value = iteratee(obj); + var low = 0, high = getLength(array); + while (low < high) { + var mid = Math.floor((low + high) / 2); + if (iteratee(array[mid]) < value) low = mid + 1; else high = mid; + } + return low; + }; + + // Generator function to create the indexOf and lastIndexOf functions + function createIndexFinder(dir, predicateFind, sortedIndex) { + return function(array, item, idx) { + var i = 0, length = getLength(array); + if (typeof idx == 'number') { + if (dir > 0) { + i = idx >= 0 ? idx : Math.max(idx + length, i); + } else { + length = idx >= 0 ? Math.min(idx + 1, length) : idx + length + 1; + } + } else if (sortedIndex && idx && length) { + idx = sortedIndex(array, item); + return array[idx] === item ? idx : -1; + } + if (item !== item) { + idx = predicateFind(slice.call(array, i, length), _.isNaN); + return idx >= 0 ? idx + i : -1; + } + for (idx = dir > 0 ? i : length - 1; idx >= 0 && idx < length; idx += dir) { + if (array[idx] === item) return idx; + } + return -1; + }; + } + + // Return the position of the first occurrence of an item in an array, + // or -1 if the item is not included in the array. + // If the array is large and already in sort order, pass `true` + // for **isSorted** to use binary search. + _.indexOf = createIndexFinder(1, _.findIndex, _.sortedIndex); + _.lastIndexOf = createIndexFinder(-1, _.findLastIndex); + + // Generate an integer Array containing an arithmetic progression. A port of + // the native Python `range()` function. See + // [the Python documentation](http://docs.python.org/library/functions.html#range). + _.range = function(start, stop, step) { + if (stop == null) { + stop = start || 0; + start = 0; + } + step = step || 1; + + var length = Math.max(Math.ceil((stop - start) / step), 0); + var range = Array(length); + + for (var idx = 0; idx < length; idx++, start += step) { + range[idx] = start; + } + + return range; + }; + + // Function (ahem) Functions + // ------------------ + + // Determines whether to execute a function as a constructor + // or a normal function with the provided arguments + var executeBound = function(sourceFunc, boundFunc, context, callingContext, args) { + if (!(callingContext instanceof boundFunc)) return sourceFunc.apply(context, args); + var self = baseCreate(sourceFunc.prototype); + var result = sourceFunc.apply(self, args); + if (_.isObject(result)) return result; + return self; + }; + + // Create a function bound to a given object (assigning `this`, and arguments, + // optionally). Delegates to **ECMAScript 5**'s native `Function.bind` if + // available. + _.bind = function(func, context) { + if (nativeBind && func.bind === nativeBind) return nativeBind.apply(func, slice.call(arguments, 1)); + if (!_.isFunction(func)) throw new TypeError('Bind must be called on a function'); + var args = slice.call(arguments, 2); + var bound = function() { + return executeBound(func, bound, context, this, args.concat(slice.call(arguments))); + }; + return bound; + }; + + // Partially apply a function by creating a version that has had some of its + // arguments pre-filled, without changing its dynamic `this` context. _ acts + // as a placeholder, allowing any combination of arguments to be pre-filled. + _.partial = function(func) { + var boundArgs = slice.call(arguments, 1); + var bound = function() { + var position = 0, length = boundArgs.length; + var args = Array(length); + for (var i = 0; i < length; i++) { + args[i] = boundArgs[i] === _ ? arguments[position++] : boundArgs[i]; + } + while (position < arguments.length) args.push(arguments[position++]); + return executeBound(func, bound, this, this, args); + }; + return bound; + }; + + // Bind a number of an object's methods to that object. Remaining arguments + // are the method names to be bound. Useful for ensuring that all callbacks + // defined on an object belong to it. + _.bindAll = function(obj) { + var i, length = arguments.length, key; + if (length <= 1) throw new Error('bindAll must be passed function names'); + for (i = 1; i < length; i++) { + key = arguments[i]; + obj[key] = _.bind(obj[key], obj); + } + return obj; + }; + + // Memoize an expensive function by storing its results. + _.memoize = function(func, hasher) { + var memoize = function(key) { + var cache = memoize.cache; + var address = '' + (hasher ? hasher.apply(this, arguments) : key); + if (!_.has(cache, address)) cache[address] = func.apply(this, arguments); + return cache[address]; + }; + memoize.cache = {}; + return memoize; + }; + + // Delays a function for the given number of milliseconds, and then calls + // it with the arguments supplied. + _.delay = function(func, wait) { + var args = slice.call(arguments, 2); + return setTimeout(function(){ + return func.apply(null, args); + }, wait); + }; + + // Defers a function, scheduling it to run after the current call stack has + // cleared. + _.defer = _.partial(_.delay, _, 1); + + // Returns a function, that, when invoked, will only be triggered at most once + // during a given window of time. Normally, the throttled function will run + // as much as it can, without ever going more than once per `wait` duration; + // but if you'd like to disable the execution on the leading edge, pass + // `{leading: false}`. To disable execution on the trailing edge, ditto. + _.throttle = function(func, wait, options) { + var context, args, result; + var timeout = null; + var previous = 0; + if (!options) options = {}; + var later = function() { + previous = options.leading === false ? 0 : _.now(); + timeout = null; + result = func.apply(context, args); + if (!timeout) context = args = null; + }; + return function() { + var now = _.now(); + if (!previous && options.leading === false) previous = now; + var remaining = wait - (now - previous); + context = this; + args = arguments; + if (remaining <= 0 || remaining > wait) { + if (timeout) { + clearTimeout(timeout); + timeout = null; + } + previous = now; + result = func.apply(context, args); + if (!timeout) context = args = null; + } else if (!timeout && options.trailing !== false) { + timeout = setTimeout(later, remaining); + } + return result; + }; + }; + + // Returns a function, that, as long as it continues to be invoked, will not + // be triggered. The function will be called after it stops being called for + // N milliseconds. If `immediate` is passed, trigger the function on the + // leading edge, instead of the trailing. + _.debounce = function(func, wait, immediate) { + var timeout, args, context, timestamp, result; + + var later = function() { + var last = _.now() - timestamp; + + if (last < wait && last >= 0) { + timeout = setTimeout(later, wait - last); + } else { + timeout = null; + if (!immediate) { + result = func.apply(context, args); + if (!timeout) context = args = null; + } + } + }; + + return function() { + context = this; + args = arguments; + timestamp = _.now(); + var callNow = immediate && !timeout; + if (!timeout) timeout = setTimeout(later, wait); + if (callNow) { + result = func.apply(context, args); + context = args = null; + } + + return result; + }; + }; + + // Returns the first function passed as an argument to the second, + // allowing you to adjust arguments, run code before and after, and + // conditionally execute the original function. + _.wrap = function(func, wrapper) { + return _.partial(wrapper, func); + }; + + // Returns a negated version of the passed-in predicate. + _.negate = function(predicate) { + return function() { + return !predicate.apply(this, arguments); + }; + }; + + // Returns a function that is the composition of a list of functions, each + // consuming the return value of the function that follows. + _.compose = function() { + var args = arguments; + var start = args.length - 1; + return function() { + var i = start; + var result = args[start].apply(this, arguments); + while (i--) result = args[i].call(this, result); + return result; + }; + }; + + // Returns a function that will only be executed on and after the Nth call. + _.after = function(times, func) { + return function() { + if (--times < 1) { + return func.apply(this, arguments); + } + }; + }; + + // Returns a function that will only be executed up to (but not including) the Nth call. + _.before = function(times, func) { + var memo; + return function() { + if (--times > 0) { + memo = func.apply(this, arguments); + } + if (times <= 1) func = null; + return memo; + }; + }; + + // Returns a function that will be executed at most one time, no matter how + // often you call it. Useful for lazy initialization. + _.once = _.partial(_.before, 2); + + // Object Functions + // ---------------- + + // Keys in IE < 9 that won't be iterated by `for key in ...` and thus missed. + var hasEnumBug = !{toString: null}.propertyIsEnumerable('toString'); + var nonEnumerableProps = ['valueOf', 'isPrototypeOf', 'toString', + 'propertyIsEnumerable', 'hasOwnProperty', 'toLocaleString']; + + function collectNonEnumProps(obj, keys) { + var nonEnumIdx = nonEnumerableProps.length; + var constructor = obj.constructor; + var proto = (_.isFunction(constructor) && constructor.prototype) || ObjProto; + + // Constructor is a special case. + var prop = 'constructor'; + if (_.has(obj, prop) && !_.contains(keys, prop)) keys.push(prop); + + while (nonEnumIdx--) { + prop = nonEnumerableProps[nonEnumIdx]; + if (prop in obj && obj[prop] !== proto[prop] && !_.contains(keys, prop)) { + keys.push(prop); + } + } + } + + // Retrieve the names of an object's own properties. + // Delegates to **ECMAScript 5**'s native `Object.keys` + _.keys = function(obj) { + if (!_.isObject(obj)) return []; + if (nativeKeys) return nativeKeys(obj); + var keys = []; + for (var key in obj) if (_.has(obj, key)) keys.push(key); + // Ahem, IE < 9. + if (hasEnumBug) collectNonEnumProps(obj, keys); + return keys; + }; + + // Retrieve all the property names of an object. + _.allKeys = function(obj) { + if (!_.isObject(obj)) return []; + var keys = []; + for (var key in obj) keys.push(key); + // Ahem, IE < 9. + if (hasEnumBug) collectNonEnumProps(obj, keys); + return keys; + }; + + // Retrieve the values of an object's properties. + _.values = function(obj) { + var keys = _.keys(obj); + var length = keys.length; + var values = Array(length); + for (var i = 0; i < length; i++) { + values[i] = obj[keys[i]]; + } + return values; + }; + + // Returns the results of applying the iteratee to each element of the object + // In contrast to _.map it returns an object + _.mapObject = function(obj, iteratee, context) { + iteratee = cb(iteratee, context); + var keys = _.keys(obj), + length = keys.length, + results = {}, + currentKey; + for (var index = 0; index < length; index++) { + currentKey = keys[index]; + results[currentKey] = iteratee(obj[currentKey], currentKey, obj); + } + return results; + }; + + // Convert an object into a list of `[key, value]` pairs. + _.pairs = function(obj) { + var keys = _.keys(obj); + var length = keys.length; + var pairs = Array(length); + for (var i = 0; i < length; i++) { + pairs[i] = [keys[i], obj[keys[i]]]; + } + return pairs; + }; + + // Invert the keys and values of an object. The values must be serializable. + _.invert = function(obj) { + var result = {}; + var keys = _.keys(obj); + for (var i = 0, length = keys.length; i < length; i++) { + result[obj[keys[i]]] = keys[i]; + } + return result; + }; + + // Return a sorted list of the function names available on the object. + // Aliased as `methods` + _.functions = _.methods = function(obj) { + var names = []; + for (var key in obj) { + if (_.isFunction(obj[key])) names.push(key); + } + return names.sort(); + }; + + // Extend a given object with all the properties in passed-in object(s). + _.extend = createAssigner(_.allKeys); + + // Assigns a given object with all the own properties in the passed-in object(s) + // (https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object/assign) + _.extendOwn = _.assign = createAssigner(_.keys); + + // Returns the first key on an object that passes a predicate test + _.findKey = function(obj, predicate, context) { + predicate = cb(predicate, context); + var keys = _.keys(obj), key; + for (var i = 0, length = keys.length; i < length; i++) { + key = keys[i]; + if (predicate(obj[key], key, obj)) return key; + } + }; + + // Return a copy of the object only containing the whitelisted properties. + _.pick = function(object, oiteratee, context) { + var result = {}, obj = object, iteratee, keys; + if (obj == null) return result; + if (_.isFunction(oiteratee)) { + keys = _.allKeys(obj); + iteratee = optimizeCb(oiteratee, context); + } else { + keys = flatten(arguments, false, false, 1); + iteratee = function(value, key, obj) { return key in obj; }; + obj = Object(obj); + } + for (var i = 0, length = keys.length; i < length; i++) { + var key = keys[i]; + var value = obj[key]; + if (iteratee(value, key, obj)) result[key] = value; + } + return result; + }; + + // Return a copy of the object without the blacklisted properties. + _.omit = function(obj, iteratee, context) { + if (_.isFunction(iteratee)) { + iteratee = _.negate(iteratee); + } else { + var keys = _.map(flatten(arguments, false, false, 1), String); + iteratee = function(value, key) { + return !_.contains(keys, key); + }; + } + return _.pick(obj, iteratee, context); + }; + + // Fill in a given object with default properties. + _.defaults = createAssigner(_.allKeys, true); + + // Creates an object that inherits from the given prototype object. + // If additional properties are provided then they will be added to the + // created object. + _.create = function(prototype, props) { + var result = baseCreate(prototype); + if (props) _.extendOwn(result, props); + return result; + }; + + // Create a (shallow-cloned) duplicate of an object. + _.clone = function(obj) { + if (!_.isObject(obj)) return obj; + return _.isArray(obj) ? obj.slice() : _.extend({}, obj); + }; + + // Invokes interceptor with the obj, and then returns obj. + // The primary purpose of this method is to "tap into" a method chain, in + // order to perform operations on intermediate results within the chain. + _.tap = function(obj, interceptor) { + interceptor(obj); + return obj; + }; + + // Returns whether an object has a given set of `key:value` pairs. + _.isMatch = function(object, attrs) { + var keys = _.keys(attrs), length = keys.length; + if (object == null) return !length; + var obj = Object(object); + for (var i = 0; i < length; i++) { + var key = keys[i]; + if (attrs[key] !== obj[key] || !(key in obj)) return false; + } + return true; + }; + + + // Internal recursive comparison function for `isEqual`. + var eq = function(a, b, aStack, bStack) { + // Identical objects are equal. `0 === -0`, but they aren't identical. + // See the [Harmony `egal` proposal](http://wiki.ecmascript.org/doku.php?id=harmony:egal). + if (a === b) return a !== 0 || 1 / a === 1 / b; + // A strict comparison is necessary because `null == undefined`. + if (a == null || b == null) return a === b; + // Unwrap any wrapped objects. + if (a instanceof _) a = a._wrapped; + if (b instanceof _) b = b._wrapped; + // Compare `[[Class]]` names. + var className = toString.call(a); + if (className !== toString.call(b)) return false; + switch (className) { + // Strings, numbers, regular expressions, dates, and booleans are compared by value. + case '[object RegExp]': + // RegExps are coerced to strings for comparison (Note: '' + /a/i === '/a/i') + case '[object String]': + // Primitives and their corresponding object wrappers are equivalent; thus, `"5"` is + // equivalent to `new String("5")`. + return '' + a === '' + b; + case '[object Number]': + // `NaN`s are equivalent, but non-reflexive. + // Object(NaN) is equivalent to NaN + if (+a !== +a) return +b !== +b; + // An `egal` comparison is performed for other numeric values. + return +a === 0 ? 1 / +a === 1 / b : +a === +b; + case '[object Date]': + case '[object Boolean]': + // Coerce dates and booleans to numeric primitive values. Dates are compared by their + // millisecond representations. Note that invalid dates with millisecond representations + // of `NaN` are not equivalent. + return +a === +b; + } + + var areArrays = className === '[object Array]'; + if (!areArrays) { + if (typeof a != 'object' || typeof b != 'object') return false; + + // Objects with different constructors are not equivalent, but `Object`s or `Array`s + // from different frames are. + var aCtor = a.constructor, bCtor = b.constructor; + if (aCtor !== bCtor && !(_.isFunction(aCtor) && aCtor instanceof aCtor && + _.isFunction(bCtor) && bCtor instanceof bCtor) + && ('constructor' in a && 'constructor' in b)) { + return false; + } + } + // Assume equality for cyclic structures. The algorithm for detecting cyclic + // structures is adapted from ES 5.1 section 15.12.3, abstract operation `JO`. + + // Initializing stack of traversed objects. + // It's done here since we only need them for objects and arrays comparison. + aStack = aStack || []; + bStack = bStack || []; + var length = aStack.length; + while (length--) { + // Linear search. Performance is inversely proportional to the number of + // unique nested structures. + if (aStack[length] === a) return bStack[length] === b; + } + + // Add the first object to the stack of traversed objects. + aStack.push(a); + bStack.push(b); + + // Recursively compare objects and arrays. + if (areArrays) { + // Compare array lengths to determine if a deep comparison is necessary. + length = a.length; + if (length !== b.length) return false; + // Deep compare the contents, ignoring non-numeric properties. + while (length--) { + if (!eq(a[length], b[length], aStack, bStack)) return false; + } + } else { + // Deep compare objects. + var keys = _.keys(a), key; + length = keys.length; + // Ensure that both objects contain the same number of properties before comparing deep equality. + if (_.keys(b).length !== length) return false; + while (length--) { + // Deep compare each member + key = keys[length]; + if (!(_.has(b, key) && eq(a[key], b[key], aStack, bStack))) return false; + } + } + // Remove the first object from the stack of traversed objects. + aStack.pop(); + bStack.pop(); + return true; + }; + + // Perform a deep comparison to check if two objects are equal. + _.isEqual = function(a, b) { + return eq(a, b); + }; + + // Is a given array, string, or object empty? + // An "empty" object has no enumerable own-properties. + _.isEmpty = function(obj) { + if (obj == null) return true; + if (isArrayLike(obj) && (_.isArray(obj) || _.isString(obj) || _.isArguments(obj))) return obj.length === 0; + return _.keys(obj).length === 0; + }; + + // Is a given value a DOM element? + _.isElement = function(obj) { + return !!(obj && obj.nodeType === 1); + }; + + // Is a given value an array? + // Delegates to ECMA5's native Array.isArray + _.isArray = nativeIsArray || function(obj) { + return toString.call(obj) === '[object Array]'; + }; + + // Is a given variable an object? + _.isObject = function(obj) { + var type = typeof obj; + return type === 'function' || type === 'object' && !!obj; + }; + + // Add some isType methods: isArguments, isFunction, isString, isNumber, isDate, isRegExp, isError. + _.each(['Arguments', 'Function', 'String', 'Number', 'Date', 'RegExp', 'Error'], function(name) { + _['is' + name] = function(obj) { + return toString.call(obj) === '[object ' + name + ']'; + }; + }); + + // Define a fallback version of the method in browsers (ahem, IE < 9), where + // there isn't any inspectable "Arguments" type. + if (!_.isArguments(arguments)) { + _.isArguments = function(obj) { + return _.has(obj, 'callee'); + }; + } + + // Optimize `isFunction` if appropriate. Work around some typeof bugs in old v8, + // IE 11 (#1621), and in Safari 8 (#1929). + if (typeof /./ != 'function' && typeof Int8Array != 'object') { + _.isFunction = function(obj) { + return typeof obj == 'function' || false; + }; + } + + // Is a given object a finite number? + _.isFinite = function(obj) { + return isFinite(obj) && !isNaN(parseFloat(obj)); + }; + + // Is the given value `NaN`? (NaN is the only number which does not equal itself). + _.isNaN = function(obj) { + return _.isNumber(obj) && obj !== +obj; + }; + + // Is a given value a boolean? + _.isBoolean = function(obj) { + return obj === true || obj === false || toString.call(obj) === '[object Boolean]'; + }; + + // Is a given value equal to null? + _.isNull = function(obj) { + return obj === null; + }; + + // Is a given variable undefined? + _.isUndefined = function(obj) { + return obj === void 0; + }; + + // Shortcut function for checking if an object has a given property directly + // on itself (in other words, not on a prototype). + _.has = function(obj, key) { + return obj != null && hasOwnProperty.call(obj, key); + }; + + // Utility Functions + // ----------------- + + // Run Underscore.js in *noConflict* mode, returning the `_` variable to its + // previous owner. Returns a reference to the Underscore object. + _.noConflict = function() { + root._ = previousUnderscore; + return this; + }; + + // Keep the identity function around for default iteratees. + _.identity = function(value) { + return value; + }; + + // Predicate-generating functions. Often useful outside of Underscore. + _.constant = function(value) { + return function() { + return value; + }; + }; + + _.noop = function(){}; + + _.property = property; + + // Generates a function for a given object that returns a given property. + _.propertyOf = function(obj) { + return obj == null ? function(){} : function(key) { + return obj[key]; + }; + }; + + // Returns a predicate for checking whether an object has a given set of + // `key:value` pairs. + _.matcher = _.matches = function(attrs) { + attrs = _.extendOwn({}, attrs); + return function(obj) { + return _.isMatch(obj, attrs); + }; + }; + + // Run a function **n** times. + _.times = function(n, iteratee, context) { + var accum = Array(Math.max(0, n)); + iteratee = optimizeCb(iteratee, context, 1); + for (var i = 0; i < n; i++) accum[i] = iteratee(i); + return accum; + }; + + // Return a random integer between min and max (inclusive). + _.random = function(min, max) { + if (max == null) { + max = min; + min = 0; + } + return min + Math.floor(Math.random() * (max - min + 1)); + }; + + // A (possibly faster) way to get the current timestamp as an integer. + _.now = Date.now || function() { + return new Date().getTime(); + }; + + // List of HTML entities for escaping. + var escapeMap = { + '&': '&', + '<': '<', + '>': '>', + '"': '"', + "'": ''', + '`': '`' + }; + var unescapeMap = _.invert(escapeMap); + + // Functions for escaping and unescaping strings to/from HTML interpolation. + var createEscaper = function(map) { + var escaper = function(match) { + return map[match]; + }; + // Regexes for identifying a key that needs to be escaped + var source = '(?:' + _.keys(map).join('|') + ')'; + var testRegexp = RegExp(source); + var replaceRegexp = RegExp(source, 'g'); + return function(string) { + string = string == null ? '' : '' + string; + return testRegexp.test(string) ? string.replace(replaceRegexp, escaper) : string; + }; + }; + _.escape = createEscaper(escapeMap); + _.unescape = createEscaper(unescapeMap); + + // If the value of the named `property` is a function then invoke it with the + // `object` as context; otherwise, return it. + _.result = function(object, property, fallback) { + var value = object == null ? void 0 : object[property]; + if (value === void 0) { + value = fallback; + } + return _.isFunction(value) ? value.call(object) : value; + }; + + // Generate a unique integer id (unique within the entire client session). + // Useful for temporary DOM ids. + var idCounter = 0; + _.uniqueId = function(prefix) { + var id = ++idCounter + ''; + return prefix ? prefix + id : id; + }; + + // By default, Underscore uses ERB-style template delimiters, change the + // following template settings to use alternative delimiters. + _.templateSettings = { + evaluate : /<%([\s\S]+?)%>/g, + interpolate : /<%=([\s\S]+?)%>/g, + escape : /<%-([\s\S]+?)%>/g + }; + + // When customizing `templateSettings`, if you don't want to define an + // interpolation, evaluation or escaping regex, we need one that is + // guaranteed not to match. + var noMatch = /(.)^/; + + // Certain characters need to be escaped so that they can be put into a + // string literal. + var escapes = { + "'": "'", + '\\': '\\', + '\r': 'r', + '\n': 'n', + '\u2028': 'u2028', + '\u2029': 'u2029' + }; + + var escaper = /\\|'|\r|\n|\u2028|\u2029/g; + + var escapeChar = function(match) { + return '\\' + escapes[match]; + }; + + // JavaScript micro-templating, similar to John Resig's implementation. + // Underscore templating handles arbitrary delimiters, preserves whitespace, + // and correctly escapes quotes within interpolated code. + // NB: `oldSettings` only exists for backwards compatibility. + _.template = function(text, settings, oldSettings) { + if (!settings && oldSettings) settings = oldSettings; + settings = _.defaults({}, settings, _.templateSettings); + + // Combine delimiters into one regular expression via alternation. + var matcher = RegExp([ + (settings.escape || noMatch).source, + (settings.interpolate || noMatch).source, + (settings.evaluate || noMatch).source + ].join('|') + '|$', 'g'); + + // Compile the template source, escaping string literals appropriately. + var index = 0; + var source = "__p+='"; + text.replace(matcher, function(match, escape, interpolate, evaluate, offset) { + source += text.slice(index, offset).replace(escaper, escapeChar); + index = offset + match.length; + + if (escape) { + source += "'+\n((__t=(" + escape + "))==null?'':_.escape(__t))+\n'"; + } else if (interpolate) { + source += "'+\n((__t=(" + interpolate + "))==null?'':__t)+\n'"; + } else if (evaluate) { + source += "';\n" + evaluate + "\n__p+='"; + } + + // Adobe VMs need the match returned to produce the correct offest. + return match; + }); + source += "';\n"; + + // If a variable is not specified, place data values in local scope. + if (!settings.variable) source = 'with(obj||{}){\n' + source + '}\n'; + + source = "var __t,__p='',__j=Array.prototype.join," + + "print=function(){__p+=__j.call(arguments,'');};\n" + + source + 'return __p;\n'; + + try { + var render = new Function(settings.variable || 'obj', '_', source); + } catch (e) { + e.source = source; + throw e; + } + + var template = function(data) { + return render.call(this, data, _); + }; + + // Provide the compiled source as a convenience for precompilation. + var argument = settings.variable || 'obj'; + template.source = 'function(' + argument + '){\n' + source + '}'; + + return template; + }; + + // Add a "chain" function. Start chaining a wrapped Underscore object. + _.chain = function(obj) { + var instance = _(obj); + instance._chain = true; + return instance; + }; + + // OOP + // --------------- + // If Underscore is called as a function, it returns a wrapped object that + // can be used OO-style. This wrapper holds altered versions of all the + // underscore functions. Wrapped objects may be chained. + + // Helper function to continue chaining intermediate results. + var result = function(instance, obj) { + return instance._chain ? _(obj).chain() : obj; + }; + + // Add your own custom functions to the Underscore object. + _.mixin = function(obj) { + _.each(_.functions(obj), function(name) { + var func = _[name] = obj[name]; + _.prototype[name] = function() { + var args = [this._wrapped]; + push.apply(args, arguments); + return result(this, func.apply(_, args)); + }; + }); + }; + + // Add all of the Underscore functions to the wrapper object. + _.mixin(_); + + // Add all mutator Array functions to the wrapper. + _.each(['pop', 'push', 'reverse', 'shift', 'sort', 'splice', 'unshift'], function(name) { + var method = ArrayProto[name]; + _.prototype[name] = function() { + var obj = this._wrapped; + method.apply(obj, arguments); + if ((name === 'shift' || name === 'splice') && obj.length === 0) delete obj[0]; + return result(this, obj); + }; + }); + + // Add all accessor Array functions to the wrapper. + _.each(['concat', 'join', 'slice'], function(name) { + var method = ArrayProto[name]; + _.prototype[name] = function() { + return result(this, method.apply(this._wrapped, arguments)); + }; + }); + + // Extracts the result from a wrapped and chained object. + _.prototype.value = function() { + return this._wrapped; + }; + + // Provide unwrapping proxy for some methods used in engine operations + // such as arithmetic and JSON stringification. + _.prototype.valueOf = _.prototype.toJSON = _.prototype.value; + + _.prototype.toString = function() { + return '' + this._wrapped; + }; + + // AMD registration happens at the end for compatibility with AMD loaders + // that may not enforce next-turn semantics on modules. Even though general + // practice for AMD registration is to be anonymous, underscore registers + // as a named module because, like jQuery, it is a base library that is + // popular enough to be bundled in a third party lib, but not be part of + // an AMD load request. Those cases could generate an error when an + // anonymous define() is called outside of a loader request. + if (typeof define === 'function' && define.amd) { + define('underscore', [], function() { + return _; + }); + } +}.call(this)); + +},{}],26:[function(require,module,exports){ +arguments[4][19][0].apply(exports,arguments) +},{"dup":19}],27:[function(require,module,exports){ +module.exports = function isBuffer(arg) { + return arg && typeof arg === 'object' + && typeof arg.copy === 'function' + && typeof arg.fill === 'function' + && typeof arg.readUInt8 === 'function'; +} +},{}],28:[function(require,module,exports){ +(function (process,global){ +// Copyright Joyent, Inc. and other Node contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to permit +// persons to whom the Software is furnished to do so, subject to the +// following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +// USE OR OTHER DEALINGS IN THE SOFTWARE. + +var formatRegExp = /%[sdj%]/g; +exports.format = function(f) { + if (!isString(f)) { + var objects = []; + for (var i = 0; i < arguments.length; i++) { + objects.push(inspect(arguments[i])); + } + return objects.join(' '); + } + + var i = 1; + var args = arguments; + var len = args.length; + var str = String(f).replace(formatRegExp, function(x) { + if (x === '%%') return '%'; + if (i >= len) return x; + switch (x) { + case '%s': return String(args[i++]); + case '%d': return Number(args[i++]); + case '%j': + try { + return JSON.stringify(args[i++]); + } catch (_) { + return '[Circular]'; + } + default: + return x; + } + }); + for (var x = args[i]; i < len; x = args[++i]) { + if (isNull(x) || !isObject(x)) { + str += ' ' + x; + } else { + str += ' ' + inspect(x); + } + } + return str; +}; + + +// Mark that a method should not be used. +// Returns a modified function which warns once by default. +// If --no-deprecation is set, then it is a no-op. +exports.deprecate = function(fn, msg) { + // Allow for deprecating things in the process of starting up. + if (isUndefined(global.process)) { + return function() { + return exports.deprecate(fn, msg).apply(this, arguments); + }; + } + + if (process.noDeprecation === true) { + return fn; + } + + var warned = false; + function deprecated() { + if (!warned) { + if (process.throwDeprecation) { + throw new Error(msg); + } else if (process.traceDeprecation) { + console.trace(msg); + } else { + console.error(msg); + } + warned = true; + } + return fn.apply(this, arguments); + } + + return deprecated; +}; + + +var debugs = {}; +var debugEnviron; +exports.debuglog = function(set) { + if (isUndefined(debugEnviron)) + debugEnviron = process.env.NODE_DEBUG || ''; + set = set.toUpperCase(); + if (!debugs[set]) { + if (new RegExp('\\b' + set + '\\b', 'i').test(debugEnviron)) { + var pid = process.pid; + debugs[set] = function() { + var msg = exports.format.apply(exports, arguments); + console.error('%s %d: %s', set, pid, msg); + }; + } else { + debugs[set] = function() {}; + } + } + return debugs[set]; +}; + + +/** + * Echos the value of a value. Trys to print the value out + * in the best way possible given the different types. + * + * @param {Object} obj The object to print out. + * @param {Object} opts Optional options object that alters the output. + */ +/* legacy: obj, showHidden, depth, colors*/ +function inspect(obj, opts) { + // default options + var ctx = { + seen: [], + stylize: stylizeNoColor + }; + // legacy... + if (arguments.length >= 3) ctx.depth = arguments[2]; + if (arguments.length >= 4) ctx.colors = arguments[3]; + if (isBoolean(opts)) { + // legacy... + ctx.showHidden = opts; + } else if (opts) { + // got an "options" object + exports._extend(ctx, opts); + } + // set default options + if (isUndefined(ctx.showHidden)) ctx.showHidden = false; + if (isUndefined(ctx.depth)) ctx.depth = 2; + if (isUndefined(ctx.colors)) ctx.colors = false; + if (isUndefined(ctx.customInspect)) ctx.customInspect = true; + if (ctx.colors) ctx.stylize = stylizeWithColor; + return formatValue(ctx, obj, ctx.depth); +} +exports.inspect = inspect; + + +// http://en.wikipedia.org/wiki/ANSI_escape_code#graphics +inspect.colors = { + 'bold' : [1, 22], + 'italic' : [3, 23], + 'underline' : [4, 24], + 'inverse' : [7, 27], + 'white' : [37, 39], + 'grey' : [90, 39], + 'black' : [30, 39], + 'blue' : [34, 39], + 'cyan' : [36, 39], + 'green' : [32, 39], + 'magenta' : [35, 39], + 'red' : [31, 39], + 'yellow' : [33, 39] +}; + +// Don't use 'blue' not visible on cmd.exe +inspect.styles = { + 'special': 'cyan', + 'number': 'yellow', + 'boolean': 'yellow', + 'undefined': 'grey', + 'null': 'bold', + 'string': 'green', + 'date': 'magenta', + // "name": intentionally not styling + 'regexp': 'red' +}; + + +function stylizeWithColor(str, styleType) { + var style = inspect.styles[styleType]; + + if (style) { + return '\u001b[' + inspect.colors[style][0] + 'm' + str + + '\u001b[' + inspect.colors[style][1] + 'm'; + } else { + return str; + } +} + + +function stylizeNoColor(str, styleType) { + return str; +} + + +function arrayToHash(array) { + var hash = {}; + + array.forEach(function(val, idx) { + hash[val] = true; + }); + + return hash; +} + + +function formatValue(ctx, value, recurseTimes) { + // Provide a hook for user-specified inspect functions. + // Check that value is an object with an inspect function on it + if (ctx.customInspect && + value && + isFunction(value.inspect) && + // Filter out the util module, it's inspect function is special + value.inspect !== exports.inspect && + // Also filter out any prototype objects using the circular check. + !(value.constructor && value.constructor.prototype === value)) { + var ret = value.inspect(recurseTimes, ctx); + if (!isString(ret)) { + ret = formatValue(ctx, ret, recurseTimes); + } + return ret; + } + + // Primitive types cannot have properties + var primitive = formatPrimitive(ctx, value); + if (primitive) { + return primitive; + } + + // Look up the keys of the object. + var keys = Object.keys(value); + var visibleKeys = arrayToHash(keys); + + if (ctx.showHidden) { + keys = Object.getOwnPropertyNames(value); + } + + // IE doesn't make error fields non-enumerable + // http://msdn.microsoft.com/en-us/library/ie/dww52sbt(v=vs.94).aspx + if (isError(value) + && (keys.indexOf('message') >= 0 || keys.indexOf('description') >= 0)) { + return formatError(value); + } + + // Some type of object without properties can be shortcutted. + if (keys.length === 0) { + if (isFunction(value)) { + var name = value.name ? ': ' + value.name : ''; + return ctx.stylize('[Function' + name + ']', 'special'); + } + if (isRegExp(value)) { + return ctx.stylize(RegExp.prototype.toString.call(value), 'regexp'); + } + if (isDate(value)) { + return ctx.stylize(Date.prototype.toString.call(value), 'date'); + } + if (isError(value)) { + return formatError(value); + } + } + + var base = '', array = false, braces = ['{', '}']; + + // Make Array say that they are Array + if (isArray(value)) { + array = true; + braces = ['[', ']']; + } + + // Make functions say that they are functions + if (isFunction(value)) { + var n = value.name ? ': ' + value.name : ''; + base = ' [Function' + n + ']'; + } + + // Make RegExps say that they are RegExps + if (isRegExp(value)) { + base = ' ' + RegExp.prototype.toString.call(value); + } + + // Make dates with properties first say the date + if (isDate(value)) { + base = ' ' + Date.prototype.toUTCString.call(value); + } + + // Make error with message first say the error + if (isError(value)) { + base = ' ' + formatError(value); + } + + if (keys.length === 0 && (!array || value.length == 0)) { + return braces[0] + base + braces[1]; + } + + if (recurseTimes < 0) { + if (isRegExp(value)) { + return ctx.stylize(RegExp.prototype.toString.call(value), 'regexp'); + } else { + return ctx.stylize('[Object]', 'special'); + } + } + + ctx.seen.push(value); + + var output; + if (array) { + output = formatArray(ctx, value, recurseTimes, visibleKeys, keys); + } else { + output = keys.map(function(key) { + return formatProperty(ctx, value, recurseTimes, visibleKeys, key, array); + }); + } + + ctx.seen.pop(); + + return reduceToSingleString(output, base, braces); +} + + +function formatPrimitive(ctx, value) { + if (isUndefined(value)) + return ctx.stylize('undefined', 'undefined'); + if (isString(value)) { + var simple = '\'' + JSON.stringify(value).replace(/^"|"$/g, '') + .replace(/'/g, "\\'") + .replace(/\\"/g, '"') + '\''; + return ctx.stylize(simple, 'string'); + } + if (isNumber(value)) + return ctx.stylize('' + value, 'number'); + if (isBoolean(value)) + return ctx.stylize('' + value, 'boolean'); + // For some reason typeof null is "object", so special case here. + if (isNull(value)) + return ctx.stylize('null', 'null'); +} + + +function formatError(value) { + return '[' + Error.prototype.toString.call(value) + ']'; +} + + +function formatArray(ctx, value, recurseTimes, visibleKeys, keys) { + var output = []; + for (var i = 0, l = value.length; i < l; ++i) { + if (hasOwnProperty(value, String(i))) { + output.push(formatProperty(ctx, value, recurseTimes, visibleKeys, + String(i), true)); + } else { + output.push(''); + } + } + keys.forEach(function(key) { + if (!key.match(/^\d+$/)) { + output.push(formatProperty(ctx, value, recurseTimes, visibleKeys, + key, true)); + } + }); + return output; +} + + +function formatProperty(ctx, value, recurseTimes, visibleKeys, key, array) { + var name, str, desc; + desc = Object.getOwnPropertyDescriptor(value, key) || { value: value[key] }; + if (desc.get) { + if (desc.set) { + str = ctx.stylize('[Getter/Setter]', 'special'); + } else { + str = ctx.stylize('[Getter]', 'special'); + } + } else { + if (desc.set) { + str = ctx.stylize('[Setter]', 'special'); + } + } + if (!hasOwnProperty(visibleKeys, key)) { + name = '[' + key + ']'; + } + if (!str) { + if (ctx.seen.indexOf(desc.value) < 0) { + if (isNull(recurseTimes)) { + str = formatValue(ctx, desc.value, null); + } else { + str = formatValue(ctx, desc.value, recurseTimes - 1); + } + if (str.indexOf('\n') > -1) { + if (array) { + str = str.split('\n').map(function(line) { + return ' ' + line; + }).join('\n').substr(2); + } else { + str = '\n' + str.split('\n').map(function(line) { + return ' ' + line; + }).join('\n'); + } + } + } else { + str = ctx.stylize('[Circular]', 'special'); + } + } + if (isUndefined(name)) { + if (array && key.match(/^\d+$/)) { + return str; + } + name = JSON.stringify('' + key); + if (name.match(/^"([a-zA-Z_][a-zA-Z_0-9]*)"$/)) { + name = name.substr(1, name.length - 2); + name = ctx.stylize(name, 'name'); + } else { + name = name.replace(/'/g, "\\'") + .replace(/\\"/g, '"') + .replace(/(^"|"$)/g, "'"); + name = ctx.stylize(name, 'string'); + } + } + + return name + ': ' + str; +} + + +function reduceToSingleString(output, base, braces) { + var numLinesEst = 0; + var length = output.reduce(function(prev, cur) { + numLinesEst++; + if (cur.indexOf('\n') >= 0) numLinesEst++; + return prev + cur.replace(/\u001b\[\d\d?m/g, '').length + 1; + }, 0); + + if (length > 60) { + return braces[0] + + (base === '' ? '' : base + '\n ') + + ' ' + + output.join(',\n ') + + ' ' + + braces[1]; + } + + return braces[0] + base + ' ' + output.join(', ') + ' ' + braces[1]; +} + + +// NOTE: These type checking functions intentionally don't use `instanceof` +// because it is fragile and can be easily faked with `Object.create()`. +function isArray(ar) { + return Array.isArray(ar); +} +exports.isArray = isArray; + +function isBoolean(arg) { + return typeof arg === 'boolean'; +} +exports.isBoolean = isBoolean; + +function isNull(arg) { + return arg === null; +} +exports.isNull = isNull; + +function isNullOrUndefined(arg) { + return arg == null; +} +exports.isNullOrUndefined = isNullOrUndefined; + +function isNumber(arg) { + return typeof arg === 'number'; +} +exports.isNumber = isNumber; + +function isString(arg) { + return typeof arg === 'string'; +} +exports.isString = isString; + +function isSymbol(arg) { + return typeof arg === 'symbol'; +} +exports.isSymbol = isSymbol; + +function isUndefined(arg) { + return arg === void 0; +} +exports.isUndefined = isUndefined; + +function isRegExp(re) { + return isObject(re) && objectToString(re) === '[object RegExp]'; +} +exports.isRegExp = isRegExp; + +function isObject(arg) { + return typeof arg === 'object' && arg !== null; +} +exports.isObject = isObject; + +function isDate(d) { + return isObject(d) && objectToString(d) === '[object Date]'; +} +exports.isDate = isDate; + +function isError(e) { + return isObject(e) && + (objectToString(e) === '[object Error]' || e instanceof Error); +} +exports.isError = isError; + +function isFunction(arg) { + return typeof arg === 'function'; +} +exports.isFunction = isFunction; + +function isPrimitive(arg) { + return arg === null || + typeof arg === 'boolean' || + typeof arg === 'number' || + typeof arg === 'string' || + typeof arg === 'symbol' || // ES6 symbol + typeof arg === 'undefined'; +} +exports.isPrimitive = isPrimitive; + +exports.isBuffer = require('./support/isBuffer'); + +function objectToString(o) { + return Object.prototype.toString.call(o); +} + + +function pad(n) { + return n < 10 ? '0' + n.toString(10) : n.toString(10); +} + + +var months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', + 'Oct', 'Nov', 'Dec']; + +// 26 Feb 16:19:34 +function timestamp() { + var d = new Date(); + var time = [pad(d.getHours()), + pad(d.getMinutes()), + pad(d.getSeconds())].join(':'); + return [d.getDate(), months[d.getMonth()], time].join(' '); +} + + +// log is just a thin wrapper to console.log that prepends a timestamp +exports.log = function() { + console.log('%s - %s', timestamp(), exports.format.apply(exports, arguments)); +}; + + +/** + * Inherit the prototype methods from one constructor into another. + * + * The Function.prototype.inherits from lang.js rewritten as a standalone + * function (not on Function.prototype). NOTE: If this file is to be loaded + * during bootstrapping this function needs to be rewritten using some native + * functions as prototype setup using normal JavaScript does not work as + * expected during bootstrapping (see mirror.js in r114903). + * + * @param {function} ctor Constructor function which needs to inherit the + * prototype. + * @param {function} superCtor Constructor function to inherit prototype from. + */ +exports.inherits = require('inherits'); + +exports._extend = function(origin, add) { + // Don't do anything if add isn't an object + if (!add || !isObject(add)) return origin; + + var keys = Object.keys(add); + var i = keys.length; + while (i--) { + origin[keys[i]] = add[keys[i]]; + } + return origin; +}; + +function hasOwnProperty(obj, prop) { + return Object.prototype.hasOwnProperty.call(obj, prop); +} + +}).call(this,require('_process'),typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {}) +},{"./support/isBuffer":27,"_process":24,"inherits":26}],29:[function(require,module,exports){ +// Returns a wrapper function that returns a wrapped callback +// The wrapper function should do some stuff, and return a +// presumably different callback function. +// This makes sure that own properties are retained, so that +// decorations and such are not lost along the way. +module.exports = wrappy +function wrappy (fn, cb) { + if (fn && cb) return wrappy(fn)(cb) + + if (typeof fn !== 'function') + throw new TypeError('need wrapper function') + + Object.keys(fn).forEach(function (k) { + wrapper[k] = fn[k] + }) + + return wrapper + + function wrapper() { + var args = new Array(arguments.length) + for (var i = 0; i < args.length; i++) { + args[i] = arguments[i] + } + var ret = fn.apply(this, args) + var cb = args[args.length-1] + if (typeof ret === 'function' && ret !== cb) { + Object.keys(cb).forEach(function (k) { + ret[k] = cb[k] + }) + } + return ret + } +} + +},{}]},{},[7])(7) +}); \ No newline at end of file diff --git a/develop/assets/javascripts/workers/search.b97dbffb.min.js b/develop/assets/javascripts/workers/search.b97dbffb.min.js new file mode 100644 index 0000000..8201c5e --- /dev/null +++ b/develop/assets/javascripts/workers/search.b97dbffb.min.js @@ -0,0 +1,48 @@ +"use strict";(()=>{var ge=Object.create;var W=Object.defineProperty,ye=Object.defineProperties,me=Object.getOwnPropertyDescriptor,ve=Object.getOwnPropertyDescriptors,xe=Object.getOwnPropertyNames,G=Object.getOwnPropertySymbols,Se=Object.getPrototypeOf,X=Object.prototype.hasOwnProperty,Qe=Object.prototype.propertyIsEnumerable;var J=(t,e,r)=>e in t?W(t,e,{enumerable:!0,configurable:!0,writable:!0,value:r}):t[e]=r,M=(t,e)=>{for(var r in e||(e={}))X.call(e,r)&&J(t,r,e[r]);if(G)for(var r of G(e))Qe.call(e,r)&&J(t,r,e[r]);return t},Z=(t,e)=>ye(t,ve(e));var K=(t,e)=>()=>(e||t((e={exports:{}}).exports,e),e.exports);var be=(t,e,r,n)=>{if(e&&typeof e=="object"||typeof e=="function")for(let i of xe(e))!X.call(t,i)&&i!==r&&W(t,i,{get:()=>e[i],enumerable:!(n=me(e,i))||n.enumerable});return t};var H=(t,e,r)=>(r=t!=null?ge(Se(t)):{},be(e||!t||!t.__esModule?W(r,"default",{value:t,enumerable:!0}):r,t));var z=(t,e,r)=>new Promise((n,i)=>{var s=u=>{try{a(r.next(u))}catch(c){i(c)}},o=u=>{try{a(r.throw(u))}catch(c){i(c)}},a=u=>u.done?n(u.value):Promise.resolve(u.value).then(s,o);a((r=r.apply(t,e)).next())});var re=K((ee,te)=>{/** + * lunr - http://lunrjs.com - A bit like Solr, but much smaller and not as bright - 2.3.9 + * Copyright (C) 2020 Oliver Nightingale + * @license MIT + */(function(){var t=function(e){var r=new t.Builder;return r.pipeline.add(t.trimmer,t.stopWordFilter,t.stemmer),r.searchPipeline.add(t.stemmer),e.call(r,r),r.build()};t.version="2.3.9";/*! + * lunr.utils + * Copyright (C) 2020 Oliver Nightingale + */t.utils={},t.utils.warn=function(e){return function(r){e.console&&console.warn&&console.warn(r)}}(this),t.utils.asString=function(e){return e==null?"":e.toString()},t.utils.clone=function(e){if(e==null)return e;for(var r=Object.create(null),n=Object.keys(e),i=0;i0){var h=t.utils.clone(r)||{};h.position=[a,c],h.index=s.length,s.push(new t.Token(n.slice(a,o),h))}a=o+1}}return s},t.tokenizer.separator=/[\s\-]+/;/*! + * lunr.Pipeline + * Copyright (C) 2020 Oliver Nightingale + */t.Pipeline=function(){this._stack=[]},t.Pipeline.registeredFunctions=Object.create(null),t.Pipeline.registerFunction=function(e,r){r in this.registeredFunctions&&t.utils.warn("Overwriting existing registered function: "+r),e.label=r,t.Pipeline.registeredFunctions[e.label]=e},t.Pipeline.warnIfFunctionNotRegistered=function(e){var r=e.label&&e.label in this.registeredFunctions;r||t.utils.warn(`Function is not registered with pipeline. This may cause problems when serialising the index. +`,e)},t.Pipeline.load=function(e){var r=new t.Pipeline;return e.forEach(function(n){var i=t.Pipeline.registeredFunctions[n];if(i)r.add(i);else throw new Error("Cannot load unregistered function: "+n)}),r},t.Pipeline.prototype.add=function(){var e=Array.prototype.slice.call(arguments);e.forEach(function(r){t.Pipeline.warnIfFunctionNotRegistered(r),this._stack.push(r)},this)},t.Pipeline.prototype.after=function(e,r){t.Pipeline.warnIfFunctionNotRegistered(r);var n=this._stack.indexOf(e);if(n==-1)throw new Error("Cannot find existingFn");n=n+1,this._stack.splice(n,0,r)},t.Pipeline.prototype.before=function(e,r){t.Pipeline.warnIfFunctionNotRegistered(r);var n=this._stack.indexOf(e);if(n==-1)throw new Error("Cannot find existingFn");this._stack.splice(n,0,r)},t.Pipeline.prototype.remove=function(e){var r=this._stack.indexOf(e);r!=-1&&this._stack.splice(r,1)},t.Pipeline.prototype.run=function(e){for(var r=this._stack.length,n=0;n1&&(oe&&(n=s),o!=e);)i=n-r,s=r+Math.floor(i/2),o=this.elements[s*2];if(o==e||o>e)return s*2;if(ou?h+=2:a==u&&(r+=n[c+1]*i[h+1],c+=2,h+=2);return r},t.Vector.prototype.similarity=function(e){return this.dot(e)/this.magnitude()||0},t.Vector.prototype.toArray=function(){for(var e=new Array(this.elements.length/2),r=1,n=0;r0){var o=s.str.charAt(0),a;o in s.node.edges?a=s.node.edges[o]:(a=new t.TokenSet,s.node.edges[o]=a),s.str.length==1&&(a.final=!0),i.push({node:a,editsRemaining:s.editsRemaining,str:s.str.slice(1)})}if(s.editsRemaining!=0){if("*"in s.node.edges)var u=s.node.edges["*"];else{var u=new t.TokenSet;s.node.edges["*"]=u}if(s.str.length==0&&(u.final=!0),i.push({node:u,editsRemaining:s.editsRemaining-1,str:s.str}),s.str.length>1&&i.push({node:s.node,editsRemaining:s.editsRemaining-1,str:s.str.slice(1)}),s.str.length==1&&(s.node.final=!0),s.str.length>=1){if("*"in s.node.edges)var c=s.node.edges["*"];else{var c=new t.TokenSet;s.node.edges["*"]=c}s.str.length==1&&(c.final=!0),i.push({node:c,editsRemaining:s.editsRemaining-1,str:s.str.slice(1)})}if(s.str.length>1){var h=s.str.charAt(0),y=s.str.charAt(1),g;y in s.node.edges?g=s.node.edges[y]:(g=new t.TokenSet,s.node.edges[y]=g),s.str.length==1&&(g.final=!0),i.push({node:g,editsRemaining:s.editsRemaining-1,str:h+s.str.slice(2)})}}}return n},t.TokenSet.fromString=function(e){for(var r=new t.TokenSet,n=r,i=0,s=e.length;i=e;r--){var n=this.uncheckedNodes[r],i=n.child.toString();i in this.minimizedNodes?n.parent.edges[n.char]=this.minimizedNodes[i]:(n.child._str=i,this.minimizedNodes[i]=n.child),this.uncheckedNodes.pop()}};/*! + * lunr.Index + * Copyright (C) 2020 Oliver Nightingale + */t.Index=function(e){this.invertedIndex=e.invertedIndex,this.fieldVectors=e.fieldVectors,this.tokenSet=e.tokenSet,this.fields=e.fields,this.pipeline=e.pipeline},t.Index.prototype.search=function(e){return this.query(function(r){var n=new t.QueryParser(e,r);n.parse()})},t.Index.prototype.query=function(e){for(var r=new t.Query(this.fields),n=Object.create(null),i=Object.create(null),s=Object.create(null),o=Object.create(null),a=Object.create(null),u=0;u1?this._b=1:this._b=e},t.Builder.prototype.k1=function(e){this._k1=e},t.Builder.prototype.add=function(e,r){var n=e[this._ref],i=Object.keys(this._fields);this._documents[n]=r||{},this.documentCount+=1;for(var s=0;s=this.length)return t.QueryLexer.EOS;var e=this.str.charAt(this.pos);return this.pos+=1,e},t.QueryLexer.prototype.width=function(){return this.pos-this.start},t.QueryLexer.prototype.ignore=function(){this.start==this.pos&&(this.pos+=1),this.start=this.pos},t.QueryLexer.prototype.backup=function(){this.pos-=1},t.QueryLexer.prototype.acceptDigitRun=function(){var e,r;do e=this.next(),r=e.charCodeAt(0);while(r>47&&r<58);e!=t.QueryLexer.EOS&&this.backup()},t.QueryLexer.prototype.more=function(){return this.pos1&&(e.backup(),e.emit(t.QueryLexer.TERM)),e.ignore(),e.more())return t.QueryLexer.lexText},t.QueryLexer.lexEditDistance=function(e){return e.ignore(),e.acceptDigitRun(),e.emit(t.QueryLexer.EDIT_DISTANCE),t.QueryLexer.lexText},t.QueryLexer.lexBoost=function(e){return e.ignore(),e.acceptDigitRun(),e.emit(t.QueryLexer.BOOST),t.QueryLexer.lexText},t.QueryLexer.lexEOS=function(e){e.width()>0&&e.emit(t.QueryLexer.TERM)},t.QueryLexer.termSeparator=t.tokenizer.separator,t.QueryLexer.lexText=function(e){for(;;){var r=e.next();if(r==t.QueryLexer.EOS)return t.QueryLexer.lexEOS;if(r.charCodeAt(0)==92){e.escapeCharacter();continue}if(r==":")return t.QueryLexer.lexField;if(r=="~")return e.backup(),e.width()>0&&e.emit(t.QueryLexer.TERM),t.QueryLexer.lexEditDistance;if(r=="^")return e.backup(),e.width()>0&&e.emit(t.QueryLexer.TERM),t.QueryLexer.lexBoost;if(r=="+"&&e.width()===1||r=="-"&&e.width()===1)return e.emit(t.QueryLexer.PRESENCE),t.QueryLexer.lexText;if(r.match(t.QueryLexer.termSeparator))return t.QueryLexer.lexTerm}},t.QueryParser=function(e,r){this.lexer=new t.QueryLexer(e),this.query=r,this.currentClause={},this.lexemeIdx=0},t.QueryParser.prototype.parse=function(){this.lexer.run(),this.lexemes=this.lexer.lexemes;for(var e=t.QueryParser.parseClause;e;)e=e(this);return this.query},t.QueryParser.prototype.peekLexeme=function(){return this.lexemes[this.lexemeIdx]},t.QueryParser.prototype.consumeLexeme=function(){var e=this.peekLexeme();return this.lexemeIdx+=1,e},t.QueryParser.prototype.nextClause=function(){var e=this.currentClause;this.query.clause(e),this.currentClause={}},t.QueryParser.parseClause=function(e){var r=e.peekLexeme();if(r!=null)switch(r.type){case t.QueryLexer.PRESENCE:return t.QueryParser.parsePresence;case t.QueryLexer.FIELD:return t.QueryParser.parseField;case t.QueryLexer.TERM:return t.QueryParser.parseTerm;default:var n="expected either a field or a term, found "+r.type;throw r.str.length>=1&&(n+=" with value '"+r.str+"'"),new t.QueryParseError(n,r.start,r.end)}},t.QueryParser.parsePresence=function(e){var r=e.consumeLexeme();if(r!=null){switch(r.str){case"-":e.currentClause.presence=t.Query.presence.PROHIBITED;break;case"+":e.currentClause.presence=t.Query.presence.REQUIRED;break;default:var n="unrecognised presence operator'"+r.str+"'";throw new t.QueryParseError(n,r.start,r.end)}var i=e.peekLexeme();if(i==null){var n="expecting term or field, found nothing";throw new t.QueryParseError(n,r.start,r.end)}switch(i.type){case t.QueryLexer.FIELD:return t.QueryParser.parseField;case t.QueryLexer.TERM:return t.QueryParser.parseTerm;default:var n="expecting term or field, found '"+i.type+"'";throw new t.QueryParseError(n,i.start,i.end)}}},t.QueryParser.parseField=function(e){var r=e.consumeLexeme();if(r!=null){if(e.query.allFields.indexOf(r.str)==-1){var n=e.query.allFields.map(function(o){return"'"+o+"'"}).join(", "),i="unrecognised field '"+r.str+"', possible fields: "+n;throw new t.QueryParseError(i,r.start,r.end)}e.currentClause.fields=[r.str];var s=e.peekLexeme();if(s==null){var i="expecting term, found nothing";throw new t.QueryParseError(i,r.start,r.end)}switch(s.type){case t.QueryLexer.TERM:return t.QueryParser.parseTerm;default:var i="expecting term, found '"+s.type+"'";throw new t.QueryParseError(i,s.start,s.end)}}},t.QueryParser.parseTerm=function(e){var r=e.consumeLexeme();if(r!=null){e.currentClause.term=r.str.toLowerCase(),r.str.indexOf("*")!=-1&&(e.currentClause.usePipeline=!1);var n=e.peekLexeme();if(n==null){e.nextClause();return}switch(n.type){case t.QueryLexer.TERM:return e.nextClause(),t.QueryParser.parseTerm;case t.QueryLexer.FIELD:return e.nextClause(),t.QueryParser.parseField;case t.QueryLexer.EDIT_DISTANCE:return t.QueryParser.parseEditDistance;case t.QueryLexer.BOOST:return t.QueryParser.parseBoost;case t.QueryLexer.PRESENCE:return e.nextClause(),t.QueryParser.parsePresence;default:var i="Unexpected lexeme type '"+n.type+"'";throw new t.QueryParseError(i,n.start,n.end)}}},t.QueryParser.parseEditDistance=function(e){var r=e.consumeLexeme();if(r!=null){var n=parseInt(r.str,10);if(isNaN(n)){var i="edit distance must be numeric";throw new t.QueryParseError(i,r.start,r.end)}e.currentClause.editDistance=n;var s=e.peekLexeme();if(s==null){e.nextClause();return}switch(s.type){case t.QueryLexer.TERM:return e.nextClause(),t.QueryParser.parseTerm;case t.QueryLexer.FIELD:return e.nextClause(),t.QueryParser.parseField;case t.QueryLexer.EDIT_DISTANCE:return t.QueryParser.parseEditDistance;case t.QueryLexer.BOOST:return t.QueryParser.parseBoost;case t.QueryLexer.PRESENCE:return e.nextClause(),t.QueryParser.parsePresence;default:var i="Unexpected lexeme type '"+s.type+"'";throw new t.QueryParseError(i,s.start,s.end)}}},t.QueryParser.parseBoost=function(e){var r=e.consumeLexeme();if(r!=null){var n=parseInt(r.str,10);if(isNaN(n)){var i="boost must be numeric";throw new t.QueryParseError(i,r.start,r.end)}e.currentClause.boost=n;var s=e.peekLexeme();if(s==null){e.nextClause();return}switch(s.type){case t.QueryLexer.TERM:return e.nextClause(),t.QueryParser.parseTerm;case t.QueryLexer.FIELD:return e.nextClause(),t.QueryParser.parseField;case t.QueryLexer.EDIT_DISTANCE:return t.QueryParser.parseEditDistance;case t.QueryLexer.BOOST:return t.QueryParser.parseBoost;case t.QueryLexer.PRESENCE:return e.nextClause(),t.QueryParser.parsePresence;default:var i="Unexpected lexeme type '"+s.type+"'";throw new t.QueryParseError(i,s.start,s.end)}}},function(e,r){typeof define=="function"&&define.amd?define(r):typeof ee=="object"?te.exports=r():e.lunr=r()}(this,function(){return t})})()});var q=K((Re,ne)=>{"use strict";/*! + * escape-html + * Copyright(c) 2012-2013 TJ Holowaychuk + * Copyright(c) 2015 Andreas Lubbe + * Copyright(c) 2015 Tiancheng "Timothy" Gu + * MIT Licensed + */var Le=/["'&<>]/;ne.exports=we;function we(t){var e=""+t,r=Le.exec(e);if(!r)return e;var n,i="",s=0,o=0;for(s=r.index;s=0;r--){let n=t[r];typeof n!="object"?n=document.createTextNode(n):n.parentNode&&n.parentNode.removeChild(n),r?e.insertBefore(this.previousSibling,n):e.replaceChild(n,this)}}}));var ie=H(q());function se(t){let e=new Map,r=new Set;for(let n of t){let[i,s]=n.location.split("#"),o=n.location,a=n.title,u=n.tags,c=(0,ie.default)(n.text).replace(/\s+(?=[,.:;!?])/g,"").replace(/\s+/g," ");if(s){let h=e.get(i);r.has(h)?e.set(o,{location:o,title:a,text:c,parent:h}):(h.title=n.title,h.text=c,r.add(h))}else e.set(o,M({location:o,title:a,text:c},u&&{tags:u}))}return e}var oe=H(q());function ae(t,e){let r=new RegExp(t.separator,"img"),n=(i,s,o)=>`${s}${o}`;return i=>{i=i.replace(/[\s*+\-:~^]+/g," ").trim();let s=new RegExp(`(^|${t.separator})(${i.replace(/[|\\{}()[\]^$+*?.-]/g,"\\$&").replace(r,"|")})`,"img");return o=>(e?(0,oe.default)(o):o).replace(s,n).replace(/<\/mark>(\s+)]*>/img,"$1")}}function ue(t){let e=new lunr.Query(["title","text"]);return new lunr.QueryParser(t,e).parse(),e.clauses}function ce(t,e){var i;let r=new Set(t),n={};for(let s=0;s!n.has(i)))]}var U=class{constructor({config:e,docs:r,options:n}){this.options=n,this.documents=se(r),this.highlight=ae(e,!1),lunr.tokenizer.separator=new RegExp(e.separator),this.index=lunr(function(){e.lang.length===1&&e.lang[0]!=="en"?this.use(lunr[e.lang[0]]):e.lang.length>1&&this.use(lunr.multiLanguage(...e.lang));let i=Ee(["trimmer","stopWordFilter","stemmer"],n.pipeline);for(let s of e.lang.map(o=>o==="en"?lunr:lunr[o]))for(let o of i)this.pipeline.remove(s[o]),this.searchPipeline.remove(s[o]);this.ref("location"),this.field("title",{boost:1e3}),this.field("text"),this.field("tags",{boost:1e6,extractor:s=>{let{tags:o=[]}=s;return o.reduce((a,u)=>[...a,...lunr.tokenizer(u)],[])}});for(let s of r)this.add(s,{boost:s.boost})})}search(e){if(e)try{let r=this.highlight(e),n=ue(e).filter(o=>o.presence!==lunr.Query.presence.PROHIBITED),i=this.index.search(`${e}*`).reduce((o,{ref:a,score:u,matchData:c})=>{let h=this.documents.get(a);if(typeof h!="undefined"){let{location:y,title:g,text:b,tags:m,parent:Q}=h,p=ce(n,Object.keys(c.metadata)),d=+!Q+ +Object.values(p).every(w=>w);o.push(Z(M({location:y,title:r(g),text:r(b)},m&&{tags:m.map(r)}),{score:u*(1+d),terms:p}))}return o},[]).sort((o,a)=>a.score-o.score).reduce((o,a)=>{let u=this.documents.get(a.location);if(typeof u!="undefined"){let c="parent"in u?u.parent.location:u.location;o.set(c,[...o.get(c)||[],a])}return o},new Map),s;if(this.options.suggestions){let o=this.index.query(a=>{for(let u of n)a.term(u.term,{fields:["title"],presence:lunr.Query.presence.REQUIRED,wildcard:lunr.Query.wildcard.TRAILING})});s=o.length?Object.keys(o[0].matchData.metadata):[]}return M({items:[...i.values()]},typeof s!="undefined"&&{suggestions:s})}catch(r){console.warn(`Invalid query: ${e} \u2013 see https://bit.ly/2s3ChXG`)}return{items:[]}}};var Y;function ke(t){return z(this,null,function*(){let e="../lunr";if(typeof parent!="undefined"&&"IFrameWorker"in parent){let n=document.querySelector("script[src]"),[i]=n.src.split("/worker");e=e.replace("..",i)}let r=[];for(let n of t.lang){switch(n){case"ja":r.push(`${e}/tinyseg.js`);break;case"hi":case"th":r.push(`${e}/wordcut.js`);break}n!=="en"&&r.push(`${e}/min/lunr.${n}.min.js`)}t.lang.length>1&&r.push(`${e}/min/lunr.multi.min.js`),r.length&&(yield importScripts(`${e}/min/lunr.stemmer.support.min.js`,...r))})}function Te(t){return z(this,null,function*(){switch(t.type){case 0:return yield ke(t.data.config),Y=new U(t.data),{type:1};case 2:return{type:3,data:Y?Y.search(t.data):{items:[]}};default:throw new TypeError("Invalid message type")}})}self.lunr=le.default;addEventListener("message",t=>z(void 0,null,function*(){postMessage(yield Te(t.data))}));})(); +//# sourceMappingURL=search.b97dbffb.min.js.map + diff --git a/develop/assets/javascripts/workers/search.b97dbffb.min.js.map b/develop/assets/javascripts/workers/search.b97dbffb.min.js.map new file mode 100644 index 0000000..5764b1b --- /dev/null +++ b/develop/assets/javascripts/workers/search.b97dbffb.min.js.map @@ -0,0 +1,8 @@ +{ + "version": 3, + "sources": ["node_modules/lunr/lunr.js", "node_modules/escape-html/index.js", "src/assets/javascripts/integrations/search/worker/main/index.ts", "src/assets/javascripts/polyfills/index.ts", "src/assets/javascripts/integrations/search/document/index.ts", "src/assets/javascripts/integrations/search/highlighter/index.ts", "src/assets/javascripts/integrations/search/query/_/index.ts", "src/assets/javascripts/integrations/search/_/index.ts"], + "sourceRoot": "../../../..", + "sourcesContent": ["/**\n * lunr - http://lunrjs.com - A bit like Solr, but much smaller and not as bright - 2.3.9\n * Copyright (C) 2020 Oliver Nightingale\n * @license MIT\n */\n\n;(function(){\n\n/**\n * A convenience function for configuring and constructing\n * a new lunr Index.\n *\n * A lunr.Builder instance is created and the pipeline setup\n * with a trimmer, stop word filter and stemmer.\n *\n * This builder object is yielded to the configuration function\n * that is passed as a parameter, allowing the list of fields\n * and other builder parameters to be customised.\n *\n * All documents _must_ be added within the passed config function.\n *\n * @example\n * var idx = lunr(function () {\n * this.field('title')\n * this.field('body')\n * this.ref('id')\n *\n * documents.forEach(function (doc) {\n * this.add(doc)\n * }, this)\n * })\n *\n * @see {@link lunr.Builder}\n * @see {@link lunr.Pipeline}\n * @see {@link lunr.trimmer}\n * @see {@link lunr.stopWordFilter}\n * @see {@link lunr.stemmer}\n * @namespace {function} lunr\n */\nvar lunr = function (config) {\n var builder = new lunr.Builder\n\n builder.pipeline.add(\n lunr.trimmer,\n lunr.stopWordFilter,\n lunr.stemmer\n )\n\n builder.searchPipeline.add(\n lunr.stemmer\n )\n\n config.call(builder, builder)\n return builder.build()\n}\n\nlunr.version = \"2.3.9\"\n/*!\n * lunr.utils\n * Copyright (C) 2020 Oliver Nightingale\n */\n\n/**\n * A namespace containing utils for the rest of the lunr library\n * @namespace lunr.utils\n */\nlunr.utils = {}\n\n/**\n * Print a warning message to the console.\n *\n * @param {String} message The message to be printed.\n * @memberOf lunr.utils\n * @function\n */\nlunr.utils.warn = (function (global) {\n /* eslint-disable no-console */\n return function (message) {\n if (global.console && console.warn) {\n console.warn(message)\n }\n }\n /* eslint-enable no-console */\n})(this)\n\n/**\n * Convert an object to a string.\n *\n * In the case of `null` and `undefined` the function returns\n * the empty string, in all other cases the result of calling\n * `toString` on the passed object is returned.\n *\n * @param {Any} obj The object to convert to a string.\n * @return {String} string representation of the passed object.\n * @memberOf lunr.utils\n */\nlunr.utils.asString = function (obj) {\n if (obj === void 0 || obj === null) {\n return \"\"\n } else {\n return obj.toString()\n }\n}\n\n/**\n * Clones an object.\n *\n * Will create a copy of an existing object such that any mutations\n * on the copy cannot affect the original.\n *\n * Only shallow objects are supported, passing a nested object to this\n * function will cause a TypeError.\n *\n * Objects with primitives, and arrays of primitives are supported.\n *\n * @param {Object} obj The object to clone.\n * @return {Object} a clone of the passed object.\n * @throws {TypeError} when a nested object is passed.\n * @memberOf Utils\n */\nlunr.utils.clone = function (obj) {\n if (obj === null || obj === undefined) {\n return obj\n }\n\n var clone = Object.create(null),\n keys = Object.keys(obj)\n\n for (var i = 0; i < keys.length; i++) {\n var key = keys[i],\n val = obj[key]\n\n if (Array.isArray(val)) {\n clone[key] = val.slice()\n continue\n }\n\n if (typeof val === 'string' ||\n typeof val === 'number' ||\n typeof val === 'boolean') {\n clone[key] = val\n continue\n }\n\n throw new TypeError(\"clone is not deep and does not support nested objects\")\n }\n\n return clone\n}\nlunr.FieldRef = function (docRef, fieldName, stringValue) {\n this.docRef = docRef\n this.fieldName = fieldName\n this._stringValue = stringValue\n}\n\nlunr.FieldRef.joiner = \"/\"\n\nlunr.FieldRef.fromString = function (s) {\n var n = s.indexOf(lunr.FieldRef.joiner)\n\n if (n === -1) {\n throw \"malformed field ref string\"\n }\n\n var fieldRef = s.slice(0, n),\n docRef = s.slice(n + 1)\n\n return new lunr.FieldRef (docRef, fieldRef, s)\n}\n\nlunr.FieldRef.prototype.toString = function () {\n if (this._stringValue == undefined) {\n this._stringValue = this.fieldName + lunr.FieldRef.joiner + this.docRef\n }\n\n return this._stringValue\n}\n/*!\n * lunr.Set\n * Copyright (C) 2020 Oliver Nightingale\n */\n\n/**\n * A lunr set.\n *\n * @constructor\n */\nlunr.Set = function (elements) {\n this.elements = Object.create(null)\n\n if (elements) {\n this.length = elements.length\n\n for (var i = 0; i < this.length; i++) {\n this.elements[elements[i]] = true\n }\n } else {\n this.length = 0\n }\n}\n\n/**\n * A complete set that contains all elements.\n *\n * @static\n * @readonly\n * @type {lunr.Set}\n */\nlunr.Set.complete = {\n intersect: function (other) {\n return other\n },\n\n union: function () {\n return this\n },\n\n contains: function () {\n return true\n }\n}\n\n/**\n * An empty set that contains no elements.\n *\n * @static\n * @readonly\n * @type {lunr.Set}\n */\nlunr.Set.empty = {\n intersect: function () {\n return this\n },\n\n union: function (other) {\n return other\n },\n\n contains: function () {\n return false\n }\n}\n\n/**\n * Returns true if this set contains the specified object.\n *\n * @param {object} object - Object whose presence in this set is to be tested.\n * @returns {boolean} - True if this set contains the specified object.\n */\nlunr.Set.prototype.contains = function (object) {\n return !!this.elements[object]\n}\n\n/**\n * Returns a new set containing only the elements that are present in both\n * this set and the specified set.\n *\n * @param {lunr.Set} other - set to intersect with this set.\n * @returns {lunr.Set} a new set that is the intersection of this and the specified set.\n */\n\nlunr.Set.prototype.intersect = function (other) {\n var a, b, elements, intersection = []\n\n if (other === lunr.Set.complete) {\n return this\n }\n\n if (other === lunr.Set.empty) {\n return other\n }\n\n if (this.length < other.length) {\n a = this\n b = other\n } else {\n a = other\n b = this\n }\n\n elements = Object.keys(a.elements)\n\n for (var i = 0; i < elements.length; i++) {\n var element = elements[i]\n if (element in b.elements) {\n intersection.push(element)\n }\n }\n\n return new lunr.Set (intersection)\n}\n\n/**\n * Returns a new set combining the elements of this and the specified set.\n *\n * @param {lunr.Set} other - set to union with this set.\n * @return {lunr.Set} a new set that is the union of this and the specified set.\n */\n\nlunr.Set.prototype.union = function (other) {\n if (other === lunr.Set.complete) {\n return lunr.Set.complete\n }\n\n if (other === lunr.Set.empty) {\n return this\n }\n\n return new lunr.Set(Object.keys(this.elements).concat(Object.keys(other.elements)))\n}\n/**\n * A function to calculate the inverse document frequency for\n * a posting. This is shared between the builder and the index\n *\n * @private\n * @param {object} posting - The posting for a given term\n * @param {number} documentCount - The total number of documents.\n */\nlunr.idf = function (posting, documentCount) {\n var documentsWithTerm = 0\n\n for (var fieldName in posting) {\n if (fieldName == '_index') continue // Ignore the term index, its not a field\n documentsWithTerm += Object.keys(posting[fieldName]).length\n }\n\n var x = (documentCount - documentsWithTerm + 0.5) / (documentsWithTerm + 0.5)\n\n return Math.log(1 + Math.abs(x))\n}\n\n/**\n * A token wraps a string representation of a token\n * as it is passed through the text processing pipeline.\n *\n * @constructor\n * @param {string} [str=''] - The string token being wrapped.\n * @param {object} [metadata={}] - Metadata associated with this token.\n */\nlunr.Token = function (str, metadata) {\n this.str = str || \"\"\n this.metadata = metadata || {}\n}\n\n/**\n * Returns the token string that is being wrapped by this object.\n *\n * @returns {string}\n */\nlunr.Token.prototype.toString = function () {\n return this.str\n}\n\n/**\n * A token update function is used when updating or optionally\n * when cloning a token.\n *\n * @callback lunr.Token~updateFunction\n * @param {string} str - The string representation of the token.\n * @param {Object} metadata - All metadata associated with this token.\n */\n\n/**\n * Applies the given function to the wrapped string token.\n *\n * @example\n * token.update(function (str, metadata) {\n * return str.toUpperCase()\n * })\n *\n * @param {lunr.Token~updateFunction} fn - A function to apply to the token string.\n * @returns {lunr.Token}\n */\nlunr.Token.prototype.update = function (fn) {\n this.str = fn(this.str, this.metadata)\n return this\n}\n\n/**\n * Creates a clone of this token. Optionally a function can be\n * applied to the cloned token.\n *\n * @param {lunr.Token~updateFunction} [fn] - An optional function to apply to the cloned token.\n * @returns {lunr.Token}\n */\nlunr.Token.prototype.clone = function (fn) {\n fn = fn || function (s) { return s }\n return new lunr.Token (fn(this.str, this.metadata), this.metadata)\n}\n/*!\n * lunr.tokenizer\n * Copyright (C) 2020 Oliver Nightingale\n */\n\n/**\n * A function for splitting a string into tokens ready to be inserted into\n * the search index. Uses `lunr.tokenizer.separator` to split strings, change\n * the value of this property to change how strings are split into tokens.\n *\n * This tokenizer will convert its parameter to a string by calling `toString` and\n * then will split this string on the character in `lunr.tokenizer.separator`.\n * Arrays will have their elements converted to strings and wrapped in a lunr.Token.\n *\n * Optional metadata can be passed to the tokenizer, this metadata will be cloned and\n * added as metadata to every token that is created from the object to be tokenized.\n *\n * @static\n * @param {?(string|object|object[])} obj - The object to convert into tokens\n * @param {?object} metadata - Optional metadata to associate with every token\n * @returns {lunr.Token[]}\n * @see {@link lunr.Pipeline}\n */\nlunr.tokenizer = function (obj, metadata) {\n if (obj == null || obj == undefined) {\n return []\n }\n\n if (Array.isArray(obj)) {\n return obj.map(function (t) {\n return new lunr.Token(\n lunr.utils.asString(t).toLowerCase(),\n lunr.utils.clone(metadata)\n )\n })\n }\n\n var str = obj.toString().toLowerCase(),\n len = str.length,\n tokens = []\n\n for (var sliceEnd = 0, sliceStart = 0; sliceEnd <= len; sliceEnd++) {\n var char = str.charAt(sliceEnd),\n sliceLength = sliceEnd - sliceStart\n\n if ((char.match(lunr.tokenizer.separator) || sliceEnd == len)) {\n\n if (sliceLength > 0) {\n var tokenMetadata = lunr.utils.clone(metadata) || {}\n tokenMetadata[\"position\"] = [sliceStart, sliceLength]\n tokenMetadata[\"index\"] = tokens.length\n\n tokens.push(\n new lunr.Token (\n str.slice(sliceStart, sliceEnd),\n tokenMetadata\n )\n )\n }\n\n sliceStart = sliceEnd + 1\n }\n\n }\n\n return tokens\n}\n\n/**\n * The separator used to split a string into tokens. Override this property to change the behaviour of\n * `lunr.tokenizer` behaviour when tokenizing strings. By default this splits on whitespace and hyphens.\n *\n * @static\n * @see lunr.tokenizer\n */\nlunr.tokenizer.separator = /[\\s\\-]+/\n/*!\n * lunr.Pipeline\n * Copyright (C) 2020 Oliver Nightingale\n */\n\n/**\n * lunr.Pipelines maintain an ordered list of functions to be applied to all\n * tokens in documents entering the search index and queries being ran against\n * the index.\n *\n * An instance of lunr.Index created with the lunr shortcut will contain a\n * pipeline with a stop word filter and an English language stemmer. Extra\n * functions can be added before or after either of these functions or these\n * default functions can be removed.\n *\n * When run the pipeline will call each function in turn, passing a token, the\n * index of that token in the original list of all tokens and finally a list of\n * all the original tokens.\n *\n * The output of functions in the pipeline will be passed to the next function\n * in the pipeline. To exclude a token from entering the index the function\n * should return undefined, the rest of the pipeline will not be called with\n * this token.\n *\n * For serialisation of pipelines to work, all functions used in an instance of\n * a pipeline should be registered with lunr.Pipeline. Registered functions can\n * then be loaded. If trying to load a serialised pipeline that uses functions\n * that are not registered an error will be thrown.\n *\n * If not planning on serialising the pipeline then registering pipeline functions\n * is not necessary.\n *\n * @constructor\n */\nlunr.Pipeline = function () {\n this._stack = []\n}\n\nlunr.Pipeline.registeredFunctions = Object.create(null)\n\n/**\n * A pipeline function maps lunr.Token to lunr.Token. A lunr.Token contains the token\n * string as well as all known metadata. A pipeline function can mutate the token string\n * or mutate (or add) metadata for a given token.\n *\n * A pipeline function can indicate that the passed token should be discarded by returning\n * null, undefined or an empty string. This token will not be passed to any downstream pipeline\n * functions and will not be added to the index.\n *\n * Multiple tokens can be returned by returning an array of tokens. Each token will be passed\n * to any downstream pipeline functions and all will returned tokens will be added to the index.\n *\n * Any number of pipeline functions may be chained together using a lunr.Pipeline.\n *\n * @interface lunr.PipelineFunction\n * @param {lunr.Token} token - A token from the document being processed.\n * @param {number} i - The index of this token in the complete list of tokens for this document/field.\n * @param {lunr.Token[]} tokens - All tokens for this document/field.\n * @returns {(?lunr.Token|lunr.Token[])}\n */\n\n/**\n * Register a function with the pipeline.\n *\n * Functions that are used in the pipeline should be registered if the pipeline\n * needs to be serialised, or a serialised pipeline needs to be loaded.\n *\n * Registering a function does not add it to a pipeline, functions must still be\n * added to instances of the pipeline for them to be used when running a pipeline.\n *\n * @param {lunr.PipelineFunction} fn - The function to check for.\n * @param {String} label - The label to register this function with\n */\nlunr.Pipeline.registerFunction = function (fn, label) {\n if (label in this.registeredFunctions) {\n lunr.utils.warn('Overwriting existing registered function: ' + label)\n }\n\n fn.label = label\n lunr.Pipeline.registeredFunctions[fn.label] = fn\n}\n\n/**\n * Warns if the function is not registered as a Pipeline function.\n *\n * @param {lunr.PipelineFunction} fn - The function to check for.\n * @private\n */\nlunr.Pipeline.warnIfFunctionNotRegistered = function (fn) {\n var isRegistered = fn.label && (fn.label in this.registeredFunctions)\n\n if (!isRegistered) {\n lunr.utils.warn('Function is not registered with pipeline. This may cause problems when serialising the index.\\n', fn)\n }\n}\n\n/**\n * Loads a previously serialised pipeline.\n *\n * All functions to be loaded must already be registered with lunr.Pipeline.\n * If any function from the serialised data has not been registered then an\n * error will be thrown.\n *\n * @param {Object} serialised - The serialised pipeline to load.\n * @returns {lunr.Pipeline}\n */\nlunr.Pipeline.load = function (serialised) {\n var pipeline = new lunr.Pipeline\n\n serialised.forEach(function (fnName) {\n var fn = lunr.Pipeline.registeredFunctions[fnName]\n\n if (fn) {\n pipeline.add(fn)\n } else {\n throw new Error('Cannot load unregistered function: ' + fnName)\n }\n })\n\n return pipeline\n}\n\n/**\n * Adds new functions to the end of the pipeline.\n *\n * Logs a warning if the function has not been registered.\n *\n * @param {lunr.PipelineFunction[]} functions - Any number of functions to add to the pipeline.\n */\nlunr.Pipeline.prototype.add = function () {\n var fns = Array.prototype.slice.call(arguments)\n\n fns.forEach(function (fn) {\n lunr.Pipeline.warnIfFunctionNotRegistered(fn)\n this._stack.push(fn)\n }, this)\n}\n\n/**\n * Adds a single function after a function that already exists in the\n * pipeline.\n *\n * Logs a warning if the function has not been registered.\n *\n * @param {lunr.PipelineFunction} existingFn - A function that already exists in the pipeline.\n * @param {lunr.PipelineFunction} newFn - The new function to add to the pipeline.\n */\nlunr.Pipeline.prototype.after = function (existingFn, newFn) {\n lunr.Pipeline.warnIfFunctionNotRegistered(newFn)\n\n var pos = this._stack.indexOf(existingFn)\n if (pos == -1) {\n throw new Error('Cannot find existingFn')\n }\n\n pos = pos + 1\n this._stack.splice(pos, 0, newFn)\n}\n\n/**\n * Adds a single function before a function that already exists in the\n * pipeline.\n *\n * Logs a warning if the function has not been registered.\n *\n * @param {lunr.PipelineFunction} existingFn - A function that already exists in the pipeline.\n * @param {lunr.PipelineFunction} newFn - The new function to add to the pipeline.\n */\nlunr.Pipeline.prototype.before = function (existingFn, newFn) {\n lunr.Pipeline.warnIfFunctionNotRegistered(newFn)\n\n var pos = this._stack.indexOf(existingFn)\n if (pos == -1) {\n throw new Error('Cannot find existingFn')\n }\n\n this._stack.splice(pos, 0, newFn)\n}\n\n/**\n * Removes a function from the pipeline.\n *\n * @param {lunr.PipelineFunction} fn The function to remove from the pipeline.\n */\nlunr.Pipeline.prototype.remove = function (fn) {\n var pos = this._stack.indexOf(fn)\n if (pos == -1) {\n return\n }\n\n this._stack.splice(pos, 1)\n}\n\n/**\n * Runs the current list of functions that make up the pipeline against the\n * passed tokens.\n *\n * @param {Array} tokens The tokens to run through the pipeline.\n * @returns {Array}\n */\nlunr.Pipeline.prototype.run = function (tokens) {\n var stackLength = this._stack.length\n\n for (var i = 0; i < stackLength; i++) {\n var fn = this._stack[i]\n var memo = []\n\n for (var j = 0; j < tokens.length; j++) {\n var result = fn(tokens[j], j, tokens)\n\n if (result === null || result === void 0 || result === '') continue\n\n if (Array.isArray(result)) {\n for (var k = 0; k < result.length; k++) {\n memo.push(result[k])\n }\n } else {\n memo.push(result)\n }\n }\n\n tokens = memo\n }\n\n return tokens\n}\n\n/**\n * Convenience method for passing a string through a pipeline and getting\n * strings out. This method takes care of wrapping the passed string in a\n * token and mapping the resulting tokens back to strings.\n *\n * @param {string} str - The string to pass through the pipeline.\n * @param {?object} metadata - Optional metadata to associate with the token\n * passed to the pipeline.\n * @returns {string[]}\n */\nlunr.Pipeline.prototype.runString = function (str, metadata) {\n var token = new lunr.Token (str, metadata)\n\n return this.run([token]).map(function (t) {\n return t.toString()\n })\n}\n\n/**\n * Resets the pipeline by removing any existing processors.\n *\n */\nlunr.Pipeline.prototype.reset = function () {\n this._stack = []\n}\n\n/**\n * Returns a representation of the pipeline ready for serialisation.\n *\n * Logs a warning if the function has not been registered.\n *\n * @returns {Array}\n */\nlunr.Pipeline.prototype.toJSON = function () {\n return this._stack.map(function (fn) {\n lunr.Pipeline.warnIfFunctionNotRegistered(fn)\n\n return fn.label\n })\n}\n/*!\n * lunr.Vector\n * Copyright (C) 2020 Oliver Nightingale\n */\n\n/**\n * A vector is used to construct the vector space of documents and queries. These\n * vectors support operations to determine the similarity between two documents or\n * a document and a query.\n *\n * Normally no parameters are required for initializing a vector, but in the case of\n * loading a previously dumped vector the raw elements can be provided to the constructor.\n *\n * For performance reasons vectors are implemented with a flat array, where an elements\n * index is immediately followed by its value. E.g. [index, value, index, value]. This\n * allows the underlying array to be as sparse as possible and still offer decent\n * performance when being used for vector calculations.\n *\n * @constructor\n * @param {Number[]} [elements] - The flat list of element index and element value pairs.\n */\nlunr.Vector = function (elements) {\n this._magnitude = 0\n this.elements = elements || []\n}\n\n\n/**\n * Calculates the position within the vector to insert a given index.\n *\n * This is used internally by insert and upsert. If there are duplicate indexes then\n * the position is returned as if the value for that index were to be updated, but it\n * is the callers responsibility to check whether there is a duplicate at that index\n *\n * @param {Number} insertIdx - The index at which the element should be inserted.\n * @returns {Number}\n */\nlunr.Vector.prototype.positionForIndex = function (index) {\n // For an empty vector the tuple can be inserted at the beginning\n if (this.elements.length == 0) {\n return 0\n }\n\n var start = 0,\n end = this.elements.length / 2,\n sliceLength = end - start,\n pivotPoint = Math.floor(sliceLength / 2),\n pivotIndex = this.elements[pivotPoint * 2]\n\n while (sliceLength > 1) {\n if (pivotIndex < index) {\n start = pivotPoint\n }\n\n if (pivotIndex > index) {\n end = pivotPoint\n }\n\n if (pivotIndex == index) {\n break\n }\n\n sliceLength = end - start\n pivotPoint = start + Math.floor(sliceLength / 2)\n pivotIndex = this.elements[pivotPoint * 2]\n }\n\n if (pivotIndex == index) {\n return pivotPoint * 2\n }\n\n if (pivotIndex > index) {\n return pivotPoint * 2\n }\n\n if (pivotIndex < index) {\n return (pivotPoint + 1) * 2\n }\n}\n\n/**\n * Inserts an element at an index within the vector.\n *\n * Does not allow duplicates, will throw an error if there is already an entry\n * for this index.\n *\n * @param {Number} insertIdx - The index at which the element should be inserted.\n * @param {Number} val - The value to be inserted into the vector.\n */\nlunr.Vector.prototype.insert = function (insertIdx, val) {\n this.upsert(insertIdx, val, function () {\n throw \"duplicate index\"\n })\n}\n\n/**\n * Inserts or updates an existing index within the vector.\n *\n * @param {Number} insertIdx - The index at which the element should be inserted.\n * @param {Number} val - The value to be inserted into the vector.\n * @param {function} fn - A function that is called for updates, the existing value and the\n * requested value are passed as arguments\n */\nlunr.Vector.prototype.upsert = function (insertIdx, val, fn) {\n this._magnitude = 0\n var position = this.positionForIndex(insertIdx)\n\n if (this.elements[position] == insertIdx) {\n this.elements[position + 1] = fn(this.elements[position + 1], val)\n } else {\n this.elements.splice(position, 0, insertIdx, val)\n }\n}\n\n/**\n * Calculates the magnitude of this vector.\n *\n * @returns {Number}\n */\nlunr.Vector.prototype.magnitude = function () {\n if (this._magnitude) return this._magnitude\n\n var sumOfSquares = 0,\n elementsLength = this.elements.length\n\n for (var i = 1; i < elementsLength; i += 2) {\n var val = this.elements[i]\n sumOfSquares += val * val\n }\n\n return this._magnitude = Math.sqrt(sumOfSquares)\n}\n\n/**\n * Calculates the dot product of this vector and another vector.\n *\n * @param {lunr.Vector} otherVector - The vector to compute the dot product with.\n * @returns {Number}\n */\nlunr.Vector.prototype.dot = function (otherVector) {\n var dotProduct = 0,\n a = this.elements, b = otherVector.elements,\n aLen = a.length, bLen = b.length,\n aVal = 0, bVal = 0,\n i = 0, j = 0\n\n while (i < aLen && j < bLen) {\n aVal = a[i], bVal = b[j]\n if (aVal < bVal) {\n i += 2\n } else if (aVal > bVal) {\n j += 2\n } else if (aVal == bVal) {\n dotProduct += a[i + 1] * b[j + 1]\n i += 2\n j += 2\n }\n }\n\n return dotProduct\n}\n\n/**\n * Calculates the similarity between this vector and another vector.\n *\n * @param {lunr.Vector} otherVector - The other vector to calculate the\n * similarity with.\n * @returns {Number}\n */\nlunr.Vector.prototype.similarity = function (otherVector) {\n return this.dot(otherVector) / this.magnitude() || 0\n}\n\n/**\n * Converts the vector to an array of the elements within the vector.\n *\n * @returns {Number[]}\n */\nlunr.Vector.prototype.toArray = function () {\n var output = new Array (this.elements.length / 2)\n\n for (var i = 1, j = 0; i < this.elements.length; i += 2, j++) {\n output[j] = this.elements[i]\n }\n\n return output\n}\n\n/**\n * A JSON serializable representation of the vector.\n *\n * @returns {Number[]}\n */\nlunr.Vector.prototype.toJSON = function () {\n return this.elements\n}\n/* eslint-disable */\n/*!\n * lunr.stemmer\n * Copyright (C) 2020 Oliver Nightingale\n * Includes code from - http://tartarus.org/~martin/PorterStemmer/js.txt\n */\n\n/**\n * lunr.stemmer is an english language stemmer, this is a JavaScript\n * implementation of the PorterStemmer taken from http://tartarus.org/~martin\n *\n * @static\n * @implements {lunr.PipelineFunction}\n * @param {lunr.Token} token - The string to stem\n * @returns {lunr.Token}\n * @see {@link lunr.Pipeline}\n * @function\n */\nlunr.stemmer = (function(){\n var step2list = {\n \"ational\" : \"ate\",\n \"tional\" : \"tion\",\n \"enci\" : \"ence\",\n \"anci\" : \"ance\",\n \"izer\" : \"ize\",\n \"bli\" : \"ble\",\n \"alli\" : \"al\",\n \"entli\" : \"ent\",\n \"eli\" : \"e\",\n \"ousli\" : \"ous\",\n \"ization\" : \"ize\",\n \"ation\" : \"ate\",\n \"ator\" : \"ate\",\n \"alism\" : \"al\",\n \"iveness\" : \"ive\",\n \"fulness\" : \"ful\",\n \"ousness\" : \"ous\",\n \"aliti\" : \"al\",\n \"iviti\" : \"ive\",\n \"biliti\" : \"ble\",\n \"logi\" : \"log\"\n },\n\n step3list = {\n \"icate\" : \"ic\",\n \"ative\" : \"\",\n \"alize\" : \"al\",\n \"iciti\" : \"ic\",\n \"ical\" : \"ic\",\n \"ful\" : \"\",\n \"ness\" : \"\"\n },\n\n c = \"[^aeiou]\", // consonant\n v = \"[aeiouy]\", // vowel\n C = c + \"[^aeiouy]*\", // consonant sequence\n V = v + \"[aeiou]*\", // vowel sequence\n\n mgr0 = \"^(\" + C + \")?\" + V + C, // [C]VC... is m>0\n meq1 = \"^(\" + C + \")?\" + V + C + \"(\" + V + \")?$\", // [C]VC[V] is m=1\n mgr1 = \"^(\" + C + \")?\" + V + C + V + C, // [C]VCVC... is m>1\n s_v = \"^(\" + C + \")?\" + v; // vowel in stem\n\n var re_mgr0 = new RegExp(mgr0);\n var re_mgr1 = new RegExp(mgr1);\n var re_meq1 = new RegExp(meq1);\n var re_s_v = new RegExp(s_v);\n\n var re_1a = /^(.+?)(ss|i)es$/;\n var re2_1a = /^(.+?)([^s])s$/;\n var re_1b = /^(.+?)eed$/;\n var re2_1b = /^(.+?)(ed|ing)$/;\n var re_1b_2 = /.$/;\n var re2_1b_2 = /(at|bl|iz)$/;\n var re3_1b_2 = new RegExp(\"([^aeiouylsz])\\\\1$\");\n var re4_1b_2 = new RegExp(\"^\" + C + v + \"[^aeiouwxy]$\");\n\n var re_1c = /^(.+?[^aeiou])y$/;\n var re_2 = /^(.+?)(ational|tional|enci|anci|izer|bli|alli|entli|eli|ousli|ization|ation|ator|alism|iveness|fulness|ousness|aliti|iviti|biliti|logi)$/;\n\n var re_3 = /^(.+?)(icate|ative|alize|iciti|ical|ful|ness)$/;\n\n var re_4 = /^(.+?)(al|ance|ence|er|ic|able|ible|ant|ement|ment|ent|ou|ism|ate|iti|ous|ive|ize)$/;\n var re2_4 = /^(.+?)(s|t)(ion)$/;\n\n var re_5 = /^(.+?)e$/;\n var re_5_1 = /ll$/;\n var re3_5 = new RegExp(\"^\" + C + v + \"[^aeiouwxy]$\");\n\n var porterStemmer = function porterStemmer(w) {\n var stem,\n suffix,\n firstch,\n re,\n re2,\n re3,\n re4;\n\n if (w.length < 3) { return w; }\n\n firstch = w.substr(0,1);\n if (firstch == \"y\") {\n w = firstch.toUpperCase() + w.substr(1);\n }\n\n // Step 1a\n re = re_1a\n re2 = re2_1a;\n\n if (re.test(w)) { w = w.replace(re,\"$1$2\"); }\n else if (re2.test(w)) { w = w.replace(re2,\"$1$2\"); }\n\n // Step 1b\n re = re_1b;\n re2 = re2_1b;\n if (re.test(w)) {\n var fp = re.exec(w);\n re = re_mgr0;\n if (re.test(fp[1])) {\n re = re_1b_2;\n w = w.replace(re,\"\");\n }\n } else if (re2.test(w)) {\n var fp = re2.exec(w);\n stem = fp[1];\n re2 = re_s_v;\n if (re2.test(stem)) {\n w = stem;\n re2 = re2_1b_2;\n re3 = re3_1b_2;\n re4 = re4_1b_2;\n if (re2.test(w)) { w = w + \"e\"; }\n else if (re3.test(w)) { re = re_1b_2; w = w.replace(re,\"\"); }\n else if (re4.test(w)) { w = w + \"e\"; }\n }\n }\n\n // Step 1c - replace suffix y or Y by i if preceded by a non-vowel which is not the first letter of the word (so cry -> cri, by -> by, say -> say)\n re = re_1c;\n if (re.test(w)) {\n var fp = re.exec(w);\n stem = fp[1];\n w = stem + \"i\";\n }\n\n // Step 2\n re = re_2;\n if (re.test(w)) {\n var fp = re.exec(w);\n stem = fp[1];\n suffix = fp[2];\n re = re_mgr0;\n if (re.test(stem)) {\n w = stem + step2list[suffix];\n }\n }\n\n // Step 3\n re = re_3;\n if (re.test(w)) {\n var fp = re.exec(w);\n stem = fp[1];\n suffix = fp[2];\n re = re_mgr0;\n if (re.test(stem)) {\n w = stem + step3list[suffix];\n }\n }\n\n // Step 4\n re = re_4;\n re2 = re2_4;\n if (re.test(w)) {\n var fp = re.exec(w);\n stem = fp[1];\n re = re_mgr1;\n if (re.test(stem)) {\n w = stem;\n }\n } else if (re2.test(w)) {\n var fp = re2.exec(w);\n stem = fp[1] + fp[2];\n re2 = re_mgr1;\n if (re2.test(stem)) {\n w = stem;\n }\n }\n\n // Step 5\n re = re_5;\n if (re.test(w)) {\n var fp = re.exec(w);\n stem = fp[1];\n re = re_mgr1;\n re2 = re_meq1;\n re3 = re3_5;\n if (re.test(stem) || (re2.test(stem) && !(re3.test(stem)))) {\n w = stem;\n }\n }\n\n re = re_5_1;\n re2 = re_mgr1;\n if (re.test(w) && re2.test(w)) {\n re = re_1b_2;\n w = w.replace(re,\"\");\n }\n\n // and turn initial Y back to y\n\n if (firstch == \"y\") {\n w = firstch.toLowerCase() + w.substr(1);\n }\n\n return w;\n };\n\n return function (token) {\n return token.update(porterStemmer);\n }\n})();\n\nlunr.Pipeline.registerFunction(lunr.stemmer, 'stemmer')\n/*!\n * lunr.stopWordFilter\n * Copyright (C) 2020 Oliver Nightingale\n */\n\n/**\n * lunr.generateStopWordFilter builds a stopWordFilter function from the provided\n * list of stop words.\n *\n * The built in lunr.stopWordFilter is built using this generator and can be used\n * to generate custom stopWordFilters for applications or non English languages.\n *\n * @function\n * @param {Array} token The token to pass through the filter\n * @returns {lunr.PipelineFunction}\n * @see lunr.Pipeline\n * @see lunr.stopWordFilter\n */\nlunr.generateStopWordFilter = function (stopWords) {\n var words = stopWords.reduce(function (memo, stopWord) {\n memo[stopWord] = stopWord\n return memo\n }, {})\n\n return function (token) {\n if (token && words[token.toString()] !== token.toString()) return token\n }\n}\n\n/**\n * lunr.stopWordFilter is an English language stop word list filter, any words\n * contained in the list will not be passed through the filter.\n *\n * This is intended to be used in the Pipeline. If the token does not pass the\n * filter then undefined will be returned.\n *\n * @function\n * @implements {lunr.PipelineFunction}\n * @params {lunr.Token} token - A token to check for being a stop word.\n * @returns {lunr.Token}\n * @see {@link lunr.Pipeline}\n */\nlunr.stopWordFilter = lunr.generateStopWordFilter([\n 'a',\n 'able',\n 'about',\n 'across',\n 'after',\n 'all',\n 'almost',\n 'also',\n 'am',\n 'among',\n 'an',\n 'and',\n 'any',\n 'are',\n 'as',\n 'at',\n 'be',\n 'because',\n 'been',\n 'but',\n 'by',\n 'can',\n 'cannot',\n 'could',\n 'dear',\n 'did',\n 'do',\n 'does',\n 'either',\n 'else',\n 'ever',\n 'every',\n 'for',\n 'from',\n 'get',\n 'got',\n 'had',\n 'has',\n 'have',\n 'he',\n 'her',\n 'hers',\n 'him',\n 'his',\n 'how',\n 'however',\n 'i',\n 'if',\n 'in',\n 'into',\n 'is',\n 'it',\n 'its',\n 'just',\n 'least',\n 'let',\n 'like',\n 'likely',\n 'may',\n 'me',\n 'might',\n 'most',\n 'must',\n 'my',\n 'neither',\n 'no',\n 'nor',\n 'not',\n 'of',\n 'off',\n 'often',\n 'on',\n 'only',\n 'or',\n 'other',\n 'our',\n 'own',\n 'rather',\n 'said',\n 'say',\n 'says',\n 'she',\n 'should',\n 'since',\n 'so',\n 'some',\n 'than',\n 'that',\n 'the',\n 'their',\n 'them',\n 'then',\n 'there',\n 'these',\n 'they',\n 'this',\n 'tis',\n 'to',\n 'too',\n 'twas',\n 'us',\n 'wants',\n 'was',\n 'we',\n 'were',\n 'what',\n 'when',\n 'where',\n 'which',\n 'while',\n 'who',\n 'whom',\n 'why',\n 'will',\n 'with',\n 'would',\n 'yet',\n 'you',\n 'your'\n])\n\nlunr.Pipeline.registerFunction(lunr.stopWordFilter, 'stopWordFilter')\n/*!\n * lunr.trimmer\n * Copyright (C) 2020 Oliver Nightingale\n */\n\n/**\n * lunr.trimmer is a pipeline function for trimming non word\n * characters from the beginning and end of tokens before they\n * enter the index.\n *\n * This implementation may not work correctly for non latin\n * characters and should either be removed or adapted for use\n * with languages with non-latin characters.\n *\n * @static\n * @implements {lunr.PipelineFunction}\n * @param {lunr.Token} token The token to pass through the filter\n * @returns {lunr.Token}\n * @see lunr.Pipeline\n */\nlunr.trimmer = function (token) {\n return token.update(function (s) {\n return s.replace(/^\\W+/, '').replace(/\\W+$/, '')\n })\n}\n\nlunr.Pipeline.registerFunction(lunr.trimmer, 'trimmer')\n/*!\n * lunr.TokenSet\n * Copyright (C) 2020 Oliver Nightingale\n */\n\n/**\n * A token set is used to store the unique list of all tokens\n * within an index. Token sets are also used to represent an\n * incoming query to the index, this query token set and index\n * token set are then intersected to find which tokens to look\n * up in the inverted index.\n *\n * A token set can hold multiple tokens, as in the case of the\n * index token set, or it can hold a single token as in the\n * case of a simple query token set.\n *\n * Additionally token sets are used to perform wildcard matching.\n * Leading, contained and trailing wildcards are supported, and\n * from this edit distance matching can also be provided.\n *\n * Token sets are implemented as a minimal finite state automata,\n * where both common prefixes and suffixes are shared between tokens.\n * This helps to reduce the space used for storing the token set.\n *\n * @constructor\n */\nlunr.TokenSet = function () {\n this.final = false\n this.edges = {}\n this.id = lunr.TokenSet._nextId\n lunr.TokenSet._nextId += 1\n}\n\n/**\n * Keeps track of the next, auto increment, identifier to assign\n * to a new tokenSet.\n *\n * TokenSets require a unique identifier to be correctly minimised.\n *\n * @private\n */\nlunr.TokenSet._nextId = 1\n\n/**\n * Creates a TokenSet instance from the given sorted array of words.\n *\n * @param {String[]} arr - A sorted array of strings to create the set from.\n * @returns {lunr.TokenSet}\n * @throws Will throw an error if the input array is not sorted.\n */\nlunr.TokenSet.fromArray = function (arr) {\n var builder = new lunr.TokenSet.Builder\n\n for (var i = 0, len = arr.length; i < len; i++) {\n builder.insert(arr[i])\n }\n\n builder.finish()\n return builder.root\n}\n\n/**\n * Creates a token set from a query clause.\n *\n * @private\n * @param {Object} clause - A single clause from lunr.Query.\n * @param {string} clause.term - The query clause term.\n * @param {number} [clause.editDistance] - The optional edit distance for the term.\n * @returns {lunr.TokenSet}\n */\nlunr.TokenSet.fromClause = function (clause) {\n if ('editDistance' in clause) {\n return lunr.TokenSet.fromFuzzyString(clause.term, clause.editDistance)\n } else {\n return lunr.TokenSet.fromString(clause.term)\n }\n}\n\n/**\n * Creates a token set representing a single string with a specified\n * edit distance.\n *\n * Insertions, deletions, substitutions and transpositions are each\n * treated as an edit distance of 1.\n *\n * Increasing the allowed edit distance will have a dramatic impact\n * on the performance of both creating and intersecting these TokenSets.\n * It is advised to keep the edit distance less than 3.\n *\n * @param {string} str - The string to create the token set from.\n * @param {number} editDistance - The allowed edit distance to match.\n * @returns {lunr.Vector}\n */\nlunr.TokenSet.fromFuzzyString = function (str, editDistance) {\n var root = new lunr.TokenSet\n\n var stack = [{\n node: root,\n editsRemaining: editDistance,\n str: str\n }]\n\n while (stack.length) {\n var frame = stack.pop()\n\n // no edit\n if (frame.str.length > 0) {\n var char = frame.str.charAt(0),\n noEditNode\n\n if (char in frame.node.edges) {\n noEditNode = frame.node.edges[char]\n } else {\n noEditNode = new lunr.TokenSet\n frame.node.edges[char] = noEditNode\n }\n\n if (frame.str.length == 1) {\n noEditNode.final = true\n }\n\n stack.push({\n node: noEditNode,\n editsRemaining: frame.editsRemaining,\n str: frame.str.slice(1)\n })\n }\n\n if (frame.editsRemaining == 0) {\n continue\n }\n\n // insertion\n if (\"*\" in frame.node.edges) {\n var insertionNode = frame.node.edges[\"*\"]\n } else {\n var insertionNode = new lunr.TokenSet\n frame.node.edges[\"*\"] = insertionNode\n }\n\n if (frame.str.length == 0) {\n insertionNode.final = true\n }\n\n stack.push({\n node: insertionNode,\n editsRemaining: frame.editsRemaining - 1,\n str: frame.str\n })\n\n // deletion\n // can only do a deletion if we have enough edits remaining\n // and if there are characters left to delete in the string\n if (frame.str.length > 1) {\n stack.push({\n node: frame.node,\n editsRemaining: frame.editsRemaining - 1,\n str: frame.str.slice(1)\n })\n }\n\n // deletion\n // just removing the last character from the str\n if (frame.str.length == 1) {\n frame.node.final = true\n }\n\n // substitution\n // can only do a substitution if we have enough edits remaining\n // and if there are characters left to substitute\n if (frame.str.length >= 1) {\n if (\"*\" in frame.node.edges) {\n var substitutionNode = frame.node.edges[\"*\"]\n } else {\n var substitutionNode = new lunr.TokenSet\n frame.node.edges[\"*\"] = substitutionNode\n }\n\n if (frame.str.length == 1) {\n substitutionNode.final = true\n }\n\n stack.push({\n node: substitutionNode,\n editsRemaining: frame.editsRemaining - 1,\n str: frame.str.slice(1)\n })\n }\n\n // transposition\n // can only do a transposition if there are edits remaining\n // and there are enough characters to transpose\n if (frame.str.length > 1) {\n var charA = frame.str.charAt(0),\n charB = frame.str.charAt(1),\n transposeNode\n\n if (charB in frame.node.edges) {\n transposeNode = frame.node.edges[charB]\n } else {\n transposeNode = new lunr.TokenSet\n frame.node.edges[charB] = transposeNode\n }\n\n if (frame.str.length == 1) {\n transposeNode.final = true\n }\n\n stack.push({\n node: transposeNode,\n editsRemaining: frame.editsRemaining - 1,\n str: charA + frame.str.slice(2)\n })\n }\n }\n\n return root\n}\n\n/**\n * Creates a TokenSet from a string.\n *\n * The string may contain one or more wildcard characters (*)\n * that will allow wildcard matching when intersecting with\n * another TokenSet.\n *\n * @param {string} str - The string to create a TokenSet from.\n * @returns {lunr.TokenSet}\n */\nlunr.TokenSet.fromString = function (str) {\n var node = new lunr.TokenSet,\n root = node\n\n /*\n * Iterates through all characters within the passed string\n * appending a node for each character.\n *\n * When a wildcard character is found then a self\n * referencing edge is introduced to continually match\n * any number of any characters.\n */\n for (var i = 0, len = str.length; i < len; i++) {\n var char = str[i],\n final = (i == len - 1)\n\n if (char == \"*\") {\n node.edges[char] = node\n node.final = final\n\n } else {\n var next = new lunr.TokenSet\n next.final = final\n\n node.edges[char] = next\n node = next\n }\n }\n\n return root\n}\n\n/**\n * Converts this TokenSet into an array of strings\n * contained within the TokenSet.\n *\n * This is not intended to be used on a TokenSet that\n * contains wildcards, in these cases the results are\n * undefined and are likely to cause an infinite loop.\n *\n * @returns {string[]}\n */\nlunr.TokenSet.prototype.toArray = function () {\n var words = []\n\n var stack = [{\n prefix: \"\",\n node: this\n }]\n\n while (stack.length) {\n var frame = stack.pop(),\n edges = Object.keys(frame.node.edges),\n len = edges.length\n\n if (frame.node.final) {\n /* In Safari, at this point the prefix is sometimes corrupted, see:\n * https://github.com/olivernn/lunr.js/issues/279 Calling any\n * String.prototype method forces Safari to \"cast\" this string to what\n * it's supposed to be, fixing the bug. */\n frame.prefix.charAt(0)\n words.push(frame.prefix)\n }\n\n for (var i = 0; i < len; i++) {\n var edge = edges[i]\n\n stack.push({\n prefix: frame.prefix.concat(edge),\n node: frame.node.edges[edge]\n })\n }\n }\n\n return words\n}\n\n/**\n * Generates a string representation of a TokenSet.\n *\n * This is intended to allow TokenSets to be used as keys\n * in objects, largely to aid the construction and minimisation\n * of a TokenSet. As such it is not designed to be a human\n * friendly representation of the TokenSet.\n *\n * @returns {string}\n */\nlunr.TokenSet.prototype.toString = function () {\n // NOTE: Using Object.keys here as this.edges is very likely\n // to enter 'hash-mode' with many keys being added\n //\n // avoiding a for-in loop here as it leads to the function\n // being de-optimised (at least in V8). From some simple\n // benchmarks the performance is comparable, but allowing\n // V8 to optimize may mean easy performance wins in the future.\n\n if (this._str) {\n return this._str\n }\n\n var str = this.final ? '1' : '0',\n labels = Object.keys(this.edges).sort(),\n len = labels.length\n\n for (var i = 0; i < len; i++) {\n var label = labels[i],\n node = this.edges[label]\n\n str = str + label + node.id\n }\n\n return str\n}\n\n/**\n * Returns a new TokenSet that is the intersection of\n * this TokenSet and the passed TokenSet.\n *\n * This intersection will take into account any wildcards\n * contained within the TokenSet.\n *\n * @param {lunr.TokenSet} b - An other TokenSet to intersect with.\n * @returns {lunr.TokenSet}\n */\nlunr.TokenSet.prototype.intersect = function (b) {\n var output = new lunr.TokenSet,\n frame = undefined\n\n var stack = [{\n qNode: b,\n output: output,\n node: this\n }]\n\n while (stack.length) {\n frame = stack.pop()\n\n // NOTE: As with the #toString method, we are using\n // Object.keys and a for loop instead of a for-in loop\n // as both of these objects enter 'hash' mode, causing\n // the function to be de-optimised in V8\n var qEdges = Object.keys(frame.qNode.edges),\n qLen = qEdges.length,\n nEdges = Object.keys(frame.node.edges),\n nLen = nEdges.length\n\n for (var q = 0; q < qLen; q++) {\n var qEdge = qEdges[q]\n\n for (var n = 0; n < nLen; n++) {\n var nEdge = nEdges[n]\n\n if (nEdge == qEdge || qEdge == '*') {\n var node = frame.node.edges[nEdge],\n qNode = frame.qNode.edges[qEdge],\n final = node.final && qNode.final,\n next = undefined\n\n if (nEdge in frame.output.edges) {\n // an edge already exists for this character\n // no need to create a new node, just set the finality\n // bit unless this node is already final\n next = frame.output.edges[nEdge]\n next.final = next.final || final\n\n } else {\n // no edge exists yet, must create one\n // set the finality bit and insert it\n // into the output\n next = new lunr.TokenSet\n next.final = final\n frame.output.edges[nEdge] = next\n }\n\n stack.push({\n qNode: qNode,\n output: next,\n node: node\n })\n }\n }\n }\n }\n\n return output\n}\nlunr.TokenSet.Builder = function () {\n this.previousWord = \"\"\n this.root = new lunr.TokenSet\n this.uncheckedNodes = []\n this.minimizedNodes = {}\n}\n\nlunr.TokenSet.Builder.prototype.insert = function (word) {\n var node,\n commonPrefix = 0\n\n if (word < this.previousWord) {\n throw new Error (\"Out of order word insertion\")\n }\n\n for (var i = 0; i < word.length && i < this.previousWord.length; i++) {\n if (word[i] != this.previousWord[i]) break\n commonPrefix++\n }\n\n this.minimize(commonPrefix)\n\n if (this.uncheckedNodes.length == 0) {\n node = this.root\n } else {\n node = this.uncheckedNodes[this.uncheckedNodes.length - 1].child\n }\n\n for (var i = commonPrefix; i < word.length; i++) {\n var nextNode = new lunr.TokenSet,\n char = word[i]\n\n node.edges[char] = nextNode\n\n this.uncheckedNodes.push({\n parent: node,\n char: char,\n child: nextNode\n })\n\n node = nextNode\n }\n\n node.final = true\n this.previousWord = word\n}\n\nlunr.TokenSet.Builder.prototype.finish = function () {\n this.minimize(0)\n}\n\nlunr.TokenSet.Builder.prototype.minimize = function (downTo) {\n for (var i = this.uncheckedNodes.length - 1; i >= downTo; i--) {\n var node = this.uncheckedNodes[i],\n childKey = node.child.toString()\n\n if (childKey in this.minimizedNodes) {\n node.parent.edges[node.char] = this.minimizedNodes[childKey]\n } else {\n // Cache the key for this node since\n // we know it can't change anymore\n node.child._str = childKey\n\n this.minimizedNodes[childKey] = node.child\n }\n\n this.uncheckedNodes.pop()\n }\n}\n/*!\n * lunr.Index\n * Copyright (C) 2020 Oliver Nightingale\n */\n\n/**\n * An index contains the built index of all documents and provides a query interface\n * to the index.\n *\n * Usually instances of lunr.Index will not be created using this constructor, instead\n * lunr.Builder should be used to construct new indexes, or lunr.Index.load should be\n * used to load previously built and serialized indexes.\n *\n * @constructor\n * @param {Object} attrs - The attributes of the built search index.\n * @param {Object} attrs.invertedIndex - An index of term/field to document reference.\n * @param {Object} attrs.fieldVectors - Field vectors\n * @param {lunr.TokenSet} attrs.tokenSet - An set of all corpus tokens.\n * @param {string[]} attrs.fields - The names of indexed document fields.\n * @param {lunr.Pipeline} attrs.pipeline - The pipeline to use for search terms.\n */\nlunr.Index = function (attrs) {\n this.invertedIndex = attrs.invertedIndex\n this.fieldVectors = attrs.fieldVectors\n this.tokenSet = attrs.tokenSet\n this.fields = attrs.fields\n this.pipeline = attrs.pipeline\n}\n\n/**\n * A result contains details of a document matching a search query.\n * @typedef {Object} lunr.Index~Result\n * @property {string} ref - The reference of the document this result represents.\n * @property {number} score - A number between 0 and 1 representing how similar this document is to the query.\n * @property {lunr.MatchData} matchData - Contains metadata about this match including which term(s) caused the match.\n */\n\n/**\n * Although lunr provides the ability to create queries using lunr.Query, it also provides a simple\n * query language which itself is parsed into an instance of lunr.Query.\n *\n * For programmatically building queries it is advised to directly use lunr.Query, the query language\n * is best used for human entered text rather than program generated text.\n *\n * At its simplest queries can just be a single term, e.g. `hello`, multiple terms are also supported\n * and will be combined with OR, e.g `hello world` will match documents that contain either 'hello'\n * or 'world', though those that contain both will rank higher in the results.\n *\n * Wildcards can be included in terms to match one or more unspecified characters, these wildcards can\n * be inserted anywhere within the term, and more than one wildcard can exist in a single term. Adding\n * wildcards will increase the number of documents that will be found but can also have a negative\n * impact on query performance, especially with wildcards at the beginning of a term.\n *\n * Terms can be restricted to specific fields, e.g. `title:hello`, only documents with the term\n * hello in the title field will match this query. Using a field not present in the index will lead\n * to an error being thrown.\n *\n * Modifiers can also be added to terms, lunr supports edit distance and boost modifiers on terms. A term\n * boost will make documents matching that term score higher, e.g. `foo^5`. Edit distance is also supported\n * to provide fuzzy matching, e.g. 'hello~2' will match documents with hello with an edit distance of 2.\n * Avoid large values for edit distance to improve query performance.\n *\n * Each term also supports a presence modifier. By default a term's presence in document is optional, however\n * this can be changed to either required or prohibited. For a term's presence to be required in a document the\n * term should be prefixed with a '+', e.g. `+foo bar` is a search for documents that must contain 'foo' and\n * optionally contain 'bar'. Conversely a leading '-' sets the terms presence to prohibited, i.e. it must not\n * appear in a document, e.g. `-foo bar` is a search for documents that do not contain 'foo' but may contain 'bar'.\n *\n * To escape special characters the backslash character '\\' can be used, this allows searches to include\n * characters that would normally be considered modifiers, e.g. `foo\\~2` will search for a term \"foo~2\" instead\n * of attempting to apply a boost of 2 to the search term \"foo\".\n *\n * @typedef {string} lunr.Index~QueryString\n * @example Simple single term query\n * hello\n * @example Multiple term query\n * hello world\n * @example term scoped to a field\n * title:hello\n * @example term with a boost of 10\n * hello^10\n * @example term with an edit distance of 2\n * hello~2\n * @example terms with presence modifiers\n * -foo +bar baz\n */\n\n/**\n * Performs a search against the index using lunr query syntax.\n *\n * Results will be returned sorted by their score, the most relevant results\n * will be returned first. For details on how the score is calculated, please see\n * the {@link https://lunrjs.com/guides/searching.html#scoring|guide}.\n *\n * For more programmatic querying use lunr.Index#query.\n *\n * @param {lunr.Index~QueryString} queryString - A string containing a lunr query.\n * @throws {lunr.QueryParseError} If the passed query string cannot be parsed.\n * @returns {lunr.Index~Result[]}\n */\nlunr.Index.prototype.search = function (queryString) {\n return this.query(function (query) {\n var parser = new lunr.QueryParser(queryString, query)\n parser.parse()\n })\n}\n\n/**\n * A query builder callback provides a query object to be used to express\n * the query to perform on the index.\n *\n * @callback lunr.Index~queryBuilder\n * @param {lunr.Query} query - The query object to build up.\n * @this lunr.Query\n */\n\n/**\n * Performs a query against the index using the yielded lunr.Query object.\n *\n * If performing programmatic queries against the index, this method is preferred\n * over lunr.Index#search so as to avoid the additional query parsing overhead.\n *\n * A query object is yielded to the supplied function which should be used to\n * express the query to be run against the index.\n *\n * Note that although this function takes a callback parameter it is _not_ an\n * asynchronous operation, the callback is just yielded a query object to be\n * customized.\n *\n * @param {lunr.Index~queryBuilder} fn - A function that is used to build the query.\n * @returns {lunr.Index~Result[]}\n */\nlunr.Index.prototype.query = function (fn) {\n // for each query clause\n // * process terms\n // * expand terms from token set\n // * find matching documents and metadata\n // * get document vectors\n // * score documents\n\n var query = new lunr.Query(this.fields),\n matchingFields = Object.create(null),\n queryVectors = Object.create(null),\n termFieldCache = Object.create(null),\n requiredMatches = Object.create(null),\n prohibitedMatches = Object.create(null)\n\n /*\n * To support field level boosts a query vector is created per\n * field. An empty vector is eagerly created to support negated\n * queries.\n */\n for (var i = 0; i < this.fields.length; i++) {\n queryVectors[this.fields[i]] = new lunr.Vector\n }\n\n fn.call(query, query)\n\n for (var i = 0; i < query.clauses.length; i++) {\n /*\n * Unless the pipeline has been disabled for this term, which is\n * the case for terms with wildcards, we need to pass the clause\n * term through the search pipeline. A pipeline returns an array\n * of processed terms. Pipeline functions may expand the passed\n * term, which means we may end up performing multiple index lookups\n * for a single query term.\n */\n var clause = query.clauses[i],\n terms = null,\n clauseMatches = lunr.Set.empty\n\n if (clause.usePipeline) {\n terms = this.pipeline.runString(clause.term, {\n fields: clause.fields\n })\n } else {\n terms = [clause.term]\n }\n\n for (var m = 0; m < terms.length; m++) {\n var term = terms[m]\n\n /*\n * Each term returned from the pipeline needs to use the same query\n * clause object, e.g. the same boost and or edit distance. The\n * simplest way to do this is to re-use the clause object but mutate\n * its term property.\n */\n clause.term = term\n\n /*\n * From the term in the clause we create a token set which will then\n * be used to intersect the indexes token set to get a list of terms\n * to lookup in the inverted index\n */\n var termTokenSet = lunr.TokenSet.fromClause(clause),\n expandedTerms = this.tokenSet.intersect(termTokenSet).toArray()\n\n /*\n * If a term marked as required does not exist in the tokenSet it is\n * impossible for the search to return any matches. We set all the field\n * scoped required matches set to empty and stop examining any further\n * clauses.\n */\n if (expandedTerms.length === 0 && clause.presence === lunr.Query.presence.REQUIRED) {\n for (var k = 0; k < clause.fields.length; k++) {\n var field = clause.fields[k]\n requiredMatches[field] = lunr.Set.empty\n }\n\n break\n }\n\n for (var j = 0; j < expandedTerms.length; j++) {\n /*\n * For each term get the posting and termIndex, this is required for\n * building the query vector.\n */\n var expandedTerm = expandedTerms[j],\n posting = this.invertedIndex[expandedTerm],\n termIndex = posting._index\n\n for (var k = 0; k < clause.fields.length; k++) {\n /*\n * For each field that this query term is scoped by (by default\n * all fields are in scope) we need to get all the document refs\n * that have this term in that field.\n *\n * The posting is the entry in the invertedIndex for the matching\n * term from above.\n */\n var field = clause.fields[k],\n fieldPosting = posting[field],\n matchingDocumentRefs = Object.keys(fieldPosting),\n termField = expandedTerm + \"/\" + field,\n matchingDocumentsSet = new lunr.Set(matchingDocumentRefs)\n\n /*\n * if the presence of this term is required ensure that the matching\n * documents are added to the set of required matches for this clause.\n *\n */\n if (clause.presence == lunr.Query.presence.REQUIRED) {\n clauseMatches = clauseMatches.union(matchingDocumentsSet)\n\n if (requiredMatches[field] === undefined) {\n requiredMatches[field] = lunr.Set.complete\n }\n }\n\n /*\n * if the presence of this term is prohibited ensure that the matching\n * documents are added to the set of prohibited matches for this field,\n * creating that set if it does not yet exist.\n */\n if (clause.presence == lunr.Query.presence.PROHIBITED) {\n if (prohibitedMatches[field] === undefined) {\n prohibitedMatches[field] = lunr.Set.empty\n }\n\n prohibitedMatches[field] = prohibitedMatches[field].union(matchingDocumentsSet)\n\n /*\n * Prohibited matches should not be part of the query vector used for\n * similarity scoring and no metadata should be extracted so we continue\n * to the next field\n */\n continue\n }\n\n /*\n * The query field vector is populated using the termIndex found for\n * the term and a unit value with the appropriate boost applied.\n * Using upsert because there could already be an entry in the vector\n * for the term we are working with. In that case we just add the scores\n * together.\n */\n queryVectors[field].upsert(termIndex, clause.boost, function (a, b) { return a + b })\n\n /**\n * If we've already seen this term, field combo then we've already collected\n * the matching documents and metadata, no need to go through all that again\n */\n if (termFieldCache[termField]) {\n continue\n }\n\n for (var l = 0; l < matchingDocumentRefs.length; l++) {\n /*\n * All metadata for this term/field/document triple\n * are then extracted and collected into an instance\n * of lunr.MatchData ready to be returned in the query\n * results\n */\n var matchingDocumentRef = matchingDocumentRefs[l],\n matchingFieldRef = new lunr.FieldRef (matchingDocumentRef, field),\n metadata = fieldPosting[matchingDocumentRef],\n fieldMatch\n\n if ((fieldMatch = matchingFields[matchingFieldRef]) === undefined) {\n matchingFields[matchingFieldRef] = new lunr.MatchData (expandedTerm, field, metadata)\n } else {\n fieldMatch.add(expandedTerm, field, metadata)\n }\n\n }\n\n termFieldCache[termField] = true\n }\n }\n }\n\n /**\n * If the presence was required we need to update the requiredMatches field sets.\n * We do this after all fields for the term have collected their matches because\n * the clause terms presence is required in _any_ of the fields not _all_ of the\n * fields.\n */\n if (clause.presence === lunr.Query.presence.REQUIRED) {\n for (var k = 0; k < clause.fields.length; k++) {\n var field = clause.fields[k]\n requiredMatches[field] = requiredMatches[field].intersect(clauseMatches)\n }\n }\n }\n\n /**\n * Need to combine the field scoped required and prohibited\n * matching documents into a global set of required and prohibited\n * matches\n */\n var allRequiredMatches = lunr.Set.complete,\n allProhibitedMatches = lunr.Set.empty\n\n for (var i = 0; i < this.fields.length; i++) {\n var field = this.fields[i]\n\n if (requiredMatches[field]) {\n allRequiredMatches = allRequiredMatches.intersect(requiredMatches[field])\n }\n\n if (prohibitedMatches[field]) {\n allProhibitedMatches = allProhibitedMatches.union(prohibitedMatches[field])\n }\n }\n\n var matchingFieldRefs = Object.keys(matchingFields),\n results = [],\n matches = Object.create(null)\n\n /*\n * If the query is negated (contains only prohibited terms)\n * we need to get _all_ fieldRefs currently existing in the\n * index. This is only done when we know that the query is\n * entirely prohibited terms to avoid any cost of getting all\n * fieldRefs unnecessarily.\n *\n * Additionally, blank MatchData must be created to correctly\n * populate the results.\n */\n if (query.isNegated()) {\n matchingFieldRefs = Object.keys(this.fieldVectors)\n\n for (var i = 0; i < matchingFieldRefs.length; i++) {\n var matchingFieldRef = matchingFieldRefs[i]\n var fieldRef = lunr.FieldRef.fromString(matchingFieldRef)\n matchingFields[matchingFieldRef] = new lunr.MatchData\n }\n }\n\n for (var i = 0; i < matchingFieldRefs.length; i++) {\n /*\n * Currently we have document fields that match the query, but we\n * need to return documents. The matchData and scores are combined\n * from multiple fields belonging to the same document.\n *\n * Scores are calculated by field, using the query vectors created\n * above, and combined into a final document score using addition.\n */\n var fieldRef = lunr.FieldRef.fromString(matchingFieldRefs[i]),\n docRef = fieldRef.docRef\n\n if (!allRequiredMatches.contains(docRef)) {\n continue\n }\n\n if (allProhibitedMatches.contains(docRef)) {\n continue\n }\n\n var fieldVector = this.fieldVectors[fieldRef],\n score = queryVectors[fieldRef.fieldName].similarity(fieldVector),\n docMatch\n\n if ((docMatch = matches[docRef]) !== undefined) {\n docMatch.score += score\n docMatch.matchData.combine(matchingFields[fieldRef])\n } else {\n var match = {\n ref: docRef,\n score: score,\n matchData: matchingFields[fieldRef]\n }\n matches[docRef] = match\n results.push(match)\n }\n }\n\n /*\n * Sort the results objects by score, highest first.\n */\n return results.sort(function (a, b) {\n return b.score - a.score\n })\n}\n\n/**\n * Prepares the index for JSON serialization.\n *\n * The schema for this JSON blob will be described in a\n * separate JSON schema file.\n *\n * @returns {Object}\n */\nlunr.Index.prototype.toJSON = function () {\n var invertedIndex = Object.keys(this.invertedIndex)\n .sort()\n .map(function (term) {\n return [term, this.invertedIndex[term]]\n }, this)\n\n var fieldVectors = Object.keys(this.fieldVectors)\n .map(function (ref) {\n return [ref, this.fieldVectors[ref].toJSON()]\n }, this)\n\n return {\n version: lunr.version,\n fields: this.fields,\n fieldVectors: fieldVectors,\n invertedIndex: invertedIndex,\n pipeline: this.pipeline.toJSON()\n }\n}\n\n/**\n * Loads a previously serialized lunr.Index\n *\n * @param {Object} serializedIndex - A previously serialized lunr.Index\n * @returns {lunr.Index}\n */\nlunr.Index.load = function (serializedIndex) {\n var attrs = {},\n fieldVectors = {},\n serializedVectors = serializedIndex.fieldVectors,\n invertedIndex = Object.create(null),\n serializedInvertedIndex = serializedIndex.invertedIndex,\n tokenSetBuilder = new lunr.TokenSet.Builder,\n pipeline = lunr.Pipeline.load(serializedIndex.pipeline)\n\n if (serializedIndex.version != lunr.version) {\n lunr.utils.warn(\"Version mismatch when loading serialised index. Current version of lunr '\" + lunr.version + \"' does not match serialized index '\" + serializedIndex.version + \"'\")\n }\n\n for (var i = 0; i < serializedVectors.length; i++) {\n var tuple = serializedVectors[i],\n ref = tuple[0],\n elements = tuple[1]\n\n fieldVectors[ref] = new lunr.Vector(elements)\n }\n\n for (var i = 0; i < serializedInvertedIndex.length; i++) {\n var tuple = serializedInvertedIndex[i],\n term = tuple[0],\n posting = tuple[1]\n\n tokenSetBuilder.insert(term)\n invertedIndex[term] = posting\n }\n\n tokenSetBuilder.finish()\n\n attrs.fields = serializedIndex.fields\n\n attrs.fieldVectors = fieldVectors\n attrs.invertedIndex = invertedIndex\n attrs.tokenSet = tokenSetBuilder.root\n attrs.pipeline = pipeline\n\n return new lunr.Index(attrs)\n}\n/*!\n * lunr.Builder\n * Copyright (C) 2020 Oliver Nightingale\n */\n\n/**\n * lunr.Builder performs indexing on a set of documents and\n * returns instances of lunr.Index ready for querying.\n *\n * All configuration of the index is done via the builder, the\n * fields to index, the document reference, the text processing\n * pipeline and document scoring parameters are all set on the\n * builder before indexing.\n *\n * @constructor\n * @property {string} _ref - Internal reference to the document reference field.\n * @property {string[]} _fields - Internal reference to the document fields to index.\n * @property {object} invertedIndex - The inverted index maps terms to document fields.\n * @property {object} documentTermFrequencies - Keeps track of document term frequencies.\n * @property {object} documentLengths - Keeps track of the length of documents added to the index.\n * @property {lunr.tokenizer} tokenizer - Function for splitting strings into tokens for indexing.\n * @property {lunr.Pipeline} pipeline - The pipeline performs text processing on tokens before indexing.\n * @property {lunr.Pipeline} searchPipeline - A pipeline for processing search terms before querying the index.\n * @property {number} documentCount - Keeps track of the total number of documents indexed.\n * @property {number} _b - A parameter to control field length normalization, setting this to 0 disabled normalization, 1 fully normalizes field lengths, the default value is 0.75.\n * @property {number} _k1 - A parameter to control how quickly an increase in term frequency results in term frequency saturation, the default value is 1.2.\n * @property {number} termIndex - A counter incremented for each unique term, used to identify a terms position in the vector space.\n * @property {array} metadataWhitelist - A list of metadata keys that have been whitelisted for entry in the index.\n */\nlunr.Builder = function () {\n this._ref = \"id\"\n this._fields = Object.create(null)\n this._documents = Object.create(null)\n this.invertedIndex = Object.create(null)\n this.fieldTermFrequencies = {}\n this.fieldLengths = {}\n this.tokenizer = lunr.tokenizer\n this.pipeline = new lunr.Pipeline\n this.searchPipeline = new lunr.Pipeline\n this.documentCount = 0\n this._b = 0.75\n this._k1 = 1.2\n this.termIndex = 0\n this.metadataWhitelist = []\n}\n\n/**\n * Sets the document field used as the document reference. Every document must have this field.\n * The type of this field in the document should be a string, if it is not a string it will be\n * coerced into a string by calling toString.\n *\n * The default ref is 'id'.\n *\n * The ref should _not_ be changed during indexing, it should be set before any documents are\n * added to the index. Changing it during indexing can lead to inconsistent results.\n *\n * @param {string} ref - The name of the reference field in the document.\n */\nlunr.Builder.prototype.ref = function (ref) {\n this._ref = ref\n}\n\n/**\n * A function that is used to extract a field from a document.\n *\n * Lunr expects a field to be at the top level of a document, if however the field\n * is deeply nested within a document an extractor function can be used to extract\n * the right field for indexing.\n *\n * @callback fieldExtractor\n * @param {object} doc - The document being added to the index.\n * @returns {?(string|object|object[])} obj - The object that will be indexed for this field.\n * @example Extracting a nested field\n * function (doc) { return doc.nested.field }\n */\n\n/**\n * Adds a field to the list of document fields that will be indexed. Every document being\n * indexed should have this field. Null values for this field in indexed documents will\n * not cause errors but will limit the chance of that document being retrieved by searches.\n *\n * All fields should be added before adding documents to the index. Adding fields after\n * a document has been indexed will have no effect on already indexed documents.\n *\n * Fields can be boosted at build time. This allows terms within that field to have more\n * importance when ranking search results. Use a field boost to specify that matches within\n * one field are more important than other fields.\n *\n * @param {string} fieldName - The name of a field to index in all documents.\n * @param {object} attributes - Optional attributes associated with this field.\n * @param {number} [attributes.boost=1] - Boost applied to all terms within this field.\n * @param {fieldExtractor} [attributes.extractor] - Function to extract a field from a document.\n * @throws {RangeError} fieldName cannot contain unsupported characters '/'\n */\nlunr.Builder.prototype.field = function (fieldName, attributes) {\n if (/\\//.test(fieldName)) {\n throw new RangeError (\"Field '\" + fieldName + \"' contains illegal character '/'\")\n }\n\n this._fields[fieldName] = attributes || {}\n}\n\n/**\n * A parameter to tune the amount of field length normalisation that is applied when\n * calculating relevance scores. A value of 0 will completely disable any normalisation\n * and a value of 1 will fully normalise field lengths. The default is 0.75. Values of b\n * will be clamped to the range 0 - 1.\n *\n * @param {number} number - The value to set for this tuning parameter.\n */\nlunr.Builder.prototype.b = function (number) {\n if (number < 0) {\n this._b = 0\n } else if (number > 1) {\n this._b = 1\n } else {\n this._b = number\n }\n}\n\n/**\n * A parameter that controls the speed at which a rise in term frequency results in term\n * frequency saturation. The default value is 1.2. Setting this to a higher value will give\n * slower saturation levels, a lower value will result in quicker saturation.\n *\n * @param {number} number - The value to set for this tuning parameter.\n */\nlunr.Builder.prototype.k1 = function (number) {\n this._k1 = number\n}\n\n/**\n * Adds a document to the index.\n *\n * Before adding fields to the index the index should have been fully setup, with the document\n * ref and all fields to index already having been specified.\n *\n * The document must have a field name as specified by the ref (by default this is 'id') and\n * it should have all fields defined for indexing, though null or undefined values will not\n * cause errors.\n *\n * Entire documents can be boosted at build time. Applying a boost to a document indicates that\n * this document should rank higher in search results than other documents.\n *\n * @param {object} doc - The document to add to the index.\n * @param {object} attributes - Optional attributes associated with this document.\n * @param {number} [attributes.boost=1] - Boost applied to all terms within this document.\n */\nlunr.Builder.prototype.add = function (doc, attributes) {\n var docRef = doc[this._ref],\n fields = Object.keys(this._fields)\n\n this._documents[docRef] = attributes || {}\n this.documentCount += 1\n\n for (var i = 0; i < fields.length; i++) {\n var fieldName = fields[i],\n extractor = this._fields[fieldName].extractor,\n field = extractor ? extractor(doc) : doc[fieldName],\n tokens = this.tokenizer(field, {\n fields: [fieldName]\n }),\n terms = this.pipeline.run(tokens),\n fieldRef = new lunr.FieldRef (docRef, fieldName),\n fieldTerms = Object.create(null)\n\n this.fieldTermFrequencies[fieldRef] = fieldTerms\n this.fieldLengths[fieldRef] = 0\n\n // store the length of this field for this document\n this.fieldLengths[fieldRef] += terms.length\n\n // calculate term frequencies for this field\n for (var j = 0; j < terms.length; j++) {\n var term = terms[j]\n\n if (fieldTerms[term] == undefined) {\n fieldTerms[term] = 0\n }\n\n fieldTerms[term] += 1\n\n // add to inverted index\n // create an initial posting if one doesn't exist\n if (this.invertedIndex[term] == undefined) {\n var posting = Object.create(null)\n posting[\"_index\"] = this.termIndex\n this.termIndex += 1\n\n for (var k = 0; k < fields.length; k++) {\n posting[fields[k]] = Object.create(null)\n }\n\n this.invertedIndex[term] = posting\n }\n\n // add an entry for this term/fieldName/docRef to the invertedIndex\n if (this.invertedIndex[term][fieldName][docRef] == undefined) {\n this.invertedIndex[term][fieldName][docRef] = Object.create(null)\n }\n\n // store all whitelisted metadata about this token in the\n // inverted index\n for (var l = 0; l < this.metadataWhitelist.length; l++) {\n var metadataKey = this.metadataWhitelist[l],\n metadata = term.metadata[metadataKey]\n\n if (this.invertedIndex[term][fieldName][docRef][metadataKey] == undefined) {\n this.invertedIndex[term][fieldName][docRef][metadataKey] = []\n }\n\n this.invertedIndex[term][fieldName][docRef][metadataKey].push(metadata)\n }\n }\n\n }\n}\n\n/**\n * Calculates the average document length for this index\n *\n * @private\n */\nlunr.Builder.prototype.calculateAverageFieldLengths = function () {\n\n var fieldRefs = Object.keys(this.fieldLengths),\n numberOfFields = fieldRefs.length,\n accumulator = {},\n documentsWithField = {}\n\n for (var i = 0; i < numberOfFields; i++) {\n var fieldRef = lunr.FieldRef.fromString(fieldRefs[i]),\n field = fieldRef.fieldName\n\n documentsWithField[field] || (documentsWithField[field] = 0)\n documentsWithField[field] += 1\n\n accumulator[field] || (accumulator[field] = 0)\n accumulator[field] += this.fieldLengths[fieldRef]\n }\n\n var fields = Object.keys(this._fields)\n\n for (var i = 0; i < fields.length; i++) {\n var fieldName = fields[i]\n accumulator[fieldName] = accumulator[fieldName] / documentsWithField[fieldName]\n }\n\n this.averageFieldLength = accumulator\n}\n\n/**\n * Builds a vector space model of every document using lunr.Vector\n *\n * @private\n */\nlunr.Builder.prototype.createFieldVectors = function () {\n var fieldVectors = {},\n fieldRefs = Object.keys(this.fieldTermFrequencies),\n fieldRefsLength = fieldRefs.length,\n termIdfCache = Object.create(null)\n\n for (var i = 0; i < fieldRefsLength; i++) {\n var fieldRef = lunr.FieldRef.fromString(fieldRefs[i]),\n fieldName = fieldRef.fieldName,\n fieldLength = this.fieldLengths[fieldRef],\n fieldVector = new lunr.Vector,\n termFrequencies = this.fieldTermFrequencies[fieldRef],\n terms = Object.keys(termFrequencies),\n termsLength = terms.length\n\n\n var fieldBoost = this._fields[fieldName].boost || 1,\n docBoost = this._documents[fieldRef.docRef].boost || 1\n\n for (var j = 0; j < termsLength; j++) {\n var term = terms[j],\n tf = termFrequencies[term],\n termIndex = this.invertedIndex[term]._index,\n idf, score, scoreWithPrecision\n\n if (termIdfCache[term] === undefined) {\n idf = lunr.idf(this.invertedIndex[term], this.documentCount)\n termIdfCache[term] = idf\n } else {\n idf = termIdfCache[term]\n }\n\n score = idf * ((this._k1 + 1) * tf) / (this._k1 * (1 - this._b + this._b * (fieldLength / this.averageFieldLength[fieldName])) + tf)\n score *= fieldBoost\n score *= docBoost\n scoreWithPrecision = Math.round(score * 1000) / 1000\n // Converts 1.23456789 to 1.234.\n // Reducing the precision so that the vectors take up less\n // space when serialised. Doing it now so that they behave\n // the same before and after serialisation. Also, this is\n // the fastest approach to reducing a number's precision in\n // JavaScript.\n\n fieldVector.insert(termIndex, scoreWithPrecision)\n }\n\n fieldVectors[fieldRef] = fieldVector\n }\n\n this.fieldVectors = fieldVectors\n}\n\n/**\n * Creates a token set of all tokens in the index using lunr.TokenSet\n *\n * @private\n */\nlunr.Builder.prototype.createTokenSet = function () {\n this.tokenSet = lunr.TokenSet.fromArray(\n Object.keys(this.invertedIndex).sort()\n )\n}\n\n/**\n * Builds the index, creating an instance of lunr.Index.\n *\n * This completes the indexing process and should only be called\n * once all documents have been added to the index.\n *\n * @returns {lunr.Index}\n */\nlunr.Builder.prototype.build = function () {\n this.calculateAverageFieldLengths()\n this.createFieldVectors()\n this.createTokenSet()\n\n return new lunr.Index({\n invertedIndex: this.invertedIndex,\n fieldVectors: this.fieldVectors,\n tokenSet: this.tokenSet,\n fields: Object.keys(this._fields),\n pipeline: this.searchPipeline\n })\n}\n\n/**\n * Applies a plugin to the index builder.\n *\n * A plugin is a function that is called with the index builder as its context.\n * Plugins can be used to customise or extend the behaviour of the index\n * in some way. A plugin is just a function, that encapsulated the custom\n * behaviour that should be applied when building the index.\n *\n * The plugin function will be called with the index builder as its argument, additional\n * arguments can also be passed when calling use. The function will be called\n * with the index builder as its context.\n *\n * @param {Function} plugin The plugin to apply.\n */\nlunr.Builder.prototype.use = function (fn) {\n var args = Array.prototype.slice.call(arguments, 1)\n args.unshift(this)\n fn.apply(this, args)\n}\n/**\n * Contains and collects metadata about a matching document.\n * A single instance of lunr.MatchData is returned as part of every\n * lunr.Index~Result.\n *\n * @constructor\n * @param {string} term - The term this match data is associated with\n * @param {string} field - The field in which the term was found\n * @param {object} metadata - The metadata recorded about this term in this field\n * @property {object} metadata - A cloned collection of metadata associated with this document.\n * @see {@link lunr.Index~Result}\n */\nlunr.MatchData = function (term, field, metadata) {\n var clonedMetadata = Object.create(null),\n metadataKeys = Object.keys(metadata || {})\n\n // Cloning the metadata to prevent the original\n // being mutated during match data combination.\n // Metadata is kept in an array within the inverted\n // index so cloning the data can be done with\n // Array#slice\n for (var i = 0; i < metadataKeys.length; i++) {\n var key = metadataKeys[i]\n clonedMetadata[key] = metadata[key].slice()\n }\n\n this.metadata = Object.create(null)\n\n if (term !== undefined) {\n this.metadata[term] = Object.create(null)\n this.metadata[term][field] = clonedMetadata\n }\n}\n\n/**\n * An instance of lunr.MatchData will be created for every term that matches a\n * document. However only one instance is required in a lunr.Index~Result. This\n * method combines metadata from another instance of lunr.MatchData with this\n * objects metadata.\n *\n * @param {lunr.MatchData} otherMatchData - Another instance of match data to merge with this one.\n * @see {@link lunr.Index~Result}\n */\nlunr.MatchData.prototype.combine = function (otherMatchData) {\n var terms = Object.keys(otherMatchData.metadata)\n\n for (var i = 0; i < terms.length; i++) {\n var term = terms[i],\n fields = Object.keys(otherMatchData.metadata[term])\n\n if (this.metadata[term] == undefined) {\n this.metadata[term] = Object.create(null)\n }\n\n for (var j = 0; j < fields.length; j++) {\n var field = fields[j],\n keys = Object.keys(otherMatchData.metadata[term][field])\n\n if (this.metadata[term][field] == undefined) {\n this.metadata[term][field] = Object.create(null)\n }\n\n for (var k = 0; k < keys.length; k++) {\n var key = keys[k]\n\n if (this.metadata[term][field][key] == undefined) {\n this.metadata[term][field][key] = otherMatchData.metadata[term][field][key]\n } else {\n this.metadata[term][field][key] = this.metadata[term][field][key].concat(otherMatchData.metadata[term][field][key])\n }\n\n }\n }\n }\n}\n\n/**\n * Add metadata for a term/field pair to this instance of match data.\n *\n * @param {string} term - The term this match data is associated with\n * @param {string} field - The field in which the term was found\n * @param {object} metadata - The metadata recorded about this term in this field\n */\nlunr.MatchData.prototype.add = function (term, field, metadata) {\n if (!(term in this.metadata)) {\n this.metadata[term] = Object.create(null)\n this.metadata[term][field] = metadata\n return\n }\n\n if (!(field in this.metadata[term])) {\n this.metadata[term][field] = metadata\n return\n }\n\n var metadataKeys = Object.keys(metadata)\n\n for (var i = 0; i < metadataKeys.length; i++) {\n var key = metadataKeys[i]\n\n if (key in this.metadata[term][field]) {\n this.metadata[term][field][key] = this.metadata[term][field][key].concat(metadata[key])\n } else {\n this.metadata[term][field][key] = metadata[key]\n }\n }\n}\n/**\n * A lunr.Query provides a programmatic way of defining queries to be performed\n * against a {@link lunr.Index}.\n *\n * Prefer constructing a lunr.Query using the {@link lunr.Index#query} method\n * so the query object is pre-initialized with the right index fields.\n *\n * @constructor\n * @property {lunr.Query~Clause[]} clauses - An array of query clauses.\n * @property {string[]} allFields - An array of all available fields in a lunr.Index.\n */\nlunr.Query = function (allFields) {\n this.clauses = []\n this.allFields = allFields\n}\n\n/**\n * Constants for indicating what kind of automatic wildcard insertion will be used when constructing a query clause.\n *\n * This allows wildcards to be added to the beginning and end of a term without having to manually do any string\n * concatenation.\n *\n * The wildcard constants can be bitwise combined to select both leading and trailing wildcards.\n *\n * @constant\n * @default\n * @property {number} wildcard.NONE - The term will have no wildcards inserted, this is the default behaviour\n * @property {number} wildcard.LEADING - Prepend the term with a wildcard, unless a leading wildcard already exists\n * @property {number} wildcard.TRAILING - Append a wildcard to the term, unless a trailing wildcard already exists\n * @see lunr.Query~Clause\n * @see lunr.Query#clause\n * @see lunr.Query#term\n * @example query term with trailing wildcard\n * query.term('foo', { wildcard: lunr.Query.wildcard.TRAILING })\n * @example query term with leading and trailing wildcard\n * query.term('foo', {\n * wildcard: lunr.Query.wildcard.LEADING | lunr.Query.wildcard.TRAILING\n * })\n */\n\nlunr.Query.wildcard = new String (\"*\")\nlunr.Query.wildcard.NONE = 0\nlunr.Query.wildcard.LEADING = 1\nlunr.Query.wildcard.TRAILING = 2\n\n/**\n * Constants for indicating what kind of presence a term must have in matching documents.\n *\n * @constant\n * @enum {number}\n * @see lunr.Query~Clause\n * @see lunr.Query#clause\n * @see lunr.Query#term\n * @example query term with required presence\n * query.term('foo', { presence: lunr.Query.presence.REQUIRED })\n */\nlunr.Query.presence = {\n /**\n * Term's presence in a document is optional, this is the default value.\n */\n OPTIONAL: 1,\n\n /**\n * Term's presence in a document is required, documents that do not contain\n * this term will not be returned.\n */\n REQUIRED: 2,\n\n /**\n * Term's presence in a document is prohibited, documents that do contain\n * this term will not be returned.\n */\n PROHIBITED: 3\n}\n\n/**\n * A single clause in a {@link lunr.Query} contains a term and details on how to\n * match that term against a {@link lunr.Index}.\n *\n * @typedef {Object} lunr.Query~Clause\n * @property {string[]} fields - The fields in an index this clause should be matched against.\n * @property {number} [boost=1] - Any boost that should be applied when matching this clause.\n * @property {number} [editDistance] - Whether the term should have fuzzy matching applied, and how fuzzy the match should be.\n * @property {boolean} [usePipeline] - Whether the term should be passed through the search pipeline.\n * @property {number} [wildcard=lunr.Query.wildcard.NONE] - Whether the term should have wildcards appended or prepended.\n * @property {number} [presence=lunr.Query.presence.OPTIONAL] - The terms presence in any matching documents.\n */\n\n/**\n * Adds a {@link lunr.Query~Clause} to this query.\n *\n * Unless the clause contains the fields to be matched all fields will be matched. In addition\n * a default boost of 1 is applied to the clause.\n *\n * @param {lunr.Query~Clause} clause - The clause to add to this query.\n * @see lunr.Query~Clause\n * @returns {lunr.Query}\n */\nlunr.Query.prototype.clause = function (clause) {\n if (!('fields' in clause)) {\n clause.fields = this.allFields\n }\n\n if (!('boost' in clause)) {\n clause.boost = 1\n }\n\n if (!('usePipeline' in clause)) {\n clause.usePipeline = true\n }\n\n if (!('wildcard' in clause)) {\n clause.wildcard = lunr.Query.wildcard.NONE\n }\n\n if ((clause.wildcard & lunr.Query.wildcard.LEADING) && (clause.term.charAt(0) != lunr.Query.wildcard)) {\n clause.term = \"*\" + clause.term\n }\n\n if ((clause.wildcard & lunr.Query.wildcard.TRAILING) && (clause.term.slice(-1) != lunr.Query.wildcard)) {\n clause.term = \"\" + clause.term + \"*\"\n }\n\n if (!('presence' in clause)) {\n clause.presence = lunr.Query.presence.OPTIONAL\n }\n\n this.clauses.push(clause)\n\n return this\n}\n\n/**\n * A negated query is one in which every clause has a presence of\n * prohibited. These queries require some special processing to return\n * the expected results.\n *\n * @returns boolean\n */\nlunr.Query.prototype.isNegated = function () {\n for (var i = 0; i < this.clauses.length; i++) {\n if (this.clauses[i].presence != lunr.Query.presence.PROHIBITED) {\n return false\n }\n }\n\n return true\n}\n\n/**\n * Adds a term to the current query, under the covers this will create a {@link lunr.Query~Clause}\n * to the list of clauses that make up this query.\n *\n * The term is used as is, i.e. no tokenization will be performed by this method. Instead conversion\n * to a token or token-like string should be done before calling this method.\n *\n * The term will be converted to a string by calling `toString`. Multiple terms can be passed as an\n * array, each term in the array will share the same options.\n *\n * @param {object|object[]} term - The term(s) to add to the query.\n * @param {object} [options] - Any additional properties to add to the query clause.\n * @returns {lunr.Query}\n * @see lunr.Query#clause\n * @see lunr.Query~Clause\n * @example adding a single term to a query\n * query.term(\"foo\")\n * @example adding a single term to a query and specifying search fields, term boost and automatic trailing wildcard\n * query.term(\"foo\", {\n * fields: [\"title\"],\n * boost: 10,\n * wildcard: lunr.Query.wildcard.TRAILING\n * })\n * @example using lunr.tokenizer to convert a string to tokens before using them as terms\n * query.term(lunr.tokenizer(\"foo bar\"))\n */\nlunr.Query.prototype.term = function (term, options) {\n if (Array.isArray(term)) {\n term.forEach(function (t) { this.term(t, lunr.utils.clone(options)) }, this)\n return this\n }\n\n var clause = options || {}\n clause.term = term.toString()\n\n this.clause(clause)\n\n return this\n}\nlunr.QueryParseError = function (message, start, end) {\n this.name = \"QueryParseError\"\n this.message = message\n this.start = start\n this.end = end\n}\n\nlunr.QueryParseError.prototype = new Error\nlunr.QueryLexer = function (str) {\n this.lexemes = []\n this.str = str\n this.length = str.length\n this.pos = 0\n this.start = 0\n this.escapeCharPositions = []\n}\n\nlunr.QueryLexer.prototype.run = function () {\n var state = lunr.QueryLexer.lexText\n\n while (state) {\n state = state(this)\n }\n}\n\nlunr.QueryLexer.prototype.sliceString = function () {\n var subSlices = [],\n sliceStart = this.start,\n sliceEnd = this.pos\n\n for (var i = 0; i < this.escapeCharPositions.length; i++) {\n sliceEnd = this.escapeCharPositions[i]\n subSlices.push(this.str.slice(sliceStart, sliceEnd))\n sliceStart = sliceEnd + 1\n }\n\n subSlices.push(this.str.slice(sliceStart, this.pos))\n this.escapeCharPositions.length = 0\n\n return subSlices.join('')\n}\n\nlunr.QueryLexer.prototype.emit = function (type) {\n this.lexemes.push({\n type: type,\n str: this.sliceString(),\n start: this.start,\n end: this.pos\n })\n\n this.start = this.pos\n}\n\nlunr.QueryLexer.prototype.escapeCharacter = function () {\n this.escapeCharPositions.push(this.pos - 1)\n this.pos += 1\n}\n\nlunr.QueryLexer.prototype.next = function () {\n if (this.pos >= this.length) {\n return lunr.QueryLexer.EOS\n }\n\n var char = this.str.charAt(this.pos)\n this.pos += 1\n return char\n}\n\nlunr.QueryLexer.prototype.width = function () {\n return this.pos - this.start\n}\n\nlunr.QueryLexer.prototype.ignore = function () {\n if (this.start == this.pos) {\n this.pos += 1\n }\n\n this.start = this.pos\n}\n\nlunr.QueryLexer.prototype.backup = function () {\n this.pos -= 1\n}\n\nlunr.QueryLexer.prototype.acceptDigitRun = function () {\n var char, charCode\n\n do {\n char = this.next()\n charCode = char.charCodeAt(0)\n } while (charCode > 47 && charCode < 58)\n\n if (char != lunr.QueryLexer.EOS) {\n this.backup()\n }\n}\n\nlunr.QueryLexer.prototype.more = function () {\n return this.pos < this.length\n}\n\nlunr.QueryLexer.EOS = 'EOS'\nlunr.QueryLexer.FIELD = 'FIELD'\nlunr.QueryLexer.TERM = 'TERM'\nlunr.QueryLexer.EDIT_DISTANCE = 'EDIT_DISTANCE'\nlunr.QueryLexer.BOOST = 'BOOST'\nlunr.QueryLexer.PRESENCE = 'PRESENCE'\n\nlunr.QueryLexer.lexField = function (lexer) {\n lexer.backup()\n lexer.emit(lunr.QueryLexer.FIELD)\n lexer.ignore()\n return lunr.QueryLexer.lexText\n}\n\nlunr.QueryLexer.lexTerm = function (lexer) {\n if (lexer.width() > 1) {\n lexer.backup()\n lexer.emit(lunr.QueryLexer.TERM)\n }\n\n lexer.ignore()\n\n if (lexer.more()) {\n return lunr.QueryLexer.lexText\n }\n}\n\nlunr.QueryLexer.lexEditDistance = function (lexer) {\n lexer.ignore()\n lexer.acceptDigitRun()\n lexer.emit(lunr.QueryLexer.EDIT_DISTANCE)\n return lunr.QueryLexer.lexText\n}\n\nlunr.QueryLexer.lexBoost = function (lexer) {\n lexer.ignore()\n lexer.acceptDigitRun()\n lexer.emit(lunr.QueryLexer.BOOST)\n return lunr.QueryLexer.lexText\n}\n\nlunr.QueryLexer.lexEOS = function (lexer) {\n if (lexer.width() > 0) {\n lexer.emit(lunr.QueryLexer.TERM)\n }\n}\n\n// This matches the separator used when tokenising fields\n// within a document. These should match otherwise it is\n// not possible to search for some tokens within a document.\n//\n// It is possible for the user to change the separator on the\n// tokenizer so it _might_ clash with any other of the special\n// characters already used within the search string, e.g. :.\n//\n// This means that it is possible to change the separator in\n// such a way that makes some words unsearchable using a search\n// string.\nlunr.QueryLexer.termSeparator = lunr.tokenizer.separator\n\nlunr.QueryLexer.lexText = function (lexer) {\n while (true) {\n var char = lexer.next()\n\n if (char == lunr.QueryLexer.EOS) {\n return lunr.QueryLexer.lexEOS\n }\n\n // Escape character is '\\'\n if (char.charCodeAt(0) == 92) {\n lexer.escapeCharacter()\n continue\n }\n\n if (char == \":\") {\n return lunr.QueryLexer.lexField\n }\n\n if (char == \"~\") {\n lexer.backup()\n if (lexer.width() > 0) {\n lexer.emit(lunr.QueryLexer.TERM)\n }\n return lunr.QueryLexer.lexEditDistance\n }\n\n if (char == \"^\") {\n lexer.backup()\n if (lexer.width() > 0) {\n lexer.emit(lunr.QueryLexer.TERM)\n }\n return lunr.QueryLexer.lexBoost\n }\n\n // \"+\" indicates term presence is required\n // checking for length to ensure that only\n // leading \"+\" are considered\n if (char == \"+\" && lexer.width() === 1) {\n lexer.emit(lunr.QueryLexer.PRESENCE)\n return lunr.QueryLexer.lexText\n }\n\n // \"-\" indicates term presence is prohibited\n // checking for length to ensure that only\n // leading \"-\" are considered\n if (char == \"-\" && lexer.width() === 1) {\n lexer.emit(lunr.QueryLexer.PRESENCE)\n return lunr.QueryLexer.lexText\n }\n\n if (char.match(lunr.QueryLexer.termSeparator)) {\n return lunr.QueryLexer.lexTerm\n }\n }\n}\n\nlunr.QueryParser = function (str, query) {\n this.lexer = new lunr.QueryLexer (str)\n this.query = query\n this.currentClause = {}\n this.lexemeIdx = 0\n}\n\nlunr.QueryParser.prototype.parse = function () {\n this.lexer.run()\n this.lexemes = this.lexer.lexemes\n\n var state = lunr.QueryParser.parseClause\n\n while (state) {\n state = state(this)\n }\n\n return this.query\n}\n\nlunr.QueryParser.prototype.peekLexeme = function () {\n return this.lexemes[this.lexemeIdx]\n}\n\nlunr.QueryParser.prototype.consumeLexeme = function () {\n var lexeme = this.peekLexeme()\n this.lexemeIdx += 1\n return lexeme\n}\n\nlunr.QueryParser.prototype.nextClause = function () {\n var completedClause = this.currentClause\n this.query.clause(completedClause)\n this.currentClause = {}\n}\n\nlunr.QueryParser.parseClause = function (parser) {\n var lexeme = parser.peekLexeme()\n\n if (lexeme == undefined) {\n return\n }\n\n switch (lexeme.type) {\n case lunr.QueryLexer.PRESENCE:\n return lunr.QueryParser.parsePresence\n case lunr.QueryLexer.FIELD:\n return lunr.QueryParser.parseField\n case lunr.QueryLexer.TERM:\n return lunr.QueryParser.parseTerm\n default:\n var errorMessage = \"expected either a field or a term, found \" + lexeme.type\n\n if (lexeme.str.length >= 1) {\n errorMessage += \" with value '\" + lexeme.str + \"'\"\n }\n\n throw new lunr.QueryParseError (errorMessage, lexeme.start, lexeme.end)\n }\n}\n\nlunr.QueryParser.parsePresence = function (parser) {\n var lexeme = parser.consumeLexeme()\n\n if (lexeme == undefined) {\n return\n }\n\n switch (lexeme.str) {\n case \"-\":\n parser.currentClause.presence = lunr.Query.presence.PROHIBITED\n break\n case \"+\":\n parser.currentClause.presence = lunr.Query.presence.REQUIRED\n break\n default:\n var errorMessage = \"unrecognised presence operator'\" + lexeme.str + \"'\"\n throw new lunr.QueryParseError (errorMessage, lexeme.start, lexeme.end)\n }\n\n var nextLexeme = parser.peekLexeme()\n\n if (nextLexeme == undefined) {\n var errorMessage = \"expecting term or field, found nothing\"\n throw new lunr.QueryParseError (errorMessage, lexeme.start, lexeme.end)\n }\n\n switch (nextLexeme.type) {\n case lunr.QueryLexer.FIELD:\n return lunr.QueryParser.parseField\n case lunr.QueryLexer.TERM:\n return lunr.QueryParser.parseTerm\n default:\n var errorMessage = \"expecting term or field, found '\" + nextLexeme.type + \"'\"\n throw new lunr.QueryParseError (errorMessage, nextLexeme.start, nextLexeme.end)\n }\n}\n\nlunr.QueryParser.parseField = function (parser) {\n var lexeme = parser.consumeLexeme()\n\n if (lexeme == undefined) {\n return\n }\n\n if (parser.query.allFields.indexOf(lexeme.str) == -1) {\n var possibleFields = parser.query.allFields.map(function (f) { return \"'\" + f + \"'\" }).join(', '),\n errorMessage = \"unrecognised field '\" + lexeme.str + \"', possible fields: \" + possibleFields\n\n throw new lunr.QueryParseError (errorMessage, lexeme.start, lexeme.end)\n }\n\n parser.currentClause.fields = [lexeme.str]\n\n var nextLexeme = parser.peekLexeme()\n\n if (nextLexeme == undefined) {\n var errorMessage = \"expecting term, found nothing\"\n throw new lunr.QueryParseError (errorMessage, lexeme.start, lexeme.end)\n }\n\n switch (nextLexeme.type) {\n case lunr.QueryLexer.TERM:\n return lunr.QueryParser.parseTerm\n default:\n var errorMessage = \"expecting term, found '\" + nextLexeme.type + \"'\"\n throw new lunr.QueryParseError (errorMessage, nextLexeme.start, nextLexeme.end)\n }\n}\n\nlunr.QueryParser.parseTerm = function (parser) {\n var lexeme = parser.consumeLexeme()\n\n if (lexeme == undefined) {\n return\n }\n\n parser.currentClause.term = lexeme.str.toLowerCase()\n\n if (lexeme.str.indexOf(\"*\") != -1) {\n parser.currentClause.usePipeline = false\n }\n\n var nextLexeme = parser.peekLexeme()\n\n if (nextLexeme == undefined) {\n parser.nextClause()\n return\n }\n\n switch (nextLexeme.type) {\n case lunr.QueryLexer.TERM:\n parser.nextClause()\n return lunr.QueryParser.parseTerm\n case lunr.QueryLexer.FIELD:\n parser.nextClause()\n return lunr.QueryParser.parseField\n case lunr.QueryLexer.EDIT_DISTANCE:\n return lunr.QueryParser.parseEditDistance\n case lunr.QueryLexer.BOOST:\n return lunr.QueryParser.parseBoost\n case lunr.QueryLexer.PRESENCE:\n parser.nextClause()\n return lunr.QueryParser.parsePresence\n default:\n var errorMessage = \"Unexpected lexeme type '\" + nextLexeme.type + \"'\"\n throw new lunr.QueryParseError (errorMessage, nextLexeme.start, nextLexeme.end)\n }\n}\n\nlunr.QueryParser.parseEditDistance = function (parser) {\n var lexeme = parser.consumeLexeme()\n\n if (lexeme == undefined) {\n return\n }\n\n var editDistance = parseInt(lexeme.str, 10)\n\n if (isNaN(editDistance)) {\n var errorMessage = \"edit distance must be numeric\"\n throw new lunr.QueryParseError (errorMessage, lexeme.start, lexeme.end)\n }\n\n parser.currentClause.editDistance = editDistance\n\n var nextLexeme = parser.peekLexeme()\n\n if (nextLexeme == undefined) {\n parser.nextClause()\n return\n }\n\n switch (nextLexeme.type) {\n case lunr.QueryLexer.TERM:\n parser.nextClause()\n return lunr.QueryParser.parseTerm\n case lunr.QueryLexer.FIELD:\n parser.nextClause()\n return lunr.QueryParser.parseField\n case lunr.QueryLexer.EDIT_DISTANCE:\n return lunr.QueryParser.parseEditDistance\n case lunr.QueryLexer.BOOST:\n return lunr.QueryParser.parseBoost\n case lunr.QueryLexer.PRESENCE:\n parser.nextClause()\n return lunr.QueryParser.parsePresence\n default:\n var errorMessage = \"Unexpected lexeme type '\" + nextLexeme.type + \"'\"\n throw new lunr.QueryParseError (errorMessage, nextLexeme.start, nextLexeme.end)\n }\n}\n\nlunr.QueryParser.parseBoost = function (parser) {\n var lexeme = parser.consumeLexeme()\n\n if (lexeme == undefined) {\n return\n }\n\n var boost = parseInt(lexeme.str, 10)\n\n if (isNaN(boost)) {\n var errorMessage = \"boost must be numeric\"\n throw new lunr.QueryParseError (errorMessage, lexeme.start, lexeme.end)\n }\n\n parser.currentClause.boost = boost\n\n var nextLexeme = parser.peekLexeme()\n\n if (nextLexeme == undefined) {\n parser.nextClause()\n return\n }\n\n switch (nextLexeme.type) {\n case lunr.QueryLexer.TERM:\n parser.nextClause()\n return lunr.QueryParser.parseTerm\n case lunr.QueryLexer.FIELD:\n parser.nextClause()\n return lunr.QueryParser.parseField\n case lunr.QueryLexer.EDIT_DISTANCE:\n return lunr.QueryParser.parseEditDistance\n case lunr.QueryLexer.BOOST:\n return lunr.QueryParser.parseBoost\n case lunr.QueryLexer.PRESENCE:\n parser.nextClause()\n return lunr.QueryParser.parsePresence\n default:\n var errorMessage = \"Unexpected lexeme type '\" + nextLexeme.type + \"'\"\n throw new lunr.QueryParseError (errorMessage, nextLexeme.start, nextLexeme.end)\n }\n}\n\n /**\n * export the module via AMD, CommonJS or as a browser global\n * Export code from https://github.com/umdjs/umd/blob/master/returnExports.js\n */\n ;(function (root, factory) {\n if (typeof define === 'function' && define.amd) {\n // AMD. Register as an anonymous module.\n define(factory)\n } else if (typeof exports === 'object') {\n /**\n * Node. Does not work with strict CommonJS, but\n * only CommonJS-like enviroments that support module.exports,\n * like Node.\n */\n module.exports = factory()\n } else {\n // Browser globals (root is window)\n root.lunr = factory()\n }\n }(this, function () {\n /**\n * Just return a value to define the module export.\n * This example returns an object, but the module\n * can return a function as the exported value.\n */\n return lunr\n }))\n})();\n", "/*!\n * escape-html\n * Copyright(c) 2012-2013 TJ Holowaychuk\n * Copyright(c) 2015 Andreas Lubbe\n * Copyright(c) 2015 Tiancheng \"Timothy\" Gu\n * MIT Licensed\n */\n\n'use strict';\n\n/**\n * Module variables.\n * @private\n */\n\nvar matchHtmlRegExp = /[\"'&<>]/;\n\n/**\n * Module exports.\n * @public\n */\n\nmodule.exports = escapeHtml;\n\n/**\n * Escape special characters in the given string of html.\n *\n * @param {string} string The string to escape for inserting into HTML\n * @return {string}\n * @public\n */\n\nfunction escapeHtml(string) {\n var str = '' + string;\n var match = matchHtmlRegExp.exec(str);\n\n if (!match) {\n return str;\n }\n\n var escape;\n var html = '';\n var index = 0;\n var lastIndex = 0;\n\n for (index = match.index; index < str.length; index++) {\n switch (str.charCodeAt(index)) {\n case 34: // \"\n escape = '"';\n break;\n case 38: // &\n escape = '&';\n break;\n case 39: // '\n escape = ''';\n break;\n case 60: // <\n escape = '<';\n break;\n case 62: // >\n escape = '>';\n break;\n default:\n continue;\n }\n\n if (lastIndex !== index) {\n html += str.substring(lastIndex, index);\n }\n\n lastIndex = index + 1;\n html += escape;\n }\n\n return lastIndex !== index\n ? html + str.substring(lastIndex, index)\n : html;\n}\n", "/*\n * Copyright (c) 2016-2022 Martin Donath \n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A RTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\nimport lunr from \"lunr\"\n\nimport \"~/polyfills\"\n\nimport { Search, SearchIndexConfig } from \"../../_\"\nimport {\n SearchMessage,\n SearchMessageType\n} from \"../message\"\n\n/* ----------------------------------------------------------------------------\n * Types\n * ------------------------------------------------------------------------- */\n\n/**\n * Add support for usage with `iframe-worker` polyfill\n *\n * While `importScripts` is synchronous when executed inside of a web worker,\n * it's not possible to provide a synchronous polyfilled implementation. The\n * cool thing is that awaiting a non-Promise is a noop, so extending the type\n * definition to return a `Promise` shouldn't break anything.\n *\n * @see https://bit.ly/2PjDnXi - GitHub comment\n */\ndeclare global {\n function importScripts(...urls: string[]): Promise | void\n}\n\n/* ----------------------------------------------------------------------------\n * Data\n * ------------------------------------------------------------------------- */\n\n/**\n * Search index\n */\nlet index: Search\n\n/* ----------------------------------------------------------------------------\n * Helper functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Fetch (= import) multi-language support through `lunr-languages`\n *\n * This function automatically imports the stemmers necessary to process the\n * languages, which are defined through the search index configuration.\n *\n * If the worker runs inside of an `iframe` (when using `iframe-worker` as\n * a shim), the base URL for the stemmers to be loaded must be determined by\n * searching for the first `script` element with a `src` attribute, which will\n * contain the contents of this script.\n *\n * @param config - Search index configuration\n *\n * @returns Promise resolving with no result\n */\nasync function setupSearchLanguages(\n config: SearchIndexConfig\n): Promise {\n let base = \"../lunr\"\n\n /* Detect `iframe-worker` and fix base URL */\n if (typeof parent !== \"undefined\" && \"IFrameWorker\" in parent) {\n const worker = document.querySelector(\"script[src]\")!\n const [path] = worker.src.split(\"/worker\")\n\n /* Prefix base with path */\n base = base.replace(\"..\", path)\n }\n\n /* Add scripts for languages */\n const scripts = []\n for (const lang of config.lang) {\n switch (lang) {\n\n /* Add segmenter for Japanese */\n case \"ja\":\n scripts.push(`${base}/tinyseg.js`)\n break\n\n /* Add segmenter for Hindi and Thai */\n case \"hi\":\n case \"th\":\n scripts.push(`${base}/wordcut.js`)\n break\n }\n\n /* Add language support */\n if (lang !== \"en\")\n scripts.push(`${base}/min/lunr.${lang}.min.js`)\n }\n\n /* Add multi-language support */\n if (config.lang.length > 1)\n scripts.push(`${base}/min/lunr.multi.min.js`)\n\n /* Load scripts synchronously */\n if (scripts.length)\n await importScripts(\n `${base}/min/lunr.stemmer.support.min.js`,\n ...scripts\n )\n}\n\n/* ----------------------------------------------------------------------------\n * Functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Message handler\n *\n * @param message - Source message\n *\n * @returns Target message\n */\nexport async function handler(\n message: SearchMessage\n): Promise {\n switch (message.type) {\n\n /* Search setup message */\n case SearchMessageType.SETUP:\n await setupSearchLanguages(message.data.config)\n index = new Search(message.data)\n return {\n type: SearchMessageType.READY\n }\n\n /* Search query message */\n case SearchMessageType.QUERY:\n return {\n type: SearchMessageType.RESULT,\n data: index ? index.search(message.data) : { items: [] }\n }\n\n /* All other messages */\n default:\n throw new TypeError(\"Invalid message type\")\n }\n}\n\n/* ----------------------------------------------------------------------------\n * Worker\n * ------------------------------------------------------------------------- */\n\n/* @ts-expect-error - expose Lunr.js in global scope, or stemmers won't work */\nself.lunr = lunr\n\n/* Handle messages */\naddEventListener(\"message\", async ev => {\n postMessage(await handler(ev.data))\n})\n", "/*\n * Copyright (c) 2016-2022 Martin Donath \n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\n/* ----------------------------------------------------------------------------\n * Polyfills\n * ------------------------------------------------------------------------- */\n\n/* Polyfill `Object.entries` */\nif (!Object.entries)\n Object.entries = function (obj: object) {\n const data: [string, string][] = []\n for (const key of Object.keys(obj))\n // @ts-expect-error - ignore property access warning\n data.push([key, obj[key]])\n\n /* Return entries */\n return data\n }\n\n/* Polyfill `Object.values` */\nif (!Object.values)\n Object.values = function (obj: object) {\n const data: string[] = []\n for (const key of Object.keys(obj))\n // @ts-expect-error - ignore property access warning\n data.push(obj[key])\n\n /* Return values */\n return data\n }\n\n/* ------------------------------------------------------------------------- */\n\n/* Polyfills for `Element` */\nif (typeof Element !== \"undefined\") {\n\n /* Polyfill `Element.scrollTo` */\n if (!Element.prototype.scrollTo)\n Element.prototype.scrollTo = function (\n x?: ScrollToOptions | number, y?: number\n ): void {\n if (typeof x === \"object\") {\n this.scrollLeft = x.left!\n this.scrollTop = x.top!\n } else {\n this.scrollLeft = x!\n this.scrollTop = y!\n }\n }\n\n /* Polyfill `Element.replaceWith` */\n if (!Element.prototype.replaceWith)\n Element.prototype.replaceWith = function (\n ...nodes: Array\n ): void {\n const parent = this.parentNode\n if (parent) {\n if (nodes.length === 0)\n parent.removeChild(this)\n\n /* Replace children and create text nodes */\n for (let i = nodes.length - 1; i >= 0; i--) {\n let node = nodes[i]\n if (typeof node !== \"object\")\n node = document.createTextNode(node)\n else if (node.parentNode)\n node.parentNode.removeChild(node)\n\n /* Replace child or insert before previous sibling */\n if (!i)\n parent.replaceChild(node, this)\n else\n parent.insertBefore(this.previousSibling!, node)\n }\n }\n }\n}\n", "/*\n * Copyright (c) 2016-2022 Martin Donath \n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\nimport escapeHTML from \"escape-html\"\n\nimport { SearchIndexDocument } from \"../_\"\n\n/* ----------------------------------------------------------------------------\n * Types\n * ------------------------------------------------------------------------- */\n\n/**\n * Search document\n */\nexport interface SearchDocument extends SearchIndexDocument {\n parent?: SearchIndexDocument /* Parent article */\n}\n\n/* ------------------------------------------------------------------------- */\n\n/**\n * Search document mapping\n */\nexport type SearchDocumentMap = Map\n\n/* ----------------------------------------------------------------------------\n * Functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Create a search document mapping\n *\n * @param docs - Search index documents\n *\n * @returns Search document map\n */\nexport function setupSearchDocumentMap(\n docs: SearchIndexDocument[]\n): SearchDocumentMap {\n const documents = new Map()\n const parents = new Set()\n for (const doc of docs) {\n const [path, hash] = doc.location.split(\"#\")\n\n /* Extract location, title and tags */\n const location = doc.location\n const title = doc.title\n const tags = doc.tags\n\n /* Escape and cleanup text */\n const text = escapeHTML(doc.text)\n .replace(/\\s+(?=[,.:;!?])/g, \"\")\n .replace(/\\s+/g, \" \")\n\n /* Handle section */\n if (hash) {\n const parent = documents.get(path)!\n\n /* Ignore first section, override article */\n if (!parents.has(parent)) {\n parent.title = doc.title\n parent.text = text\n\n /* Remember that we processed the article */\n parents.add(parent)\n\n /* Add subsequent section */\n } else {\n documents.set(location, {\n location,\n title,\n text,\n parent\n })\n }\n\n /* Add article */\n } else {\n documents.set(location, {\n location,\n title,\n text,\n ...tags && { tags }\n })\n }\n }\n return documents\n}\n", "/*\n * Copyright (c) 2016-2022 Martin Donath \n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\nimport escapeHTML from \"escape-html\"\n\nimport { SearchIndexConfig } from \"../_\"\n\n/* ----------------------------------------------------------------------------\n * Types\n * ------------------------------------------------------------------------- */\n\n/**\n * Search highlight function\n *\n * @param value - Value\n *\n * @returns Highlighted value\n */\nexport type SearchHighlightFn = (value: string) => string\n\n/**\n * Search highlight factory function\n *\n * @param query - Query value\n *\n * @returns Search highlight function\n */\nexport type SearchHighlightFactoryFn = (query: string) => SearchHighlightFn\n\n/* ----------------------------------------------------------------------------\n * Functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Create a search highlighter\n *\n * @param config - Search index configuration\n * @param escape - Whether to escape HTML\n *\n * @returns Search highlight factory function\n */\nexport function setupSearchHighlighter(\n config: SearchIndexConfig, escape: boolean\n): SearchHighlightFactoryFn {\n const separator = new RegExp(config.separator, \"img\")\n const highlight = (_: unknown, data: string, term: string) => {\n return `${data}${term}`\n }\n\n /* Return factory function */\n return (query: string) => {\n query = query\n .replace(/[\\s*+\\-:~^]+/g, \" \")\n .trim()\n\n /* Create search term match expression */\n const match = new RegExp(`(^|${config.separator})(${\n query\n .replace(/[|\\\\{}()[\\]^$+*?.-]/g, \"\\\\$&\")\n .replace(separator, \"|\")\n })`, \"img\")\n\n /* Highlight string value */\n return value => (\n escape\n ? escapeHTML(value)\n : value\n )\n .replace(match, highlight)\n .replace(/<\\/mark>(\\s+)]*>/img, \"$1\")\n }\n}\n", "/*\n * Copyright (c) 2016-2022 Martin Donath \n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\n/* ----------------------------------------------------------------------------\n * Types\n * ------------------------------------------------------------------------- */\n\n/**\n * Search query clause\n */\nexport interface SearchQueryClause {\n presence: lunr.Query.presence /* Clause presence */\n term: string /* Clause term */\n}\n\n/* ------------------------------------------------------------------------- */\n\n/**\n * Search query terms\n */\nexport type SearchQueryTerms = Record\n\n/* ----------------------------------------------------------------------------\n * Functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Parse a search query for analysis\n *\n * @param value - Query value\n *\n * @returns Search query clauses\n */\nexport function parseSearchQuery(\n value: string\n): SearchQueryClause[] {\n const query = new (lunr as any).Query([\"title\", \"text\"])\n const parser = new (lunr as any).QueryParser(value, query)\n\n /* Parse and return query clauses */\n parser.parse()\n return query.clauses\n}\n\n/**\n * Analyze the search query clauses in regard to the search terms found\n *\n * @param query - Search query clauses\n * @param terms - Search terms\n *\n * @returns Search query terms\n */\nexport function getSearchQueryTerms(\n query: SearchQueryClause[], terms: string[]\n): SearchQueryTerms {\n const clauses = new Set(query)\n\n /* Match query clauses against terms */\n const result: SearchQueryTerms = {}\n for (let t = 0; t < terms.length; t++)\n for (const clause of clauses)\n if (terms[t].startsWith(clause.term)) {\n result[clause.term] = true\n clauses.delete(clause)\n }\n\n /* Annotate unmatched non-stopword query clauses */\n for (const clause of clauses)\n if (lunr.stopWordFilter?.(clause.term as any))\n result[clause.term] = false\n\n /* Return query terms */\n return result\n}\n", "/*\n * Copyright (c) 2016-2022 Martin Donath \n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\nimport {\n SearchDocument,\n SearchDocumentMap,\n setupSearchDocumentMap\n} from \"../document\"\nimport {\n SearchHighlightFactoryFn,\n setupSearchHighlighter\n} from \"../highlighter\"\nimport { SearchOptions } from \"../options\"\nimport {\n SearchQueryTerms,\n getSearchQueryTerms,\n parseSearchQuery\n} from \"../query\"\n\n/* ----------------------------------------------------------------------------\n * Types\n * ------------------------------------------------------------------------- */\n\n/**\n * Search index configuration\n */\nexport interface SearchIndexConfig {\n lang: string[] /* Search languages */\n separator: string /* Search separator */\n}\n\n/**\n * Search index document\n */\nexport interface SearchIndexDocument {\n location: string /* Document location */\n title: string /* Document title */\n text: string /* Document text */\n tags?: string[] /* Document tags */\n boost?: number /* Document boost */\n}\n\n/* ------------------------------------------------------------------------- */\n\n/**\n * Search index\n *\n * This interfaces describes the format of the `search_index.json` file which\n * is automatically built by the MkDocs search plugin.\n */\nexport interface SearchIndex {\n config: SearchIndexConfig /* Search index configuration */\n docs: SearchIndexDocument[] /* Search index documents */\n options: SearchOptions /* Search options */\n}\n\n/* ------------------------------------------------------------------------- */\n\n/**\n * Search metadata\n */\nexport interface SearchMetadata {\n score: number /* Score (relevance) */\n terms: SearchQueryTerms /* Search query terms */\n}\n\n/* ------------------------------------------------------------------------- */\n\n/**\n * Search result document\n */\nexport type SearchResultDocument = SearchDocument & SearchMetadata\n\n/**\n * Search result item\n */\nexport type SearchResultItem = SearchResultDocument[]\n\n/* ------------------------------------------------------------------------- */\n\n/**\n * Search result\n */\nexport interface SearchResult {\n items: SearchResultItem[] /* Search result items */\n suggestions?: string[] /* Search suggestions */\n}\n\n/* ----------------------------------------------------------------------------\n * Functions\n * ------------------------------------------------------------------------- */\n\n/**\n * Compute the difference of two lists of strings\n *\n * @param a - 1st list of strings\n * @param b - 2nd list of strings\n *\n * @returns Difference\n */\nfunction difference(a: string[], b: string[]): string[] {\n const [x, y] = [new Set(a), new Set(b)]\n return [\n ...new Set([...x].filter(value => !y.has(value)))\n ]\n}\n\n/* ----------------------------------------------------------------------------\n * Class\n * ------------------------------------------------------------------------- */\n\n/**\n * Search index\n */\nexport class Search {\n\n /**\n * Search document mapping\n *\n * A mapping of URLs (including hash fragments) to the actual articles and\n * sections of the documentation. The search document mapping must be created\n * regardless of whether the index was prebuilt or not, as Lunr.js itself\n * only stores the actual index.\n */\n protected documents: SearchDocumentMap\n\n /**\n * Search highlight factory function\n */\n protected highlight: SearchHighlightFactoryFn\n\n /**\n * The underlying Lunr.js search index\n */\n protected index: lunr.Index\n\n /**\n * Search options\n */\n protected options: SearchOptions\n\n /**\n * Create the search integration\n *\n * @param data - Search index\n */\n public constructor({ config, docs, options }: SearchIndex) {\n this.options = options\n\n /* Set up document map and highlighter factory */\n this.documents = setupSearchDocumentMap(docs)\n this.highlight = setupSearchHighlighter(config, false)\n\n /* Set separator for tokenizer */\n lunr.tokenizer.separator = new RegExp(config.separator)\n\n /* Create search index */\n this.index = lunr(function () {\n\n /* Set up multi-language support */\n if (config.lang.length === 1 && config.lang[0] !== \"en\") {\n this.use((lunr as any)[config.lang[0]])\n } else if (config.lang.length > 1) {\n this.use((lunr as any).multiLanguage(...config.lang))\n }\n\n /* Compute functions to be removed from the pipeline */\n const fns = difference([\n \"trimmer\", \"stopWordFilter\", \"stemmer\"\n ], options.pipeline)\n\n /* Remove functions from the pipeline for registered languages */\n for (const lang of config.lang.map(language => (\n language === \"en\" ? lunr : (lunr as any)[language]\n ))) {\n for (const fn of fns) {\n this.pipeline.remove(lang[fn])\n this.searchPipeline.remove(lang[fn])\n }\n }\n\n /* Set up reference */\n this.ref(\"location\")\n\n /* Set up fields */\n this.field(\"title\", { boost: 1e3 })\n this.field(\"text\")\n this.field(\"tags\", { boost: 1e6, extractor: doc => {\n const { tags = [] } = doc as SearchDocument\n return tags.reduce((list, tag) => [\n ...list,\n ...lunr.tokenizer(tag)\n ], [] as lunr.Token[])\n } })\n\n /* Index documents */\n for (const doc of docs)\n this.add(doc, { boost: doc.boost })\n })\n }\n\n /**\n * Search for matching documents\n *\n * The search index which MkDocs provides is divided up into articles, which\n * contain the whole content of the individual pages, and sections, which only\n * contain the contents of the subsections obtained by breaking the individual\n * pages up at `h1` ... `h6`. As there may be many sections on different pages\n * with identical titles (for example within this very project, e.g. \"Usage\"\n * or \"Installation\"), they need to be put into the context of the containing\n * page. For this reason, section results are grouped within their respective\n * articles which are the top-level results that are returned.\n *\n * @param query - Query value\n *\n * @returns Search results\n */\n public search(query: string): SearchResult {\n if (query) {\n try {\n const highlight = this.highlight(query)\n\n /* Parse query to extract clauses for analysis */\n const clauses = parseSearchQuery(query)\n .filter(clause => (\n clause.presence !== lunr.Query.presence.PROHIBITED\n ))\n\n /* Perform search and post-process results */\n const groups = this.index.search(`${query}*`)\n\n /* Apply post-query boosts based on title and search query terms */\n .reduce((item, { ref, score, matchData }) => {\n const document = this.documents.get(ref)\n if (typeof document !== \"undefined\") {\n const { location, title, text, tags, parent } = document\n\n /* Compute and analyze search query terms */\n const terms = getSearchQueryTerms(\n clauses,\n Object.keys(matchData.metadata)\n )\n\n /* Highlight title and text and apply post-query boosts */\n const boost = +!parent + +Object.values(terms).every(t => t)\n item.push({\n location,\n title: highlight(title),\n text: highlight(text),\n ...tags && { tags: tags.map(highlight) },\n score: score * (1 + boost),\n terms\n })\n }\n return item\n }, [])\n\n /* Sort search results again after applying boosts */\n .sort((a, b) => b.score - a.score)\n\n /* Group search results by page */\n .reduce((items, result) => {\n const document = this.documents.get(result.location)\n if (typeof document !== \"undefined\") {\n const ref = \"parent\" in document\n ? document.parent!.location\n : document.location\n items.set(ref, [...items.get(ref) || [], result])\n }\n return items\n }, new Map())\n\n /* Generate search suggestions, if desired */\n let suggestions: string[] | undefined\n if (this.options.suggestions) {\n const titles = this.index.query(builder => {\n for (const clause of clauses)\n builder.term(clause.term, {\n fields: [\"title\"],\n presence: lunr.Query.presence.REQUIRED,\n wildcard: lunr.Query.wildcard.TRAILING\n })\n })\n\n /* Retrieve suggestions for best match */\n suggestions = titles.length\n ? Object.keys(titles[0].matchData.metadata)\n : []\n }\n\n /* Return items and suggestions */\n return {\n items: [...groups.values()],\n ...typeof suggestions !== \"undefined\" && { suggestions }\n }\n\n /* Log errors to console (for now) */\n } catch {\n console.warn(`Invalid query: ${query} \u2013 see https://bit.ly/2s3ChXG`)\n }\n }\n\n /* Return nothing in case of error or empty query */\n return { items: [] }\n }\n}\n"], + "mappings": "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", + "names": [] +} diff --git a/develop/assets/stylesheets/friendica.css b/develop/assets/stylesheets/friendica.css new file mode 100644 index 0000000..3a07af4 --- /dev/null +++ b/develop/assets/stylesheets/friendica.css @@ -0,0 +1,91 @@ +:root > * { + + // Default color shades + --md-default-fg-color: hsla(0, 0%, 0%, 0.87); + --md-default-fg-color--light: hsla(0, 0%, 0%, 0.54); + --md-default-fg-color--lighter: hsla(0, 0%, 0%, 0.32); + --md-default-fg-color--lightest: hsla(0, 0%, 0%, 0.07); + --md-default-bg-color: hsla(0, 0%, 100%, 1); + --md-default-bg-color--light: hsla(0, 0%, 100%, 0.7); + --md-default-bg-color--lighter: hsla(0, 0%, 100%, 0.3); + --md-default-bg-color--lightest: hsla(0, 0%, 100%, 0.12); + + // Primary color shades + --md-primary-fg-color: hsla(#{hex2hsl($clr-indigo-500)}, 1); + --md-primary-fg-color--light: hsla(#{hex2hsl($clr-indigo-400)}, 1); + --md-primary-fg-color--dark: hsla(#{hex2hsl($clr-indigo-700)}, 1); + --md-primary-bg-color: hsla(0, 0%, 100%, 1); + --md-primary-bg-color--light: hsla(0, 0%, 100%, 0.7); + + // Accent color shades + --md-accent-fg-color: hsla(#{hex2hsl($clr-indigo-a200)}, 1); + --md-accent-fg-color--transparent: hsla(#{hex2hsl($clr-indigo-a200)}, 0.1); + --md-accent-bg-color: hsla(0, 0%, 100%, 1); + --md-accent-bg-color--light: hsla(0, 0%, 100%, 0.7); + + // Code color shades + --md-code-fg-color: hsla(200, 18%, 26%, 1); + --md-code-bg-color: hsla(0, 0%, 96%, 1); + + // Code highlighting color shades + --md-code-hl-color: hsla(#{hex2hsl($clr-yellow-a200)}, 0.5); + --md-code-hl-number-color: hsla(0, 67%, 50%, 1); + --md-code-hl-special-color: hsla(340, 83%, 47%, 1); + --md-code-hl-function-color: hsla(291, 45%, 50%, 1); + --md-code-hl-constant-color: hsla(250, 63%, 60%, 1); + --md-code-hl-keyword-color: hsla(219, 54%, 51%, 1); + --md-code-hl-string-color: hsla(150, 63%, 30%, 1); + --md-code-hl-name-color: var(--md-code-fg-color); + --md-code-hl-operator-color: var(--md-default-fg-color--light); + --md-code-hl-punctuation-color: var(--md-default-fg-color--light); + --md-code-hl-comment-color: var(--md-default-fg-color--light); + --md-code-hl-generic-color: var(--md-default-fg-color--light); + --md-code-hl-variable-color: var(--md-default-fg-color--light); + + // Typeset color shades + --md-typeset-color: var(--md-default-fg-color); + + // Typeset `a` color shades + --md-typeset-a-color: var(--md-primary-fg-color); + + // Typeset `mark` color shades + --md-typeset-mark-color: hsla(#{hex2hsl($clr-yellow-a200)}, 0.5); + + // Typeset `del` and `ins` color shades + --md-typeset-del-color: hsla(6, 90%, 60%, 0.15); + --md-typeset-ins-color: hsla(150, 90%, 44%, 0.15); + + // Typeset `kbd` color shades + --md-typeset-kbd-color: hsla(0, 0%, 98%, 1); + --md-typeset-kbd-accent-color: hsla(0, 100%, 100%, 1); + --md-typeset-kbd-border-color: hsla(0, 0%, 72%, 1); + + // Typeset `table` color shades + --md-typeset-table-color: hsla(0, 0%, 0%, 0.12); + + // Admonition color shades + --md-admonition-fg-color: var(--md-default-fg-color); + --md-admonition-bg-color: var(--md-default-bg-color); + + // Footer color shades + --md-footer-fg-color: hsla(0, 0%, 100%, 1); + --md-footer-fg-color--light: hsla(0, 0%, 100%, 0.7); + --md-footer-fg-color--lighter: hsla(0, 0%, 100%, 0.3); + --md-footer-bg-color: hsla(0, 0%, 0%, 0.87); + --md-footer-bg-color--dark: hsla(0, 0%, 0%, 0.32); + + // Shadow depth 1 + --md-shadow-z1: + 0 #{px2rem(4px)} #{px2rem(10px)} hsla(0, 0%, 0%, 0.05), + 0 0 #{px2rem(1px)} hsla(0, 0%, 0%, 0.1); + + // Shadow depth 2 + --md-shadow-z2: + 0 #{px2rem(4px)} #{px2rem(10px)} hsla(0, 0%, 0%, 0.1), + 0 0 #{px2rem(1px)} hsla(0, 0%, 0%, 0.25); + + // Shadow depth 3 + --md-shadow-z3: + 0 #{px2rem(4px)} #{px2rem(10px)} hsla(0, 0%, 0%, 0.2), + 0 0 #{px2rem(1px)} hsla(0, 0%, 0%, 0.35); + } diff --git a/develop/assets/stylesheets/main.1d29e8d0.min.css b/develop/assets/stylesheets/main.1d29e8d0.min.css new file mode 100644 index 0000000..2730c64 --- /dev/null +++ b/develop/assets/stylesheets/main.1d29e8d0.min.css @@ -0,0 +1 @@ +@charset "UTF-8";html{-webkit-text-size-adjust:none;-moz-text-size-adjust:none;-ms-text-size-adjust:none;text-size-adjust:none;box-sizing:border-box}*,:after,:before{box-sizing:inherit}@media (prefers-reduced-motion){*,:after,:before{transition:none!important}}body{margin:0}a,button,input,label{-webkit-tap-highlight-color:transparent}a{color:inherit;text-decoration:none}hr{border:0;box-sizing:initial;display:block;height:.05rem;overflow:visible;padding:0}small{font-size:80%}sub,sup{line-height:1em}img{border-style:none}table{border-collapse:initial;border-spacing:0}td,th{font-weight:400;vertical-align:top}button{background:transparent;border:0;font-family:inherit;font-size:inherit;margin:0;padding:0}input{border:0;outline:none}:root,[data-md-color-scheme=default]{--md-default-fg-color:rgba(0,0,0,.87);--md-default-fg-color--light:rgba(0,0,0,.54);--md-default-fg-color--lighter:rgba(0,0,0,.32);--md-default-fg-color--lightest:rgba(0,0,0,.07);--md-default-bg-color:#fff;--md-default-bg-color--light:hsla(0,0%,100%,.7);--md-default-bg-color--lighter:hsla(0,0%,100%,.3);--md-default-bg-color--lightest:hsla(0,0%,100%,.12);--md-primary-fg-color:#4051b5;--md-primary-fg-color--light:#5d6cc0;--md-primary-fg-color--dark:#303fa1;--md-primary-bg-color:#fff;--md-primary-bg-color--light:hsla(0,0%,100%,.7);--md-accent-fg-color:#526cfe;--md-accent-fg-color--transparent:rgba(82,108,254,.1);--md-accent-bg-color:#fff;--md-accent-bg-color--light:hsla(0,0%,100%,.7);--md-code-fg-color:#36464e;--md-code-bg-color:#f5f5f5;--md-code-hl-color:rgba(255,255,0,.5);--md-code-hl-number-color:#d52a2a;--md-code-hl-special-color:#db1457;--md-code-hl-function-color:#a846b9;--md-code-hl-constant-color:#6e59d9;--md-code-hl-keyword-color:#3f6ec6;--md-code-hl-string-color:#1c7d4d;--md-code-hl-name-color:var(--md-code-fg-color);--md-code-hl-operator-color:var(--md-default-fg-color--light);--md-code-hl-punctuation-color:var(--md-default-fg-color--light);--md-code-hl-comment-color:var(--md-default-fg-color--light);--md-code-hl-generic-color:var(--md-default-fg-color--light);--md-code-hl-variable-color:var(--md-default-fg-color--light);--md-typeset-color:var(--md-default-fg-color);--md-typeset-a-color:var(--md-primary-fg-color);--md-typeset-mark-color:rgba(255,255,0,.5);--md-typeset-del-color:rgba(245,80,61,.15);--md-typeset-ins-color:rgba(11,213,112,.15);--md-typeset-kbd-color:#fafafa;--md-typeset-kbd-accent-color:#fff;--md-typeset-kbd-border-color:#b8b8b8;--md-typeset-table-color:rgba(0,0,0,.12);--md-admonition-fg-color:var(--md-default-fg-color);--md-admonition-bg-color:var(--md-default-bg-color);--md-footer-fg-color:#fff;--md-footer-fg-color--light:hsla(0,0%,100%,.7);--md-footer-fg-color--lighter:hsla(0,0%,100%,.3);--md-footer-bg-color:rgba(0,0,0,.87);--md-footer-bg-color--dark:rgba(0,0,0,.32);--md-shadow-z1:0 0.2rem 0.5rem rgba(0,0,0,.05),0 0 0.05rem rgba(0,0,0,.1);--md-shadow-z2:0 0.2rem 0.5rem rgba(0,0,0,.1),0 0 0.05rem rgba(0,0,0,.25);--md-shadow-z3:0 0.2rem 0.5rem rgba(0,0,0,.2),0 0 0.05rem rgba(0,0,0,.35)}.md-icon svg{fill:currentcolor;display:block;height:1.2rem;width:1.2rem}body{-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;--md-text-font-family:var(--md-text-font,_),-apple-system,BlinkMacSystemFont,Helvetica,Arial,sans-serif;--md-code-font-family:var(--md-code-font,_),SFMono-Regular,Consolas,Menlo,monospace}body,input{font-feature-settings:"kern","liga";font-family:var(--md-text-font-family)}body,code,input,kbd,pre{color:var(--md-typeset-color)}code,kbd,pre{font-feature-settings:"kern";font-family:var(--md-code-font-family)}:root{--md-typeset-table-sort-icon:url('data:image/svg+xml;charset=utf-8,');--md-typeset-table-sort-icon--asc:url('data:image/svg+xml;charset=utf-8,');--md-typeset-table-sort-icon--desc:url('data:image/svg+xml;charset=utf-8,')}.md-typeset{-webkit-print-color-adjust:exact;color-adjust:exact;font-size:.8rem;line-height:1.6}@media print{.md-typeset{font-size:.68rem}}.md-typeset blockquote,.md-typeset dl,.md-typeset figure,.md-typeset ol,.md-typeset pre,.md-typeset ul{margin-bottom:1em;margin-top:1em}.md-typeset h1{color:var(--md-default-fg-color--light);font-size:2em;line-height:1.3;margin:0 0 1.25em}.md-typeset h1,.md-typeset h2{font-weight:300;letter-spacing:-.01em}.md-typeset h2{font-size:1.5625em;line-height:1.4;margin:1.6em 0 .64em}.md-typeset h3{font-size:1.25em;font-weight:400;letter-spacing:-.01em;line-height:1.5;margin:1.6em 0 .8em}.md-typeset h2+h3{margin-top:.8em}.md-typeset h4{font-weight:700;letter-spacing:-.01em;margin:1em 0}.md-typeset h5,.md-typeset h6{color:var(--md-default-fg-color--light);font-size:.8em;font-weight:700;letter-spacing:-.01em;margin:1.25em 0}.md-typeset h5{text-transform:uppercase}.md-typeset hr{border-bottom:.05rem solid var(--md-default-fg-color--lightest);display:flow-root;margin:1.5em 0}.md-typeset a{color:var(--md-typeset-a-color);word-break:break-word}.md-typeset a,.md-typeset a:before{transition:color 125ms}.md-typeset a:focus,.md-typeset a:hover{color:var(--md-accent-fg-color)}.md-typeset a:focus code,.md-typeset a:hover code{background-color:var(--md-accent-fg-color--transparent)}.md-typeset a code{color:currentcolor;transition:background-color 125ms}.md-typeset a.focus-visible{outline-color:var(--md-accent-fg-color);outline-offset:.2rem}.md-typeset code,.md-typeset kbd,.md-typeset pre{color:var(--md-code-fg-color);direction:ltr}@media print{.md-typeset code,.md-typeset kbd,.md-typeset pre{white-space:pre-wrap}}.md-typeset code{background-color:var(--md-code-bg-color);border-radius:.1rem;-webkit-box-decoration-break:clone;box-decoration-break:clone;font-size:.85em;padding:0 .2941176471em;word-break:break-word}.md-typeset code:not(.focus-visible){-webkit-tap-highlight-color:transparent;outline:none}.md-typeset pre{display:flow-root;line-height:1.4;position:relative}.md-typeset pre>code{-webkit-box-decoration-break:slice;box-decoration-break:slice;box-shadow:none;display:block;margin:0;outline-color:var(--md-accent-fg-color);overflow:auto;padding:.7720588235em 1.1764705882em;scrollbar-color:var(--md-default-fg-color--lighter) transparent;scrollbar-width:thin;touch-action:auto;word-break:normal}.md-typeset pre>code:hover{scrollbar-color:var(--md-accent-fg-color) transparent}.md-typeset pre>code::-webkit-scrollbar{height:.2rem;width:.2rem}.md-typeset pre>code::-webkit-scrollbar-thumb{background-color:var(--md-default-fg-color--lighter)}.md-typeset pre>code::-webkit-scrollbar-thumb:hover{background-color:var(--md-accent-fg-color)}.md-typeset kbd{background-color:var(--md-typeset-kbd-color);border-radius:.1rem;box-shadow:0 .1rem 0 .05rem var(--md-typeset-kbd-border-color),0 .1rem 0 var(--md-typeset-kbd-border-color),0 -.1rem .2rem var(--md-typeset-kbd-accent-color) inset;color:var(--md-default-fg-color);display:inline-block;font-size:.75em;padding:0 .6666666667em;vertical-align:text-top;word-break:break-word}.md-typeset mark{background-color:var(--md-typeset-mark-color);-webkit-box-decoration-break:clone;box-decoration-break:clone;color:inherit;word-break:break-word}.md-typeset abbr{border-bottom:.05rem dotted var(--md-default-fg-color--light);cursor:help;text-decoration:none}@media (hover:none){.md-typeset abbr{position:relative}.md-typeset abbr[title]:-webkit-any(:focus,:hover):after{background-color:var(--md-default-fg-color);border-radius:.1rem;box-shadow:var(--md-shadow-z3);color:var(--md-default-bg-color);content:attr(title);display:inline-block;font-size:.7rem;margin-top:2em;max-width:80%;min-width:-webkit-max-content;min-width:max-content;padding:.2rem .3rem;position:absolute;width:auto}.md-typeset abbr[title]:-moz-any(:focus,:hover):after{background-color:var(--md-default-fg-color);border-radius:.1rem;box-shadow:var(--md-shadow-z3);color:var(--md-default-bg-color);content:attr(title);display:inline-block;font-size:.7rem;margin-top:2em;max-width:80%;min-width:-moz-max-content;min-width:max-content;padding:.2rem .3rem;position:absolute;width:auto}[dir=ltr] .md-typeset abbr[title]:-webkit-any(:focus,:hover):after{left:0}[dir=ltr] .md-typeset abbr[title]:-moz-any(:focus,:hover):after{left:0}[dir=ltr] .md-typeset abbr[title]:is(:focus,:hover):after{left:0}[dir=rtl] .md-typeset abbr[title]:-webkit-any(:focus,:hover):after{right:0}[dir=rtl] .md-typeset abbr[title]:-moz-any(:focus,:hover):after{right:0}[dir=rtl] .md-typeset abbr[title]:is(:focus,:hover):after{right:0}.md-typeset abbr[title]:is(:focus,:hover):after{background-color:var(--md-default-fg-color);border-radius:.1rem;box-shadow:var(--md-shadow-z3);color:var(--md-default-bg-color);content:attr(title);display:inline-block;font-size:.7rem;margin-top:2em;max-width:80%;min-width:-webkit-max-content;min-width:-moz-max-content;min-width:max-content;padding:.2rem .3rem;position:absolute;width:auto}}.md-typeset small{opacity:.75}[dir=ltr] .md-typeset sub,[dir=ltr] .md-typeset sup{margin-left:.078125em}[dir=rtl] .md-typeset sub,[dir=rtl] .md-typeset sup{margin-right:.078125em}[dir=ltr] .md-typeset blockquote{padding-left:.6rem}[dir=rtl] .md-typeset blockquote{padding-right:.6rem}[dir=ltr] .md-typeset blockquote{border-left:.2rem solid var(--md-default-fg-color--lighter)}[dir=rtl] .md-typeset blockquote{border-right:.2rem solid var(--md-default-fg-color--lighter)}.md-typeset blockquote{color:var(--md-default-fg-color--light);margin-left:0;margin-right:0}.md-typeset ul{list-style-type:disc}[dir=ltr] .md-typeset ol,[dir=ltr] .md-typeset ul{margin-left:.625em}[dir=rtl] .md-typeset ol,[dir=rtl] .md-typeset ul{margin-right:.625em}.md-typeset ol,.md-typeset ul{padding:0}.md-typeset ol:not([hidden]),.md-typeset ul:not([hidden]){display:flow-root}.md-typeset ol ol,.md-typeset ul ol{list-style-type:lower-alpha}.md-typeset ol ol ol,.md-typeset ul ol ol{list-style-type:lower-roman}[dir=ltr] .md-typeset ol li,[dir=ltr] .md-typeset ul li{margin-left:1.25em}[dir=rtl] .md-typeset ol li,[dir=rtl] .md-typeset ul li{margin-right:1.25em}.md-typeset ol li,.md-typeset ul li{margin-bottom:.5em}.md-typeset ol li blockquote,.md-typeset ol li p,.md-typeset ul li blockquote,.md-typeset ul li p{margin:.5em 0}.md-typeset ol li:last-child,.md-typeset ul li:last-child{margin-bottom:0}.md-typeset ol li :-webkit-any(ul,ol),.md-typeset ul li :-webkit-any(ul,ol){margin-bottom:.5em;margin-top:.5em}.md-typeset ol li :-moz-any(ul,ol),.md-typeset ul li :-moz-any(ul,ol){margin-bottom:.5em;margin-top:.5em}[dir=ltr] .md-typeset ol li :-webkit-any(ul,ol),[dir=ltr] .md-typeset ul li :-webkit-any(ul,ol){margin-left:.625em}[dir=ltr] .md-typeset ol li :-moz-any(ul,ol),[dir=ltr] .md-typeset ul li :-moz-any(ul,ol){margin-left:.625em}[dir=ltr] .md-typeset ol li :is(ul,ol),[dir=ltr] .md-typeset ul li :is(ul,ol){margin-left:.625em}[dir=rtl] .md-typeset ol li :-webkit-any(ul,ol),[dir=rtl] .md-typeset ul li :-webkit-any(ul,ol){margin-right:.625em}[dir=rtl] .md-typeset ol li :-moz-any(ul,ol),[dir=rtl] .md-typeset ul li :-moz-any(ul,ol){margin-right:.625em}[dir=rtl] .md-typeset ol li :is(ul,ol),[dir=rtl] .md-typeset ul li :is(ul,ol){margin-right:.625em}.md-typeset ol li :is(ul,ol),.md-typeset ul li :is(ul,ol){margin-bottom:.5em;margin-top:.5em}[dir=ltr] .md-typeset dd{margin-left:1.875em}[dir=rtl] .md-typeset dd{margin-right:1.875em}.md-typeset dd{margin-bottom:1.5em;margin-top:1em}.md-typeset img,.md-typeset svg,.md-typeset video{height:auto;max-width:100%}.md-typeset img[align=left]{margin:1em 1em 1em 0}.md-typeset img[align=right]{margin:1em 0 1em 1em}.md-typeset img[align]:only-child{margin-top:0}.md-typeset img[src$="#gh-dark-mode-only"],.md-typeset img[src$="#only-dark"]{display:none}.md-typeset figure{display:flow-root;margin:1em auto;max-width:100%;text-align:center;width:-webkit-fit-content;width:-moz-fit-content;width:fit-content}.md-typeset figure img{display:block}.md-typeset figcaption{font-style:italic;margin:1em auto;max-width:24rem}.md-typeset iframe{max-width:100%}.md-typeset table:not([class]){background-color:var(--md-default-bg-color);border:.05rem solid var(--md-typeset-table-color);border-radius:.1rem;display:inline-block;font-size:.64rem;max-width:100%;overflow:auto;touch-action:auto}@media print{.md-typeset table:not([class]){display:table}}.md-typeset table:not([class])+*{margin-top:1.5em}.md-typeset table:not([class]) :-webkit-any(th,td)>:first-child{margin-top:0}.md-typeset table:not([class]) :-moz-any(th,td)>:first-child{margin-top:0}.md-typeset table:not([class]) :is(th,td)>:first-child{margin-top:0}.md-typeset table:not([class]) :-webkit-any(th,td)>:last-child{margin-bottom:0}.md-typeset table:not([class]) :-moz-any(th,td)>:last-child{margin-bottom:0}.md-typeset table:not([class]) :is(th,td)>:last-child{margin-bottom:0}.md-typeset table:not([class]) :-webkit-any(th,td):not([align]){text-align:left}.md-typeset table:not([class]) :-moz-any(th,td):not([align]){text-align:left}.md-typeset table:not([class]) :is(th,td):not([align]){text-align:left}[dir=rtl] .md-typeset table:not([class]) :-webkit-any(th,td):not([align]){text-align:right}[dir=rtl] .md-typeset table:not([class]) :-moz-any(th,td):not([align]){text-align:right}[dir=rtl] .md-typeset table:not([class]) :is(th,td):not([align]){text-align:right}.md-typeset table:not([class]) th{font-weight:700;min-width:5rem;padding:.9375em 1.25em;vertical-align:top}.md-typeset table:not([class]) th a{color:inherit}.md-typeset table:not([class]) td{border-top:.05rem solid var(--md-typeset-table-color);padding:.9375em 1.25em;vertical-align:top}.md-typeset table:not([class]) tbody tr{transition:background-color 125ms}.md-typeset table:not([class]) tbody tr:hover{background-color:rgba(0,0,0,.035);box-shadow:0 .05rem 0 var(--md-default-bg-color) inset}.md-typeset table:not([class]) a{word-break:normal}.md-typeset table th[role=columnheader]{cursor:pointer}[dir=ltr] .md-typeset table th[role=columnheader]:after{margin-left:.5em}[dir=rtl] .md-typeset table th[role=columnheader]:after{margin-right:.5em}.md-typeset table th[role=columnheader]:after{content:"";display:inline-block;height:1.2em;-webkit-mask-image:var(--md-typeset-table-sort-icon);mask-image:var(--md-typeset-table-sort-icon);-webkit-mask-repeat:no-repeat;mask-repeat:no-repeat;-webkit-mask-size:contain;mask-size:contain;transition:background-color 125ms;vertical-align:text-bottom;width:1.2em}.md-typeset table th[role=columnheader]:hover:after{background-color:var(--md-default-fg-color--lighter)}.md-typeset table th[role=columnheader][aria-sort=ascending]:after{background-color:var(--md-default-fg-color--light);-webkit-mask-image:var(--md-typeset-table-sort-icon--asc);mask-image:var(--md-typeset-table-sort-icon--asc)}.md-typeset table th[role=columnheader][aria-sort=descending]:after{background-color:var(--md-default-fg-color--light);-webkit-mask-image:var(--md-typeset-table-sort-icon--desc);mask-image:var(--md-typeset-table-sort-icon--desc)}.md-typeset__scrollwrap{margin:1em -.8rem;overflow-x:auto;touch-action:auto}.md-typeset__table{display:inline-block;margin-bottom:.5em;padding:0 .8rem}@media print{.md-typeset__table{display:block}}html .md-typeset__table table{display:table;margin:0;overflow:hidden;width:100%}@media screen and (max-width:44.9375em){.md-content__inner>pre{margin:1em -.8rem}.md-content__inner>pre code{border-radius:0}}.md-banner{background-color:var(--md-footer-bg-color);color:var(--md-footer-fg-color);overflow:auto}@media print{.md-banner{display:none}}.md-banner--warning{background:var(--md-typeset-mark-color);color:var(--md-default-fg-color)}.md-banner__inner{font-size:.7rem;margin:.6rem auto;padding:0 .8rem}html{font-size:125%;height:100%;overflow-x:hidden}@media screen and (min-width:100em){html{font-size:137.5%}}@media screen and (min-width:125em){html{font-size:150%}}body{background-color:var(--md-default-bg-color);display:flex;flex-direction:column;font-size:.5rem;min-height:100%;position:relative;width:100%}@media print{body{display:block}}@media screen and (max-width:59.9375em){body[data-md-scrolllock]{position:fixed}}.md-grid{margin-left:auto;margin-right:auto;max-width:61rem}.md-container{display:flex;flex-direction:column;flex-grow:1}@media print{.md-container{display:block}}.md-main{flex-grow:1}.md-main__inner{display:flex;height:100%;margin-top:1.5rem}.md-ellipsis{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.md-toggle{display:none}.md-option{height:0;opacity:0;position:absolute;width:0}.md-option:checked+label:not([hidden]){display:block}.md-option.focus-visible+label{outline-color:var(--md-accent-fg-color);outline-style:auto}.md-skip{background-color:var(--md-default-fg-color);border-radius:.1rem;color:var(--md-default-bg-color);font-size:.64rem;margin:.5rem;opacity:0;outline-color:var(--md-accent-fg-color);padding:.3rem .5rem;position:fixed;transform:translateY(.4rem);z-index:-1}.md-skip:focus{opacity:1;transform:translateY(0);transition:transform .25s cubic-bezier(.4,0,.2,1),opacity 175ms 75ms;z-index:10}@page{margin:25mm}:root{--md-clipboard-icon:url('data:image/svg+xml;charset=utf-8,')}.md-clipboard{border-radius:.1rem;color:var(--md-default-fg-color--lightest);cursor:pointer;height:1.5em;outline-color:var(--md-accent-fg-color);outline-offset:.1rem;position:absolute;right:.5em;top:.5em;transition:color .25s;width:1.5em;z-index:1}@media print{.md-clipboard{display:none}}.md-clipboard:not(.focus-visible){-webkit-tap-highlight-color:transparent;outline:none}:hover>.md-clipboard{color:var(--md-default-fg-color--light)}.md-clipboard:-webkit-any(:focus,:hover){color:var(--md-accent-fg-color)}.md-clipboard:-moz-any(:focus,:hover){color:var(--md-accent-fg-color)}.md-clipboard:is(:focus,:hover){color:var(--md-accent-fg-color)}.md-clipboard:after{background-color:currentcolor;content:"";display:block;height:1.125em;margin:0 auto;-webkit-mask-image:var(--md-clipboard-icon);mask-image:var(--md-clipboard-icon);-webkit-mask-repeat:no-repeat;mask-repeat:no-repeat;-webkit-mask-size:contain;mask-size:contain;width:1.125em}.md-clipboard--inline{cursor:pointer}.md-clipboard--inline code{transition:color .25s,background-color .25s}.md-clipboard--inline:-webkit-any(:focus,:hover) code{background-color:var(--md-accent-fg-color--transparent);color:var(--md-accent-fg-color)}.md-clipboard--inline:-moz-any(:focus,:hover) code{background-color:var(--md-accent-fg-color--transparent);color:var(--md-accent-fg-color)}.md-clipboard--inline:is(:focus,:hover) code{background-color:var(--md-accent-fg-color--transparent);color:var(--md-accent-fg-color)}.md-content{flex-grow:1;min-width:0}.md-content__inner{margin:0 .8rem 1.2rem;padding-top:.6rem}@media screen and (min-width:76.25em){[dir=ltr] .md-sidebar--primary:not([hidden])~.md-content>.md-content__inner{margin-left:1.2rem}[dir=ltr] .md-sidebar--secondary:not([hidden])~.md-content>.md-content__inner,[dir=rtl] .md-sidebar--primary:not([hidden])~.md-content>.md-content__inner{margin-right:1.2rem}[dir=rtl] .md-sidebar--secondary:not([hidden])~.md-content>.md-content__inner{margin-left:1.2rem}}.md-content__inner:before{content:"";display:block;height:.4rem}.md-content__inner>:last-child{margin-bottom:0}[dir=ltr] .md-content__button{margin-left:.4rem}[dir=rtl] .md-content__button{margin-right:.4rem}.md-content__button{float:right;margin:.4rem 0;padding:0}@media print{.md-content__button{display:none}}[dir=rtl] .md-content__button{float:left}.md-typeset .md-content__button{color:var(--md-default-fg-color--lighter)}.md-content__button svg{display:inline;vertical-align:top}[dir=rtl] .md-content__button svg{transform:scaleX(-1)}[dir=ltr] .md-dialog{right:.8rem}[dir=rtl] .md-dialog{left:.8rem}.md-dialog{background-color:var(--md-default-fg-color);border-radius:.1rem;bottom:.8rem;box-shadow:var(--md-shadow-z3);min-width:11.1rem;opacity:0;padding:.4rem .6rem;pointer-events:none;position:fixed;transform:translateY(100%);transition:transform 0ms .4s,opacity .4s;z-index:4}@media print{.md-dialog{display:none}}.md-dialog--active{opacity:1;pointer-events:auto;transform:translateY(0);transition:transform .4s cubic-bezier(.075,.85,.175,1),opacity .4s}.md-dialog__inner{color:var(--md-default-bg-color);font-size:.7rem}.md-footer{background-color:var(--md-footer-bg-color);color:var(--md-footer-fg-color)}@media print{.md-footer{display:none}}.md-footer__inner{justify-content:space-between;overflow:auto;padding:.2rem}.md-footer__inner:not([hidden]){display:flex}.md-footer__link{display:flex;flex-grow:0.01;outline-color:var(--md-accent-fg-color);overflow:hidden;padding-bottom:.4rem;padding-top:1.4rem;transition:opacity .25s}.md-footer__link:-webkit-any(:focus,:hover){opacity:.7}.md-footer__link:-moz-any(:focus,:hover){opacity:.7}.md-footer__link:is(:focus,:hover){opacity:.7}[dir=rtl] .md-footer__link svg{transform:scaleX(-1)}@media screen and (max-width:44.9375em){.md-footer__link--prev .md-footer__title{display:none}}[dir=ltr] .md-footer__link--next{margin-left:auto}[dir=rtl] .md-footer__link--next{margin-right:auto}.md-footer__link--next{text-align:right}[dir=rtl] .md-footer__link--next{text-align:left}.md-footer__title{flex-grow:1;font-size:.9rem;line-height:2.4rem;max-width:calc(100% - 2.4rem);padding:0 1rem;position:relative;white-space:nowrap}.md-footer__button{margin:.2rem;padding:.4rem}.md-footer__direction{font-size:.64rem;left:0;margin-top:-1rem;opacity:.7;padding:0 1rem;position:absolute;right:0}.md-footer-meta{background-color:var(--md-footer-bg-color--dark)}.md-footer-meta__inner{display:flex;flex-wrap:wrap;justify-content:space-between;padding:.2rem}html .md-footer-meta.md-typeset a{color:var(--md-footer-fg-color--light)}html .md-footer-meta.md-typeset a:-webkit-any(:focus,:hover){color:var(--md-footer-fg-color)}html .md-footer-meta.md-typeset a:-moz-any(:focus,:hover){color:var(--md-footer-fg-color)}html .md-footer-meta.md-typeset a:is(:focus,:hover){color:var(--md-footer-fg-color)}.md-copyright{color:var(--md-footer-fg-color--lighter);font-size:.64rem;margin:auto .6rem;padding:.4rem 0;width:100%}@media screen and (min-width:45em){.md-copyright{width:auto}}.md-copyright__highlight{color:var(--md-footer-fg-color--light)}.md-social{margin:0 .4rem;padding:.2rem 0 .6rem}@media screen and (min-width:45em){.md-social{padding:.6rem 0}}.md-social__link{display:inline-block;height:1.6rem;text-align:center;width:1.6rem}.md-social__link:before{line-height:1.9}.md-social__link svg{fill:currentcolor;max-height:.8rem;vertical-align:-25%}.md-typeset .md-button{border:.1rem solid;border-radius:.1rem;color:var(--md-primary-fg-color);cursor:pointer;display:inline-block;font-weight:700;padding:.625em 2em;transition:color 125ms,background-color 125ms,border-color 125ms}.md-typeset .md-button--primary{background-color:var(--md-primary-fg-color);border-color:var(--md-primary-fg-color);color:var(--md-primary-bg-color)}.md-typeset .md-button:-webkit-any(:focus,:hover){background-color:var(--md-accent-fg-color);border-color:var(--md-accent-fg-color);color:var(--md-accent-bg-color)}.md-typeset .md-button:-moz-any(:focus,:hover){background-color:var(--md-accent-fg-color);border-color:var(--md-accent-fg-color);color:var(--md-accent-bg-color)}.md-typeset .md-button:is(:focus,:hover){background-color:var(--md-accent-fg-color);border-color:var(--md-accent-fg-color);color:var(--md-accent-bg-color)}[dir=ltr] .md-typeset .md-input{border-top-left-radius:.1rem}[dir=ltr] .md-typeset .md-input,[dir=rtl] .md-typeset .md-input{border-top-right-radius:.1rem}[dir=rtl] .md-typeset .md-input{border-top-left-radius:.1rem}.md-typeset .md-input{border-bottom:.1rem solid var(--md-default-fg-color--lighter);box-shadow:var(--md-shadow-z1);font-size:.8rem;height:1.8rem;padding:0 .6rem;transition:border .25s,box-shadow .25s}.md-typeset .md-input:-webkit-any(:focus,:hover){border-bottom-color:var(--md-accent-fg-color);box-shadow:var(--md-shadow-z2)}.md-typeset .md-input:-moz-any(:focus,:hover){border-bottom-color:var(--md-accent-fg-color);box-shadow:var(--md-shadow-z2)}.md-typeset .md-input:is(:focus,:hover){border-bottom-color:var(--md-accent-fg-color);box-shadow:var(--md-shadow-z2)}.md-typeset .md-input--stretch{width:100%}.md-header{background-color:var(--md-primary-fg-color);box-shadow:0 0 .2rem transparent,0 .2rem .4rem transparent;color:var(--md-primary-bg-color);display:block;left:0;position:-webkit-sticky;position:sticky;right:0;top:0;z-index:4}@media print{.md-header{display:none}}.md-header[hidden]{transform:translateY(-100%);transition:transform .25s cubic-bezier(.8,0,.6,1),box-shadow .25s}.md-header--shadow{box-shadow:0 0 .2rem rgba(0,0,0,.1),0 .2rem .4rem rgba(0,0,0,.2);transition:transform .25s cubic-bezier(.1,.7,.1,1),box-shadow .25s}.md-header__inner{align-items:center;display:flex;padding:0 .2rem}.md-header__button{color:currentcolor;cursor:pointer;margin:.2rem;outline-color:var(--md-accent-fg-color);padding:.4rem;position:relative;transition:opacity .25s;vertical-align:middle;z-index:1}.md-header__button:hover{opacity:.7}.md-header__button:not([hidden]){display:inline-block}.md-header__button:not(.focus-visible){-webkit-tap-highlight-color:transparent;outline:none}.md-header__button.md-logo{margin:.2rem;padding:.4rem}@media screen and (max-width:76.1875em){.md-header__button.md-logo{display:none}}.md-header__button.md-logo :-webkit-any(img,svg){fill:currentcolor;display:block;height:1.2rem;width:auto}.md-header__button.md-logo :-moz-any(img,svg){fill:currentcolor;display:block;height:1.2rem;width:auto}.md-header__button.md-logo :is(img,svg){fill:currentcolor;display:block;height:1.2rem;width:auto}@media screen and (min-width:60em){.md-header__button[for=__search]{display:none}}.no-js .md-header__button[for=__search]{display:none}[dir=rtl] .md-header__button[for=__search] svg{transform:scaleX(-1)}@media screen and (min-width:76.25em){.md-header__button[for=__drawer]{display:none}}.md-header__topic{display:flex;max-width:100%;position:absolute;transition:transform .4s cubic-bezier(.1,.7,.1,1),opacity .15s;white-space:nowrap}.md-header__topic+.md-header__topic{opacity:0;pointer-events:none;transform:translateX(1.25rem);transition:transform .4s cubic-bezier(1,.7,.1,.1),opacity .15s;z-index:-1}[dir=rtl] .md-header__topic+.md-header__topic{transform:translateX(-1.25rem)}.md-header__topic:first-child{font-weight:700}[dir=ltr] .md-header__title{margin-right:.4rem}[dir=rtl] .md-header__title{margin-left:.4rem}[dir=ltr] .md-header__title{margin-left:1rem}[dir=rtl] .md-header__title{margin-right:1rem}.md-header__title{flex-grow:1;font-size:.9rem;height:2.4rem;line-height:2.4rem}.md-header__title--active .md-header__topic{opacity:0;pointer-events:none;transform:translateX(-1.25rem);transition:transform .4s cubic-bezier(1,.7,.1,.1),opacity .15s;z-index:-1}[dir=rtl] .md-header__title--active .md-header__topic{transform:translateX(1.25rem)}.md-header__title--active .md-header__topic+.md-header__topic{opacity:1;pointer-events:auto;transform:translateX(0);transition:transform .4s cubic-bezier(.1,.7,.1,1),opacity .15s;z-index:0}.md-header__title>.md-header__ellipsis{height:100%;position:relative;width:100%}.md-header__option{display:flex;flex-shrink:0;max-width:100%;transition:max-width 0ms .25s,opacity .25s .25s;white-space:nowrap}[data-md-toggle=search]:checked~.md-header .md-header__option{max-width:0;opacity:0;transition:max-width 0ms,opacity 0ms}.md-header__source{display:none}@media screen and (min-width:60em){[dir=ltr] .md-header__source{margin-left:1rem}[dir=rtl] .md-header__source{margin-right:1rem}.md-header__source{display:block;max-width:11.7rem;width:11.7rem}}@media screen and (min-width:76.25em){[dir=ltr] .md-header__source{margin-left:1.4rem}[dir=rtl] .md-header__source{margin-right:1.4rem}}:root{--md-nav-icon--prev:url('data:image/svg+xml;charset=utf-8,');--md-nav-icon--next:url('data:image/svg+xml;charset=utf-8,');--md-toc-icon:url('data:image/svg+xml;charset=utf-8,')}.md-nav{font-size:.7rem;line-height:1.3}.md-nav__title{display:block;font-weight:700;overflow:hidden;padding:0 .6rem;text-overflow:ellipsis}.md-nav__title .md-nav__button{display:none}.md-nav__title .md-nav__button img{height:100%;width:auto}.md-nav__title .md-nav__button.md-logo :-webkit-any(img,svg){fill:currentcolor;display:block;height:2.4rem;max-width:100%;object-fit:contain;width:auto}.md-nav__title .md-nav__button.md-logo :-moz-any(img,svg){fill:currentcolor;display:block;height:2.4rem;max-width:100%;object-fit:contain;width:auto}.md-nav__title .md-nav__button.md-logo :is(img,svg){fill:currentcolor;display:block;height:2.4rem;max-width:100%;object-fit:contain;width:auto}.md-nav__list{list-style:none;margin:0;padding:0}.md-nav__item{padding:0 .6rem}[dir=ltr] .md-nav__item .md-nav__item{padding-right:0}[dir=rtl] .md-nav__item .md-nav__item{padding-left:0}.md-nav__link{align-items:center;cursor:pointer;display:flex;justify-content:space-between;margin-top:.625em;overflow:hidden;scroll-snap-align:start;text-overflow:ellipsis;transition:color 125ms}.md-nav__link--passed{color:var(--md-default-fg-color--light)}.md-nav__item .md-nav__link--active{color:var(--md-typeset-a-color)}.md-nav__item .md-nav__link--index [href]{width:100%}.md-nav__link:-webkit-any(:focus,:hover){color:var(--md-accent-fg-color)}.md-nav__link:-moz-any(:focus,:hover){color:var(--md-accent-fg-color)}.md-nav__link:is(:focus,:hover){color:var(--md-accent-fg-color)}.md-nav__link.focus-visible{outline-color:var(--md-accent-fg-color);outline-offset:.2rem}.md-nav--primary .md-nav__link[for=__toc]{display:none}.md-nav--primary .md-nav__link[for=__toc] .md-icon:after{background-color:currentcolor;display:block;height:100%;-webkit-mask-image:var(--md-toc-icon);mask-image:var(--md-toc-icon);width:100%}.md-nav--primary .md-nav__link[for=__toc]~.md-nav{display:none}.md-nav__link>*{cursor:pointer;display:flex}.md-nav__icon{flex-shrink:0}.md-nav__source{display:none}@media screen and (max-width:76.1875em){.md-nav--primary,.md-nav--primary .md-nav{background-color:var(--md-default-bg-color);display:flex;flex-direction:column;height:100%;left:0;position:absolute;right:0;top:0;z-index:1}.md-nav--primary :-webkit-any(.md-nav__title,.md-nav__item){font-size:.8rem;line-height:1.5}.md-nav--primary :-moz-any(.md-nav__title,.md-nav__item){font-size:.8rem;line-height:1.5}.md-nav--primary :is(.md-nav__title,.md-nav__item){font-size:.8rem;line-height:1.5}.md-nav--primary .md-nav__title{background-color:var(--md-default-fg-color--lightest);color:var(--md-default-fg-color--light);cursor:pointer;height:5.6rem;line-height:2.4rem;padding:3rem .8rem .2rem;position:relative;white-space:nowrap}[dir=ltr] .md-nav--primary .md-nav__title .md-nav__icon{left:.4rem}[dir=rtl] .md-nav--primary .md-nav__title .md-nav__icon{right:.4rem}.md-nav--primary .md-nav__title .md-nav__icon{display:block;height:1.2rem;margin:.2rem;position:absolute;top:.4rem;width:1.2rem}.md-nav--primary .md-nav__title .md-nav__icon:after{background-color:currentcolor;content:"";display:block;height:100%;-webkit-mask-image:var(--md-nav-icon--prev);mask-image:var(--md-nav-icon--prev);-webkit-mask-repeat:no-repeat;mask-repeat:no-repeat;-webkit-mask-size:contain;mask-size:contain;width:100%}.md-nav--primary .md-nav__title~.md-nav__list{background-color:var(--md-default-bg-color);box-shadow:0 .05rem 0 var(--md-default-fg-color--lightest) inset;overflow-y:auto;-ms-scroll-snap-type:y mandatory;scroll-snap-type:y mandatory;touch-action:pan-y}.md-nav--primary .md-nav__title~.md-nav__list>:first-child{border-top:0}.md-nav--primary .md-nav__title[for=__drawer]{background-color:var(--md-primary-fg-color);color:var(--md-primary-bg-color);font-weight:700}.md-nav--primary .md-nav__title .md-logo{display:block;left:.2rem;margin:.2rem;padding:.4rem;position:absolute;right:.2rem;top:.2rem}.md-nav--primary .md-nav__list{flex:1}.md-nav--primary .md-nav__item{border-top:.05rem solid var(--md-default-fg-color--lightest);padding:0}.md-nav--primary .md-nav__item--active>.md-nav__link{color:var(--md-typeset-a-color)}.md-nav--primary .md-nav__item--active>.md-nav__link:-webkit-any(:focus,:hover){color:var(--md-accent-fg-color)}.md-nav--primary .md-nav__item--active>.md-nav__link:-moz-any(:focus,:hover){color:var(--md-accent-fg-color)}.md-nav--primary .md-nav__item--active>.md-nav__link:is(:focus,:hover){color:var(--md-accent-fg-color)}.md-nav--primary .md-nav__link{margin-top:0;padding:.6rem .8rem}[dir=ltr] .md-nav--primary .md-nav__link .md-nav__icon{margin-right:-.2rem}[dir=rtl] .md-nav--primary .md-nav__link .md-nav__icon{margin-left:-.2rem}.md-nav--primary .md-nav__link .md-nav__icon{font-size:1.2rem;height:1.2rem;width:1.2rem}.md-nav--primary .md-nav__link .md-nav__icon:after{background-color:currentcolor;content:"";display:block;height:100%;-webkit-mask-image:var(--md-nav-icon--next);mask-image:var(--md-nav-icon--next);-webkit-mask-repeat:no-repeat;mask-repeat:no-repeat;-webkit-mask-size:contain;mask-size:contain;width:100%}[dir=rtl] .md-nav--primary .md-nav__icon:after{transform:scale(-1)}.md-nav--primary .md-nav--secondary .md-nav{background-color:initial;position:static}[dir=ltr] .md-nav--primary .md-nav--secondary .md-nav .md-nav__link{padding-left:1.4rem}[dir=rtl] .md-nav--primary .md-nav--secondary .md-nav .md-nav__link{padding-right:1.4rem}[dir=ltr] .md-nav--primary .md-nav--secondary .md-nav .md-nav .md-nav__link{padding-left:2rem}[dir=rtl] .md-nav--primary .md-nav--secondary .md-nav .md-nav .md-nav__link{padding-right:2rem}[dir=ltr] .md-nav--primary .md-nav--secondary .md-nav .md-nav .md-nav .md-nav__link{padding-left:2.6rem}[dir=rtl] .md-nav--primary .md-nav--secondary .md-nav .md-nav .md-nav .md-nav__link{padding-right:2.6rem}[dir=ltr] .md-nav--primary .md-nav--secondary .md-nav .md-nav .md-nav .md-nav .md-nav__link{padding-left:3.2rem}[dir=rtl] .md-nav--primary .md-nav--secondary .md-nav .md-nav .md-nav .md-nav .md-nav__link{padding-right:3.2rem}.md-nav--secondary{background-color:initial}.md-nav__toggle~.md-nav{display:flex;opacity:0;transform:translateX(100%);transition:transform .25s cubic-bezier(.8,0,.6,1),opacity 125ms 50ms}[dir=rtl] .md-nav__toggle~.md-nav{transform:translateX(-100%)}.md-nav__toggle:checked~.md-nav{opacity:1;transform:translateX(0);transition:transform .25s cubic-bezier(.4,0,.2,1),opacity 125ms 125ms}.md-nav__toggle:checked~.md-nav>.md-nav__list{-webkit-backface-visibility:hidden;backface-visibility:hidden}}@media screen and (max-width:59.9375em){.md-nav--primary .md-nav__link[for=__toc]{display:flex}.md-nav--primary .md-nav__link[for=__toc] .md-icon:after{content:""}.md-nav--primary .md-nav__link[for=__toc]+.md-nav__link{display:none}.md-nav--primary .md-nav__link[for=__toc]~.md-nav{display:flex}.md-nav__source{background-color:var(--md-primary-fg-color--dark);color:var(--md-primary-bg-color);display:block;padding:0 .2rem}}@media screen and (min-width:60em) and (max-width:76.1875em){.md-nav--integrated .md-nav__link[for=__toc]{display:flex}.md-nav--integrated .md-nav__link[for=__toc] .md-icon:after{content:""}.md-nav--integrated .md-nav__link[for=__toc]+.md-nav__link{display:none}.md-nav--integrated .md-nav__link[for=__toc]~.md-nav{display:flex}}@media screen and (min-width:60em){.md-nav--secondary .md-nav__title[for=__toc]{scroll-snap-align:start}.md-nav--secondary .md-nav__title .md-nav__icon{display:none}}@media screen and (min-width:76.25em){.md-nav{transition:max-height .25s cubic-bezier(.86,0,.07,1)}.md-nav--primary .md-nav__title[for=__drawer]{scroll-snap-align:start}.md-nav--primary .md-nav__title .md-nav__icon,.md-nav__toggle~.md-nav{display:none}.md-nav__toggle:-webkit-any(:checked,:indeterminate)~.md-nav{display:block}.md-nav__toggle:-moz-any(:checked,:indeterminate)~.md-nav{display:block}.md-nav__toggle:is(:checked,:indeterminate)~.md-nav{display:block}.md-nav__item--nested>.md-nav>.md-nav__title{display:none}.md-nav__item--section{display:block;margin:1.25em 0}.md-nav__item--section:last-child{margin-bottom:0}.md-nav__item--section>.md-nav__link{font-weight:700;pointer-events:none}.md-nav__item--section>.md-nav__link--index [href]{pointer-events:auto}.md-nav__item--section>.md-nav__link .md-nav__icon{display:none}.md-nav__item--section>.md-nav{display:block}.md-nav__item--section>.md-nav>.md-nav__list>.md-nav__item{padding:0}.md-nav__icon{border-radius:100%;float:right;height:.9rem;transition:background-color .25s,transform .25s;width:.9rem}[dir=rtl] .md-nav__icon{float:left;transform:rotate(180deg)}.md-nav__icon:hover{background-color:var(--md-accent-fg-color--transparent)}.md-nav__icon:after{background-color:currentcolor;content:"";display:inline-block;height:100%;-webkit-mask-image:var(--md-nav-icon--next);mask-image:var(--md-nav-icon--next);-webkit-mask-repeat:no-repeat;mask-repeat:no-repeat;-webkit-mask-size:contain;mask-size:contain;vertical-align:-.1rem;width:100%}.md-nav__item--nested .md-nav__toggle:checked~.md-nav__link .md-nav__icon,.md-nav__item--nested .md-nav__toggle:indeterminate~.md-nav__link .md-nav__icon{transform:rotate(90deg)}.md-nav--lifted>.md-nav__list>.md-nav__item,.md-nav--lifted>.md-nav__list>.md-nav__item--nested,.md-nav--lifted>.md-nav__title{display:none}.md-nav--lifted>.md-nav__list>.md-nav__item--active{display:block;padding:0}.md-nav--lifted>.md-nav__list>.md-nav__item--active>.md-nav__link{font-weight:700;margin-top:0;padding:0 .6rem;pointer-events:none}.md-nav--lifted>.md-nav__list>.md-nav__item--active>.md-nav__link--index [href]{pointer-events:auto}.md-nav--lifted>.md-nav__list>.md-nav__item--active>.md-nav__link .md-nav__icon{display:none}.md-nav--lifted .md-nav[data-md-level="1"]{display:block}[dir=ltr] .md-nav--lifted .md-nav[data-md-level="1"]>.md-nav__list>.md-nav__item{padding-right:.6rem}[dir=rtl] .md-nav--lifted .md-nav[data-md-level="1"]>.md-nav__list>.md-nav__item{padding-left:.6rem}.md-nav--integrated>.md-nav__list>.md-nav__item--active:not(.md-nav__item--nested){padding:0 .6rem}.md-nav--integrated>.md-nav__list>.md-nav__item--active:not(.md-nav__item--nested)>.md-nav__link{padding:0}[dir=ltr] .md-nav--integrated>.md-nav__list>.md-nav__item--active .md-nav--secondary{border-left:.05rem solid var(--md-primary-fg-color)}[dir=rtl] .md-nav--integrated>.md-nav__list>.md-nav__item--active .md-nav--secondary{border-right:.05rem solid var(--md-primary-fg-color)}.md-nav--integrated>.md-nav__list>.md-nav__item--active .md-nav--secondary{display:block;margin-bottom:1.25em}.md-nav--integrated>.md-nav__list>.md-nav__item--active .md-nav--secondary>.md-nav__title{display:none}}:root{--md-search-result-icon:url('data:image/svg+xml;charset=utf-8,')}.md-search{position:relative}@media screen and (min-width:60em){.md-search{padding:.2rem 0}}.no-js .md-search{display:none}.md-search__overlay{opacity:0;z-index:1}@media screen and (max-width:59.9375em){[dir=ltr] .md-search__overlay{left:-2.2rem}[dir=rtl] .md-search__overlay{right:-2.2rem}.md-search__overlay{background-color:var(--md-default-bg-color);border-radius:1rem;height:2rem;overflow:hidden;pointer-events:none;position:absolute;top:-1rem;transform-origin:center;transition:transform .3s .1s,opacity .2s .2s;width:2rem}[data-md-toggle=search]:checked~.md-header .md-search__overlay{opacity:1;transition:transform .4s,opacity .1s}}@media screen and (min-width:60em){[dir=ltr] .md-search__overlay{left:0}[dir=rtl] .md-search__overlay{right:0}.md-search__overlay{background-color:rgba(0,0,0,.54);cursor:pointer;height:0;position:fixed;top:0;transition:width 0ms .25s,height 0ms .25s,opacity .25s;width:0}[data-md-toggle=search]:checked~.md-header .md-search__overlay{height:200vh;opacity:1;transition:width 0ms,height 0ms,opacity .25s;width:100%}}@media screen and (max-width:29.9375em){[data-md-toggle=search]:checked~.md-header .md-search__overlay{transform:scale(45)}}@media screen and (min-width:30em) and (max-width:44.9375em){[data-md-toggle=search]:checked~.md-header .md-search__overlay{transform:scale(60)}}@media screen and (min-width:45em) and (max-width:59.9375em){[data-md-toggle=search]:checked~.md-header .md-search__overlay{transform:scale(75)}}.md-search__inner{-webkit-backface-visibility:hidden;backface-visibility:hidden}@media screen and (max-width:59.9375em){[dir=ltr] .md-search__inner{left:0}[dir=rtl] .md-search__inner{right:0}.md-search__inner{height:0;opacity:0;overflow:hidden;position:fixed;top:0;transform:translateX(5%);transition:width 0ms .3s,height 0ms .3s,transform .15s cubic-bezier(.4,0,.2,1) .15s,opacity .15s .15s;width:0;z-index:2}[dir=rtl] .md-search__inner{transform:translateX(-5%)}[data-md-toggle=search]:checked~.md-header .md-search__inner{height:100%;opacity:1;transform:translateX(0);transition:width 0ms 0ms,height 0ms 0ms,transform .15s cubic-bezier(.1,.7,.1,1) .15s,opacity .15s .15s;width:100%}}@media screen and (min-width:60em){.md-search__inner{float:right;padding:.1rem 0;position:relative;transition:width .25s cubic-bezier(.1,.7,.1,1);width:11.7rem}[dir=rtl] .md-search__inner{float:left}}@media screen and (min-width:60em) and (max-width:76.1875em){[data-md-toggle=search]:checked~.md-header .md-search__inner{width:23.4rem}}@media screen and (min-width:76.25em){[data-md-toggle=search]:checked~.md-header .md-search__inner{width:34.4rem}}.md-search__form{background-color:var(--md-default-bg-color);box-shadow:0 0 .6rem transparent;height:2.4rem;position:relative;transition:color .25s,background-color .25s;z-index:2}@media screen and (min-width:60em){.md-search__form{background-color:rgba(0,0,0,.26);border-radius:.1rem;height:1.8rem}.md-search__form:hover{background-color:hsla(0,0%,100%,.12)}}[data-md-toggle=search]:checked~.md-header .md-search__form{background-color:var(--md-default-bg-color);border-radius:.1rem .1rem 0 0;box-shadow:0 0 .6rem rgba(0,0,0,.07);color:var(--md-default-fg-color)}[dir=ltr] .md-search__input{padding-left:3.6rem;padding-right:2.2rem}[dir=rtl] .md-search__input{padding-left:2.2rem;padding-right:3.6rem}.md-search__input{background:transparent;font-size:.9rem;height:100%;position:relative;text-overflow:ellipsis;width:100%;z-index:2}.md-search__input::-ms-input-placeholder{-ms-transition:color .25s;transition:color .25s}.md-search__input::placeholder{transition:color .25s}.md-search__input::-ms-input-placeholder{color:var(--md-default-fg-color--light)}.md-search__input::placeholder,.md-search__input~.md-search__icon{color:var(--md-default-fg-color--light)}.md-search__input::-ms-clear{display:none}@media screen and (max-width:59.9375em){.md-search__input{font-size:.9rem;height:2.4rem;width:100%}}@media screen and (min-width:60em){[dir=ltr] .md-search__input{padding-left:2.2rem}[dir=rtl] .md-search__input{padding-right:2.2rem}.md-search__input{color:inherit;font-size:.8rem}.md-search__input::-ms-input-placeholder{color:var(--md-primary-bg-color--light)}.md-search__input::placeholder{color:var(--md-primary-bg-color--light)}.md-search__input+.md-search__icon{color:var(--md-primary-bg-color)}[data-md-toggle=search]:checked~.md-header .md-search__input{text-overflow:clip}[data-md-toggle=search]:checked~.md-header .md-search__input::-ms-input-placeholder{color:var(--md-default-fg-color--light)}[data-md-toggle=search]:checked~.md-header .md-search__input+.md-search__icon,[data-md-toggle=search]:checked~.md-header .md-search__input::placeholder{color:var(--md-default-fg-color--light)}}.md-search__icon{cursor:pointer;display:inline-block;height:1.2rem;transition:color .25s,opacity .25s;width:1.2rem}.md-search__icon:hover{opacity:.7}[dir=ltr] .md-search__icon[for=__search]{left:.5rem}[dir=rtl] .md-search__icon[for=__search]{right:.5rem}.md-search__icon[for=__search]{position:absolute;top:.3rem;z-index:2}[dir=rtl] .md-search__icon[for=__search] svg{transform:scaleX(-1)}@media screen and (max-width:59.9375em){[dir=ltr] .md-search__icon[for=__search]{left:.8rem}[dir=rtl] .md-search__icon[for=__search]{right:.8rem}.md-search__icon[for=__search]{top:.6rem}.md-search__icon[for=__search] svg:first-child{display:none}}@media screen and (min-width:60em){.md-search__icon[for=__search]{pointer-events:none}.md-search__icon[for=__search] svg:last-child{display:none}}[dir=ltr] .md-search__options{right:.5rem}[dir=rtl] .md-search__options{left:.5rem}.md-search__options{pointer-events:none;position:absolute;top:.3rem;z-index:2}@media screen and (max-width:59.9375em){[dir=ltr] .md-search__options{right:.8rem}[dir=rtl] .md-search__options{left:.8rem}.md-search__options{top:.6rem}}[dir=ltr] .md-search__options>*{margin-left:.2rem}[dir=rtl] .md-search__options>*{margin-right:.2rem}.md-search__options>*{color:var(--md-default-fg-color--light);opacity:0;transform:scale(.75);transition:transform .15s cubic-bezier(.1,.7,.1,1),opacity .15s}.md-search__options>:not(.focus-visible){-webkit-tap-highlight-color:transparent;outline:none}[data-md-toggle=search]:checked~.md-header .md-search__input:valid~.md-search__options>*{opacity:1;pointer-events:auto;transform:scale(1)}[data-md-toggle=search]:checked~.md-header .md-search__input:valid~.md-search__options>:hover{opacity:.7}[dir=ltr] .md-search__suggest{padding-left:3.6rem;padding-right:2.2rem}[dir=rtl] .md-search__suggest{padding-left:2.2rem;padding-right:3.6rem}.md-search__suggest{align-items:center;color:var(--md-default-fg-color--lighter);display:flex;font-size:.9rem;height:100%;opacity:0;position:absolute;top:0;transition:opacity 50ms;white-space:nowrap;width:100%}@media screen and (min-width:60em){[dir=ltr] .md-search__suggest{padding-left:2.2rem}[dir=rtl] .md-search__suggest{padding-right:2.2rem}.md-search__suggest{font-size:.8rem}}[data-md-toggle=search]:checked~.md-header .md-search__suggest{opacity:1;transition:opacity .3s .1s}[dir=ltr] .md-search__output{border-bottom-left-radius:.1rem}[dir=ltr] .md-search__output,[dir=rtl] .md-search__output{border-bottom-right-radius:.1rem}[dir=rtl] .md-search__output{border-bottom-left-radius:.1rem}.md-search__output{overflow:hidden;position:absolute;width:100%;z-index:1}@media screen and (max-width:59.9375em){.md-search__output{bottom:0;top:2.4rem}}@media screen and (min-width:60em){.md-search__output{opacity:0;top:1.9rem;transition:opacity .4s}[data-md-toggle=search]:checked~.md-header .md-search__output{box-shadow:var(--md-shadow-z3);opacity:1}}.md-search__scrollwrap{-webkit-backface-visibility:hidden;backface-visibility:hidden;background-color:var(--md-default-bg-color);height:100%;overflow-y:auto;touch-action:pan-y}@media (-webkit-max-device-pixel-ratio:1),(max-resolution:1dppx){.md-search__scrollwrap{transform:translateZ(0)}}@media screen and (min-width:60em) and (max-width:76.1875em){.md-search__scrollwrap{width:23.4rem}}@media screen and (min-width:76.25em){.md-search__scrollwrap{width:34.4rem}}@media screen and (min-width:60em){.md-search__scrollwrap{max-height:0;scrollbar-color:var(--md-default-fg-color--lighter) transparent;scrollbar-width:thin}[data-md-toggle=search]:checked~.md-header .md-search__scrollwrap{max-height:75vh}.md-search__scrollwrap:hover{scrollbar-color:var(--md-accent-fg-color) transparent}.md-search__scrollwrap::-webkit-scrollbar{height:.2rem;width:.2rem}.md-search__scrollwrap::-webkit-scrollbar-thumb{background-color:var(--md-default-fg-color--lighter)}.md-search__scrollwrap::-webkit-scrollbar-thumb:hover{background-color:var(--md-accent-fg-color)}}.md-search-result{color:var(--md-default-fg-color);word-break:break-word}.md-search-result__meta{background-color:var(--md-default-fg-color--lightest);color:var(--md-default-fg-color--light);font-size:.64rem;line-height:1.8rem;padding:0 .8rem;scroll-snap-align:start}@media screen and (min-width:60em){[dir=ltr] .md-search-result__meta{padding-left:2.2rem}[dir=rtl] .md-search-result__meta{padding-right:2.2rem}}.md-search-result__list{list-style:none;margin:0;padding:0;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.md-search-result__item{box-shadow:0 -.05rem var(--md-default-fg-color--lightest)}.md-search-result__item:first-child{box-shadow:none}.md-search-result__link{display:block;outline:none;scroll-snap-align:start;transition:background-color .25s}.md-search-result__link:-webkit-any(:focus,:hover){background-color:var(--md-accent-fg-color--transparent)}.md-search-result__link:-moz-any(:focus,:hover){background-color:var(--md-accent-fg-color--transparent)}.md-search-result__link:is(:focus,:hover){background-color:var(--md-accent-fg-color--transparent)}.md-search-result__link:last-child p:last-child{margin-bottom:.6rem}.md-search-result__more summary{color:var(--md-typeset-a-color);cursor:pointer;display:block;font-size:.64rem;outline:none;padding:.75em .8rem;scroll-snap-align:start;transition:color .25s,background-color .25s}@media screen and (min-width:60em){[dir=ltr] .md-search-result__more summary{padding-left:2.2rem}[dir=rtl] .md-search-result__more summary{padding-right:2.2rem}}.md-search-result__more summary:-webkit-any(:focus,:hover){background-color:var(--md-accent-fg-color--transparent);color:var(--md-accent-fg-color)}.md-search-result__more summary:-moz-any(:focus,:hover){background-color:var(--md-accent-fg-color--transparent);color:var(--md-accent-fg-color)}.md-search-result__more summary:is(:focus,:hover){background-color:var(--md-accent-fg-color--transparent);color:var(--md-accent-fg-color)}.md-search-result__more summary::marker{display:none}.md-search-result__more summary::-webkit-details-marker{display:none}.md-search-result__more summary~*>*{opacity:.65}.md-search-result__article{overflow:hidden;padding:0 .8rem;position:relative}@media screen and (min-width:60em){[dir=ltr] .md-search-result__article{padding-left:2.2rem}[dir=rtl] .md-search-result__article{padding-right:2.2rem}}.md-search-result__article--document .md-search-result__title{font-size:.8rem;font-weight:400;line-height:1.4;margin:.55rem 0}[dir=ltr] .md-search-result__icon{left:0}[dir=rtl] .md-search-result__icon{right:0}.md-search-result__icon{color:var(--md-default-fg-color--light);height:1.2rem;margin:.5rem;position:absolute;width:1.2rem}@media screen and (max-width:59.9375em){.md-search-result__icon{display:none}}.md-search-result__icon:after{background-color:currentcolor;content:"";display:inline-block;height:100%;-webkit-mask-image:var(--md-search-result-icon);mask-image:var(--md-search-result-icon);-webkit-mask-repeat:no-repeat;mask-repeat:no-repeat;-webkit-mask-size:contain;mask-size:contain;width:100%}[dir=rtl] .md-search-result__icon:after{transform:scaleX(-1)}.md-search-result__title{font-size:.64rem;font-weight:700;line-height:1.6;margin:.5em 0}.md-search-result__teaser{-webkit-box-orient:vertical;-webkit-line-clamp:2;color:var(--md-default-fg-color--light);display:-webkit-box;font-size:.64rem;line-height:1.6;margin:.5em 0;max-height:2rem;overflow:hidden;text-overflow:ellipsis}@media screen and (max-width:44.9375em){.md-search-result__teaser{-webkit-line-clamp:3;max-height:3rem}}@media screen and (min-width:60em) and (max-width:76.1875em){.md-search-result__teaser{-webkit-line-clamp:3;max-height:3rem}}.md-search-result__teaser mark{background-color:initial;text-decoration:underline}.md-search-result__terms{font-size:.64rem;font-style:italic;margin:.5em 0}.md-search-result mark{background-color:initial;color:var(--md-accent-fg-color)}.md-select{position:relative;z-index:1}.md-select__inner{background-color:var(--md-default-bg-color);border-radius:.1rem;box-shadow:var(--md-shadow-z2);color:var(--md-default-fg-color);left:50%;margin-top:.2rem;max-height:0;opacity:0;position:absolute;top:calc(100% - .2rem);transform:translate3d(-50%,.3rem,0);transition:transform .25s 375ms,opacity .25s .25s,max-height 0ms .5s}.md-select:-webkit-any(:focus-within,:hover) .md-select__inner{max-height:10rem;opacity:1;transform:translate3d(-50%,0,0);-webkit-transition:transform .25s cubic-bezier(.1,.7,.1,1),opacity .25s,max-height 0ms;transition:transform .25s cubic-bezier(.1,.7,.1,1),opacity .25s,max-height 0ms}.md-select:-moz-any(:focus-within,:hover) .md-select__inner{max-height:10rem;opacity:1;transform:translate3d(-50%,0,0);-moz-transition:transform .25s cubic-bezier(.1,.7,.1,1),opacity .25s,max-height 0ms;transition:transform .25s cubic-bezier(.1,.7,.1,1),opacity .25s,max-height 0ms}.md-select:is(:focus-within,:hover) .md-select__inner{max-height:10rem;opacity:1;transform:translate3d(-50%,0,0);transition:transform .25s cubic-bezier(.1,.7,.1,1),opacity .25s,max-height 0ms}.md-select__inner:after{border-bottom:.2rem solid transparent;border-bottom-color:var(--md-default-bg-color);border-left:.2rem solid transparent;border-right:.2rem solid transparent;border-top:0;content:"";height:0;left:50%;margin-left:-.2rem;margin-top:-.2rem;position:absolute;top:0;width:0}.md-select__list{border-radius:.1rem;font-size:.8rem;list-style-type:none;margin:0;max-height:inherit;overflow:auto;padding:0}.md-select__item{line-height:1.8rem}[dir=ltr] .md-select__link{padding-left:.6rem;padding-right:1.2rem}[dir=rtl] .md-select__link{padding-left:1.2rem;padding-right:.6rem}.md-select__link{cursor:pointer;display:block;outline:none;scroll-snap-align:start;transition:background-color .25s,color .25s;width:100%}.md-select__link:-webkit-any(:focus,:hover){color:var(--md-accent-fg-color)}.md-select__link:-moz-any(:focus,:hover){color:var(--md-accent-fg-color)}.md-select__link:is(:focus,:hover){color:var(--md-accent-fg-color)}.md-select__link:focus{background-color:var(--md-default-fg-color--lightest)}.md-sidebar{align-self:flex-start;flex-shrink:0;padding:1.2rem 0;position:-webkit-sticky;position:sticky;top:2.4rem;width:12.1rem}@media print{.md-sidebar{display:none}}@media screen and (max-width:76.1875em){[dir=ltr] .md-sidebar--primary{left:-12.1rem}[dir=rtl] .md-sidebar--primary{right:-12.1rem}.md-sidebar--primary{background-color:var(--md-default-bg-color);display:block;height:100%;position:fixed;top:0;transform:translateX(0);transition:transform .25s cubic-bezier(.4,0,.2,1),box-shadow .25s;width:12.1rem;z-index:5}[data-md-toggle=drawer]:checked~.md-container .md-sidebar--primary{box-shadow:var(--md-shadow-z3);transform:translateX(12.1rem)}[dir=rtl] [data-md-toggle=drawer]:checked~.md-container .md-sidebar--primary{transform:translateX(-12.1rem)}.md-sidebar--primary .md-sidebar__scrollwrap{bottom:0;left:0;margin:0;overflow:hidden;position:absolute;right:0;-ms-scroll-snap-type:none;scroll-snap-type:none;top:0}}@media screen and (min-width:76.25em){.md-sidebar{height:0}.no-js .md-sidebar{height:auto}}.md-sidebar--secondary{display:none;order:2}@media screen and (min-width:60em){.md-sidebar--secondary{height:0}.no-js .md-sidebar--secondary{height:auto}.md-sidebar--secondary:not([hidden]){display:block}.md-sidebar--secondary .md-sidebar__scrollwrap{touch-action:pan-y}}.md-sidebar__scrollwrap{-webkit-backface-visibility:hidden;backface-visibility:hidden;margin:0 .2rem;overflow-y:auto;scrollbar-color:var(--md-default-fg-color--lighter) transparent;scrollbar-width:thin}.md-sidebar__scrollwrap:hover{scrollbar-color:var(--md-accent-fg-color) transparent}.md-sidebar__scrollwrap::-webkit-scrollbar{height:.2rem;width:.2rem}.md-sidebar__scrollwrap::-webkit-scrollbar-thumb{background-color:var(--md-default-fg-color--lighter)}.md-sidebar__scrollwrap::-webkit-scrollbar-thumb:hover{background-color:var(--md-accent-fg-color)}@media screen and (max-width:76.1875em){.md-overlay{background-color:rgba(0,0,0,.54);height:0;opacity:0;position:fixed;top:0;transition:width 0ms .25s,height 0ms .25s,opacity .25s;width:0;z-index:5}[data-md-toggle=drawer]:checked~.md-overlay{height:100%;opacity:1;transition:width 0ms,height 0ms,opacity .25s;width:100%}}@-webkit-keyframes facts{0%{height:0}to{height:.65rem}}@keyframes facts{0%{height:0}to{height:.65rem}}@-webkit-keyframes fact{0%{opacity:0;transform:translateY(100%)}50%{opacity:0}to{opacity:1;transform:translateY(0)}}@keyframes fact{0%{opacity:0;transform:translateY(100%)}50%{opacity:0}to{opacity:1;transform:translateY(0)}}:root{--md-source-forks-icon:url('data:image/svg+xml;charset=utf-8,');--md-source-repositories-icon:url('data:image/svg+xml;charset=utf-8,');--md-source-stars-icon:url('data:image/svg+xml;charset=utf-8,');--md-source-version-icon:url('data:image/svg+xml;charset=utf-8,')}.md-source{-webkit-backface-visibility:hidden;backface-visibility:hidden;display:block;font-size:.65rem;line-height:1.2;outline-color:var(--md-accent-fg-color);transition:opacity .25s;white-space:nowrap}.md-source:hover{opacity:.7}.md-source__icon{display:inline-block;height:2.4rem;vertical-align:middle;width:2rem}[dir=ltr] .md-source__icon svg{margin-left:.6rem}[dir=rtl] .md-source__icon svg{margin-right:.6rem}.md-source__icon svg{margin-top:.6rem}[dir=ltr] .md-source__icon+.md-source__repository{margin-left:-2rem}[dir=rtl] .md-source__icon+.md-source__repository{margin-right:-2rem}[dir=ltr] .md-source__icon+.md-source__repository{padding-left:2rem}[dir=rtl] .md-source__icon+.md-source__repository{padding-right:2rem}[dir=ltr] .md-source__repository{margin-left:.6rem}[dir=rtl] .md-source__repository{margin-right:.6rem}.md-source__repository{display:inline-block;max-width:calc(100% - 1.2rem);overflow:hidden;text-overflow:ellipsis;vertical-align:middle}.md-source__facts{font-size:.55rem;list-style-type:none;margin:.1rem 0 0;opacity:.75;overflow:hidden;padding:0}.md-source__repository--active .md-source__facts{-webkit-animation:facts .25s ease-in;animation:facts .25s ease-in}.md-source__fact{display:inline-block}.md-source__repository--active .md-source__fact{-webkit-animation:fact .4s ease-out;animation:fact .4s ease-out}[dir=ltr] .md-source__fact:before{margin-right:.1rem}[dir=rtl] .md-source__fact:before{margin-left:.1rem}.md-source__fact:before{background-color:currentcolor;content:"";display:inline-block;height:.6rem;-webkit-mask-repeat:no-repeat;mask-repeat:no-repeat;-webkit-mask-size:contain;mask-size:contain;vertical-align:text-top;width:.6rem}[dir=ltr] .md-source__fact:nth-child(1n+2):before{margin-left:.4rem}[dir=rtl] .md-source__fact:nth-child(1n+2):before{margin-right:.4rem}.md-source__fact--version:before{-webkit-mask-image:var(--md-source-version-icon);mask-image:var(--md-source-version-icon)}.md-source__fact--stars:before{-webkit-mask-image:var(--md-source-stars-icon);mask-image:var(--md-source-stars-icon)}.md-source__fact--forks:before{-webkit-mask-image:var(--md-source-forks-icon);mask-image:var(--md-source-forks-icon)}.md-source__fact--repositories:before{-webkit-mask-image:var(--md-source-repositories-icon);mask-image:var(--md-source-repositories-icon)}.md-tabs{background-color:var(--md-primary-fg-color);color:var(--md-primary-bg-color);display:block;line-height:1.3;overflow:auto;width:100%;z-index:3}@media print{.md-tabs{display:none}}@media screen and (max-width:76.1875em){.md-tabs{display:none}}.md-tabs[hidden]{pointer-events:none}[dir=ltr] .md-tabs__list{margin-left:.2rem}[dir=rtl] .md-tabs__list{margin-right:.2rem}.md-tabs__list{contain:content;list-style:none;margin:0;padding:0;white-space:nowrap}.md-tabs__item{display:inline-block;height:2.4rem;padding-left:.6rem;padding-right:.6rem}.md-tabs__link{-webkit-backface-visibility:hidden;backface-visibility:hidden;display:block;font-size:.7rem;margin-top:.8rem;opacity:.7;outline-color:var(--md-accent-fg-color);outline-offset:.2rem;transition:transform .4s cubic-bezier(.1,.7,.1,1),opacity .25s}.md-tabs__link--active,.md-tabs__link:-webkit-any(:focus,:hover){color:inherit;opacity:1}.md-tabs__link--active,.md-tabs__link:-moz-any(:focus,:hover){color:inherit;opacity:1}.md-tabs__link--active,.md-tabs__link:is(:focus,:hover){color:inherit;opacity:1}.md-tabs__item:nth-child(2) .md-tabs__link{transition-delay:20ms}.md-tabs__item:nth-child(3) .md-tabs__link{transition-delay:40ms}.md-tabs__item:nth-child(4) .md-tabs__link{transition-delay:60ms}.md-tabs__item:nth-child(5) .md-tabs__link{transition-delay:80ms}.md-tabs__item:nth-child(6) .md-tabs__link{transition-delay:.1s}.md-tabs__item:nth-child(7) .md-tabs__link{transition-delay:.12s}.md-tabs__item:nth-child(8) .md-tabs__link{transition-delay:.14s}.md-tabs__item:nth-child(9) .md-tabs__link{transition-delay:.16s}.md-tabs__item:nth-child(10) .md-tabs__link{transition-delay:.18s}.md-tabs__item:nth-child(11) .md-tabs__link{transition-delay:.2s}.md-tabs__item:nth-child(12) .md-tabs__link{transition-delay:.22s}.md-tabs__item:nth-child(13) .md-tabs__link{transition-delay:.24s}.md-tabs__item:nth-child(14) .md-tabs__link{transition-delay:.26s}.md-tabs__item:nth-child(15) .md-tabs__link{transition-delay:.28s}.md-tabs__item:nth-child(16) .md-tabs__link{transition-delay:.3s}.md-tabs[hidden] .md-tabs__link{opacity:0;transform:translateY(50%);transition:transform 0ms .1s,opacity .1s}.md-tags{margin-bottom:.75em}[dir=ltr] .md-tag{margin-right:.5em}[dir=rtl] .md-tag{margin-left:.5em}.md-tag{background:var(--md-default-fg-color--lightest);border-radius:.4rem;display:inline-block;font-size:.64rem;font-weight:700;line-height:1.6;margin-bottom:.5em;padding:.3125em .9375em}.md-tag[href]{-webkit-tap-highlight-color:transparent;color:inherit;outline:none;transition:color 125ms,background-color 125ms}.md-tag[href]:focus,.md-tag[href]:hover{background-color:var(--md-accent-fg-color);color:var(--md-accent-bg-color)}[id]>.md-tag{vertical-align:text-top}@-webkit-keyframes pulse{0%{box-shadow:0 0 0 0 var(--md-default-fg-color--lightest);transform:scale(.95)}75%{box-shadow:0 0 0 .625em transparent;transform:scale(1)}to{box-shadow:0 0 0 0 transparent;transform:scale(.95)}}@keyframes pulse{0%{box-shadow:0 0 0 0 var(--md-default-fg-color--lightest);transform:scale(.95)}75%{box-shadow:0 0 0 .625em transparent;transform:scale(1)}to{box-shadow:0 0 0 0 transparent;transform:scale(.95)}}:root{--md-tooltip-width:20rem}.md-tooltip{-webkit-backface-visibility:hidden;backface-visibility:hidden;background-color:var(--md-default-bg-color);border-radius:.1rem;box-shadow:var(--md-shadow-z2);color:var(--md-default-fg-color);left:clamp(var(--md-tooltip-0,0rem) + .8rem,var(--md-tooltip-x),100vw + var(--md-tooltip-0,0rem) + .8rem - var(--md-tooltip-width) - 2 * .8rem);max-height:0;max-width:calc(100vw - 1.6rem);opacity:0;position:absolute;top:var(--md-tooltip-y);transform:translateY(-.4rem);transition:transform 0ms .25s,opacity .25s,max-height 0ms .25s,z-index .25s;width:var(--md-tooltip-width);z-index:0}:focus-within>.md-tooltip{max-height:1000%;opacity:1;transform:translateY(0);transition:transform .25s cubic-bezier(.1,.7,.1,1),opacity .25s,max-height .25s,z-index 0ms}.focus-visible>.md-tooltip{outline:var(--md-accent-fg-color) auto}.md-tooltip__inner{font-size:.64rem;padding:.8rem}.md-tooltip__inner.md-typeset>:first-child{margin-top:0}.md-tooltip__inner.md-typeset>:last-child{margin-bottom:0}.md-annotation{outline:none;white-space:normal}[dir=rtl] .md-annotation{direction:rtl}.md-annotation:not([hidden]){display:inline-block;line-height:1.325}.md-annotation:focus-within>*{z-index:2}.md-annotation__inner{font-family:var(--md-text-font-family);top:calc(var(--md-tooltip-y) + 1.2ch)}:not(:focus-within)>.md-annotation__inner{pointer-events:none;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.md-annotation__index{color:#fff;cursor:pointer;margin:0 1ch;position:relative;transition:z-index .25s;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;z-index:0}.md-annotation__index:after{background-color:var(--md-default-fg-color--lighter);border-radius:2ch;content:"";height:2.2ch;left:-.126em;margin:0 -.4ch;padding:0 .4ch;position:absolute;transition:color .25s,background-color .25s;width:calc(100% + 1.2ch);width:max(2.2ch,100% + 1.2ch);z-index:-1}@media not all and (prefers-reduced-motion){[data-md-visible]>.md-annotation__index:after{-webkit-animation:pulse 2s infinite;animation:pulse 2s infinite}}:-webkit-any(:focus-within,:hover)>.md-annotation__index:after{background-color:var(--md-accent-fg-color)}:-moz-any(:focus-within,:hover)>.md-annotation__index:after{background-color:var(--md-accent-fg-color)}:is(:focus-within,:hover)>.md-annotation__index:after{background-color:var(--md-accent-fg-color)}:focus-within>.md-annotation__index:after{-webkit-animation:none;animation:none;transition:color .25s,background-color .25s}.md-annotation__index [data-md-annotation-id]{display:inline-block;line-height:90%}.md-annotation__index [data-md-annotation-id]:before{content:attr(data-md-annotation-id);display:inline-block;padding-bottom:.1em;transform:scale(1.15);transition:transform .4s cubic-bezier(.1,.7,.1,1);vertical-align:.065em}@media not print{.md-annotation__index [data-md-annotation-id]:before{content:"+"}:focus-within>.md-annotation__index [data-md-annotation-id]:before{transform:scale(1.25) rotate(45deg)}}:-webkit-any(:focus-within,:hover)>.md-annotation__index{color:var(--md-accent-bg-color)}:-moz-any(:focus-within,:hover)>.md-annotation__index{color:var(--md-accent-bg-color)}:is(:focus-within,:hover)>.md-annotation__index{color:var(--md-accent-bg-color)}:focus-within>.md-annotation__index{-webkit-animation:none;animation:none;transition:none}[dir=ltr] .md-top{margin-left:50%}[dir=rtl] .md-top{margin-right:50%}.md-top{background-color:var(--md-default-bg-color);border-radius:1.6rem;box-shadow:var(--md-shadow-z2);color:var(--md-default-fg-color--light);display:block;font-size:.7rem;outline:none;padding:.4rem .8rem;position:fixed;top:3.2rem;transform:translate(-50%);transition:color 125ms,background-color 125ms,transform 125ms cubic-bezier(.4,0,.2,1),opacity 125ms;z-index:2}@media print{.md-top{display:none}}[dir=rtl] .md-top{transform:translate(50%)}.md-top[hidden]{opacity:0;pointer-events:none;transform:translate(-50%,.2rem);transition-duration:0ms}[dir=rtl] .md-top[hidden]{transform:translate(50%,.2rem)}.md-top:-webkit-any(:focus,:hover){background-color:var(--md-accent-fg-color);color:var(--md-accent-bg-color)}.md-top:-moz-any(:focus,:hover){background-color:var(--md-accent-fg-color);color:var(--md-accent-bg-color)}.md-top:is(:focus,:hover){background-color:var(--md-accent-fg-color);color:var(--md-accent-bg-color)}.md-top svg{display:inline-block;vertical-align:-.5em}@-webkit-keyframes hoverfix{0%{pointer-events:none}}@keyframes hoverfix{0%{pointer-events:none}}:root{--md-version-icon:url('data:image/svg+xml;charset=utf-8,')}.md-version{flex-shrink:0;font-size:.8rem;height:2.4rem}[dir=ltr] .md-version__current{margin-left:1.4rem;margin-right:.4rem}[dir=rtl] .md-version__current{margin-left:.4rem;margin-right:1.4rem}.md-version__current{color:inherit;cursor:pointer;outline:none;position:relative;top:.05rem}[dir=ltr] .md-version__current:after{margin-left:.4rem}[dir=rtl] .md-version__current:after{margin-right:.4rem}.md-version__current:after{background-color:currentcolor;content:"";display:inline-block;height:.6rem;-webkit-mask-image:var(--md-version-icon);mask-image:var(--md-version-icon);-webkit-mask-repeat:no-repeat;mask-repeat:no-repeat;width:.4rem}.md-version__list{background-color:var(--md-default-bg-color);border-radius:.1rem;box-shadow:var(--md-shadow-z2);color:var(--md-default-fg-color);list-style-type:none;margin:.2rem .8rem;max-height:0;opacity:0;overflow:auto;padding:0;position:absolute;-ms-scroll-snap-type:y mandatory;scroll-snap-type:y mandatory;top:.15rem;transition:max-height 0ms .5s,opacity .25s .25s;z-index:3}.md-version:-webkit-any(:focus-within,:hover) .md-version__list{max-height:10rem;opacity:1;-webkit-transition:max-height 0ms,opacity .25s;transition:max-height 0ms,opacity .25s}.md-version:-moz-any(:focus-within,:hover) .md-version__list{max-height:10rem;opacity:1;-moz-transition:max-height 0ms,opacity .25s;transition:max-height 0ms,opacity .25s}.md-version:is(:focus-within,:hover) .md-version__list{max-height:10rem;opacity:1;transition:max-height 0ms,opacity .25s}@media (pointer:coarse){.md-version:hover .md-version__list{-webkit-animation:hoverfix .25s forwards;animation:hoverfix .25s forwards}.md-version:focus-within .md-version__list{-webkit-animation:none;animation:none}}.md-version__item{line-height:1.8rem}[dir=ltr] .md-version__link{padding-left:.6rem;padding-right:1.2rem}[dir=rtl] .md-version__link{padding-left:1.2rem;padding-right:.6rem}.md-version__link{cursor:pointer;display:block;outline:none;scroll-snap-align:start;transition:color .25s,background-color .25s;white-space:nowrap;width:100%}.md-version__link:-webkit-any(:focus,:hover){color:var(--md-accent-fg-color)}.md-version__link:-moz-any(:focus,:hover){color:var(--md-accent-fg-color)}.md-version__link:is(:focus,:hover){color:var(--md-accent-fg-color)}.md-version__link:focus{background-color:var(--md-default-fg-color--lightest)}:root{--md-admonition-icon--note:url('data:image/svg+xml;charset=utf-8,');--md-admonition-icon--abstract:url('data:image/svg+xml;charset=utf-8,');--md-admonition-icon--info:url('data:image/svg+xml;charset=utf-8,');--md-admonition-icon--tip:url('data:image/svg+xml;charset=utf-8,');--md-admonition-icon--success:url('data:image/svg+xml;charset=utf-8,');--md-admonition-icon--question:url('data:image/svg+xml;charset=utf-8,');--md-admonition-icon--warning:url('data:image/svg+xml;charset=utf-8,');--md-admonition-icon--failure:url('data:image/svg+xml;charset=utf-8,');--md-admonition-icon--danger:url('data:image/svg+xml;charset=utf-8,');--md-admonition-icon--bug:url('data:image/svg+xml;charset=utf-8,');--md-admonition-icon--example:url('data:image/svg+xml;charset=utf-8,');--md-admonition-icon--quote:url('data:image/svg+xml;charset=utf-8,')}.md-typeset :-webkit-any(.admonition,details){background-color:var(--md-admonition-bg-color);border:0 solid #448aff;border-radius:.1rem;box-shadow:var(--md-shadow-z1);color:var(--md-admonition-fg-color);display:flow-root;font-size:.64rem;margin:1.5625em 0;padding:0 .6rem;page-break-inside:avoid}.md-typeset :-moz-any(.admonition,details){background-color:var(--md-admonition-bg-color);border:0 solid #448aff;border-radius:.1rem;box-shadow:var(--md-shadow-z1);color:var(--md-admonition-fg-color);display:flow-root;font-size:.64rem;margin:1.5625em 0;padding:0 .6rem;page-break-inside:avoid}[dir=ltr] .md-typeset :-webkit-any(.admonition,details){border-left-width:.2rem}[dir=ltr] .md-typeset :-moz-any(.admonition,details){border-left-width:.2rem}[dir=ltr] .md-typeset :is(.admonition,details){border-left-width:.2rem}[dir=rtl] .md-typeset :-webkit-any(.admonition,details){border-right-width:.2rem}[dir=rtl] .md-typeset :-moz-any(.admonition,details){border-right-width:.2rem}[dir=rtl] .md-typeset :is(.admonition,details){border-right-width:.2rem}.md-typeset :is(.admonition,details){background-color:var(--md-admonition-bg-color);border:0 solid #448aff;border-radius:.1rem;box-shadow:var(--md-shadow-z1);color:var(--md-admonition-fg-color);display:flow-root;font-size:.64rem;margin:1.5625em 0;padding:0 .6rem;page-break-inside:avoid}@media print{.md-typeset :-webkit-any(.admonition,details){box-shadow:none}.md-typeset :-moz-any(.admonition,details){box-shadow:none}.md-typeset :is(.admonition,details){box-shadow:none}}.md-typeset :-webkit-any(.admonition,details)>*{box-sizing:border-box}.md-typeset :-moz-any(.admonition,details)>*{box-sizing:border-box}.md-typeset :is(.admonition,details)>*{box-sizing:border-box}.md-typeset :-webkit-any(.admonition,details) :-webkit-any(.admonition,details){margin-bottom:1em;margin-top:1em}.md-typeset :-moz-any(.admonition,details) :-moz-any(.admonition,details){margin-bottom:1em;margin-top:1em}.md-typeset :is(.admonition,details) :is(.admonition,details){margin-bottom:1em;margin-top:1em}.md-typeset :-webkit-any(.admonition,details) .md-typeset__scrollwrap{margin:1em -.6rem}.md-typeset :-moz-any(.admonition,details) .md-typeset__scrollwrap{margin:1em -.6rem}.md-typeset :is(.admonition,details) .md-typeset__scrollwrap{margin:1em -.6rem}.md-typeset :-webkit-any(.admonition,details) .md-typeset__table{padding:0 .6rem}.md-typeset :-moz-any(.admonition,details) .md-typeset__table{padding:0 .6rem}.md-typeset :is(.admonition,details) .md-typeset__table{padding:0 .6rem}.md-typeset :-webkit-any(.admonition,details)>.tabbed-set:only-child{margin-top:0}.md-typeset :-moz-any(.admonition,details)>.tabbed-set:only-child{margin-top:0}.md-typeset :is(.admonition,details)>.tabbed-set:only-child{margin-top:0}html .md-typeset :-webkit-any(.admonition,details)>:last-child{margin-bottom:.6rem}html .md-typeset :-moz-any(.admonition,details)>:last-child{margin-bottom:.6rem}html .md-typeset :is(.admonition,details)>:last-child{margin-bottom:.6rem}.md-typeset :-webkit-any(.admonition-title,summary){background-color:rgba(68,138,255,.1);border:none;font-weight:700;margin-bottom:0;margin-top:0;padding-bottom:.4rem;padding-top:.4rem;position:relative}.md-typeset :-moz-any(.admonition-title,summary){background-color:rgba(68,138,255,.1);border:none;font-weight:700;margin-bottom:0;margin-top:0;padding-bottom:.4rem;padding-top:.4rem;position:relative}[dir=ltr] .md-typeset :-webkit-any(.admonition-title,summary){margin-left:-.8rem;margin-right:-.6rem}[dir=ltr] .md-typeset :-moz-any(.admonition-title,summary){margin-left:-.8rem;margin-right:-.6rem}[dir=ltr] .md-typeset :is(.admonition-title,summary){margin-left:-.8rem;margin-right:-.6rem}[dir=rtl] .md-typeset :-webkit-any(.admonition-title,summary){margin-left:-.6rem;margin-right:-.8rem}[dir=rtl] .md-typeset :-moz-any(.admonition-title,summary){margin-left:-.6rem;margin-right:-.8rem}[dir=rtl] .md-typeset :is(.admonition-title,summary){margin-left:-.6rem;margin-right:-.8rem}[dir=ltr] .md-typeset :-webkit-any(.admonition-title,summary){padding-left:2.2rem;padding-right:.6rem}[dir=ltr] .md-typeset :-moz-any(.admonition-title,summary){padding-left:2.2rem;padding-right:.6rem}[dir=ltr] .md-typeset :is(.admonition-title,summary){padding-left:2.2rem;padding-right:.6rem}[dir=rtl] .md-typeset :-webkit-any(.admonition-title,summary){padding-left:.6rem;padding-right:2.2rem}[dir=rtl] .md-typeset :-moz-any(.admonition-title,summary){padding-left:.6rem;padding-right:2.2rem}[dir=rtl] .md-typeset :is(.admonition-title,summary){padding-left:.6rem;padding-right:2.2rem}[dir=ltr] .md-typeset :-webkit-any(.admonition-title,summary){border-left-width:.2rem}[dir=ltr] .md-typeset :-moz-any(.admonition-title,summary){border-left-width:.2rem}[dir=ltr] .md-typeset :is(.admonition-title,summary){border-left-width:.2rem}[dir=rtl] .md-typeset :-webkit-any(.admonition-title,summary){border-right-width:.2rem}[dir=rtl] .md-typeset :-moz-any(.admonition-title,summary){border-right-width:.2rem}[dir=rtl] .md-typeset :is(.admonition-title,summary){border-right-width:.2rem}[dir=ltr] .md-typeset :-webkit-any(.admonition-title,summary){border-top-left-radius:.1rem}[dir=ltr] .md-typeset :-moz-any(.admonition-title,summary){border-top-left-radius:.1rem}[dir=ltr] .md-typeset :is(.admonition-title,summary){border-top-left-radius:.1rem}[dir=rtl] .md-typeset :-webkit-any(.admonition-title,summary){border-top-right-radius:.1rem}[dir=rtl] .md-typeset :-moz-any(.admonition-title,summary){border-top-right-radius:.1rem}[dir=rtl] .md-typeset :is(.admonition-title,summary){border-top-right-radius:.1rem}[dir=ltr] .md-typeset :-webkit-any(.admonition-title,summary){border-top-right-radius:.1rem}[dir=ltr] .md-typeset :-moz-any(.admonition-title,summary){border-top-right-radius:.1rem}[dir=ltr] .md-typeset :is(.admonition-title,summary){border-top-right-radius:.1rem}[dir=rtl] .md-typeset :-webkit-any(.admonition-title,summary){border-top-left-radius:.1rem}[dir=rtl] .md-typeset :-moz-any(.admonition-title,summary){border-top-left-radius:.1rem}[dir=rtl] .md-typeset :is(.admonition-title,summary){border-top-left-radius:.1rem}.md-typeset :is(.admonition-title,summary){background-color:rgba(68,138,255,.1);border:none;font-weight:700;margin-bottom:0;margin-top:0;padding-bottom:.4rem;padding-top:.4rem;position:relative}html .md-typeset :-webkit-any(.admonition-title,summary):last-child{margin-bottom:0}html .md-typeset :-moz-any(.admonition-title,summary):last-child{margin-bottom:0}html .md-typeset :is(.admonition-title,summary):last-child{margin-bottom:0}.md-typeset :-webkit-any(.admonition-title,summary):before{background-color:#448aff;content:"";height:1rem;-webkit-mask-image:var(--md-admonition-icon--note);mask-image:var(--md-admonition-icon--note);-webkit-mask-repeat:no-repeat;mask-repeat:no-repeat;-webkit-mask-size:contain;mask-size:contain;position:absolute;top:.625em;width:1rem}.md-typeset :-moz-any(.admonition-title,summary):before{background-color:#448aff;content:"";height:1rem;mask-image:var(--md-admonition-icon--note);mask-repeat:no-repeat;mask-size:contain;position:absolute;top:.625em;width:1rem}[dir=ltr] .md-typeset :-webkit-any(.admonition-title,summary):before{left:.8rem}[dir=ltr] .md-typeset :-moz-any(.admonition-title,summary):before{left:.8rem}[dir=ltr] .md-typeset :is(.admonition-title,summary):before{left:.8rem}[dir=rtl] .md-typeset :-webkit-any(.admonition-title,summary):before{right:.8rem}[dir=rtl] .md-typeset :-moz-any(.admonition-title,summary):before{right:.8rem}[dir=rtl] .md-typeset :is(.admonition-title,summary):before{right:.8rem}.md-typeset :is(.admonition-title,summary):before{background-color:#448aff;content:"";height:1rem;-webkit-mask-image:var(--md-admonition-icon--note);mask-image:var(--md-admonition-icon--note);-webkit-mask-repeat:no-repeat;mask-repeat:no-repeat;-webkit-mask-size:contain;mask-size:contain;position:absolute;top:.625em;width:1rem}.md-typeset :-webkit-any(.admonition,details):-webkit-any(.note){border-color:#448aff}.md-typeset :-moz-any(.admonition,details):-moz-any(.note){border-color:#448aff}.md-typeset :is(.admonition,details):is(.note){border-color:#448aff}.md-typeset :-webkit-any(.note)>:-webkit-any(.admonition-title,summary){background-color:rgba(68,138,255,.1)}.md-typeset :-moz-any(.note)>:-moz-any(.admonition-title,summary){background-color:rgba(68,138,255,.1)}.md-typeset :is(.note)>:is(.admonition-title,summary){background-color:rgba(68,138,255,.1)}.md-typeset :-webkit-any(.note)>:-webkit-any(.admonition-title,summary):before{background-color:#448aff;-webkit-mask-image:var(--md-admonition-icon--note);mask-image:var(--md-admonition-icon--note);-webkit-mask-repeat:no-repeat;mask-repeat:no-repeat;-webkit-mask-size:contain;mask-size:contain}.md-typeset :-moz-any(.note)>:-moz-any(.admonition-title,summary):before{background-color:#448aff;mask-image:var(--md-admonition-icon--note);mask-repeat:no-repeat;mask-size:contain}.md-typeset :is(.note)>:is(.admonition-title,summary):before{background-color:#448aff;-webkit-mask-image:var(--md-admonition-icon--note);mask-image:var(--md-admonition-icon--note);-webkit-mask-repeat:no-repeat;mask-repeat:no-repeat;-webkit-mask-size:contain;mask-size:contain}.md-typeset :-webkit-any(.admonition,details):-webkit-any(.abstract,.summary,.tldr){border-color:#00b0ff}.md-typeset :-moz-any(.admonition,details):-moz-any(.abstract,.summary,.tldr){border-color:#00b0ff}.md-typeset :is(.admonition,details):is(.abstract,.summary,.tldr){border-color:#00b0ff}.md-typeset :-webkit-any(.abstract,.summary,.tldr)>:-webkit-any(.admonition-title,summary){background-color:rgba(0,176,255,.1)}.md-typeset :-moz-any(.abstract,.summary,.tldr)>:-moz-any(.admonition-title,summary){background-color:rgba(0,176,255,.1)}.md-typeset :is(.abstract,.summary,.tldr)>:is(.admonition-title,summary){background-color:rgba(0,176,255,.1)}.md-typeset :-webkit-any(.abstract,.summary,.tldr)>:-webkit-any(.admonition-title,summary):before{background-color:#00b0ff;-webkit-mask-image:var(--md-admonition-icon--abstract);mask-image:var(--md-admonition-icon--abstract);-webkit-mask-repeat:no-repeat;mask-repeat:no-repeat;-webkit-mask-size:contain;mask-size:contain}.md-typeset :-moz-any(.abstract,.summary,.tldr)>:-moz-any(.admonition-title,summary):before{background-color:#00b0ff;mask-image:var(--md-admonition-icon--abstract);mask-repeat:no-repeat;mask-size:contain}.md-typeset :is(.abstract,.summary,.tldr)>:is(.admonition-title,summary):before{background-color:#00b0ff;-webkit-mask-image:var(--md-admonition-icon--abstract);mask-image:var(--md-admonition-icon--abstract);-webkit-mask-repeat:no-repeat;mask-repeat:no-repeat;-webkit-mask-size:contain;mask-size:contain}.md-typeset :-webkit-any(.admonition,details):-webkit-any(.info,.todo){border-color:#00b8d4}.md-typeset :-moz-any(.admonition,details):-moz-any(.info,.todo){border-color:#00b8d4}.md-typeset :is(.admonition,details):is(.info,.todo){border-color:#00b8d4}.md-typeset :-webkit-any(.info,.todo)>:-webkit-any(.admonition-title,summary){background-color:rgba(0,184,212,.1)}.md-typeset :-moz-any(.info,.todo)>:-moz-any(.admonition-title,summary){background-color:rgba(0,184,212,.1)}.md-typeset :is(.info,.todo)>:is(.admonition-title,summary){background-color:rgba(0,184,212,.1)}.md-typeset :-webkit-any(.info,.todo)>:-webkit-any(.admonition-title,summary):before{background-color:#00b8d4;-webkit-mask-image:var(--md-admonition-icon--info);mask-image:var(--md-admonition-icon--info);-webkit-mask-repeat:no-repeat;mask-repeat:no-repeat;-webkit-mask-size:contain;mask-size:contain}.md-typeset :-moz-any(.info,.todo)>:-moz-any(.admonition-title,summary):before{background-color:#00b8d4;mask-image:var(--md-admonition-icon--info);mask-repeat:no-repeat;mask-size:contain}.md-typeset :is(.info,.todo)>:is(.admonition-title,summary):before{background-color:#00b8d4;-webkit-mask-image:var(--md-admonition-icon--info);mask-image:var(--md-admonition-icon--info);-webkit-mask-repeat:no-repeat;mask-repeat:no-repeat;-webkit-mask-size:contain;mask-size:contain}.md-typeset :-webkit-any(.admonition,details):-webkit-any(.tip,.hint,.important){border-color:#00bfa5}.md-typeset :-moz-any(.admonition,details):-moz-any(.tip,.hint,.important){border-color:#00bfa5}.md-typeset :is(.admonition,details):is(.tip,.hint,.important){border-color:#00bfa5}.md-typeset :-webkit-any(.tip,.hint,.important)>:-webkit-any(.admonition-title,summary){background-color:rgba(0,191,165,.1)}.md-typeset :-moz-any(.tip,.hint,.important)>:-moz-any(.admonition-title,summary){background-color:rgba(0,191,165,.1)}.md-typeset :is(.tip,.hint,.important)>:is(.admonition-title,summary){background-color:rgba(0,191,165,.1)}.md-typeset :-webkit-any(.tip,.hint,.important)>:-webkit-any(.admonition-title,summary):before{background-color:#00bfa5;-webkit-mask-image:var(--md-admonition-icon--tip);mask-image:var(--md-admonition-icon--tip);-webkit-mask-repeat:no-repeat;mask-repeat:no-repeat;-webkit-mask-size:contain;mask-size:contain}.md-typeset :-moz-any(.tip,.hint,.important)>:-moz-any(.admonition-title,summary):before{background-color:#00bfa5;mask-image:var(--md-admonition-icon--tip);mask-repeat:no-repeat;mask-size:contain}.md-typeset :is(.tip,.hint,.important)>:is(.admonition-title,summary):before{background-color:#00bfa5;-webkit-mask-image:var(--md-admonition-icon--tip);mask-image:var(--md-admonition-icon--tip);-webkit-mask-repeat:no-repeat;mask-repeat:no-repeat;-webkit-mask-size:contain;mask-size:contain}.md-typeset :-webkit-any(.admonition,details):-webkit-any(.success,.check,.done){border-color:#00c853}.md-typeset :-moz-any(.admonition,details):-moz-any(.success,.check,.done){border-color:#00c853}.md-typeset :is(.admonition,details):is(.success,.check,.done){border-color:#00c853}.md-typeset :-webkit-any(.success,.check,.done)>:-webkit-any(.admonition-title,summary){background-color:rgba(0,200,83,.1)}.md-typeset :-moz-any(.success,.check,.done)>:-moz-any(.admonition-title,summary){background-color:rgba(0,200,83,.1)}.md-typeset :is(.success,.check,.done)>:is(.admonition-title,summary){background-color:rgba(0,200,83,.1)}.md-typeset :-webkit-any(.success,.check,.done)>:-webkit-any(.admonition-title,summary):before{background-color:#00c853;-webkit-mask-image:var(--md-admonition-icon--success);mask-image:var(--md-admonition-icon--success);-webkit-mask-repeat:no-repeat;mask-repeat:no-repeat;-webkit-mask-size:contain;mask-size:contain}.md-typeset :-moz-any(.success,.check,.done)>:-moz-any(.admonition-title,summary):before{background-color:#00c853;mask-image:var(--md-admonition-icon--success);mask-repeat:no-repeat;mask-size:contain}.md-typeset :is(.success,.check,.done)>:is(.admonition-title,summary):before{background-color:#00c853;-webkit-mask-image:var(--md-admonition-icon--success);mask-image:var(--md-admonition-icon--success);-webkit-mask-repeat:no-repeat;mask-repeat:no-repeat;-webkit-mask-size:contain;mask-size:contain}.md-typeset :-webkit-any(.admonition,details):-webkit-any(.question,.help,.faq){border-color:#64dd17}.md-typeset :-moz-any(.admonition,details):-moz-any(.question,.help,.faq){border-color:#64dd17}.md-typeset :is(.admonition,details):is(.question,.help,.faq){border-color:#64dd17}.md-typeset :-webkit-any(.question,.help,.faq)>:-webkit-any(.admonition-title,summary){background-color:rgba(100,221,23,.1)}.md-typeset :-moz-any(.question,.help,.faq)>:-moz-any(.admonition-title,summary){background-color:rgba(100,221,23,.1)}.md-typeset :is(.question,.help,.faq)>:is(.admonition-title,summary){background-color:rgba(100,221,23,.1)}.md-typeset :-webkit-any(.question,.help,.faq)>:-webkit-any(.admonition-title,summary):before{background-color:#64dd17;-webkit-mask-image:var(--md-admonition-icon--question);mask-image:var(--md-admonition-icon--question);-webkit-mask-repeat:no-repeat;mask-repeat:no-repeat;-webkit-mask-size:contain;mask-size:contain}.md-typeset :-moz-any(.question,.help,.faq)>:-moz-any(.admonition-title,summary):before{background-color:#64dd17;mask-image:var(--md-admonition-icon--question);mask-repeat:no-repeat;mask-size:contain}.md-typeset :is(.question,.help,.faq)>:is(.admonition-title,summary):before{background-color:#64dd17;-webkit-mask-image:var(--md-admonition-icon--question);mask-image:var(--md-admonition-icon--question);-webkit-mask-repeat:no-repeat;mask-repeat:no-repeat;-webkit-mask-size:contain;mask-size:contain}.md-typeset :-webkit-any(.admonition,details):-webkit-any(.warning,.caution,.attention){border-color:#ff9100}.md-typeset :-moz-any(.admonition,details):-moz-any(.warning,.caution,.attention){border-color:#ff9100}.md-typeset :is(.admonition,details):is(.warning,.caution,.attention){border-color:#ff9100}.md-typeset :-webkit-any(.warning,.caution,.attention)>:-webkit-any(.admonition-title,summary){background-color:rgba(255,145,0,.1)}.md-typeset :-moz-any(.warning,.caution,.attention)>:-moz-any(.admonition-title,summary){background-color:rgba(255,145,0,.1)}.md-typeset :is(.warning,.caution,.attention)>:is(.admonition-title,summary){background-color:rgba(255,145,0,.1)}.md-typeset :-webkit-any(.warning,.caution,.attention)>:-webkit-any(.admonition-title,summary):before{background-color:#ff9100;-webkit-mask-image:var(--md-admonition-icon--warning);mask-image:var(--md-admonition-icon--warning);-webkit-mask-repeat:no-repeat;mask-repeat:no-repeat;-webkit-mask-size:contain;mask-size:contain}.md-typeset :-moz-any(.warning,.caution,.attention)>:-moz-any(.admonition-title,summary):before{background-color:#ff9100;mask-image:var(--md-admonition-icon--warning);mask-repeat:no-repeat;mask-size:contain}.md-typeset :is(.warning,.caution,.attention)>:is(.admonition-title,summary):before{background-color:#ff9100;-webkit-mask-image:var(--md-admonition-icon--warning);mask-image:var(--md-admonition-icon--warning);-webkit-mask-repeat:no-repeat;mask-repeat:no-repeat;-webkit-mask-size:contain;mask-size:contain}.md-typeset :-webkit-any(.admonition,details):-webkit-any(.failure,.fail,.missing){border-color:#ff5252}.md-typeset :-moz-any(.admonition,details):-moz-any(.failure,.fail,.missing){border-color:#ff5252}.md-typeset :is(.admonition,details):is(.failure,.fail,.missing){border-color:#ff5252}.md-typeset :-webkit-any(.failure,.fail,.missing)>:-webkit-any(.admonition-title,summary){background-color:rgba(255,82,82,.1)}.md-typeset :-moz-any(.failure,.fail,.missing)>:-moz-any(.admonition-title,summary){background-color:rgba(255,82,82,.1)}.md-typeset :is(.failure,.fail,.missing)>:is(.admonition-title,summary){background-color:rgba(255,82,82,.1)}.md-typeset :-webkit-any(.failure,.fail,.missing)>:-webkit-any(.admonition-title,summary):before{background-color:#ff5252;-webkit-mask-image:var(--md-admonition-icon--failure);mask-image:var(--md-admonition-icon--failure);-webkit-mask-repeat:no-repeat;mask-repeat:no-repeat;-webkit-mask-size:contain;mask-size:contain}.md-typeset :-moz-any(.failure,.fail,.missing)>:-moz-any(.admonition-title,summary):before{background-color:#ff5252;mask-image:var(--md-admonition-icon--failure);mask-repeat:no-repeat;mask-size:contain}.md-typeset :is(.failure,.fail,.missing)>:is(.admonition-title,summary):before{background-color:#ff5252;-webkit-mask-image:var(--md-admonition-icon--failure);mask-image:var(--md-admonition-icon--failure);-webkit-mask-repeat:no-repeat;mask-repeat:no-repeat;-webkit-mask-size:contain;mask-size:contain}.md-typeset :-webkit-any(.admonition,details):-webkit-any(.danger,.error){border-color:#ff1744}.md-typeset :-moz-any(.admonition,details):-moz-any(.danger,.error){border-color:#ff1744}.md-typeset :is(.admonition,details):is(.danger,.error){border-color:#ff1744}.md-typeset :-webkit-any(.danger,.error)>:-webkit-any(.admonition-title,summary){background-color:rgba(255,23,68,.1)}.md-typeset :-moz-any(.danger,.error)>:-moz-any(.admonition-title,summary){background-color:rgba(255,23,68,.1)}.md-typeset :is(.danger,.error)>:is(.admonition-title,summary){background-color:rgba(255,23,68,.1)}.md-typeset :-webkit-any(.danger,.error)>:-webkit-any(.admonition-title,summary):before{background-color:#ff1744;-webkit-mask-image:var(--md-admonition-icon--danger);mask-image:var(--md-admonition-icon--danger);-webkit-mask-repeat:no-repeat;mask-repeat:no-repeat;-webkit-mask-size:contain;mask-size:contain}.md-typeset :-moz-any(.danger,.error)>:-moz-any(.admonition-title,summary):before{background-color:#ff1744;mask-image:var(--md-admonition-icon--danger);mask-repeat:no-repeat;mask-size:contain}.md-typeset :is(.danger,.error)>:is(.admonition-title,summary):before{background-color:#ff1744;-webkit-mask-image:var(--md-admonition-icon--danger);mask-image:var(--md-admonition-icon--danger);-webkit-mask-repeat:no-repeat;mask-repeat:no-repeat;-webkit-mask-size:contain;mask-size:contain}.md-typeset :-webkit-any(.admonition,details):-webkit-any(.bug){border-color:#f50057}.md-typeset :-moz-any(.admonition,details):-moz-any(.bug){border-color:#f50057}.md-typeset :is(.admonition,details):is(.bug){border-color:#f50057}.md-typeset :-webkit-any(.bug)>:-webkit-any(.admonition-title,summary){background-color:rgba(245,0,87,.1)}.md-typeset :-moz-any(.bug)>:-moz-any(.admonition-title,summary){background-color:rgba(245,0,87,.1)}.md-typeset :is(.bug)>:is(.admonition-title,summary){background-color:rgba(245,0,87,.1)}.md-typeset :-webkit-any(.bug)>:-webkit-any(.admonition-title,summary):before{background-color:#f50057;-webkit-mask-image:var(--md-admonition-icon--bug);mask-image:var(--md-admonition-icon--bug);-webkit-mask-repeat:no-repeat;mask-repeat:no-repeat;-webkit-mask-size:contain;mask-size:contain}.md-typeset :-moz-any(.bug)>:-moz-any(.admonition-title,summary):before{background-color:#f50057;mask-image:var(--md-admonition-icon--bug);mask-repeat:no-repeat;mask-size:contain}.md-typeset :is(.bug)>:is(.admonition-title,summary):before{background-color:#f50057;-webkit-mask-image:var(--md-admonition-icon--bug);mask-image:var(--md-admonition-icon--bug);-webkit-mask-repeat:no-repeat;mask-repeat:no-repeat;-webkit-mask-size:contain;mask-size:contain}.md-typeset :-webkit-any(.admonition,details):-webkit-any(.example){border-color:#7c4dff}.md-typeset :-moz-any(.admonition,details):-moz-any(.example){border-color:#7c4dff}.md-typeset :is(.admonition,details):is(.example){border-color:#7c4dff}.md-typeset :-webkit-any(.example)>:-webkit-any(.admonition-title,summary){background-color:rgba(124,77,255,.1)}.md-typeset :-moz-any(.example)>:-moz-any(.admonition-title,summary){background-color:rgba(124,77,255,.1)}.md-typeset :is(.example)>:is(.admonition-title,summary){background-color:rgba(124,77,255,.1)}.md-typeset :-webkit-any(.example)>:-webkit-any(.admonition-title,summary):before{background-color:#7c4dff;-webkit-mask-image:var(--md-admonition-icon--example);mask-image:var(--md-admonition-icon--example);-webkit-mask-repeat:no-repeat;mask-repeat:no-repeat;-webkit-mask-size:contain;mask-size:contain}.md-typeset :-moz-any(.example)>:-moz-any(.admonition-title,summary):before{background-color:#7c4dff;mask-image:var(--md-admonition-icon--example);mask-repeat:no-repeat;mask-size:contain}.md-typeset :is(.example)>:is(.admonition-title,summary):before{background-color:#7c4dff;-webkit-mask-image:var(--md-admonition-icon--example);mask-image:var(--md-admonition-icon--example);-webkit-mask-repeat:no-repeat;mask-repeat:no-repeat;-webkit-mask-size:contain;mask-size:contain}.md-typeset :-webkit-any(.admonition,details):-webkit-any(.quote,.cite){border-color:#9e9e9e}.md-typeset :-moz-any(.admonition,details):-moz-any(.quote,.cite){border-color:#9e9e9e}.md-typeset :is(.admonition,details):is(.quote,.cite){border-color:#9e9e9e}.md-typeset :-webkit-any(.quote,.cite)>:-webkit-any(.admonition-title,summary){background-color:hsla(0,0%,62%,.1)}.md-typeset :-moz-any(.quote,.cite)>:-moz-any(.admonition-title,summary){background-color:hsla(0,0%,62%,.1)}.md-typeset :is(.quote,.cite)>:is(.admonition-title,summary){background-color:hsla(0,0%,62%,.1)}.md-typeset :-webkit-any(.quote,.cite)>:-webkit-any(.admonition-title,summary):before{background-color:#9e9e9e;-webkit-mask-image:var(--md-admonition-icon--quote);mask-image:var(--md-admonition-icon--quote);-webkit-mask-repeat:no-repeat;mask-repeat:no-repeat;-webkit-mask-size:contain;mask-size:contain}.md-typeset :-moz-any(.quote,.cite)>:-moz-any(.admonition-title,summary):before{background-color:#9e9e9e;mask-image:var(--md-admonition-icon--quote);mask-repeat:no-repeat;mask-size:contain}.md-typeset :is(.quote,.cite)>:is(.admonition-title,summary):before{background-color:#9e9e9e;-webkit-mask-image:var(--md-admonition-icon--quote);mask-image:var(--md-admonition-icon--quote);-webkit-mask-repeat:no-repeat;mask-repeat:no-repeat;-webkit-mask-size:contain;mask-size:contain}:root{--md-footnotes-icon:url('data:image/svg+xml;charset=utf-8,')}.md-typeset .footnote{color:var(--md-default-fg-color--light);font-size:.64rem}[dir=ltr] .md-typeset .footnote>ol{margin-left:0}[dir=rtl] .md-typeset .footnote>ol{margin-right:0}.md-typeset .footnote>ol>li{transition:color 125ms}.md-typeset .footnote>ol>li:target{color:var(--md-default-fg-color)}.md-typeset .footnote>ol>li:focus-within .footnote-backref{opacity:1;transform:translateX(0);transition:none}.md-typeset .footnote>ol>li:-webkit-any(:hover,:target) .footnote-backref{opacity:1;transform:translateX(0)}.md-typeset .footnote>ol>li:-moz-any(:hover,:target) .footnote-backref{opacity:1;transform:translateX(0)}.md-typeset .footnote>ol>li:is(:hover,:target) .footnote-backref{opacity:1;transform:translateX(0)}.md-typeset .footnote>ol>li>:first-child{margin-top:0}.md-typeset .footnote-ref{font-size:.75em;font-weight:700}html .md-typeset .footnote-ref{outline-offset:.1rem}.md-typeset [id^="fnref:"]:target>.footnote-ref{outline:auto}.md-typeset .footnote-backref{color:var(--md-typeset-a-color);display:inline-block;font-size:0;opacity:0;transform:translateX(.25rem);transition:color .25s,transform .25s .25s,opacity 125ms .25s;vertical-align:text-bottom}@media print{.md-typeset .footnote-backref{color:var(--md-typeset-a-color);opacity:1;transform:translateX(0)}}[dir=rtl] .md-typeset .footnote-backref{transform:translateX(-.25rem)}.md-typeset .footnote-backref:hover{color:var(--md-accent-fg-color)}.md-typeset .footnote-backref:before{background-color:currentcolor;content:"";display:inline-block;height:.8rem;-webkit-mask-image:var(--md-footnotes-icon);mask-image:var(--md-footnotes-icon);-webkit-mask-repeat:no-repeat;mask-repeat:no-repeat;-webkit-mask-size:contain;mask-size:contain;width:.8rem}[dir=rtl] .md-typeset .footnote-backref:before svg{transform:scaleX(-1)}[dir=ltr] .md-typeset .headerlink{margin-left:.5rem}[dir=rtl] .md-typeset .headerlink{margin-right:.5rem}.md-typeset .headerlink{color:var(--md-default-fg-color--lighter);display:inline-block;opacity:0;transition:color .25s,opacity 125ms}@media print{.md-typeset .headerlink{display:none}}.md-typeset .headerlink:focus,.md-typeset :-webkit-any(:hover,:target)>.headerlink{opacity:1;-webkit-transition:color .25s,opacity 125ms;transition:color .25s,opacity 125ms}.md-typeset .headerlink:focus,.md-typeset :-moz-any(:hover,:target)>.headerlink{opacity:1;-moz-transition:color .25s,opacity 125ms;transition:color .25s,opacity 125ms}.md-typeset .headerlink:focus,.md-typeset :is(:hover,:target)>.headerlink{opacity:1;transition:color .25s,opacity 125ms}.md-typeset .headerlink:-webkit-any(:focus,:hover),.md-typeset :target>.headerlink{color:var(--md-accent-fg-color)}.md-typeset .headerlink:-moz-any(:focus,:hover),.md-typeset :target>.headerlink{color:var(--md-accent-fg-color)}.md-typeset .headerlink:is(:focus,:hover),.md-typeset :target>.headerlink{color:var(--md-accent-fg-color)}.md-typeset :target{--md-scroll-margin:3.6rem;--md-scroll-offset:0rem;scroll-margin-top:calc(var(--md-scroll-margin) - var(--md-scroll-offset))}@media screen and (min-width:76.25em){.md-header--lifted~.md-container .md-typeset :target{--md-scroll-margin:6rem}}.md-typeset :-webkit-any(h1,h2,h3):target{--md-scroll-offset:0.2rem}.md-typeset :-moz-any(h1,h2,h3):target{--md-scroll-offset:0.2rem}.md-typeset :is(h1,h2,h3):target{--md-scroll-offset:0.2rem}.md-typeset h4:target{--md-scroll-offset:0.15rem}.md-typeset div.arithmatex{overflow:auto}@media screen and (max-width:44.9375em){.md-typeset div.arithmatex{margin:0 -.8rem}}.md-typeset div.arithmatex>*{margin-left:auto!important;margin-right:auto!important;padding:0 .8rem;touch-action:auto;width:-webkit-min-content;width:-moz-min-content;width:min-content}.md-typeset div.arithmatex>* mjx-container{margin:0!important}.md-typeset :-webkit-any(del,ins,.comment).critic{-webkit-box-decoration-break:clone;box-decoration-break:clone}.md-typeset :-moz-any(del,ins,.comment).critic{box-decoration-break:clone}.md-typeset :is(del,ins,.comment).critic{-webkit-box-decoration-break:clone;box-decoration-break:clone}.md-typeset del.critic{background-color:var(--md-typeset-del-color)}.md-typeset ins.critic{background-color:var(--md-typeset-ins-color)}.md-typeset .critic.comment{color:var(--md-code-hl-comment-color)}.md-typeset .critic.comment:before{content:"/* "}.md-typeset .critic.comment:after{content:" */"}.md-typeset .critic.block{box-shadow:none;display:block;margin:1em 0;overflow:auto;padding-left:.8rem;padding-right:.8rem}.md-typeset .critic.block>:first-child{margin-top:.5em}.md-typeset .critic.block>:last-child{margin-bottom:.5em}:root{--md-details-icon:url('data:image/svg+xml;charset=utf-8,')}.md-typeset details{display:flow-root;overflow:visible;padding-top:0}.md-typeset details[open]>summary:after{transform:rotate(90deg)}.md-typeset details:not([open]){box-shadow:none;padding-bottom:0}.md-typeset details:not([open])>summary{border-radius:.1rem}[dir=ltr] .md-typeset summary{padding-right:1.8rem}[dir=rtl] .md-typeset summary{padding-left:1.8rem}[dir=ltr] .md-typeset summary{border-top-left-radius:.1rem}[dir=ltr] .md-typeset summary,[dir=rtl] .md-typeset summary{border-top-right-radius:.1rem}[dir=rtl] .md-typeset summary{border-top-left-radius:.1rem}.md-typeset summary{cursor:pointer;display:block;min-height:1rem}.md-typeset summary.focus-visible{outline-color:var(--md-accent-fg-color);outline-offset:.2rem}.md-typeset summary:not(.focus-visible){-webkit-tap-highlight-color:transparent;outline:none}[dir=ltr] .md-typeset summary:after{right:.4rem}[dir=rtl] .md-typeset summary:after{left:.4rem}.md-typeset summary:after{background-color:currentcolor;content:"";height:1rem;-webkit-mask-image:var(--md-details-icon);mask-image:var(--md-details-icon);-webkit-mask-repeat:no-repeat;mask-repeat:no-repeat;-webkit-mask-size:contain;mask-size:contain;position:absolute;top:.625em;transform:rotate(0deg);transition:transform .25s;width:1rem}[dir=rtl] .md-typeset summary:after{transform:rotate(180deg)}.md-typeset summary::marker{display:none}.md-typeset summary::-webkit-details-marker{display:none}.md-typeset :-webkit-any(.emojione,.twemoji,.gemoji){display:inline-flex;height:1.125em;vertical-align:text-top}.md-typeset :-moz-any(.emojione,.twemoji,.gemoji){display:inline-flex;height:1.125em;vertical-align:text-top}.md-typeset :is(.emojione,.twemoji,.gemoji){display:inline-flex;height:1.125em;vertical-align:text-top}.md-typeset :-webkit-any(.emojione,.twemoji,.gemoji) svg{fill:currentcolor;max-height:100%;width:1.125em}.md-typeset :-moz-any(.emojione,.twemoji,.gemoji) svg{fill:currentcolor;max-height:100%;width:1.125em}.md-typeset :is(.emojione,.twemoji,.gemoji) svg{fill:currentcolor;max-height:100%;width:1.125em}.highlight :-webkit-any(.o,.ow){color:var(--md-code-hl-operator-color)}.highlight :-moz-any(.o,.ow){color:var(--md-code-hl-operator-color)}.highlight :is(.o,.ow){color:var(--md-code-hl-operator-color)}.highlight .p{color:var(--md-code-hl-punctuation-color)}.highlight :-webkit-any(.cpf,.l,.s,.sb,.sc,.s2,.si,.s1,.ss){color:var(--md-code-hl-string-color)}.highlight :-moz-any(.cpf,.l,.s,.sb,.sc,.s2,.si,.s1,.ss){color:var(--md-code-hl-string-color)}.highlight :is(.cpf,.l,.s,.sb,.sc,.s2,.si,.s1,.ss){color:var(--md-code-hl-string-color)}.highlight :-webkit-any(.cp,.se,.sh,.sr,.sx){color:var(--md-code-hl-special-color)}.highlight :-moz-any(.cp,.se,.sh,.sr,.sx){color:var(--md-code-hl-special-color)}.highlight :is(.cp,.se,.sh,.sr,.sx){color:var(--md-code-hl-special-color)}.highlight :-webkit-any(.m,.mb,.mf,.mh,.mi,.il,.mo){color:var(--md-code-hl-number-color)}.highlight :-moz-any(.m,.mb,.mf,.mh,.mi,.il,.mo){color:var(--md-code-hl-number-color)}.highlight :is(.m,.mb,.mf,.mh,.mi,.il,.mo){color:var(--md-code-hl-number-color)}.highlight :-webkit-any(.k,.kd,.kn,.kp,.kr,.kt){color:var(--md-code-hl-keyword-color)}.highlight :-moz-any(.k,.kd,.kn,.kp,.kr,.kt){color:var(--md-code-hl-keyword-color)}.highlight :is(.k,.kd,.kn,.kp,.kr,.kt){color:var(--md-code-hl-keyword-color)}.highlight :-webkit-any(.kc,.n){color:var(--md-code-hl-name-color)}.highlight :-moz-any(.kc,.n){color:var(--md-code-hl-name-color)}.highlight :is(.kc,.n){color:var(--md-code-hl-name-color)}.highlight :-webkit-any(.no,.nb,.bp){color:var(--md-code-hl-constant-color)}.highlight :-moz-any(.no,.nb,.bp){color:var(--md-code-hl-constant-color)}.highlight :is(.no,.nb,.bp){color:var(--md-code-hl-constant-color)}.highlight :-webkit-any(.nc,.ne,.nf,.nn){color:var(--md-code-hl-function-color)}.highlight :-moz-any(.nc,.ne,.nf,.nn){color:var(--md-code-hl-function-color)}.highlight :is(.nc,.ne,.nf,.nn){color:var(--md-code-hl-function-color)}.highlight :-webkit-any(.nd,.ni,.nl,.nt){color:var(--md-code-hl-keyword-color)}.highlight :-moz-any(.nd,.ni,.nl,.nt){color:var(--md-code-hl-keyword-color)}.highlight :is(.nd,.ni,.nl,.nt){color:var(--md-code-hl-keyword-color)}.highlight :-webkit-any(.c,.cm,.c1,.ch,.cs,.sd){color:var(--md-code-hl-comment-color)}.highlight :-moz-any(.c,.cm,.c1,.ch,.cs,.sd){color:var(--md-code-hl-comment-color)}.highlight :is(.c,.cm,.c1,.ch,.cs,.sd){color:var(--md-code-hl-comment-color)}.highlight :-webkit-any(.na,.nv,.vc,.vg,.vi){color:var(--md-code-hl-variable-color)}.highlight :-moz-any(.na,.nv,.vc,.vg,.vi){color:var(--md-code-hl-variable-color)}.highlight :is(.na,.nv,.vc,.vg,.vi){color:var(--md-code-hl-variable-color)}.highlight :-webkit-any(.ge,.gr,.gh,.go,.gp,.gs,.gu,.gt){color:var(--md-code-hl-generic-color)}.highlight :-moz-any(.ge,.gr,.gh,.go,.gp,.gs,.gu,.gt){color:var(--md-code-hl-generic-color)}.highlight :is(.ge,.gr,.gh,.go,.gp,.gs,.gu,.gt){color:var(--md-code-hl-generic-color)}.highlight :-webkit-any(.gd,.gi){border-radius:.1rem;margin:0 -.125em;padding:0 .125em}.highlight :-moz-any(.gd,.gi){border-radius:.1rem;margin:0 -.125em;padding:0 .125em}.highlight :is(.gd,.gi){border-radius:.1rem;margin:0 -.125em;padding:0 .125em}.highlight .gd{background-color:var(--md-typeset-del-color)}.highlight .gi{background-color:var(--md-typeset-ins-color)}.highlight .hll{background-color:var(--md-code-hl-color);display:block;margin:0 -1.1764705882em;padding:0 1.1764705882em}.highlight span.filename{background-color:var(--md-code-bg-color);border-bottom:.05rem solid var(--md-default-fg-color--lightest);border-top-left-radius:.1rem;border-top-right-radius:.1rem;display:flow-root;font-size:.85em;font-weight:700;margin-top:1em;padding:.6617647059em 1.1764705882em;position:relative}.highlight span.filename+pre{margin-top:0}.highlight span.filename+pre>code{border-top-left-radius:0;border-top-right-radius:0}.highlight [data-linenos]:before{background-color:var(--md-code-bg-color);box-shadow:-.05rem 0 var(--md-default-fg-color--lightest) inset;color:var(--md-default-fg-color--light);content:attr(data-linenos);float:left;left:-1.1764705882em;margin-left:-1.1764705882em;margin-right:1.1764705882em;padding-left:1.1764705882em;position:-webkit-sticky;position:sticky;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;z-index:3}.highlight code a[id]{position:absolute;visibility:hidden}.highlight code[data-md-copying] .hll{display:contents}.highlight code[data-md-copying] .md-annotation{display:none}.highlighttable{display:flow-root}.highlighttable :-webkit-any(tbody,td){display:block;padding:0}.highlighttable :-moz-any(tbody,td){display:block;padding:0}.highlighttable :is(tbody,td){display:block;padding:0}.highlighttable tr{display:flex}.highlighttable pre{margin:0}.highlighttable th.filename{flex-grow:1;padding:0;text-align:left}.highlighttable th.filename span.filename{margin-top:0}.highlighttable .linenos{background-color:var(--md-code-bg-color);border-bottom-left-radius:.1rem;border-top-left-radius:.1rem;font-size:.85em;padding:.7720588235em 0 .7720588235em 1.1764705882em;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.highlighttable .linenodiv{box-shadow:-.05rem 0 var(--md-default-fg-color--lightest) inset;padding-right:.5882352941em}.highlighttable .linenodiv pre{color:var(--md-default-fg-color--light);text-align:right}.highlighttable .code{flex:1;min-width:0}.linenodiv a{color:inherit}.md-typeset .highlighttable{direction:ltr;margin:1em 0}.md-typeset .highlighttable>tbody>tr>.code>div>pre>code{border-bottom-left-radius:0;border-top-left-radius:0}.md-typeset .highlight+.result{border:.05rem solid var(--md-code-bg-color);border-bottom-left-radius:.1rem;border-bottom-right-radius:.1rem;border-top-width:.1rem;margin-top:-1.125em;overflow:visible;padding:0 1em}.md-typeset .highlight+.result:after{clear:both;content:"";display:block}@media screen and (max-width:44.9375em){.md-content__inner>.highlight{margin:1em -.8rem}.md-content__inner>.highlight>.filename,.md-content__inner>.highlight>.highlighttable>tbody>tr>.code>div>pre>code,.md-content__inner>.highlight>.highlighttable>tbody>tr>.filename span.filename,.md-content__inner>.highlight>.highlighttable>tbody>tr>.linenos,.md-content__inner>.highlight>pre>code{border-radius:0}.md-content__inner>.highlight+.result{border-left-width:0;border-radius:0;border-right-width:0;margin-left:-.8rem;margin-right:-.8rem}}.md-typeset .keys kbd:-webkit-any(:before,:after){-moz-osx-font-smoothing:initial;-webkit-font-smoothing:initial;color:inherit;margin:0;position:relative}.md-typeset .keys kbd:-moz-any(:before,:after){-moz-osx-font-smoothing:initial;-webkit-font-smoothing:initial;color:inherit;margin:0;position:relative}.md-typeset .keys kbd:is(:before,:after){-moz-osx-font-smoothing:initial;-webkit-font-smoothing:initial;color:inherit;margin:0;position:relative}.md-typeset .keys span{color:var(--md-default-fg-color--light);padding:0 .2em}.md-typeset .keys .key-alt:before,.md-typeset .keys .key-left-alt:before,.md-typeset .keys .key-right-alt:before{content:"⎇";padding-right:.4em}.md-typeset .keys .key-command:before,.md-typeset .keys .key-left-command:before,.md-typeset .keys .key-right-command:before{content:"⌘";padding-right:.4em}.md-typeset .keys .key-control:before,.md-typeset .keys .key-left-control:before,.md-typeset .keys .key-right-control:before{content:"⌃";padding-right:.4em}.md-typeset .keys .key-left-meta:before,.md-typeset .keys .key-meta:before,.md-typeset .keys .key-right-meta:before{content:"◆";padding-right:.4em}.md-typeset .keys .key-left-option:before,.md-typeset .keys .key-option:before,.md-typeset .keys .key-right-option:before{content:"⌥";padding-right:.4em}.md-typeset .keys .key-left-shift:before,.md-typeset .keys .key-right-shift:before,.md-typeset .keys .key-shift:before{content:"⇧";padding-right:.4em}.md-typeset .keys .key-left-super:before,.md-typeset .keys .key-right-super:before,.md-typeset .keys .key-super:before{content:"❖";padding-right:.4em}.md-typeset .keys .key-left-windows:before,.md-typeset .keys .key-right-windows:before,.md-typeset .keys .key-windows:before{content:"⊞";padding-right:.4em}.md-typeset .keys .key-arrow-down:before{content:"↓";padding-right:.4em}.md-typeset .keys .key-arrow-left:before{content:"←";padding-right:.4em}.md-typeset .keys .key-arrow-right:before{content:"→";padding-right:.4em}.md-typeset .keys .key-arrow-up:before{content:"↑";padding-right:.4em}.md-typeset .keys .key-backspace:before{content:"⌫";padding-right:.4em}.md-typeset .keys .key-backtab:before{content:"⇤";padding-right:.4em}.md-typeset .keys .key-caps-lock:before{content:"⇪";padding-right:.4em}.md-typeset .keys .key-clear:before{content:"⌧";padding-right:.4em}.md-typeset .keys .key-context-menu:before{content:"☰";padding-right:.4em}.md-typeset .keys .key-delete:before{content:"⌦";padding-right:.4em}.md-typeset .keys .key-eject:before{content:"⏏";padding-right:.4em}.md-typeset .keys .key-end:before{content:"⤓";padding-right:.4em}.md-typeset .keys .key-escape:before{content:"⎋";padding-right:.4em}.md-typeset .keys .key-home:before{content:"⤒";padding-right:.4em}.md-typeset .keys .key-insert:before{content:"⎀";padding-right:.4em}.md-typeset .keys .key-page-down:before{content:"⇟";padding-right:.4em}.md-typeset .keys .key-page-up:before{content:"⇞";padding-right:.4em}.md-typeset .keys .key-print-screen:before{content:"⎙";padding-right:.4em}.md-typeset .keys .key-tab:after{content:"⇥";padding-left:.4em}.md-typeset .keys .key-num-enter:after{content:"⌤";padding-left:.4em}.md-typeset .keys .key-enter:after{content:"⏎";padding-left:.4em}:root{--md-tabbed-icon--prev:url('data:image/svg+xml;charset=utf-8,');--md-tabbed-icon--next:url('data:image/svg+xml;charset=utf-8,')}.md-typeset .tabbed-set{border-radius:.1rem;display:flex;flex-flow:column wrap;margin:1em 0;position:relative}.md-typeset .tabbed-set>input{height:0;opacity:0;position:absolute;width:0}.md-typeset .tabbed-set>input:target{--md-scroll-offset:0.625em}.md-typeset .tabbed-labels{-ms-overflow-style:none;box-shadow:0 -.05rem var(--md-default-fg-color--lightest) inset;display:flex;max-width:100%;overflow:auto;scrollbar-width:none}@media print{.md-typeset .tabbed-labels{display:contents}}@media screen{.js .md-typeset .tabbed-labels{position:relative}.js .md-typeset .tabbed-labels:before{background:var(--md-accent-fg-color);bottom:0;content:"";display:block;height:2px;left:0;position:absolute;transform:translateX(var(--md-indicator-x));transition:width 225ms,transform .25s;transition-timing-function:cubic-bezier(.4,0,.2,1);width:var(--md-indicator-width)}}.md-typeset .tabbed-labels::-webkit-scrollbar{display:none}.md-typeset .tabbed-labels>label{border-bottom:.1rem solid transparent;border-radius:.1rem .1rem 0 0;color:var(--md-default-fg-color--light);cursor:pointer;flex-shrink:0;font-size:.64rem;font-weight:700;padding:.78125em 1.25em .625em;scroll-margin-inline-start:1rem;transition:background-color .25s,color .25s;white-space:nowrap;width:auto}@media print{.md-typeset .tabbed-labels>label:first-child{order:1}.md-typeset .tabbed-labels>label:nth-child(2){order:2}.md-typeset .tabbed-labels>label:nth-child(3){order:3}.md-typeset .tabbed-labels>label:nth-child(4){order:4}.md-typeset .tabbed-labels>label:nth-child(5){order:5}.md-typeset .tabbed-labels>label:nth-child(6){order:6}.md-typeset .tabbed-labels>label:nth-child(7){order:7}.md-typeset .tabbed-labels>label:nth-child(8){order:8}.md-typeset .tabbed-labels>label:nth-child(9){order:9}.md-typeset .tabbed-labels>label:nth-child(10){order:10}.md-typeset .tabbed-labels>label:nth-child(11){order:11}.md-typeset .tabbed-labels>label:nth-child(12){order:12}.md-typeset .tabbed-labels>label:nth-child(13){order:13}.md-typeset .tabbed-labels>label:nth-child(14){order:14}.md-typeset .tabbed-labels>label:nth-child(15){order:15}.md-typeset .tabbed-labels>label:nth-child(16){order:16}.md-typeset .tabbed-labels>label:nth-child(17){order:17}.md-typeset .tabbed-labels>label:nth-child(18){order:18}.md-typeset .tabbed-labels>label:nth-child(19){order:19}.md-typeset .tabbed-labels>label:nth-child(20){order:20}}.md-typeset .tabbed-labels>label:hover{color:var(--md-accent-fg-color)}.md-typeset .tabbed-content{width:100%}@media print{.md-typeset .tabbed-content{display:contents}}.md-typeset .tabbed-block{display:none}@media print{.md-typeset .tabbed-block{display:block}.md-typeset .tabbed-block:first-child{order:1}.md-typeset .tabbed-block:nth-child(2){order:2}.md-typeset .tabbed-block:nth-child(3){order:3}.md-typeset .tabbed-block:nth-child(4){order:4}.md-typeset .tabbed-block:nth-child(5){order:5}.md-typeset .tabbed-block:nth-child(6){order:6}.md-typeset .tabbed-block:nth-child(7){order:7}.md-typeset .tabbed-block:nth-child(8){order:8}.md-typeset .tabbed-block:nth-child(9){order:9}.md-typeset .tabbed-block:nth-child(10){order:10}.md-typeset .tabbed-block:nth-child(11){order:11}.md-typeset .tabbed-block:nth-child(12){order:12}.md-typeset .tabbed-block:nth-child(13){order:13}.md-typeset .tabbed-block:nth-child(14){order:14}.md-typeset .tabbed-block:nth-child(15){order:15}.md-typeset .tabbed-block:nth-child(16){order:16}.md-typeset .tabbed-block:nth-child(17){order:17}.md-typeset .tabbed-block:nth-child(18){order:18}.md-typeset .tabbed-block:nth-child(19){order:19}.md-typeset .tabbed-block:nth-child(20){order:20}}.md-typeset .tabbed-block>.highlight:first-child>pre,.md-typeset .tabbed-block>pre:first-child{margin:0}.md-typeset .tabbed-block>.highlight:first-child>pre>code,.md-typeset .tabbed-block>pre:first-child>code{border-top-left-radius:0;border-top-right-radius:0}.md-typeset .tabbed-block>.highlight:first-child>.filename{border-top-left-radius:0;border-top-right-radius:0;margin:0}.md-typeset .tabbed-block>.highlight:first-child>.highlighttable{margin:0}.md-typeset .tabbed-block>.highlight:first-child>.highlighttable>tbody>tr>.filename span.filename,.md-typeset .tabbed-block>.highlight:first-child>.highlighttable>tbody>tr>.linenos{border-top-left-radius:0;border-top-right-radius:0;margin:0}.md-typeset .tabbed-block>.highlight:first-child>.highlighttable>tbody>tr>.code>div>pre>code{border-top-left-radius:0;border-top-right-radius:0}.md-typeset .tabbed-block>.highlight:first-child+.result{margin-top:-.125em}.md-typeset .tabbed-block>.tabbed-set{margin:0}.md-typeset .tabbed-button{align-self:center;border-radius:100%;color:var(--md-default-fg-color--light);cursor:pointer;display:block;height:.9rem;margin-top:.1rem;pointer-events:auto;transition:background-color .25s;width:.9rem}.md-typeset .tabbed-button:hover{background-color:var(--md-accent-fg-color--transparent);color:var(--md-accent-fg-color)}.md-typeset .tabbed-button:after{background-color:currentcolor;content:"";display:block;height:100%;-webkit-mask-image:var(--md-tabbed-icon--prev);mask-image:var(--md-tabbed-icon--prev);-webkit-mask-repeat:no-repeat;mask-repeat:no-repeat;-webkit-mask-size:contain;mask-size:contain;transition:background-color .25s,transform .25s;width:100%}.md-typeset .tabbed-control{background:linear-gradient(to right,var(--md-default-bg-color) 60%,transparent);display:flex;height:1.9rem;justify-content:start;pointer-events:none;position:absolute;transition:opacity 125ms;width:1.2rem}[dir=rtl] .md-typeset .tabbed-control{transform:rotate(180deg)}.md-typeset .tabbed-control[hidden]{opacity:0}.md-typeset .tabbed-control--next{background:linear-gradient(to left,var(--md-default-bg-color) 60%,transparent);justify-content:end;right:0}.md-typeset .tabbed-control--next .tabbed-button:after{-webkit-mask-image:var(--md-tabbed-icon--next);mask-image:var(--md-tabbed-icon--next)}@media screen and (max-width:44.9375em){[dir=ltr] .md-content__inner>.tabbed-set .tabbed-labels{padding-left:.8rem}[dir=rtl] .md-content__inner>.tabbed-set .tabbed-labels{padding-right:.8rem}.md-content__inner>.tabbed-set .tabbed-labels{margin:0 -.8rem;max-width:100vw;scroll-padding-inline-start:.8rem}[dir=ltr] .md-content__inner>.tabbed-set .tabbed-labels:after{padding-right:.8rem}[dir=rtl] .md-content__inner>.tabbed-set .tabbed-labels:after{padding-left:.8rem}.md-content__inner>.tabbed-set .tabbed-labels:after{content:""}[dir=ltr] .md-content__inner>.tabbed-set .tabbed-labels~.tabbed-control--prev{margin-left:-.8rem}[dir=rtl] .md-content__inner>.tabbed-set .tabbed-labels~.tabbed-control--prev{margin-right:-.8rem}[dir=ltr] .md-content__inner>.tabbed-set .tabbed-labels~.tabbed-control--prev{padding-left:.8rem}[dir=rtl] .md-content__inner>.tabbed-set .tabbed-labels~.tabbed-control--prev{padding-right:.8rem}.md-content__inner>.tabbed-set .tabbed-labels~.tabbed-control--prev{width:2rem}[dir=ltr] .md-content__inner>.tabbed-set .tabbed-labels~.tabbed-control--next{margin-right:-.8rem}[dir=rtl] .md-content__inner>.tabbed-set .tabbed-labels~.tabbed-control--next{margin-left:-.8rem}[dir=ltr] .md-content__inner>.tabbed-set .tabbed-labels~.tabbed-control--next{padding-right:.8rem}[dir=rtl] .md-content__inner>.tabbed-set .tabbed-labels~.tabbed-control--next{padding-left:.8rem}.md-content__inner>.tabbed-set .tabbed-labels~.tabbed-control--next{width:2rem}}@media screen{.md-typeset .tabbed-set>input:first-child:checked~.tabbed-labels>:first-child,.md-typeset .tabbed-set>input:nth-child(10):checked~.tabbed-labels>:nth-child(10),.md-typeset .tabbed-set>input:nth-child(11):checked~.tabbed-labels>:nth-child(11),.md-typeset .tabbed-set>input:nth-child(12):checked~.tabbed-labels>:nth-child(12),.md-typeset .tabbed-set>input:nth-child(13):checked~.tabbed-labels>:nth-child(13),.md-typeset .tabbed-set>input:nth-child(14):checked~.tabbed-labels>:nth-child(14),.md-typeset .tabbed-set>input:nth-child(15):checked~.tabbed-labels>:nth-child(15),.md-typeset .tabbed-set>input:nth-child(16):checked~.tabbed-labels>:nth-child(16),.md-typeset .tabbed-set>input:nth-child(17):checked~.tabbed-labels>:nth-child(17),.md-typeset .tabbed-set>input:nth-child(18):checked~.tabbed-labels>:nth-child(18),.md-typeset .tabbed-set>input:nth-child(19):checked~.tabbed-labels>:nth-child(19),.md-typeset .tabbed-set>input:nth-child(2):checked~.tabbed-labels>:nth-child(2),.md-typeset .tabbed-set>input:nth-child(20):checked~.tabbed-labels>:nth-child(20),.md-typeset .tabbed-set>input:nth-child(3):checked~.tabbed-labels>:nth-child(3),.md-typeset .tabbed-set>input:nth-child(4):checked~.tabbed-labels>:nth-child(4),.md-typeset .tabbed-set>input:nth-child(5):checked~.tabbed-labels>:nth-child(5),.md-typeset .tabbed-set>input:nth-child(6):checked~.tabbed-labels>:nth-child(6),.md-typeset .tabbed-set>input:nth-child(7):checked~.tabbed-labels>:nth-child(7),.md-typeset .tabbed-set>input:nth-child(8):checked~.tabbed-labels>:nth-child(8),.md-typeset .tabbed-set>input:nth-child(9):checked~.tabbed-labels>:nth-child(9){color:var(--md-accent-fg-color)}.md-typeset .no-js .tabbed-set>input:first-child:checked~.tabbed-labels>:first-child,.md-typeset .no-js .tabbed-set>input:nth-child(10):checked~.tabbed-labels>:nth-child(10),.md-typeset .no-js .tabbed-set>input:nth-child(11):checked~.tabbed-labels>:nth-child(11),.md-typeset .no-js .tabbed-set>input:nth-child(12):checked~.tabbed-labels>:nth-child(12),.md-typeset .no-js .tabbed-set>input:nth-child(13):checked~.tabbed-labels>:nth-child(13),.md-typeset .no-js .tabbed-set>input:nth-child(14):checked~.tabbed-labels>:nth-child(14),.md-typeset .no-js .tabbed-set>input:nth-child(15):checked~.tabbed-labels>:nth-child(15),.md-typeset .no-js .tabbed-set>input:nth-child(16):checked~.tabbed-labels>:nth-child(16),.md-typeset .no-js .tabbed-set>input:nth-child(17):checked~.tabbed-labels>:nth-child(17),.md-typeset .no-js .tabbed-set>input:nth-child(18):checked~.tabbed-labels>:nth-child(18),.md-typeset .no-js .tabbed-set>input:nth-child(19):checked~.tabbed-labels>:nth-child(19),.md-typeset .no-js .tabbed-set>input:nth-child(2):checked~.tabbed-labels>:nth-child(2),.md-typeset .no-js .tabbed-set>input:nth-child(20):checked~.tabbed-labels>:nth-child(20),.md-typeset .no-js .tabbed-set>input:nth-child(3):checked~.tabbed-labels>:nth-child(3),.md-typeset .no-js .tabbed-set>input:nth-child(4):checked~.tabbed-labels>:nth-child(4),.md-typeset .no-js .tabbed-set>input:nth-child(5):checked~.tabbed-labels>:nth-child(5),.md-typeset .no-js .tabbed-set>input:nth-child(6):checked~.tabbed-labels>:nth-child(6),.md-typeset .no-js .tabbed-set>input:nth-child(7):checked~.tabbed-labels>:nth-child(7),.md-typeset .no-js .tabbed-set>input:nth-child(8):checked~.tabbed-labels>:nth-child(8),.md-typeset .no-js .tabbed-set>input:nth-child(9):checked~.tabbed-labels>:nth-child(9),.no-js .md-typeset .tabbed-set>input:first-child:checked~.tabbed-labels>:first-child,.no-js .md-typeset .tabbed-set>input:nth-child(10):checked~.tabbed-labels>:nth-child(10),.no-js .md-typeset .tabbed-set>input:nth-child(11):checked~.tabbed-labels>:nth-child(11),.no-js .md-typeset .tabbed-set>input:nth-child(12):checked~.tabbed-labels>:nth-child(12),.no-js .md-typeset .tabbed-set>input:nth-child(13):checked~.tabbed-labels>:nth-child(13),.no-js .md-typeset .tabbed-set>input:nth-child(14):checked~.tabbed-labels>:nth-child(14),.no-js .md-typeset .tabbed-set>input:nth-child(15):checked~.tabbed-labels>:nth-child(15),.no-js .md-typeset .tabbed-set>input:nth-child(16):checked~.tabbed-labels>:nth-child(16),.no-js .md-typeset .tabbed-set>input:nth-child(17):checked~.tabbed-labels>:nth-child(17),.no-js .md-typeset .tabbed-set>input:nth-child(18):checked~.tabbed-labels>:nth-child(18),.no-js .md-typeset .tabbed-set>input:nth-child(19):checked~.tabbed-labels>:nth-child(19),.no-js .md-typeset .tabbed-set>input:nth-child(2):checked~.tabbed-labels>:nth-child(2),.no-js .md-typeset .tabbed-set>input:nth-child(20):checked~.tabbed-labels>:nth-child(20),.no-js .md-typeset .tabbed-set>input:nth-child(3):checked~.tabbed-labels>:nth-child(3),.no-js .md-typeset .tabbed-set>input:nth-child(4):checked~.tabbed-labels>:nth-child(4),.no-js .md-typeset .tabbed-set>input:nth-child(5):checked~.tabbed-labels>:nth-child(5),.no-js .md-typeset .tabbed-set>input:nth-child(6):checked~.tabbed-labels>:nth-child(6),.no-js .md-typeset .tabbed-set>input:nth-child(7):checked~.tabbed-labels>:nth-child(7),.no-js .md-typeset .tabbed-set>input:nth-child(8):checked~.tabbed-labels>:nth-child(8),.no-js .md-typeset .tabbed-set>input:nth-child(9):checked~.tabbed-labels>:nth-child(9){border-color:var(--md-accent-fg-color)}}.md-typeset .tabbed-set>input:first-child.focus-visible~.tabbed-labels>:first-child,.md-typeset .tabbed-set>input:nth-child(10).focus-visible~.tabbed-labels>:nth-child(10),.md-typeset .tabbed-set>input:nth-child(11).focus-visible~.tabbed-labels>:nth-child(11),.md-typeset .tabbed-set>input:nth-child(12).focus-visible~.tabbed-labels>:nth-child(12),.md-typeset .tabbed-set>input:nth-child(13).focus-visible~.tabbed-labels>:nth-child(13),.md-typeset .tabbed-set>input:nth-child(14).focus-visible~.tabbed-labels>:nth-child(14),.md-typeset .tabbed-set>input:nth-child(15).focus-visible~.tabbed-labels>:nth-child(15),.md-typeset .tabbed-set>input:nth-child(16).focus-visible~.tabbed-labels>:nth-child(16),.md-typeset .tabbed-set>input:nth-child(17).focus-visible~.tabbed-labels>:nth-child(17),.md-typeset .tabbed-set>input:nth-child(18).focus-visible~.tabbed-labels>:nth-child(18),.md-typeset .tabbed-set>input:nth-child(19).focus-visible~.tabbed-labels>:nth-child(19),.md-typeset .tabbed-set>input:nth-child(2).focus-visible~.tabbed-labels>:nth-child(2),.md-typeset .tabbed-set>input:nth-child(20).focus-visible~.tabbed-labels>:nth-child(20),.md-typeset .tabbed-set>input:nth-child(3).focus-visible~.tabbed-labels>:nth-child(3),.md-typeset .tabbed-set>input:nth-child(4).focus-visible~.tabbed-labels>:nth-child(4),.md-typeset .tabbed-set>input:nth-child(5).focus-visible~.tabbed-labels>:nth-child(5),.md-typeset .tabbed-set>input:nth-child(6).focus-visible~.tabbed-labels>:nth-child(6),.md-typeset .tabbed-set>input:nth-child(7).focus-visible~.tabbed-labels>:nth-child(7),.md-typeset .tabbed-set>input:nth-child(8).focus-visible~.tabbed-labels>:nth-child(8),.md-typeset .tabbed-set>input:nth-child(9).focus-visible~.tabbed-labels>:nth-child(9){background-color:var(--md-accent-fg-color--transparent)}.md-typeset .tabbed-set>input:first-child:checked~.tabbed-content>:first-child,.md-typeset .tabbed-set>input:nth-child(10):checked~.tabbed-content>:nth-child(10),.md-typeset .tabbed-set>input:nth-child(11):checked~.tabbed-content>:nth-child(11),.md-typeset .tabbed-set>input:nth-child(12):checked~.tabbed-content>:nth-child(12),.md-typeset .tabbed-set>input:nth-child(13):checked~.tabbed-content>:nth-child(13),.md-typeset .tabbed-set>input:nth-child(14):checked~.tabbed-content>:nth-child(14),.md-typeset .tabbed-set>input:nth-child(15):checked~.tabbed-content>:nth-child(15),.md-typeset .tabbed-set>input:nth-child(16):checked~.tabbed-content>:nth-child(16),.md-typeset .tabbed-set>input:nth-child(17):checked~.tabbed-content>:nth-child(17),.md-typeset .tabbed-set>input:nth-child(18):checked~.tabbed-content>:nth-child(18),.md-typeset .tabbed-set>input:nth-child(19):checked~.tabbed-content>:nth-child(19),.md-typeset .tabbed-set>input:nth-child(2):checked~.tabbed-content>:nth-child(2),.md-typeset .tabbed-set>input:nth-child(20):checked~.tabbed-content>:nth-child(20),.md-typeset .tabbed-set>input:nth-child(3):checked~.tabbed-content>:nth-child(3),.md-typeset .tabbed-set>input:nth-child(4):checked~.tabbed-content>:nth-child(4),.md-typeset .tabbed-set>input:nth-child(5):checked~.tabbed-content>:nth-child(5),.md-typeset .tabbed-set>input:nth-child(6):checked~.tabbed-content>:nth-child(6),.md-typeset .tabbed-set>input:nth-child(7):checked~.tabbed-content>:nth-child(7),.md-typeset .tabbed-set>input:nth-child(8):checked~.tabbed-content>:nth-child(8),.md-typeset .tabbed-set>input:nth-child(9):checked~.tabbed-content>:nth-child(9){display:block}:root{--md-tasklist-icon:url('data:image/svg+xml;charset=utf-8,');--md-tasklist-icon--checked:url('data:image/svg+xml;charset=utf-8,')}.md-typeset .task-list-item{list-style-type:none;position:relative}[dir=ltr] .md-typeset .task-list-item [type=checkbox]{left:-2em}[dir=rtl] .md-typeset .task-list-item [type=checkbox]{right:-2em}.md-typeset .task-list-item [type=checkbox]{position:absolute;top:.45em}.md-typeset .task-list-control [type=checkbox]{opacity:0;z-index:-1}[dir=ltr] .md-typeset .task-list-indicator:before{left:-1.5em}[dir=rtl] .md-typeset .task-list-indicator:before{right:-1.5em}.md-typeset .task-list-indicator:before{background-color:var(--md-default-fg-color--lightest);content:"";height:1.25em;-webkit-mask-image:var(--md-tasklist-icon);mask-image:var(--md-tasklist-icon);-webkit-mask-repeat:no-repeat;mask-repeat:no-repeat;-webkit-mask-size:contain;mask-size:contain;position:absolute;top:.15em;width:1.25em}.md-typeset [type=checkbox]:checked+.task-list-indicator:before{background-color:#00e676;-webkit-mask-image:var(--md-tasklist-icon--checked);mask-image:var(--md-tasklist-icon--checked)}:root>*{--md-mermaid-font-family:var(--md-text-font-family),sans-serif;--md-mermaid-edge-color:var(--md-code-fg-color);--md-mermaid-node-bg-color:var(--md-accent-fg-color--transparent);--md-mermaid-node-fg-color:var(--md-accent-fg-color);--md-mermaid-label-bg-color:var(--md-default-bg-color);--md-mermaid-label-fg-color:var(--md-code-fg-color)}.mermaid{line-height:normal;margin:1em 0}@media screen and (min-width:45em){[dir=ltr] .md-typeset .inline{margin-right:.8rem}[dir=rtl] .md-typeset .inline{margin-left:.8rem}.md-typeset .inline{float:left;margin-bottom:.8rem;margin-top:0;width:11.7rem}[dir=rtl] .md-typeset .inline{float:right}[dir=ltr] .md-typeset .inline.end{margin-left:.8rem;margin-right:0}[dir=rtl] .md-typeset .inline.end{margin-left:0;margin-right:.8rem}.md-typeset .inline.end{float:right}[dir=rtl] .md-typeset .inline.end{float:left}} \ No newline at end of file diff --git a/develop/assets/stylesheets/main.1d29e8d0.min.css.map b/develop/assets/stylesheets/main.1d29e8d0.min.css.map new file mode 100644 index 0000000..cddba76 --- /dev/null +++ b/develop/assets/stylesheets/main.1d29e8d0.min.css.map @@ -0,0 +1 @@ +{"version":3,"sources":["src/assets/stylesheets/main/extensions/pymdownx/_keys.scss","../../../src/assets/stylesheets/main.scss","src/assets/stylesheets/main/_resets.scss","src/assets/stylesheets/main/_colors.scss","src/assets/stylesheets/main/_icons.scss","src/assets/stylesheets/main/_typeset.scss","src/assets/stylesheets/utilities/_break.scss","src/assets/stylesheets/main/layout/_banner.scss","src/assets/stylesheets/main/layout/_base.scss","src/assets/stylesheets/main/layout/_clipboard.scss","src/assets/stylesheets/main/layout/_content.scss","src/assets/stylesheets/main/layout/_dialog.scss","src/assets/stylesheets/main/layout/_footer.scss","src/assets/stylesheets/main/layout/_form.scss","src/assets/stylesheets/main/layout/_header.scss","src/assets/stylesheets/main/layout/_nav.scss","src/assets/stylesheets/main/layout/_search.scss","src/assets/stylesheets/main/layout/_select.scss","src/assets/stylesheets/main/layout/_sidebar.scss","src/assets/stylesheets/main/layout/_source.scss","src/assets/stylesheets/main/layout/_tabs.scss","src/assets/stylesheets/main/layout/_tag.scss","src/assets/stylesheets/main/layout/_tooltip.scss","src/assets/stylesheets/main/layout/_top.scss","src/assets/stylesheets/main/layout/_version.scss","src/assets/stylesheets/main/extensions/markdown/_admonition.scss","node_modules/material-design-color/material-color.scss","src/assets/stylesheets/main/extensions/markdown/_footnotes.scss","src/assets/stylesheets/main/extensions/markdown/_toc.scss","src/assets/stylesheets/main/extensions/pymdownx/_arithmatex.scss","src/assets/stylesheets/main/extensions/pymdownx/_critic.scss","src/assets/stylesheets/main/extensions/pymdownx/_details.scss","src/assets/stylesheets/main/extensions/pymdownx/_emoji.scss","src/assets/stylesheets/main/extensions/pymdownx/_highlight.scss","src/assets/stylesheets/main/extensions/pymdownx/_tabbed.scss","src/assets/stylesheets/main/extensions/pymdownx/_tasklist.scss","src/assets/stylesheets/main/integrations/_mermaid.scss","src/assets/stylesheets/main/_modifiers.scss"],"names":[],"mappings":"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mGJ,C+B/mGE,4CACE,mBAAA,CACA,cAAA,CACA,uB/B4mGJ,C+BzmGI,yDAGE,iBAAA,CADA,eAAA,CADA,a/B6mGN,C+B9mGI,sDAGE,iBAAA,CADA,eAAA,CADA,a/B6mGN,C+B9mGI,gDAGE,iBAAA,CADA,eAAA,CADA,a/B6mGN,CgCnnGE,gCACE,sChCsnGJ,CgCvnGE,6BACE,sChCsnGJ,CgCvnGE,uBACE,sChCsnGJ,CgCnnGE,cACE,yChCqnGJ,CgCzmGE,4DACE,oChC2mGJ,CgC5mGE,yDACE,oChC2mGJ,CgC5mGE,mDACE,oChC2mGJ,CgCnmGE,6CACE,qChCqmGJ,CgCtmGE,0CACE,qChCqmGJ,CgCtmGE,oCACE,qChCqmGJ,CgC3lGE,oDACE,oChC6lGJ,CgC9lGE,iDACE,oChC6lGJ,CgC9lGE,2CACE,oChC6lGJ,CgCplGE,gDACE,qChCslGJ,CgCvlGE,6CACE,qChCslGJ,CgCvlGE,uCACE,qChCslGJ,CgCjlGE,gCACE,kChCmlGJ,CgCplGE,6BACE,kChCmlGJ,CgCplGE,uBACE,kChCmlGJ,CgC7kGE,qCACE,sChC+kGJ,CgChlGE,kCACE,sChC+kGJ,CgChlGE,4BACE,sChC+kGJ,CgCxkGE,yCACE,sChC0kGJ,CgC3kGE,sCACE,sChC0kGJ,CgC3kGE,gCACE,sChC0kGJ,CgCnkGE,yCACE,qChCqkGJ,CgCtkGE,sCACE,qChCqkGJ,CgCtkGE,gCACE,qChCqkGJ,CgC5jGE,gDACE,qChC8jGJ,CgC/jGE,6CACE,qChC8jGJ,CgC/jGE,uCACE,qChC8jGJ,CgCtjGE,6CACE,sChCwjGJ,CgCzjGE,0CACE,sChCwjGJ,CgCzjGE,oCACE,sChCwjGJ,CgC7iGE,yDACE,qChC+iGJ,CgChjGE,sDACE,qChC+iGJ,CgChjGE,gDACE,qChC+iGJ,CgC1iGE,iCAGE,mBAAA,CAFA,gBAAA,CACA,gBhC6iGJ,CgC/iGE,8BAGE,mBAAA,CAFA,gBAAA,CACA,gBhC6iGJ,CgC/iGE,wBAGE,mBAAA,CAFA,gBAAA,CACA,gBhC6iGJ,CgCziGE,eACE,4ChC2iGJ,CgCxiGE,eACE,4Ch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","file":"main.css"} \ No newline at end of file diff --git a/develop/assets/stylesheets/palette.cbb835fc.min.css b/develop/assets/stylesheets/palette.cbb835fc.min.css new file mode 100644 index 0000000..30f9264 --- /dev/null +++ b/develop/assets/stylesheets/palette.cbb835fc.min.css @@ -0,0 +1 @@ +@media screen{[data-md-color-scheme=slate]{--md-hue:232;--md-default-fg-color:hsla(var(--md-hue),75%,95%,1);--md-default-fg-color--light:hsla(var(--md-hue),75%,90%,0.62);--md-default-fg-color--lighter:hsla(var(--md-hue),75%,90%,0.32);--md-default-fg-color--lightest:hsla(var(--md-hue),75%,90%,0.12);--md-default-bg-color:hsla(var(--md-hue),15%,21%,1);--md-default-bg-color--light:hsla(var(--md-hue),15%,21%,0.54);--md-default-bg-color--lighter:hsla(var(--md-hue),15%,21%,0.26);--md-default-bg-color--lightest:hsla(var(--md-hue),15%,21%,0.07);--md-code-fg-color:hsla(var(--md-hue),18%,86%,1);--md-code-bg-color:hsla(var(--md-hue),15%,15%,1);--md-code-hl-color:rgba(66,135,255,.15);--md-code-hl-number-color:#e6695b;--md-code-hl-special-color:#f06090;--md-code-hl-function-color:#c973d9;--md-code-hl-constant-color:#9383e2;--md-code-hl-keyword-color:#6791e0;--md-code-hl-string-color:#2fb170;--md-code-hl-name-color:var(--md-code-fg-color);--md-code-hl-operator-color:var(--md-default-fg-color--light);--md-code-hl-punctuation-color:var(--md-default-fg-color--light);--md-code-hl-comment-color:var(--md-default-fg-color--light);--md-code-hl-generic-color:var(--md-default-fg-color--light);--md-code-hl-variable-color:var(--md-default-fg-color--light);--md-typeset-color:var(--md-default-fg-color);--md-typeset-a-color:var(--md-primary-fg-color);--md-typeset-mark-color:rgba(66,135,255,.3);--md-typeset-kbd-color:hsla(var(--md-hue),15%,94%,0.12);--md-typeset-kbd-accent-color:hsla(var(--md-hue),15%,94%,0.2);--md-typeset-kbd-border-color:hsla(var(--md-hue),15%,14%,1);--md-typeset-table-color:hsla(var(--md-hue),75%,95%,0.12);--md-admonition-fg-color:var(--md-default-fg-color);--md-admonition-bg-color:var(--md-default-bg-color);--md-footer-bg-color:hsla(var(--md-hue),15%,12%,0.87);--md-footer-bg-color--dark:hsla(var(--md-hue),15%,10%,1);--md-shadow-z1:0 0.2rem 0.5rem rgba(0,0,0,.2),0 0 0.05rem rgba(0,0,0,.1);--md-shadow-z2:0 0.2rem 0.5rem rgba(0,0,0,.3),0 0 0.05rem rgba(0,0,0,.25);--md-shadow-z3:0 0.2rem 0.5rem rgba(0,0,0,.4),0 0 0.05rem rgba(0,0,0,.35)}[data-md-color-scheme=slate] img[src$="#gh-light-mode-only"],[data-md-color-scheme=slate] img[src$="#only-light"]{display:none}[data-md-color-scheme=slate] img[src$="#gh-dark-mode-only"],[data-md-color-scheme=slate] img[src$="#only-dark"]{display:initial}[data-md-color-scheme=slate][data-md-color-primary=pink]{--md-typeset-a-color:#ed5487}[data-md-color-scheme=slate][data-md-color-primary=purple]{--md-typeset-a-color:#bd78c9}[data-md-color-scheme=slate][data-md-color-primary=deep-purple]{--md-typeset-a-color:#a682e3}[data-md-color-scheme=slate][data-md-color-primary=indigo]{--md-typeset-a-color:#6c91d5}[data-md-color-scheme=slate][data-md-color-primary=teal]{--md-typeset-a-color:#00ccb8}[data-md-color-scheme=slate][data-md-color-primary=green]{--md-typeset-a-color:#71c174}[data-md-color-scheme=slate][data-md-color-primary=deep-orange]{--md-typeset-a-color:#ff9575}[data-md-color-scheme=slate][data-md-color-primary=brown]{--md-typeset-a-color:#c7846b}[data-md-color-scheme=slate][data-md-color-primary=black],[data-md-color-scheme=slate][data-md-color-primary=blue-grey],[data-md-color-scheme=slate][data-md-color-primary=grey],[data-md-color-scheme=slate][data-md-color-primary=white]{--md-typeset-a-color:#6c91d5}[data-md-color-switching] *,[data-md-color-switching] :after,[data-md-color-switching] :before{transition-duration:0ms!important}}[data-md-color-accent=red]{--md-accent-fg-color:#ff1947;--md-accent-fg-color--transparent:rgba(255,25,71,.1);--md-accent-bg-color:#fff;--md-accent-bg-color--light:hsla(0,0%,100%,.7)}[data-md-color-accent=pink]{--md-accent-fg-color:#f50056;--md-accent-fg-color--transparent:rgba(245,0,86,.1);--md-accent-bg-color:#fff;--md-accent-bg-color--light:hsla(0,0%,100%,.7)}[data-md-color-accent=purple]{--md-accent-fg-color:#df41fb;--md-accent-fg-color--transparent:rgba(223,65,251,.1);--md-accent-bg-color:#fff;--md-accent-bg-color--light:hsla(0,0%,100%,.7)}[data-md-color-accent=deep-purple]{--md-accent-fg-color:#7c4dff;--md-accent-fg-color--transparent:rgba(124,77,255,.1);--md-accent-bg-color:#fff;--md-accent-bg-color--light:hsla(0,0%,100%,.7)}[data-md-color-accent=indigo]{--md-accent-fg-color:#526cfe;--md-accent-fg-color--transparent:rgba(82,108,254,.1);--md-accent-bg-color:#fff;--md-accent-bg-color--light:hsla(0,0%,100%,.7)}[data-md-color-accent=blue]{--md-accent-fg-color:#4287ff;--md-accent-fg-color--transparent:rgba(66,135,255,.1);--md-accent-bg-color:#fff;--md-accent-bg-color--light:hsla(0,0%,100%,.7)}[data-md-color-accent=light-blue]{--md-accent-fg-color:#0091eb;--md-accent-fg-color--transparent:rgba(0,145,235,.1);--md-accent-bg-color:#fff;--md-accent-bg-color--light:hsla(0,0%,100%,.7)}[data-md-color-accent=cyan]{--md-accent-fg-color:#00bad6;--md-accent-fg-color--transparent:rgba(0,186,214,.1);--md-accent-bg-color:#fff;--md-accent-bg-color--light:hsla(0,0%,100%,.7)}[data-md-color-accent=teal]{--md-accent-fg-color:#00bda4;--md-accent-fg-color--transparent:rgba(0,189,164,.1);--md-accent-bg-color:#fff;--md-accent-bg-color--light:hsla(0,0%,100%,.7)}[data-md-color-accent=green]{--md-accent-fg-color:#00c753;--md-accent-fg-color--transparent:rgba(0,199,83,.1);--md-accent-bg-color:#fff;--md-accent-bg-color--light:hsla(0,0%,100%,.7)}[data-md-color-accent=light-green]{--md-accent-fg-color:#63de17;--md-accent-fg-color--transparent:rgba(99,222,23,.1);--md-accent-bg-color:#fff;--md-accent-bg-color--light:hsla(0,0%,100%,.7)}[data-md-color-accent=lime]{--md-accent-fg-color:#b0eb00;--md-accent-fg-color--transparent:rgba(176,235,0,.1);--md-accent-bg-color:rgba(0,0,0,.87);--md-accent-bg-color--light:rgba(0,0,0,.54)}[data-md-color-accent=yellow]{--md-accent-fg-color:#ffd500;--md-accent-fg-color--transparent:rgba(255,213,0,.1);--md-accent-bg-color:rgba(0,0,0,.87);--md-accent-bg-color--light:rgba(0,0,0,.54)}[data-md-color-accent=amber]{--md-accent-fg-color:#fa0;--md-accent-fg-color--transparent:rgba(255,170,0,.1);--md-accent-bg-color:rgba(0,0,0,.87);--md-accent-bg-color--light:rgba(0,0,0,.54)}[data-md-color-accent=orange]{--md-accent-fg-color:#ff9100;--md-accent-fg-color--transparent:rgba(255,145,0,.1);--md-accent-bg-color:rgba(0,0,0,.87);--md-accent-bg-color--light:rgba(0,0,0,.54)}[data-md-color-accent=deep-orange]{--md-accent-fg-color:#ff6e42;--md-accent-fg-color--transparent:rgba(255,110,66,.1);--md-accent-bg-color:#fff;--md-accent-bg-color--light:hsla(0,0%,100%,.7)}[data-md-color-primary=red]{--md-primary-fg-color:#ef5552;--md-primary-fg-color--light:#e57171;--md-primary-fg-color--dark:#e53734;--md-primary-bg-color:#fff;--md-primary-bg-color--light:hsla(0,0%,100%,.7)}[data-md-color-primary=pink]{--md-primary-fg-color:#e92063;--md-primary-fg-color--light:#ec417a;--md-primary-fg-color--dark:#c3185d;--md-primary-bg-color:#fff;--md-primary-bg-color--light:hsla(0,0%,100%,.7)}[data-md-color-primary=purple]{--md-primary-fg-color:#ab47bd;--md-primary-fg-color--light:#bb69c9;--md-primary-fg-color--dark:#8c24a8;--md-primary-bg-color:#fff;--md-primary-bg-color--light:hsla(0,0%,100%,.7)}[data-md-color-primary=deep-purple]{--md-primary-fg-color:#7e56c2;--md-primary-fg-color--light:#9574cd;--md-primary-fg-color--dark:#673ab6;--md-primary-bg-color:#fff;--md-primary-bg-color--light:hsla(0,0%,100%,.7)}[data-md-color-primary=indigo]{--md-primary-fg-color:#4051b5;--md-primary-fg-color--light:#5d6cc0;--md-primary-fg-color--dark:#303fa1;--md-primary-bg-color:#fff;--md-primary-bg-color--light:hsla(0,0%,100%,.7)}[data-md-color-primary=blue]{--md-primary-fg-color:#2094f3;--md-primary-fg-color--light:#42a5f5;--md-primary-fg-color--dark:#1975d2;--md-primary-bg-color:#fff;--md-primary-bg-color--light:hsla(0,0%,100%,.7)}[data-md-color-primary=light-blue]{--md-primary-fg-color:#02a6f2;--md-primary-fg-color--light:#28b5f6;--md-primary-fg-color--dark:#0287cf;--md-primary-bg-color:#fff;--md-primary-bg-color--light:hsla(0,0%,100%,.7)}[data-md-color-primary=cyan]{--md-primary-fg-color:#00bdd6;--md-primary-fg-color--light:#25c5da;--md-primary-fg-color--dark:#0097a8;--md-primary-bg-color:#fff;--md-primary-bg-color--light:hsla(0,0%,100%,.7)}[data-md-color-primary=teal]{--md-primary-fg-color:#009485;--md-primary-fg-color--light:#26a699;--md-primary-fg-color--dark:#007a6c;--md-primary-bg-color:#fff;--md-primary-bg-color--light:hsla(0,0%,100%,.7)}[data-md-color-primary=green]{--md-primary-fg-color:#4cae4f;--md-primary-fg-color--light:#68bb6c;--md-primary-fg-color--dark:#398e3d;--md-primary-bg-color:#fff;--md-primary-bg-color--light:hsla(0,0%,100%,.7)}[data-md-color-primary=light-green]{--md-primary-fg-color:#8bc34b;--md-primary-fg-color--light:#9ccc66;--md-primary-fg-color--dark:#689f38;--md-primary-bg-color:#fff;--md-primary-bg-color--light:hsla(0,0%,100%,.7)}[data-md-color-primary=lime]{--md-primary-fg-color:#cbdc38;--md-primary-fg-color--light:#d3e156;--md-primary-fg-color--dark:#b0b52c;--md-primary-bg-color:rgba(0,0,0,.87);--md-primary-bg-color--light:rgba(0,0,0,.54)}[data-md-color-primary=yellow]{--md-primary-fg-color:#ffec3d;--md-primary-fg-color--light:#ffee57;--md-primary-fg-color--dark:#fbc02d;--md-primary-bg-color:rgba(0,0,0,.87);--md-primary-bg-color--light:rgba(0,0,0,.54)}[data-md-color-primary=amber]{--md-primary-fg-color:#ffc105;--md-primary-fg-color--light:#ffc929;--md-primary-fg-color--dark:#ffa200;--md-primary-bg-color:rgba(0,0,0,.87);--md-primary-bg-color--light:rgba(0,0,0,.54)}[data-md-color-primary=orange]{--md-primary-fg-color:#ffa724;--md-primary-fg-color--light:#ffa724;--md-primary-fg-color--dark:#fa8900;--md-primary-bg-color:rgba(0,0,0,.87);--md-primary-bg-color--light:rgba(0,0,0,.54)}[data-md-color-primary=deep-orange]{--md-primary-fg-color:#ff6e42;--md-primary-fg-color--light:#ff8a66;--md-primary-fg-color--dark:#f4511f;--md-primary-bg-color:#fff;--md-primary-bg-color--light:hsla(0,0%,100%,.7)}[data-md-color-primary=brown]{--md-primary-fg-color:#795649;--md-primary-fg-color--light:#8d6e62;--md-primary-fg-color--dark:#5d4037;--md-primary-bg-color:#fff;--md-primary-bg-color--light:hsla(0,0%,100%,.7)}[data-md-color-primary=grey]{--md-primary-fg-color:#757575;--md-primary-fg-color--light:#9e9e9e;--md-primary-fg-color--dark:#616161;--md-primary-bg-color:#fff;--md-primary-bg-color--light:hsla(0,0%,100%,.7);--md-typeset-a-color:#4051b5}[data-md-color-primary=blue-grey]{--md-primary-fg-color:#546d78;--md-primary-fg-color--light:#607c8a;--md-primary-fg-color--dark:#455a63;--md-primary-bg-color:#fff;--md-primary-bg-color--light:hsla(0,0%,100%,.7);--md-typeset-a-color:#4051b5}[data-md-color-primary=light-green]:not([data-md-color-scheme=slate]){--md-typeset-a-color:#72ad2e}[data-md-color-primary=lime]:not([data-md-color-scheme=slate]){--md-typeset-a-color:#8b990a}[data-md-color-primary=yellow]:not([data-md-color-scheme=slate]){--md-typeset-a-color:#b8a500}[data-md-color-primary=amber]:not([data-md-color-scheme=slate]){--md-typeset-a-color:#d19d00}[data-md-color-primary=orange]:not([data-md-color-scheme=slate]){--md-typeset-a-color:#e68a00}[data-md-color-primary=white]{--md-primary-fg-color:#fff;--md-primary-fg-color--light:hsla(0,0%,100%,.7);--md-primary-fg-color--dark:rgba(0,0,0,.07);--md-primary-bg-color:rgba(0,0,0,.87);--md-primary-bg-color--light:rgba(0,0,0,.54);--md-typeset-a-color:#4051b5}@media screen and (min-width:60em){[data-md-color-primary=white] .md-search__form{background-color:rgba(0,0,0,.07)}[data-md-color-primary=white] .md-search__form:hover{background-color:rgba(0,0,0,.32)}[data-md-color-primary=white] .md-search__input+.md-search__icon{color:rgba(0,0,0,.87)}}@media screen and (min-width:76.25em){[data-md-color-primary=white] .md-tabs{border-bottom:.05rem solid rgba(0,0,0,.07)}}[data-md-color-primary=black]{--md-primary-fg-color:#000;--md-primary-fg-color--light:rgba(0,0,0,.54);--md-primary-fg-color--dark:#000;--md-primary-bg-color:#fff;--md-primary-bg-color--light:hsla(0,0%,100%,.7);--md-typeset-a-color:#4051b5}[data-md-color-primary=black] .md-header{background-color:#000}@media screen and (max-width:59.9375em){[data-md-color-primary=black] .md-nav__source{background-color:rgba(0,0,0,.87)}}@media screen and (min-width:60em){[data-md-color-primary=black] .md-search__form{background-color:hsla(0,0%,100%,.12)}[data-md-color-primary=black] .md-search__form:hover{background-color:hsla(0,0%,100%,.3)}}@media screen and (max-width:76.1875em){html [data-md-color-primary=black] .md-nav--primary .md-nav__title[for=__drawer]{background-color:#000}}@media screen and (min-width:76.25em){[data-md-color-primary=black] .md-tabs{background-color:#000}} \ No newline at end of file diff --git a/develop/assets/stylesheets/palette.cbb835fc.min.css.map b/develop/assets/stylesheets/palette.cbb835fc.min.css.map new file mode 100644 index 0000000..96e380c --- /dev/null +++ b/develop/assets/stylesheets/palette.cbb835fc.min.css.map @@ -0,0 +1 @@ +{"version":3,"sources":["src/assets/stylesheets/palette/_scheme.scss","../../../src/assets/stylesheets/palette.scss","src/assets/stylesheets/palette/_accent.scss","src/assets/stylesheets/palette/_primary.scss","src/assets/stylesheets/utilities/_break.scss"],"names":[],"mappings":"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","file":"palette.css"} \ No newline at end of file diff --git a/develop/bugs-and-issues/index.html b/develop/bugs-and-issues/index.html new file mode 100644 index 0000000..3c95247 --- /dev/null +++ b/develop/bugs-and-issues/index.html @@ -0,0 +1,3269 @@ + + + + + + + + + + + + + + + + + + + + + + Bugs and Issues - Friendica documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + +
    + + + + +
    + + +
    + +
    + + + + + + + + + +
    +
    + + + +
    +
    +
    + + + + + + +
    +
    +
    + + + +
    +
    +
    + + + +
    +
    +
    + + +
    +
    + + + + + + + + + + + + + +

    Bugs and Issues#

    +

    If your server has a support page, you should report any bugs/issues you encounter there first. +Reporting to your support page before reporting to the developers makes their job easier, as they don't have to deal with bug reports that might not have anything to do with them. +Reducing the workload in this way helps us get new features faster. +You can also contact the friendica support forum and report your problem there. +Bugs are rarely limited to one person, and the chances are somebody from another node has encountered the issue too, and will be able to help you.

    +

    If you're a technical user, or your site doesn't have a support page, you'll need to use the Bug Tracker. +This is also used for issues with addons. +Please perform a search to see if there's already an open bug that matches yours before submitting anything.

    +

    Try to provide as much information as you can about the bug, including the full text of any error messages or notices, and any steps required to replicate the problem in as much detail as possible. +It's generally better to provide too much information than not enough.

    +

    See this article to learn more about submitting good bug reports. The better your bug report, the more likely we are to be able to actually fix it.

    +

    And last but not least: It is better to report an issue you encountered even if you can't write the perfect bug report!

    + + +
    + +
    +
    + + + +
    + + + +
    +
    +
    +
    + + + + + + + + \ No newline at end of file diff --git a/develop/de/admin/config/index.html b/develop/de/admin/config/index.html new file mode 100644 index 0000000..01009f8 --- /dev/null +++ b/develop/de/admin/config/index.html @@ -0,0 +1,3731 @@ + + + + + + + + + + + + + + + + + + + + + + Config Values - Friendica Dokumentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + +
    + + + + +
    + + +
    + +
    + + + + + + + + + +
    +
    + + + +
    +
    +
    + + + + + + +
    +
    +
    + + + + + + +
    +
    + + + + + + + + + + + + + +

    Config values that can only be set in config/local.config.php#

    +

    Friendica's configuration is done in two places: in PHP array configuration files and in the config database table. +Database config values overwrite the same file config values.

    +

    File configuration#

    +

    The configuration format for file configuration is an array returned from a PHP file. +This prevents your webserver from displaying your private configuration. It interprets the configuration files and displays nothing.

    +

    A typical configuration file looks like this:

    +
    <?php
    +
    +/*
    + * Comment block
    + */
    +
    +return [
    +    'section1' => [
    +        // Comment line
    +        'key' => 'value',
    +    ],
    +    'section2' => [
    +        'array' => ['value0', 'value1', 'value2'],
    +    ],
    +];
    +
    +

    Configuration location#

    +

    The config directory holds key configuration files and can have different config files. +All of them have to end with .config.php and must not include -sample in their name.

    +

    Some examples of common known configuration files: +- local.config.php holds the current node custom configuration. +- addon.config.php is optional and holds the custom configuration for specific addons.

    +

    Addons can define their own default configuration values in addon/[addon]/config/[addon].config.php which is loaded when the addon is activated.

    +

    If needed, an alternative config path can be used by using the FRIENDICA_CONFIG_DIR environment variable (full path required!). +This is useful in case of hardening the system by separating configuration from program binaries.

    +

    Static Configuration location#

    +

    The static directory holds the codebase default configurations files. +They must not be changed by users, because they can get changed from release to release.

    +

    Currently, the following configurations are included: +- defaults.config.php holds the default values for all the configuration keys that can only be set in local.config.php. +- settings.config.php holds the default values for some configuration keys that are set through the admin settings page.

    +

    Migrating from .htconfig.php to config/local.config.php#

    +

    The legacy .htconfig.php configuration file is still supported, but is deprecated and will be removed in a subsequent Friendica release.

    +

    The migration is pretty straightforward: +If you had any addon-specific configuration in your .htconfig.php, just copy config/addon-sample.config.php to config/addon.config.php and move your configuration values. +Afterwards, copy config/local-sample.config.php to config/local.config.php, move the remaining configuration values to it according to the following conversion chart, then rename your .htconfig.php to check your node is working as expected before deleting it.

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    .htconfig.phpconfig/local.config.php
    +$db_host = 'localhost';
    +$db_user = 'mysqlusername';
    +$db_pass = 'mysqlpassword';
    +$db_data = 'mysqldatabasename';
    +$a->config["system"]["db_charset"] = 'utf8mb4';
    +
    +'database' => [
    +    'hostname' => 'localhost',
    +    'username' => 'mysqlusername',
    +    'password' => 'mysqlpassword',
    +    'database' => 'database',
    +    'charset' => 'utf8mb4',
    +],
    +
    +$a->config["section"]["key"] = "value";
    +
    +'section' => [
    +    'key' => 'value',
    +],
    +
    +$a->config["section"]["key"] = array(
    +    "value1",
    +    "value2",
    +    "value3"
    +);
    +
    +'section' => [
    +    'key' => ['value1', 'value2', 'value3'],
    +],
    +
    +$a->config["key"] = "value";
    +
    +'config' => [
    +    'key' => 'value',
    +],
    +
    +$a->config['register_policy'] = REGISTER_CLOSED;
    +
    +'config' => [
    +    'register_policy' => \Friendica\Module\Register::CLOSED,
    +],
    +
    +$a->path = "value";
    +
    +'system' => [
    +    'urlpath' => 'value',
    +],
    +
    +$default_timezone = "value";
    +
    +'system' => [
    +    'default_timezone' => 'value',
    +],
    +
    +$pidfile = "value";
    +
    +'system' => [
    +    'pidfile' => 'value',
    +],
    +
    +$lang = "value";
    +
    +'system' => [
    +    'language' => 'value',
    +],
    +
    + +

    Migrating from config/local.ini.php to config/local.config.php#

    +

    The legacy config/local.ini.php configuration file is still supported, but is deprecated and will be removed in a subsequent Friendica release.

    +

    The migration is pretty straightforward: +If you had any addon-specific configuration in your config/addon.ini.php, just copy config/addon-sample.config.php to config/addon.config.php and move your configuration values. +Afterwards, copy config/local-sample.config.php to config/local.config.php, move the remaining configuration values to it according to the following conversion chart, then rename your config/local.ini.php file to check your node is working as expected before deleting it.

    + + + + + + + + + + + + + + + + + + + + + + + + + +
    config/local.ini.phpconfig/local.config.php
    +[database]
    +hostname = localhost
    +username = mysqlusername
    +password = mysqlpassword
    +database = mysqldatabasename
    +charset = utf8mb4
    +
    +'database' => [
    +    'hostname' => 'localhost',
    +    'username' => 'mysqlusername',
    +    'password' => 'mysqlpassword',
    +    'database' => 'database',
    +    'charset' => 'utf8mb4',
    +],
    +
    +[section]
    +key = value
    +
    +'section' => [
    +    'key' => 'value',
    +],
    +
    +[config]
    +register_policty = REGISTER_CLOSED
    +
    +'config' => [
    +    'register_policy' => \Friendica\Module\Register::CLOSED,
    +],
    +
    +[section]
    +key[] = value1
    +key[] = value2
    +key[] = value3
    +
    +'section' => [
    +    'key' => ['value1', 'value2', 'value3'],
    +],
    +
    + +

    Database Settings#

    +

    The configuration variables database.hostname, database.username, database.password, database.database and database.charset are holding your credentials for the database connection. +If you need to specify a port to access the database, you can do so by appending :portnumber to the database.hostname variable.

    +
    'database' => [
    +    'hostname' => 'your.mysqlhost.com:123456',
    +]
    +
    +

    If all the following environment variables are set, Friendica will use them instead of the previously configured variables for the db:

    +
    MYSQL_HOST
    +MYSQL_PORT
    +MYSQL_USERNAME
    +MYSQL_PASSWORD
    +MYSQL_DATABASE
    +
    +

    Config values that can only be set in config/local.config.php#

    +

    There are some config values that haven't found their way into the administration page. +This has several reasons. +Maybe they are part of a current development that isn't considered stable and will be added later in the administration page when it is considered safe. +Or it triggers something that isn't expected to be of public interest. +Or it is for testing purposes only.

    +

    Attention: Please be warned that you shouldn't use one of these values without the knowledge what it could trigger. +Especially don't do that with undocumented values.

    +

    These configurations keys and their default value are listed in static/defaults.config.php and should be overwritten in config/local.config.php.

    +

    Administrator Options#

    +

    Enabling the admin panel for an account, and thus making the account holder admin of the node, is done by setting the variable

    +
    'config' => [
    +    'admin_email' => 'someone@example.com',
    +]
    +
    +

    Where you have to match the email address used for the account with the one you enter to the config/local.config.php file. +If more than one account should be able to access the admin panel, separate the email addresses with a comma.

    +
    'config' => [
    +    'admin_email' => 'someone@example.com,someoneelse@example.com',
    +]
    +
    +

    If you want to have a more personalized closing line for the notification emails you can set a variable for the admin_name.

    +
    'config' => [
    +    'admin_name' => 'Marvin',
    +]
    +
    + + +
    + +
    +
    + + + +
    + + + +
    +
    +
    +
    + + + + + + + + \ No newline at end of file diff --git a/develop/de/admin/faq/index.html b/develop/de/admin/faq/index.html new file mode 100644 index 0000000..9c1417b --- /dev/null +++ b/develop/de/admin/faq/index.html @@ -0,0 +1,3397 @@ + + + + + + + + + + + + + + + + + + + + + + FAQ - Friendica Dokumentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + +
    + + + + +
    + + +
    + +
    + + + + + + + + + +
    +
    + + + +
    +
    +
    + + + + + + +
    +
    +
    + + + + + + +
    +
    + + + + + + + + + + + + + +

    Häufig gestellte Fragen (Admin)#

    +

    Kann ich mehrere Domains mit denselben Dateien aufsetzen?#

    +

    Ja, das ist möglich. +Es ist allerdings nicht möglich, eine Datenbank durch zwei Domains zu nutzen. +Solange Du Deine config/local.config.php allerdings so einrichtest, dass das System nicht versucht, eine Installation durchzuführen, kannst Du die richtige Config-Datei in include/$hostname/config/local.config.php hinterlegen. +Alle Cache-Aspekte und der Zugriffsschutz können pro Instanz konfiguriert werden.

    +

    Wo kann ich den Quellcode von Friendica, Add-ons und Themes finden?#

    +

    Du kannst den Friendica-Quellcode hier finden. +Dort findest Du immer die aktuellste stabile Version von Friendica. +Der Quellcode von Friendica Red ist hier zu finden.

    +

    Add-ons findest Du auf dieser Seite.

    +

    Wenn Du neue Themen suchst, findest Du sie auf Friendica-Themes.com.

    +

    Ich habe meine E-Mail-Adresse geändert und jetzt ist das Adminpanel verschwunden?#

    +

    Bitte aktualisiere deine E-Mail Adresse in der config/local.config.php Datei.

    +

    Kann es mehr als einen Admin auf einer Friendica Instanz geben?#

    +

    Ja. +Du kannst in der config/local.config.php Datei mehrere E-Mail Adressen auflisten. +Die aufgelisteten Adressen werden mit Kommata voneinander getrennt.

    +

    Die Datenbank Struktur schein nicht aktuell zu sein. Was kann ich tun?#

    +

    Rufe bitte im Admin Panel den Punkt DB Updates auf und folge dem Link Datenbank Struktur überprüfen. +Damit wird ein Hintergrundprozess gestartet der die Struktur deiner Datenbank überprüft und gegebenenfalls aktualisiert.

    +

    Du kannst das Strukturupdates auch manuell auf der Kommandoeingabe ausführen. +Starte dazu bitte vom Grundverzeichnis deiner Friendica Instanz folgendes Kommando:

    +
    bin/console dbstructure update
    +
    +

    Sollte bei der Ausführung Fehler auftreten, kontaktiere bitte das Support-Forum.

    + + +
    + +
    +
    + + + +
    + + + +
    +
    +
    +
    + + + + + + + + \ No newline at end of file diff --git a/develop/de/admin/improve-performance/index.html b/develop/de/admin/improve-performance/index.html new file mode 100644 index 0000000..421729c --- /dev/null +++ b/develop/de/admin/improve-performance/index.html @@ -0,0 +1,3521 @@ + + + + + + + + + + + + + + + + + + + + + + Performance verbessern - Friendica Dokumentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + +
    + + + + +
    + + +
    + +
    + + + + + + + + + +
    +
    + + + +
    +
    +
    + + + + + + +
    +
    +
    + + + +
    +
    +
    + + + +
    +
    +
    + + +
    +
    + + + + + + + + + + + + + +

    How-to: Performance verbessern#

    +

    Eine kleine Anleitung, um die Performance einer Friendica-Seite zu verbessern.

    +

    Vorab:

    +

    Wenn du Fragen zu den folgenden Anweisungen oder zu anderen Themen hast, dann kannst du jederzeit beim Friendica-Support unter https://forum.friendi.ca/profile/helpers nachfragen.

    +

    Systemeinstellungen#

    +

    Geh auf /admin/site in deinem System und ändere die folgenden Werte:

    +
    setze "Qualität des JPEG Bildes" auf 50.
    +
    +

    Dieser Wert reduziert die Daten, die vom Server an den Client geschickt werden. +50 ist ein Wert, der die Bildqualität nicht zu stark beeinflusst.

    +
    setze "Intervall zum Vervollständigen von OStatus Unterhaltungen" auf "niemals"
    +
    +

    Wenn du viele OStatus-Kontakte hast, dann kann die Vervollständigung von Unterhaltungen sehr zeitraubend sein. +Der Nachteil: Du siehst nicht jede Antwort einer OStatus-Unterhaltung. Aus diesem Grund ist die Option "Beim Empfang von Nachrichten" in der Regel ein guter Kompromiss.

    +
    setze "Nutze MySQL full text engine".
    +
    +

    Wenn du MyISAM (Standardeinstellung) oder InnoDB mit MariaDB 10 nutzt, dann beschleunigt dies die Suche.

    +

    Addons#

    +

    Aktiviere die folgenden Addons:

    +
    rendertime
    +
    +

    rendertime#

    +

    Beschreibung

    +

    Dieses Add-on beschleunigt dein System nicht, aber es hilft dabei, die Flaschenhälse zu ermitteln.

    +

    Wenn es aktiviert ist, dann siehst du Werte wie die folgenden auf jeder deiner Seiten:

    +
    Performance: Database: 0.244, Network: 0.002, Rendering: 0.044, Parser: 0.001, I/O: 0.021, Other: 0.237, Total: 0.548
    +
    +Database: Dies ist die Zeit für alle Datenbankabfragen
    +Network: Zeit, die benötigt wird, um Inhalte von externen Seiten vorzuladen
    +Rendering: Zeit, die zum rendern des Themas benötigt wird
    +Parser: Die Zeit, die der BBCode-Parser benötigt, um die Ausgabe der Seite zu erstellen
    +I/O: Zeit, die der lokale Dateizugriff benötigt
    +Others: alles andere :)
    +Total: Die Summe aller genannten Werte
    +
    +

    Diese Werte zeigen deine Performance-Probleme.

    +

    Webserver#

    +

    Wenn du einen Apache-Webserver nutzt, aktiviere bitte die folgenden Module:

    +

    Cache-Control#

    +

    Dieses Modul weist den Client an, den Inhalt statischer Dateien zu speichern, um diese nicht immer wieder neu laden zu müssen.

    +

    Aktiviere das Modul mod_expires, indem du a2enmod expires als root eingibst.

    +

    Füge die folgenden Zeilen in die Apache-Konfiguration deiner Seite im "directory"-Bereich ein.

    +

    ExpiresActive on ExpiresDefault "access plus 1 week"

    +

    Weitere Informationen findest du hier: http://httpd.apache.org/docs/2.2/mod/mod_expires.html.

    +

    Compress content#

    +

    Dieses Modul komprimiert den Datenverkehr (Traffic) zwischen dem Webserver und dem Client.

    +

    Aktiviere das Modul mod_deflate durch die Eingabe a2enmod deflate als root.

    +

    Weitere Informationen findest du hier: http://httpd.apache.org/docs/2.2/mod/mod_deflate.html

    +

    PHP#

    +

    FCGI#

    +

    Wenn du Apache nutzt, dann denk darüber nach, FCGI zu nutzen. +Wenn du eine Debian-basierte Distribution nutzt, dann wirst du die Pakete php5-cgi und libapache2-mod-fcgid benötigen. +Nutze externe Dokumente, um eine detailliertere Erklärung für die Einrichtung eines Systems auf FCGI-Basis zu erhalten.

    +

    Database#

    +

    Es gibt Skripte wie tuning-primer.sh und mysqltuner.pl, die den Datenbankserver analysieren und Hinweise darauf geben, welche Werte verändert werden könnten.

    +

    Aktivere hierfür die "Slow query" Log-Datei, um Performanceprobleme zu erkennen.

    + + +
    + +
    +
    + + + +
    + + + +
    +
    +
    +
    + + + + + + + + \ No newline at end of file diff --git a/develop/de/admin/install-ejabberd/index.html b/develop/de/admin/install-ejabberd/index.html new file mode 100644 index 0000000..2444b65 --- /dev/null +++ b/develop/de/admin/install-ejabberd/index.html @@ -0,0 +1,3381 @@ + + + + + + + + + + + + + + + + + + + + + + Install ejabberd - Friendica Dokumentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + +
    + + + + +
    + + +
    + +
    + + + + + + + + + +
    +
    + + + +
    +
    +
    + + + + + + +
    +
    +
    + + + +
    +
    +
    + + + +
    +
    +
    + + +
    +
    + + + + + + + + + + + + + +

    Install an ejabberd with synchronized credentials#

    +

    Ejabberd is a chat server that uses XMPP as messaging protocol that you can use with a large amount of clients. +In conjunction with the "xmpp" addon it can be used for a web based chat solution for your users.

    +

    Installation#

    +
      +
    • +

      Change its owner to whichever user is running the server, i.e. ejabberd

      +
      $ chown ejabberd:ejabberd /path/to/friendica/bin/auth_ejabberd.php
      +
      +
    • +
    • +

      Change the access mode, so it is readable only to the user ejabberd and has exec

      +
      $ chmod 700 /path/to/friendica/bin/auth_ejabberd.php
      +
      +
    • +
    • +

      Edit your ejabberd.cfg file, comment out your auth_method and add:

      +
      {auth_method, external}.
      +{extauth_program, "/path/to/friendica/bin/auth_ejabberd.php"}.
      +
      +
    • +
    • +

      Disable the module "mod_register" and disable the registration:

      +
      {access, register, [{deny, all}]}.
      +
      +
    • +
    • +

      Enable BOSH:

      +
    • +
    • Enable the module "mod_http_bind"
    • +
    • +

      Edit this line:

      +
      {5280, ejabberd_http,    [captcha, http_poll, http_bind]}
      +
      +
    • +
    • +

      In your apache configuration for your site add this line:

      +
      ProxyPass /http-bind http://127.0.0.1:5280/http-bind retry=0
      +
      +
    • +
    • +

      Restart your ejabberd service, you should be able to log in with your friendica credentials

      +
    • +
    +

    Other hints#

    +
      +
    • if a user has a space or a @ in the nickname, the user has to replace these characters:
    • +
    • " " (space) is replaced with "%20"
    • +
    • "@" is replaced with "(a)"
    • +
    + + +
    + +
    +
    + + + +
    + + + +
    +
    +
    +
    + + + + + + + + \ No newline at end of file diff --git a/develop/de/admin/install/index.html b/develop/de/admin/install/index.html new file mode 100644 index 0000000..99c7746 --- /dev/null +++ b/develop/de/admin/install/index.html @@ -0,0 +1,3709 @@ + + + + + + + + + + + + + + + + + + + + + + Installation - Friendica Dokumentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + +
    + + + + +
    + + +
    + +
    + + + + + + + + + +
    +
    + + + +
    +
    +
    + + + + + + +
    +
    +
    + + + + + + +
    +
    + + + + + + + + + + + + + +

    Friendica Installation#

    +

    Wir haben hart daran gearbeitet, um Friendica auf vorgefertigten Hosting-Plattformen zum Laufen zu bringen - solche, auf denen auch Wordpress Blogs und Drupal-Installationen laufen. +Wir bieten eine manuelle und eine automatische Installation an. +Aber bedenke, dass Friendica mehr als eine einfache Webanwendung ist. +Es handelt sich um ein komplexes Kommunikationssystem, das eher an einen E-Mail-Server erinnert als an einen Webserver. +Um die Verfügbarkeit und Performance zu gewährleisten, werden Nachrichten im Hintergrund verschickt und gespeichert, um sie später zu verschicken, wenn eine Webseite gerade nicht erreichbar ist. +Diese Funktionalität benötigt ein wenig mehr als die normalen Blogs. +Nicht jeder PHP/MySQL-Hosting-Anbieter kann Friendica unterstützen. +Viele hingegen können es. Aber bitte prüfe die Voraussetzungen deines Servers vor der Installation.

    +

    Wenn dir Fehler während der Installation auffallen, sag uns bitte über Helper oder das Entwickler-Forum Bescheid oder erstelle ein Issue. +Gib uns bitte so viele Informationen zu deinem System, wie du kannst, und beschreibe den Fehler mit allen Details und Fehlermeldungen, sodass wir den Fehler zukünftig verhindern können. +Aufgrund der großen Anzahl an verschiedenen Betriebssystemen und PHP-Plattformen haben wir nur geringe Kapazitäten, um deine PHP-Installation zu debuggen oder fehlende Module zu ersetzen, aber wir tun unser Bestes, um allgemeine Code-Fehler zu beheben.

    +

    Bevor du anfängst: suche dir einen Domain- oder Subdomainnamen für deinen Server. +Dinge verändern sich und einige deiner Freunde haben möglicherweise Probleme, mit dir zu kommunizieren. +Wir planen, diese Einschränkung in einer zukünftigen Version zu beheben.

    +

    Anforderungen#

    +
      +
    • Apache mit einer aktiverten mod-rewrite-Funktion und dem Eintrag "Options All", so dass du die lokale .htaccess-Datei nutzen kannst
    • +
    • PHP 7.3+ (PHP 8 wird noch nicht komplett unterstützt)
    • +
    • PHP Kommandozeilen-Zugang mit register_argc_argv auf "true" gesetzt in der php.ini-Datei
    • +
    • Curl, GD, GMP, PDO, MySQLi, xml, zip und OpenSSL-Erweiterung
    • +
    • Das POSIX Modul muss aktiviert sein (CentOS, RHEL haben dies z.B. deaktiviert)
    • +
    • Einen E-Mail Server, sodass PHP mail() funktioniert. + Wenn kein eigener E-Mail-Server zur Verfügung steht, kann alternativ das phpmailer Add-on mit einem externen SMTP Account verwendet werden.
    • +
    • Mysql 5.6+ (oder eine äquivalente Alternative: MariaDB, Percona Server etc.)
    • +
    • die Möglichkeit, wiederkehrende Aufgaben mit cron (Linux/Mac) oder "Scheduled Tasks" einzustellen (Windows) [Beachte: andere Optionen sind in Abschnitt 7 dieser Dokumentation zu finden]
    • +
    • Installation in einer Top-Level-Domain oder Subdomain (ohne eine Verzeichnis/Pfad-Komponente in der URL) wird bevorzugt. Verzeichnispfade sind für diesen Zweck nicht so günstig und wurden auch nicht ausführlich getestet.
    • +
    +

    Installation#

    +

    Alternative Wege um Friendica zu Installieren#

    +

    Diese Anleitung wird dir Schritt-für-Schritt zeigen wie du Friendica auf deinem Server installieren kannst. +Falls du an automatischen Möglichkeiten interesse hast, wirf doch einen Blick auf

    + +

    Friendica#

    +

    Entpacke die Friendica-Daten in das Quellverzeichnis (root) des Dokumentenbereichs deines Webservers. +Wenn du die Möglichkeit hierzu hast, empfehlen wir dir git zu nutzen, um die Daten direkt von der Quelle zu klonen, statt die gepackte .tar- oder .zip-Datei zu nutzen. +Das macht die Aktualisierung wesentlich einfacher. +Der Linux-Code, mit dem man die Dateien direkt in ein Verzeichnis wie "meinewebseite" kopiert, ist

    +
    git clone https://github.com/friendica/friendica.git -b stable mywebsite
    +cd mywebsite
    +bin/composer.phar install
    +
    +

    Stelle sicher, dass der Ordner view/smarty3 existiert and von dem Webserver-Benutzer beschreibbar ist

    +
    mkdir view/smarty3
    +chmod 775 view/smarty3
    +
    +

    Falls Add-ons installiert werden sollen: Gehe in den Friendica-Ordner

    +
    cd mywebsite
    +
    +

    Und die Addon Repository klonst:

    +
    git clone https://github.com/friendica/friendica-addons.git -b stable addon
    +
    +

    Um das Addon-Verzeichnis aktuell zu halten, solltest du in diesem Pfad ein git pull-Befehl eintragen

    +
    cd meinewebseite/addon
    +git pull
    +
    +

    Wenn du den Verzeichnispfad auf deinen Webserver kopierst, dann stelle sicher, dass du auch die .htaccess kopierst, da "Punkt"-Dateien oft versteckt sind und normalerweise nicht direkt kopiert werden.

    +

    Wenn du die Entwickler Version von Friendica verwenden möchtest, kannst du auf den develop Branch im git Repository wechseln. +Dies tust du mit den folgenden Befehlen

    +
    git checkout develop
    +bin/composer.phar install
    +cd addon
    +git checkout develop
    +
    +

    Die Entwickler Version kann nach einem fehlerhaften Commit vorübergehend Probleme haben oder gar nicht mehr funktionieren. +Sollte dir so etwas passieren, lass es uns bitte wissen, damit der Fehler behoben werden kann.

    +

    Erstelle eine Datenbank#

    +

    Erstelle eine leere Datenbank und notiere alle Zugangsdaten (Adresse der Datenbank, Nutzername, Passwort, Datenbankname).

    +

    Friendica benötigt die Berechtigungen, um neue Felder in dieser Datenbank zu erstellen (create) und zu löschen (delete).

    +

    Mit neueren Versionen von MySQL (5.7.17+) musst du den sql_mode zu '' (blank) setzen. +Benutze diese Einstellung, wenn der Installer nicht in der Lage ist, die Tabellen aufgrund eines Timestamp-Format-Problems zu erstellen. +Falls dem so ist, finde den [mysqld] Bereich in deiner my.conf Datei und füge diese Zeile hinzu:

    +
    sql_mode = ''
    +
    +

    Starte MySQL dann neu und es sollte klappen.

    +

    Option A: Der manuelle Installer#

    +

    Besuche deine Webseite mit deinem Browser und befolge die Anleitung. +Bevor du dies tust, kopiere die Datei .htaccess-dist nach .htaccess, wenn du den Apache Webserver verwendest. +Bitte beachte jeden Fehler und korrigiere diese, bevor du fortfährst.

    +

    Falls du einen Port für die Datenbankverbindung angeben musst, kannst du diesen in der Host-Eingabe Zeile angeben.

    +

    Wenn die manuelle Installation aus irgendeinem Grund fehlschlägt, dann prüfe das Folgende: +* config/local.config.php existiert ... wenn nicht, bearbeite die config/local-sample.config.php und ändere die Systemeinstellungen. Benenne sie um in config/local.config.php. +* die Datenbank beinhaltet Daten. ... wenn nicht, importiere den Inhalt der Datei database.sql mit phpmyadmin oder per mysql-Kommandozeile.

    +

    Besuche deine Seite an diesem Punkt wieder und registriere deinen persönlichen Account. +Alle Registrierungsprobleme sollten automatisch behebbar sein. +Wenn du irgendwelche kritischen Fehler zu diesem Zeitpunkt erhalten solltest, deutet das darauf hin, dass die Datenbank nicht korrekt installiert wurde. +Du kannst bei Bedarf die Datei config/local.config.php verschieben/umbenennen und die Datenbank leeren (als „Dropping“ bezeichnet), sodass du mit einem sauberen System neu starten kannst.

    +

    Option B: Starte das automatische Installationsscript#

    +

    Es existieren folgende Varianten zur automatischen Installation von Friendica: +- Eine vorgefertigte Konfigurationsdatei erstellen (z.B. prepared.config.php) +- Verwendung von Umgebungsvariablen (z.B. MYSQL_HOST) +- Verwendung von Optionen (z.B. --dbhost <host>)

    +

    Umgebungsvariablen und Optionen können auch kombiniert werden. +Dabei ist jedoch darauf zu achten, dass etwaige Optionen immer die zugehörigen Umgebungsvariablen überschreiben.

    +

    Für mehr Informationen kannst du diese Option verwenden:

    +
    bin/console autoinstall -v
    +
    +

    Falls du alle optionalen Checks ausführen lassen möchtest, benutze diese Option:

    +
    bin/console autoinstall -a
    +
    +

    Wenn die automatisierte Installation aus irgendeinem Grund fehlschlägt, dann prüfe das Folgende: +* Existiert die config/local.config.php? Falls ja, wird die automatisierte Installation nicht gestartet. +* Sind Einstellungen in der config/local.config.php korrekt? Falls nicht, bitte bearbeite diese Datei erneut. +* Ist die leere MySQL-Datenbank erstellt? Falls nicht, erstelle diese.

    +

    B.1: Konfigurationsdatei#

    +

    Für diese Variante muss ein Konfigurationsdatei bereits vor der Installation fertig definiert sein (z.B. local-sample.config.php.

    +

    Gehe im Anschluss in den Friendica-Hauptordner und führe den Kommandozeilen Befehl aus:

    +
    bin/console autoinstall -f <prepared.config.php>
    +
    +

    B.2: Umgebungsvariablen#

    +

    Es existieren Zwei Arten von Umgebungsvariablen in Friendica: +- Jene, die auch im normalen Betrieb verwendet werden können (derzeit ausschließlich Datenbank Einstellungen) +- Jene, die nur während der Installation verwendet werden können (im normalen Betrieb werden sie ignoriert)

    +

    Umgebungsvariablen können auch durch adäquate Optionen (z.B. --dbhost <hostname>)übersteuert werden.

    +
    Datenbank Einstellungen#
    +

    Nur wenn die Option --savedb gesetzt ist, werden diese Umgebungsvariablen auch in config/local.config.php gespeichert!

    +
      +
    • MYSQL_HOST Der Host der MySQL/MariaDB Datenbank
    • +
    • MYSQL_PORT Der Port der MySQL/MariaDB Datenbank
    • +
    • MYSQL_USERNAME Der Benutzername des MySQL Datenbanklogins (MySql - Variante)
    • +
    • MYSQL_USER Der Benutzername des MariaDB Datenbanklogins (MariaDB-Variante)
    • +
    • MYSQL_PASSWORD Das Passwort der MySQL/MariaDB Datenbanklogins
    • +
    • MYSQL_DATABASE Der Name der MySQL/MariaDB Datenbank
    • +
    +
    Friendica Einstellungen#
    +

    Diese Umgebungsvariablen können nicht während des normalen Friendica Betriebs verwendet werden. +Sie werden stattdessen direkt in config/local.config.php gespeichert.

    +
      +
    • FRIENDICA_PHP_PATH Der Pfad zur PHP-Datei
    • +
    • FRIENDICA_ADMIN_MAIL Die Admin E-Mail-Adresse dieses Friendica Knotens (wird auch für den Admin-Zugang benötigt)
    • +
    • FRIENDICA_TZ Die Zeitzone von Friendica
    • +
    • FRIENDICA_LANG Die Sprache von Friendica
    • +
    +

    Gehe im Anschluss in den Friendica-Hauptordner und führe den Kommandozeilen Befehl aus:

    +
    bin/console autoinstall [--savedb]
    +
    +

    B.3: Optionen#

    +

    Alle Optionen werden in config/local.config.php gespeichert und überschreiben etwaige, zugehörige Umgebungsvariablen.

    +
      +
    • -H|--dbhost <host> Der Host der MySQL/MariaDB Datenbank (env MYSQL_HOST)
    • +
    • -p|--dbport <port> Der Port der MySQL/MariaDB Datenbank (env MYSQL_PORT)
    • +
    • -U|--dbuser <username> Der Benutzername des MySQL/MariaDB Datenbanklogins (env MYSQL_USER or MYSQL_USERNAME)
    • +
    • -P|--dbpass <password> Das Passwort der MySQL/MariaDB Datenbanklogins (env MYSQL_PASSWORD)
    • +
    • -d|--dbdata <database> Der Name der MySQL/MariaDB Datenbank (env MYSQL_DATABASE)
    • +
    • -b|--phppath <path> Der Pfad zur PHP-Datei (env FRIENDICA_PHP_PATH)
    • +
    • -A|--admin <mail> Die Admin E-Mail Adresse dieses Friendica Knotens (env FRIENDICA_ADMIN_MAIL)
    • +
    • -T|--tz <timezone> Die Zeitzone von Friendica (env FRIENDICA_TZ)
    • +
    • -L|--lang <language> Die Sprache von Friendica (env FRIENDICA_LANG)
    • +
    +

    Gehe in den Friendica-Hauptordner und führe den Kommandozeilen Befehl aus:

    +
    bin/console autoinstall [options]
    +
    +

    Einen Worker einrichten#

    +

    Erstelle einen Cron job oder einen regelmäßigen Task, um den Poller alle 5-10 Minuten im Hintergrund ablaufen zu lassen. +Beispiel:

    +
    cd /base/directory; /path/to/php bin/worker.php
    +
    +

    Ändere /base/directory und /path/to/php auf deine Systemvorgaben.

    +

    Wenn du einen Linux-Server nutzt, benutze den Befehl crontab -e und ergänze eine Zeile wie die Folgende; angepasst an dein System

    +
    */10 * * * * cd /home/myname/mywebsite; /usr/bin/php bin/worker.php
    +
    +

    Du kannst den PHP-Pfad finden, indem du den Befehl which php ausführst. +Wenn du Schwierigkeiten mit diesem Schritt hast, kannst du deinen Hosting-Anbieter kontaktieren. +Friendica wird nicht korrekt laufen, wenn dieser Schritt nicht erfolgreich abgeschlossen werden kann.

    +

    Falls das Einrichten des cron nicht möglich ist, kannst Du alternativ den "frontend worker" vom Administrationsinterface aus aktivieren.

    +

    Erstelle einen Backup-Plan#

    +

    Es werden schlimme Dinge geschehen. +Sei es nun ein Hardwareversagen oder eine kaputte Datenbank. +Deshalb solltest du dir, nachdem die Installation deines Friendica Knotens abgeschlossen ist, einen Backup-Plan erstellen.

    +

    Die wichtigste Datei ist die config/local.config.php im Stammverzeichnis deiner Friendica Installation. +Und da alle Daten in der Datenbank gespeichert werden, solltest du einen nicht allzu alten Dump der Friendica Datenbank zur Hand haben, solltest du deinen Knoten wieder herstellen müssen.

    + + +
    + +
    +
    + + + +
    + + + +
    +
    +
    +
    + + + + + + + + \ No newline at end of file diff --git a/develop/de/admin/installing-connectors/index.html b/develop/de/admin/installing-connectors/index.html new file mode 100644 index 0000000..a5cd383 --- /dev/null +++ b/develop/de/admin/installing-connectors/index.html @@ -0,0 +1,3482 @@ + + + + + + + + + + + + + + + + + + + + + + Connectors - Friendica Dokumentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + +
    + + + + +
    + + +
    + +
    + + + + + + + + + +
    +
    + + + +
    +
    +
    + + + + + + +
    +
    +
    + + + +
    +
    +
    + + + +
    +
    +
    + + +
    +
    + + + + + + + + + + + + + +

    Installing Connectors (Twitter/GNU Social)#

    +

    Friendica uses addons to provide connectivity to some networks, such as Twitter.

    +

    There is also an addon to post through to an existing account on a GNU Social service. +You only need this to post to an already existing GNU Social account, but not to communicate with GNU Social members in general.

    +

    All three addons require an account on the target network. +In addition, you (or typically the server administrator) will need to obtain an API key to provide authenticated access to your Friendica server.

    +

    Site Configuration#

    +

    Addons must be installed by the site administrator before they can be used. +This is accomplished through the site administration panel.

    +

    Each of the connectors also requires an "API key" from the service you wish to connect with. +Some addons allow you to enter this information in the site administration pages, while others may require you to edit your configuration file (config/local.config.php). +The ways to obtain these keys vary between the services, but they all require an existing account on the target service. +Once installed, these API keys can usually be shared by all site members.

    +

    The details of configuring each service follow (much of this information comes directly from the addon source files):

    +

    Twitter Addon for Friendica#

    +
      +
    • Author: Tobias Diekershoff
    • +
    • tobias.diekershoff@gmx.net
    • +
    • License: 3-clause BSD license
    • +
    +

    Configuration#

    +

    To use this addon you need a OAuth Consumer key pair (key & secret). +You can get it from Twitter.

    +

    Register your Friendica site as "Client" application with "Read & Write" access. +We do not need "Twitter as login". +When you've registered the app you get a key pair with an OAuth Consumer key and a secret key for your application/site. +Add this key pair to your config/local.config.php:

    +
    [twitter]
    +consumerkey = your consumer_key here
    +consumersecret = your consumer_secret here
    +
    +

    After this, your users can configure their Twitter account settings from "Settings -> Connector Settings".

    +

    More documentation#

    +

    Find the author's documentation here: http://diekershoff.homeunix.net/redmine/wiki/friendikaplugin/Twitter_Plugin

    +

    GNU Social Addon for Friendica#

    +
      +
    • Author: Tobias Diekershoff
    • +
    • tobias.diekershoff@gmx.net
    • +
    • License: 3-clause BSD license
    • +
    +

    Configuration#

    +

    When the addon is activated the user has to acquire the following in order to connect to the GNU Social account of choice.

    +
      +
    • The base URL for the GNU Social API, for quitter.se this is https://quitter.se/api/
    • +
    • OAuth Consumer key & secret
    • +
    +

    To get the OAuth Consumer key pair the user has to

    +

    1 ask her Friendica admin if a pair already exists or +2 has to register the Friendica server as a client application on the GNU Social server.

    +

    This can be done from the account settings under "Settings -> Connections -> Register an OAuth client application -> Register a new application" on the GNU Social server.

    +

    During the registration of the OAuth client remember the following:

    +
      +
    • Application names must be unique on the GNU Social site, so we recommend a Name of 'friendica-nnnn', replace 'nnnn' with a random number or your website name.
    • +
    • there is no callback url
    • +
    • register a desktop client
    • +
    • with read & write access
    • +
    • the Source URL should be the URL of your Friendica server
    • +
    +

    After the required credentials for the application are stored in the configuration you have to actually connect your Friendica account with GNU Social. +This is done from the Settings -> Connector Settings page. +Follow the Sign in with GNU Social button, allow access and then copy the security code into the box provided. +Friendica will then try to acquire the final OAuth credentials from the API.

    +

    If successful, the addon settings will allow you to select to post your public messages to your GNU Social account (have a look behind the little lock symbol beneath the status "editor" on your Home or Network pages).

    + + +
    + +
    +
    + + + +
    + + + +
    +
    +
    +
    + + + + + + + + \ No newline at end of file diff --git a/develop/de/admin/migrate/index.html b/develop/de/admin/migrate/index.html new file mode 100644 index 0000000..8b0d606 --- /dev/null +++ b/develop/de/admin/migrate/index.html @@ -0,0 +1,3584 @@ + + + + + + + + + + + + + + + + + + + + + + Migrate - Friendica Dokumentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + +
    + + + + +
    + + +
    + +
    + + + + + + + + + +
    +
    + + + +
    +
    +
    + + + + + + +
    +
    +
    + + + +
    +
    +
    + + + +
    +
    +
    + + +
    +
    + + + + + + + + + + + + + +

    Migrating to a new server installation#

    +

    Preparation#

    +

    New server#

    +

    Set up your new server as described here; follow the installation procedure until you have created a database.

    +

    Heads up to users#

    +

    Inform your users of an upcoming interruption to your service. +To ensure data consistency, your server needs to be offline during some steps of the migration processes.

    +

    You may also find these addons useful for communicating with your users prior to the migration process: +* blackout +* notifyall

    +

    Storage#

    +

    Check your storage backend with bin/console storage list in the root folder. +The output should look like this: +

    Sel | Name
    +-----------------------
    +     | Filesystem
    + *   | Database
    +

    +

    If you are not using Database run the following commands: +1. bin/console storage set Database to activate the database backend. +2. bin/console storage move to initiate moving the stored image files.

    +

    This process may take a long time depending on the size of your storage and your server's capacity. +Prior to initiating this process, you may want to check the number of files in the storage with the following command: tree -if -I index.html /path/to/storage/.

    +

    Cleaning up#

    +

    Before transferring your database, you may want to clean it up; ensure the expiration of database items is set to a reasonable value and activated via the administrator panel. +Admin > Site > Performance > Enable "Clean up database" +After adjusting these settings, the database cleaning up processes will be initiated according to your configured daily cron job.

    +

    To review the size of your database, log into MySQL with mysql -p run the following query: +

    SELECT table_schema AS "Database", SUM(data_length + index_length) / 1024 / 1024 / 1024 AS "Size (GB)" FROM information_schema.TABLES GROUP BY table_schema;
    +

    +

    You should see an output like this: +

    +--------------------+----------------+
    +| Database           | Size (GB)      |
    ++--------------------+----------------+
    +| friendica_db       | 8.054092407227 |
    +| [..........]       | [...........]  |
    ++--------------------+----------------+
    +

    +

    Finally, you may also want to optimise your database with the following command: mysqloptimize -p friendica-db

    +

    Going offline#

    +

    Stop background tasks and put your server in maintenance mode. +1. If you had set up a worker cron job like this */10 * * * * cd /var/www/friendica; /usr/bin/php bin/worker.php run crontab -e and comment out this line. Alternatively if you deploy a worker daemon, disable this instead. +2. Put your server into maintenance mode: bin/console maintenance 1 "We are currently upgrading our system and will be back soon."

    +

    Dumping DB#

    +

    Export your database: mysqldump -p friendica_db > friendica_db-$(date +%Y%m%d).sql and possibly compress it.

    +

    Transferring to new server#

    +

    Transfer your database and a copy of your configuration file config/local.config.php.copy to your new server installation.

    +

    Restoring your DB#

    +

    Import your database on your new server: mysql -p friendica_db < your-friendica_db-file.sql

    +

    Completing migration#

    +

    Configuration file#

    +

    Copy your old server's configuration file to config/local.config.php. +Ensure the newly created database credentials are identical to the setting in the configuration file; otherwise update them accordingly.

    +

    Cron job for worker#

    +

    Set up the required daily cron job. +Run crontab -e and add the following line according to your system specification +

    */10 * * * * cd /var/www/friendica; /usr/bin/php bin/worker.php
    +

    +

    DNS settings#

    +

    Adjust your DNS records by pointing them to your new server.

    +

    Troubleshooting#

    +

    If you are unable to log in to your newly migrated Friendica installation, check your web server's error and access logs and mysql logs for obvious issues.

    +

    If still unable to resolve the problem, it's likely an issue with your installation. +In this case, you may try to an entirely new Friendica installation on your new server, but use a different FQDN and DNS name. +Once you have this up and running, take it offline and purge the database and configuration file and try migrating to this installation.

    + + +
    + +
    +
    + + + +
    + + + +
    +
    +
    +
    + + + + + + + + \ No newline at end of file diff --git a/develop/de/admin/settings/index.html b/develop/de/admin/settings/index.html new file mode 100644 index 0000000..c9bb7b8 --- /dev/null +++ b/develop/de/admin/settings/index.html @@ -0,0 +1,4320 @@ + + + + + + + + + + + + + + + + + + + + + + Einstellungen - Friendica Dokumentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + +
    + + + + +
    + + +
    + +
    + + + + + + + + + +
    +
    + + + +
    +
    +
    + + + + + + +
    +
    +
    + + + +
    +
    +
    + + + +
    +
    +
    + + +
    +
    + + + + + + + + + + + + + +

    Einstellungen#

    +

    Wenn du der Administrator einer Friendica-Instanz bist, hast du Zugriff auf das sogenannte Adminpanel, in dem du die Friendica-Instanz konfigurieren kannst,

    +

    Auf der Startseite des Adminpanels werden die Informationen zu der Instanz zusammengefasst. +Die erste Zahl gibt die Anzahl von Nachrichten an, die nicht zugestellt werden konnten. +Die Zustellung wird zu einem späteren Zeitpunkt noch einmal versucht. +Unter dem Punkt "Warteschlange Inspizieren" kannst du einen schnellen Blick auf die zweite Warteschlange werfen. +Die zweite Zahl steht für die Anzahl der Aufgaben, die die Worker noch vor sich haben. +Die Worker arbeiten Hintergrundprozesse ab. +Die Aufgaben der Worker sind priorisiert und werden anhand dieser Prioritäten abgearbeitet.

    +

    Des Weiteren findest du eine Übersicht über die Accounts auf dem Friendica Knoten, die unter dem Punkt "Nutzer" moderiert werden können. +Sowie eine Liste der derzeit aktivierten Add-ons. +Diese Liste ist verlinkt, sodass du schnellen Zugriff auf die Informationsseiten der einzelnen Add-ons hast. +Abschließend findest du auf der Startseite des Adminpanels die installierte Version von Friendica. +Wenn du in Kontakt mit den Entwicklern trittst und Probleme oder Fehler zu schildern, gib diese Version bitte immer mit an.

    +

    Die Unterabschnitte des Adminpanels kannst du in der Seitenleiste auswählen.

    +

    Seite#

    +

    In diesem Bereich des Adminpanels findest du die Hauptkonfiguration deiner Friendica-Instanz. +Er ist in mehrere Unterabschnitte aufgeteilt, wobei die Grundeinstellungen oben auf der Seite zu finden sind.

    +

    Da die meisten Konfigurationsoptionen einen Hilfstext im Admin Panel haben, kann und will dieser Artikel nicht alle Einstellungen abdecken.

    +

    Grundeinstellungen#

    + +

    Hiermit legst du das Banner der Seite fest. Standardmäßig ist das Friendica-Logo und der Name festgelegt. +Du kannst hierfür HTML/CSS nutzen, um den Inhalt zu gestalten und/oder die Position zu ändern, wenn es nicht bereits voreingestellt ist.

    +

    Systemsprache#

    +

    Diese Einstellung legt die Standardsprache der Instanz fest. +Sie wird verwendet, wenn es Friendica nicht gelingt die Spracheinstellungen des Besuchers zu erkennen oder diese nicht unterstützt wird. +Nutzer können diese Auswahl in den Einstellungen des Benutzerkontos überschreiben.

    +

    Die Friendica Gemeinschaft bietet einige Übersetzungen an, von denen einige mehr andere weniger komplett sind. +Mehr Informationen zum Übersetzungsprozess von Friendica findest du auf dieser Seite der Dokumentation.

    +

    Systemweites Theme#

    +

    Hier kann das Theme bestimmt werden, welches standardmäßig zum Anzeigen der Seite verwendet werden soll. +Nutzer können in ihren Einstellungen andere Themes wählen. +Derzeit ist das "vier" Theme das vorausgewählte Theme.

    +

    Für mobile Geräte kannst du ein spezielles Theme wählen, wenn das Standard-theme ungeeignet für mobile Geräte sein sollte. +Das vier Theme z.B. unterstützt kleine Anzeigen und benötigt kein zusätzliches mobiles Theme.

    +

    Registrierung#

    +

    Registrierungsmethode#

    +

    Diese Einstellung regelt die Art der Registrierung. +Dabei kannst du zwischen den folgenden Optionen wählen:

    +
      +
    • Offen: Jeder kann ein neues Nutzerkonto anlegen und es sofort benutzen.
    • +
    • Bedarf der Zustimmung: Jeder kann ein Nutzerkonto anlegen. Dieses muss allerdings durch den Admin freigeschaltet werden, bevor es verwendet werden kann.
    • +
    • Geschlossen: Es können keine weiteren Nutzerkonten angelegt werden.
    • +
    +
    Einladungen#
    +

    Zusätzlich zu den oben genannten Möglichkeiten kann die Registrierung eines neuen Nutzerkontos an eine Einladung durch einen bestehenden Nutzer gekoppelt werden. +Hierzu muss in der config/local.config.php Datei die Option invitation_only aktiviert und als Registrierungsmethode entweder Offen oder Bedarf der Zustimmung gewählt werden.

    +

    Namen auf Vollständigkeit überprüfen#

    +

    Es kann vorkommen, dass viele Spammer versuchen, sich auf deiner Seite zu registrieren. +In Testphasen haben wir festgestellt, dass diese automatischen Registrierungen das Feld "Vollständiger Name" oft nur mit Namen ausfüllen, die kein Leerzeichen beinhalten. +Wenn du Leuten erlauben willst, sich nur mit einem Namen anzumelden, dann setze die Einstellung auf "true". +Die Standardeinstellung ist auf "false" gesetzt.

    +

    OpenID Unterstützung#

    +

    Standardmäßig wird OpenID für die Registrierung und für Logins genutzt. +Wenn du nicht willst, dass OpenID-Strukturen für dein System übernommen werden, dann setze "no_openid" auf "true". +Standardmäßig ist hier "false" gesetzt.

    +

    Unterbinde Mehrfachregistrierung#

    +

    Um mehrfache Seiten zu erstellen, muss sich eine Person mehrfach registrieren können. +Deine Seiteneinstellung kann Registrierungen komplett blockieren oder an Bedingungen knüpfen. +Standardmäßig können eingeloggte Nutzer weitere Accounts für die Seitenerstellung registrieren. +Hier ist weiterhin eine Bestätigung notwendig, wenn "REGISTER_APPROVE" ausgewählt ist. +Wenn du die Erstellung weiterer Accounts blockieren willst, dann setze die Einstellung "block_extended_register" auf "true". +Standardmäßig ist hier "false" gesetzt.

    +

    Datei hochladen#

    +

    Datenspeicher Backend#

    +

    Legt das Datenspeicher-Backend fest, mit dem Friendica hoch geladene Daten speichert. +Zwei Speicher Backends sind standardmäßig bei Friendica verfügbar:

    +
      +
    • Database: Die Daten werden in einer speziellen Tabelle in der Datenbank (storage) gespeichert.
    • +
    • Filesystem: Die Daten werden als Dateien im Dateisystem gespeichert.
    • +
    +

    Weitere Speicher Backends können als Add-ons von Drittanbietern verfügbar sein. +Falls ein solches verwendet wird, sei an dieser Stelle nur auf deren Dokumentation für weitere Informationen verwiesen.

    +

    Die Grundeinstellung ist 'Datenbank (legacy)': Dies ist die alte Methode von Friendica Daten direkt in der Datenbank abzulegen.

    +

    Bestehende Daten können zum aktuell ausgewählten Backend verschoben werden. +Hierfür kann der 'storage move' Befehl der Friendica Konsole verwendet werden.

    +

    Sollte das ausgewählte Speicher-Backand zusätzliche Konfigurationsparameter besitzen, werden nach der Auswahl des Backends hier weitere Felder angezeigt.

    +
    Dateipfad zum Speicher#
    +

    Der Basispfad unter dem das Filesystem Datenspeicher Backend die Daten speichern soll.

    +

    Um zu verhindern, dass Daten unter Umgehung der Privatsphären-Einstellungen heruntergeladen werden, sollte dieser Pfad außerhalb der Verzeichnisstruktur des Webservers liegen.

    +

    Die Grundeinstellung ist storage, das ist das storage Unterverzeichnis innerhalb des Friendica Verzeichnisses.

    +

    Maximale Bildgröße#

    +

    Maximale Bild-Dateigröße in Byte. Standardmäßig ist 0 gesetzt, was bedeutet, dass kein Limit gesetzt ist.

    +

    Regeln#

    +

    URL des weltweiten Verzeichnisses#

    +

    Mit diesem Befehl wird die URL eingestellt, die zum Update des globalen Verzeichnisses genutzt wird. +Dieser Befehl ist in der Standardkonfiguration enthalten. +Der nicht dokumentierte Teil dieser Einstellung ist, dass das globale Verzeichnis gar nicht verfügbar ist, wenn diese Einstellung nicht gesetzt wird. +Dies erlaubt eine private Kommunikation, die komplett vom globalen Verzeichnis isoliert ist.

    +

    Erzwinge Veröffentlichung#

    +

    Standardmäßig können Nutzer selbst auswählen, ob ihr Profil im Seitenverzeichnis erscheint. +Diese Einstellung zwingt alle Nutzer dazu, im Verzeichnis zu erscheinen. +Diese Einstellung kann vom Nutzer nicht deaktiviert werden. Die Standardeinstellung steht auf "false".

    +

    Öffentlichen Zugriff blockieren#

    +

    Aktiviere diese Einstellung, um den öffentlichen Zugriff auf alle Seiten zu sperren, solange man nicht eingeloggt ist. +Das blockiert die Ansicht von Profilen, Freunden, Fotos, vom Verzeichnis und den Suchseiten. +Ein Nebeneffekt ist, dass Einträge dieser Seite nicht im globalen Verzeichnis erscheinen. +Wir empfehlen, speziell diese Einstellung auszuschalten (die Einstellung ist an anderer Stelle auf dieser Seite erklärt). +Beachte: Das ist speziell für Seiten, die beabsichtigen, von anderen Friendica-Netzwerken abgeschottet zu sein. +Unautorisierte Personen haben ebenfalls nicht die Möglichkeit, Freundschaftsanfragen von Seitennutzern zu beantworten. +Die Standardeinstellung ist deaktiviert. +Verfügbar in Version 2.2 und höher.

    +

    Für Besucher verfügbare Gemeinschaftsseiten#

    +

    Die Gemeinschaftsseiten zeigen all öffentlichen Beiträge. +Es gibt zwei Gemeinschaftsseiten, eine lokale auf der die Beiträge der Nutzer des Knotens gesammelt werden und eine globale auf der alle bekannten Beiträge aus dem gesamten Netzwerk erscheinen. +Mit dieser Einstellung kann geregelt werden, welche dieser beiden Seiten Besucher aufrufen können. +Angemeldete Nutzer des Knotens können grundsätzlich beide Seiten verwenden.

    +

    Hinweis: Einige Einstellungen, wie z.B. das Verbergen von Kontakten auf der Profilseite, können die Sichtbarkeit der Beiträge auf der Gemeinschaftsseiten beeinflussen.

    +

    Erlaubte Domains für Kontakte#

    +

    Kommagetrennte Liste von Domains, welche eine Freundschaft mit dieser Seite eingehen dürfen. +Wildcards werden akzeptiert Standardmäßig sind alle gültigen Domains erlaubt.

    +

    Mit dieser Option kann man einfach geschlossene Netzwerke, z.B. im schulischen Bereich aufbauen, aus denen nicht mit dem Rest des Netzwerks kommuniziert werden soll.

    +

    Erlaubte Domains für E-Mails#

    +

    Kommagetrennte Liste von Domains, welche bei der Registrierung als Part der E-Mail-Adresse erlaubt sind. +Das grenzt Leute aus, die nicht Teil der Gruppe oder Organisation sind. +Wildcards werden akzeptiert Standardmäßig sind alle gültigen E-Mail-Adressen erlaubt.

    +

    Nutzern erlauben das remote_self Flag zu setzen#

    +

    Webb du die Option Nutzern erlauben das remote_self Flag zu setzen aktivierst, können alle Nutzer Atom Feeds in den Kontakteinstellungen als "Entferntes Konto" markieren. +Dadurch werden automatisch alle Beiträge dieser Feeds für diesen Nutzer gespiegelt und an die Kontakte bei Friendica verteilt.

    +

    Dieses Feature kann z.B. dafür genutzt werden Blogbeiträge zu spiegeln. +In der Grundeinstellung ist es nicht aktiviert, da es zusätzliche Last auf dem Server verursachen kann. +Außerdem könnte es durch Nutzer als Spam Verteiler missbraucht werden.

    +

    Als Administrator der Friendica-Instanz kannst du diese Einstellungen ansonsten nur direkt in der Datenbank vornehmen. +Bevor du das machst, solltest du sicherstellen, dass du ein Backup der Datenbank hast und genau weißt was die Änderungen an der Datenbank bewirken, die du vornehmen willst.

    +

    Explizite Inhalte#

    +

    Wenn Sie einen Knoten mit explizitem, nicht jugendfreien Inhalt betreiben, können Sie dies mit dieser Option ankündigen. +Ist diese Option aktiviert, wird ein Informationsflag in den veröffentlichten Informationen zu Ihrem Knoten gesetzt. +(Sollte Server Informationen veröffentlichen aktiviert sein.)

    +

    Zusätzlich wird auf der Registrierungsseite für neue Benutzer ein Hinweis angezeigt.

    +

    Erweitert#

    +

    Proxy Einstellungen#

    +

    Wenn deine Seite eine Proxy-Einstellung nutzt, musst du diese Einstellungen vornehmen, um mit anderen Seiten im Internet zu kommunizieren.

    +

    Netzwerk Wartezeit#

    +

    Legt fest, wie lange das Netzwerk warten soll, bevor ein Timeout eintritt. +Der Wert wird in Sekunden angegeben. Standardmäßig ist 60 eingestellt; 0 steht für "unbegrenzt" (nicht empfohlen).

    +

    UTF-8 Reguläre Ausdrücke#

    +

    Während der Registrierung werden die Namen daraufhin geprüft, ob sie reguläre UTF-8-Ausdrücke nutzen. +Hierfür wird PHP benötigt, um mit einer speziellen Einstellung kompiliert zu werden, die UTF-8-Ausdrücke benutzt. +Wenn du absolut keine Möglichkeit hast, Accounts zu registrieren, setze diesen Wert auf ja.

    +

    SSL Überprüfen#

    +

    Standardmäßig erlaubt Friendica SSL-Kommunikation von Seiten, die "selbst unterzeichnete" SSL-Zertifikate nutzen. +Um eine weitreichende Kompatibilität mit anderen Netzwerken und Browsern zu gewährleisten, empfehlen wir, selbst unterzeichnete Zertifikate nicht zu nutzen. +Aber wir halten dich nicht davon ab, solche zu nutzen. SSL verschlüsselt alle Daten zwischen den Webseiten (und für deinen Browser), was dir eine komplett verschlüsselte Kommunikation erlaubt. +Auch schützt es deine Login-Daten vor Datendiebstahl. Selbst unterzeichnete Zertifikate können kostenlos erstellt werden. +Diese Zertifikate können allerdings Opfer eines sogenannten "man-in-the-middle"-Angriffs werden, und sind daher weniger bevorzugt. +Wenn du es wünscht, kannst du eine strikte Zertifikatabfrage einstellen. +Das führt dazu, dass du keinerlei Verbindung zu einer selbst unterzeichneten SSL-Seite erstellen kannst

    +

    Automatisch ein Kontaktverzeichnis erstellen#

    +

    Performance#

    +

    Worker#

    +

    In diesem Abschnitt kann der Hintergrund-Prozess konfiguriert werden. +Bevor ein neuer Worker Prozess gestartet wird, überprüft das System, dass die vorhandenen Resource ausreichend sind. +Aus diesem Grund kann es sein, dass die maximale Zahl der Hintergrundprozesse nicht erreicht wird.

    +

    Die Aufgaben, die im Hintergrund erledigt werden, haben Prioritäten zugeteilt. +Um garantieren zu können, dass wichtige Prozesse schnellstmöglich abgearbeitet werden können, selbst wenn das System gerade stark belastet ist, sollte die fastlane aktiviert sein.

    +

    Umsiedeln#

    +

    Nutzer#

    +

    In diesem Abschnitt des Adminpanels kannst du die Nutzer deiner Friendica-Instanz moderieren.

    +

    Solltest du für Registrierungsmethode die Einstellung "Bedarf Zustimmung" gewählt haben, werden hier zu Beginn der Seite neue Registrationen aufgelistet. +Als Administrator kannst du hier die Registration akzeptieren oder ablehnen.

    +

    Unter dem Abschnitt mit den Registrationen werden die aktuell auf der Instanz registrierten Nutzer aufgelistet. +Die Liste kann nach Namen, E-Mail-Adresse, Datum der Registration, der letzten Anmeldung oder dem letzten Beitrag und dem Account Typ sortiert werden. +An dieser Stelle kannst du existierende Accounts vom Zugriff auf die Instanz blockieren, sie wieder freigeben oder Accounts endgültig löschen.

    +

    Im letzten Bereich auf der Seite kannst du als Administrator neue Accounts anlegen. +Das Passwort für so eingerichtete Accounts werden per E-Mail an die Nutzer geschickt.

    +

    Addons#

    +

    Dieser Bereich des Adminpanels dient der Auswahl und Konfiguration der Erweiterungen von Friendica. +Sie müssen in das /addon Verzeichnis kopiert werden. +Auf der Seite wird eine Liste der verfügbaren Erweiterungen angezeigt. +Neben den Namen der Erweiterungen wird ein Indikator angezeigt, der anzeigt, ob das Add-on gerade aktiviert ist oder nicht.

    +

    Wenn du die Erweiterungen aktualisiert, die du auf deiner Friendica-Instanz nutzt, könnte es sein, dass sie neu geladen werden müssen, damit die Änderungen aktiviert werden. +Um diesen Prozess zu vereinfachen, gibt es am Anfang der Seite einen Button um alle aktiven Add-ons neu zu laden.

    +

    Themen#

    +

    Der Bereich zur Kontrolle der auf der Friendica-Instanz verfügbaren Themen funktioniert analog zum Add-ons Bereich. +Jedes Theme hat eine extra Seite auf der aktuelle Status, ein Bildschirmfoto des Themes, zusätzliche Informationen und eventuelle Einstellungen des Themes zu finden sind. +Genau wie Erweiterungen können Themes in der Übersichtsliste oder der Theme-Seite aktiviert bzw. deaktiviert werden. +Um ein Standard-theme für die Instanz zu wählen, benutze bitte die Seiten Bereich des Adminpanels.

    +

    Zusätzliche Features#

    +

    Es gibt einige optionale Features in Friendica, die Nutzer benutzen können oder halt nicht. +Zum Beispiel den dislike Button oder den Webeditor beim Erstellen von neuen Beiträgen. +In diesem Bereich des Adminpanels kannst du die Grundeinstellungen für diese Features festlegen und gegebenenfalls die Entscheidung treffen, dass Nutzer deiner Instanz diese auch nicht mehr ändern können.

    +

    DB Updates#

    +

    Wenn sich die Datenbankstruktur Friendicas ändert, werden die Änderungen automatisch angewandt. +Solltest du den Verdacht haben, dass eine Aktualisierung fehlgeschlagen ist, kannst du in diesem Bereich des Adminpanels den Status der Aktualisierungen überprüfen.

    +

    Warteschlange Inspizieren#

    +

    Auf der Eingangsseite des Adminpanels werden zwei Zahlen fpr die Warteschlangen angegeben. +Die zweite Zahl steht für die Beiträge, die initial nicht zugestellt werden konnten und später nochmal zugestellt werden sollen. +Sollte diese Zahl durch die Decke brechen, solltest du nachsehen an welchen Kontakt die Zustellung der Beiträge nicht funktioniert.

    +

    Unter dem Menüpunkt "Warteschlange Inspizieren" findest du eine Liste dieser nicht zustellbaren Beiträge. +Diese Liste ist nach dem Empfänger sortiert. +Die Kommunikation zu dem Empfänger kann aus unterschiedlichen Gründen gestört sein. +Der andere Server könnte offline sein, oder gerade einfach nur eine hohe Systemlast aufweisen.

    +

    Aber keine Panik! +Friendica wird die Beiträge nicht für alle Zeiten in der Warteschlange behalten. +Nach einiger Zeit werden Knoten als inaktiv identifiziert und Nachrichten an Nutzer dieser Knoten aus der Warteschlange gelöscht.

    +

    Server Blockliste#

    +

    Auf dieser Seite des Adminpanels können Administratoren einer Friendica-Instanz die komplette Kommunikation (eingehend und ausgehend) mit bestimmten Domains unterbinden. +Für jede dieser Blockierungen muss ein Grund angegeben werden, welcher auf der Informationsseite https://your-site.info/friendica angezeigt wird. +Der Abgleich der Domainnamen ist exakt, Subdomains werden nicht automatisch ebenfalls blockiert.

    +

    Federation Statistik#

    +

    Deine Instanz ist ein Teil eines Netzwerks von Servern dezentraler sozialer Netzwerke, der sogenannten Federation. +In diesem Bereich des Adminpanels findest du ein paar Zahlen zu dem Teil der Federation, die deine Instanz kennt.

    +

    Eintrag löschen#

    +

    Hier kann man als Admin einer Friendica Instanz Beiträge und ggf. damit gekoppelte Unterhaltungen von der Instanz löschen. +Dazu muss man nur die GUID des Beitrags kennen. +Diese kann u.a. in der Adresse der /display-Seite gefunden werden, wenn man dem Link zum Originalbeitrag folgt. +Hier ist die GUID der letzte Teil der URL in der Adresszeile des Browsers.

    +

    Addon Features#

    +

    Einige der Erweiterungen von Friendica benötigen global gültige Einstellungen, die der Administrator vornehmen muss. +Diese Erweiterungen sind hier aufgelistet, damit du die Einstellungen schneller findest.

    +

    Protokolle#

    +

    Dieser Bereich des Adminpanels ist auf zwei Seiten verteilt. +Die eine Seite dient der Konfiguration, die andere dem Anzeigen der Logs.

    +

    Du solltest die Logdatei nicht in einem Verzeichnis anlegen, auf das man vom Internet aus zugreifen kann. +Wenn du das dennoch tun musste und die Standardeinstellungen des Apache Servers verwendest, dann solltest du darauf achten, dass die Logdateien mit der Endung .log oder .out enden. +Solltest du einen anderen Webserver verwenden, solltest du sicherstellen, dass der Zugriff zu Dateien mit diesen Endungen nicht möglich ist.

    +

    Es gibt fünf Level der Ausführlichkeit, mit denen Friendica arbeitet: Normal, Trace, Debug, Data und All. +Normalerweise solltest du für den Betrieb deiner Friendica-Instanz keine Logs benötigen. +Wenn du versuchst einem Problem auf den Grund zu gehen, solltest du das "DEBUG" Level wählen. +Mit dem "All" Level schreibt Friendica alles in die Logdatei. +Die Datenmenge der geloggten Daten kann relativ schnell anwachsen, deshalb empfehlen wir das Anlegen von Protokollen nur zu aktivieren, wenn es unbedingt nötig ist.

    +

    Die Größe der Logdateien kann schnell anwachsen. +Du solltest deshalb einen Dienst zur log rotation einrichten.

    +

    Bekannte Probleme: Der Dateiname friendica.log kann bei speziellen Server Konfigurationen zu Problemen führen (siehe issue 2209).

    +

    Normalerweise werden Fehler- und Warnmeldungen von PHP unterdrückt. +Wenn du sie aktivieren willst, musst du folgendes in der config/local.config.php Datei eintragen um die Meldungen in die Datei php.out zu speichern

    +
    <?php 
    +error_reporting(E_ERROR | E_WARNING | E_PARSE );
    +ini_set('error_log','php.out');
    +ini_set('log_errors','1');
    +ini_set('display_errors', '0');
    +
    +

    Die Datei php.out muss vom Webserver schreibbar sein und sollte ebenfalls außerhalb der Webverzeichnisse liegen. +Es kommt gelegentlich vor, dass nicht deklarierte Variablen referenziert werden, deshalb raten wir davon ab E_NOTICE oder E_ALL zu verwenden. +Die überwiegende Mehrzahl der auf diesen Stufen dokumentierten Fehler sind absolut harmlos. +Solltest du mit den oben empfohlenen Einstellungen Fehler finden, teile sie bitte den Entwicklern mit. +Im Allgemeinen sind dies Fehler, die behoben werden sollten.

    +

    Solltest du eine leere (weiße) Seite vorfinden, während du Friendica nutzt, werfe bitte einen Blick in die PHP Logs. +Solche White Screens sind so gut wie immer ein Zeichen dafür, dass ein Fehler aufgetreten ist.

    +

    Diagnose#

    +

    In diesem Bereich des Adminpanels findest du zwei Werkzeuge, mit der du untersuchen kannst, wie Friendica bestimmte Ressourcen einschätzt. +Diese Werkzeuge sind insbesondere bei der Analyse von Kommunikationsproblemen hilfreich.

    +

    "Adresse untersuchen" zeigt Informationen zu einer URL an, wie Friendica sie wahrnimmt.

    +

    Mit dem zweiten Werkzeug "Webfinger überprüfen" kannst du Informationen zu einem Ding anfordern, das über einen Webfinger (jemand@example.com) identifiziert wird.

    +

    Die Ausnahmen der Regel#

    +

    Für die oben genannte Regel gibt es vier Ausnahmen, deren Konfiguration nicht über das Adminpanel vorgenommen werden kann. +Dies sind die Datenbank Einstellungen, die Administrator Accounts, der PHP Pfad und die Konfiguration einer eventuellen Installation in ein Unterverzeichnis unterhalb der Hauptdomain.

    +

    Datenbank Einstellungen#

    +

    Mit den folgenden Einstellungen kannst du die Zugriffsdaten für den Datenbankserver festlegen.

    +
    'database' => [
    +    'hostname' => 'localhost',
    +    'username' => 'mysqlusername',
    +    'password' => 'mysqlpassword',
    +    'database' => 'mysqldatabasename',
    +    'charset' => 'utf8mb4',
    +],
    +
    +

    Sollten alle der folgenden Environment-Variablen gesetzt sein, wird Friendica diese anstatt der vorher konfigurierten Werte nutzen.

    +
    MYSQL_HOST
    +MYSQL_PORT
    +MYSQL_USERNAME
    +MYSQL_PASSWORD
    +MYSQL_DATABASE
    +
    +

    Administratoren#

    +

    Du kannst einen, oder mehrere Accounts, zu Administratoren machen. +Normalerweise trifft dies auf den ersten Account zu, der nach der Installation angelegt wird. +Die Liste der E-Mail-Adressen kann aber einfach erweitert werden. +Mit keiner der angegebenen E-Mail-Adressen können weitere Accounts registriert werden.

    +
    'config' => [
    +    'admin_email' => 'you@example.com, buddy@example.com',
    +],
    +
    +

    PHP Pfad#

    +

    Einige Prozesse von Friendica laufen im Hintergrund. +Für diese Prozesse muss der Pfad zu der PHP Version gesetzt sein, die verwendet werden soll.

    +
    'config' => [
    +    'php_path' => '/usr/bin/php',
    +],
    +
    +

    Unterverzeichnis Konfiguration#

    +

    Man kann Friendica in ein Unterverzeichnis des Webservers installieren. +Wir raten allerdings dringen davon ab, da es die Interoperabilität mit anderen Netzwerken (z.B. Diaspora, GNU Social, Hubzilla) verhindert. +Mal angenommen, du hast ein Unterverzeichnis tests und willst Friendica in ein weiteres Unterverzeichnis installieren, dann lautet die Konfiguration hierfür:

    +
    'system' => [
    +    'urlpath' => 'tests/friendica',
    +],
    +
    +

    Weitere Ausnahmen#

    +

    Es gibt noch einige experimentelle Einstellungen, die nur in der config/local.config.php Datei konfiguriert werden können. +Im Konfigurationswerte, die nur in der config/local.config.php gesetzt werden können (EN) Artikel kannst du mehr darüber erfahren.

    + + +
    + +
    +
    + + + +
    + + + +
    +
    +
    +
    + + + + + + + + \ No newline at end of file diff --git a/develop/de/admin/ssl/index.html b/develop/de/admin/ssl/index.html new file mode 100644 index 0000000..071074f --- /dev/null +++ b/develop/de/admin/ssl/index.html @@ -0,0 +1,3522 @@ + + + + + + + + + + + + + + + + + + + + + + SSL Einstellungen - Friendica Dokumentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + +
    + + + + +
    + + +
    + +
    + + + + + + + + + +
    +
    + + + +
    +
    +
    + + + + + + +
    +
    +
    + + + +
    + +
    + + +
    +
    + + + + + + + + + + + + + +

    Friendica mit SSL nutzen#

    +

    Disclaimer#

    +

    Dieses Dokument wurde im November 2016 aktualisiert. +SSL-Verschlüsselung ist sicherheitskritisch. +Das bedeutet, dass sich die empfohlenen Einstellungen schnell verändern. +Halte deine Installation auf dem aktuellen Stand und verlasse dich nicht darauf, dass dieses Dokument genauso schnell aktualisiert wird, wie sich Technologien verändern!

    +

    Einleitung#

    +

    Wenn du deine eigene Friendica-Seite betreibst, willst du vielleicht SSL (https) nutzen, um die Kommunikation zwischen den Servern und zwischen dir und deinem Server zu verschlüsseln.

    +

    Dafür gibt es grundsätzlich zwei Arten von SSL-Zertifikaten: Selbst-signierte Zertifikate und Zertifikate, die von einer Zertifizierungsstelle (CA) unterschrieben sind. +Technisch gesehen sorgen beide für valide Verschlüsselung. +Mit selbst-signierten Zertifikaten gibt es jedoch ein Problem: +Sie sind weder in Browsern noch auf anderen Servern installiert. +Deshalb führen sie zu Warnungen über "nicht vertrauenswürdige Zertifikate". +Das ist verwirrend und stört sehr.

    +

    Aus diesem Grund empfehlen wir, dass du dir ein von einer CA unterschriebenes Zertifikat besorgst. +Normalerweise kosten sie Geld - und sind nur für eine begrenzte Zeit gültig (z.B. ein Jahr oder zwei).

    +

    Es gibt aber Möglichkeiten, ein vertrauenswürdiges Zertifikat umsonst zu bekommen.

    +

    Wähle deinen Domainnamen#

    +

    Dein SSL-Zertifikat wird für eine bestimmte Domain gültig sein oder sogar nur für eine Subdomain. +Entscheide dich endgültig für einen Domainnamen, bevor du ein Zertifikat bestellst. +Wenn du das Zertifikat hast, brauchst du ein neues, wenn du den Domainnamen ändern möchtest.

    +

    Gehosteter Webspace#

    +

    Wenn deine Friendica-Instanz auf einem gehosteten Webspace läuft, solltest du dich bei deinem Hosting-Provider informieren. +Dort bekommst du Instruktionen, wie es dort läuft. +Du kannst bei deinem Provider immer ein kostenpflichtiges Zertifikat bestellen. +Sie installieren es für dich oder haben in der Weboberfläche eine einfache Upload-Möglichkeit für Zertifikat und Schlüssel.

    +

    Um Geld zu sparen, kann es sich lohnen, dort auch nachzufragen, ob sie ein anderes Zertifikat, das du selbst beschaffst, für dich installieren würden. +Wenn ja, dann lies weiter.

    +

    Let's encrypt#

    +

    Wenn du einen eigenen Server betreibst und den Nameserver kontrollierst, könnte auch die Initiative "Let's encrypt" interessant für dich werden. +Sie bietet nicht nur freie SSL Zertifikate, sondern auch einen automatisierten Prozess zum Erneuern der Zertifikate. +Um LetsEncrypt Zertifikate verwenden zu können, musst du dir einen Client auf deinem Server installieren. +Eine Anleitung zum offiziellen Client findest du hier. +Falls du dir andere Clients anschauen willst, kannst du einen Blick in diese Liste von alternativen LetsEncrypt Clients.

    +

    Webserver-Einstellungen#

    +

    Im Wiki von Mozilla gibt es Anleitungen für die Konfiguration sicherer Webserver. +Dort findest du Empfehlungen, die auf verschiedene Webserver zugeschnitten sind.

    +

    Teste deine SSL-Einstellungen#

    +

    Wenn du fertig bist, kannst du auf der Testseite SSL-Labs prüfen lassen, ob du alles richtig gemacht hast.

    +

    Friendica Konfigurieren#

    +

    Wenn du deine Friendica Instanz über https erreichen kannst, solltest du ein paar Einstellungen vornehmen, um sicherzustellen, dass deine Nutzer ausschließlich über https zugreifen können.

    +

    Webserver-Umleitungen#

    +

    Dies ist der einfachste Weg den Zugriff für die ganze Webseite abzusichern. +Jedes Mal, wenn ein Nutzer Friendica aufruft wird er permanent vom Webserver auf die abgesicherte Seite umgeleitet.

    +

    Wenn du den Apache Webserver verwendest, aktiviere die Module rewrite und ssl (bei einem Shared-Hosting Prider sollte dies bereits der Fall sein):

    +
    sudo a2enmod rewrite ssl
    +
    +

    und füge die folgenden Zeilen zur .htaccess Datei im Wurzelverzeichnis deiner Friendica Instanz hinzu:

    +
        RewriteEngine On
    +    RewriteCond %{SERVER_PORT} 80
    +    RewriteRule ^(.*)$ https://your.friendica.domain/$1 [R=301,L]
    +
    +

    (Dank an AlfredSK.

    +

    Bei nginx solltest du deinen Server folgendermaßen konfigurieren (documentation):

    +
        server {
    +         listen 80;
    +         server_name your.friendica.domain;
    +         return 301 https://$server_name$request_uri;
    +    }
    +
    +

    SSL Einstellungen#

    +

    Im Admin-Panel gibt es drei Einstellungen, die SSL betreffen:

    +
      +
    1. Regeln für SSL Links: Diese Einstellung betrifft wie Friendica interne Links erzeugt. Wenn deine SSL Installation erfolgreich war, empfehlen wir die Einstellung "SSL für alle Links erzwingen".
    2. +
    3. Erzwinge SSL: Mit dieser Einstellung werden alle externen Links auf HTTPS gesetzt. Dies kann bei Mixed-Content Problemen helfen, allerdings unterstützen noch nicht alle Webseiten HTTPS. Benutzung auf eigene Gefahr.
    4. +
    5. SSL überprüfen: Wenn diese Einstellung aktiv ist, wird Friendica nicht mehr mit Instanzen interagieren, die über ein selbst signiertes Zertifikat verfügen. Da selbst signierte Zertifikate ein Hinweis auf Man-in-the. Middle Angriffe sein können, empfehlen wir dies zu tun.
    6. +
    + + +
    + +
    +
    + + + +
    + + + +
    +
    +
    +
    + + + + + + + + \ No newline at end of file diff --git a/develop/de/admin/tools/index.html b/develop/de/admin/tools/index.html new file mode 100644 index 0000000..48cebf7 --- /dev/null +++ b/develop/de/admin/tools/index.html @@ -0,0 +1,3433 @@ + + + + + + + + + + + + + + + + + + + + + + Tools - Friendica Dokumentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + +
    + + + + +
    + + +
    + +
    + + + + + + + + + +
    +
    + + + +
    +
    +
    + + + + + + +
    +
    +
    + + + +
    +
    +
    + + + +
    +
    +
    + + +
    +
    + + + + + + + + + + + + + +

    Admin Tools#

    +

    Friendica Tools#

    +

    Friendica has a build in command console you can find in the bin directory. +The console provides the following commands:

    +
      +
    • cache: Manage node cache
    • +
    • config: Edit site config
    • +
    • createdoxygen: Generate Doxygen headers
    • +
    • dbstructure: Do database updates
    • +
    • docbloxerrorchecker: Check the file tree for DocBlox errors
    • +
    • extract: Generate translation string file for the Friendica project (deprecated)
    • +
    • globalcommunityblock: Block remote profile from interacting with this node
    • +
    • globalcommunitysilence: Silence remote profile from global community page
    • +
    • archivecontact: Archive a contact when you know that it isn't existing anymore
    • +
    • help: Show help about a command, e.g (bin/console help config)
    • +
    • autoinstall: Starts automatic installation of friendica based on values from htconfig.php
    • +
    • maintenance: Set maintenance mode for this node
    • +
    • newpassword: Set a new password for a given user
    • +
    • php2po: Generate a messages.po file from a strings.php file
    • +
    • po2php: Generate a strings.php file from a messages.po file
    • +
    • typo: Checks for parse errors in Friendica files
    • +
    • postupdate: Execute pending post update scripts (can last days)
    • +
    • storage: Manage storage backend
    • +
    • relay: Manage ActivityPub relay servers
    • +
    +

    Please consult bin/console help on the command line interface of your server for details about the commands.

    +

    3rd Party Tools#

    +

    In addition to the tools Friendica includes, some 3rd party tools can make your admin days easier.

    +

    Fail2ban#

    +

    Fail2ban is an intrusion prevention framework (see Wikipedia) that you can use to forbid access to a server under certain conditions, e.g. 3 failed attempts to log in, for a certain amount of time.

    +

    The following configuration was provided by Steffen K9 using Debian. +You need to adjust the logpath in the jail.local file and the bantime (value is in seconds).

    +

    In /etc/fail2ban/jail.local create a section for Friendica:

    +
    [friendica]
    +enabled = true
    +findtime = 300
    +bantime  = 900
    +filter = friendica
    +port = http,https
    +logpath = /var/log/friend.log
    +logencoding = utf-8
    +
    +

    And create a filter definition in /etc/fail2ban/filter.d/friendica.conf:

    +
    [Definition]
    +failregex = ^.*authenticate\: failed login attempt.*\"ip\"\:\"<HOST>\".*$
    +ignoreregex =
    +
    +

    Additionally, you have to define the number of failed logins before the ban should be activated. +This is done either in the global configuration or for each jail separately. +You should inform your users about the number of failed login attempts you grant them. +Otherwise, you'll get many reports about the server not functioning if the number is too low.

    +

    Log rotation#

    +

    If you have activated the logs in Friendica, be aware that they can grow to a significant size. +To keep them in control you should add them to the automatic log rotation, e.g. using the logrotate command.

    +

    In /etc/logrotate.d/ add a file called friendica that contains the configuration. +The following will compress /var/log/friendica (assuming this is the location of the log file) on a daily basis and keep 2 days of back-log.

    +
    /var/log/friendica.log {
    +    compress
    +    daily
    +    rotate 2
    +}
    +
    + + +
    + +
    +
    + + + +
    + + + +
    +
    +
    +
    + + + + + + + + \ No newline at end of file diff --git a/develop/de/admin/update/index.html b/develop/de/admin/update/index.html new file mode 100644 index 0000000..ba880c5 --- /dev/null +++ b/develop/de/admin/update/index.html @@ -0,0 +1,3507 @@ + + + + + + + + + + + + + + + + + + + + + + Update - Friendica Dokumentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + +
    + + + + +
    + + +
    + +
    + + + + + + + + + +
    +
    + + + +
    +
    +
    + + + + + + +
    +
    +
    + + + +
    +
    +
    + + + +
    +
    +
    + + +
    +
    + + + + + + + + + + + + + +

    Updating Friendica#

    +

    Using a Friendica archive#

    +

    If you installed Friendica in the path/to/friendica folder:

    +
      +
    1. Unpack the new Friendica archive in path/to/friendica_new.
    2. +
    3. Copy the following items from path/to/friendica to path/to/friendica_new:
    4. +
    5. config/local.config.php
    6. +
    7. proxy/
      +The following items only need to be copied if they are located inside your friendica path:
    8. +
    9. your storage folder as set in Admin -> Site -> File Upload -> Storage base path
    10. +
    11. your item cache as set in Admin -> Site -> Performance -> Path to item cache
    12. +
    13. your temp folder as set in Admin -> Site -> Advanced -> Temp path
    14. +
    15. Rename the path/to/friendica folder to path/to/friendica_old.
    16. +
    17. Rename the path/to/friendica_new folder to path/to/friendica.
    18. +
    19. Check your site. Note: it may go into maintenance mode to update the database schema.
    20. +
    21. If everything works, just delete the path/to/friendica_old folder.
    22. +
    +

    To update Addons from an archive, simply delete the path/to/friendica/addon and replace it with the provided archive.

    +

    Using Git#

    +

    You can get the latest changes at any time with

    +
    cd path/to/friendica
    +git pull
    +bin/composer.phar install --no-dev
    +
    +

    The addon tree has to be updated separately like so:

    +
    cd path/to/friendica/addon
    +git pull
    +
    +

    For both repositories: +The default branch to use is the stable branch, which is the stable version of Friendica. +It is updated about four times a year on a fixed schedule.

    +

    If you want to use and test bleeding edge code please check out the develop branch. +The new features and fixes will be merged from develop into stable after a release candidate period before each release.

    +

    Warning: The develop branch is unstable, and breaks on average once a month for at most 24 hours until a patch is submitted and merged. +Be sure to pull frequently if you choose the develop branch.

    +

    Considerations before upgrading Friendica#

    +

    MySQL >= 5.7.4#

    +

    Starting from MySQL version 5.7.4, the IGNORE keyword in ALTER TABLE statements is ignored. +This prevents automatic table deduplication if a UNIQUE index is added to a Friendica table's structure. +If a DB update fails for you while creating a UNIQUE index, make sure to manually deduplicate the table before trying the update again.

    +

    Manual deduplication#

    +

    There are two main ways of doing it, either by manually removing the duplicates or by recreating the table. +Manually removing the duplicates is usually faster if they're not too numerous. +To manually remove the duplicates, you need to know the UNIQUE index columns available in database.sql.

    +
    SELECT GROUP_CONCAT(id), <index columns>, count(*) as count FROM users
    +GROUP BY <index columns> HAVING count >= 2;
    +
    +/* delete or merge duplicate from above query */;
    +
    +

    If there are too many rows to handle manually, you can create a new table with the same structure as the table with duplicates and insert the existing content with INSERT IGNORE. +To recreate the table you need to know the table structure available in database.sql.

    +
    CREATE TABLE <table_name>_new <rest of the CREATE TABLE>;
    +INSERT IGNORE INTO <table_name>_new SELECT * FROM <table_name>;
    +DROP TABLE <table_name>;
    +RENAME TABLE <table_name>_new TO <table_name>;
    +
    +

    This method is slower overall, but it is better suited for large numbers of duplicates.

    +

    Resolving Possible Database Issues Post Upgrading#

    +

    Foreign Keys#

    +

    Some updates include the use of foreign keys now that will bump into issues with previous versions, which would sometimes shove bad data into tables, preventing, causing errors such as below.

    +
    Error 1452 occurred during database update:
    +Cannot add or update a child row: a foreign key constraint fails (`friendica`.`#sql-10ea6_5a6d`, CONSTRAINT `#sql-10ea6_5a6d_ibfk_1` FOREIGN KEY (`contact-id`) REFERENCES `contact` (`id`))
    +ALTER TABLE `thread` ADD FOREIGN KEY (`iid`) REFERENCES `item` (`id`) ON UPDATE RESTRICT ON DELETE CASCADE; 
    +
    +

    All current known fixes for possible items that can go wrong are as below.

    +
    DELETE FROM `item` WHERE `owner-id` NOT IN (SELECT `id` FROM `contact`);
    +DELETE FROM `item` WHERE `contact-id` NOT IN (SELECT `id` FROM `contact`);
    +DELETE FROM `notify` WHERE `uri-id` NOT IN (SELECT `id` FROM `item-uri`);
    +DELETE FROM `photo` WHERE `contact-id` NOT IN (SELECT `id` FROM `contact`);
    +DELETE FROM `thread` WHERE `iid` NOT IN (SELECT `id` FROM `item`);
    +DELETE FROM `item` WHERE `author-id` NOT IN (SELECT `id` FROM `contact`);
    +DELETE FROM `diaspora-interaction` WHERE `uri-id` NOT IN (SELECT `id` FROM `item-uri`);
    +
    +

    This all has been compiled as of currently from issue #9746, #9753, and #9878.

    + + +
    + +
    +
    + + + +
    + + + +
    +
    +
    +
    + + + + + + + + \ No newline at end of file diff --git a/develop/de/assets/images/acl_win.png b/develop/de/assets/images/acl_win.png new file mode 100644 index 0000000..559493d Binary files /dev/null and b/develop/de/assets/images/acl_win.png differ diff --git a/develop/de/assets/images/camera.png b/develop/de/assets/images/camera.png new file mode 100644 index 0000000..89acdbd Binary files /dev/null and b/develop/de/assets/images/camera.png differ diff --git a/develop/de/assets/images/chain.png b/develop/de/assets/images/chain.png new file mode 100644 index 0000000..6537c07 Binary files /dev/null and b/develop/de/assets/images/chain.png differ diff --git a/develop/de/assets/images/darkbubble.png b/develop/de/assets/images/darkbubble.png new file mode 100644 index 0000000..08c7a93 Binary files /dev/null and b/develop/de/assets/images/darkbubble.png differ diff --git a/develop/de/assets/images/darkzero.png b/develop/de/assets/images/darkzero.png new file mode 100644 index 0000000..00dc3ee Binary files /dev/null and b/develop/de/assets/images/darkzero.png differ diff --git a/develop/de/assets/images/diabook.png b/develop/de/assets/images/diabook.png new file mode 100644 index 0000000..1a1f8d9 Binary files /dev/null and b/develop/de/assets/images/diabook.png differ diff --git a/develop/de/assets/images/dispy.png b/develop/de/assets/images/dispy.png new file mode 100644 index 0000000..476fa33 Binary files /dev/null and b/develop/de/assets/images/dispy.png differ diff --git a/develop/de/assets/images/editor_darkbubble.png b/develop/de/assets/images/editor_darkbubble.png new file mode 100644 index 0000000..d766648 Binary files /dev/null and b/develop/de/assets/images/editor_darkbubble.png differ diff --git a/develop/de/assets/images/editor_dpzero.png b/develop/de/assets/images/editor_dpzero.png new file mode 100644 index 0000000..79f0cb3 Binary files /dev/null and b/develop/de/assets/images/editor_dpzero.png differ diff --git a/develop/de/assets/images/editor_frio.png b/develop/de/assets/images/editor_frio.png new file mode 100644 index 0000000..d969f26 Binary files /dev/null and b/develop/de/assets/images/editor_frio.png differ diff --git a/develop/de/assets/images/editor_vier.png b/develop/de/assets/images/editor_vier.png new file mode 100644 index 0000000..7ffac7f Binary files /dev/null and b/develop/de/assets/images/editor_vier.png differ diff --git a/develop/de/assets/images/editor_zero.png b/develop/de/assets/images/editor_zero.png new file mode 100644 index 0000000..a1ee37b Binary files /dev/null and b/develop/de/assets/images/editor_zero.png differ diff --git a/develop/de/assets/images/friendica-32.png b/develop/de/assets/images/friendica-32.png new file mode 100644 index 0000000..e025e4c Binary files /dev/null and b/develop/de/assets/images/friendica-32.png differ diff --git a/develop/de/assets/images/friendica.svg b/develop/de/assets/images/friendica.svg new file mode 100644 index 0000000..180fe2a --- /dev/null +++ b/develop/de/assets/images/friendica.svg @@ -0,0 +1,4 @@ + + + + diff --git a/develop/de/assets/images/friendica_rich_editor.png b/develop/de/assets/images/friendica_rich_editor.png new file mode 100644 index 0000000..170b8ec Binary files /dev/null and b/develop/de/assets/images/friendica_rich_editor.png differ diff --git a/develop/de/assets/images/frio_location.png b/develop/de/assets/images/frio_location.png new file mode 100644 index 0000000..8850c80 Binary files /dev/null and b/develop/de/assets/images/frio_location.png differ diff --git a/develop/de/assets/images/globe.png b/develop/de/assets/images/globe.png new file mode 100644 index 0000000..7a563ab Binary files /dev/null and b/develop/de/assets/images/globe.png differ diff --git a/develop/de/assets/images/lock.png b/develop/de/assets/images/lock.png new file mode 100644 index 0000000..b9a1cef Binary files /dev/null and b/develop/de/assets/images/lock.png differ diff --git a/develop/de/assets/images/mic.png b/develop/de/assets/images/mic.png new file mode 100644 index 0000000..c8d4c0e Binary files /dev/null and b/develop/de/assets/images/mic.png differ diff --git a/develop/de/assets/images/padlock.png b/develop/de/assets/images/padlock.png new file mode 100644 index 0000000..40f60cf Binary files /dev/null and b/develop/de/assets/images/padlock.png differ diff --git a/develop/de/assets/images/paper_clip.png b/develop/de/assets/images/paper_clip.png new file mode 100644 index 0000000..97aea40 Binary files /dev/null and b/develop/de/assets/images/paper_clip.png differ diff --git a/develop/de/assets/images/post_categorize.png b/develop/de/assets/images/post_categorize.png new file mode 100644 index 0000000..86aee53 Binary files /dev/null and b/develop/de/assets/images/post_categorize.png differ diff --git a/develop/de/assets/images/post_choose.png b/develop/de/assets/images/post_choose.png new file mode 100644 index 0000000..762cea8 Binary files /dev/null and b/develop/de/assets/images/post_choose.png differ diff --git a/develop/de/assets/images/post_delete.png b/develop/de/assets/images/post_delete.png new file mode 100644 index 0000000..3d0a92e Binary files /dev/null and b/develop/de/assets/images/post_delete.png differ diff --git a/develop/de/assets/images/post_link.png b/develop/de/assets/images/post_link.png new file mode 100644 index 0000000..cd7df78 Binary files /dev/null and b/develop/de/assets/images/post_link.png differ diff --git a/develop/de/assets/images/post_mark.png b/develop/de/assets/images/post_mark.png new file mode 100644 index 0000000..7781b8c Binary files /dev/null and b/develop/de/assets/images/post_mark.png differ diff --git a/develop/de/assets/images/post_share.png b/develop/de/assets/images/post_share.png new file mode 100644 index 0000000..a75ce2a Binary files /dev/null and b/develop/de/assets/images/post_share.png differ diff --git a/develop/de/assets/images/post_tag.png b/develop/de/assets/images/post_tag.png new file mode 100644 index 0000000..fa9fca6 Binary files /dev/null and b/develop/de/assets/images/post_tag.png differ diff --git a/develop/de/assets/images/post_thumbs_down.png b/develop/de/assets/images/post_thumbs_down.png new file mode 100644 index 0000000..0117779 Binary files /dev/null and b/develop/de/assets/images/post_thumbs_down.png differ diff --git a/develop/de/assets/images/post_thumbs_up.png b/develop/de/assets/images/post_thumbs_up.png new file mode 100644 index 0000000..aea54ab Binary files /dev/null and b/develop/de/assets/images/post_thumbs_up.png differ diff --git a/develop/de/assets/images/posts_define.png b/develop/de/assets/images/posts_define.png new file mode 100644 index 0000000..1d2cb08 Binary files /dev/null and b/develop/de/assets/images/posts_define.png differ diff --git a/develop/de/assets/images/video.png b/develop/de/assets/images/video.png new file mode 100644 index 0000000..3ee2d12 Binary files /dev/null and b/develop/de/assets/images/video.png differ diff --git a/develop/de/assets/images/vier_icons.png b/develop/de/assets/images/vier_icons.png new file mode 100644 index 0000000..b880e95 Binary files /dev/null and b/develop/de/assets/images/vier_icons.png differ diff --git a/develop/de/assets/stylesheets/friendica.css b/develop/de/assets/stylesheets/friendica.css new file mode 100644 index 0000000..3a07af4 --- /dev/null +++ b/develop/de/assets/stylesheets/friendica.css @@ -0,0 +1,91 @@ +:root > * { + + // Default color shades + --md-default-fg-color: hsla(0, 0%, 0%, 0.87); + --md-default-fg-color--light: hsla(0, 0%, 0%, 0.54); + --md-default-fg-color--lighter: hsla(0, 0%, 0%, 0.32); + --md-default-fg-color--lightest: hsla(0, 0%, 0%, 0.07); + --md-default-bg-color: hsla(0, 0%, 100%, 1); + --md-default-bg-color--light: hsla(0, 0%, 100%, 0.7); + --md-default-bg-color--lighter: hsla(0, 0%, 100%, 0.3); + --md-default-bg-color--lightest: hsla(0, 0%, 100%, 0.12); + + // Primary color shades + --md-primary-fg-color: hsla(#{hex2hsl($clr-indigo-500)}, 1); + --md-primary-fg-color--light: hsla(#{hex2hsl($clr-indigo-400)}, 1); + --md-primary-fg-color--dark: hsla(#{hex2hsl($clr-indigo-700)}, 1); + --md-primary-bg-color: hsla(0, 0%, 100%, 1); + --md-primary-bg-color--light: hsla(0, 0%, 100%, 0.7); + + // Accent color shades + --md-accent-fg-color: hsla(#{hex2hsl($clr-indigo-a200)}, 1); + --md-accent-fg-color--transparent: hsla(#{hex2hsl($clr-indigo-a200)}, 0.1); + --md-accent-bg-color: hsla(0, 0%, 100%, 1); + --md-accent-bg-color--light: hsla(0, 0%, 100%, 0.7); + + // Code color shades + --md-code-fg-color: hsla(200, 18%, 26%, 1); + --md-code-bg-color: hsla(0, 0%, 96%, 1); + + // Code highlighting color shades + --md-code-hl-color: hsla(#{hex2hsl($clr-yellow-a200)}, 0.5); + --md-code-hl-number-color: hsla(0, 67%, 50%, 1); + --md-code-hl-special-color: hsla(340, 83%, 47%, 1); + --md-code-hl-function-color: hsla(291, 45%, 50%, 1); + --md-code-hl-constant-color: hsla(250, 63%, 60%, 1); + --md-code-hl-keyword-color: hsla(219, 54%, 51%, 1); + --md-code-hl-string-color: hsla(150, 63%, 30%, 1); + --md-code-hl-name-color: var(--md-code-fg-color); + --md-code-hl-operator-color: var(--md-default-fg-color--light); + --md-code-hl-punctuation-color: var(--md-default-fg-color--light); + --md-code-hl-comment-color: var(--md-default-fg-color--light); + --md-code-hl-generic-color: var(--md-default-fg-color--light); + --md-code-hl-variable-color: var(--md-default-fg-color--light); + + // Typeset color shades + --md-typeset-color: var(--md-default-fg-color); + + // Typeset `a` color shades + --md-typeset-a-color: var(--md-primary-fg-color); + + // Typeset `mark` color shades + --md-typeset-mark-color: hsla(#{hex2hsl($clr-yellow-a200)}, 0.5); + + // Typeset `del` and `ins` color shades + --md-typeset-del-color: hsla(6, 90%, 60%, 0.15); + --md-typeset-ins-color: hsla(150, 90%, 44%, 0.15); + + // Typeset `kbd` color shades + --md-typeset-kbd-color: hsla(0, 0%, 98%, 1); + --md-typeset-kbd-accent-color: hsla(0, 100%, 100%, 1); + --md-typeset-kbd-border-color: hsla(0, 0%, 72%, 1); + + // Typeset `table` color shades + --md-typeset-table-color: hsla(0, 0%, 0%, 0.12); + + // Admonition color shades + --md-admonition-fg-color: var(--md-default-fg-color); + --md-admonition-bg-color: var(--md-default-bg-color); + + // Footer color shades + --md-footer-fg-color: hsla(0, 0%, 100%, 1); + --md-footer-fg-color--light: hsla(0, 0%, 100%, 0.7); + --md-footer-fg-color--lighter: hsla(0, 0%, 100%, 0.3); + --md-footer-bg-color: hsla(0, 0%, 0%, 0.87); + --md-footer-bg-color--dark: hsla(0, 0%, 0%, 0.32); + + // Shadow depth 1 + --md-shadow-z1: + 0 #{px2rem(4px)} #{px2rem(10px)} hsla(0, 0%, 0%, 0.05), + 0 0 #{px2rem(1px)} hsla(0, 0%, 0%, 0.1); + + // Shadow depth 2 + --md-shadow-z2: + 0 #{px2rem(4px)} #{px2rem(10px)} hsla(0, 0%, 0%, 0.1), + 0 0 #{px2rem(1px)} hsla(0, 0%, 0%, 0.25); + + // Shadow depth 3 + --md-shadow-z3: + 0 #{px2rem(4px)} #{px2rem(10px)} hsla(0, 0%, 0%, 0.2), + 0 0 #{px2rem(1px)} hsla(0, 0%, 0%, 0.35); + } diff --git a/develop/de/bugs-and-issues/index.html b/develop/de/bugs-and-issues/index.html new file mode 100644 index 0000000..0187e51 --- /dev/null +++ b/develop/de/bugs-and-issues/index.html @@ -0,0 +1,3257 @@ + + + + + + + + + + + + + + + + + + + + + + Bugs und Probleme - Friendica Dokumentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + +
    + + + + +
    + + +
    + +
    + + + + + + + + + +
    +
    + + + +
    +
    +
    + + + + + + +
    +
    +
    + + + +
    +
    +
    + + + +
    +
    +
    + + +
    +
    + + + + + + + + + + + + + +

    Bugs und Probleme#

    +

    du solltest jeden Bug und jedes Problem, den/das du findest, zunächst dem Administrator (oder gegebenenfalls der Support-Seite) deines Servers melden, statt auf der allgemeinen Bug-Seite. +Das erleichtert den Entwicklern ihre Arbeit (z. B. neue Features zu entwickeln), da sie sich nicht mit Fehlern beschäftigen müssen, mit denen sie nichts zu tun haben.

    +

    Wenn du technisch versiert bist oder dein Knoten keine Support-Seite hat, dann kannst du den Bug Tracker nutzen. +Bitte durchsuche zunächst die Seite, ob es bereits einen offenen Bug gibt, der deiner Anfrage entspricht.

    +

    Liefere so viele Informationen wie möglich zu dem Bug. +Hierzu gehört auch die komplette Fehlermeldung oder Notiz und alle Schritte, die zu dem Fehler geführt haben. +Es ist generell besser, zu viele Informationen zu liefern, als zu wenige.

    +

    Lies dir diesen Artikel (mehrsprachig) durch, um mehr über gute Bug-Reports zu erfahren.

    + + +
    + +
    +
    + + + +
    + + + +
    +
    +
    +
    + + + + + + + + \ No newline at end of file diff --git a/develop/de/developer/addon-storage-backend/index.html b/develop/de/developer/addon-storage-backend/index.html new file mode 100644 index 0000000..eb3359d --- /dev/null +++ b/develop/de/developer/addon-storage-backend/index.html @@ -0,0 +1,3778 @@ + + + + + + + + + + + + + + + + + + + + + + Addon Storage Backend - Friendica Dokumentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + +
    + + + + +
    + + +
    + +
    + + + + + + + + + +
    +
    + + + +
    +
    +
    + + + + + + +
    +
    +
    + + + +
    +
    +
    + + + +
    +
    +
    + + +
    +
    + + + + + + + + + + + + + +

    Friendica Storage Backend Addon development#

    +

    Storage backends can be added via addons. +A storage backend is implemented as a class, and the plugin register the class to make it available to the system.

    +

    The Storage Backend Class#

    +

    The class must live in Friendica\Addon\youraddonname namespace, where youraddonname the folder name of your addon.

    +

    There are two different interfaces you need to implement.

    +

    ICanWriteToStorage#

    +

    The class must implement Friendica\Core\Storage\Capability\ICanWriteToStorage interface. All method in the interface must be implemented:

    +
    <?php
    +namespace Friendica\Core\Storage\Capability\ICanWriteToStorage;
    +
    +interface ICanWriteToStorage
    +{
    +    public function get(string $reference);
    +    public function put(string $data, string $reference = '');
    +    public function delete(string $reference);
    +    public function __toString();
    +    public static function getName();
    +}
    +
    +
      +
    • get(string $reference) returns data pointed by $reference
    • +
    • put(string $data, string $reference) saves data in $data to position $reference, or a new position if $reference is empty.
    • +
    • delete(string $reference) delete data pointed by $reference
    • +
    +

    ICanConfigureStorage#

    +

    Each storage backend can have options the admin can set in admin page. +To make the options possible, you need to implement the Friendica\Core\Storage\Capability\ICanConfigureStorage interface.

    +

    All methods in the interface must be implemented:

    +
    <?php
    +namespace Friendica\Core\Storage\Capability\ICanConfigureStorage;
    +
    +interface ICanConfigureStorage
    +{
    +    public function getOptions();
    +    public function saveOptions(array $data);
    +}
    +
    +
      +
    • getOptions() returns an array with details about each option to build the interface.
    • +
    • saveOptions(array $data) get $data from admin page, validate it and save it.
    • +
    +

    The array returned by getOptions() is defined as:

    +
    [
    +    'option1name' => [ ..info.. ],
    +    'option2name' => [ ..info.. ],
    +    ...
    +]
    +
    +

    An empty array can be returned if backend doesn't have any options.

    +

    The info array for each option is defined as:

    +
    [
    +    'type',
    +
    +

    define the field used in form, and the type of data. +one of 'checkbox', 'combobox', 'custom', 'datetime', 'input', 'intcheckbox', 'password', 'radio', 'richtext', 'select', 'select_raw', 'textarea'

    +
        'label',
    +
    +

    Translatable label of the field. This label will be shown in admin page

    +
        value,
    +
    +

    Current value of the option

    +
        'help text',
    +
    +

    Translatable description for the field. Will be shown in admin page

    +
        extra data
    +
    +

    Optional. Depends on which 'type' this option is:

    +
      +
    • 'select': array [ value => label ] of choices
    • +
    • 'intcheckbox': value of input element
    • +
    • 'select_raw': prebuild html string of <option > tags
    • +
    +

    Each label should be translatable

    +
    ];
    +
    +

    See doxygen documentation of IWritableStorage interface for details about each method.

    +

    Register a storage backend class#

    +

    Each backend must be registered in the system when the plugin is installed, to be aviable.

    +

    DI::facStorage()->register(string $class) is used to register the backend class.

    +

    When the plugin is uninstalled, registered backends must be unregistered using +DI::facStorage()->unregister(string $class).

    +

    You have to register a new hook in your addon, listening on storage_instance(App $a, array $data). +In case $data['name'] is your storage class name, you have to instance a new instance of your Friendica\Core\Storage\Capability\ICanReadFromStorage class. +Set the instance of your class as $data['storage'] to pass it back to the backend.

    +

    This is necessary because it isn't always clear, if you need further construction arguments.

    +

    Adding tests#

    +

    Currently testing is limited to core Friendica only, this shows theoretically how tests should work in the future

    +

    Each new Storage class should be added to the test-environment at Storage Tests.

    +

    Add a new test class which's naming convention is StorageClassTest, which extend the StorageTest in the same directory.

    +

    Override the two necessary instances:

    +
    use Friendica\Core\Storage\Capability\ICanWriteToStorage;
    +
    +abstract class StorageTest 
    +{
    +    // returns an instance of your newly created storage class
    +    abstract protected function getInstance();
    +
    +    // Assertion for the option array you return for your new StorageClass
    +    abstract protected function assertOption(ICanWriteToStorage $storage);
    +} 
    +
    +

    Exception handling#

    +

    There are two intended types of exceptions for storages

    +

    ReferenceStorageExecption#

    +

    This storage exception should be used in case the caller tries to use an invalid references. +This could happen in case the caller tries to delete or update an unknown reference. +The implementation of the storage backend must not ignore invalid references.

    +

    Avoid throwing the common StorageExecption instead of the ReferenceStorageException at this particular situation!

    +

    StorageException#

    +

    This is the common exception in case unexpected errors happen using the storage backend. +If there's a predecessor to this exception (e.g. you caught an exception and are throwing this execption), you should add the predecessor for transparency reasons.

    +

    Example:

    +
    use Friendica\Core\Storage\Capability\ICanWriteToStorage;
    +
    +class ExampleStorage implements ICanWriteToStorage 
    +{
    +    public function get(string $reference) : string
    +    {
    +        try {
    +            throw new Exception('a real bad exception');
    +        } catch (Exception $exception) {
    +            throw new \Friendica\Core\Storage\Exception\StorageException(sprintf('The Example Storage throws an exception for reference %s', $reference), 500, $exception);
    +        }
    +    }
    +} 
    +
    +

    Example#

    +

    Here is a hypothetical addon which register a useless storage backend. +Let's call it samplestorage.

    +

    This backend will discard all data we try to save and will return always the same image when we ask for some data. +The image returned can be set by the administrator in admin page.

    +

    First, the backend class. +The file will be addon/samplestorage/SampleStorageBackend.php:

    +
    <?php
    +namespace Friendica\Addon\samplestorage;
    +
    +use Friendica\Core\Storage\Capability\ICanWriteToStorage;
    +
    +use Friendica\Core\Config\Capability\IManageConfigValues;
    +use Friendica\Core\L10n;
    +
    +class SampleStorageBackend implements ICanWriteToStorage
    +{
    +    const NAME = 'Sample Storage';
    +
    +    /** @var string */
    +    private $filename;
    +
    +    /**
    +      * SampleStorageBackend constructor.
    +      * 
    +      * You can add here every dynamic class as dependency you like and add them to a private field
    +      * Friendica automatically creates these classes and passes them as argument to the constructor                                       
    +      */
    +    public function __construct(string $filename) 
    +    {
    +        $this->filename = $filename;
    +    }
    +
    +    public function get(string $reference)
    +    {
    +        // we return always the same image data. Which file we load is defined by
    +        // a config key
    +        return file_get_contents($this->filename);
    +    }
    +
    +    public function put(string $data, string $reference = '')
    +    {
    +        if ($reference === '') {
    +            $reference = 'sample';
    +        }
    +        // we don't save $data !
    +        return $reference;
    +    }
    +
    +    public function delete(string $reference)
    +    {
    +        // we pretend to delete the data
    +        return true;
    +    }
    +
    +    public function __toString()
    +    {
    +        return self::NAME;
    +    }
    +
    +    public static function getName()
    +    {
    +        return self::NAME;
    +    }
    +}
    +
    +
    <?php
    +namespace Friendica\Addon\samplestorage;
    +
    +use Friendica\Core\Storage\Capability\ICanConfigureStorage;
    +
    +use Friendica\Core\Config\Capability\IManageConfigValues;
    +use Friendica\Core\L10n;
    +
    +class SampleStorageBackendConfig implements ICanConfigureStorage
    +{
    +    /** @var \Friendica\Core\Config\Capability\IManageConfigValues */
    +    private $config;
    +    /** @var L10n */
    +    private $l10n;
    +
    +    /**
    +      * SampleStorageBackendConfig constructor.
    +      * 
    +      * You can add here every dynamic class as dependency you like and add them to a private field
    +      * Friendica automatically creates these classes and passes them as argument to the constructor                                       
    +      */
    +    public function __construct(IManageConfigValues $config, L10n $l10n) 
    +    {
    +        $this->config = $config;
    +        $this->l10n   = $l10n;
    +    }
    +
    +    public function getFileName(): string
    +    {
    +        return $this->config->get('storage', 'samplestorage', 'sample.jpg');
    +    }
    +
    +    public function getOptions()
    +    {
    +        $filename = $this->config->get('storage', 'samplestorage', 'sample.jpg');
    +        return [
    +            'filename' => [
    +                'input',    // will use a simple text input
    +                $this->l10n->t('The file to return'),   // the label
    +                $filename,  // the current value
    +                $this->l10n->t('Enter the path to a file'), // the help text
    +                // no extra data for 'input' type..
    +            ],
    +        ];
    +    }
    +
    +    public function saveOptions(array $data)
    +    {
    +        // the keys in $data are the same keys we defined in getOptions()
    +        $newfilename = trim($data['filename']);
    +
    +        // this function should always validate the data.
    +        // in this example we check if file exists
    +        if (!file_exists($newfilename)) {
    +            // in case of error we return an array with
    +            // ['optionname' => 'error message']
    +            return ['filename' => 'The file doesn\'t exists'];
    +        }
    +
    +        $this->config->set('storage', 'samplestorage', $newfilename);
    +
    +        // no errors, return empty array
    +        return [];
    +    }
    +
    +}
    +
    +

    Now the plugin main file. Here we register and unregister the backend class.

    +

    The file is addon/samplestorage/samplestorage.php

    +
    <?php
    +/**
    + * Name: Sample Storage Addon
    + * Description: A sample addon which implements an unusefull storage backend
    + * Version: 1.0.0
    + * Author: Alice <https://alice.social/~alice>
    + */
    +
    +use Friendica\Addon\samplestorage\SampleStorageBackend;
    +use Friendica\Addon\samplestorage\SampleStorageBackendConfig;
    +use Friendica\DI;
    +
    +function samplestorage_install()
    +{
    +    Hook::register('storage_instance' , __FILE__, 'samplestorage_storage_instance');
    +    Hook::register('storage_config' , __FILE__, 'samplestorage_storage_config');
    +    DI::storageManager()->register(SampleStorageBackend::class);
    +}
    +
    +function samplestorage_storage_uninstall()
    +{
    +    DI::storageManager()->unregister(SampleStorageBackend::class);
    +}
    +
    +function samplestorage_storage_instance(App $a, array &$data)
    +{
    +    $config          = new SampleStorageBackendConfig(DI::l10n(), DI::config());
    +    $data['storage'] = new SampleStorageBackendConfig($config->getFileName());
    +}
    +
    +function samplestorage_storage_config(App $a, array &$data)
    +{
    +    $data['storage_config'] = new SampleStorageBackendConfig(DI::l10n(), DI::config());
    +}
    +
    +

    **Theoretically - until tests for Addons are enabled too - create a test class with the name addon/tests/SampleStorageTest.php:

    +
    use Friendica\Core\Storage\Capability\ICanWriteToStorage;
    +use Friendica\Test\src\Core\Storage\StorageTest;
    +
    +class SampleStorageTest extends StorageTest 
    +{
    +    // returns an instance of your newly created storage class
    +    protected function getInstance()
    +    {
    +        // create a new SampleStorageBackend instance with all it's dependencies
    +        // Have a look at DatabaseStorageTest or FilesystemStorageTest for further insights
    +        return new SampleStorageBackend();
    +    }
    +
    +    // Assertion for the option array you return for your new StorageClass
    +    protected function assertOption(ICanWriteToStorage $storage)
    +    {
    +        $this->assertEquals([
    +            'filename' => [
    +                'input',
    +                'The file to return',
    +                'sample.jpg',
    +                'Enter the path to a file'
    +            ],
    +        ], $storage->getOptions());
    +    }
    +} 
    +
    + + +
    + +
    +
    + + + +
    + + + +
    +
    +
    +
    + + + + + + + + \ No newline at end of file diff --git a/develop/de/developer/addons/index.html b/develop/de/developer/addons/index.html new file mode 100644 index 0000000..a18226c --- /dev/null +++ b/develop/de/developer/addons/index.html @@ -0,0 +1,4591 @@ + + + + + + + + + + + + + + + + + + + + + + Addons - Friendica Dokumentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + +
    + + + + +
    + + +
    + +
    + + + + + + + + + +
    +
    + + + +
    +
    +
    + + + + + + +
    +
    +
    + + + +
    +
    +
    + + + +
    +
    +
    + + +
    +
    + + + + + + + + + + + + + +

    Friendica Addon Entwicklung#

    +

    Bitte schau dir das Beispiel-Addon "randplace" für ein funktionierendes Beispiel für manche der hier aufgeführten Funktionen an. +Das Facebook-Addon bietet ein Beispiel dafür, die "addon"- und "module"-Funktion gemeinsam zu integrieren. +Addons arbeiten, indem sie Event Hooks abfangen. +Module arbeiten, indem bestimmte Seitenanfragen (durch den URL-Pfad) abgefangen werden.

    +

    Addon-Namen können keine Leerstellen oder andere Interpunktionen enthalten und werden als Datei- und Funktionsnamen genutzt. +Du kannst einen lesbaren Namen im Kommentarblock eintragen. +Jedes Addon muss beides beinhalten - eine Installations- und eine Deinstallationsfunktion, die auf dem Addon-Namen basieren; z.B. "addon1name_install()". +Diese beiden Funktionen haben keine Argumente und sind dafür verantwortlich, Event Hooks zu registrieren und abzumelden (unregistering), die dein Addon benötigt. +Die Installations- und Deinstallationsfunktionfunktionen werden auch ausgeführt (z.B. neu installiert), wenn sich das Addon nach der Installation ändert - somit sollte deine Deinstallationsfunktion keine Daten zerstört und deine Installationsfunktion sollte bestehende Daten berücksichtigen. +Zukünftige Extensions werden möglicherweise "Setup" und "Entfernen" anbieten.

    +

    Addons sollten einen Kommentarblock mit den folgenden vier Parametern enthalten:

    +
    <? php
    +/*
    + * Name: My Great Addon
    + * Description: This is what my addon does. It's really cool.
    + * Version: 1.0
    + * Author: John Q. Public <john@myfriendicasite.com>
    + */
    +
    +

    Registriere deine Addon-Hooks während der Installation.

    +
    <? php
    +
    +\Friendica\Core\Hook::register($hookname, $file, $function);
    +
    +

    $hookname ist ein String und entspricht einem bekannten Friendica-Hook.

    +

    $file steht für den Pfadnamen, der relativ zum Top-Level-Friendica-Verzeichnis liegt. +Das sollte addon/addon_name/addon_name.php sein.

    +

    $function ist ein String und der Name der Funktion, die ausgeführt wird, wenn der Hook aufgerufen wird.

    +

    Argumente#

    +

    Deine Hook-Callback-Funktion wird mit mindestens einem und bis zu zwei Argumenten aufgerufen

    +
    <? php
    +
    +function myhook_function(App $a, &$b) {
    +
    +}
    +
    +

    Wenn du Änderungen an den aufgerufenen Daten vornehmen willst, musst du diese als Referenzvariable (mit "&") während der Funktionsdeklaration deklarieren.

    +

    $a ist die Friendica "App"-Klasse, die eine Menge an Informationen über den aktuellen Friendica-Status beinhaltet, u.a. welche Module genutzt werden, Konfigurationsinformationen, Inhalte der Seite zum Zeitpunkt des Hook-Aufrufs. +Es ist empfohlen, diese Funktion "$a" zu nennen, um seine Nutzung an den Gebrauch an anderer Stelle anzugleichen.

    +

    $b kann frei benannt werden. +Diese Information ist speziell auf den Hook bezogen, der aktuell bearbeitet wird, und beinhaltet normalerweise Daten, die du sofort nutzen, anzeigen oder bearbeiten kannst. +Achte darauf, diese mit "&" zu deklarieren, wenn du sie bearbeiten willst.

    +

    Module#

    +

    Addons können auch als "Module" agieren und alle Seitenanfragen für eine bestimmte URL abfangen. +Um ein Addon als Modul zu nutzen, ist es nötig, die Funktion "addon_name_module()" zu definieren, die keine Argumente benötigt und nichts weiter machen muss.

    +

    Wenn diese Funktion existiert, wirst du nun alle Seitenanfragen für "http://example.com/addon_name" erhalten - mit allen URL-Komponenten als zusätzliche Argumente. +Diese werden in das App\Arguments Objekt geparst. +So würde http://example.com/addon/arg1/arg2 dies ergeben: +

    <? php
    +DI::args()->getArgc(); // = 3
    +DI::args()->get(0); // = 'addon'
    +DI::args()->get(1); // = 'arg1'
    +DI::args()->get(2); // = 'arg2'
    +

    +

    Deine Modulfunktionen umfassen oft die Funktion addon_name_content(App $a), welche den Seiteninhalt definiert und zurückgibt. +Sie können auch addon_name_post(App $a) umfassen, welches vor der content-Funktion aufgerufen wird und normalerweise die Resultate der POST-Formulare handhabt. +Du kannst ebenso addon_name_init(App $a) nutzen, was oft frühzeitig aufgerufen wird und das Modul initialisiert.

    +

    Derzeitige Hooks#

    +

    'authenticate' - wird aufgerufen, wenn sich der User einloggt. + $b ist ein Array + 'username' => der übertragene Nutzername + 'password' => das übertragene Passwort + 'authenticated' => setze das auf einen anderen Wert als "0", damit der User sich authentifiziert + 'user_record' => die erfolgreiche Authentifizierung muss auch einen gültigen Nutzereintrag aus der Datenbank zurückgeben

    +

    'logged_in' - wird aufgerufen, sobald ein Nutzer sich erfolgreich angemeldet hat. + $b beinhaltet den $a->Nutzer-Array

    +

    'display_item' - wird aufgerufen, wenn ein Beitrag für die Anzeige formatiert wird. + $b ist ein Array + 'item' => Die Item-Details (Array), die von der Datenbank ausgegeben werden + 'output' => Die HTML-Ausgabe (String) des Items, bevor es zur Seite hinzugefügt wird

    +

    'post_local' - wird aufgerufen, wenn der Statusbeitrag oder ein Kommentar im lokalen System eingetragen wird. + $b ist das Item-Array der Information, die in der Datenbank hinterlegt wird. + {Bitte beachte: Der Seiteninhalt ist bbcode - nicht HTML)

    +

    'post_local_end' - wird aufgerufen, wenn ein lokaler Statusbeitrag oder Kommentar im lokalen System gespeichert wird. + $b ist das Item-Array einer Information, die gerade in der Datenbank gespeichert wurden. + {Bitte beachte: Der Seiteninhalt ist bbcode - nicht HTML)

    +

    'post_remote' - wird aufgerufen, wenn ein Beitrag aus einer anderen Quelle empfangen wird. Dies kann auch genutzt werden, um lokale Aktivitäten oder systemgenerierte Nachrichten zu veröffentlichen/posten. + $b ist das Item-Array einer Information, die in der Datenbank und im Item gespeichert ist. + {Bitte beachte: Der Seiteninhalt ist bbcode - nicht HTML)

    +

    'addon_settings' - wird aufgerufen, wenn die HTML-Ausgabe der Addon-Einstellungsseite generiert wird. + $b ist die HTML-Ausgabe (String) der Addon-Einstellungsseite vor dem finalen ""-Tag.

    +

    'addon_settings_post' - wird aufgerufen, wenn die Addon-Einstellungsseite geladen wird. + $b ist der $_POST-Array

    +

    'profile_post' - wird aufgerufen, wenn die Profilseite angezeigt wird. + $b ist der $_POST-Array

    +

    'profile_edit' - wird aufgerufen, bevor die Profil-Bearbeitungsseite angezeigt wird. + $b ist ein Array + 'profile' => Profileintrag (Array) aus der Datenbank + 'entry' => die HTML-Ausgabe (string) des generierten Eintrags

    +

    'profile_advanced' - wird aufgerufen, wenn die HTML-Ausgabe für das "Advanced profile" generiert wird; stimmt mit dem "Profil"-Tab auf der Profilseite der Nutzer überein. + $b ist die HTML-Ausgabe (String) des erstellten Profils + (Die Details des Profil-Arrays sind in $a->profile)

    +

    'directory_item' - wird von der Verzeichnisseite aufgerufen, wenn ein Item für die Anzeige formatiert wird. + $b ist ein Array + 'contact' => Kontakteintrag (Array) einer Person aus der Datenbank + 'entry' => die HTML-Ausgabe (String) des generierten Eintrags

    +

    'profile_sidebar_enter' - wird aufgerufen, bevor die Sidebar "Kurzprofil" einer Seite erstellt wird. + $b ist der Profil-Array einer Person

    +

    'profile_sidebar' - wird aufgerufen, wenn die Sidebar "Kurzprofil" einer Seite erstellt wird. + $b ist ein Array + 'profile' => Kontakteintrag (Array) einer Person aus der Datenbank + 'entry' => die HTML-Ausgabe (String) des generierten Eintrags

    +

    'contact_block_end' - wird aufgerufen, wenn der Block "Kontakte/Freunde" der Profil-Sidebar komplett formatiert wurde. + $b ist ein Array + 'contacts' => Array von "contacts" + 'output' => die HTML-Ausgabe (String) des Kontaktblocks

    +

    'bbcode' - wird während der Umwandlung von bbcode auf HTML aufgerufen. + $b ist der konvertierte Text (String)

    +

    'html2bbcode' - wird während der Umwandlung von HTML zu bbcode aufgerufen (z.B. bei Nachrichtenbeiträgen). + $b ist der konvertierte Text (String)

    +

    'page_header' - wird aufgerufen, nachdem der Bereich der Seitennavigation geladen wurde. + $b ist die HTML-Ausgabe (String) der "nav"-Region

    +

    'personal_xrd' - wird aufgerufen, bevor die Ausgabe der persönlichen XRD-Datei erzeugt wird. + $b ist ein Array + 'user' => die hinterlegten Einträge der Person + 'xml' => die komplette XML-Datei die ausgegeben wird

    +

    'home_content' - wird aufgerufen, bevor die Ausgabe des Homepage-Inhalts erstellt wird; wird nicht eingeloggten Nutzern angezeigt. + $b ist die HTML-Ausgabe (String) der Auswahlregion

    +

    'contact_edit' - wird aufgerufen, wenn die Kontaktdetails vom Nutzer auf der "Kontakte"-Seite bearbeitet werden. + $b ist ein Array + 'contact' => Kontakteintrag (Array) des abgezielten Kontakts + 'output' => die HTML-Ausgabe (String) der "Kontakt bearbeiten"-Seite

    +

    'contact_edit_post' - wird aufgerufen, wenn die "Kontakt bearbeiten"-Seite ausgegeben wird. + $b ist der $_POST-Array

    +

    'init_1' - wird aufgerufen, kurz nachdem die Datenbank vor Beginn der Sitzung geöffnet wird. + $b wird nicht genutzt

    +

    'page_end' - wird aufgerufen, nachdem die Funktion des HTML-Inhalts komplett abgeschlossen ist. + $b ist die HTML-Ausgabe (String) vom Inhalt-"div"

    +

    'avatar_lookup' - wird aufgerufen, wenn der Avatar geladen wird. + $b ist ein Array + 'size' => Größe des Avatars, der geladen wird + 'email' => Email-Adresse, um nach dem Avatar zu suchen + 'url' => generierte URL (String) des Avatars

    +

    'nav_info' + - wird aufgerufen, nachdem in include/nav.php der Inhalt des Navigationsmenüs erzeugt wurde. + - $b ist ein Array, das $nav widerspiegelt.

    +

    Komplette Liste der Hook-Callbacks#

    +

    Eine komplette Liste aller Hook-Callbacks mit den zugehörigen Dateien (am 01-Apr-2018 generiert): Bitte schau in die Quellcodes für Details zu Hooks, die oben nicht dokumentiert sind.

    +

    index.php#

    +
    Hook::callAll('init_1');
    +Hook::callAll('app_menu', $arr);
    +Hook::callAll('page_content_top', DI::page()['content']);
    +Hook::callAll($a->module.'_mod_init', $placeholder);
    +Hook::callAll($a->module.'_mod_init', $placeholder);
    +Hook::callAll($a->module.'_mod_post', $_POST);
    +Hook::callAll($a->module.'_mod_content', $arr);
    +Hook::callAll($a->module.'_mod_aftercontent', $arr);
    +Hook::callAll('page_end', DI::page()['content']);
    +
    +

    include/api.php#

    +
    Hook::callAll('logged_in', $a->user);
    +Hook::callAll('authenticate', $addon_auth);
    +Hook::callAll('logged_in', $a->user);
    +
    +

    include/enotify.php#

    +
    Hook::callAll('enotify', $h);
    +Hook::callAll('enotify_store', $datarray);
    +Hook::callAll('enotify_mail', $datarray);
    +Hook::callAll('check_item_notification', $notification_data);
    +
    +

    src/Content/Conversation.php#

    +
    Hook::callAll('conversation_start', $cb);
    +Hook::callAll('render_location', $locate);
    +Hook::callAll('display_item', $arr);
    +Hook::callAll('display_item', $arr);
    +Hook::callAll('item_photo_menu', $args);
    +Hook::callAll('jot_tool', $jotplugins);
    +
    +

    mod/directory.php#

    +
    Hook::callAll('directory_item', $arr);
    +
    +

    mod/xrd.php#

    +
    Hook::callAll('personal_xrd', $arr);
    +
    +

    mod/parse_url.php#

    +
    Hook::callAll("parse_link", $arr);
    +
    +

    src/Module/Delegation.php#

    +
    Hook::callAll('home_init', $ret);
    +
    +

    mod/acl.php#

    +
    Hook::callAll('acl_lookup_end', $results);
    +
    +

    mod/network.php#

    +
    Hook::callAll('network_content_init', $arr);
    +Hook::callAll('network_tabs', $arr);
    +
    +

    mod/friendica.php#

    +
    Hook::callAll('about_hook', $o);
    +
    +

    mod/profiles.php#

    +
    Hook::callAll('profile_post', $_POST);
    +Hook::callAll('profile_edit', $arr);
    +
    +

    mod/settings.php#

    +
    Hook::callAll('addon_settings_post', $_POST);
    +Hook::callAll('connector_settings_post', $_POST);
    +Hook::callAll('display_settings_post', $_POST);
    +Hook::callAll('addon_settings', $settings_addons);
    +Hook::callAll('connector_settings', $settings_connectors);
    +Hook::callAll('display_settings', $o);
    +
    +

    mod/photos.php#

    +
    Hook::callAll('photo_post_init', $_POST);
    +Hook::callAll('photo_post_file', $ret);
    +Hook::callAll('photo_post_end', $foo);
    +Hook::callAll('photo_post_end', $foo);
    +Hook::callAll('photo_post_end', $foo);
    +Hook::callAll('photo_post_end', $foo);
    +Hook::callAll('photo_post_end', intval($item_id));
    +Hook::callAll('photo_upload_form', $ret);
    +
    +

    mod/profile.php#

    +
    Hook::callAll('profile_advanced', $o);
    +
    +

    mod/home.php#

    +
    Hook::callAll('home_init', $ret);
    +Hook::callAll("home_content", $content);
    +
    +

    mod/poke.php#

    +
    Hook::callAll('post_local_end', $arr);
    +
    +

    mod/contacts.php#

    +
    Hook::callAll('contact_edit_post', $_POST);
    +Hook::callAll('contact_edit', $arr);
    +
    +

    mod/tagger.php#

    +
    Hook::callAll('post_local_end', $arr);
    +
    +

    mod/uexport.php#

    +
    Hook::callAll('uexport_options', $options);
    +
    +

    mod/register.php#

    +
    Hook::callAll('register_post', $arr);
    +Hook::callAll('register_form', $arr);
    +
    +

    mod/item.php#

    +
    Hook::callAll('post_local_start', $_REQUEST);
    +Hook::callAll('post_local', $datarray);
    +Hook::callAll('post_local_end', $datarray);
    +
    +

    mod/editpost.php#

    +
    Hook::callAll('jot_tool', $jotplugins);
    +
    +

    src/Network/FKOAuth1.php#

    +
    Hook::callAll('logged_in', $a->user);
    +
    +

    src/Render/FriendicaSmartyEngine.php#

    +
    Hook::callAll("template_vars", $arr);
    +
    +

    src/Model/Item.php#

    +
    Hook::callAll('post_local', $item);
    +Hook::callAll('post_remote', $item);
    +Hook::callAll('post_local_end', $posted_item);
    +Hook::callAll('post_remote_end', $posted_item);
    +Hook::callAll('tagged', $arr);
    +Hook::callAll('post_local_end', $new_item);
    +Hook::callAll('put_item_in_cache', $hook_data);
    +Hook::callAll('prepare_body_init', $item);
    +Hook::callAll('prepare_body_content_filter', $hook_data);
    +Hook::callAll('prepare_body', $hook_data);
    +Hook::callAll('prepare_body_final', $hook_data);
    +
    +

    src/Model/Contact.php#

    +
    Hook::callAll('contact_photo_menu', $args);
    +Hook::callAll('follow', $arr);
    +
    +

    src/Model/Profile.php#

    +
    Hook::callAll('profile_sidebar_enter', $profile);
    +Hook::callAll('profile_sidebar', $arr);
    +Hook::callAll('profile_tabs', $arr);
    +Hook::callAll('zrl_init', $arr);
    +
    +

    src/Model/Event.php#

    +
    Hook::callAll('event_updated', $event['id']);
    +Hook::callAll("event_created", $event['id']);
    +
    +

    src/Model/User.php#

    +
    Hook::callAll('register_account', $uid);
    +Hook::callAll('remove_user', $user);
    +
    +

    src/Content/ContactBlock.php#

    +
    Hook::callAll('contact_block_end', $arr);
    +
    +

    src/Content/Text/BBCode.php#

    +
    Hook::callAll('bbcode', $text);
    +Hook::callAll('bb2diaspora', $text);
    +
    +

    src/Content/Text/HTML.php#

    +
    Hook::callAll('html2bbcode', $message);
    +
    +

    src/Content/Smilies.php#

    +
    Hook::callAll('smilie', $params);
    +
    +

    src/Content/Feature.php#

    +
    Hook::callAll('isEnabled', $arr);
    +Hook::callAll('get', $arr);
    +
    +

    src/Content/ContactSelector.php#

    +
    Hook::callAll('network_to_name', $nets);
    +
    +

    src/Content/OEmbed.php#

    +
    Hook::callAll('oembed_fetch_url', $embedurl, $j);
    +
    +

    src/Content/Nav.php#

    +
    Hook::callAll('page_header', DI::page()['nav']);
    +Hook::callAll('nav_info', $nav);
    +
    +

    src/Core/Authentication.php#

    +
    Hook::callAll('logged_in', $a->user);
    +
    +

    src/Core/Protocol.php#

    +
    Hook::callAll('support_follow', $hook_data);
    +Hook::callAll('support_revoke_follow', $hook_data);
    +Hook::callAll('unfollow', $hook_data);
    +Hook::callAll('revoke_follow', $hook_data);
    +Hook::callAll('block', $hook_data);
    +Hook::callAll('unblock', $hook_data);
    +
    +

    src/Core/StorageManager#

    +
    Hook::callAll('storage_instance', $data);
    +Hook::callAll('storage_config', $data);
    +
    +

    src/Module/Notifications/Ping.php#

    +
    Hook::callAll('network_ping', $arr);
    +
    +

    src/Module/PermissionTooltip.php#

    +
    Hook::callAll('lockview_content', $item);
    +
    +

    src/Worker/Directory.php#

    +
    Hook::callAll('globaldir_update', $arr);
    +
    +

    src/Worker/Notifier.php#

    +
    Hook::callAll('notifier_end', $target_item);
    +
    +

    src/Module/Login.php#

    +
    Hook::callAll('authenticate', $addon_auth);
    +Hook::callAll('login_hook', $o);
    +
    +

    src/Module/Logout.php#

    +
    Hook::callAll("logging_out");
    +
    +

    src/Object/Post.php#

    +
    Hook::callAll('render_location', $locate);
    +Hook::callAll('display_item', $arr);
    +
    +

    src/Core/ACL.php#

    +
    Hook::callAll('contact_select_options', $x);
    +Hook::callAll($a->module.'_pre_'.$selname, $arr);
    +Hook::callAll($a->module.'_post_'.$selname, $o);
    +Hook::callAll($a->module.'_pre_'.$selname, $arr);
    +Hook::callAll($a->module.'_post_'.$selname, $o);
    +Hook::callAll('jot_networks', $jotnets);
    +
    +

    src/Core/Authentication.php#

    +
    Hook::callAll('logged_in', $a->user);
    +
    +

    src/Core/Hook.php#

    +
    self::callSingle(self::getApp(), 'hook_fork', $fork_hook, $hookdata);
    +
    +

    src/Core/L10n/L10n.php#

    +
    Hook::callAll('poke_verbs', $arr);
    +
    +

    src/Core/Worker.php#

    +
    Hook::callAll("proc_run", $arr);
    +
    +

    src/Util/Emailer.php#

    +
    Hook::callAll('emailer_send_prepare', $email);
    +Hook::callAll("emailer_send", $hookdata);
    +
    +

    src/Util/Map.php#

    +
    Hook::callAll('generate_map', $arr);
    +Hook::callAll('generate_named_map', $arr);
    +Hook::callAll('Map::getCoordinates', $arr);
    +
    +

    src/Util/Network.php#

    +
    Hook::callAll('avatar_lookup', $avatar);
    +
    +

    src/Util/ParseUrl.php#

    +
    Hook::callAll("getsiteinfo", $siteinfo);
    +
    +

    src/Protocol/DFRN.php#

    +
    Hook::callAll('atom_feed_end', $atom);
    +Hook::callAll('atom_feed_end', $atom);
    +
    +

    src/Protocol/Email.php#

    +
    Hook::callAll('email_getmessage', $message);
    +Hook::callAll('email_getmessage_end', $ret);
    +
    + + +
    + +
    +
    + + + +
    + + + +
    +
    +
    +
    + + + + + + + + \ No newline at end of file diff --git a/develop/de/developer/autoloader/index.html b/develop/de/developer/autoloader/index.html new file mode 100644 index 0000000..23e0816 --- /dev/null +++ b/develop/de/developer/autoloader/index.html @@ -0,0 +1,3513 @@ + + + + + + + + + + + + + + + + + + + + + + Autoloader - Friendica Dokumentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + +
    + + + + +
    + + +
    + +
    + + + + + + + + + +
    +
    + + + +
    +
    +
    + + + + + + +
    +
    +
    + + + +
    +
    +
    + + + +
    +
    +
    + + +
    +
    + + + + + + + + + + + + + +

    Autoloader with Composer#

    +

    Friendica uses Composer to manage dependencies libraries and the class autoloader both for libraries and namespaced Friendica classes.

    +

    It's a command-line tool that downloads required libraries into the vendor folder and makes any namespaced class in src available through the whole application through boot.php.

    + +

    A quick introduction to class auto-loading#

    +

    The autoloader dynamically includes the file defining a class when it is first referenced, either by instantiating an object or simply making sure that it is available, without the need to explicitly use "require_once".

    +

    Once it is set up you don't have to directly use it, you can directly use any class that is covered by the autoloader (currently vendor and src)

    +

    Under the hood, Composer registers a callback with spl_autoload_register() that receives a class name as an argument and includes the corresponding class definition file. +For more info about PHP autoloading, please refer to the official PHP documentation.

    +

    Example#

    +

    Let's say you have a PHP file in src/ that define a very useful class:

    +
    // src/ItemsManager.php
    +<?php
    +namespace Friendica;
    +
    +class ItemsManager {
    +    public function getAll() { ... }
    +    public function getByID($id) { ... }
    +}
    +
    +

    The class ItemsManager has been declared in the Friendica namespace. +Namespaces are useful to keep classes separated and avoid names conflicts (could be that a library you want to use also defines a class named ItemsManager, but as long as it is in another namespace, you don't have any problem)

    +

    Let's say now that you need to load some items in a view, maybe in a fictional mod/network.php. +In order for the Composer autoloader to work, it must first be included. +In Friendica this is already done at the top of boot.php, with require_once('vendor/autoload.php');.

    +

    The code will be something like:

    +
    // mod/network.php
    +<?php
    +
    +use Friendica\App;
    +
    +function network_content(App $a) {
    +    $itemsmanager = new \Friendica\ItemsManager();
    +    $items = $itemsmanager->getAll();
    +
    +    // pass $items to template
    +    // return result
    +}
    +
    +

    That's a quite simple example, but look: no require()! +If you need to use a class, you can simply use it, and you don't need to do anything else.

    +

    Going further: now we have a bunch of *Manager classes that cause some code duplication. +Let's define a BaseManager class, where we move all common code between all managers:

    +
    // src/BaseManager.php
    +<?php
    +namespace Friendica;
    +
    +class BaseManager {
    +    public function thatFunctionEveryManagerUses() { ... }
    +}
    +
    +

    and then let's change the ItemsManager class to use this code

    +
    // src/ItemsManager.php
    +<?php
    +namespace Friendica;
    +
    +class ItemsManager extends BaseManager {
    +    public function getAll() { ... }
    +    public function getByID($id) { ... }
    +}
    +
    +

    Even though we didn't explicitly include the src/BaseManager.php file, the autoloader will when this class is first defined, because it is referenced as a parent class. +It works with the "BaseManager" example here, and it works when we need to call static methods:

    +
    // src/Dfrn.php
    +<?php
    +namespace Friendica;
    +
    +class Dfrn {
    +    public static function  mail($item, $owner) { ... }
    +}
    +
    +
    // mod/mail.php
    +<?php
    +
    +mail_post($a){
    +    ...
    +    Friendica\Protocol\DFRN::mail($item, $owner);
    +    ...
    +}
    +
    +

    If your code is in same namespace as the class you need, you don't need to prepend it:

    +
    // include/delivery.php
    +<?php
    +
    +namespace Friendica;
    +
    +use Friendica\Protocol\DFRN;
    +
    +// this is the same content of current include/delivery.php,
    +// but has been declared to be in "Friendica" namespace
    +
    +[...]
    +switch($contact['network']) {
    +    case NETWORK_DFRN:
    +        if ($mail) {
    +            $item['body'] = ...
    +            $atom = DFRN::mail($item, $owner);
    +        } elseif ($fsuggest) {
    +            $atom = DFRN::fsuggest($item, $owner);
    +            q("DELETE FROM `fsuggest` WHERE `id` = %d LIMIT 1", intval($item['id']));
    +        } elseif ($relocate)
    +            $atom = DFRN::relocate($owner, $uid);
    +[...]
    +
    +

    This is the current code of include/delivery.php, and since the code is declared to be in the "Friendica" namespace, you don't need to write it when you need to use the "Dfrn" class. +But if you want to use classes from another library, you need to use the full namespace, e.g.

    +
    // src/Diaspora.php
    +<?php
    +
    +namespace Friendica;
    +
    +class Diaspora {
    +    public function md2bbcode() {
    +        $html = \Michelf\MarkdownExtra::defaultTransform($text);
    +    }
    +}
    +
    +

    if you use that class in many places of the code, and you don't want to write the full path to the class every time, you can use the "use" PHP keyword

    +
    // src/Diaspora.php
    +<?php
    +namespace Friendica;
    +
    +use \Michelf\MarkdownExtra;
    +
    +class Diaspora {
    +    public function md2bbcode() {
    +        $html = MarkdownExtra::defaultTransform($text);
    +    }
    +}
    +
    +

    Note that namespaces are like paths in filesystem, separated by "\", with the first "\" being the global scope. +You can go deeper if you want to, like:

    +
    // src/Network/Dfrn.php
    +<?php
    +namespace Friendica\Network;
    +
    +class Dfrn {
    +}
    +
    +

    Please note that the location of the file defining the class must be placed in the appropriate sub-folders of src if the namespace isn't plain Friendica.

    +

    or

    +
    // src/Dba/Mysql
    +<?php
    +namespace Friendica\Dba;
    +
    +class Mysql {
    +}
    +
    +

    So you can think of namespaces as folders in a Unix file system, with global scope as the root ("\").

    + + + + +
    + +
    +
    + + + +
    + + + +
    +
    +
    +
    + + + + + + + + \ No newline at end of file diff --git a/develop/de/developer/composer/index.html b/develop/de/developer/composer/index.html new file mode 100644 index 0000000..aa00f4d --- /dev/null +++ b/develop/de/developer/composer/index.html @@ -0,0 +1,3599 @@ + + + + + + + + + + + + + + + + + + + + + + Composer - Friendica Dokumentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + +
    + + + + +
    + + +
    + +
    + + + + + + + + + +
    +
    + + + +
    +
    +
    + + + + + + +
    +
    +
    + + + + + + +
    +
    + + + + + + + + + + + + + +

    Using Composer#

    +

    Friendica uses Composer to manage dependencies libraries and the class autoloader both for libraries and namespaced Friendica classes.

    +

    It's a command-line tool that downloads required libraries into the vendor folder and makes any namespaced class in src available through the whole application through boot.php.

    + +

    How to use Composer#

    +

    If you don't have Composer installed on your system, Friendica ships with a copy of it at bin/composer.phar. +For the purpose of this help, all examples will use this path to run Composer commands, however feel free to replace them with your own way of calling Composer. +Composer requires PHP CLI and the following examples assume it's available system-wide.

    +

    Installing/Updating Friendica#

    +

    From Archive#

    +

    If you just unpacked a Friendica release archive, you don't have to use Composer at all, all the required libraries are already bundled in the archive.

    +

    Installing with Git#

    +

    If you prefer to use git, you will have to run Composer to fetch the required libraries and build the autoloader before you can run Friendica. +Here are the typical commands you will have to run to do so:

    +
    ~> git clone https://github.com/friendica/friendica.git friendica
    +~/friendica> cd friendica
    +~/friendica> bin/composer.phar install
    +
    +

    That's it! Composer will take care of fetching all the required libraries in the vendor folder and build the autoloader to make those libraries available to Friendica.

    +

    Updating with Git#

    +

    Updating Friendica to the current stable or the latest develop version is easy with Git, just remember to run Composer after every branch pull.

    +
    ~> cd friendica
    +~/friendica> git pull
    +~/friendica> bin/composer.phar install
    +
    +

    And that's it. If any library used by Friendica has been upgraded, Composer will fetch the version currently used by Friendica and refresh the autoloader to ensure the best performances.

    +

    Developing Friendica#

    +

    First, thanks for contributing to Friendica! +Composer is meant to be used by developers to maintain third-party libraries required by Friendica. +If you don't need to use any third-party library, then you don't need to use Composer beyond what is above to install/update Friendica.

    +

    Adding a third-party library to Friendica#

    +

    Does your shiny new Addon need to rely on a third-party library not required by Friendica yet? +First, this library should be available on Packagist so that Composer knows how to fetch it directly just by mentioning its name in composer.json.

    +

    This file is the configuration of Friendica for Composer. It lists details about the Friendica project, but also a list of required dependencies and their target version. +Here's a simplified version of the one we currently use on Friendica:

    +
    {
    +    "name": "friendica/friendica",
    +    "description": "A decentralized social network part of The Federation",
    +    "type": "project",
    +    [...]
    +    "require": {
    +        "ezyang/htmlpurifier": "~4.7.0",
    +        "mobiledetect/mobiledetectlib": "2.8.*"
    +    },
    +    [...]
    +}
    +
    +

    The important part is under the require key, this is a list of all the libraries Friendica may need to run. +As you can see, at the moment we only require two, HTMLPurifier and MobileDetect. +Each library has a different target version, and per Composer documentation about version constraints, this means that:

    +
      +
    • We will update HTMLPurifier up to version 4.8.0 excluded
    • +
    • We will update MobileDetect up to version 2.9.0 excluded
    • +
    +

    There are other operators you can use to allow Composer to update the package up to the next major version excluded. +Or you can specify the exact version of the library if you code requires it, and Composer will never update it, although it isn't recommended.

    +

    To add a library, just add its Packagist identifier to the require list and set a target version string.

    +

    Then you should run bin/composer.phar update to add it to your local vendor folder and update the composer.lock file that specifies the current versions of the dependencies.

    +

    Updating an existing dependency#

    +

    If a package needs to be updated, whether to the next minor version or to the next major version provided you changed the adequate code in Friendica, simply edit composer.json to update the target version string of the relevant library.

    +

    Then you should run bin/composer.phar update to update it in your local vendor folder and update the composer.lock file that specifies the current versions of the dependencies.

    +

    Please note that you should commit both composer.json and composer.lock with your work every time you make a change to the former.

    +

    Composer FAQ#

    +

    I used the composer command and got a warning about not to run it as root.#

    +

    See https://getcomposer.org/root. +Composer should be run as the web server user, usually www-data with Apache or http with nginx. +If you can't switch to the web server user using su - [web user], you can directly run the Composer command with sudo -u [web user].

    +

    Running Composer with sudo complains about not being able to create the composer cache directory in /root/.composer#

    +

    This is because sudo doesn't always change the HOME environment variable, which means that the command is run as the web server user but the system still uses root home directory. +However, you can temporarily change environment variable for the execution of a single command. +For Composer, this would be: +

    $> COMPOSER_HOME=/var/tmp/composer sudo -u [web user] bin/composer.phar [mode]
    +

    + + + + +
    + +
    +
    + + + +
    + + + +
    +
    +
    +
    + + + + + + + + \ No newline at end of file diff --git a/develop/de/developer/domain-driven-design/index.html b/develop/de/developer/domain-driven-design/index.html new file mode 100644 index 0000000..08309a8 --- /dev/null +++ b/develop/de/developer/domain-driven-design/index.html @@ -0,0 +1,3619 @@ + + + + + + + + + + + + + + + + + + + + + + DDD - Friendica Dokumentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + +
    + + + + +
    + + +
    + +
    + + + + + + + + + +
    +
    + + + +
    +
    +
    + + + + + + +
    +
    +
    + + + +
    +
    +
    + + + +
    +
    +
    + + +
    +
    + + + + + + + + + + + + + +

    Domain-Driven-Design#

    +

    Friendica uses class structures inspired by Domain-Driven-Design programming patterns. +This page is meant to explain what it means in practical terms for Friendica development.

    +

    Inspiration#

    +
      +
    • https://designpatternsphp.readthedocs.io/en/latest/Structural/DependencyInjection/README.html
    • +
    • https://designpatternsphp.readthedocs.io/en/latest/Creational/SimpleFactory/README.html
    • +
    • https://designpatternsphp.readthedocs.io/en/latest/More/Repository/README.html
    • +
    • https://designpatternsphp.readthedocs.io/en/latest/Creational/FactoryMethod/README.html
    • +
    • https://designpatternsphp.readthedocs.io/en/latest/Creational/Prototype/README.html
    • +
    +

    Core concepts#

    +

    Models and Collections#

    +

    Instead of anonymous arrays of database field values, we have Models and collections to take full advantage of PHP type hints.

    +

    Before: +

    <?php
    +
    +function doSomething(array $intros)
    +{
    +    foreach ($intros as $intro) {
    +        $introId = $intro['id'];
    +    }
    +}
    +
    +$intros = \Friendica\Database\DBA::selectToArray('intros', [], ['uid' => local_user()]);
    +
    +doSomething($intros);
    +

    +

    After:

    +
    <?php
    +
    +function doSomething(\Friendica\Contact\Introductions\Collection\Introductions $intros)
    +{
    +    foreach ($intros as $intro) {
    +        /** @var $intro \Friendica\Contact\Introductions\Entity\Introduction */
    +        $introId = $intro->id;
    +    }
    +}
    +
    +/** @var $intros \Friendica\Contact\Introductions\Collection\Introductions */
    +$intros = \Friendica\DI::intro()->selecForUser(local_user());
    +
    +doSomething($intros);
    +
    +

    Dependency Injection#

    +

    Under this concept, we want class objects to carry with them the dependencies they will use. +Instead of calling global/static function/methods, objects use their own class members.

    +

    Before: +

    <?php
    +
    +class Model
    +{
    +    public $id;
    +
    +    function save()
    +    {
    +        return \Friendica\Database\DBA::update('table', get_object_vars($this), ['id' => $this->id]);
    +    }
    +}
    +

    +

    After: +

    <?php
    +
    +class Model
    +{
    +    /**
    +     * @var \Friendica\Database\Database
    +     */
    +    protected $dba;
    +
    +    public $id;
    +
    +    function __construct(\Friendica\Database\Database $dba)
    +    {
    +        $this->dba = $dba;
    +    }
    +
    +    function save()
    +    {
    +        return $this->dba->update('table', get_object_vars($this), ['id' => $this->id]);
    +    }
    +}
    +

    +

    The main advantage is testability. +Another one is avoiding dependency circles and avoid implicit initializing. +In the first example the method save() has to be tested with the DBA::update() method, which may or may not have dependencies itself.

    +

    In the second example we can mock \Friendica\Database\Database, e.g. overload the class by replacing its methods by placeholders, which allows us to test only Model::save() and nothing else implicitly.

    +

    The main drawback is lengthy constructors for dependency-heavy classes. +To alleviate this issue we are using DiCe to simplify the instantiation of the higher level objects Friendica uses.

    +

    We also added a convenience factory named \Friendica\DI that creates some of the most common objects used in modules.

    +

    Factories#

    +

    Since we added a bunch of parameters to class constructors, instantiating objects has become cumbersome. +To keep it simple, we are using Factories. +Factories are classes used to generate other objects, centralizing the dependencies required in their constructor. +Factories encapsulate more or less complex creation of objects and create them redundancy free.

    +

    Before: +

    <?php
    +
    +$model = new Model(\Friendica\DI::dba());
    +$model->id = 1;
    +$model->key = 'value';
    +
    +$model->save();
    +

    +

    After: +

    <?php
    +
    +class Factory
    +{
    +    /**
    +     * @var \Friendica\Database\Database
    +     */
    +    protected $dba;
    +
    +    function __construct(\Friendica\Database\Database $dba)
    +    {
    +        $this->dba;
    +    }
    +
    +    public function create()
    +    {
    +        return new Model($this->dba);    
    +    }
    +}
    +
    +$model = \Friendica\DI::factory()->create();
    +$model->id = 1;
    +$model->key = 'value';
    +
    +$model->save();
    +

    +

    Here, DI::factory() returns an instance of Factory that can then be used to create a Model object without having to care about its dependencies.

    +

    Repositories#

    +

    Last building block of our code architecture, repositories are meant as the interface between models and how they are stored. +In Friendica they are stored in a relational database but repositories allow models not to have to care about it. +Repositories also act as factories for the Model they are managing.

    +

    Before: +

    <?php
    +
    +class Model
    +{
    +    /**
    +     * @var \Friendica\Database\Database
    +     */
    +    protected $dba;
    +
    +    public $id;
    +
    +    function __construct(\Friendica\Database\Database $dba)
    +    {
    +        $this->dba = $dba;
    +    }
    +
    +    function save()
    +    {
    +        return $this->dba->update('table', get_object_vars($this), ['id' => $this->id]);
    +    }
    +}
    +
    +class Factory
    +{
    +    /**
    +     * @var \Friendica\Database\Database
    +     */
    +    protected $dba;
    +
    +    function __construct(\Friendica\Database\Database $dba)
    +    {
    +        $this->dba;
    +    }
    +
    +    public function create()
    +    {
    +        return new Model($this->dba);    
    +    }
    +}
    +
    +
    +$model = \Friendica\DI::factory()->create();
    +$model->id = 1;
    +$model->key = 'value';
    +
    +$model->save();
    +

    +

    After: +

    <?php
    +
    +class Model {
    +    public $id;
    +}
    +
    +class Repository extends Factory
    +{
    +    /**
    +     * @var \Friendica\Database\Database
    +     */
    +    protected $dba;
    +
    +    function __construct(\Friendica\Database\Database $dba)
    +    {
    +        $this->dba;
    +    }
    +
    +    public function create()
    +    {
    +        return new Model($this->dba);    
    +    }
    +
    +    public function save(Model $model)
    +    {
    +        return $this->dba->update('table', get_object_vars($model), ['id' => $model->id]);
    +    }
    +}
    +
    +$model = \Friendica\DI::repository()->create();
    +$model->id = 1;
    +$model->key = 'value';
    +
    +\Friendica\DI::repository()->save($model);
    +

    + + +
    + +
    +
    + + + +
    + + + +
    +
    +
    +
    + + + + + + + + \ No newline at end of file diff --git a/develop/de/developer/github/index.html b/develop/de/developer/github/index.html new file mode 100644 index 0000000..7d8679a --- /dev/null +++ b/develop/de/developer/github/index.html @@ -0,0 +1,3441 @@ + + + + + + + + + + + + + + + + + + + + + + GitHub - Friendica Dokumentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + +
    + + + + +
    + + +
    + +
    + + + + + + + + + +
    +
    + + + +
    +
    +
    + + + + + + +
    +
    +
    + + + +
    +
    + +
    +
    + + +
    +
    + + + + + + + + + + + + + +

    Friendica on GitHub#

    +

    Here is how you can work on the code with us. If you have any questions please write to the Friendica developers' forum.

    +

    Introduction to the workflow with our GitHub repository#

    +
      +
    1. Install git on the system you will be developing on.
    2. +
    3. Create your own GitHub account.
    4. +
    5. Fork the Friendica repository from https://github.com/friendica/friendica.git.
    6. +
    7. Clone your fork from your GitHub account to your machine. +Follow the instructions provided here: http://help.github.com/fork-a-repo/ to create and use your own tracking fork on GitHub
    8. +
    9. Run bin/composer.phar install in Friendica's folder.
    10. +
    11. Commit your changes to your fork. +Then go to your GitHub page and create a "Pull request" to notify us to merge your work.
    12. +
    +

    Our Git Branches#

    +

    There are two relevant branches in the main repo on GitHub:

    +
      +
    1. stable: This branch contains stable releases only.
    2. +
    3. develop: This branch contains the latest code. +This is what you want to work with.
    4. +
    +

    Fast-forwarding#

    +

    Fast forwarding is enabled by default in git. +When you merge with fast-forwarding it does not add a new commit to mark when you've performed the merge and how. +This means in your commit history you can't know exactly what happened in terms of merges. +It's best to turn off fast-forwarding. +This is done by running "git merge --no-ff". +Here is an explanation on how to configure git to turn off fast-forwarding by default. +You can find some more background reading here.

    +

    Release branches#

    +

    A release branch is created when the develop branch contains all features it should have. +A release branch is used for a few things.

    +
      +
    1. It allows last-minute bug fixing before the release goes to stable branch.
    2. +
    3. It allows meta-data changes (README, CHANGELOG, etc.) for version bumps and documentation changes.
    4. +
    5. It makes sure the develop branch can receive new features that are not part of this release.
    6. +
    +

    That last point is important because... +The moment a release branch is created, develop is now intended for the version after this release. +So please don't ever merge develop into a release! +An example: If a release branch "release-3.4" is created, "develop" becomes either 3.5 or 4.0. +If you were to merge develop into release-3.4 at this point, features and bug-fixes intended for 3.5 or 4.0 might leak into this release branch. +This might introduce new bugs, too. +Which defeats the purpose of the release branch.

    +

    Some important reminders#

    +
      +
    1. +

      Please pull in any changes from the project repository and merge them with your work before issuing a pull request. +We reserve the right to reject any patch which results in a large number of merge conflicts. +This is especially true in the case of language translations - where we may not be able to understand the subtle differences between conflicting versions.

      +
    2. +
    3. +

      Test your changes. +Don't assume that a simple fix won't break anything else. +If possible get an experienced Friendica developer to review the code. +Don't hesitate to ask us in case of doubt.

      +
    4. +
    5. +

      Check your code for typos. +There is a console command called typo for this.

      +
    6. +
    +
    $> php bin/console.php typo
    +
    +

    Check out how to work with our Vagrant to save a lot of setup time!

    + + +
    + +
    +
    + + + +
    + + + +
    +
    +
    +
    + + + + + + + + \ No newline at end of file diff --git a/develop/de/developer/how-to-move-classes-to-src/index.html b/develop/de/developer/how-to-move-classes-to-src/index.html new file mode 100644 index 0000000..d432cea --- /dev/null +++ b/develop/de/developer/how-to-move-classes-to-src/index.html @@ -0,0 +1,3461 @@ + + + + + + + + + + + + + + + + + + + + + + src Migration - Friendica Dokumentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + +
    + + + + +
    + + +
    + +
    + + + + + + + + + +
    +
    + + + +
    +
    +
    + + + + + + +
    +
    +
    + + + +
    +
    +
    + + + +
    +
    +
    + + +
    +
    + + + + + + + + + + + + + +

    How To Move Classes to src#

    +

    Friendica uses Composer to manage autoloading. +This means that all the PHP class files moved to the src folder will be automatically included when the class it defines is first used in the flow. +This is an improvement over the current require usage since files will be included on an actual usage basis instead of the presence of a require call.

    +

    However, there are a significant number of items to check when moving a class file from the include folder to the src folder, and this page is there to list them.

    +

    Decide the namespace#

    +

    This isn't the most technical decision of them all, but it has long-lasting consequences as it will be the name that will be used to refer to this class from now on. +There is a shared Ethercalc sheet to suggest namespace/class names that lists all the already moved class files for inspiration.

    +

    A few pointers though: +* Friendica is the base namespace for all classes in the src folder +* Namespaces match the directory structure, with Friendica namespace being the base src directory. The Config class set in the Friendica\Core namespace is expected to be found at src/Core/Config.php. +* Namespaces can help group classes with a similar purpose or relevant to a particular feature

    +

    When you're done deciding the namespace, it's time to use it. +Let's say we choose Friendica\Core for the Config class.

    +

    Use the namespace#

    +

    To declare the namespace, the file src/Core/Config.php must start with the following statement:

    +
    namespace Friendica\Core;
    +
    +

    From now on, the Config class can be referred to as Friendica\Core\Config, however it isn't very practical, especially when the class was previously used as Config. +Thankfully, PHP provides namespace shortcuts through use.

    +

    This language construct just provides a different naming scheme for a namespace or a class, but doesn't trigger the autoload-mechanism on its own. +Here are the different ways you can use use:

    +

    // No use
    +$config = new Friendica\Core\Config();
    +
    +
    // Namespace shortcut
    +use Friendica\Core;
    +
    +$config = new Core\Config();
    +
    +
    // Class name shortcut
    +use Friendica\Core\Config;
    +
    +$config = new Config();
    +
    +
    // Aliasing
    +use Friendica\Core\Config as Cfg;
    +
    +$config = new Cfg();
    +

    +

    Whatever the style chosen, a repository-wide search has to be done to find all the class name usage and either use the fully-qualified class name (including the namespace) or add a use statement at the start of each relevant file.

    +

    Escape non-namespace classes#

    +

    The class file you just moved is now in the Friendica namespace, but it probably isn't the case for all the classes referenced in this file. +Since we added a namespace Friendica\Core; to the file, all the class names still declared in include will be implicitly understood as Friendica\Core\ClassName, which is rarely what we expect.

    +

    To avoid Class Friendica\Core\ClassName not found errors, all the include-declared class names have to be prepended with a \, it tells the autoloader not to look for the class in the namespace but in the global space where non-namespaced classes are set. +If there are only a handful of references to a single non-namespaced class, just prepending \ is enough. However, if there are many instance, we can use use again.

    +

    namespace Friendica\Core;
    +...
    +if (\DBM::is_result($r)) {
    +    ...
    +}
    +
    +
    namespace Friendica\Core;
    +
    +use Friendica\Database\DBM;
    +
    +if (DBM::is_result($r)) {
    +    ...
    +}
    +

    +

    Remove any useless require#

    +

    Now that you successfully moved your class to the autoloaded src folder, there's no need to include this file anywhere in the app ever again. +Please remove all the require_once mentions of the former file, as they will provoke a Fatal Error even if the class isn't used.

    +

    Miscellaneous tips#

    +

    When you are done with moving the class, please run php bin/console.php typo from the Friendica base directory to check for obvious mistakes. +However, this tool isn't bullet-proof, and a staging install of Friendica is recommended to test your class move without impairing your production server if you host one.

    +

    Most of Friendica processes are run in the background, so make sure to turn on your debug log to check for errors that wouldn't show up while simply browsing Friendica.

    +

    Check the class file for any magic constant __FILE__ or __DIR__, as their value changed since you moved the class in the file tree. +Most of the time it's used for debugging purposes but there can be instances where it's used to create cache folders for example.

    + + + + +
    + +
    +
    + + + +
    + + + +
    +
    +
    +
    + + + + + + + + \ No newline at end of file diff --git a/develop/de/developer/index.html b/develop/de/developer/index.html new file mode 100644 index 0000000..f981ceb --- /dev/null +++ b/develop/de/developer/index.html @@ -0,0 +1,3332 @@ + + + + + + + + + + + + + + + + + + + + + + Guide - Friendica Dokumentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + +
    + + + + +
    + + +
    + +
    + + + + + + + + + +
    +
    + + + +
    +
    +
    + + + + + + +
    +
    +
    + + + +
    +
    +
    + + + +
    +
    +
    + + +
    +
    + + + + + + + + + + + + + +

    Friendica - Entwickler-Guide#

    +

    Hier erfährst du, wie du bei uns mitmachen kannst:

    +

    Zunächst erstelle dir per 'git clone https://github.com/friendica/friendica.git' ein funktionierendes Git-Paket auf deinem System, auf dem du die Entwicklung durchführst, und einen eigenen Github-Account.

    +

    Erstelle deine eigene Kopie (fork) der Ursprungsdaten auf Github, an der du dann entspannt arbeiten kannst. +Deine Arbeiten sollten mit einem neuen Arbeitszweig (branch) beginnen, den du vom develop Zweig des Repositories beginnst. +Die Anleitung unter http://help.github.com/fork-a-repo/ erklärt dir genau, wie du das tun musst.

    +

    Gehe dann nach getaner Arbeit zu Deiner Github-Seite und erstelle eine "Pull request", um Deine Änderungen in das Hauptprojekt einzugliedern (merge).

    +

    Solltest du keine Idee haben, an welcher Stelle du einsteigen könntest? +Wir haben einige Aufgaben auf github mit dem Schlagwort Junior Job versehen. +Bei diesen Aufgaben gehen wir davon aus, dass sie geeignete Einstiegsstellen sind. +Du musst dich aber natürlich nicht mit diesen Aufgaben beschäftigen, um den Friendica Code zu verbessern.

    +

    Wichtig#

    +

    Bitte hole Dir alle Änderungen aus dem Projektverzeichnis und führe sie mit Deiner Arbeit zusammen, bevor Du Deine "pull request" erstellst. Wir behalten es uns vor, Patches abzulehnen, die eine große Anzahl an Fehlern hervorrufen. +Dies gilt vor allem für Übersetzungen, da wir hier möglicherweise nicht alle feinen Unterschiede in konfliktähren Versionen erkennen können.

    +

    Außerdem: teste Deine Änderungen! Vergiss nicht, dass eine simple Fehlerlösung einen anderen Fehler auslösen kann. +Lass Deine Änderungen von einem erfahrenen Friendica-Entwickler gegenprüfen.

    +

    Eine ausführliche Anleitung zu Git findest Du unter https://git-scm.com/book/de/v1.

    + + +
    + +
    +
    + + + +
    + + + +
    +
    +
    +
    + + + + + + + + \ No newline at end of file diff --git a/develop/de/developer/smarty3-templates/index.html b/develop/de/developer/smarty3-templates/index.html new file mode 100644 index 0000000..660395f --- /dev/null +++ b/develop/de/developer/smarty3-templates/index.html @@ -0,0 +1,3652 @@ + + + + + + + + + + + + + + + + + + + + + + Smarty3 - Friendica Dokumentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + +
    + + + + +
    + + +
    + +
    + + + + + + + + + +
    +
    + + + +
    +
    +
    + + + + + + +
    +
    +
    + + + + + + +
    +
    + + + + + + + + + + + + + +

    Friendica Templating Documentation#

    +

    Friendica uses Smarty 3 as PHP templating engine. +The main templates are found in

    +
        /view/templates
    +
    +

    theme authors may overwrite the default templates by putting a files with the same name into the

    +
        /view/themes/$themename/templates
    +
    +

    directory.

    +

    Templates that are only used by addons shall be placed in the

    +
        /addon/$addonname/templates
    +
    +

    directory.

    +

    To render a template use the function getMarkupTemplate to load the template and replaceMacros to replace the macros/variables in the just loaded template file.

    +
    $tpl = Renderer::getMarkupTemplate('install_settings.tpl');
    +$o .= Renderer::replaceMacros($tpl, array( ... ));
    +
    +

    the array consists of an association of an identifier and the value for that identifier, i.e.

    +
        '$title' => $install_title,
    +
    +

    where the value may as well be an array by its own.

    +

    Form Templates#

    +

    To guarantee a consistent look and feel for input forms, i.e. in the settings sections, there are templates for the basic form fields. +They are initialized with an array of data, depending on the style of the field.

    +

    All of these take an array holding the values, e.g. for a one line text input field, which is required and should be used to type email addresses use something along the lines of:

    +
        '$adminmail' => array('adminmail', DI::l10n()->t('Site administrator email address'), $adminmail, DI::l10n()->t('Your account email address must match this in order to use the web admin panel.'), 'required', '', 'email'),
    +
    +

    To evaluate the input value, you can then use the $_POST array, more precisely the $_POST['adminemail'] variable.

    +

    Listed below are the template file names, the general purpose of the template and their field parameters.

    +

    field_checkbox.tpl#

    +

    A checkbox. +If the checkbox is checked its value is 1. +Field parameter:

    +
      +
    1. Name of the checkbox,
    2. +
    3. Label for the checkbox,
    4. +
    5. State checked? if true then the checkbox will be marked as checked,
    6. +
    7. Help text for the checkbox.
    8. +
    +

    field_combobox.tpl#

    +

    A combobox, combining a pull down selection and a textual input field. +Field parameter:

    +
      +
    1. Name of the combobox,
    2. +
    3. Label for the combobox,
    4. +
    5. Current value of the variable,
    6. +
    7. Help text for the combobox,
    8. +
    9. Array holding the possible values for the textual input,
    10. +
    11. Array holding the possible values for the pull down selection.
    12. +
    +

    field_custom.tpl#

    +

    A customizable template to include a custom element in the form with the usual surroundings, +Field parameter:

    +
      +
    1. Name of the field,
    2. +
    3. Label for the field,
    4. +
    5. the field,
    6. +
    7. Help text for the field.
    8. +
    +

    field_input.tpl#

    +

    A single line input field for any type of input. +Field parameter:

    +
      +
    1. Name of the field,
    2. +
    3. Label for the input box,
    4. +
    5. Current value of the variable,
    6. +
    7. Help text for the input box,
    8. +
    9. Should be set to the translation of "Required" to mark this field as required,
    10. +
    11. if set to "autofocus" modern browser will put the cursor into this box once the page is loaded,
    12. +
    13. if set, it will be used for the input type, default is text (possible types: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input#%3Cinput%3E_types).
    14. +
    +

    field_intcheckbox.tpl#

    +

    A checkbox (see above) but you can define the value of it. +Field parameter:

    +
      +
    1. Name of the checkbox,
    2. +
    3. Label for the checkbox,
    4. +
    5. State checked? if true then the checkbox will be marked as checked,
    6. +
    7. Value of the checkbox,
    8. +
    9. Help text for the checkbox.
    10. +
    +

    field_openid.tpl#

    +

    An input box (see above) but prepared for special CSS styling for openID input. +Field parameter:

    +
      +
    1. Name of the field,
    2. +
    3. Label for the input box,
    4. +
    5. Current value of the variable,
    6. +
    7. Help text for the input field.
    8. +
    +

    field_password.tpl#

    +

    A single line input field (see above) for textual input. +The characters typed in will not be shown by the browser. +Field parameter:

    +
      +
    1. Name of the field,
    2. +
    3. Label for the field,
    4. +
    5. Value for the field, e.g. the old password,
    6. +
    7. Help text for the input field,
    8. +
    9. Should be set to the translation of "Required" to mark this field as required,
    10. +
    11. if set to "autofocus" modern browser will put the cursor automatically into this input field.
    12. +
    +

    field_radio.tpl#

    +

    A radio button. +Field parameter:

    +
      +
    1. Name of the radio button,
    2. +
    3. Label for the radio button,
    4. +
    5. Current value of the variable,
    6. +
    7. Help text for the button,
    8. +
    9. if set, the radio button will be checked.
    10. +
    +

    field_richtext.tpl#

    +

    A multi-line input field for rich textual content. +Field parameter:

    +
      +
    1. Name of the input field,
    2. +
    3. Label for the input box,
    4. +
    5. Current text for the box,
    6. +
    7. Help text for the input box.
    8. +
    +

    field_select.tpl#

    +

    A drop-down selection box. +Field parameter:

    +
      +
    1. Name of the field,
    2. +
    3. Label of the selection box,
    4. +
    5. Current selected value,
    6. +
    7. Help text for the selection box,
    8. +
    9. Array holding the possible values of the selection drop-down.
    10. +
    +

    field_select_raw.tpl#

    +

    A drop-down selection box (see above) but you have to prepare the values yourself. +Field parameter:

    +
      +
    1. Name of the field,
    2. +
    3. Label of the selection box,
    4. +
    5. Current selected value,
    6. +
    7. Help text for the selection box,
    8. +
    9. Possible values of the selection drop-down.
    10. +
    +

    field_textarea.tpl#

    +

    A multi-line input field for (plain) textual content. +Field parameter:

    +
      +
    1. Name of the input field,
    2. +
    3. Label for the input box,
    4. +
    5. Current text for the box,
    6. +
    7. Help text for the input box,
    8. +
    9. Should be set to the translation of "Required" to mark this field as required.
    10. +
    + + +
    + +
    +
    + + + +
    + + + +
    +
    +
    +
    + + + + + + + + \ No newline at end of file diff --git a/develop/de/developer/tests/index.html b/develop/de/developer/tests/index.html new file mode 100644 index 0000000..369e141 --- /dev/null +++ b/develop/de/developer/tests/index.html @@ -0,0 +1,3280 @@ + + + + + + + + + + + + + + + + + + + + + + Tests - Friendica Dokumentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + +
    + + + + +
    + + +
    + +
    + + + + + + + + + +
    +
    + + + +
    +
    +
    + + + + + + +
    +
    +
    + + + +
    +
    +
    + + + +
    +
    +
    + + +
    +
    + + + + + + + + + + + + + +

    Tests#

    +

    You can run unit tests with PHPUnit:

    +
    phpunit
    +
    +

    Some tests require access to a MySQL database. +You can specify the database credentials in environment variables:

    +
    USER=database_user PASS=database_password DB=database_name phpunit
    +
    +

    Warning: This will empty all the tables! Never use this on a production database.

    + + +
    + +
    +
    + + + +
    + + + +
    +
    +
    +
    + + + + + + + + \ No newline at end of file diff --git a/develop/de/developer/themes/index.html b/develop/de/developer/themes/index.html new file mode 100644 index 0000000..11caf25 --- /dev/null +++ b/develop/de/developer/themes/index.html @@ -0,0 +1,3757 @@ + + + + + + + + + + + + + + + + + + + + + + Themes - Friendica Dokumentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + +
    + + + + +
    + + +
    + +
    + + + + + + + + + +
    +
    + + + +
    +
    +
    + + + + + + +
    +
    +
    + + + +
    +
    +
    + + + +
    +
    +
    + + +
    +
    + + + + + + + + + + + + + +

    Themes#

    +

    To change the look of friendica you have to touch the themes. +The current default theme is Vier but there are numerous others. +Have a look at github.com/bkil/friendica-themes for an overview of the existing themes. +In case none of them suits your needs, there are several ways to change a theme.

    +

    So, how to work on the UI of friendica.

    +

    You can either directly edit an existing theme. +But you might lose your changes when the theme is updated by the friendica team.

    +

    If you are almost happy with an existing theme, the easiest way to cover your needs is to create a new theme, inheritance most of the properties of the parent theme and change just minor stuff. +The below for a more detailed description of theme heritage.

    +

    Some themes also allow users to select variants of the theme. +Those theme variants most often contain an additional CSS file to override some styling of the default theme values. +From the themes in the main repository duepunto zero and vier are using these methods for variations. +Quattro is using a slightly different approach.

    +

    Third you can start your theme from scratch. +Which is the most complex way to change friendicas look. +But it leaves you the most freedom. +So below for a detailed description and the meaning of some special files.

    +

    Styling#

    +

    If you want to change the styling of a theme, have a look at the themes CSS file. +In most cases, you can find these in

    +
    /view/theme/**your-theme-name**/style.css
    +
    +

    sometimes, there is also a file called style.php in the theme directory. +This is only needed if the theme allows the user to change certain things of the theme dynamically. +Say the font size or set a background image.

    +

    Templates#

    +

    If you want to change the structure of the theme, you need to change the templates used by the theme. +Friendica themes are using SMARTY3 for templating. +The default template can be found in

    +
    /view/templates
    +
    +

    if you want to override any template within your theme create your version of the template in

    +
    /view/theme/**your-theme-name**/templates
    +
    +

    any template that exists there will be used instead of the default one.

    +

    Javascript#

    +

    The same rule applies to the JavaScript files found in

    +
    /js
    +
    +

    they will be overwritten by files in

    +
    /view/theme/**your-theme-name**/js.
    +
    +

    Expand an existing Theme#

    +

    Theme Variations#

    +

    Many themes are more theme families than only one theme. +duepunto zero and vier allow easily to add new theme variation. +We will go through the process of creating a new variation for duepunto zero. +The same (well almost, some names change) procedure applies to the vier theme. +And similar steps are needed for quattro but this theme is using lesscss to maintain the CSS files.

    +

    In

    +
    /view/theme/duepuntozero/deriv
    +
    +

    you find a couple of CSS files that define color derivations from the duepunto theme. +These resemble some now as unsupported marked themes, that were inherited by the duepunto theme. +Darkzero and Easter Bunny for example.

    +

    The selection of the colorset is done in a combination of a template for a new form in the settings and some functions in the theme.php file. +The template (theme_settings.tpl)

    +
    {{ '{{include file="field_select.tpl" field=$colorset}}' }}
    +<div class="settings-submit-wrapper">
    +    {{ '<input type="submit" value="{{$submit}}" class="settings-submit" name="duepuntozero-settings-submit" />' }}
    +</div>
    +
    +

    defines a formular consisting of a select pull-down which contains all available variants and s submit button. +See the documentation about SMARTY3 templates for a summary of friendica specific blocks other than the select element. +But we don't really need to change anything at the template itself.

    +

    The template alone won't work though. +You make friendica aware of its existence and tell it how to use the template file, by defining a config.php file. +It needs to define at least the following functions

    +
      +
    • theme_content
    • +
    • theme_post
    • +
    +

    and may also define functions for the admin interface

    +
      +
    • theme_admin
    • +
    • theme_admin_post.
    • +
    +

    theme_content and theme_admin are used to make the form available in the settings, respectively the admin panel. +The _post functions handle the processing of the send-form, in this case they save to selected variant in friendicas database.

    +

    To make your own variation appear in the menu, all you need to do is to create a new CSS file in the deriv-directory and include it in the array in the config.php:

    +
    $colorset = array(
    +    'default'=>DI::l10n()->t('default'),
    +    'greenzero'=>DI::l10n()->t('greenzero'),
    +    'purplezero'=>DI::l10n()->t('purplezero'),
    +    'easterbunny'=>DI::l10n()->t('easterbunny'),
    +    'darkzero'=>DI::l10n()->t('darkzero'),
    +    'comix'=>DI::l10n()->t('comix'),
    +    'slackr'=>DI::l10n()->t('slackr'),
    +);
    + ```
    +
    +the 1st part of the line is the name of the CSS file (without the .css) the 2nd part is the common name of the variant.
    +Calling the DI::l10n()->t() function with the common name makes the string translatable.
    +The selected 1st part will be saved in the database by the theme_post function.
    +
    +```php
    +<?php
    +function theme_post(App $a){
    +    // non-local users shall not pass
    +    if (! local_user()) {
    +        return;
    +    }
    +    // if the one specific submit button was pressed then proceed
    +    if (isset($_POST['duepuntozero-settings-submit'])){
    +        // and save the selection key into the personal config of the user
    +        DI::pConfig()->set(local_user(), 'duepuntozero', 'colorset', $_POST['duepuntozero_colorset']);
    +    }
    +}
    +
    +

    Now that this information is set in the database, what should friendica do with it? +For this, have a look at the theme.php file of the duepunto zero. +There you'll find something alike

    +
    <?php
    +$colorset = DI::pConfig()->get( local_user(), 'duepuntozero','colorset');
    +if (!$colorset)
    +    $colorset = DI::config()->get('duepuntozero', 'colorset');
    +if ($colorset) {
    +    if ($colorset == 'greenzero')
    +        DI::page()['htmlhead'] .= '<link rel="stylesheet" href="view/theme/duepuntozero/deriv/greenzero.css" type="text/css" media="screen" />'."\n";
    +    /* some more variants */
    +}
    +
    +

    which tells friendica to get the personal config of a user. +Check if it is set and if not look for the global config. +And finally if a config for the colorset was found, apply it by adding a link to the CSS file into the HTML header of the page. +So you'll just need to add an if selection, fitting your variant keyword and link to the CSS file of it.

    +

    Done. +Now you can use the variant on your system. +But remember once the theme.php or the config.php you have to re-add your variant to them. +If you think your color variation could be beneficial for other friendica users as well, feel free to generate a pull request at GitHub, so we can include your work into the repository.

    +

    Inheritance#

    +

    Say, you like the duepuntozero, but you want to have the content of the outer columns left and right exchanged. +That would be not a color variation as shown above. +Instead, we will create a new theme, duepuntozero_lr, inherit the properties of duepuntozero and make small changes to the underlying php files.

    +

    So create a directory called duepunto_lr and create a file called theme.php with your favorite text editor. +The content of this file should be something like

    +
    <?php
    +/* meta information for the theme, see below */
    +use Friendica\App;
    +
    +function duepuntozero_lr_init(App $a) {
    +    $a->setThemeInfoValue('extends', 'duepuntozero');
    +
    +    $a->set_template_engine('smarty3');
    +    /* and more stuff e.g. the JavaScript function for the header */
    +}
    +
    +

    Next take the default.php file found in the /view directory and exchange the aside and right_aside elements. +So the central part of the file now looks like this:

    +
    <body>
    +    <?php if(!empty($page['nav'])) echo $page['nav']; ?>
    +    <aside><?php if(!empty($page['right_aside'])) echo $page['right_aside']; ?></aside>
    +    <section><?php if(!empty($page['content'])) echo $page['content']; ?>
    +            <div id="page-footer"></div>
    +    </section>
    +    <right_aside><?php if(!empty($page['aside'])) echo $page['aside']; ?></right_aside>
    +    <footer><?php if(!empty($page['footer'])) echo $page['footer']; ?></footer>
    +</body>
    +
    +

    Finally, we need a style.css file, inheriting the definitions from the parent theme and containing out changes for the new theme. +Note:You need to create the style.css and at lest import the base CSS file from the parent theme.

    +
    @import url('../duepuntozero/style.css');
    +
    +

    Done. +But I agree it is not really useful at this state. +Nevertheless, to use it, you just need to activate in the admin panel. +That done, you can select it in the settings like any other activated theme.

    +

    Creating a Theme from Scratch#

    +

    Keep patient. +Basically what you have to do is identify which template you have to change, so it looks more like what you want. +Adopt the CSS of the theme accordingly. +And iterate the process until you have the theme the way you want it.

    +

    Use the source Luke. and don't hesitate to ask in @developers or @helpers.

    +

    Special Files#

    +

    unsupported#

    +

    If a file with this name (which might be empty) exists in the theme directory, the theme is marked as unsupported. +An unsupported theme may not be selected by a user in the settings. +Users who are already using it won't notice anything.

    +

    README(.md)#

    +

    The contents of this file, with or without the .md which indicates Markdown syntax, will be displayed at most repository hosting services and in the theme page within the admin panel of friendica.

    +

    This file should contain information you want to let others know about your theme.

    +

    screenshot.[png|jpg]#

    +

    If you want to have a preview image of your theme displayed in the settings you should take a screenshot and save it with this name. +Supported formats are PNG and JPEG.

    +

    theme.php#

    +

    This is the main definition file of the theme. +In the header of that file, some meta information is stored. +For example, have a look at the theme.php of the quattro theme:

    +
    <?php
    +/**
    + * Name: Quattro
    + * Version: 0.6
    + * Author: Fabio <https://kirgroup.com/profile/fabrixxm>
    + * Maintainer: Fabio <https://kirgroup.com/profile/fabrixxm>
    + * Maintainer: Tobias <https://f.diekershoff.de/profile/tobias>
    + */
    + ```
    +
    +You see the definition of the theme's name, it's version and the initial author of the theme.
    +These three pieces of information should be listed.
    +If the original author is no longer working on the theme, but a maintainer has taken over, the maintainer should be listed as well.
    +The information from the theme header will be displayed in the admin panel.
    +
    +The first thing in file is to import the `App` class from `\Friendica\` namespace.
    +
    +```php
    +use Friendica\App;
    +
    +

    This will make our job a little easier, as we don't have to specify the full name every time we need to use the App class.

    +

    The next crucial part of the theme.php file is a definition of an init function. +The name of the function is _init. +So in the case of quattro it is

    +
    <?php
    +function quattro_init(App $a) {
    +  $a->theme_info = array();
    +  $a->set_template_engine('smarty3');
    +}
    +
    +

    Here we have set the basic theme information, in this case they are empty. +But the array needs to be set. +And we have set the template engine that should be used by friendica for this theme. +At the moment you should use the smarty3 engine. +There once was a friendica specific templating engine as well but that is not used anymore. +If you like to use another templating engine, please implement it.

    +

    When you want to inherit stuff from another theme you have to announce this in the theme_info:

    +
    $a->setThemeInfoValue('extends', 'duepuntozero');
    +
    +

    which declares duepuntozero as parent of the theme.

    +

    If you want to add something to the HTML header of the theme, one way to do so is by adding it to the theme.php file. +To do so, add something alike

    +
    DI::page()['htmlhead'] .= <<< EOT
    +/* stuff you want to add to the header */
    +EOT;
    +
    +

    The $a variable holds the friendica application. +So you can access the properties of this friendica session from the theme.php file as well.

    +

    default.php#

    +

    This file covers the structure of the underlying HTML layout. +The default file is in

    +
    /view/default.php
    +
    +

    if you want to change it, say adding a 4th column for banners of your favourite FLOSS projects, place a new default.php file in your theme directory. +As with the theme.php file, you can use the properties of the $a variable with holds the friendica application to decide what content is displayed.

    + + +
    + +
    +
    + + + +
    + + + +
    +
    +
    +
    + + + + + + + + \ No newline at end of file diff --git a/develop/de/developer/translations/index.html b/develop/de/developer/translations/index.html new file mode 100644 index 0000000..d0903e0 --- /dev/null +++ b/develop/de/developer/translations/index.html @@ -0,0 +1,3540 @@ + + + + + + + + + + + + + + + + + + + + + + Translations - Friendica Dokumentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + +
    + + + + +
    + + +
    + +
    + + + + + + + + + +
    +
    + + + +
    +
    +
    + + + + + + +
    +
    +
    + + + +
    +
    +
    + + + +
    +
    +
    + + +
    +
    + + + + + + + + + + + + + +

    Friendica translations#

    +

    Overview#

    +

    The Friendica translation process is based on gettext PO files.

    +

    Basic workflow: +1. xgettext is used to collect translation strings across the project in the authoritative PO file located in view/lang/C/messages.po. +2. This file makes translations strings available at the Transifex Friendica page. +3. The translation itself is done at Transifex by volunteers. +4. The resulting PO files by languages are manually updated in view/lang/<language>/messages.po. +5. PO files are converted to PHP arrays in view/lang/<language>/strings.php that are ultimately used by Friendica to display the translations.

    +

    Translate Friendica in your favorite language#

    +

    Thank you for your interest in improving Friendica's translation! +Please register a free Transifex account and ask over at the Transifex Friendica page to join the translation team for your favorite language.

    +

    As a rule of thumb, we add support for a language in Friendica when at least 50% of the strings have been translated to avoid a scattered experience. +For addons, we add support for a language when if we already support the language in Friendica.

    +

    Add new translation strings#

    +

    Core#

    +

    Once you have added new translation strings in your code changes, please run bin/run_xgettext.sh from the base Friendica directory and commit the updated view/lang/C/messages.po to your branch.

    +

    Addon#

    +

    If you have the friendica-addons repository in the addon directory of your Friendica cloned repository, just run bin/run_xgettext.sh -a <addon_name> from the base Friendica directory.

    +

    Otherwise:

    +
    cd /path/to/friendica-addons/<addon_name>
    +/path/to/friendica/bin/run_xgettext.sh -s
    +
    +

    In either case, you need to commit the updated <addon_name>/lang/C/messages.po to your working branch.

    +

    Update translations from Transifex#

    +

    Please download the Transifex file "for use" in view/lang/<language>/messages.po.

    +

    Then run bin/console po2php view/lang/<language>/messages.po to update the related strings.php file and commit both files to your working branch.

    +

    Using the Transifex client#

    +

    Transifex has a client program which allows you to sync files between your cloned Friendica repository and Transifex. +Help for the client can be found at the Transifex Help Center. +Here we will only cover basic usage.

    +

    After installation of the client, you should have a tx command available on your system. +To use it, first create a configuration file with your credentials. +On Linux this file should be placed into your home directory ~/.transifexrc. +The content of the file should be something like the following:

    +
    [https://www.transifex.com]
    +username = user
    +token =
    +password = p@ssw0rd
    +hostname = https://www.transifex.com
    +
    +

    Since Friendica version 3.5.1 we ship configuration files for the Transifex client in the core repository and the addon repository in .tx/config. +To update the PO files after you have translated strings of e.g. Esperanto on the Transifex website you can use tx to download the updated PO-file in the right location.

    +
    tx pull -l eo
    +
    +

    Then run bin/console po2php view/lang/<language>/messages.po to update the related strings.php file and commit both files to your working branch.

    +

    Translation functions usage#

    +

    Basic usage#

    +
      +
    • Friendica\DI::l10n()->t('Label') => Label
    • +
    • Friendica\DI::l10n()->t('Label %s', 'test') => Label test
    • +
    +

    Plural#

    +
      +
    • Friendica\DI::l10n()->tt('Label', 'Labels', 1) => Label
    • +
    • Friendica\DI::l10n()->tt('Label', 'Labels', 3) => Labels
    • +
    • Friendica\DI::l10n()->tt('%d Label', '%d Labels', 1) => 1 Label
    • +
    • Friendica\DI::l10n()->tt('%d Label', '%d Labels', 3) => 3 Labels
    • +
    • Friendica\DI::l10n()->tt('%d Label', 'Labels %2%s %3%s', 1, 'test', 'test2') => Label test test2
    • +
    • Friendica\DI::l10n()->tt('%d Label', 'Labels %2%s %3%s', 3, 'test', 'test2') => Labels test test2
    • +
    + + +
    + +
    +
    + + + +
    + + + +
    +
    +
    +
    + + + + + + + + \ No newline at end of file diff --git a/develop/de/developer/vagrant/index.html b/develop/de/developer/vagrant/index.html new file mode 100644 index 0000000..9cb53a6 --- /dev/null +++ b/develop/de/developer/vagrant/index.html @@ -0,0 +1,3396 @@ + + + + + + + + + + + + + + + + + + + + + + Vagrant - Friendica Dokumentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + +
    + + + + +
    + + +
    + +
    + + + + + + + + + +
    +
    + + + +
    +
    +
    + + + + + + +
    +
    +
    + + + +
    +
    +
    + + + +
    +
    +
    + + +
    +
    + + + + + + + + + + + + + +

    Vagrant for Friendica Developers#

    +

    Getting started#

    +

    Vagrant is a virtualization solution for developers. +No need to set up a webserver, database etc. before actually starting. +Vagrant creates a virtual machine for you that you can just run inside VirtualBox and start to work directly on Friendica.

    +

    It brings a Debian Bullseye with PHP 7.4 and MariaDB 10.5.11.

    +

    What you need to do:

    +
      +
    1. Install VirtualBox and vagrant. +Please use an up-to-date vagrant version from https://www.vagrantup.com/downloads.html.
    2. +
    3. Git clone your Friendica repository. +Inside, you'll find a Vagrantfile and some scripts in the bin/dev folder. +Pull the PHP requirements with bin/composer install.
    4. +
    5. Run vagrant up from inside the friendica clone. +This will start the virtual machine. +Be patient: When it runs for the first time, it downloads a Debian Server image and installs Friendica.
    6. +
    7. Run vagrant ssh to log into the virtual machine to log in to the VM in case you need to debug something on the server.
    8. +
    9. Open you test installation in a browser. +Go to friendica.local (or 192.168.22.10). +friendica.local is using a self-signed TLS certificate, so you will need to add an exception to trust the certificate the first time you are visiting the page. +The mysql database is called "friendica", the mysql user and password both are "friendica".
    10. +
    11. Work on Friendica's code in your git clone on your machine (not in the VM). +Your local working directory is set up as a shared directory with the VM (/vagrant).
    12. +
    13. Check the changes in your browser in the VM. +Find the Friendica log file /vagrant/logfile.out on the VM or in the logfile.out in you local Friendica directory.
    14. +
    15. Commit and push your changes directly back to GitHub.
    16. +
    +

    If you want to stop vagrant after finishing your work, run the following command

    +
    vagrant halt
    +
    +

    in the development directory. +This will not delete the virtual machine. +9. To ultimately delete the virtual machine run

    +
    vagrant destroy
    +rm /vagrant/config/local.config.php
    +
    +

    to make sure that you can start from scratch with another "vagrant up".

    +

    Default User Accounts#

    +

    By default, the provision script will set up two user accounts.

    +
      +
    • admin, password admin
    • +
    • friendica, password friendica
    • +
    +

    Trouble Shooting#

    +

    If you see a version mismatch for the VirtualBox Guest Additions between host and guest during the initial setup of the Vagrant VM, you will need to install an addon to Vagrant (ref. Stack Overflow). +Stop the Vagrant VM and run the following command:

    +
    vagrant plugin install vagrant-vbguest
    +
    +

    On the next Vagrant up, the version problem should be fixed.

    +

    If friendica.local is not resolved, you may need to add an entry to the /etc/hosts file (or similar configuration depending on the OS you are using).

    +

    For further documentation of vagrant, please see the vagrantdocs.

    + + +
    + +
    +
    + + + +
    + + + +
    +
    +
    +
    + + + + + + + + \ No newline at end of file diff --git a/develop/de/html/index.php b/develop/de/html/index.php new file mode 100644 index 0000000..ab83759 --- /dev/null +++ b/develop/de/html/index.php @@ -0,0 +1,16 @@ + + + + $Projectname Doxygen API Documentation + + +

    $Projectname Doxygen API Documentation not rendered

    + +To get the Doxygen API Documentation you must render it with the program Doxygen (included in most distributions). +
    +$ doxygen Doxyfile
    +
    +
    +back + + diff --git a/develop/de/index.html b/develop/de/index.html new file mode 100644 index 0000000..9835396 --- /dev/null +++ b/develop/de/index.html @@ -0,0 +1,3404 @@ + + + + + + + + + + + + + + + + + + + + + + Start - Friendica Dokumentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + +
    + + + + +
    + + +
    + +
    + + + + + + + + + +
    +
    + + + +
    +
    +
    + + + + + + +
    +
    +
    + + + +
    +
    +
    + + + +
    +
    +
    + + +
    +
    + + + + + + + + + + + +

    Friendica - Dokumentation und Ressourcen#

    +

    Inhalte#

    + +

    Dokumentation für Administratoren#

    + +

    Dokumentation für Entwickler#

    + +

    Externe Ressourcen#

    + + + +
    + +
    +
    + + + +
    + + + +
    +
    +
    +
    + + + + + + + + \ No newline at end of file diff --git a/develop/de/spec/api/entities/index.html b/develop/de/spec/api/entities/index.html new file mode 100644 index 0000000..4026512 --- /dev/null +++ b/develop/de/spec/api/entities/index.html @@ -0,0 +1,4926 @@ + + + + + + + + + + + + + + + + + + + + + + Entities - Friendica Dokumentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + +
    + + + + +
    + + +
    + +
    + + + + + + + + + +
    +
    + + + +
    +
    +
    + + + + + + +
    +
    +
    + + + +
    +
    +
    + + + +
    +
    +
    + + +
    +
    + + + + + + + + + + + + + +

    Friendica API entities#

    +

    Activities#

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    AttributeTypeNullable
    likeList of [Contacts](./entities.md#contact)No
    dislikeList of [Contacts](./entities.md#contact)No
    attendyesList of [Contacts](./entities.md#contact)No
    attendnoList of [Contacts](./entities.md#contact)No
    attendmaybeList of [Contacts](./entities.md#contact)No
    + +

    Attachment#

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    AttributeTypeNullable
    urlString (URL)No
    mimetypeStringNo
    sizeInteger (bytes)No
    + +

    Contact#

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    AttributeTypeNullable
    idIntegerNo
    id_strStringNo
    nameStringNo
    screen_nameStringNo
    locationStringNo
    descriptionStringNo
    profile_image_urlString (URL)No
    profile_image_url_httpsString (URL)No
    profile_image_url_profile_sizeString (URL)No
    profile_image_url_largeString (URL)No
    urlString (URL)No
    protectedBooleanNo
    followers_countIntegerNo
    friends_countIntegerNo
    listed_countIntegerNo
    favourites_countIntegerNo
    statuses_countIntegerNo
    created_atString (Date)
    +Ex: Wed May 23 06:01:13 +0000 2007 +
    No
    utc_offsetIntegerNo
    time_zoneStringNo
    geo_enabledBooleanNo
    verifiedBooleanNo
    langStringNo
    contributors_enabledBooleanNo
    is_translatorBooleanNo
    is_translation_enabledBooleanNo
    followingBooleanNo
    follow_request_sentBooleanNo
    statusnet_blockingBooleanNo
    notificationsBooleanNo
    statusnet_profile_urlString (URL)No
    uidIntegerNo
    cidIntegerNo
    pidIntegerNo
    selfIntegerNo
    networkStringNo
    + +

    Entities#

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    AttributeTypeNullable
    hashtagsList of [Hashtags](./entities.md#hashtag)No
    symbolsList of [Symbols](./entities.md#symbol)No
    urlsList of [URLs](./entities.md#url)No
    user_mentionsList of [User mentions](./entities.md#user+mention)No
    mediaList of [Medias](./entities.md#media)No
    + +

    Event#

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    AttributeTypeDescription
    idInteger
    uidIntegerOwner User Id
    cidIntegerTarget Contact Id
    uriStringItem unique URI string
    nameString (Plaintext)Title
    descString (HTML)Description
    startTimeString (UTC YYYY-MM-DD HH:II:SS))
    endTimeString (UTC YYYY-MM-DD HH:II:SS))Optional (null date is 0001-01-01 00:00:00
    typeString (event or birthday)
    nofinishBooleanOngoing event
    placeStringOptional. Location.
    ignoreBoolean???
    allow_cidString (angle-brackets escaped integers)Optional. List of allowed contact ids
    allow_gidString (angle-brackets escaped integers)Optional. List of allowed group ids
    deny_cidString (angle-brackets escaped integers)Optional. List of disallowed contact ids
    deny_gidString (angle-brackets escaped integers)Optional. List of disallowed group ids
    + +

    Hashtag#

    +

    Unused

    +

    Item#

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    AttributeTypeNullable
    textString (Plaintext)No
    truncatedBooleanNo
    created_atString (Date)
    +Ex: Wed May 23 06:01:13 +0000 2007 +
    No
    in_reply_to_status_idIntegerNo
    in_reply_to_status_id_strStringNo
    sourceStringNo
    idIntegerNo
    id_strStringNo
    in_reply_to_user_idIntegerNo
    in_reply_to_user_id_strStringNo
    in_reply_to_screen_nameStringNo
    geoStringYes
    favoritedBooleanNo
    user[Contact](./entities.md#contact)No
    friendica_author[Contact](./entities.md#contact)No
    friendica_owner + +[Contact](./entities.md#contact)No
    friendica_privateBooleanNo
    statusnet_htmlString (HTML)No
    statusnet_conversation_idIntegerNo
    external_urlString (URL)No
    friendica_activities[Activities](./entities.md#activities)No
    friendica_titleString (Plaintext)No
    friendica_htmlString (HTML)No
    attachmentsList of [Attachments](./entities.md#attachment)Yes
    entities[Entities](./entities.md#entities)Yes
    + +

    Media#

    +

    Identical to the Twitter Media Object.

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    AttributeTypeNullable
    idIntegerNo
    id_strStringNo
    indicesList of IntegerNo
    media_urlString (URL)No
    media_url_httpsString (URL)No
    urlString (URL)No
    display_urlString (URL)No
    expanded_urlString (URL)No
    ext_alt_textStringNo
    typeStringNo
    sizes[Sizes](./entities.md#sizes)No
    + +

    Notification#

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    AttributeTypeDescription
    idInteger
    hashString
    typeInteger
      +
    • 1: Inbound follow request
    • +
    • 2: Outbound follow request confirmation
    • +
    • 4: Wall-to-wall post
    • +
    • 8: Reply
    • +
    • 16: Private message
    • +
    • 32: Friend suggestion
    • +
    • 64: Unused
    • +
    • 128: Mention
    • +
    • 256: Tag added to a post
    • +
    • 512: Poke
    • +
    • 1024: New post
    • +
    • 16384: System email
    • +
    • 32768: System event
    • +
    nameStringFull name of the contact subject
    urlString (URL)Profile page URL of the contact subject
    photoString (URL)Profile photo URL of the contact subject
    dateString (Date)YYYY-MM-DD hh:mm:ss local server time
    msgString (BBCode)
    uidIntegerOwner User Id
    linkString (URL)Notification URL
    iidIntegerItem Id
    parentIntegerParent Item Id
    seenInteger (Boolean)Whether the notification was read or not.
    verbString (URL)[Activity Streams](http://activitystrea.ms) Verb URL
    seenInteger (Boolean)Whether the notification was read or not.
    otypeEnumSubject type (`item`, `intro` or `mail`)
    name_cacheString (HTML)Full name of the contact subject
    msg_cacheString (Plaintext)Plaintext version of the notification text with a placeholder (`{0}`) for the subject contact's name.
    timestampIntegerUnix timestamp
    date_relStringTime since the note was posted, eg "1 hour ago"
    msg_htmlString (HTML)
    msg_plainString (Plaintext)
    + +

    Photo#

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    AttributeTypeDescription
    idStringResource ID (32 hex chars)
    createdString (Date)Format YYYY-MM-DD HH:MM:SS
    editedString (Date)Format YYYY-MM-DD HH:MM:SS
    titleString
    descString (Plaintext)Picture caption
    albumStringAlbum name
    filenameStringOriginal image filename
    typeStringMIME Type
    heightIntegerImage height in pixels
    widthIntegerImage width in pixels
    profileInteger1 if it is a profile photo
    allow_cidString (ACL)List of contact ids wrapped in angle brackets allowed to access the photo.
    allow_gidString (ACL)List of contact group ids wrapped in angle brackets allowed to access the photo.
    deny_cidString (ACL)List of contact ids wrapped in angle brackets forbidden to access the photo.
    deny_gidString (ACL)List of contact group ids wrapped in angle brackets forbidden to access the photo.
    linkArray of Strings (URL) +URLs to the different scales indexed by scale number if no specific scale was requested. +Mutually exclusive with data datasize. +
    datasizeInteger +Picture size in bytes if a single scale was requested. +Mutually exclusive with link. +
    dataString +Base64-encoded image data if a single scale was requested. +Mutually exclusive with link. +
    friendica_activities[Activities](./entities.md#activities)
    friendica_commentsList of [Items](./entities.md#item)
    rights_mismatchBooleanTrue if the ACL differs between the picture and the associated item.
    + +

    Photo List Item#

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    AttributeTypeDescription
    idStringResource ID (32 hex chars)
    albumStringAlbum name
    filenameStringOriginal image filename
    typeStringMIME Type
    createdString (Date)Format YYYY-MM-DD HH:MM:SS
    editedString (Date)Format YYYY-MM-DD HH:MM:SS
    descString (Plaintext)Picture caption
    thumbString (URL)URL of the smallest scale version of the picture.
    + +

    Private message#

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    AttributeTypeDescription
    idInteger
    sender_idIntegerSender Contact Id
    textStringCan be HTML or plaintext depending on the API call parameter `getText`.
    recipient_idIntegerRecipient Contact Id
    created_atString (Date)Ex: Wed May 23 06:01:13 +0000 2007
    sender_screen_nameString
    recipient_screen_nameString
    sender[Contact](./entities.md#contact)
    recipient[Contact](./entities.md#contact)
    titleStringEmpty if the API call parameter `getText` is empty or absent.
    friendica_seenInteger (Boolean)Whether the private message has been read or not.
    friendica_parent_uriString
    + +

    Profile#

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    AttributeTypeDescription
    profile_idInteger
    profile_nameString
    is_defaultBoolean
    hide_friendsBooleanWhether the user chose to hide their contact list on their profile.
    profile_photoString (URL)Largest size profile picture URL.
    profile_thumbString (URL)Smallest size profile picture URL.
    publishBooleanWhether the user chose to publish their profile in the local directory.
    net_publishBooleanWhether the user chose to publish their profile in the global directory.
    descriptionString
    date_of_birthString
    addressString
    cityString
    regionString
    postal_codeString
    countryString
    public_keywordsStringComma-separated list of words meant to be displayed as hashtags.
    private_keywordsStringComma-separated list of words meant to be used for search only.
    homepageString (URL)
    + +

    Size#

    + + + + + + + + + +
    AttributeTypeNullable
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    AttributeTypeNullable
    wIntegerNo
    hIntegerNo
    resizeEnum (fit, crop)Yes
    + + +## Sizes + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    AttributeTypeNullable
    medium[Size](./entities.md#size)No
    large[Size](./entities.md#size)Yes
    thumb[Size](./entities.md#size)Yes
    small[Size](./entities.md#size)Yes
    + +## Symbol + +Unused + +## URL + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    AttributeTypeNullable
    urlString (URL)No
    expanded_urlString (URL)No
    display_urlString (URL)No
    indicesList of IntegerNo
    + +## User Mention + +Unused + + +
    + +
    +
    + + + +
    + + + +
    +
    +
    +
    + + + + + + + + \ No newline at end of file diff --git a/develop/de/spec/api/friendica/index.html b/develop/de/spec/api/friendica/index.html new file mode 100644 index 0000000..ed90082 --- /dev/null +++ b/develop/de/spec/api/friendica/index.html @@ -0,0 +1,5418 @@ + + + + + + + + + + + + + + + + + + + + + + Friendica - Friendica Dokumentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + +
    + + + + +
    + + +
    + +
    + + + + + + + + + +
    +
    + + + +
    +
    +
    + + + + + + +
    +
    +
    + + + +
    +
    +
    + + + +
    +
    +
    + + +
    +
    + + + + + + + + + + + + + +

    Friendica API#

    +

    Overview#

    +

    Friendica provides the following specific endpoints.

    +

    Authentication is the same as described in Using the APIs.

    +

    Entities#

    +

    These endpoints use the Friendica API entities.

    +

    Endpoints#

    +

    GET api/friendica/events#

    +

    Returns a list of Event entities for the current logged-in user.

    +

    Parameters#

    +
      +
    • since_id: (optional) minimum event id for pagination
    • +
    • count: maximum number of items returned, default 20
    • +
    +

    GET api/externalprofile/show#

    +

    Returns a Contact entity for the provided profile URL.

    +

    Parameters#

    +
      +
    • profileurl: Profile URL
    • +
    +

    GET api/statuses/public_timeline#

    +

    Returns a list of public Items posted on this node. +Equivalent of the local community page.

    +

    Parameters#

    +
      +
    • count: Items per page (default: 20)
    • +
    • page: page number
    • +
    • since_id: minimum id
    • +
    • max_id: maximum id
    • +
    • exclude_replies: don't show replies (default: false)
    • +
    • conversation_id: Shows all statuses of a given conversation.
    • +
    • include_entities: "true" shows entities for pictures and links (Default: false)
    • +
    +

    Unsupported parameters#

    +
      +
    • trim_user
    • +
    +

    GET api/statuses/networkpublic_timeline#

    +

    Returns a list of public Items this node is aware of. +Equivalent of the global community page.

    +

    Parameters#

    +
      +
    • count: Items per page (default: 20)
    • +
    • page: page number
    • +
    • since_id: minimum id
    • +
    • max_id: maximum id
    • +
    • exclude_replies: don't show replies (default: false)
    • +
    • conversation_id: Shows all statuses of a given conversation.
    • +
    • include_entities: "true" shows entities for pictures and links (Default: false)
    • +
    +

    GET api/statuses/replies#

    +

    Parameters#

    +
      +
    • count: Items per page (default: 20)
    • +
    • page: page number
    • +
    • since_id: minimum id
    • +
    • max_id: maximum id
    • +
    • include_entities: "true" shows entities for pictures and links (Default: false)
    • +
    +

    Unsupported parameters#

    +
      +
    • include_rts
    • +
    • trim_user
    • +
    • contributor_details
    • +
    +
    +

    GET api/conversation/show#

    +

    Unofficial Twitter command. It shows all direct answers (excluding the original post) to a given id.

    +

    Parameters#

    +
      +
    • id: id of the post
    • +
    • count: Items per page (default: 20)
    • +
    • page: page number
    • +
    • since_id: minimum id
    • +
    • max_id: maximum id
    • +
    • include_entities: "true" shows entities for pictures and links (Default: false)
    • +
    +

    Unsupported parameters#

    +
      +
    • include_rts
    • +
    • trim_user
    • +
    • contributor_details
    • +
    +

    GET api/statusnet/conversation#

    +

    Alias of api/conversation/show.

    +

    GET api/statusnet/config#

    +

    Returns the public Friendica node configuration.

    +

    GET api/gnusocial/config#

    +

    Alias of api/statusnet/config.

    +

    GET api/statusnet/version#

    +

    Returns a fake static StatusNet protocol version.

    +

    GET api/gnusocial/version#

    +

    Alias of api/statusnet/version.

    +
    +

    POST api/friendica/activity/[verb]#

    +

    Add or remove an activity from an item. +'verb' can be one of:

    +
      +
    • like
    • +
    • dislike
    • +
    • attendyes
    • +
    • attendno
    • +
    • attendmaybe
    • +
    +

    To remove an activity, prepend the verb with "un", eg. "unlike" or "undislike" +Attend verbs disable eachother: that means that if "attendyes" was added to an item, adding "attendno" remove previous "attendyes". +Attend verbs should be used only with event-related items (there is no check at the moment).

    +

    Parameters#

    +
      +
    • id: item id
    • +
    +

    Return values#

    +

    On success: +json:

    +

    "ok"

    +

    xml:

    +

    <ok>true</ok>

    +

    On error: +HTTP 400 BadRequest

    +
    +

    GET api/direct_messages#

    +

    Deprecated Twitter received direct message list endpoint.

    +

    Parameters#

    +
      +
    • count: Items per page (default: 20)
    • +
    • page: page number
    • +
    • since_id: minimum id
    • +
    • max_id: maximum id
    • +
    • getText: Defines the format of the status field. Can be "html" or "plain"
    • +
    • include_entities: "true" shows entities for pictures and links (Default: false)
    • +
    • friendica_verbose: "true" enables different error returns (default: "false")
    • +
    +

    Unsupported parameters#

    +
      +
    • skip_status
    • +
    +

    GET api/direct_messages/all#

    +

    Returns all Private Messages.

    +

    Parameters#

    +
      +
    • count: Items per page (default: 20)
    • +
    • page: page number
    • +
    • since_id: minimum id
    • +
    • max_id: maximum id
    • +
    • getText: Defines the format of the status field. Can be "html" or "plain"
    • +
    • friendica_verbose: "true" enables different error returns (default: "false")
    • +
    +

    GET api/direct_messages/conversation#

    +

    Returns all replies of a single private message conversation. Returns Private Messages

    +

    Parameters#

    +
      +
    • count: Items per page (default: 20)
    • +
    • page: page number
    • +
    • since_id: minimum id
    • +
    • max_id: maximum id
    • +
    • getText: Defines the format of the status field. Can be "html" or "plain"
    • +
    • uri: URI of the conversation
    • +
    • friendica_verbose: "true" enables different error returns (default: "false")
    • +
    +

    GET api/direct_messages/sent#

    +

    Deprecated Twitter sent direct message list endpoint. Returns Private Messages.

    +

    Parameters#

    +
      +
    • count: Items per page (default: 20)
    • +
    • page: page number
    • +
    • since_id: minimum id
    • +
    • max_id: maximum id
    • +
    • getText: Defines the format of the status field. Can be "html" or "plain"
    • +
    • include_entities: "true" shows entities for pictures and links (Default: false)
    • +
    • friendica_verbose: "true" enables different error returns (default: "false")
    • +
    +

    POST api/direct_messages/new#

    +

    Deprecated Twitter direct message submission endpoint.

    +

    Parameters#

    +
      +
    • user_id: id of the user
    • +
    • screen_name: screen name (for technical reasons, this value is not unique!)
    • +
    • text: The message
    • +
    • replyto: ID of the replied direct message
    • +
    • title: Title of the direct message
    • +
    +

    POST api/direct_messages/destroy#

    +

    Deprecated Twitter direct message deletion endpoint.

    +

    Parameters#

    +
      +
    • id: id of the message to be deleted
    • +
    • include_entities: optional, currently not yet implemented
    • +
    • friendica_parenturi: optional, can be used for increased safety to delete only intended messages
    • +
    • friendica_verbose: "true" enables different error returns (default: "false")
    • +
    +

    Return values#

    +

    On success:

    +
      +
    • JSON return as defined for Twitter API not yet implemented
    • +
    • on friendica_verbose=true: JSON return {"result":"ok","message":"message deleted"}
    • +
    +

    On error: +HTTP 400 BadRequest

    +
      +
    • on friendica_verbose=true: different JSON returns {"result":"error","message":"xyz"}
    • +
    +

    GET api/friendica/direct_messages_setseen#

    +

    Parameters#

    +
      +
    • id: id of the message to be updated as seen
    • +
    +

    Return values#

    +

    On success:

    +
      +
    • JSON return {"result": "ok", "message": "message set to seen"}
    • +
    +

    On error:

    +
      +
    • different JSON returns {"result": "error", "message": "xyz"}
    • +
    +

    GET api/friendica/direct_messages_search (GET; AUTH)#

    +

    Returns Private Messages matching the provided search string.

    +

    Parameters#

    +
      +
    • searchstring: string for which the API call should search as '%searchstring%' in field 'body' of all messages of the authenticated user (caption ignored)
    • +
    • getText (optional): plain|html If ommited, the title is prepended to the plaintext body in the text attribute of the private message objects.
    • +
    • getUserObjects (optional): true|false If false, the sender and recipient attributes of the private message object are absent.
    • +
    +

    Return values#

    +

    Returns only tested with JSON, XML might work as well.

    +

    On success:

    +
      +
    • JSON return {"success":"true", "search_results": array of found messages}
    • +
    • JSOn return {"success":"false", "search_results": "nothing found"}
    • +
    +

    On error:

    +
      +
    • different JSON returns {"result": "error", "message": "searchstring not specified"}
    • +
    +
    +

    GET api/friendica/group_show#

    +

    Return all or a specified group of the user with the containing contacts as array.

    +

    Parameters#

    +
      +
    • gid: optional, if not given, API returns all groups of the user
    • +
    +

    Return values#

    +

    Array of:

    +
      +
    • name: name of the group
    • +
    • gid: id of the group
    • +
    • user: array of Contacts
    • +
    +

    POST api/friendica/group_create#

    +

    Create the group with the posted array of contacts as members.

    +

    Parameters#

    +
      +
    • name: name of the group to be created
    • +
    +

    POST data#

    +

    JSON data as Array like the result of GET api/friendica/group_show:

    + +

    Return values#

    +

    Array of:

    +
      +
    • success: true if successfully created or reactivated
    • +
    • gid: gid of the created group
    • +
    • name: name of the created group
    • +
    • status: "missing user" | "reactivated" | "ok"
    • +
    • wrong users: array of users, which were not available in the contact table
    • +
    +

    POST api/friendica/group_update#

    +

    Update the group with the posted array of contacts as members (post all members of the group to the call; function will remove members not posted).

    +

    Parameters#

    +
      +
    • gid: id of the group to be changed
    • +
    • name: name of the group to be changed
    • +
    +

    POST data#

    +

    JSON data as array like the result of GET api/friendica/group_show:

    + +

    Return values#

    +

    Array of:

    +
      +
    • success: true if successfully updated
    • +
    • gid: gid of the changed group
    • +
    • name: name of the changed group
    • +
    • status: "missing user" | "ok"
    • +
    • wrong users: array of users, which were not available in the contact table
    • +
    +

    POST api/friendica/group_delete#

    +

    Delete the specified group of contacts; API call need to include the correct gid AND name of the group to be deleted.

    +

    Parameters#

    +
      +
    • gid: id of the group to be deleted
    • +
    • name: name of the group to be deleted
    • +
    +

    Return values#

    +

    Array of:

    +
      +
    • success: true if successfully deleted
    • +
    • gid: gid of the deleted group
    • +
    • name: name of the deleted group
    • +
    • status: "deleted" if successfully deleted
    • +
    • wrong users: empty array
    • +
    +
    +

    GET api/friendica/notifications#

    +

    Return last 50 Notifications for the current user, ordered by date with unseen item on top.

    +

    Parameters#

    +

    none

    +

    POST api/friendica/notifications/seen#

    +

    Set notification as seen.

    +

    Parameters#

    +
      +
    • id: id of the notification to set seen
    • +
    +

    Return values#

    +

    If the note is linked to an item, returns an Item.

    +

    Otherwise, a success status is returned:

    +
      +
    • success (json) | <status>success</status> (xml)
    • +
    +
    +

    GET api/friendica/photo#

    +

    Returns a Photo.

    +

    Parameters#

    +
      +
    • photo_id: Resource id of a photo.
    • +
    • scale: (optional) scale value of the photo
    • +
    +

    Returns data of a picture with the given resource. +If 'scale' isn't provided, returned data include full url to each scale of the photo. +If 'scale' is set, returned data include image data base64 encoded.

    +

    possibile scale value are:

    +
      +
    • 0: original or max size by server settings
    • +
    • 1: image with or height at <= 640
    • +
    • 2: image with or height at <= 320
    • +
    • 3: thumbnail 160x160
    • +
    • 4: Profile image at 300x300
    • +
    • 5: Profile image at 80x80
    • +
    • 6: Profile image at 48x48
    • +
    +

    An image used as profile image has only scaled 4-6, other images only 0-3

    +

    Return values#

    +

    json:

    +
        {
    +        "id": "photo id",
    +        "created": "date(YYYY-MM-DD HH:MM:SS)",
    +        "edited": "date(YYYY-MM-DD HH:MM:SS)",
    +        "title": "photo title",
    +        "desc": "photo description",
    +        "album": "album name",
    +        "filename": "original file name",
    +        "type": "mime type",
    +        "height": "number",
    +        "width": "number",
    +        "profile": "1 if is profile photo",
    +        "link": {
    +            "<scale>": "url to image",
    +            ...
    +        },
    +        // if 'scale' is set
    +        "datasize": "size in byte",
    +        "data": "base64 encoded image data"
    +    }
    +
    +

    xml:

    +
        <photo>
    +        <id>photo id</id>
    +        <created>date(YYYY-MM-DD HH:MM:SS)</created>
    +        <edited>date(YYYY-MM-DD HH:MM:SS)</edited>
    +        <title>photo title</title>
    +        <desc>photo description</desc>
    +        <album>album name</album>
    +        <filename>original file name</filename>
    +        <type>mime type</type>
    +        <height>number</height>
    +        <width>number</width>
    +        <profile>1 if is profile photo</profile>
    +        <links type="array">
    +        <link type="mime type" scale="scale number" href="image url"/>
    +            ...
    +        </links>
    +    </photo>
    +
    +

    GET api/friendica/photos/list#

    +

    Returns the API user's Photo List Items.

    +

    Return values#

    +

    json:

    +
        [
    +        {
    +            "id": "resource_id",
    +            "album": "album name",
    +            "filename": "original file name",
    +            "type": "image mime type",
    +            "thumb": "url to thumb sized image"
    +        },
    +        ...
    +    ]
    +
    +

    xml:

    +
        <photos type="array">
    +        <photo id="resource_id"
    +        album="album name"
    +        filename="original file name"
    +        type="image mime type">
    +            "url to thumb sized image"
    +        </photo>
    +        ...
    +    </photos>
    +
    +

    POST api/friendica/photo/create#

    +

    Alias of api/friendica/photo/update

    +

    POST api/friendica/photo/update#

    +

    Saves data for the scales 0-2 to database (see above for scale description). +Call adds non-public entries to items table to enable authenticated contacts to comment/like the photo. +Client should pay attention to the fact that updated access rights are not transferred to the contacts. i.e. public photos remain publicly visible if they have been commented/liked before setting visibility back to a limited group. +Currently, it is best to inform user that updating rights is not the right way to do this, and offer a solution to add photo as a new photo with the new rights instead.

    +

    Parameters#

    +
      +
    • photo_id (optional): if specified the photo with this id will be updated
    • +
    • media (optional): image data as base64, only optional if photo_id is specified (new upload must have media)
    • +
    • desc (optional): description for the photo, updated when photo_id is specified
    • +
    • album: name of the album to be deleted (always necessary)
    • +
    • album_new (optional): can be used to change the album of a single photo if photo_id is specified
    • +
    • allow_cid/allow_gid/deny_cid/deny_gid (optional):
        +
      • on create: empty string or omitting = public photo, specify in format <x><y><z>for private photo
      • +
      • on update: keys need to be present with empty values for changing a private photo to public
      • +
      +
    • +
    +

    Return values#

    +

    On success:

    +
      +
    • new photo uploaded: JSON return with photo data (see GET api/friendica/photo)
    • +
    • photo updated - changed photo data: JSON return with photo data (see GET api/friendica/photo)
    • +
    • photo updated - changed info: JSON return {"result": "updated", "message":"Image id 'xyz' has been updated."}
    • +
    • photo updated - nothing changed: JSON return {"result": "cancelled","message": "Nothing to update for image id 'xyz'."}
    • +
    +

    On error:

    +
      +
    • 403 FORBIDDEN: if not authenticated
    • +
    • 400 BADREQUEST: "no albumname specified", "no media data submitted", "photo not available", "acl data invalid"
    • +
    • 500 INTERNALSERVERERROR: "image size exceeds PHP config settings, file was rejected by server", + "image size exceeds Friendica Config setting (uploaded size: x)", + "unable to process image data", + "image upload failed", + "unknown error - uploading photo failed, see Friendica log for more information", + "unknown error - update photo entry in database failed", + "unknown error - this error on uploading or updating a photo should never happen"
    • +
    +

    POST api/friendica/photo/delete#

    +

    Deletes a single image with the specified id, is not reversible -> ensure that client is asking user for being sure to do this +Sets item table entries for this photo to deleted = 1.

    +

    Parameters#

    +
      +
    • photo_id: id of the photo to be deleted
    • +
    +

    Return values#

    +

    On success:

    +
      +
    • JSON return
    • +
    +
    {
    +    "result": "deleted",
    +    "message": "photo with id 'xyz' has been deleted from server."
    +}
    +
    +

    On error:

    +
      +
    • 403 FORBIDDEN: if not authenticated
    • +
    • 400 BADREQUEST: "no photo_id specified", "photo not available"
    • +
    • 500 INTERNALSERVERERROR: "unknown error on deleting photo", "problem with deleting items occurred"
    • +
    +
    +

    POST api/friendica/photoalbum/delete#

    +

    Deletes all images with the specified album name, is not reversible -> ensure that client is asking user for being sure to do this.

    +

    Parameters#

    +
      +
    • album: name of the album to be deleted
    • +
    +

    Return values#

    +

    On success:

    +
      +
    • JSON return
    • +
    +
    {
    +    "result": "deleted",
    +    "message": "album 'xyz' with all containing photos has been deleted."
    +}
    +
    +

    On error:

    +
      +
    • 403 FORBIDDEN: if not authenticated
    • +
    • 400 BADREQUEST: "no albumname specified", "album not available"
    • +
    • 500 INTERNALSERVERERROR: "problem with deleting item occured", "unknown error - deleting from database failed"
    • +
    +

    POST api/friendica/photoalbum/update#

    +

    Changes the album name to album_new for all photos in album.

    +

    Parameters#

    +
      +
    • album: name of the album to be updated
    • +
    • album_new: new name of the album
    • +
    +

    Return values#

    +

    On success:

    +
      +
    • JSON return
    • +
    +
    {
    +    "result": "updated",
    +    "message":"album 'abc' with all containing photos has been renamed to 'xyz'."
    +}
    +
    +

    On error:

    +
      +
    • 403 FORBIDDEN: if not authenticated
    • +
    • 400 BADREQUEST: "no albumname specified", "no new albumname specified", "album not available"
    • +
    • 500 INTERNALSERVERERROR: "unknown error - updating in database failed"
    • +
    +
    +

    GET api/friendica/profile/show#

    +

    Returns the Profile data of the authenticated user.

    +

    Return values#

    +

    On success: Array of:

    +
      +
    • global_dir: URL of the global directory set in server settings
    • +
    • friendica_owner: user data of the authenticated user
    • +
    • profiles: array of the profile data
    • +
    +

    On error: +HTTP 403 Forbidden: when no authentication was provided +HTTP 400 Bad Request: if given profile_id is not in the database or is not assigned to the authenticated user

    +

    General description of profile data in API returns: +- hide_friends: true if friends are hidden +- profile_photo +- profile_thumb +- publish: true if published on the server's local directory +- net_publish: true if published to global_dir +- fullname +- date_of_birth +- description +- xmpp +- homepage +- address +- locality +- region +- postal_code +- country +- pub_keywords +- custom_fields: list of public custom fields

    +
    +

    Deprecated endpoints#

    +
      +
    • POST api/statuses/mediap
    • +
    + + +
    + +
    +
    + + + +
    + + + +
    +
    +
    +
    + + + + + + + + \ No newline at end of file diff --git a/develop/de/spec/api/gnu-social/index.html b/develop/de/spec/api/gnu-social/index.html new file mode 100644 index 0000000..308b418 --- /dev/null +++ b/develop/de/spec/api/gnu-social/index.html @@ -0,0 +1,3441 @@ + + + + + + + + + + + + + + + + + + + + + + GNU Social - Friendica Dokumentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + +
    + + + + +
    + + +
    + +
    + + + + + + + + + +
    +
    + + + +
    +
    +
    + + + + + + +
    +
    +
    + + + +
    +
    +
    + + + +
    +
    +
    + + +
    +
    + + + + + + + + + + + + + +

    GNU Social API#

    +

    Overview#

    +

    Friendica provides the following endpoints defined in the official GNU Social Twitter-like API reference.

    +

    Authentication is the same as described in Using the APIs.

    +

    Entities#

    +

    These endpoints use the Friendica API entities.

    +

    Implemented endpoints#

    +
      +
    • GET api/account/rate_limit_status
    • +
    • POST api/account/update_profile_image
    • +
    • +

      GET api/account/verify_credentials

      +
    • +
    • +

      GET api/direct_messages

      +
    • +
    • POST/DELETE api/direct_messages/destroy
    • +
    • POST api/direct_messages/new
    • +
    • GET api/direct_messages/sent
    • +
    • GET api/favorites
    • +
    • POST api/favorites/create/:id
    • +
    • POST api/favorites/destroy/:id
    • +
    • GET api/followers/ids
    • +
    • POST api/friendships/destroy
    • +
    • GET api/friends/ids
    • +
    • GET/POST api/help/test
    • +
    • GET api/search
    • +
    • GET api/statuses/show/:id
    • +
    • POST api/statuses/destroy/:id
    • +
    • GET api/statuses/followers
    • +
    • GET api/statuses/friends
    • +
    • GET api/statuses/friends_timeline
    • +
    • GET api/statuses/friends_timeline/:username
    • +
    • GET api/statuses/home_timeline
    • +
    • GET api/statuses/mentions
    • +
    • GET api/statuses/replies
    • +
    • GET api/statuses/replies/:username
    • +
    • POST api/statuses/retweet/:id
    • +
    • GET api/statuses/public_timeline
    • +
    • POST api/statuses/update
    • +
    • GET api/statuses/user_timeline
    • +
    • GET api/users/show
    • +
    +

    Non-implemented endpoints#

    +
      +
    • statuses/retweeted_to_me
    • +
    • statuses/retweeted_by_me
    • +
    • statuses/retweets_of_me
    • +
    • friendships/create
    • +
    • friendships/exists
    • +
    • friendships/show
    • +
    • account/end_session
    • +
    • account/update_delivery_device
    • +
    • account/update_profile_background_image
    • +
    • notifications/follow
    • +
    • notifications/leave
    • +
    • blocks/create
    • +
    • blocks/destroy
    • +
    • blocks/exists
    • +
    • blocks/blocking
    • +
    • oauth/authorize
    • +
    • oauth/access_token
    • +
    • oauth/request_token
    • +
    • statusnet/groups/timeline
    • +
    • statusnet/groups/show
    • +
    • statusnet/groups/create
    • +
    • statusnet/groups/join
    • +
    • statusnet/groups/leave
    • +
    • statusnet/groups/list
    • +
    • statusnet/groups/list_all
    • +
    • statusnet/groups/membership
    • +
    • statusnet/groups/is_member
    • +
    • statusnet/tags/timeline
    • +
    • statusnet/media/upload
    • +
    • statusnet/config
    • +
    + + +
    + +
    +
    + + + +
    + + + +
    +
    +
    +
    + + + + + + + + \ No newline at end of file diff --git a/develop/de/spec/api/index.html b/develop/de/spec/api/index.html new file mode 100644 index 0000000..36bf899 --- /dev/null +++ b/develop/de/spec/api/index.html @@ -0,0 +1,3479 @@ + + + + + + + + + + + + + + + + + + + + + + Usage - Friendica Dokumentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + +
    + + + + +
    + + +
    + +
    + + + + + + + + + +
    +
    + + + +
    +
    +
    + + + + + + +
    +
    +
    + + + +
    +
    +
    + + + +
    +
    +
    + + +
    +
    + + + + + + + + + + + + + +

    Using the APIs#

    + + +

    Friendica offers multiple API endpoints to interface with third-party applications:

    + +

    Usage#

    +

    HTTP Method#

    +

    API endpoints can restrict the HTTP method used to request them. +Using an invalid method results in HTTP error 405 "Method Not Allowed".

    +

    Authentication#

    +

    Friendica supports basic HTTP Auth and OAuth to authenticate the user to the APIs.

    +

    Errors#

    +

    When an error occurs in API call, an HTTP error code is returned, with an error message +Usually:

    +
      +
    • 400 Bad Request: if parameters are missing or items can't be found
    • +
    • 403 Forbidden: if the authenticated user is missing
    • +
    • 405 Method Not Allowed: if API was called with an invalid method, e.g. GET when API require POST
    • +
    • 501 Not Implemented: if the requested API doesn't exist
    • +
    • 500 Internal Server Error: on other error conditions
    • +
    +

    Error body is

    +

    json:

    +
    {
    +    "error": "Specific error message",
    +    "request": "API path requested",
    +    "code": "HTTP error code"
    +}
    +
    +

    xml:

    +
    <status>
    +    <error>Specific error message</error>
    +    <request>API path requested</request>
    +    <code>HTTP error code</code>
    +</status>
    +
    +

    Usage Examples#

    +

    BASH / cURL#

    +
    /usr/bin/curl -u USER:PASS https://YOUR.FRIENDICA.TLD/api/statuses/update.xml -d source="some source id" -d status="the status you want to post"
    +
    +

    Python#

    +

    The RSStoFriendika code can be used as an example of how to use the API with python. +The lines for posting are located at line 21 and following.

    +

    def tweet(server, message, group_allow=None): +url = server + '/api/statuses/update' +urllib2.urlopen(url, urllib.urlencode({'status': message,'group_allow[]':group_allow}, doseq=True))

    +

    There is also a module for python 3 for using the API.

    + + +
    + +
    +
    + + + +
    + + + +
    +
    +
    +
    + + + + + + + + \ No newline at end of file diff --git a/develop/de/spec/api/mastodon/index.html b/develop/de/spec/api/mastodon/index.html new file mode 100644 index 0000000..7317461 --- /dev/null +++ b/develop/de/spec/api/mastodon/index.html @@ -0,0 +1,3683 @@ + + + + + + + + + + + + + + + + + + + + + + Mastodon - Friendica Dokumentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + +
    + + + + +
    + + +
    + +
    + + + + + + + + + +
    +
    + + + +
    +
    +
    + + + + + + +
    +
    +
    + + + +
    +
    +
    + + + +
    +
    +
    + + +
    +
    + + + + + + + + + + + + + +

    Mastodon API#

    +

    Overview#

    +

    Friendica provides the following endpoints defined in the official Mastodon API reference.

    +

    Authentication is the same as described in Using the APIs.

    +

    Clients#

    +

    Supported apps#

    +

    For supported apps please have a look at the FAQ

    +

    Unsupported apps#

    +

    Android#

    +
      +
    • Fedilab Automatically uses the legacy API, see issue: https://framagit.org/tom79/fedilab/-/issues/520
    • +
    • Mammut There are problems with the token request, see issue https://github.com/jamiesanson/Mammut/issues/19
    • +
    +

    iOS#

    +
      +
    • Mast Doesn't accept the entered instance name. Claims that it is invalid (Message is: "Not a valid instance (maybe closed or dead)")
    • +
    • Toot!
    • +
    +

    Entities#

    +

    These endpoints use the Mastodon API entities.

    +

    Implemented endpoints#

    + +

    Currently unimplemented endpoints#

    +

    These endpoints are planned to be implemented somewhere in the future.

    + +

    Dummy endpoints#

    +

    These endpoints are returning empty data to avoid error messages when using third party clients. +They refer to features that don't exist in Friendica yet.

    + +

    Non supportable endpoints#

    +

    These endpoints won't be implemented at the moment. +They refer to features or data that don't exist in Friendica yet.

    + + + +
    + +
    +
    + + + +
    + + + +
    +
    +
    +
    + + + + + + + + \ No newline at end of file diff --git a/develop/de/spec/api/twitter/index.html b/develop/de/spec/api/twitter/index.html new file mode 100644 index 0000000..0499529 --- /dev/null +++ b/develop/de/spec/api/twitter/index.html @@ -0,0 +1,3828 @@ + + + + + + + + + + + + + + + + + + + + + + Twitter - Friendica Dokumentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + +
    + + + + +
    + + +
    + +
    + + + + + + + + + +
    +
    + + + +
    +
    +
    + + + + + + +
    +
    +
    + + + +
    +
    +
    + + + +
    +
    +
    + + +
    +
    + + + + + + + + + + + + + +

    Twitter API#

    +

    Overview#

    +

    Friendica provides the following endpoints defined in the official Twitter API reference.

    +

    Authentication is the same as described in Using the APIs.

    +

    Entities#

    +

    These endpoints use the Friendica API entities.

    +

    Different behaviour#

    +
      +
    • screen_name: The nickname in Friendica is only unique in each network but not for all networks. The users are searched in the following priority: Friendica, StatusNet/GNU Social, Diaspora, pump.io, Twitter. If no contact was found by this way, then the first contact is taken.
    • +
    • include_entities: Default is "false". If set to "true" then the plain text is formatted so that links are having descriptions.
    • +
    +

    Friendica-specific return values#

    +
      +
    • cid: Contact id of the user (important for "contact_allow" and "contact_deny")
    • +
    • network: network of the user
    • +
    +

    Unsupported parameters#

    +
      +
    • cursor
    • +
    • trim_user
    • +
    • contributor_details
    • +
    • place_id
    • +
    • display_coordinates
    • +
    • include_rts: To-Do
    • +
    • include_my_retweet: Retweets in Friendica are implemented in a different way
    • +
    +

    Implemented endpoints#

    + +

    Non-implemented endpoints#

    + + + +
    + +
    +
    + + + +
    + + + +
    +
    +
    +
    + + + + + + + + \ No newline at end of file diff --git a/develop/de/spec/database/db_2fa_app_specific_password/index.html b/develop/de/spec/database/db_2fa_app_specific_password/index.html new file mode 100644 index 0000000..db87269 --- /dev/null +++ b/develop/de/spec/database/db_2fa_app_specific_password/index.html @@ -0,0 +1,3460 @@ + + + + + + + + + + + + + + + + + + + + + + 2fa_app_specific_password - Friendica Dokumentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + +
    + + + + +
    + + +
    + +
    + + + + + + + + + +
    +
    + + + +
    +
    +
    + + + + + + +
    +
    +
    + + + +
    +
    +
    + + + +
    +
    +
    + + +
    +
    + + + + + + + + + + + + + +

    Table 2fa_app_specific_password#

    +

    Two-factor app-specific _password

    +

    Fields#

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    FieldDescriptionTypeNullKeyDefaultExtra
    idPassword ID for revocationmediumint unsignedNOPRINULLauto_increment
    uidUser IDmediumint unsignedNONULL
    descriptionDescription of the usage of the passwordvarchar(255)YESNULL
    hashed_passwordHashed passwordvarchar(255)NONULL
    generatedDatetime the password was generateddatetimeNONULL
    last_usedDatetime the password was last useddatetimeYESNULL
    +

    Indexes#

    + + + + + + + + + + + + + + + + + +
    NameFields
    PRIMARYid
    uid_descriptionuid, description(190)
    +

    Foreign Keys#

    + + + + + + + + + + + + + + + +
    FieldTarget TableTarget Field
    uiduseruid
    +

    Return to database documentation

    + + +
    + +
    +
    + + + +
    + + + +
    +
    +
    +
    + + + + + + + + \ No newline at end of file diff --git a/develop/de/spec/database/db_2fa_recovery_codes/index.html b/develop/de/spec/database/db_2fa_recovery_codes/index.html new file mode 100644 index 0000000..0b012e6 --- /dev/null +++ b/develop/de/spec/database/db_2fa_recovery_codes/index.html @@ -0,0 +1,3438 @@ + + + + + + + + + + + + + + + + + + + + + + 2fa_recovery_codes - Friendica Dokumentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + +
    + + + + +
    + + +
    + +
    + + + + + + + + + +
    +
    + + + +
    +
    +
    + + + + + + +
    +
    +
    + + + +
    +
    +
    + + + +
    +
    +
    + + +
    +
    + + + + + + + + + + + + + +

    Table 2fa_recovery_codes#

    +

    Two-factor authentication recovery codes

    +

    Fields#

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    FieldDescriptionTypeNullKeyDefaultExtra
    uidUser IDmediumint unsignedNOPRINULL
    codeRecovery code stringvarchar(50)NOPRINULL
    generatedDatetime the code was generateddatetimeNONULL
    usedDatetime the code was useddatetimeYESNULL
    +

    Indexes#

    + + + + + + + + + + + + + +
    NameFields
    PRIMARYuid, code
    +

    Foreign Keys#

    + + + + + + + + + + + + + + + +
    FieldTarget TableTarget Field
    uiduseruid
    +

    Return to database documentation

    + + +
    + +
    +
    + + + +
    + + + +
    +
    +
    +
    + + + + + + + + \ No newline at end of file diff --git a/develop/de/spec/database/db_2fa_trusted_browser/index.html b/develop/de/spec/database/db_2fa_trusted_browser/index.html new file mode 100644 index 0000000..9993c37 --- /dev/null +++ b/develop/de/spec/database/db_2fa_trusted_browser/index.html @@ -0,0 +1,3460 @@ + + + + + + + + + + + + + + + + + + + + + + 2fa_trusted_browser - Friendica Dokumentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + +
    + + + + +
    + + +
    + +
    + + + + + + + + + +
    +
    + + + +
    +
    +
    + + + + + + +
    +
    +
    + + + +
    +
    +
    + + + +
    +
    +
    + + +
    +
    + + + + + + + + + + + + + +

    Table 2fa_trusted_browser#

    +

    Two-factor authentication trusted browsers

    +

    Fields#

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    FieldDescriptionTypeNullKeyDefaultExtra
    cookie_hashTrusted cookie hashvarchar(80)NOPRINULL
    uidUser IDmediumint unsignedNONULL
    user_agentUser agent stringtextYESNULL
    trustedWhenever this browser should be trusted or notbooleanNO1
    createdDatetime the trusted browser was recordeddatetimeNONULL
    last_usedDatetime the trusted browser was last useddatetimeYESNULL
    +

    Indexes#

    + + + + + + + + + + + + + + + + + +
    NameFields
    PRIMARYcookie_hash
    uiduid
    +

    Foreign Keys#

    + + + + + + + + + + + + + + + +
    FieldTarget TableTarget Field
    uiduseruid
    +

    Return to database documentation

    + + +
    + +
    +
    + + + +
    + + + +
    +
    +
    +
    + + + + + + + + \ No newline at end of file diff --git a/develop/de/spec/database/db_addon/index.html b/develop/de/spec/database/db_addon/index.html new file mode 100644 index 0000000..d99aeaf --- /dev/null +++ b/develop/de/spec/database/db_addon/index.html @@ -0,0 +1,3442 @@ + + + + + + + + + + + + + + + + + + + + + + addon - Friendica Dokumentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + +
    + + + + +
    + + +
    + +
    + + + + + + + + + +
    +
    + + + +
    +
    +
    + + + + + + +
    +
    +
    + + + +
    +
    +
    + + + +
    +
    +
    + + +
    +
    + + + + + + + + + + + + + +

    Table addon#

    +

    registered addons

    +

    Fields#

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    FieldDescriptionTypeNullKeyDefaultExtra
    idint unsignedNOPRINULLauto_increment
    nameaddon base (file)namevarchar(50)NO
    versioncurrently unusedvarchar(50)NO
    installedcurrently always 1booleanNO0
    hiddencurrently unusedbooleanNO0
    timestampfile timestamp to check for reloadsint unsignedNO0
    plugin_admin1 = has admin config, 0 = has no admin configbooleanNO0
    +

    Indexes#

    + + + + + + + + + + + + + + + + + + + + + +
    NameFields
    PRIMARYid
    installed_nameinstalled, name
    nameUNIQUE, name
    +

    Return to database documentation

    + + +
    + +
    +
    + + + +
    + + + +
    +
    +
    +
    + + + + + + + + \ No newline at end of file diff --git a/develop/de/spec/database/db_apcontact/index.html b/develop/de/spec/database/db_apcontact/index.html new file mode 100644 index 0000000..e377b7f --- /dev/null +++ b/develop/de/spec/database/db_apcontact/index.html @@ -0,0 +1,3714 @@ + + + + + + + + + + + + + + + + + + + + + + apcontact - Friendica Dokumentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + +
    + + + + +
    + + +
    + +
    + + + + + + + + + +
    +
    + + + +
    +
    +
    + + + + + + +
    +
    +
    + + + +
    +
    +
    + + + +
    +
    +
    + + +
    +
    + + + + + + + + + + + + + +

    Table apcontact#

    +

    ActivityPub compatible contacts - used in the ActivityPub implementation

    +

    Fields#

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    FieldDescriptionTypeNullKeyDefaultExtra
    urlURL of the contactvarbinary(255)NOPRINULL
    uri-idId of the item-uri table entry that contains the apcontact urlint unsignedYESNULL
    uuidvarchar(255)YESNULL
    typevarchar(20)NONULL
    followingvarchar(255)YESNULL
    followersvarchar(255)YESNULL
    inboxvarchar(255)NONULL
    outboxvarchar(255)YESNULL
    sharedinboxvarchar(255)YESNULL
    featuredAddress for the collection of featured postsvarchar(255)YESNULL
    featured-tagsAddress for the collection of featured tagsvarchar(255)YESNULL
    manually-approvebooleanYESNULL
    discoverableMastodon extension: true if profile is published in their directorybooleanYESNULL
    nickvarchar(255)NO
    namevarchar(255)YESNULL
    abouttextYESNULL
    xmppXMPP addressvarchar(255)YESNULL
    matrixMatrix addressvarchar(255)YESNULL
    photovarchar(255)YESNULL
    headerHeader picturevarchar(255)YESNULL
    addrvarchar(255)YESNULL
    aliasvarchar(255)YESNULL
    pubkeytextYESNULL
    subscribevarchar(255)YESNULL
    baseurlbaseurl of the ap contactvarchar(255)YESNULL
    gsidGlobal Server IDint unsignedYESNULL
    generatorName of the contact's systemvarchar(255)YESNULL
    following_countNumber of following contactsint unsignedYES0
    followers_countNumber of followersint unsignedYES0
    statuses_countNumber of postsint unsignedYES0
    updateddatetimeNO0001-01-01 00:00:00
    +

    Indexes#

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    NameFields
    PRIMARYurl
    addraddr(32)
    aliasalias(190)
    followersfollowers(190)
    baseurlbaseurl(190)
    sharedinboxsharedinbox(190)
    gsidgsid
    uri-idUNIQUE, uri-id
    +

    Foreign Keys#

    + + + + + + + + + + + + + + + + + + + + +
    FieldTarget TableTarget Field
    uri-iditem-uriid
    gsidgserverid
    +

    Return to database documentation

    + + +
    + +
    +
    + + + +
    + + + +
    +
    +
    +
    + + + + + + + + \ No newline at end of file diff --git a/develop/de/spec/database/db_application-marker/index.html b/develop/de/spec/database/db_application-marker/index.html new file mode 100644 index 0000000..d0c172f --- /dev/null +++ b/develop/de/spec/database/db_application-marker/index.html @@ -0,0 +1,3465 @@ + + + + + + + + + + + + + + + + + + + + + + application-marker - Friendica Dokumentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + +
    + + + + +
    + + +
    + +
    + + + + + + + + + +
    +
    + + + +
    +
    +
    + + + + + + +
    +
    +
    + + + +
    +
    +
    + + + +
    +
    +
    + + +
    +
    + + + + + + + + + + + + + +

    Table application-marker#

    +

    Timeline marker

    +

    Fields#

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    FieldDescriptionTypeNullKeyDefaultExtra
    application-idint unsignedNOPRINULL
    uidOwner User idmediumint unsignedNOPRINULL
    timelineMarker (home, notifications)varchar(64)NOPRINULL
    last_read_idMarker id for the timelinevarchar(255)YESNULL
    versionVersion numbersmallint unsignedYESNULL
    updated_atcreation timedatetimeYESNULL
    +

    Indexes#

    + + + + + + + + + + + + + + + + + +
    NameFields
    PRIMARYapplication-id, uid, timeline
    uid_iduid
    +

    Foreign Keys#

    + + + + + + + + + + + + + + + + + + + + +
    FieldTarget TableTarget Field
    application-idapplicationid
    uiduseruid
    +

    Return to database documentation

    + + +
    + +
    +
    + + + +
    + + + +
    +
    +
    +
    + + + + + + + + \ No newline at end of file diff --git a/develop/de/spec/database/db_application-token/index.html b/develop/de/spec/database/db_application-token/index.html new file mode 100644 index 0000000..5fa4538 --- /dev/null +++ b/develop/de/spec/database/db_application-token/index.html @@ -0,0 +1,3501 @@ + + + + + + + + + + + + + + + + + + + + + + application-token - Friendica Dokumentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + +
    + + + + +
    + + +
    + +
    + + + + + + + + + +
    +
    + + + +
    +
    +
    + + + + + + +
    +
    +
    + + + +
    +
    +
    + + + +
    +
    +
    + + +
    +
    + + + + + + + + + + + + + +

    Table application-token#

    +

    OAuth user token

    +

    Fields#

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    FieldDescriptionTypeNullKeyDefaultExtra
    application-idint unsignedNOPRINULL
    uidOwner User idmediumint unsignedNOPRINULL
    codevarchar(64)NONULL
    access_tokenvarchar(64)NONULL
    created_atcreation timedatetimeNONULL
    scopesvarchar(255)YESNULL
    readRead scopebooleanYESNULL
    writeWrite scopebooleanYESNULL
    followFollow scopebooleanYESNULL
    pushPush scopebooleanYESNULL
    +

    Indexes#

    + + + + + + + + + + + + + + + + + +
    NameFields
    PRIMARYapplication-id, uid
    uid_iduid, application-id
    +

    Foreign Keys#

    + + + + + + + + + + + + + + + + + + + + +
    FieldTarget TableTarget Field
    application-idapplicationid
    uiduseruid
    +

    Return to database documentation

    + + +
    + +
    +
    + + + +
    + + + +
    +
    +
    +
    + + + + + + + + \ No newline at end of file diff --git a/develop/de/spec/database/db_application/index.html b/develop/de/spec/database/db_application/index.html new file mode 100644 index 0000000..9b9d089 --- /dev/null +++ b/develop/de/spec/database/db_application/index.html @@ -0,0 +1,3474 @@ + + + + + + + + + + + + + + + + + + + + + + application - Friendica Dokumentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + +
    + + + + +
    + + +
    + +
    + + + + + + + + + +
    +
    + + + +
    +
    +
    + + + + + + +
    +
    +
    + + + +
    +
    +
    + + + +
    +
    +
    + + +
    +
    + + + + + + + + + + + + + +

    Table application#

    +

    OAuth application

    +

    Fields#

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    FieldDescriptionTypeNullKeyDefaultExtra
    idgenerated indexint unsignedNOPRINULLauto_increment
    client_idvarchar(64)NONULL
    client_secretvarchar(64)NONULL
    namevarchar(255)NONULL
    redirect_urivarchar(255)NONULL
    websitevarchar(255)YESNULL
    scopesvarchar(255)YESNULL
    readRead scopebooleanYESNULL
    writeWrite scopebooleanYESNULL
    followFollow scopebooleanYESNULL
    pushPush scopebooleanYESNULL
    +

    Indexes#

    + + + + + + + + + + + + + + + + + +
    NameFields
    PRIMARYid
    client_idUNIQUE, client_id
    +

    Return to database documentation

    + + +
    + +
    +
    + + + +
    + + + +
    +
    +
    +
    + + + + + + + + \ No newline at end of file diff --git a/develop/de/spec/database/db_attach/index.html b/develop/de/spec/database/db_attach/index.html new file mode 100644 index 0000000..10abacc --- /dev/null +++ b/develop/de/spec/database/db_attach/index.html @@ -0,0 +1,3541 @@ + + + + + + + + + + + + + + + + + + + + + + attach - Friendica Dokumentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + +
    + + + + +
    + + +
    + +
    + + + + + + + + + +
    +
    + + + +
    +
    +
    + + + + + + +
    +
    +
    + + + +
    +
    +
    + + + +
    +
    +
    + + +
    +
    + + + + + + + + + + + + + +

    Table attach#

    +

    file attachments

    +

    Fields#

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    FieldDescriptionTypeNullKeyDefaultExtra
    idgenerated indexint unsignedNOPRINULLauto_increment
    uidOwner User idmediumint unsignedNO0
    hashhashvarchar(64)NO
    filenamefilename of originalvarchar(255)NO
    filetypemimetypevarchar(64)NO
    filesizesize in bytesint unsignedNO0
    datafile datalongblobNONULL
    createdcreation timedatetimeNO0001-01-01 00:00:00
    editedlast edit timedatetimeNO0001-01-01 00:00:00
    allow_cidAccess Control - list of allowed contact.id '<19><78>mediumtextYESNULL
    allow_gidAccess Control - list of allowed groupsmediumtextYESNULL
    deny_cidAccess Control - list of denied contact.idmediumtextYESNULL
    deny_gidAccess Control - list of denied groupsmediumtextYESNULL
    backend-classStorage backend classtinytextYESNULL
    backend-refStorage backend data referencetextYESNULL
    +

    Indexes#

    + + + + + + + + + + + + + + + + + +
    NameFields
    PRIMARYid
    uiduid
    +

    Foreign Keys#

    + + + + + + + + + + + + + + + +
    FieldTarget TableTarget Field
    uiduseruid
    +

    Return to database documentation

    + + +
    + +
    +
    + + + +
    + + + +
    +
    +
    +
    + + + + + + + + \ No newline at end of file diff --git a/develop/de/spec/database/db_cache/index.html b/develop/de/spec/database/db_cache/index.html new file mode 100644 index 0000000..bfc7f6f --- /dev/null +++ b/develop/de/spec/database/db_cache/index.html @@ -0,0 +1,3411 @@ + + + + + + + + + + + + + + + + + + + + + + cache - Friendica Dokumentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + +
    + + + + +
    + + +
    + +
    + + + + + + + + + +
    +
    + + + +
    +
    +
    + + + + + + +
    +
    +
    + + + +
    +
    +
    + + + +
    +
    +
    + + +
    +
    + + + + + + + + + + + + + +

    Table cache#

    +

    Stores temporary data

    +

    Fields#

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    FieldDescriptionTypeNullKeyDefaultExtra
    kcache keyvarbinary(255)NOPRINULL
    vcached serialized valuemediumtextYESNULL
    expiresdatetime of cache expirationdatetimeNO0001-01-01 00:00:00
    updateddatetime of cache insertiondatetimeNO0001-01-01 00:00:00
    +

    Indexes#

    + + + + + + + + + + + + + + + + + +
    NameFields
    PRIMARYk
    k_expiresk, expires
    +

    Return to database documentation

    + + +
    + +
    +
    + + + +
    + + + +
    +
    +
    +
    + + + + + + + + \ No newline at end of file diff --git a/develop/de/spec/database/db_config/index.html b/develop/de/spec/database/db_config/index.html new file mode 100644 index 0000000..c6e10c4 --- /dev/null +++ b/develop/de/spec/database/db_config/index.html @@ -0,0 +1,3411 @@ + + + + + + + + + + + + + + + + + + + + + + config - Friendica Dokumentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + +
    + + + + +
    + + +
    + +
    + + + + + + + + + +
    +
    + + + +
    +
    +
    + + + + + + +
    +
    +
    + + + +
    +
    +
    + + + +
    +
    +
    + + +
    +
    + + + + + + + + + + + + + +

    Table config#

    +

    main configuration storage

    +

    Fields#

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    FieldDescriptionTypeNullKeyDefaultExtra
    idint unsignedNOPRINULLauto_increment
    catvarbinary(50)NO
    kvarbinary(50)NO
    vmediumtextYESNULL
    +

    Indexes#

    + + + + + + + + + + + + + + + + + +
    NameFields
    PRIMARYid
    cat_kUNIQUE, cat, k
    +

    Return to database documentation

    + + +
    + +
    +
    + + + +
    + + + +
    +
    +
    +
    + + + + + + + + \ No newline at end of file diff --git a/develop/de/spec/database/db_contact-relation/index.html b/develop/de/spec/database/db_contact-relation/index.html new file mode 100644 index 0000000..b1d42b2 --- /dev/null +++ b/develop/de/spec/database/db_contact-relation/index.html @@ -0,0 +1,3456 @@ + + + + + + + + + + + + + + + + + + + + + + contact-relation - Friendica Dokumentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + +
    + + + + +
    + + +
    + +
    + + + + + + + + + +
    +
    + + + +
    +
    +
    + + + + + + +
    +
    +
    + + + +
    +
    +
    + + + +
    +
    +
    + + +
    +
    + + + + + + + + + + + + + +

    Table contact-relation#

    +

    Contact relations

    +

    Fields#

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    FieldDescriptionTypeNullKeyDefaultExtra
    cidcontact the related contact had interacted withint unsignedNOPRI0
    relation-cidrelated contact who had interacted with the contactint unsignedNOPRI0
    last-interactionDate of the last interactiondatetimeNO0001-01-01 00:00:00
    follow-updatedDate of the last update of the contact relationshipdatetimeNO0001-01-01 00:00:00
    followsbooleanNO0
    +

    Indexes#

    + + + + + + + + + + + + + + + + + +
    NameFields
    PRIMARYcid, relation-cid
    relation-cidrelation-cid
    +

    Foreign Keys#

    + + + + + + + + + + + + + + + + + + + + +
    FieldTarget TableTarget Field
    cidcontactid
    relation-cidcontactid
    +

    Return to database documentation

    + + +
    + +
    +
    + + + +
    + + + +
    +
    +
    +
    + + + + + + + + \ No newline at end of file diff --git a/develop/de/spec/database/db_contact/index.html b/develop/de/spec/database/db_contact/index.html new file mode 100644 index 0000000..0071056 --- /dev/null +++ b/develop/de/spec/database/db_contact/index.html @@ -0,0 +1,4243 @@ + + + + + + + + + + + + + + + + + + + + + + contact - Friendica Dokumentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + +
    + + + + +
    + + +
    + +
    + + + + + + + + + +
    +
    + + + +
    +
    +
    + + + + + + +
    +
    +
    + + + +
    +
    +
    + + + +
    +
    +
    + + +
    +
    + + + + + + + + + + + + + +

    Table contact#

    +

    contact table

    +

    Fields#

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    FieldDescriptionTypeNullKeyDefaultExtra
    idsequential IDint unsignedNOPRINULLauto_increment
    uidOwner User idmediumint unsignedNO0
    createddatetimeNO0001-01-01 00:00:00
    updatedDate of last contact updatedatetimeYES0001-01-01 00:00:00
    networkNetwork of the contactchar(4)NO
    nameName that this contact is known byvarchar(255)NO
    nickNick- and user name of the contactvarchar(255)NO
    locationvarchar(255)YES
    abouttextYESNULL
    keywordspublic keywords (interests) of the contacttextYESNULL
    xmppXMPP addressvarchar(255)NO
    matrixMatrix addressvarchar(255)NO
    avatarvarchar(255)NO
    headerHeader picturevarchar(255)YESNULL
    urlvarchar(255)NO
    nurlvarchar(255)NO
    uri-idId of the item-uri table entry that contains the contact urlint unsignedYESNULL
    addrvarchar(255)NO
    aliasvarchar(255)NO
    pubkeyRSA public key 4096 bittextYESNULL
    prvkeyRSA private key 4096 bittextYESNULL
    batchvarchar(255)NO
    notifyvarchar(255)YESNULL
    pollvarchar(255)YESNULL
    subscribevarchar(255)YESNULL
    last-updateDate of the last try to update the contact infodatetimeNO0001-01-01 00:00:00
    success_updateDate of the last successful contact updatedatetimeNO0001-01-01 00:00:00
    failure_updateDate of the last failed updatedatetimeNO0001-01-01 00:00:00
    failedConnection failedbooleanYESNULL
    term-datedatetimeNO0001-01-01 00:00:00
    last-itemdate of the last postdatetimeNO0001-01-01 00:00:00
    last-discoverydate of the last follower discoverydatetimeNO0001-01-01 00:00:00
    blockedNode-wide block statusbooleanNO1
    block_reasonNode-wide block reasontextYESNULL
    readonlyposts of the contact are readonlybooleanNO0
    contact-typePerson, organisation, news, community, relaytinyintNO0
    manually-approveContact requests have to be approved manuallybooleanYESNULL
    archivebooleanNO0
    unsearchableContact prefers to not be searchablebooleanNO0
    sensitiveContact posts sensitive contentbooleanNO0
    baseurlbaseurl of the contactvarchar(255)YES
    gsidGlobal Server IDint unsignedYESNULL
    bddateNO0001-01-01
    reasontextYESNULL
    self1 if the contact is the user him/her selfbooleanNO0
    remote_selfbooleanNO0
    relThe kind of the relation between the user and the contacttinyint unsignedNO0
    protocolProtocol of the contactchar(4)NO
    subhubbooleanNO0
    hub-verifyvarchar(255)NO
    ratingAutomatically detected feed poll frequencytinyintNO0
    priorityFeed poll prioritytinyint unsignedNO0
    attagvarchar(255)NO
    hiddenbooleanNO0
    pendingContact request is pendingbooleanNO1
    deletedContact has been deletedbooleanNO0
    infomediumtextYESNULL
    notify_new_postsbooleanNO0
    fetch_further_informationtinyint unsignedNO0
    ffi_keyword_denylisttextYESNULL
    photoLink to the profile photo of the contactvarchar(255)YES
    thumbLink to the profile photo (thumb size)varchar(255)YES
    microLink to the profile photo (micro size)varchar(255)YES
    name-datedatetimeNO0001-01-01 00:00:00
    uri-datedatetimeNO0001-01-01 00:00:00
    avatar-datedatetimeNO0001-01-01 00:00:00
    requestvarchar(255)YESNULL
    confirmvarchar(255)YESNULL
    pocovarchar(255)YESNULL
    writablebooleanNO0
    forumcontact is a forum. Deprecated, use 'contact-type' = 'community' and 'manually-approve' = false insteadbooleanNO0
    prvcontact is a private group. Deprecated, use 'contact-type' = 'community' and 'manually-approve' = true insteadbooleanNO0
    bdyearvarchar(4)NO
    site-pubkeyDeprecatedtextYESNULL
    genderDeprecatedvarchar(32)NO
    duplexDeprecatedbooleanNO0
    issued-idDeprecatedvarchar(255)NO
    dfrn-idDeprecatedvarchar(255)NO
    aes_allowDeprecatedbooleanNO0
    ret-aesDeprecatedbooleanNO0
    usehubDeprecatedbooleanNO0
    closenessDeprecatedtinyint unsignedNO99
    profile-idDeprecatedint unsignedYESNULL
    +

    Indexes#

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    NameFields
    PRIMARYid
    uid_nameuid, name(190)
    self_uidself, uid
    alias_uidalias(128), uid
    pending_uidpending, uid
    blocked_uidblocked, uid
    uid_rel_network_polluid, rel, network, poll(64), archive
    uid_network_batchuid, network, batch(64)
    batch_contact-typebatch(64), contact-type
    addr_uidaddr(128), uid
    nurl_uidnurl(128), uid
    nick_uidnick(128), uid
    attag_uidattag(96), uid
    network_uid_lastupdatenetwork, uid, last-update
    uid_network_self_lastupdateuid, network, self, last-update
    uid_lastitemuid, last-item
    baseurlbaseurl(64)
    uid_contact-typeuid, contact-type
    uid_self_contact-typeuid, self, contact-type
    self_network_uidself, network, uid
    gsidgsid
    uri-iduri-id
    +

    Foreign Keys#

    + + + + + + + + + + + + + + + + + + + + + + + + + +
    FieldTarget TableTarget Field
    uiduseruid
    uri-iditem-uriid
    gsidgserverid
    +

    Return to database documentation

    + + +
    + +
    +
    + + + +
    + + + +
    +
    +
    +
    + + + + + + + + \ No newline at end of file diff --git a/develop/de/spec/database/db_conv/index.html b/develop/de/spec/database/db_conv/index.html new file mode 100644 index 0000000..c2d6cfe --- /dev/null +++ b/develop/de/spec/database/db_conv/index.html @@ -0,0 +1,3478 @@ + + + + + + + + + + + + + + + + + + + + + + conv - Friendica Dokumentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + +
    + + + + +
    + + +
    + +
    + + + + + + + + + +
    +
    + + + +
    +
    +
    + + + + + + +
    +
    +
    + + + +
    +
    +
    + + + +
    +
    +
    + + +
    +
    + + + + + + + + + + + + + +

    Table conv#

    +

    private messages

    +

    Fields#

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    FieldDescriptionTypeNullKeyDefaultExtra
    idsequential IDint unsignedNOPRINULLauto_increment
    guidA unique identifier for this conversationvarchar(255)NO
    recipssender_handle;recipient_handletextYESNULL
    uidOwner User idmediumint unsignedNO0
    creatorhandle of creatorvarchar(255)NO
    createdcreation timestampdatetimeNO0001-01-01 00:00:00
    updatededited timestampdatetimeNO0001-01-01 00:00:00
    subjectsubject of initial messagetextYESNULL
    +

    Indexes#

    + + + + + + + + + + + + + + + + + +
    NameFields
    PRIMARYid
    uiduid
    +

    Foreign Keys#

    + + + + + + + + + + + + + + + +
    FieldTarget TableTarget Field
    uiduseruid
    +

    Return to database documentation

    + + +
    + +
    +
    + + + +
    + + + +
    +
    +
    +
    + + + + + + + + \ No newline at end of file diff --git a/develop/de/spec/database/db_conversation/index.html b/develop/de/spec/database/db_conversation/index.html new file mode 100644 index 0000000..ccb215f --- /dev/null +++ b/develop/de/spec/database/db_conversation/index.html @@ -0,0 +1,3451 @@ + + + + + + + + + + + + + + + + + + + + + + conversation - Friendica Dokumentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + +
    + + + + +
    + + +
    + +
    + + + + + + + + + +
    +
    + + + +
    +
    +
    + + + + + + +
    +
    +
    + + + +
    +
    +
    + + + +
    +
    +
    + + +
    +
    + + + + + + + + + + + + + +

    Table conversation#

    +

    Raw data and structure information for messages

    +

    Fields#

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    FieldDescriptionTypeNullKeyDefaultExtra
    item-uriOriginal URI of the item - unrelated to the table with the same namevarbinary(255)NOPRINULL
    reply-to-uriURI to which this item is a replyvarbinary(255)NO
    conversation-uriGNU Social conversation URIvarbinary(255)NO
    conversation-hrefGNU Social conversation linkvarbinary(255)NO
    protocolThe protocol of the itemtinyint unsignedNO255
    directionHow the message arrived here: 1=push, 2=pulltinyint unsignedNO0
    sourceOriginal sourcemediumtextYESNULL
    receivedReceiving datedatetimeNO0001-01-01 00:00:00
    +

    Indexes#

    + + + + + + + + + + + + + + + + + + + + + +
    NameFields
    PRIMARYitem-uri
    conversation-uriconversation-uri
    receivedreceived
    +

    Return to database documentation

    + + +
    + +
    +
    + + + +
    + + + +
    +
    +
    +
    + + + + + + + + \ No newline at end of file diff --git a/develop/de/spec/database/db_delayed-post/index.html b/develop/de/spec/database/db_delayed-post/index.html new file mode 100644 index 0000000..83ab12b --- /dev/null +++ b/develop/de/spec/database/db_delayed-post/index.html @@ -0,0 +1,3460 @@ + + + + + + + + + + + + + + + + + + + + + + delayed-post - Friendica Dokumentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + +
    + + + + +
    + + +
    + +
    + + + + + + + + + +
    +
    + + + +
    +
    +
    + + + + + + +
    +
    +
    + + + +
    +
    +
    + + + +
    +
    +
    + + +
    +
    + + + + + + + + + + + + + +

    Table delayed-post#

    +

    Posts that are about to be distributed at a later time

    +

    Fields#

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    FieldDescriptionTypeNullKeyDefaultExtra
    idint unsignedNOPRINULLauto_increment
    uriURI of the post that will be distributed latervarchar(255)YESNULL
    uidOwner User idmediumint unsignedYESNULL
    delayeddelay timedatetimeYESNULL
    widWorkerqueue idint unsignedYESNULL
    +

    Indexes#

    + + + + + + + + + + + + + + + + + + + + + +
    NameFields
    PRIMARYid
    uid_uriUNIQUE, uid, uri(190)
    widwid
    +

    Foreign Keys#

    + + + + + + + + + + + + + + + + + + + + +
    FieldTarget TableTarget Field
    uiduseruid
    widworkerqueueid
    +

    Return to database documentation

    + + +
    + +
    +
    + + + +
    + + + +
    +
    +
    +
    + + + + + + + + \ No newline at end of file diff --git a/develop/de/spec/database/db_diaspora-interaction/index.html b/develop/de/spec/database/db_diaspora-interaction/index.html new file mode 100644 index 0000000..47baff4 --- /dev/null +++ b/develop/de/spec/database/db_diaspora-interaction/index.html @@ -0,0 +1,3420 @@ + + + + + + + + + + + + + + + + + + + + + + diaspora-interaction - Friendica Dokumentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + +
    + + + + +
    + + +
    + +
    + + + + + + + + + +
    +
    + + + +
    +
    +
    + + + + + + +
    +
    +
    + + + +
    +
    +
    + + + +
    +
    +
    + + +
    +
    + + + + + + + + + + + + + +

    Table diaspora-interaction#

    +

    Signed Diaspora Interaction

    +

    Fields#

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    FieldDescriptionTypeNullKeyDefaultExtra
    uri-idId of the item-uri table entry that contains the item uriint unsignedNOPRINULL
    interactionThe Diaspora interactionmediumtextYESNULL
    +

    Indexes#

    + + + + + + + + + + + + + +
    NameFields
    PRIMARYuri-id
    +

    Foreign Keys#

    + + + + + + + + + + + + + + + +
    FieldTarget TableTarget Field
    uri-iditem-uriid
    +

    Return to database documentation

    + + +
    + +
    +
    + + + +
    + + + +
    +
    +
    +
    + + + + + + + + \ No newline at end of file diff --git a/develop/de/spec/database/db_endpoint/index.html b/develop/de/spec/database/db_endpoint/index.html new file mode 100644 index 0000000..5bdfb7a --- /dev/null +++ b/develop/de/spec/database/db_endpoint/index.html @@ -0,0 +1,3433 @@ + + + + + + + + + + + + + + + + + + + + + + endpoint - Friendica Dokumentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + +
    + + + + +
    + + +
    + +
    + + + + + + + + + +
    +
    + + + +
    +
    +
    + + + + + + +
    +
    +
    + + + +
    +
    +
    + + + +
    +
    +
    + + +
    +
    + + + + + + + + + + + + + +

    Table endpoint#

    +

    ActivityPub endpoints - used in the ActivityPub implementation

    +

    Fields#

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    FieldDescriptionTypeNullKeyDefaultExtra
    urlURL of the contactvarbinary(255)NOPRINULL
    typevarchar(20)NONULL
    owner-uri-idId of the item-uri table entry that contains the apcontact urlint unsignedYESNULL
    +

    Indexes#

    + + + + + + + + + + + + + + + + + +
    NameFields
    PRIMARYurl
    owner-uri-id_typeUNIQUE, owner-uri-id, type
    +

    Foreign Keys#

    + + + + + + + + + + + + + + + +
    FieldTarget TableTarget Field
    owner-uri-iditem-uriid
    +

    Return to database documentation

    + + +
    + +
    +
    + + + +
    + + + +
    +
    +
    +
    + + + + + + + + \ No newline at end of file diff --git a/develop/de/spec/database/db_event/index.html b/develop/de/spec/database/db_event/index.html new file mode 100644 index 0000000..74f34b4 --- /dev/null +++ b/develop/de/spec/database/db_event/index.html @@ -0,0 +1,3604 @@ + + + + + + + + + + + + + + + + + + + + + + event - Friendica Dokumentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + +
    + + + + +
    + + +
    + +
    + + + + + + + + + +
    +
    + + + +
    +
    +
    + + + + + + +
    +
    +
    + + + +
    +
    +
    + + + +
    +
    +
    + + +
    +
    + + + + + + + + + + + + + +

    Table event#

    +

    Events

    +

    Fields#

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    FieldDescriptionTypeNullKeyDefaultExtra
    idsequential IDint unsignedNOPRINULLauto_increment
    guidvarchar(255)NO
    uidOwner User idmediumint unsignedNO0
    cidcontact_id (ID of the contact in contact table)int unsignedNO0
    urivarchar(255)NO
    uri-idId of the item-uri table entry that contains the event uriint unsignedYESNULL
    createdcreation timedatetimeNO0001-01-01 00:00:00
    editedlast edit timedatetimeNO0001-01-01 00:00:00
    startevent start timedatetimeNO0001-01-01 00:00:00
    finishevent end timedatetimeNO0001-01-01 00:00:00
    summaryshort description or title of the eventtextYESNULL
    descevent descriptiontextYESNULL
    locationevent locationtextYESNULL
    typeevent or birthdayvarchar(20)NO
    nofinishif event does have no end this is 1booleanNO0
    ignore0 or 1booleanNO0
    allow_cidAccess Control - list of allowed contact.id '<19><78>'mediumtextYESNULL
    allow_gidAccess Control - list of allowed groupsmediumtextYESNULL
    deny_cidAccess Control - list of denied contact.idmediumtextYESNULL
    deny_gidAccess Control - list of denied groupsmediumtextYESNULL
    +

    Indexes#

    + + + + + + + + + + + + + + + + + + + + + + + + + +
    NameFields
    PRIMARYid
    uid_startuid, start
    cidcid
    uri-iduri-id
    +

    Foreign Keys#

    + + + + + + + + + + + + + + + + + + + + + + + + + +
    FieldTarget TableTarget Field
    uiduseruid
    cidcontactid
    uri-iditem-uriid
    +

    Return to database documentation

    + + +
    + +
    +
    + + + +
    + + + +
    +
    +
    +
    + + + + + + + + \ No newline at end of file diff --git a/develop/de/spec/database/db_fcontact/index.html b/develop/de/spec/database/db_fcontact/index.html new file mode 100644 index 0000000..1c462a6 --- /dev/null +++ b/develop/de/spec/database/db_fcontact/index.html @@ -0,0 +1,3603 @@ + + + + + + + + + + + + + + + + + + + + + + fcontact - Friendica Dokumentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + +
    + + + + +
    + + +
    + +
    + + + + + + + + + +
    +
    + + + +
    +
    +
    + + + + + + +
    +
    +
    + + + +
    +
    +
    + + + +
    +
    +
    + + +
    +
    + + + + + + + + + + + + + +

    Table fcontact#

    +

    Diaspora compatible contacts - used in the Diaspora implementation

    +

    Fields#

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    FieldDescriptionTypeNullKeyDefaultExtra
    idsequential IDint unsignedNOPRINULLauto_increment
    guidunique idvarchar(255)NO
    urlvarchar(255)NO
    uri-idId of the item-uri table entry that contains the fcontact urlint unsignedYESNULL
    namevarchar(255)NO
    photovarchar(255)NO
    requestvarchar(255)NO
    nickvarchar(255)NO
    addrvarchar(255)NO
    batchvarchar(255)NO
    notifyvarchar(255)NO
    pollvarchar(255)NO
    confirmvarchar(255)NO
    prioritytinyint unsignedNO0
    networkchar(4)NO
    aliasvarchar(255)NO
    pubkeytextYESNULL
    updateddatetimeNO0001-01-01 00:00:00
    interacting_countNumber of contacts this contact interactes withint unsignedYES0
    interacted_countNumber of contacts that interacted with this contactint unsignedYES0
    post_countNumber of posts and commentsint unsignedYES0
    +

    Indexes#

    + + + + + + + + + + + + + + + + + + + + + + + + + +
    NameFields
    PRIMARYid
    addraddr(32)
    urlUNIQUE, url(190)
    uri-idUNIQUE, uri-id
    +

    Foreign Keys#

    + + + + + + + + + + + + + + + +
    FieldTarget TableTarget Field
    uri-iditem-uriid
    +

    Return to database documentation

    + + +
    + +
    +
    + + + +
    + + + +
    +
    +
    +
    + + + + + + + + \ No newline at end of file diff --git a/develop/de/spec/database/db_fsuggest/index.html b/develop/de/spec/database/db_fsuggest/index.html new file mode 100644 index 0000000..d6238b8 --- /dev/null +++ b/develop/de/spec/database/db_fsuggest/index.html @@ -0,0 +1,3496 @@ + + + + + + + + + + + + + + + + + + + + + + fsuggest - Friendica Dokumentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + +
    + + + + +
    + + +
    + +
    + + + + + + + + + +
    +
    + + + +
    +
    +
    + + + + + + +
    +
    +
    + + + +
    +
    +
    + + + +
    +
    +
    + + +
    +
    + + + + + + + + + + + + + +

    Table fsuggest#

    +

    friend suggestion stuff

    +

    Fields#

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    FieldDescriptionTypeNullKeyDefaultExtra
    idint unsignedNOPRINULLauto_increment
    uidUser idmediumint unsignedNO0
    cidint unsignedNO0
    namevarchar(255)NO
    urlvarchar(255)NO
    requestvarchar(255)NO
    photovarchar(255)NO
    notetextYESNULL
    createddatetimeNO0001-01-01 00:00:00
    +

    Indexes#

    + + + + + + + + + + + + + + + + + + + + + +
    NameFields
    PRIMARYid
    cidcid
    uiduid
    +

    Foreign Keys#

    + + + + + + + + + + + + + + + + + + + + +
    FieldTarget TableTarget Field
    uiduseruid
    cidcontactid
    +

    Return to database documentation

    + + +
    + +
    +
    + + + +
    + + + +
    +
    +
    +
    + + + + + + + + \ No newline at end of file diff --git a/develop/de/spec/database/db_group/index.html b/develop/de/spec/database/db_group/index.html new file mode 100644 index 0000000..04845e3 --- /dev/null +++ b/develop/de/spec/database/db_group/index.html @@ -0,0 +1,3469 @@ + + + + + + + + + + + + + + + + + + + + + + group - Friendica Dokumentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + +
    + + + + +
    + + +
    + +
    + + + + + + + + + +
    +
    + + + +
    +
    +
    + + + + + + +
    +
    +
    + + + +
    +
    +
    + + + +
    +
    +
    + + +
    +
    + + + + + + + + + + + + + +

    Table group#

    +

    privacy groups, group info

    +

    Fields#

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    FieldDescriptionTypeNullKeyDefaultExtra
    idsequential IDint unsignedNOPRINULLauto_increment
    uidOwner User idmediumint unsignedNO0
    visible1 indicates the member list is not privatebooleanNO0
    deleted1 indicates the group has been deletedbooleanNO0
    cidContact id of forum. When this field is filled then the members are synced automatically.int unsignedYESNULL
    namehuman readable name of groupvarchar(255)NO
    +

    Indexes#

    + + + + + + + + + + + + + + + + + + + + + +
    NameFields
    PRIMARYid
    uiduid
    cidcid
    +

    Foreign Keys#

    + + + + + + + + + + + + + + + + + + + + +
    FieldTarget TableTarget Field
    uiduseruid
    cidcontactid
    +

    Return to database documentation

    + + +
    + +
    +
    + + + +
    + + + +
    +
    +
    +
    + + + + + + + + \ No newline at end of file diff --git a/develop/de/spec/database/db_group_member/index.html b/develop/de/spec/database/db_group_member/index.html new file mode 100644 index 0000000..4508273 --- /dev/null +++ b/develop/de/spec/database/db_group_member/index.html @@ -0,0 +1,3442 @@ + + + + + + + + + + + + + + + + + + + + + + group_member - Friendica Dokumentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + +
    + + + + +
    + + +
    + +
    + + + + + + + + + +
    +
    + + + +
    +
    +
    + + + + + + +
    +
    +
    + + + +
    +
    +
    + + + +
    +
    +
    + + +
    +
    + + + + + + + + + + + + + +

    Table group_member#

    +

    privacy groups, member info

    +

    Fields#

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    FieldDescriptionTypeNullKeyDefaultExtra
    idsequential IDint unsignedNOPRINULLauto_increment
    gidgroups.id of the associated groupint unsignedNO0
    contact-idcontact.id of the member assigned to the associated groupint unsignedNO0
    +

    Indexes#

    + + + + + + + + + + + + + + + + + + + + + +
    NameFields
    PRIMARYid
    contactidcontact-id
    gid_contactidUNIQUE, gid, contact-id
    +

    Foreign Keys#

    + + + + + + + + + + + + + + + + + + + + +
    FieldTarget TableTarget Field
    gidgroupid
    contact-idcontactid
    +

    Return to database documentation

    + + +
    + +
    +
    + + + +
    + + + +
    +
    +
    +
    + + + + + + + + \ No newline at end of file diff --git a/develop/de/spec/database/db_gserver-tag/index.html b/develop/de/spec/database/db_gserver-tag/index.html new file mode 100644 index 0000000..b7c10f9 --- /dev/null +++ b/develop/de/spec/database/db_gserver-tag/index.html @@ -0,0 +1,3424 @@ + + + + + + + + + + + + + + + + + + + + + + gserver-tag - Friendica Dokumentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + +
    + + + + +
    + + +
    + +
    + + + + + + + + + +
    +
    + + + +
    +
    +
    + + + + + + +
    +
    +
    + + + +
    +
    +
    + + + +
    +
    +
    + + +
    +
    + + + + + + + + + + + + + +

    Table gserver-tag#

    +

    Tags that the server has subscribed

    +

    Fields#

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    FieldDescriptionTypeNullKeyDefaultExtra
    gserver-idThe id of the gserverint unsignedNOPRI0
    tagTag that the server has subscribedvarchar(100)NOPRI
    +

    Indexes#

    + + + + + + + + + + + + + + + + + +
    NameFields
    PRIMARYgserver-id, tag
    tagtag
    +

    Foreign Keys#

    + + + + + + + + + + + + + + + +
    FieldTarget TableTarget Field
    gserver-idgserverid
    +

    Return to database documentation

    + + +
    + +
    +
    + + + +
    + + + +
    +
    +
    +
    + + + + + + + + \ No newline at end of file diff --git a/develop/de/spec/database/db_gserver/index.html b/develop/de/spec/database/db_gserver/index.html new file mode 100644 index 0000000..db20e59 --- /dev/null +++ b/develop/de/spec/database/db_gserver/index.html @@ -0,0 +1,3635 @@ + + + + + + + + + + + + + + + + + + + + + + gserver - Friendica Dokumentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + +
    + + + + +
    + + +
    + +
    + + + + + + + + + +
    +
    + + + +
    +
    +
    + + + + + + +
    +
    +
    + + + +
    +
    +
    + + + +
    +
    +
    + + +
    +
    + + + + + + + + + + + + + +

    Table gserver#

    +

    Global servers

    +

    Fields#

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    FieldDescriptionTypeNullKeyDefaultExtra
    idsequential IDint unsignedNOPRINULLauto_increment
    urlvarchar(255)NO
    nurlvarchar(255)NO
    versionvarchar(255)NO
    site_namevarchar(255)NO
    infotextYESNULL
    register_policytinyintNO0
    registered-usersNumber of registered usersint unsignedNO0
    active-week-usersNumber of active users in the last weekint unsignedYESNULL
    active-month-usersNumber of active users in the last monthint unsignedYESNULL
    active-halfyear-usersNumber of active users in the last six monthint unsignedYESNULL
    local-postsNumber of local postsint unsignedYESNULL
    local-commentsNumber of local commentsint unsignedYESNULL
    directory-typeType of directory service (Poco, Mastodon)tinyintYES0
    pocovarchar(255)NO
    noscrapevarchar(255)NO
    networkchar(4)NO
    protocolThe protocol of the servertinyint unsignedYESNULL
    platformvarchar(255)NO
    relay-subscribeHas the server subscribed to the relay systembooleanNO0
    relay-scopeThe scope of messages that the server wants to getvarchar(10)NO
    detection-methodMethod that had been used to detect that servertinyint unsignedYESNULL
    createddatetimeNO0001-01-01 00:00:00
    last_poco_querydatetimeYES0001-01-01 00:00:00
    last_contactLast successful connection requestdatetimeYES0001-01-01 00:00:00
    last_failureLast failed connection requestdatetimeYES0001-01-01 00:00:00
    failedConnection failedbooleanYESNULL
    next_contactNext connection requestdatetimeYES0001-01-01 00:00:00
    +

    Indexes#

    + + + + + + + + + + + + + + + + + + + + + + + + + +
    NameFields
    PRIMARYid
    nurlUNIQUE, nurl(190)
    next_contactnext_contact
    networknetwork
    +

    Return to database documentation

    + + +
    + +
    +
    + + + +
    + + + +
    +
    +
    +
    + + + + + + + + \ No newline at end of file diff --git a/develop/de/spec/database/db_hook/index.html b/develop/de/spec/database/db_hook/index.html new file mode 100644 index 0000000..6e127c4 --- /dev/null +++ b/develop/de/spec/database/db_hook/index.html @@ -0,0 +1,3424 @@ + + + + + + + + + + + + + + + + + + + + + + hook - Friendica Dokumentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + +
    + + + + +
    + + +
    + +
    + + + + + + + + + +
    +
    + + + +
    +
    +
    + + + + + + +
    +
    +
    + + + +
    +
    +
    + + + +
    +
    +
    + + +
    +
    + + + + + + + + + + + + + +

    Table hook#

    +

    addon hook registry

    +

    Fields#

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    FieldDescriptionTypeNullKeyDefaultExtra
    idsequential IDint unsignedNOPRINULLauto_increment
    hookname of hookvarbinary(100)NO
    filerelative filename of hook handlervarbinary(200)NO
    functionfunction name of hook handlervarbinary(200)NO
    prioritynot yet implemented - can be used to sort conflicts in hook handling by calling handlers in priority ordersmallint unsignedNO0
    +

    Indexes#

    + + + + + + + + + + + + + + + + + + + + + +
    NameFields
    PRIMARYid
    prioritypriority
    hook_file_functionUNIQUE, hook, file, function
    +

    Return to database documentation

    + + +
    + +
    +
    + + + +
    + + + +
    +
    +
    +
    + + + + + + + + \ No newline at end of file diff --git a/develop/de/spec/database/db_inbox-entry-receiver/index.html b/develop/de/spec/database/db_inbox-entry-receiver/index.html new file mode 100644 index 0000000..76f9460 --- /dev/null +++ b/develop/de/spec/database/db_inbox-entry-receiver/index.html @@ -0,0 +1,3429 @@ + + + + + + + + + + + + + + + + + + + + + + inbox-entry-receiver - Friendica Dokumentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + +
    + + + + +
    + + +
    + +
    + + + + + + + + + +
    +
    + + + +
    +
    +
    + + + + + + +
    +
    +
    + + + +
    +
    +
    + + + +
    +
    +
    + + +
    +
    + + + + + + + + + + + + + +

    Table inbox-entry-receiver#

    +

    Receiver for the incoming activity

    +

    Fields#

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    FieldDescriptionTypeNullKeyDefaultExtra
    queue-idint unsignedNOPRINULL
    uidUser idmediumint unsignedNOPRINULL
    +

    Indexes#

    + + + + + + + + + + + + + + + + + +
    NameFields
    PRIMARYqueue-id, uid
    uiduid
    +

    Foreign Keys#

    + + + + + + + + + + + + + + + + + + + + +
    FieldTarget TableTarget Field
    queue-idinbox-entryid
    uiduseruid
    +

    Return to database documentation

    + + +
    + +
    +
    + + + +
    + + + +
    +
    +
    +
    + + + + + + + + \ No newline at end of file diff --git a/develop/de/spec/database/db_inbox-entry/index.html b/develop/de/spec/database/db_inbox-entry/index.html new file mode 100644 index 0000000..dd6169f --- /dev/null +++ b/develop/de/spec/database/db_inbox-entry/index.html @@ -0,0 +1,3544 @@ + + + + + + + + + + + + + + + + + + + + + + inbox-entry - Friendica Dokumentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + +
    + + + + +
    + + +
    + +
    + + + + + + + + + +
    +
    + + + +
    +
    +
    + + + + + + +
    +
    +
    + + + +
    +
    +
    + + + +
    +
    +
    + + +
    +
    + + + + + + + + + + + + + +

    Table inbox-entry#

    +

    Incoming activity

    +

    Fields#

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    FieldDescriptionTypeNullKeyDefaultExtra
    idsequential IDint unsignedNOPRINULLauto_increment
    activity-idid of the incoming activityvarbinary(255)YESNULL
    object-idvarbinary(255)YESNULL
    in-reply-to-idvarbinary(255)YESNULL
    conversationvarbinary(255)YESNULL
    typeType of the activityvarchar(64)YESNULL
    object-typeType of the object activityvarchar(64)YESNULL
    object-object-typeType of the object's object activityvarchar(64)YESNULL
    receivedReceiving datedatetimeYESNULL
    activityThe JSON activitymediumtextYESNULL
    signervarchar(255)YESNULL
    pushIs the entry pushed or have pulled it?booleanYESNULL
    trustDo we trust this entry?booleanYESNULL
    widWorkerqueue idint unsignedYESNULL
    +

    Indexes#

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    NameFields
    PRIMARYid
    activity-idUNIQUE, activity-id
    object-idobject-id
    receivedreceived
    widwid
    +

    Foreign Keys#

    + + + + + + + + + + + + + + + +
    FieldTarget TableTarget Field
    widworkerqueueid
    +

    Return to database documentation

    + + +
    + +
    +
    + + + +
    + + + +
    +
    +
    +
    + + + + + + + + \ No newline at end of file diff --git a/develop/de/spec/database/db_inbox-status/index.html b/develop/de/spec/database/db_inbox-status/index.html new file mode 100644 index 0000000..bb0c203 --- /dev/null +++ b/develop/de/spec/database/db_inbox-status/index.html @@ -0,0 +1,3478 @@ + + + + + + + + + + + + + + + + + + + + + + inbox-status - Friendica Dokumentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + +
    + + + + +
    + + +
    + +
    + + + + + + + + + +
    +
    + + + +
    +
    +
    + + + + + + +
    +
    +
    + + + +
    +
    +
    + + + +
    +
    +
    + + +
    +
    + + + + + + + + + + + + + +

    Table inbox-status#

    +

    Status of ActivityPub inboxes

    +

    Fields#

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    FieldDescriptionTypeNullKeyDefaultExtra
    urlURL of the inboxvarbinary(255)NOPRINULL
    uri-idItem-uri id of inbox urlint unsignedYESNULL
    createdCreation date of this entrydatetimeNO0001-01-01 00:00:00
    successDate of the last successful deliverydatetimeNO0001-01-01 00:00:00
    failureDate of the last failed deliverydatetimeNO0001-01-01 00:00:00
    previousPrevious delivery datedatetimeNO0001-01-01 00:00:00
    archiveIs the inbox archived?booleanNO0
    sharedIs it a shared inbox?booleanNO0
    +

    Indexes#

    + + + + + + + + + + + + + + + + + +
    NameFields
    PRIMARYurl
    uri-iduri-id
    +

    Foreign Keys#

    + + + + + + + + + + + + + + + +
    FieldTarget TableTarget Field
    uri-iditem-uriid
    +

    Return to database documentation

    + + +
    + +
    +
    + + + +
    + + + +
    +
    +
    +
    + + + + + + + + \ No newline at end of file diff --git a/develop/de/spec/database/db_intro/index.html b/develop/de/spec/database/db_intro/index.html new file mode 100644 index 0000000..e0c0cab --- /dev/null +++ b/develop/de/spec/database/db_intro/index.html @@ -0,0 +1,3531 @@ + + + + + + + + + + + + + + + + + + + + + + intro - Friendica Dokumentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + +
    + + + + +
    + + +
    + +
    + + + + + + + + + +
    +
    + + + +
    +
    +
    + + + + + + +
    +
    +
    + + + +
    +
    +
    + + + +
    +
    +
    + + +
    +
    + + + + + + + + + + + + + +

    Table intro#

    +

    Fields#

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    FieldDescriptionTypeNullKeyDefaultExtra
    idsequential IDint unsignedNOPRINULLauto_increment
    uidUser idmediumint unsignedNO0
    fiddeprecatedint unsignedYESNULL
    contact-idint unsignedNO0
    suggest-cidSuggested contactint unsignedYESNULL
    knowyoubooleanNO0
    duplexdeprecatedbooleanNO0
    notetextYESNULL
    hashvarchar(255)NO
    datetimedatetimeNO0001-01-01 00:00:00
    blockeddeprecatedbooleanNO0
    ignorebooleanNO0
    +

    Indexes#

    + + + + + + + + + + + + + + + + + + + + + + + + + +
    NameFields
    PRIMARYid
    contact-idcontact-id
    suggest-cidsuggest-cid
    uiduid
    +

    Foreign Keys#

    + + + + + + + + + + + + + + + + + + + + + + + + + +
    FieldTarget TableTarget Field
    uiduseruid
    contact-idcontactid
    suggest-cidcontactid
    +

    Return to database documentation

    + + +
    + +
    +
    + + + +
    + + + +
    +
    +
    +
    + + + + + + + + \ No newline at end of file diff --git a/develop/de/spec/database/db_item-uri/index.html b/develop/de/spec/database/db_item-uri/index.html new file mode 100644 index 0000000..90a300f --- /dev/null +++ b/develop/de/spec/database/db_item-uri/index.html @@ -0,0 +1,3406 @@ + + + + + + + + + + + + + + + + + + + + + + item-uri - Friendica Dokumentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + +
    + + + + +
    + + +
    + +
    + + + + + + + + + +
    +
    + + + +
    +
    +
    + + + + + + +
    +
    +
    + + + +
    +
    +
    + + + +
    +
    +
    + + +
    +
    + + + + + + + + + + + + + +

    Table item-uri#

    +

    URI and GUID for items

    +

    Fields#

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    FieldDescriptionTypeNullKeyDefaultExtra
    idint unsignedNOPRINULLauto_increment
    uriURI of an itemvarbinary(255)NONULL
    guidA unique identifier for an itemvarbinary(255)YESNULL
    +

    Indexes#

    + + + + + + + + + + + + + + + + + + + + + +
    NameFields
    PRIMARYid
    uriUNIQUE, uri
    guidguid
    +

    Return to database documentation

    + + +
    + +
    +
    + + + +
    + + + +
    +
    +
    +
    + + + + + + + + \ No newline at end of file diff --git a/develop/de/spec/database/db_locks/index.html b/develop/de/spec/database/db_locks/index.html new file mode 100644 index 0000000..cbb7040 --- /dev/null +++ b/develop/de/spec/database/db_locks/index.html @@ -0,0 +1,3419 @@ + + + + + + + + + + + + + + + + + + + + + + locks - Friendica Dokumentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + +
    + + + + +
    + + +
    + +
    + + + + + + + + + +
    +
    + + + +
    +
    +
    + + + + + + +
    +
    +
    + + + +
    +
    +
    + + + +
    +
    +
    + + +
    +
    + + + + + + + + + + + + + +

    Table locks#

    +

    Fields#

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    FieldDescriptionTypeNullKeyDefaultExtra
    idsequential IDint unsignedNOPRINULLauto_increment
    namevarchar(128)NO
    lockedbooleanNO0
    pidProcess IDint unsignedNO0
    expiresdatetime of cache expirationdatetimeNO0001-01-01 00:00:00
    +

    Indexes#

    + + + + + + + + + + + + + + + + + +
    NameFields
    PRIMARYid
    name_expiresname, expires
    +

    Return to database documentation

    + + +
    + +
    +
    + + + +
    + + + +
    +
    +
    +
    + + + + + + + + \ No newline at end of file diff --git a/develop/de/spec/database/db_mail/index.html b/develop/de/spec/database/db_mail/index.html new file mode 100644 index 0000000..5376a70 --- /dev/null +++ b/develop/de/spec/database/db_mail/index.html @@ -0,0 +1,3656 @@ + + + + + + + + + + + + + + + + + + + + + + mail - Friendica Dokumentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + +
    + + + + +
    + + +
    + +
    + + + + + + + + + +
    +
    + + + +
    +
    +
    + + + + + + +
    +
    +
    + + + +
    +
    +
    + + + +
    +
    +
    + + +
    +
    + + + + + + + + + + + + + +

    Table mail#

    +

    private messages

    +

    Fields#

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    FieldDescriptionTypeNullKeyDefaultExtra
    idsequential IDint unsignedNOPRINULLauto_increment
    uidOwner User idmediumint unsignedNO0
    guidA unique identifier for this private messagevarchar(255)NO
    from-namename of the sendervarchar(255)NO
    from-photocontact photo link of the sendervarchar(255)NO
    from-urlprofile linke of the sendervarchar(255)NO
    contact-idcontact.idvarchar(255)YESNULL
    author-idLink to the contact table with uid=0 of the author of the mailint unsignedYESNULL
    convidconv.idint unsignedYESNULL
    titlevarchar(255)NO
    bodymediumtextYESNULL
    seenif message visited it is 1booleanNO0
    replybooleanNO0
    repliedbooleanNO0
    unknownif sender not in the contact table this is 1booleanNO0
    urivarchar(255)NO
    uri-idItem-uri id of the related mailint unsignedYESNULL
    parent-urivarchar(255)NO
    parent-uri-idItem-uri id of the parent of the related mailint unsignedYESNULL
    thr-parentvarchar(255)YESNULL
    thr-parent-idId of the item-uri table that contains the thread parent uriint unsignedYESNULL
    createdcreation time of the private messagedatetimeNO0001-01-01 00:00:00
    +

    Indexes#

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    NameFields
    PRIMARYid
    uid_seenuid, seen
    convidconvid
    uriuri(64)
    parent-uriparent-uri(64)
    contactidcontact-id(32)
    author-idauthor-id
    uri-iduri-id
    parent-uri-idparent-uri-id
    thr-parent-idthr-parent-id
    +

    Foreign Keys#

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    FieldTarget TableTarget Field
    uiduseruid
    author-idcontactid
    uri-iditem-uriid
    parent-uri-iditem-uriid
    thr-parent-iditem-uriid
    +

    Return to database documentation

    + + +
    + +
    +
    + + + +
    + + + +
    +
    +
    +
    + + + + + + + + \ No newline at end of file diff --git a/develop/de/spec/database/db_mailacct/index.html b/develop/de/spec/database/db_mailacct/index.html new file mode 100644 index 0000000..7668c35 --- /dev/null +++ b/develop/de/spec/database/db_mailacct/index.html @@ -0,0 +1,3523 @@ + + + + + + + + + + + + + + + + + + + + + + mailacct - Friendica Dokumentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + +
    + + + + +
    + + +
    + +
    + + + + + + + + + +
    +
    + + + +
    +
    +
    + + + + + + +
    +
    +
    + + + +
    +
    +
    + + + +
    +
    +
    + + +
    +
    + + + + + + + + + + + + + +

    Table mailacct#

    +

    Mail account data for fetching mails

    +

    Fields#

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    FieldDescriptionTypeNullKeyDefaultExtra
    idsequential IDint unsignedNOPRINULLauto_increment
    uidUser idmediumint unsignedNO0
    servervarchar(255)NO
    portsmallint unsignedNO0
    ssltypevarchar(16)NO
    mailboxvarchar(255)NO
    uservarchar(255)NO
    passtextYESNULL
    reply_tovarchar(255)NO
    actiontinyint unsignedNO0
    movetofoldervarchar(255)NO
    pubmailbooleanNO0
    last_checkdatetimeNO0001-01-01 00:00:00
    +

    Indexes#

    + + + + + + + + + + + + + + + + + +
    NameFields
    PRIMARYid
    uiduid
    +

    Foreign Keys#

    + + + + + + + + + + + + + + + +
    FieldTarget TableTarget Field
    uiduseruid
    +

    Return to database documentation

    + + +
    + +
    +
    + + + +
    + + + +
    +
    +
    +
    + + + + + + + + \ No newline at end of file diff --git a/develop/de/spec/database/db_manage/index.html b/develop/de/spec/database/db_manage/index.html new file mode 100644 index 0000000..5f28747 --- /dev/null +++ b/develop/de/spec/database/db_manage/index.html @@ -0,0 +1,3442 @@ + + + + + + + + + + + + + + + + + + + + + + manage - Friendica Dokumentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + +
    + + + + +
    + + +
    + +
    + + + + + + + + + +
    +
    + + + +
    +
    +
    + + + + + + +
    +
    +
    + + + +
    +
    +
    + + + +
    +
    +
    + + +
    +
    + + + + + + + + + + + + + +

    Table manage#

    +

    table of accounts that can manage each other

    +

    Fields#

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    FieldDescriptionTypeNullKeyDefaultExtra
    idsequential IDint unsignedNOPRINULLauto_increment
    uidUser idmediumint unsignedNO0
    midUser idmediumint unsignedNO0
    +

    Indexes#

    + + + + + + + + + + + + + + + + + + + + + +
    NameFields
    PRIMARYid
    uid_midUNIQUE, uid, mid
    midmid
    +

    Foreign Keys#

    + + + + + + + + + + + + + + + + + + + + +
    FieldTarget TableTarget Field
    uiduseruid
    miduseruid
    +

    Return to database documentation

    + + +
    + +
    +
    + + + +
    + + + +
    +
    +
    +
    + + + + + + + + \ No newline at end of file diff --git a/develop/de/spec/database/db_notification/index.html b/develop/de/spec/database/db_notification/index.html new file mode 100644 index 0000000..a91aad4 --- /dev/null +++ b/develop/de/spec/database/db_notification/index.html @@ -0,0 +1,3536 @@ + + + + + + + + + + + + + + + + + + + + + + notification - Friendica Dokumentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + +
    + + + + +
    + + +
    + +
    + + + + + + + + + +
    +
    + + + +
    +
    +
    + + + + + + +
    +
    +
    + + + +
    +
    +
    + + + +
    +
    +
    + + +
    +
    + + + + + + + + + + + + + +

    Table notification#

    +

    notifications

    +

    Fields#

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    FieldDescriptionTypeNullKeyDefaultExtra
    idsequential IDint unsignedNOPRINULLauto_increment
    uidOwner User idmediumint unsignedYESNULL
    vidId of the verb table entry that contains the activity verbssmallint unsignedYESNULL
    typesmallint unsignedYESNULL
    actor-idLink to the contact table with uid=0 of the actor that caused the notificationint unsignedYESNULL
    target-uri-idItem-uri id of the related postint unsignedYESNULL
    parent-uri-idItem-uri id of the parent of the related postint unsignedYESNULL
    createddatetimeYESNULL
    seenSeen on the desktopbooleanYES0
    dismissedDismissed via the APIbooleanYES0
    +

    Indexes#

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    NameFields
    PRIMARYid
    uid_vid_type_actor-id_target-uri-idUNIQUE, uid, vid, type, actor-id, target-uri-id
    vidvid
    actor-idactor-id
    target-uri-idtarget-uri-id
    parent-uri-idparent-uri-id
    seen_uidseen, uid
    +

    Foreign Keys#

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    FieldTarget TableTarget Field
    uiduseruid
    vidverbid
    actor-idcontactid
    target-uri-iditem-uriid
    parent-uri-iditem-uriid
    +

    Return to database documentation

    + + +
    + +
    +
    + + + +
    + + + +
    +
    +
    +
    + + + + + + + + \ No newline at end of file diff --git a/develop/de/spec/database/db_notify-threads/index.html b/develop/de/spec/database/db_notify-threads/index.html new file mode 100644 index 0000000..06893ee --- /dev/null +++ b/develop/de/spec/database/db_notify-threads/index.html @@ -0,0 +1,3477 @@ + + + + + + + + + + + + + + + + + + + + + + notify-threads - Friendica Dokumentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + +
    + + + + +
    + + +
    + +
    + + + + + + + + + +
    +
    + + + +
    +
    +
    + + + + + + +
    +
    +
    + + + +
    +
    +
    + + + +
    +
    +
    + + +
    +
    + + + + + + + + + + + + + +

    Table notify-threads#

    +

    Fields#

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    FieldDescriptionTypeNullKeyDefaultExtra
    idsequential IDint unsignedNOPRINULLauto_increment
    notify-idint unsignedNO0
    master-parent-itemDeprecatedint unsignedYESNULL
    master-parent-uri-idItem-uri id of the parent of the related postint unsignedYESNULL
    parent-itemint unsignedNO0
    receiver-uidUser idmediumint unsignedNO0
    +

    Indexes#

    + + + + + + + + + + + + + + + + + + + + + + + + + +
    NameFields
    PRIMARYid
    master-parent-uri-idmaster-parent-uri-id
    receiver-uidreceiver-uid
    notify-idnotify-id
    +

    Foreign Keys#

    + + + + + + + + + + + + + + + + + + + + + + + + + +
    FieldTarget TableTarget Field
    notify-idnotifyid
    master-parent-uri-iditem-uriid
    receiver-uiduseruid
    +

    Return to database documentation

    + + +
    + +
    +
    + + + +
    + + + +
    +
    +
    +
    + + + + + + + + \ No newline at end of file diff --git a/develop/de/spec/database/db_notify/index.html b/develop/de/spec/database/db_notify/index.html new file mode 100644 index 0000000..20ad499 --- /dev/null +++ b/develop/de/spec/database/db_notify/index.html @@ -0,0 +1,3594 @@ + + + + + + + + + + + + + + + + + + + + + + notify - Friendica Dokumentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + +
    + + + + +
    + + +
    + +
    + + + + + + + + + +
    +
    + + + +
    +
    +
    + + + + + + +
    +
    +
    + + + +
    +
    +
    + + + +
    +
    +
    + + +
    +
    + + + + + + + + + + + + + +

    Table notify#

    +

    [Deprecated] User notifications

    +

    Fields#

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    FieldDescriptionTypeNullKeyDefaultExtra
    idsequential IDint unsignedNOPRINULLauto_increment
    typesmallint unsignedNO0
    namevarchar(255)NO
    urlvarchar(255)NO
    photovarchar(255)NO
    datedatetimeNO0001-01-01 00:00:00
    msgmediumtextYESNULL
    uidOwner User idmediumint unsignedNO0
    linkvarchar(255)NO
    iidint unsignedYESNULL
    parentint unsignedYESNULL
    uri-idItem-uri id of the related postint unsignedYESNULL
    parent-uri-idItem-uri id of the parent of the related postint unsignedYESNULL
    seenbooleanNO0
    verbvarchar(100)NO
    otypevarchar(10)NO
    name_cacheCached bbcode parsing of nametinytextYESNULL
    msg_cacheCached bbcode parsing of msgmediumtextYESNULL
    +

    Indexes#

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    NameFields
    PRIMARYid
    seen_uid_dateseen, uid, date
    uid_dateuid, date
    uid_type_linkuid, type, link(190)
    uri-iduri-id
    parent-uri-idparent-uri-id
    +

    Foreign Keys#

    + + + + + + + + + + + + + + + + + + + + + + + + + +
    FieldTarget TableTarget Field
    uiduseruid
    uri-iditem-uriid
    parent-uri-iditem-uriid
    +

    Return to database documentation

    + + +
    + +
    +
    + + + +
    + + + +
    +
    +
    +
    + + + + + + + + \ No newline at end of file diff --git a/develop/de/spec/database/db_oembed/index.html b/develop/de/spec/database/db_oembed/index.html new file mode 100644 index 0000000..496c8e9 --- /dev/null +++ b/develop/de/spec/database/db_oembed/index.html @@ -0,0 +1,3411 @@ + + + + + + + + + + + + + + + + + + + + + + oembed - Friendica Dokumentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + +
    + + + + +
    + + +
    + +
    + + + + + + + + + +
    +
    + + + +
    +
    +
    + + + + + + +
    +
    +
    + + + +
    +
    +
    + + + +
    +
    +
    + + +
    +
    + + + + + + + + + + + + + +

    Table oembed#

    +

    cache for OEmbed queries

    +

    Fields#

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    FieldDescriptionTypeNullKeyDefaultExtra
    urlpage urlvarbinary(255)NOPRINULL
    maxwidthMaximum width passed to Oembedmediumint unsignedNOPRINULL
    contentOEmbed data of the pagemediumtextYESNULL
    createddatetime of creationdatetimeNO0001-01-01 00:00:00
    +

    Indexes#

    + + + + + + + + + + + + + + + + + +
    NameFields
    PRIMARYurl, maxwidth
    createdcreated
    +

    Return to database documentation

    + + +
    + +
    +
    + + + +
    + + + +
    +
    +
    +
    + + + + + + + + \ No newline at end of file diff --git a/develop/de/spec/database/db_openwebauth-token/index.html b/develop/de/spec/database/db_openwebauth-token/index.html new file mode 100644 index 0000000..b711696 --- /dev/null +++ b/develop/de/spec/database/db_openwebauth-token/index.html @@ -0,0 +1,3460 @@ + + + + + + + + + + + + + + + + + + + + + + openwebauth-token - Friendica Dokumentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + +
    + + + + +
    + + +
    + +
    + + + + + + + + + +
    +
    + + + +
    +
    +
    + + + + + + +
    +
    +
    + + + +
    +
    +
    + + + +
    +
    +
    + + +
    +
    + + + + + + + + + + + + + +

    Table openwebauth-token#

    +

    Store OpenWebAuth token to verify contacts

    +

    Fields#

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    FieldDescriptionTypeNullKeyDefaultExtra
    idsequential IDint unsignedNOPRINULLauto_increment
    uidUser id - currently unusedmediumint unsignedNO0
    typeVerify typevarchar(32)NO
    tokenA generated tokenvarchar(255)NO
    metavarchar(255)NO
    createddatetime of creationdatetimeNO0001-01-01 00:00:00
    +

    Indexes#

    + + + + + + + + + + + + + + + + + +
    NameFields
    PRIMARYid
    uiduid
    +

    Foreign Keys#

    + + + + + + + + + + + + + + + +
    FieldTarget TableTarget Field
    uiduseruid
    +

    Return to database documentation

    + + +
    + +
    +
    + + + +
    + + + +
    +
    +
    +
    + + + + + + + + \ No newline at end of file diff --git a/develop/de/spec/database/db_parsed_url/index.html b/develop/de/spec/database/db_parsed_url/index.html new file mode 100644 index 0000000..a9731c6 --- /dev/null +++ b/develop/de/spec/database/db_parsed_url/index.html @@ -0,0 +1,3442 @@ + + + + + + + + + + + + + + + + + + + + + + parsed_url - Friendica Dokumentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + +
    + + + + +
    + + +
    + +
    + + + + + + + + + +
    +
    + + + +
    +
    +
    + + + + + + +
    +
    +
    + + + +
    +
    +
    + + + +
    +
    +
    + + +
    +
    + + + + + + + + + + + + + +

    Table parsed_url#

    +

    cache for 'parse_url' queries

    +

    Fields#

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    FieldDescriptionTypeNullKeyDefaultExtra
    url_hashpage url hashbinary(64)NOPRINULL
    guessingis the 'guessing' mode active?booleanNOPRI0
    oembedis the data the result of oembed?booleanNOPRI0
    urlpage urltextNONULL
    contentpage datamediumtextYESNULL
    createddatetime of creationdatetimeNO0001-01-01 00:00:00
    expiresdatetime of expirationdatetimeNO0001-01-01 00:00:00
    +

    Indexes#

    + + + + + + + + + + + + + + + + + + + + + +
    NameFields
    PRIMARYurl_hash, guessing, oembed
    createdcreated
    expiresexpires
    +

    Return to database documentation

    + + +
    + +
    +
    + + + +
    + + + +
    +
    +
    +
    + + + + + + + + \ No newline at end of file diff --git a/develop/de/spec/database/db_pconfig/index.html b/develop/de/spec/database/db_pconfig/index.html new file mode 100644 index 0000000..1467121 --- /dev/null +++ b/develop/de/spec/database/db_pconfig/index.html @@ -0,0 +1,3451 @@ + + + + + + + + + + + + + + + + + + + + + + pconfig - Friendica Dokumentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + +
    + + + + +
    + + +
    + +
    + + + + + + + + + +
    +
    + + + +
    +
    +
    + + + + + + +
    +
    +
    + + + +
    +
    +
    + + + +
    +
    +
    + + +
    +
    + + + + + + + + + + + + + +

    Table pconfig#

    +

    personal (per user) configuration storage

    +

    Fields#

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    FieldDescriptionTypeNullKeyDefaultExtra
    idPrimary keyint unsignedNOPRINULLauto_increment
    uidUser idmediumint unsignedNO0
    catCategoryvarchar(50)NO
    kKeyvarchar(100)NO
    vValuemediumtextYESNULL
    +

    Indexes#

    + + + + + + + + + + + + + + + + + +
    NameFields
    PRIMARYid
    uid_cat_kUNIQUE, uid, cat, k
    +

    Foreign Keys#

    + + + + + + + + + + + + + + + +
    FieldTarget TableTarget Field
    uiduseruid
    +

    Return to database documentation

    + + +
    + +
    +
    + + + +
    + + + +
    +
    +
    +
    + + + + + + + + \ No newline at end of file diff --git a/develop/de/spec/database/db_permissionset/index.html b/develop/de/spec/database/db_permissionset/index.html new file mode 100644 index 0000000..770da47 --- /dev/null +++ b/develop/de/spec/database/db_permissionset/index.html @@ -0,0 +1,3459 @@ + + + + + + + + + + + + + + + + + + + + + + permissionset - Friendica Dokumentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + +
    + + + + +
    + + +
    + +
    + + + + + + + + + +
    +
    + + + +
    +
    +
    + + + + + + +
    +
    +
    + + + +
    +
    +
    + + + +
    +
    +
    + + +
    +
    + + + + + + + + + + + + + +

    Table permissionset#

    +

    Fields#

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    FieldDescriptionTypeNullKeyDefaultExtra
    idsequential IDint unsignedNOPRINULLauto_increment
    uidOwner id of this permission setmediumint unsignedNO0
    allow_cidAccess Control - list of allowed contact.id '<19><78>'mediumtextYESNULL
    allow_gidAccess Control - list of allowed groupsmediumtextYESNULL
    deny_cidAccess Control - list of denied contact.idmediumtextYESNULL
    deny_gidAccess Control - list of denied groupsmediumtextYESNULL
    +

    Indexes#

    + + + + + + + + + + + + + + + + + +
    NameFields
    PRIMARYid
    uid_allow_cid_allow_gid_deny_cid_deny_giduid, allow_cid(50), allow_gid(30), deny_cid(50), deny_gid(30)
    +

    Foreign Keys#

    + + + + + + + + + + + + + + + +
    FieldTarget TableTarget Field
    uiduseruid
    +

    Return to database documentation

    + + +
    + +
    +
    + + + +
    + + + +
    +
    +
    +
    + + + + + + + + \ No newline at end of file diff --git a/develop/de/spec/database/db_photo/index.html b/develop/de/spec/database/db_photo/index.html new file mode 100644 index 0000000..5621f94 --- /dev/null +++ b/develop/de/spec/database/db_photo/index.html @@ -0,0 +1,3687 @@ + + + + + + + + + + + + + + + + + + + + + + photo - Friendica Dokumentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + +
    + + + + +
    + + +
    + +
    + + + + + + + + + +
    +
    + + + +
    +
    +
    + + + + + + +
    +
    +
    + + + +
    +
    +
    + + + +
    +
    +
    + + +
    +
    + + + + + + + + + + + + + +

    Table photo#

    +

    photo storage

    +

    Fields#

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    FieldDescriptionTypeNullKeyDefaultExtra
    idsequential IDint unsignedNOPRINULLauto_increment
    uidOwner User idmediumint unsignedNO0
    contact-idcontact.idint unsignedNO0
    guidA unique identifier for this photochar(16)NO
    resource-idchar(32)NO
    hashhash value of the photochar(32)YESNULL
    createdcreation datedatetimeNO0001-01-01 00:00:00
    editedlast edited datedatetimeNO0001-01-01 00:00:00
    titlevarchar(255)NO
    desctextYESNULL
    albumThe name of the album to which the photo belongsvarchar(255)NO
    photo-typeUser avatar, user banner, contact avatar, contact banner or defaulttinyint unsignedYESNULL
    filenamevarchar(255)NO
    typevarchar(30)NOimage/jpeg
    heightsmallint unsignedNO0
    widthsmallint unsignedNO0
    datasizeint unsignedNO0
    datamediumblobNONULL
    scaletinyint unsignedNO0
    profilebooleanNO0
    allow_cidAccess Control - list of allowed contact.id '<19><78>'mediumtextYESNULL
    allow_gidAccess Control - list of allowed groupsmediumtextYESNULL
    deny_cidAccess Control - list of denied contact.idmediumtextYESNULL
    deny_gidAccess Control - list of denied groupsmediumtextYESNULL
    accessibleMake photo publicly accessible, ignoring permissionsbooleanNO0
    backend-classStorage backend classtinytextYESNULL
    backend-refStorage backend data referencetextYESNULL
    updateddatetimeNO0001-01-01 00:00:00
    +

    Indexes#

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    NameFields
    PRIMARYid
    contactidcontact-id
    uid_contactiduid, contact-id
    uid_profileuid, profile
    uid_album_scale_createduid, album(32), scale, created
    uid_album_resource-id_createduid, album(32), resource-id, created
    resource-idresource-id
    uid_photo-typeuid, photo-type
    +

    Foreign Keys#

    + + + + + + + + + + + + + + + + + + + + +
    FieldTarget TableTarget Field
    uiduseruid
    contact-idcontactid
    +

    Return to database documentation

    + + +
    + +
    +
    + + + +
    + + + +
    +
    +
    +
    + + + + + + + + \ No newline at end of file diff --git a/develop/de/spec/database/db_post-category/index.html b/develop/de/spec/database/db_post-category/index.html new file mode 100644 index 0000000..e94f0dc --- /dev/null +++ b/develop/de/spec/database/db_post-category/index.html @@ -0,0 +1,3456 @@ + + + + + + + + + + + + + + + + + + + + + + post-category - Friendica Dokumentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + +
    + + + + +
    + + +
    + +
    + + + + + + + + + +
    +
    + + + +
    +
    +
    + + + + + + +
    +
    +
    + + + +
    +
    +
    + + + +
    +
    +
    + + +
    +
    + + + + + + + + + + + + + +

    Table post-category#

    +

    post relation to categories

    +

    Fields#

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    FieldDescriptionTypeNullKeyDefaultExtra
    uri-idId of the item-uri table entry that contains the item uriint unsignedNOPRINULL
    uidUser idmediumint unsignedNOPRI0
    typetinyint unsignedNOPRI0
    tidint unsignedNOPRI0
    +

    Indexes#

    + + + + + + + + + + + + + + + + + + + + + +
    NameFields
    PRIMARYuri-id, uid, type, tid
    tidtid
    uid_uri-iduid, uri-id
    +

    Foreign Keys#

    + + + + + + + + + + + + + + + + + + + + + + + + + +
    FieldTarget TableTarget Field
    uri-iditem-uriid
    uiduseruid
    tidtagid
    +

    Return to database documentation

    + + +
    + +
    +
    + + + +
    + + + +
    +
    +
    +
    + + + + + + + + \ No newline at end of file diff --git a/develop/de/spec/database/db_post-collection/index.html b/develop/de/spec/database/db_post-collection/index.html new file mode 100644 index 0000000..37c2952 --- /dev/null +++ b/develop/de/spec/database/db_post-collection/index.html @@ -0,0 +1,3424 @@ + + + + + + + + + + + + + + + + + + + + + + post-collection - Friendica Dokumentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + +
    + + + + +
    + + +
    + +
    + + + + + + + + + +
    +
    + + + +
    +
    +
    + + + + + + +
    +
    +
    + + + +
    +
    +
    + + + +
    +
    +
    + + +
    +
    + + + + + + + + + + + + + +

    Table post-collection#

    +

    Collection of posts

    +

    Fields#

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    FieldDescriptionTypeNullKeyDefaultExtra
    uri-idId of the item-uri table entry that contains the item uriint unsignedNOPRINULL
    type0 - Featuredtinyint unsignedNOPRI0
    +

    Indexes#

    + + + + + + + + + + + + + + + + + +
    NameFields
    PRIMARYuri-id, type
    typetype
    +

    Foreign Keys#

    + + + + + + + + + + + + + + + +
    FieldTarget TableTarget Field
    uri-iditem-uriid
    +

    Return to database documentation

    + + +
    + +
    +
    + + + +
    + + + +
    +
    +
    +
    + + + + + + + + \ No newline at end of file diff --git a/develop/de/spec/database/db_post-content/index.html b/develop/de/spec/database/db_post-content/index.html new file mode 100644 index 0000000..b32598c --- /dev/null +++ b/develop/de/spec/database/db_post-content/index.html @@ -0,0 +1,3567 @@ + + + + + + + + + + + + + + + + + + + + + + post-content - Friendica Dokumentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + +
    + + + + +
    + + +
    + +
    + + + + + + + + + +
    +
    + + + +
    +
    +
    + + + + + + +
    +
    +
    + + + +
    +
    +
    + + + +
    +
    +
    + + +
    +
    + + + + + + + + + + + + + +

    Table post-content#

    +

    Content for all posts

    +

    Fields#

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    FieldDescriptionTypeNullKeyDefaultExtra
    uri-idId of the item-uri table entry that contains the item uriint unsignedNOPRINULL
    titleitem titlevarchar(255)NO
    content-warningvarchar(255)NO
    bodyitem body contentmediumtextYESNULL
    raw-bodyBody without embedded media linksmediumtextYESNULL
    locationtext location where this item originatedvarchar(255)NO
    coordlongitude/latitude pair representing location where this item originatedvarchar(255)NO
    languageLanguage information about this posttextYESNULL
    appapplication which generated this itemvarchar(255)NO
    rendered-hashvarchar(32)NO
    rendered-htmlitem.body converted to htmlmediumtextYESNULL
    object-typeActivityStreams object typevarchar(100)NO
    objectJSON encoded object structure unless it is an implied object (normal post)textYESNULL
    target-typeActivityStreams target type if applicable (URI)varchar(100)NO
    targetJSON encoded target structure if usedtextYESNULL
    resource-idUsed to link other tables to items, it identifies the linked resource (e.g. photo) and if set must also set resource_typevarchar(32)NO
    plinkpermalink or URL to a displayable copy of the message at its sourcevarchar(255)NO
    +

    Indexes#

    + + + + + + + + + + + + + + + + + + + + + + + + + +
    NameFields
    PRIMARYuri-id
    plinkplink(191)
    resource-idresource-id
    title-content-warning-bodyFULLTEXT, title, content-warning, body
    +

    Foreign Keys#

    + + + + + + + + + + + + + + + +
    FieldTarget TableTarget Field
    uri-iditem-uriid
    +

    Return to database documentation

    + + +
    + +
    +
    + + + +
    + + + +
    +
    +
    +
    + + + + + + + + \ No newline at end of file diff --git a/develop/de/spec/database/db_post-delivery-data/index.html b/develop/de/spec/database/db_post-delivery-data/index.html new file mode 100644 index 0000000..04d09e5 --- /dev/null +++ b/develop/de/spec/database/db_post-delivery-data/index.html @@ -0,0 +1,3501 @@ + + + + + + + + + + + + + + + + + + + + + + post-delivery-data - Friendica Dokumentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + +
    + + + + +
    + + +
    + +
    + + + + + + + + + +
    +
    + + + +
    +
    +
    + + + + + + +
    +
    +
    + + + +
    +
    +
    + + + +
    +
    +
    + + +
    +
    + + + + + + + + + + + + + +

    Table post-delivery-data#

    +

    Delivery data for items

    +

    Fields#

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    FieldDescriptionTypeNullKeyDefaultExtra
    uri-idId of the item-uri table entry that contains the item uriint unsignedNOPRINULL
    postoptsExternal post connectors add their network name to this comma-separated string to identify that they should be delivered to these networks during deliverytextYESNULL
    informAdditional receivers of the linked itemmediumtextYESNULL
    queue_countInitial number of delivery recipients, used as item.delivery_queue_countmediumintNO0
    queue_doneNumber of successful deliveries, used as item.delivery_queue_donemediumintNO0
    queue_failedNumber of unsuccessful deliveries, used as item.delivery_queue_failedmediumintNO0
    activitypubNumber of successful deliveries via ActivityPubmediumintNO0
    dfrnNumber of successful deliveries via DFRNmediumintNO0
    legacy_dfrnNumber of successful deliveries via legacy DFRNmediumintNO0
    diasporaNumber of successful deliveries via DiasporamediumintNO0
    ostatusNumber of successful deliveries via OStatusmediumintNO0
    +

    Indexes#

    + + + + + + + + + + + + + +
    NameFields
    PRIMARYuri-id
    +

    Foreign Keys#

    + + + + + + + + + + + + + + + +
    FieldTarget TableTarget Field
    uri-iditem-uriid
    +

    Return to database documentation

    + + +
    + +
    +
    + + + +
    + + + +
    +
    +
    +
    + + + + + + + + \ No newline at end of file diff --git a/develop/de/spec/database/db_post-delivery/index.html b/develop/de/spec/database/db_post-delivery/index.html new file mode 100644 index 0000000..b019083 --- /dev/null +++ b/develop/de/spec/database/db_post-delivery/index.html @@ -0,0 +1,3483 @@ + + + + + + + + + + + + + + + + + + + + + + post-delivery - Friendica Dokumentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + +
    + + + + +
    + + +
    + +
    + + + + + + + + + +
    +
    + + + +
    +
    +
    + + + + + + +
    +
    +
    + + + +
    +
    +
    + + + +
    +
    +
    + + +
    +
    + + + + + + + + + + + + + +

    Table post-delivery#

    +

    Delivery data for posts for the batch processing

    +

    Fields#

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    FieldDescriptionTypeNullKeyDefaultExtra
    uri-idId of the item-uri table entry that contains the item uriint unsignedNOPRINULL
    inbox-idItem-uri id of inbox urlint unsignedNOPRINULL
    uidDelivering usermediumint unsignedYESNULL
    createddatetimeYES0001-01-01 00:00:00
    commandvarbinary(32)YESNULL
    failedNumber of times the delivery has failedtinyintYES0
    receiversJSON encoded array with the receiving contactsmediumtextYESNULL
    +

    Indexes#

    + + + + + + + + + + + + + + + + + + + + + +
    NameFields
    PRIMARYuri-id, inbox-id
    inbox-id_createdinbox-id, created
    uiduid
    +

    Foreign Keys#

    + + + + + + + + + + + + + + + + + + + + + + + + + +
    FieldTarget TableTarget Field
    uri-iditem-uriid
    inbox-iditem-uriid
    uiduseruid
    +

    Return to database documentation

    + + +
    + +
    +
    + + + +
    + + + +
    +
    +
    +
    + + + + + + + + \ No newline at end of file diff --git a/develop/de/spec/database/db_post-history/index.html b/develop/de/spec/database/db_post-history/index.html new file mode 100644 index 0000000..acd4a90 --- /dev/null +++ b/develop/de/spec/database/db_post-history/index.html @@ -0,0 +1,3564 @@ + + + + + + + + + + + + + + + + + + + + + + post-history - Friendica Dokumentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + +
    + + + + +
    + + +
    + +
    + + + + + + + + + +
    +
    + + + +
    +
    +
    + + + + + + +
    +
    +
    + + + +
    +
    +
    + + + +
    +
    +
    + + +
    +
    + + + + + + + + + + + + + +

    Table post-history#

    +

    Post history

    +

    Fields#

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    FieldDescriptionTypeNullKeyDefaultExtra
    uri-idId of the item-uri table entry that contains the item uriint unsignedNOPRINULL
    editedDate of editdatetimeNOPRI0001-01-01 00:00:00
    titleitem titlevarchar(255)NO
    content-warningvarchar(255)NO
    bodyitem body contentmediumtextYESNULL
    raw-bodyBody without embedded media linksmediumtextYESNULL
    locationtext location where this item originatedvarchar(255)NO
    coordlongitude/latitude pair representing location where this item originatedvarchar(255)NO
    languageLanguage information about this posttextYESNULL
    appapplication which generated this itemvarchar(255)NO
    rendered-hashvarchar(32)NO
    rendered-htmlitem.body converted to htmlmediumtextYESNULL
    object-typeActivityStreams object typevarchar(100)NO
    objectJSON encoded object structure unless it is an implied object (normal post)textYESNULL
    target-typeActivityStreams target type if applicable (URI)varchar(100)NO
    targetJSON encoded target structure if usedtextYESNULL
    resource-idUsed to link other tables to items, it identifies the linked resource (e.g. photo) and if set must also set resource_typevarchar(32)NO
    plinkpermalink or URL to a displayable copy of the message at its sourcevarchar(255)NO
    +

    Indexes#

    + + + + + + + + + + + + + +
    NameFields
    PRIMARYuri-id, edited
    +

    Foreign Keys#

    + + + + + + + + + + + + + + + +
    FieldTarget TableTarget Field
    uri-iditem-uriid
    +

    Return to database documentation

    + + +
    + +
    +
    + + + +
    + + + +
    +
    +
    +
    + + + + + + + + \ No newline at end of file diff --git a/develop/de/spec/database/db_post-link/index.html b/develop/de/spec/database/db_post-link/index.html new file mode 100644 index 0000000..6b8d043 --- /dev/null +++ b/develop/de/spec/database/db_post-link/index.html @@ -0,0 +1,3442 @@ + + + + + + + + + + + + + + + + + + + + + + post-link - Friendica Dokumentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + +
    + + + + +
    + + +
    + +
    + + + + + + + + + +
    +
    + + + +
    +
    +
    + + + + + + +
    +
    +
    + + + +
    +
    +
    + + + +
    +
    +
    + + +
    +
    + + + + + + + + + + + + + +

    Table post-link#

    +

    Post related external links

    +

    Fields#

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    FieldDescriptionTypeNullKeyDefaultExtra
    idsequential IDint unsignedNOPRINULLauto_increment
    uri-idId of the item-uri table entry that contains the item uriint unsignedNONULL
    urlExternal URLvarbinary(511)NONULL
    mimetypevarchar(60)YESNULL
    +

    Indexes#

    + + + + + + + + + + + + + + + + + +
    NameFields
    PRIMARYid
    uri-id-urlUNIQUE, uri-id, url
    +

    Foreign Keys#

    + + + + + + + + + + + + + + + +
    FieldTarget TableTarget Field
    uri-iditem-uriid
    +

    Return to database documentation

    + + +
    + +
    +
    + + + +
    + + + +
    +
    +
    +
    + + + + + + + + \ No newline at end of file diff --git a/develop/de/spec/database/db_post-media/index.html b/develop/de/spec/database/db_post-media/index.html new file mode 100644 index 0000000..b793f6c --- /dev/null +++ b/develop/de/spec/database/db_post-media/index.html @@ -0,0 +1,3581 @@ + + + + + + + + + + + + + + + + + + + + + + post-media - Friendica Dokumentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + +
    + + + + +
    + + +
    + +
    + + + + + + + + + +
    +
    + + + +
    +
    +
    + + + + + + +
    +
    +
    + + + +
    +
    +
    + + + +
    +
    +
    + + +
    +
    + + + + + + + + + + + + + +

    Table post-media#

    +

    Attached media

    +

    Fields#

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    FieldDescriptionTypeNullKeyDefaultExtra
    idsequential IDint unsignedNOPRINULLauto_increment
    uri-idId of the item-uri table entry that contains the item uriint unsignedNONULL
    urlMedia URLvarbinary(1024)NONULL
    typeMedia typetinyint unsignedNO0
    mimetypevarchar(60)YESNULL
    heightHeight of the mediasmallint unsignedYESNULL
    widthWidth of the mediasmallint unsignedYESNULL
    sizeMedia sizebigint unsignedYESNULL
    previewPreview URLvarbinary(512)YESNULL
    preview-heightHeight of the preview picturesmallint unsignedYESNULL
    preview-widthWidth of the preview picturesmallint unsignedYESNULL
    descriptiontextYESNULL
    nameName of the mediavarchar(255)YESNULL
    author-urlURL of the author of the mediavarbinary(255)YESNULL
    author-nameName of the author of the mediavarchar(255)YESNULL
    author-imageImage of the author of the mediavarbinary(255)YESNULL
    publisher-urlURL of the publisher of the mediavarbinary(255)YESNULL
    publisher-nameName of the publisher of the mediavarchar(255)YESNULL
    publisher-imageImage of the publisher of the mediavarbinary(255)YESNULL
    +

    Indexes#

    + + + + + + + + + + + + + + + + + + + + + +
    NameFields
    PRIMARYid
    uri-id-urlUNIQUE, uri-id, url(512)
    uri-id-iduri-id, id
    +

    Foreign Keys#

    + + + + + + + + + + + + + + + +
    FieldTarget TableTarget Field
    uri-iditem-uriid
    +

    Return to database documentation

    + + +
    + +
    +
    + + + +
    + + + +
    +
    +
    +
    + + + + + + + + \ No newline at end of file diff --git a/develop/de/spec/database/db_post-question-option/index.html b/develop/de/spec/database/db_post-question-option/index.html new file mode 100644 index 0000000..98a5108 --- /dev/null +++ b/develop/de/spec/database/db_post-question-option/index.html @@ -0,0 +1,3438 @@ + + + + + + + + + + + + + + + + + + + + + + post-question-option - Friendica Dokumentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + +
    + + + + +
    + + +
    + +
    + + + + + + + + + +
    +
    + + + +
    +
    +
    + + + + + + +
    +
    +
    + + + +
    +
    +
    + + + +
    +
    +
    + + +
    +
    + + + + + + + + + + + + + +

    Table post-question-option#

    +

    Question option

    +

    Fields#

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    FieldDescriptionTypeNullKeyDefaultExtra
    idId of the questionint unsignedNOPRINULL
    uri-idId of the item-uri table entry that contains the item uriint unsignedNOPRINULL
    nameName of the optionvarchar(255)YESNULL
    repliesNumber of replies for this question optionint unsignedYESNULL
    +

    Indexes#

    + + + + + + + + + + + + + +
    NameFields
    PRIMARYuri-id, id
    +

    Foreign Keys#

    + + + + + + + + + + + + + + + +
    FieldTarget TableTarget Field
    uri-iditem-uriid
    +

    Return to database documentation

    + + +
    + +
    +
    + + + +
    + + + +
    +
    +
    +
    + + + + + + + + \ No newline at end of file diff --git a/develop/de/spec/database/db_post-question/index.html b/develop/de/spec/database/db_post-question/index.html new file mode 100644 index 0000000..450bd05 --- /dev/null +++ b/develop/de/spec/database/db_post-question/index.html @@ -0,0 +1,3451 @@ + + + + + + + + + + + + + + + + + + + + + + post-question - Friendica Dokumentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + +
    + + + + +
    + + +
    + +
    + + + + + + + + + +
    +
    + + + +
    +
    +
    + + + + + + +
    +
    +
    + + + +
    +
    +
    + + + +
    +
    +
    + + +
    +
    + + + + + + + + + + + + + +

    Table post-question#

    +

    Question

    +

    Fields#

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    FieldDescriptionTypeNullKeyDefaultExtra
    idsequential IDint unsignedNOPRINULLauto_increment
    uri-idId of the item-uri table entry that contains the item uriint unsignedNONULL
    multipleMultiple choicebooleanNO0
    votersNumber of voters for this questionint unsignedYESNULL
    end-timeQuestion end timedatetimeYES0001-01-01 00:00:00
    +

    Indexes#

    + + + + + + + + + + + + + + + + + +
    NameFields
    PRIMARYid
    uri-idUNIQUE, uri-id
    +

    Foreign Keys#

    + + + + + + + + + + + + + + + +
    FieldTarget TableTarget Field
    uri-iditem-uriid
    +

    Return to database documentation

    + + +
    + +
    +
    + + + +
    + + + +
    +
    +
    +
    + + + + + + + + \ No newline at end of file diff --git a/develop/de/spec/database/db_post-tag/index.html b/develop/de/spec/database/db_post-tag/index.html new file mode 100644 index 0000000..f593628 --- /dev/null +++ b/develop/de/spec/database/db_post-tag/index.html @@ -0,0 +1,3456 @@ + + + + + + + + + + + + + + + + + + + + + + post-tag - Friendica Dokumentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + +
    + + + + +
    + + +
    + +
    + + + + + + + + + +
    +
    + + + +
    +
    +
    + + + + + + +
    +
    +
    + + + +
    +
    +
    + + + +
    +
    +
    + + +
    +
    + + + + + + + + + + + + + +

    Table post-tag#

    +

    post relation to tags

    +

    Fields#

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    FieldDescriptionTypeNullKeyDefaultExtra
    uri-idId of the item-uri table entry that contains the item uriint unsignedNOPRINULL
    typetinyint unsignedNOPRI0
    tidint unsignedNOPRI0
    cidContact id of the mentioned public contactint unsignedNOPRI0
    +

    Indexes#

    + + + + + + + + + + + + + + + + + + + + + +
    NameFields
    PRIMARYuri-id, type, tid, cid
    tidtid
    cidcid
    +

    Foreign Keys#

    + + + + + + + + + + + + + + + + + + + + + + + + + +
    FieldTarget TableTarget Field
    uri-iditem-uriid
    tidtagid
    cidcontactid
    +

    Return to database documentation

    + + +
    + +
    +
    + + + +
    + + + +
    +
    +
    +
    + + + + + + + + \ No newline at end of file diff --git a/develop/de/spec/database/db_post-thread-user/index.html b/develop/de/spec/database/db_post-thread-user/index.html new file mode 100644 index 0000000..dac1b23 --- /dev/null +++ b/develop/de/spec/database/db_post-thread-user/index.html @@ -0,0 +1,3718 @@ + + + + + + + + + + + + + + + + + + + + + + post-thread-user - Friendica Dokumentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + +
    + + + + +
    + + +
    + +
    + + + + + + + + + +
    +
    + + + +
    +
    +
    + + + + + + +
    +
    +
    + + + +
    +
    +
    + + + +
    +
    +
    + + +
    +
    + + + + + + + + + + + + + +

    Table post-thread-user#

    +

    Thread related data per user

    +

    Fields#

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    FieldDescriptionTypeNullKeyDefaultExtra
    uri-idId of the item-uri table entry that contains the item uriint unsignedNOPRINULL
    conversation-idId of the item-uri table entry that contains the conversation uriint unsignedYESNULL
    owner-idItem ownerint unsignedNO0
    author-idItem authorint unsignedNO0
    causer-idLink to the contact table with uid=0 of the contact that caused the item creationint unsignedYESNULL
    networkchar(4)NO
    createddatetimeNO0001-01-01 00:00:00
    receiveddatetimeNO0001-01-01 00:00:00
    changedDate that something in the conversation changed, indicating clients should fetch the conversation againdatetimeNO0001-01-01 00:00:00
    commenteddatetimeNO0001-01-01 00:00:00
    uidOwner id which owns this copy of the itemmediumint unsignedNOPRI0
    pinneddeprecatedbooleanNO0
    starredbooleanNO0
    ignoredIgnore updates for this threadbooleanNO0
    wallThis item was posted to the wall of uidbooleanNO0
    mentionbooleanNO0
    pubmailbooleanNO0
    forum_modeDeprecatedtinyint unsignedNO0
    contact-idcontact.idint unsignedNO0
    unseenpost has not been seenbooleanNO1
    hiddenMarker to hide the post from the userbooleanNO0
    originitem originated at this sitebooleanNO0
    psidID of the permission set of this postint unsignedYESNULL
    post-user-idId of the post-user tableint unsignedYESNULL
    +

    Indexes#

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    NameFields
    PRIMARYuid, uri-id
    uri-iduri-id
    conversation-idconversation-id
    owner-idowner-id
    author-idauthor-id
    causer-idcauser-id
    uiduid
    contact-idcontact-id
    psidpsid
    post-user-idpost-user-id
    commentedcommented
    uid_receiveduid, received
    uid_wall_receiveduid, wall, received
    uid_commenteduid, commented
    uid_starreduid, starred
    uid_mentionuid, mention
    +

    Foreign Keys#

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    FieldTarget TableTarget Field
    uri-iditem-uriid
    conversation-iditem-uriid
    owner-idcontactid
    author-idcontactid
    causer-idcontactid
    uiduseruid
    contact-idcontactid
    psidpermissionsetid
    post-user-idpost-userid
    +

    Return to database documentation

    + + +
    + +
    +
    + + + +
    + + + +
    +
    +
    +
    + + + + + + + + \ No newline at end of file diff --git a/develop/de/spec/database/db_post-thread/index.html b/develop/de/spec/database/db_post-thread/index.html new file mode 100644 index 0000000..06f0a12 --- /dev/null +++ b/develop/de/spec/database/db_post-thread/index.html @@ -0,0 +1,3536 @@ + + + + + + + + + + + + + + + + + + + + + + post-thread - Friendica Dokumentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + +
    + + + + +
    + + +
    + +
    + + + + + + + + + +
    +
    + + + +
    +
    +
    + + + + + + +
    +
    +
    + + + +
    +
    +
    + + + +
    +
    +
    + + +
    +
    + + + + + + + + + + + + + +

    Table post-thread#

    +

    Thread related data

    +

    Fields#

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    FieldDescriptionTypeNullKeyDefaultExtra
    uri-idId of the item-uri table entry that contains the item uriint unsignedNOPRINULL
    conversation-idId of the item-uri table entry that contains the conversation uriint unsignedYESNULL
    owner-idItem ownerint unsignedNO0
    author-idItem authorint unsignedNO0
    causer-idLink to the contact table with uid=0 of the contact that caused the item creationint unsignedYESNULL
    networkchar(4)NO
    createddatetimeNO0001-01-01 00:00:00
    receiveddatetimeNO0001-01-01 00:00:00
    changedDate that something in the conversation changed, indicating clients should fetch the conversation againdatetimeNO0001-01-01 00:00:00
    commenteddatetimeNO0001-01-01 00:00:00
    +

    Indexes#

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    NameFields
    PRIMARYuri-id
    conversation-idconversation-id
    owner-idowner-id
    author-idauthor-id
    causer-idcauser-id
    receivedreceived
    commentedcommented
    +

    Foreign Keys#

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    FieldTarget TableTarget Field
    uri-iditem-uriid
    conversation-iditem-uriid
    owner-idcontactid
    author-idcontactid
    causer-idcontactid
    +

    Return to database documentation

    + + +
    + +
    +
    + + + +
    + + + +
    +
    +
    +
    + + + + + + + + \ No newline at end of file diff --git a/develop/de/spec/database/db_post-user-notification/index.html b/develop/de/spec/database/db_post-user-notification/index.html new file mode 100644 index 0000000..0d19684 --- /dev/null +++ b/develop/de/spec/database/db_post-user-notification/index.html @@ -0,0 +1,3438 @@ + + + + + + + + + + + + + + + + + + + + + + post-user-notification - Friendica Dokumentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + +
    + + + + +
    + + +
    + +
    + + + + + + + + + +
    +
    + + + +
    +
    +
    + + + + + + +
    +
    +
    + + + +
    +
    +
    + + + +
    +
    +
    + + +
    +
    + + + + + + + + + + + + + +

    Table post-user-notification#

    +

    User post notifications

    +

    Fields#

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    FieldDescriptionTypeNullKeyDefaultExtra
    uri-idId of the item-uri table entry that contains the item uriint unsignedNOPRINULL
    uidOwner id which owns this copy of the itemmediumint unsignedNOPRINULL
    notification-typesmallint unsignedNO0
    +

    Indexes#

    + + + + + + + + + + + + + + + + + +
    NameFields
    PRIMARYuid, uri-id
    uri-iduri-id
    +

    Foreign Keys#

    + + + + + + + + + + + + + + + + + + + + +
    FieldTarget TableTarget Field
    uri-iditem-uriid
    uiduseruid
    +

    Return to database documentation

    + + +
    + +
    +
    + + + +
    + + + +
    +
    +
    +
    + + + + + + + + \ No newline at end of file diff --git a/develop/de/spec/database/db_post-user/index.html b/develop/de/spec/database/db_post-user/index.html new file mode 100644 index 0000000..b9f6bf1 --- /dev/null +++ b/develop/de/spec/database/db_post-user/index.html @@ -0,0 +1,3803 @@ + + + + + + + + + + + + + + + + + + + + + + post-user - Friendica Dokumentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + +
    + + + + +
    + + +
    + +
    + + + + + + + + + +
    +
    + + + +
    +
    +
    + + + + + + +
    +
    +
    + + + +
    +
    +
    + + + +
    +
    +
    + + +
    +
    + + + + + + + + + + + + + +

    Table post-user#

    +

    User specific post data

    +

    Fields#

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    FieldDescriptionTypeNullKeyDefaultExtra
    idint unsignedNOPRINULLauto_increment
    uri-idId of the item-uri table entry that contains the item uriint unsignedNONULL
    parent-uri-idId of the item-uri table that contains the parent uriint unsignedYESNULL
    thr-parent-idId of the item-uri table that contains the thread parent uriint unsignedYESNULL
    external-idId of the item-uri table entry that contains the external uriint unsignedYESNULL
    createdCreation timestamp.datetimeNO0001-01-01 00:00:00
    editedDate of last edit (default is created)datetimeNO0001-01-01 00:00:00
    receiveddatetimedatetimeNO0001-01-01 00:00:00
    gravitytinyint unsignedNO0
    networkNetwork from where the item comes fromchar(4)NO
    owner-idLink to the contact table with uid=0 of the owner of this itemint unsignedNO0
    author-idLink to the contact table with uid=0 of the author of this itemint unsignedNO0
    causer-idLink to the contact table with uid=0 of the contact that caused the item creationint unsignedYESNULL
    post-typePost type (personal note, image, article, ...)tinyint unsignedNO0
    post-reasonReason why the post arrived at the usertinyint unsignedNO0
    vidId of the verb table entry that contains the activity verbssmallint unsignedYESNULL
    private0=public, 1=private, 2=unlistedtinyint unsignedNO0
    globalbooleanNO0
    visiblebooleanNO0
    deleteditem has been marked for deletionbooleanNO0
    uidOwner id which owns this copy of the itemmediumint unsignedNONULL
    protocolProtocol used to deliver the item for this usertinyint unsignedYESNULL
    contact-idcontact.idint unsignedNO0
    event-idUsed to link to the event.idint unsignedYESNULL
    unseenpost has not been seenbooleanNO1
    hiddenMarker to hide the post from the userbooleanNO0
    notification-typetinyint unsignedNO0
    wallThis item was posted to the wall of uidbooleanNO0
    originitem originated at this sitebooleanNO0
    psidID of the permission set of this postint unsignedYESNULL
    +

    Indexes#

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    NameFields
    PRIMARYid
    uid_uri-idUNIQUE, uid, uri-id
    uri-iduri-id
    parent-uri-idparent-uri-id
    thr-parent-idthr-parent-id
    external-idexternal-id
    owner-idowner-id
    author-idauthor-id
    causer-idcauser-id
    vidvid
    contact-idcontact-id
    event-idevent-id
    psidpsid
    author-id_uidauthor-id, uid
    author-id_receivedauthor-id, received
    parent-uri-id_uidparent-uri-id, uid
    uid_contactiduid, contact-id
    uid_unseen_contactiduid, unseen, contact-id
    uid_unseenuid, unseen
    uid_hidden_uri-iduid, hidden, uri-id
    +

    Foreign Keys#

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    FieldTarget TableTarget Field
    uri-iditem-uriid
    parent-uri-iditem-uriid
    thr-parent-iditem-uriid
    external-iditem-uriid
    owner-idcontactid
    author-idcontactid
    causer-idcontactid
    vidverbid
    uiduseruid
    contact-idcontactid
    event-ideventid
    psidpermissionsetid
    +

    Return to database documentation

    + + +
    + +
    +
    + + + +
    + + + +
    +
    +
    +
    + + + + + + + + \ No newline at end of file diff --git a/develop/de/spec/database/db_post/index.html b/develop/de/spec/database/db_post/index.html new file mode 100644 index 0000000..2b2f024 --- /dev/null +++ b/develop/de/spec/database/db_post/index.html @@ -0,0 +1,3627 @@ + + + + + + + + + + + + + + + + + + + + + + post - Friendica Dokumentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + +
    + + + + +
    + + +
    + +
    + + + + + + + + + +
    +
    + + + +
    +
    +
    + + + + + + +
    +
    +
    + + + +
    +
    +
    + + + +
    +
    +
    + + +
    +
    + + + + + + + + + + + + + +

    Table post#

    +

    Structure for all posts

    +

    Fields#

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    FieldDescriptionTypeNullKeyDefaultExtra
    uri-idId of the item-uri table entry that contains the item uriint unsignedNOPRINULL
    parent-uri-idId of the item-uri table that contains the parent uriint unsignedYESNULL
    thr-parent-idId of the item-uri table that contains the thread parent uriint unsignedYESNULL
    external-idId of the item-uri table entry that contains the external uriint unsignedYESNULL
    createdCreation timestamp.datetimeNO0001-01-01 00:00:00
    editedDate of last edit (default is created)datetimeNO0001-01-01 00:00:00
    receiveddatetimedatetimeNO0001-01-01 00:00:00
    gravitytinyint unsignedNO0
    networkNetwork from where the item comes fromchar(4)NO
    owner-idLink to the contact table with uid=0 of the owner of this itemint unsignedNO0
    author-idLink to the contact table with uid=0 of the author of this itemint unsignedNO0
    causer-idLink to the contact table with uid=0 of the contact that caused the item creationint unsignedYESNULL
    post-typePost type (personal note, image, article, ...)tinyint unsignedNO0
    vidId of the verb table entry that contains the activity verbssmallint unsignedYESNULL
    private0=public, 1=private, 2=unlistedtinyint unsignedNO0
    globalbooleanNO0
    visiblebooleanNO0
    deleteditem has been marked for deletionbooleanNO0
    +

    Indexes#

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    NameFields
    PRIMARYuri-id
    parent-uri-idparent-uri-id
    thr-parent-idthr-parent-id
    external-idexternal-id
    owner-idowner-id
    author-idauthor-id
    causer-idcauser-id
    vidvid
    +

    Foreign Keys#

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    FieldTarget TableTarget Field
    uri-iditem-uriid
    parent-uri-iditem-uriid
    thr-parent-iditem-uriid
    external-iditem-uriid
    owner-idcontactid
    author-idcontactid
    causer-idcontactid
    vidverbid
    +

    Return to database documentation

    + + +
    + +
    +
    + + + +
    + + + +
    +
    +
    +
    + + + + + + + + \ No newline at end of file diff --git a/develop/de/spec/database/db_process/index.html b/develop/de/spec/database/db_process/index.html new file mode 100644 index 0000000..4ebd628 --- /dev/null +++ b/develop/de/spec/database/db_process/index.html @@ -0,0 +1,3411 @@ + + + + + + + + + + + + + + + + + + + + + + process - Friendica Dokumentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + +
    + + + + +
    + + +
    + +
    + + + + + + + + + +
    +
    + + + +
    +
    +
    + + + + + + +
    +
    +
    + + + +
    +
    +
    + + + +
    +
    +
    + + +
    +
    + + + + + + + + + + + + + +

    Table process#

    +

    Currently running system processes

    +

    Fields#

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    FieldDescriptionTypeNullKeyDefaultExtra
    pidThe ID of the processint unsignedNOPRINULL
    hostnameThe name of the host the process is ran onvarchar(32)NOPRINULL
    commandvarbinary(32)NO
    createddatetimeNO0001-01-01 00:00:00
    +

    Indexes#

    + + + + + + + + + + + + + + + + + +
    NameFields
    PRIMARYpid, hostname
    commandcommand
    +

    Return to database documentation

    + + +
    + +
    +
    + + + +
    + + + +
    +
    +
    +
    + + + + + + + + \ No newline at end of file diff --git a/develop/de/spec/database/db_profile/index.html b/develop/de/spec/database/db_profile/index.html new file mode 100644 index 0000000..b0039aa --- /dev/null +++ b/develop/de/spec/database/db_profile/index.html @@ -0,0 +1,3797 @@ + + + + + + + + + + + + + + + + + + + + + + profile - Friendica Dokumentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + +
    + + + + +
    + + +
    + +
    + + + + + + + + + +
    +
    + + + +
    +
    +
    + + + + + + +
    +
    +
    + + + +
    +
    +
    + + + +
    +
    +
    + + +
    +
    + + + + + + + + + + + + + +

    Table profile#

    +

    user profiles data

    +

    Fields#

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    FieldDescriptionTypeNullKeyDefaultExtra
    idsequential IDint unsignedNOPRINULLauto_increment
    uidOwner User idmediumint unsignedNO0
    profile-nameDeprecatedvarchar(255)YESNULL
    is-defaultDeprecatedbooleanYESNULL
    hide-friendsHide friend list from viewers of this profilebooleanNO0
    namevarchar(255)NO
    pdescDeprecatedvarchar(255)YESNULL
    dobDay of birthvarchar(32)NO0000-00-00
    addressvarchar(255)NO
    localityvarchar(255)NO
    regionvarchar(255)NO
    postal-codevarchar(32)NO
    country-namevarchar(255)NO
    hometownDeprecatedvarchar(255)YESNULL
    genderDeprecatedvarchar(32)YESNULL
    maritalDeprecatedvarchar(255)YESNULL
    withDeprecatedtextYESNULL
    howlongDeprecateddatetimeYESNULL
    sexualDeprecatedvarchar(255)YESNULL
    politicDeprecatedvarchar(255)YESNULL
    religionDeprecatedvarchar(255)YESNULL
    pub_keywordstextYESNULL
    prv_keywordstextYESNULL
    likesDeprecatedtextYESNULL
    dislikesDeprecatedtextYESNULL
    aboutProfile descriptiontextYESNULL
    summaryDeprecatedvarchar(255)YESNULL
    musicDeprecatedtextYESNULL
    bookDeprecatedtextYESNULL
    tvDeprecatedtextYESNULL
    filmDeprecatedtextYESNULL
    interestDeprecatedtextYESNULL
    romanceDeprecatedtextYESNULL
    workDeprecatedtextYESNULL
    educationDeprecatedtextYESNULL
    contactDeprecatedtextYESNULL
    homepagevarchar(255)NO
    xmppXMPP addressvarchar(255)NO
    matrixMatrix addressvarchar(255)NO
    photovarchar(255)NO
    thumbvarchar(255)NO
    publishpublish default profile in local directorybooleanNO0
    net-publishpublish profile in global directorybooleanNO0
    +

    Indexes#

    + + + + + + + + + + + + + + + + + + + + + +
    NameFields
    PRIMARYid
    uid_is-defaultuid, is-default
    pub_keywordsFULLTEXT, pub_keywords
    +

    Foreign Keys#

    + + + + + + + + + + + + + + + +
    FieldTarget TableTarget Field
    uiduseruid
    +

    Return to database documentation

    + + +
    + +
    +
    + + + +
    + + + +
    +
    +
    +
    + + + + + + + + \ No newline at end of file diff --git a/develop/de/spec/database/db_profile_field/index.html b/develop/de/spec/database/db_profile_field/index.html new file mode 100644 index 0000000..de56ffe --- /dev/null +++ b/develop/de/spec/database/db_profile_field/index.html @@ -0,0 +1,3491 @@ + + + + + + + + + + + + + + + + + + + + + + profile_field - Friendica Dokumentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + +
    + + + + +
    + + +
    + +
    + + + + + + + + + +
    +
    + + + +
    +
    +
    + + + + + + +
    +
    +
    + + + +
    +
    +
    + + + +
    +
    +
    + + +
    +
    + + + + + + + + + + + + + +

    Table profile_field#

    +

    Custom profile fields

    +

    Fields#

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    FieldDescriptionTypeNullKeyDefaultExtra
    idsequential IDint unsignedNOPRINULLauto_increment
    uidOwner user idmediumint unsignedNO0
    orderField ordering per usermediumint unsignedNO1
    psidID of the permission set of this profile field - 0 = publicint unsignedYESNULL
    labelLabel of the fieldvarchar(255)NO
    valueValue of the fieldtextYESNULL
    createdcreation timedatetimeNO0001-01-01 00:00:00
    editedlast edit timedatetimeNO0001-01-01 00:00:00
    +

    Indexes#

    + + + + + + + + + + + + + + + + + + + + + + + + + +
    NameFields
    PRIMARYid
    uiduid
    orderorder
    psidpsid
    +

    Foreign Keys#

    + + + + + + + + + + + + + + + + + + + + +
    FieldTarget TableTarget Field
    uiduseruid
    psidpermissionsetid
    +

    Return to database documentation

    + + +
    + +
    +
    + + + +
    + + + +
    +
    +
    +
    + + + + + + + + \ No newline at end of file diff --git a/develop/de/spec/database/db_push_subscriber/index.html b/develop/de/spec/database/db_push_subscriber/index.html new file mode 100644 index 0000000..51c2408 --- /dev/null +++ b/develop/de/spec/database/db_push_subscriber/index.html @@ -0,0 +1,3500 @@ + + + + + + + + + + + + + + + + + + + + + + push_subscriber - Friendica Dokumentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + +
    + + + + +
    + + +
    + +
    + + + + + + + + + +
    +
    + + + +
    +
    +
    + + + + + + +
    +
    +
    + + + +
    +
    +
    + + + +
    +
    +
    + + +
    +
    + + + + + + + + + + + + + +

    Table push_subscriber#

    +

    Used for OStatus: Contains feed subscribers

    +

    Fields#

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    FieldDescriptionTypeNullKeyDefaultExtra
    idsequential IDint unsignedNOPRINULLauto_increment
    uidUser idmediumint unsignedNO0
    callback_urlvarchar(255)NO
    topicvarchar(255)NO
    nicknamevarchar(255)NO
    pushRetrial countertinyintNO0
    last_updateDate of last successful trialdatetimeNO0001-01-01 00:00:00
    next_tryNext retrial datedatetimeNO0001-01-01 00:00:00
    renewedDate of last subscription renewaldatetimeNO0001-01-01 00:00:00
    secretvarchar(255)NO
    +

    Indexes#

    + + + + + + + + + + + + + + + + + + + + + +
    NameFields
    PRIMARYid
    next_trynext_try
    uiduid
    +

    Foreign Keys#

    + + + + + + + + + + + + + + + +
    FieldTarget TableTarget Field
    uiduseruid
    +

    Return to database documentation

    + + +
    + +
    +
    + + + +
    + + + +
    +
    +
    +
    + + + + + + + + \ No newline at end of file diff --git a/develop/de/spec/database/db_register/index.html b/develop/de/spec/database/db_register/index.html new file mode 100644 index 0000000..673c8ae --- /dev/null +++ b/develop/de/spec/database/db_register/index.html @@ -0,0 +1,3469 @@ + + + + + + + + + + + + + + + + + + + + + + register - Friendica Dokumentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + +
    + + + + +
    + + +
    + +
    + + + + + + + + + +
    +
    + + + +
    +
    +
    + + + + + + +
    +
    +
    + + + +
    +
    +
    + + + +
    +
    +
    + + +
    +
    + + + + + + + + + + + + + +

    Table register#

    +

    registrations requiring admin approval

    +

    Fields#

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    FieldDescriptionTypeNullKeyDefaultExtra
    idsequential IDint unsignedNOPRINULLauto_increment
    hashvarchar(255)NO
    createddatetimeNO0001-01-01 00:00:00
    uidUser idmediumint unsignedNO0
    passwordvarchar(255)NO
    languagevarchar(16)NO
    notetextYESNULL
    +

    Indexes#

    + + + + + + + + + + + + + + + + + +
    NameFields
    PRIMARYid
    uiduid
    +

    Foreign Keys#

    + + + + + + + + + + + + + + + +
    FieldTarget TableTarget Field
    uiduseruid
    +

    Return to database documentation

    + + +
    + +
    +
    + + + +
    + + + +
    +
    +
    +
    + + + + + + + + \ No newline at end of file diff --git a/develop/de/spec/database/db_search/index.html b/develop/de/spec/database/db_search/index.html new file mode 100644 index 0000000..d53df30 --- /dev/null +++ b/develop/de/spec/database/db_search/index.html @@ -0,0 +1,3436 @@ + + + + + + + + + + + + + + + + + + + + + + search - Friendica Dokumentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + +
    + + + + +
    + + +
    + +
    + + + + + + + + + +
    +
    + + + +
    +
    +
    + + + + + + +
    +
    +
    + + + +
    +
    +
    + + + +
    +
    +
    + + +
    +
    + + + + + + + + + + + + + +

    Table search#

    +

    Fields#

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    FieldDescriptionTypeNullKeyDefaultExtra
    idsequential IDint unsignedNOPRINULLauto_increment
    uidUser idmediumint unsignedNO0
    termvarchar(255)NO
    +

    Indexes#

    + + + + + + + + + + + + + + + + + + + + + +
    NameFields
    PRIMARYid
    uid_termuid, term(64)
    termterm(64)
    +

    Foreign Keys#

    + + + + + + + + + + + + + + + +
    FieldTarget TableTarget Field
    uiduseruid
    +

    Return to database documentation

    + + +
    + +
    +
    + + + +
    + + + +
    +
    +
    +
    + + + + + + + + \ No newline at end of file diff --git a/develop/de/spec/database/db_session/index.html b/develop/de/spec/database/db_session/index.html new file mode 100644 index 0000000..98b977a --- /dev/null +++ b/develop/de/spec/database/db_session/index.html @@ -0,0 +1,3415 @@ + + + + + + + + + + + + + + + + + + + + + + session - Friendica Dokumentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + +
    + + + + +
    + + +
    + +
    + + + + + + + + + +
    +
    + + + +
    +
    +
    + + + + + + +
    +
    +
    + + + +
    +
    +
    + + + +
    +
    +
    + + +
    +
    + + + + + + + + + + + + + +

    Table session#

    +

    web session storage

    +

    Fields#

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    FieldDescriptionTypeNullKeyDefaultExtra
    idsequential IDbigint unsignedNOPRINULLauto_increment
    sidvarbinary(255)NO
    datatextYESNULL
    expireint unsignedNO0
    +

    Indexes#

    + + + + + + + + + + + + + + + + + + + + + +
    NameFields
    PRIMARYid
    sidsid(64)
    expireexpire
    +

    Return to database documentation

    + + +
    + +
    +
    + + + +
    + + + +
    +
    +
    +
    + + + + + + + + \ No newline at end of file diff --git a/develop/de/spec/database/db_storage/index.html b/develop/de/spec/database/db_storage/index.html new file mode 100644 index 0000000..46743a2 --- /dev/null +++ b/develop/de/spec/database/db_storage/index.html @@ -0,0 +1,3389 @@ + + + + + + + + + + + + + + + + + + + + + + storage - Friendica Dokumentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + +
    + + + + +
    + + +
    + +
    + + + + + + + + + +
    +
    + + + +
    +
    +
    + + + + + + +
    +
    +
    + + + +
    +
    +
    + + + +
    +
    +
    + + +
    +
    + + + + + + + + + + + + + +

    Table storage#

    +

    Data stored by Database storage backend

    +

    Fields#

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    FieldDescriptionTypeNullKeyDefaultExtra
    idAuto incremented image data idint unsignedNOPRINULLauto_increment
    datafile datalongblobNONULL
    +

    Indexes#

    + + + + + + + + + + + + + +
    NameFields
    PRIMARYid
    +

    Return to database documentation

    + + +
    + +
    +
    + + + +
    + + + +
    +
    +
    +
    + + + + + + + + \ No newline at end of file diff --git a/develop/de/spec/database/db_subscription/index.html b/develop/de/spec/database/db_subscription/index.html new file mode 100644 index 0000000..9e53add --- /dev/null +++ b/develop/de/spec/database/db_subscription/index.html @@ -0,0 +1,3532 @@ + + + + + + + + + + + + + + + + + + + + + + subscription - Friendica Dokumentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + +
    + + + + +
    + + +
    + +
    + + + + + + + + + +
    +
    + + + +
    +
    +
    + + + + + + +
    +
    +
    + + + +
    +
    +
    + + + +
    +
    +
    + + +
    +
    + + + + + + + + + + + + + +

    Table subscription#

    +

    Push Subscription for the API

    +

    Fields#

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    FieldDescriptionTypeNullKeyDefaultExtra
    idAuto incremented image data idint unsignedNOPRINULLauto_increment
    application-idint unsignedNONULL
    uidOwner User idmediumint unsignedNONULL
    endpointEndpoint URLvarchar(511)YESNULL
    pubkeyUser agent public keyvarchar(127)YESNULL
    secretAuth secretvarchar(32)YESNULL
    followbooleanYESNULL
    favouritebooleanYESNULL
    reblogbooleanYESNULL
    mentionbooleanYESNULL
    pollbooleanYESNULL
    follow_requestbooleanYESNULL
    statusbooleanYESNULL
    +

    Indexes#

    + + + + + + + + + + + + + + + + + + + + + +
    NameFields
    PRIMARYid
    application-id_uidUNIQUE, application-id, uid
    uid_application-iduid, application-id
    +

    Foreign Keys#

    + + + + + + + + + + + + + + + + + + + + +
    FieldTarget TableTarget Field
    application-idapplicationid
    uiduseruid
    +

    Return to database documentation

    + + +
    + +
    +
    + + + +
    + + + +
    +
    +
    +
    + + + + + + + + \ No newline at end of file diff --git a/develop/de/spec/database/db_tag/index.html b/develop/de/spec/database/db_tag/index.html new file mode 100644 index 0000000..4348a41 --- /dev/null +++ b/develop/de/spec/database/db_tag/index.html @@ -0,0 +1,3415 @@ + + + + + + + + + + + + + + + + + + + + + + tag - Friendica Dokumentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + +
    + + + + +
    + + +
    + +
    + + + + + + + + + +
    +
    + + + +
    +
    +
    + + + + + + +
    +
    +
    + + + +
    +
    +
    + + + +
    +
    +
    + + +
    +
    + + + + + + + + + + + + + +

    Table tag#

    +

    tags and mentions

    +

    Fields#

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    FieldDescriptionTypeNullKeyDefaultExtra
    idint unsignedNOPRINULLauto_increment
    namevarchar(96)NO
    urlvarbinary(255)NO
    typeType of the tag (Unknown, General Collection, Follower Collection or Account)tinyint unsignedYESNULL
    +

    Indexes#

    + + + + + + + + + + + + + + + + + + + + + +
    NameFields
    PRIMARYid
    type_name_urlUNIQUE, name, url
    urlurl
    +

    Return to database documentation

    + + +
    + +
    +
    + + + +
    + + + +
    +
    +
    +
    + + + + + + + + \ No newline at end of file diff --git a/develop/de/spec/database/db_user-contact/index.html b/develop/de/spec/database/db_user-contact/index.html new file mode 100644 index 0000000..cee824b --- /dev/null +++ b/develop/de/spec/database/db_user-contact/index.html @@ -0,0 +1,3600 @@ + + + + + + + + + + + + + + + + + + + + + + user-contact - Friendica Dokumentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + +
    + + + + +
    + + +
    + +
    + + + + + + + + + +
    +
    + + + +
    +
    +
    + + + + + + +
    +
    +
    + + + +
    +
    +
    + + + +
    +
    +
    + + +
    +
    + + + + + + + + + + + + + +

    Table user-contact#

    +

    User specific public contact data

    +

    Fields#

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    FieldDescriptionTypeNullKeyDefaultExtra
    cidContact id of the linked public contactint unsignedNOPRI0
    uidUser idmediumint unsignedNOPRI0
    uri-idId of the item-uri table entry that contains the contact urlint unsignedYESNULL
    blockedContact is completely blocked for this userbooleanYESNULL
    ignoredPosts from this contact are ignoredbooleanYESNULL
    collapsedPosts from this contact are collapsedbooleanYESNULL
    hiddenThis contact is hidden from the othersbooleanYESNULL
    is-blockedUser is blocked by this contactbooleanYESNULL
    pendingbooleanYESNULL
    relThe kind of the relation between the user and the contacttinyint unsignedYESNULL
    infomediumtextYESNULL
    notify_new_postsbooleanYESNULL
    remote_selfbooleanYESNULL
    fetch_further_informationtinyint unsignedYESNULL
    ffi_keyword_denylisttextYESNULL
    subhubbooleanYESNULL
    hub-verifyvarchar(255)YESNULL
    protocolProtocol of the contactchar(4)YESNULL
    ratingAutomatically detected feed poll frequencytinyintYESNULL
    priorityFeed poll prioritytinyint unsignedYESNULL
    +

    Indexes#

    + + + + + + + + + + + + + + + + + + + + + +
    NameFields
    PRIMARYuid, cid
    cidcid
    uri-id_uidUNIQUE, uri-id, uid
    +

    Foreign Keys#

    + + + + + + + + + + + + + + + + + + + + + + + + + +
    FieldTarget TableTarget Field
    cidcontactid
    uiduseruid
    uri-iditem-uriid
    +

    Return to database documentation

    + + +
    + +
    +
    + + + +
    + + + +
    +
    +
    +
    + + + + + + + + \ No newline at end of file diff --git a/develop/de/spec/database/db_user/index.html b/develop/de/spec/database/db_user/index.html new file mode 100644 index 0000000..b3c4eb1 --- /dev/null +++ b/develop/de/spec/database/db_user/index.html @@ -0,0 +1,3823 @@ + + + + + + + + + + + + + + + + + + + + + + user - Friendica Dokumentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + +
    + + + + +
    + + +
    + +
    + + + + + + + + + +
    +
    + + + +
    +
    +
    + + + + + + +
    +
    +
    + + + +
    +
    +
    + + + +
    +
    +
    + + +
    +
    + + + + + + + + + + + + + +

    Table user#

    +

    The local users

    +

    Fields#

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    FieldDescriptionTypeNullKeyDefaultExtra
    uidsequential IDmediumint unsignedNOPRINULLauto_increment
    parent-uidThe parent user that has full control about this usermediumint unsignedYESNULL
    guidA unique identifier for this uservarchar(64)NO
    usernameName that this user is known byvarchar(255)NO
    passwordencrypted passwordvarchar(255)NO
    legacy_passwordIs the password hash double-hashed?booleanNO0
    nicknamenick- and user namevarchar(255)NO
    emailthe users email addressvarchar(255)NO
    openidvarchar(255)NO
    timezonePHP-legal timezonevarchar(128)NO
    languagedefault languagevarchar(32)NOen
    register_datetimestamp of registrationdatetimeNO0001-01-01 00:00:00
    login_datetimestamp of last logindatetimeNO0001-01-01 00:00:00
    default-locationDefault for item.locationvarchar(255)NO
    allow_location1 allows to display the locationbooleanNO0
    themeuser theme preferencevarchar(255)NO
    pubkeyRSA public key 4096 bittextYESNULL
    prvkeyRSA private key 4096 bittextYESNULL
    spubkeytextYESNULL
    sprvkeytextYESNULL
    verifieduser is verified through emailbooleanNO0
    blocked1 for user is blockedbooleanNO0
    blockwallProhibit contacts to post to the profile page of the userbooleanNO0
    hidewallHide profile details from unkown viewersbooleanNO0
    blocktagsProhibit contacts to tag the post of this userbooleanNO0
    unkmailPermit unknown people to send private mails to this userbooleanNO0
    cntunkmailint unsignedNO10
    notify-flagsemail notification optionssmallint unsignedNO65535
    page-flagspage/profile typetinyint unsignedNO0
    account-typetinyint unsignedNO0
    prvnetsbooleanNO0
    pwdresetPassword reset request tokenvarchar(255)YESNULL
    pwdreset_timeTimestamp of the last password reset requestdatetimeYESNULL
    maxreqint unsignedNO10
    expireint unsignedNO0
    account_removedif 1 the account is removedbooleanNO0
    account_expiredbooleanNO0
    account_expires_ontimestamp when account expires and will be deleteddatetimeNO0001-01-01 00:00:00
    expire_notification_senttimestamp of last warning of account expirationdatetimeNO0001-01-01 00:00:00
    def_gidint unsignedNO0
    allow_ciddefault permission for this usermediumtextYESNULL
    allow_giddefault permission for this usermediumtextYESNULL
    deny_ciddefault permission for this usermediumtextYESNULL
    deny_giddefault permission for this usermediumtextYESNULL
    openidservertextYESNULL
    +

    Indexes#

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    NameFields
    PRIMARYuid
    nicknamenickname(32)
    parent-uidparent-uid
    guidguid
    emailemail(64)
    +

    Foreign Keys#

    + + + + + + + + + + + + + + + +
    FieldTarget TableTarget Field
    parent-uiduseruid
    +

    Return to database documentation

    + + +
    + +
    +
    + + + +
    + + + +
    +
    +
    +
    + + + + + + + + \ No newline at end of file diff --git a/develop/de/spec/database/db_userd/index.html b/develop/de/spec/database/db_userd/index.html new file mode 100644 index 0000000..212a6d1 --- /dev/null +++ b/develop/de/spec/database/db_userd/index.html @@ -0,0 +1,3393 @@ + + + + + + + + + + + + + + + + + + + + + + userd - Friendica Dokumentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + +
    + + + + +
    + + +
    + +
    + + + + + + + + + +
    +
    + + + +
    +
    +
    + + + + + + +
    +
    +
    + + + +
    +
    +
    + + + +
    +
    +
    + + +
    +
    + + + + + + + + + + + + + +

    Table userd#

    +

    Deleted usernames

    +

    Fields#

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    FieldDescriptionTypeNullKeyDefaultExtra
    idsequential IDint unsignedNOPRINULLauto_increment
    usernamevarchar(255)NONULL
    +

    Indexes#

    + + + + + + + + + + + + + + + + + +
    NameFields
    PRIMARYid
    usernameusername(32)
    +

    Return to database documentation

    + + +
    + +
    +
    + + + +
    + + + +
    +
    +
    +
    + + + + + + + + \ No newline at end of file diff --git a/develop/de/spec/database/db_verb/index.html b/develop/de/spec/database/db_verb/index.html new file mode 100644 index 0000000..d56f22c --- /dev/null +++ b/develop/de/spec/database/db_verb/index.html @@ -0,0 +1,3393 @@ + + + + + + + + + + + + + + + + + + + + + + verb - Friendica Dokumentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + +
    + + + + +
    + + +
    + +
    + + + + + + + + + +
    +
    + + + +
    +
    +
    + + + + + + +
    +
    +
    + + + +
    +
    +
    + + + +
    +
    +
    + + +
    +
    + + + + + + + + + + + + + +

    Table verb#

    +

    Activity Verbs

    +

    Fields#

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    FieldDescriptionTypeNullKeyDefaultExtra
    idsmallint unsignedNOPRINULLauto_increment
    namevarchar(100)NO
    +

    Indexes#

    + + + + + + + + + + + + + + + + + +
    NameFields
    PRIMARYid
    namename
    +

    Return to database documentation

    + + +
    + +
    +
    + + + +
    + + + +
    +
    +
    +
    + + + + + + + + \ No newline at end of file diff --git a/develop/de/spec/database/db_worker-ipc/index.html b/develop/de/spec/database/db_worker-ipc/index.html new file mode 100644 index 0000000..f274d00 --- /dev/null +++ b/develop/de/spec/database/db_worker-ipc/index.html @@ -0,0 +1,3389 @@ + + + + + + + + + + + + + + + + + + + + + + worker-ipc - Friendica Dokumentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + +
    + + + + +
    + + +
    + +
    + + + + + + + + + +
    +
    + + + +
    +
    +
    + + + + + + +
    +
    +
    + + + +
    +
    +
    + + + +
    +
    +
    + + +
    +
    + + + + + + + + + + + + + +

    Table worker-ipc#

    +

    Inter process communication between the frontend and the worker

    +

    Fields#

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    FieldDescriptionTypeNullKeyDefaultExtra
    keyintNOPRINULL
    jobsFlag for outstanding jobsbooleanYESNULL
    +

    Indexes#

    + + + + + + + + + + + + + +
    NameFields
    PRIMARYkey
    +

    Return to database documentation

    + + +
    + +
    +
    + + + +
    + + + +
    +
    +
    +
    + + + + + + + + \ No newline at end of file diff --git a/develop/de/spec/database/db_workerqueue/index.html b/develop/de/spec/database/db_workerqueue/index.html new file mode 100644 index 0000000..125b8f0 --- /dev/null +++ b/develop/de/spec/database/db_workerqueue/index.html @@ -0,0 +1,3493 @@ + + + + + + + + + + + + + + + + + + + + + + workerqueue - Friendica Dokumentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + +
    + + + + +
    + + +
    + +
    + + + + + + + + + +
    +
    + + + +
    +
    +
    + + + + + + +
    +
    +
    + + + +
    +
    +
    + + + +
    +
    +
    + + +
    +
    + + + + + + + + + + + + + +

    Table workerqueue#

    +

    Background tasks queue entries

    +

    Fields#

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    FieldDescriptionTypeNullKeyDefaultExtra
    idAuto incremented worker task idint unsignedNOPRINULLauto_increment
    commandTask commandvarchar(100)YESNULL
    parameterTask parametermediumtextYESNULL
    priorityTask prioritytinyint unsignedNO0
    createdCreation datedatetimeNO0001-01-01 00:00:00
    pidProcess id of the workerint unsignedNO0
    executedExecution datedatetimeNO0001-01-01 00:00:00
    next_tryNext retrial datedatetimeNO0001-01-01 00:00:00
    retrialRetrial countertinyintNO0
    doneMarked 1 when the task was done - will be deleted laterbooleanNO0
    +

    Indexes#

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    NameFields
    PRIMARYid
    commandcommand
    done_command_parameterdone, command, parameter(64)
    done_executeddone, executed
    done_priority_retrial_createddone, priority, retrial, created
    done_priority_next_trydone, priority, next_try
    done_pid_next_trydone, pid, next_try
    done_pid_retrialdone, pid, retrial
    done_pid_priority_createddone, pid, priority, created
    +

    Return to database documentation

    + + +
    + +
    +
    + + + +
    + + + +
    +
    +
    +
    + + + + + + + + \ No newline at end of file diff --git a/develop/de/spec/database/index.html b/develop/de/spec/database/index.html new file mode 100644 index 0000000..8e03fc1 --- /dev/null +++ b/develop/de/spec/database/index.html @@ -0,0 +1,3576 @@ + + + + + + + + + + + + + + + + + + + + + + Database Tables - Friendica Dokumentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + +
    + + + + +
    + + +
    + +
    + + + + + + + + + +
    +
    + + + +
    +
    +
    + + + + + + +
    +
    +
    + + + +
    +
    +
    + + + +
    +
    +
    + + +
    +
    + + + + + + + + + + + +

    Database Tables#

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    TableComment
    2fa_app_specific_passwordTwo-factor app-specific _password
    2fa_recovery_codesTwo-factor authentication recovery codes
    2fa_trusted_browserTwo-factor authentication trusted browsers
    addonregistered addons
    apcontactActivityPub compatible contacts - used in the ActivityPub implementation
    application-markerTimeline marker
    application-tokenOAuth user token
    applicationOAuth application
    attachfile attachments
    cacheStores temporary data
    configmain configuration storage
    contact-relationContact relations
    contactcontact table
    convprivate messages
    conversationRaw data and structure information for messages
    delayed-postPosts that are about to be distributed at a later time
    diaspora-interactionSigned Diaspora Interaction
    endpointActivityPub endpoints - used in the ActivityPub implementation
    eventEvents
    fcontactDiaspora compatible contacts - used in the Diaspora implementation
    fsuggestfriend suggestion stuff
    groupprivacy groups, group info
    group_memberprivacy groups, member info
    gserver-tagTags that the server has subscribed
    gserverGlobal servers
    hookaddon hook registry
    inbox-entry-receiverReceiver for the incoming activity
    inbox-entryIncoming activity
    inbox-statusStatus of ActivityPub inboxes
    intro
    item-uriURI and GUID for items
    locks
    mailprivate messages
    mailacctMail account data for fetching mails
    managetable of accounts that can manage each other
    notificationnotifications
    notify-threads
    notify[Deprecated] User notifications
    oembedcache for OEmbed queries
    openwebauth-tokenStore OpenWebAuth token to verify contacts
    parsed_urlcache for 'parse_url' queries
    pconfigpersonal (per user) configuration storage
    permissionset
    photophoto storage
    post-categorypost relation to categories
    post-collectionCollection of posts
    post-contentContent for all posts
    post-delivery-dataDelivery data for items
    post-deliveryDelivery data for posts for the batch processing
    post-historyPost history
    post-linkPost related external links
    post-mediaAttached media
    post-question-optionQuestion option
    post-questionQuestion
    post-tagpost relation to tags
    post-thread-userThread related data per user
    post-threadThread related data
    post-user-notificationUser post notifications
    post-userUser specific post data
    postStructure for all posts
    processCurrently running system processes
    profileuser profiles data
    profile_fieldCustom profile fields
    push_subscriberUsed for OStatus: Contains feed subscribers
    registerregistrations requiring admin approval
    search
    sessionweb session storage
    storageData stored by Database storage backend
    subscriptionPush Subscription for the API
    tagtags and mentions
    user-contactUser specific public contact data
    userThe local users
    userdDeleted usernames
    verbActivity Verbs
    worker-ipcInter process communication between the frontend and the worker
    workerqueueBackground tasks queue entries
    + + +
    + +
    +
    + + + +
    + + + +
    +
    +
    +
    + + + + + + + + \ No newline at end of file diff --git a/develop/de/spec/protocol/dfrn-snap2.jpg b/develop/de/spec/protocol/dfrn-snap2.jpg new file mode 100644 index 0000000..5697732 Binary files /dev/null and b/develop/de/spec/protocol/dfrn-snap2.jpg differ diff --git a/develop/de/spec/protocol/dfrn2.odt b/develop/de/spec/protocol/dfrn2.odt new file mode 100644 index 0000000..b95f6cc Binary files /dev/null and b/develop/de/spec/protocol/dfrn2.odt differ diff --git a/develop/de/spec/protocol/dfrn2.pdf b/develop/de/spec/protocol/dfrn2.pdf new file mode 100644 index 0000000..f524c45 Binary files /dev/null and b/develop/de/spec/protocol/dfrn2.pdf differ diff --git a/develop/de/spec/protocol/dfrn2_contact_confirmation.png b/develop/de/spec/protocol/dfrn2_contact_confirmation.png new file mode 100644 index 0000000..e192ebc Binary files /dev/null and b/develop/de/spec/protocol/dfrn2_contact_confirmation.png differ diff --git a/develop/de/spec/protocol/dfrn2_contact_confirmation.svg b/develop/de/spec/protocol/dfrn2_contact_confirmation.svg new file mode 100644 index 0000000..8126850 --- /dev/null +++ b/develop/de/spec/protocol/dfrn2_contact_confirmation.svg @@ -0,0 +1 @@ +Friendica - Contact confirmationbob@example.comkaren@karenhompage.comnotifications.phpnotifications_content()------------------------------------------ This is the page where Karen see Bobs friendship request- the submit form redirects to Karens local dfrn_confirm page($dfrn_id, $contact_id, $intro_id are submitted)dfrn_confirm.phpdfrn_confirm_post()SCENARIO 1 ( no $_POST['source_url'] available)--------------------------------------------------------------------------------- contact data come either form $handsfree (if autoconfirm) orfrom $_POST- get all data about Karen form the user table[Note: Bob have been issued an ID (contact issue-id) when he firstrequested the friendship. Locate Bobs contact record. At thistime, his record will have both pending and blocked set to 1.There won't be any dfrn_id if this is a network follower, so usethe contact_id instead]- search for Bob in the contact table by contact_id, dfrn_id andissued-id not empty (for the uid -> Karens user id)- if network = dfrn-> create a new keypair (prvkey & pubkey) and update thecontact[Note: Generate a key pair for all further communications withthis person. We have a keypair for every contact, and a site keyfor unknown people. This provides a means to carry onrelationships with other people any single key is compromised. Itis a robust key. We're much more worried about key leakagethan anybody cracking it.]-> update Bobs contact record (in the contact table) with thegenerated prvkey-> encrypting the dfrn_id with Karens prvkey (Bob can decrypt iton the other and with Karens site-pubkey) and add it to thetransmit params.-> encrypting Karens profile url with Bobs site-pubkey (Bobcan decrypt it with his own private key) and add it to thetransmit params.-> add the above generated public key to params whichgetting transmitted (if $aes_allow -> encrypt the the public key)-> add duplex state and page-flags to the params-> send params to Bobs dfrn_confirm page ($res =Network::post($dfrn_confirm,$params);dfrn_confirm_post()SCENARIO 2 ( $_POST['source_url'] is available)------------------------------------------------------------------------- get all data about Bob from the user table (prvkey and uid formBob )- decrypt the transmitted source_url (profile url) with Bobsprvkey- get data of Karen from contact table by her source_url (and byher user id)- decrypt the dfrn_id sent by Karen with Karens site-pubkey(taken from contact table)- if possible decrpyt the pubkey sent by Karen with the prvkey ofBob (taken from user table) -> if this is not possible use the rawpubkey- search if the dfrn_id is already present in the contact table (if itis prensent it is a duplicate)- update dfrn-id and pubkey for Karens contact entry in thecontact table-> set the relation for the contact and set pending = 0 andblocked = 0- update the relationship of the contact Karen-> if duplex delete the issued-id-> set blocked = 0 and pending = 0send a notificationdelete the intro of BobNote: this chart respects only dfrncontacts and focuses on key exchange(for other areas it might be veryincomplete) \ No newline at end of file diff --git a/develop/de/spec/protocol/dfrn2_contact_request.png b/develop/de/spec/protocol/dfrn2_contact_request.png new file mode 100644 index 0000000..7ca6bca Binary files /dev/null and b/develop/de/spec/protocol/dfrn2_contact_request.png differ diff --git a/develop/de/spec/protocol/dfrn2_contact_request.svg b/develop/de/spec/protocol/dfrn2_contact_request.svg new file mode 100644 index 0000000..9b75acd --- /dev/null +++ b/develop/de/spec/protocol/dfrn2_contact_request.svg @@ -0,0 +1 @@ +Friendica - Contact requestkarenn@karenhompage.combob@example.comdfrn_request.php-https://karenhompage/dfrn_request/karindfrn_request_post - SCENARIO 1----------------------------------------------- Cleanup old introductions that remain blocked + Cleanupany old email intros - which will have a greater lifetime- Probe::uri Bobs posted dfrn_url and get the network withwebfinger_dfrn- try to select all contact data of Bob (contact table) by theurl ($_POST['dfrn_url] and profile uid ($a->profile['uid'])where self = 0 to look if this contact is already there (ifissued-id or rel is already available return here because itseems that we are already connected)- create a issued-id with $issued_id = Strings::getRandomHex();- if we already found a contact record above update theissued-id with the one we have created- otherwise if Bob is not already in the contact table scrapeBobs profile and create a new contact with this data (e.g.the scraped issued-id / profiles pubkey becomes contactssite-pubkey) in the contact table (blocked = 1, pending = 1)- select this created contact from contact table and createan intro in the intro table (blocked = 1)$_POST['dfrn_url'] is transmited and is Bobs profile urlredirect to Bobs request pagegoaway($parms['dfrn-request'] . "?dfrn_url=$dfrn_url". '&dfrn_version=' .DFRN_PROTOCOL_VERSION. '&confirm_key=' . $hash. (($aes_allow) ? "&aes_allow=1" : ""));http://example.com/dfrn_request/bob?dfrn_url=687474703a2f2f6b6172656e686f6d65706167652e636f6d2f70726f66696c652f6b6172656e&aes_allow=1&confirm_key=”ABC123”dfrn_request.phphttp://example.com/dfrn_request/bob?dfrn_url=687474703a2f2f6b6172656e686f6d65706167652e636f6d2f70726f66696c652f6b6172656e&aes_allow=1&confirm_key=”ABC123”dfrn_request_content()------------------------------------------- copy the posted parameters (dfrn_url, key and so on)to $_POSTdfrn_request_post() - SCENARIO 2($_POST['localconfirm'] == 1)------------------------------------------------------------------------ if(local_user() && ($a->user['nickname'] == $a->argv[1]) && !empty($_POST['dfrn_url']))->- $confirm_key comes from $_POST- get data for contact Karen (contact table) by$dfrn_url (contacts url and nurl) -> if contact Karendoes already have a dfrn-id Bob seems alreadyconnected with Karen (abort here)- if this contact (Karen) isn't available in the contacttabel, scrape Karens profile page to pick up the dfrnlinks, key, fn, and photo- create a contact for Karen in the contact table withthe scraped data with blocked = 1 and pending = 1(Karens pubkey becomes the contact site-pubkey)- Network::fetchUrl($dfrn_request . '?confirm_key=' .$confirm_key);- Network::fetchUrl(http://karenhomepage.com/dfrn_request?confirm_key=”ABC123”)dfrn_request.phphttp://karenhomepage.com/dfrn_request?confirm_key=”ABC123”dfrn_request_content() -elseif (!empty($_GET['confirm_key']))----------------------------------------------------------------------------------------------- select the intro by confirm_key (intro table) -> get contact id- use the intro contact id to get the contact in the contact table- build a notification package ( notification(array.....) )- update intro in intro table (blocked = 0)Bob stays on his Friendica server- goaway($forwardurl);Note: this chart respects only dfrncontacts and focuses on key exchange(for other areas it might be veryincomplete)dfrn_request_content()------------------------------------- the page for the on Katrins server where Bob do a connectionrequest- the form transmit on submit Bobs profile url as dfrn_urlbob wants to make a request and is directed from karens profile page to karens dfrn-request pageredirict to bobs dfrn_request pagehttp://karenhomepage.com/dfrn_request?confirm_key=”ABC123”Bob fills request form and presses submit \ No newline at end of file diff --git a/develop/de/spec/protocol/message-flow/index.html b/develop/de/spec/protocol/message-flow/index.html new file mode 100644 index 0000000..cbea9ba --- /dev/null +++ b/develop/de/spec/protocol/message-flow/index.html @@ -0,0 +1,3418 @@ + + + + + + + + + + + + + + + + + + + + + + Nachrichtenfluss - Friendica Dokumentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + +
    + + + + +
    + + +
    + +
    + + + + + + + + + +
    +
    + + + +
    +
    +
    + + + + + + +
    +
    +
    + + + + + + +
    +
    + + + + + + + + + + + + + +

    Friendica Nachrichtenfluss#

    +

    Diese Seite soll einige Informationen darüber dokumentieren, wie Nachrichten innerhalb von Friendica von einer Person zur anderen übertragen werden. +Es gibt verschiedene Pfade, die verschiedene Protokolle und Nachrichtenformate nutzen.

    +

    Diejenigen, die den Nachrichtenfluss genauer verstehen wollen, sollten sich mindestens mit dem DFRN-Protokoll (Dokument mit den DFRN Spezifikationen) und den Elementen zur Nachrichtenverarbeitung des OStatus Stack informieren (salmon und Pubsubhubbub).

    +

    Wenn eine Nachricht veröffentlicht wird, werden alle Übermittlungen an alle Netzwerke mit include/notifier.php durchgeführt, welche entscheidet, wie und an wen die Nachricht geliefert wird. +Diese Datei bindet dabei die lokale Bearbeitung aller Übertragungen ein inklusive dfrn-notify.

    +

    mod/dfrn_notify.php handhabt die Rückmeldung (remote side) von dfrn-notify.

    +

    Lokale Feeds werden durch mod/dfrn_poll.php generiert - was ebenfalls die Rückmeldung (remote side) von dfrn-notify handhabt.

    +

    Salmon-Benachrichtigungen kommen via mod/salmon.php an.

    +

    PuSh-Feeds (pubsubhubbub) kommen via mod/pubsub.php an.

    +

    DFRN-poll Feed-Imports kommen via src/Worker/OnePoll.php als geplanter Task an, das implementiert die lokale Bearbeitung (local side) des DFRN-Protokolls.

    +

    Szenario #1. Bob schreibt eine öffentliche Statusnachricht#

    +

    Dies ist eine öffentliche Nachricht ohne begrenzte Nutzerfreigabe, sodass keine private Übertragung notwendig ist. +Es gibt zwei Wege, die genutzt werden können - als bbcode an DFRN-Clients oder als durch den Server konvertierten HTML-Code (mit PuSH; pubsubhubbub). +Wenn ein PuSH-Hub einsatzfähig ist, nutzen DFRN-Poll-Clients vorrangig die Informationen, die durch den PuSH-Kanal kommen. +Sie fallen zurück auf eine tägliche Abfrage, wenn der Hub Übertragungsschwierigkeiten hat (das kann vorkommen, wenn der standardmäßige Google-Referenzhub genutzt wird). +Wenn kein spezifizierter Hub oder Hubs ausgewählt sind, werden DFRN-Clients in einer pro Kontakt konfigurierbaren Rate mit bis zu 5-Minuten-Intervallen abfragen. +Feeds, die via DFRN-Poll abgerufen werden, sind bbcode und können auch private Unterhaltungen enthalten, die vom Poller auf ihre Zugriffsrechte hin geprüft werden.

    +

    Szenario #2. Jack antwortet auf Bobs öffentliche Nachricht. Jack ist im Friendica/DFRN-Netzwerk.#

    +

    Jack nutzt dfrn-notify, um eine direkte Antwort an Bob zu schicken. +Bob erstellt dann einen Feed der Unterhaltung und sendet diesen an jeden, der an der Unterhaltung beteiligt ist und dfrn-notify nutzt. +Die PuSH-Hubs werden darüber informiert, dass neuer Inhalt verfügbar ist. Der/die Hub/s erhalten dann die neuesten Feeds und übertragen diese an alle Hub-Teilnehmer (die auch zu verschiedenen Netzwerken gehören können).

    +

    Szenario #3. Mary antwortet auf Bobs öffentliche Nachricht. Mary ist im Friendica/DFRN-Netzwerk.#

    +

    Mary nutzt dfrn-notify, um eine direkte Antwort an Bob zu schicken. +Bob erstellt dann einen Feed der Unterhaltung und sendet diesen an jeden, der an der Unterhaltung beteiligt ist (mit Ausnahme von Bob selbst; die Unterhaltung wird nun an Jack und Mary geschickt). +Die Nachrichten werden mit dfrn-notify übertragen. +PuSH-Hubs werden darüber informiert, dass neuer Inhalt verfügbar ist. +Der/die Hub/s erhalten dann die neuesten Feeds und übertragen sie an alle Hub-Teilnehmer (die auch zu verschiedenen Netzwerken gehören können).

    +

    Szenario #4. William antwortet auf Bobs öffentliche Nachricht. William ist in einem OStatus-Netzwerk.#

    +

    William nutzt salmon, um Bob über seine Antwort zu benachrichtigen. +Der Inhalt ist HTML-Code, der in das Salmon Magic Envelope eingebettet ist. +Bob erstellt dann einen Feed der Unterhaltung und sendet es an alle Friendica-Nutzer, die an der Unterhaltung beteiligt sind und dfrn-notify nutzen (mit Ausnahme von William selbst; die Unterhaltung wird an Jack und Mary weitergeleitet). +PuSH-Hubs werden darüber informiert, dass neuer Inhalt verfügbar ist. Der/die Hub/s erhalten dann die neuesten Feeds und übertragen sie an alle Hub-Teilnehmer (die auch zu verschiedenen Netzwerken gehören können).

    +

    Szenario #5. Bob schreibt eine private Nachricht an Mary und Jack.#

    +

    Die Nachricht wird sofort an Mary und Jack mithilfe von dfrn_notify geschickt. +Öffentliche Hubs werden nicht benachrichtigt. +Im Falle eines Timeouts wird eine erneute Verarbeitung angestoßen. +Antworten folgen dem gleichen Nachrichtenfluss wie öffentliche Antworten, allerdings werden die Hubs nicht darüber informiert, wodurch die Nachrichten niemals in öffentliche Feeds gelangen. +Die komplette Unterhaltung ist nur für Mary und Jack in ihren durch dfrn-poll personalisierten Feeds verfügbar (und für niemanden sonst).

    + + +
    + +
    +
    + + + +
    + + + +
    +
    +
    +
    + + + + + + + + \ No newline at end of file diff --git a/develop/de/spec/protocol/protocol/index.html b/develop/de/spec/protocol/protocol/index.html new file mode 100644 index 0000000..6f91cfe --- /dev/null +++ b/develop/de/spec/protocol/protocol/index.html @@ -0,0 +1,3401 @@ + + + + + + + + + + + + + + + + + + + + + + Protocols - Friendica Dokumentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + +
    + + + + +
    + + +
    + +
    + + + + + + + + + +
    +
    + + + +
    +
    +
    + + + + + + +
    +
    +
    + + + +
    +
    +
    + + + +
    +
    +
    + + +
    +
    + + + + + + + + + + + + + +

    Used Protocols#

    +

    Friendicas DFRN Protocol#

    + +

    ActivityStreams#

    +

    Friendica is using ActivityStreams in version 1.0 for its activities and object types. +Additional types are used for non-standard activities.

    + +

    Salmon#

    +

    Salmon is used as a message exchange protocol for replies and mentions.

    + +

    Portable Contacts#

    +

    Portable Contacts is used for friends lists.

    + +

    pubsubhubbub#

    +

    pubsubhubbub is used for OStatus.

    + + + +
    + +
    +
    + + + +
    + + + +
    +
    +
    +
    + + + + + + + + \ No newline at end of file diff --git a/develop/de/spec/protocol/zot-2012.txt b/develop/de/spec/protocol/zot-2012.txt new file mode 100644 index 0000000..3d939ba --- /dev/null +++ b/develop/de/spec/protocol/zot-2012.txt @@ -0,0 +1,182 @@ + +Initial cut at Zot-2012 protocol. This is a very rough draft of some very rough ideas and concepts. +It is not yet intended to be a definitive specification and many things like the security handshakes are yet to be specified precisely. + +All communications are https + + +First create a global unique userid + + +Site userid: +https://macgirvin.com/1 + +$guuid = Strings::base64UrlEncode(hash('whirlpool','https://macgirvin.com/1.' . mt_rand(1000000,9999999),1); + + +Then create a hashed site destination. + +$gduid = Strings::base64UrlEncode(hash('whirlpool', $guuid . 'https://macgirvin.com',1); + +These two keys will identify you as a person+site pair in the future. +You will also obtain a password upon introducing yourself to a site. +This can be used to edit locations in the future. You will always keep your global unique userid + + +The steps to connect with somebody are to first register your location with their site. +Then introduce yourself to the person. This contains flags for the desired relationship. +At some future time, they may confirm and adjust the relationship based on their comfort level. +Lack of confirmation is tantamount to denial. + +You can set either or both of FOLLOW and SHARE which indicates the relationship from your viewpoint. +They may do likewise. + +A relationship is based on you as a person and provided you register new locations with the site you can post from anywhere. +You do not need to register locations with each person, only with the site. + + +Introduce yourself to a site: + + +POST https://example.com/post + +{ +'type' => 'register' +'person' => $guuid +'address' => $gduid +'site' => 'https://macgirvin.com' +'info' => 'mike@macgirvin.com' +} + +Returns: + +{ +'success' => 'true' +'pass' => me_encrypt($random_string) +} + +--- +Add location +--- + +POST https://example.com/post + +{ +'type' => 'location' +'person' => $guuid +'address' => $new_gduid +'site' => 'https://newsite.com' +'info' => 'mike@newsite.com' +'pass' => me_encrypt($gduid . '.' . $pass) +} + +Returns: + +{ +'success' => 'true' +'pass' => me_encrypt($random_string) +} + +--- +Remove location +--- + +POST https://example.com/post + +{ +'type' => 'remove_location' +'person' => $guuid +'address' => $gduid +'pass' => me_encrypt($pass) +} + +Returns: + +{ +'success' => 'true' +'message' => 'OK' +} + + +------------ +Make friends +------------ +This message may be reversed/repeated by the destination site to confirm. +flags is the desired friendship bits. The same message may be used with different flags +to edit or remove a relationship. + + +POST https://example.com/post + +{ +'type' => 'contact' +'person' => $gduid +'address' => $guuid +'target' => 'bobjones@example.com' +'flags' => HIDDEN=0,FOLLOW=1,SHARE=1,NOHIDDEN=1,NOFOLLOW=0,NOSHARE=0 +'confirm' => me_encrypt($guuid . '.' . $pass) +} + +Returns: + +{ +'success' => 'true' +'message' => 'OK' +'flags' => PENDING=1 +} + + + + + + + +------- +Message +------- + +Passing messages is done asynchronously. This may (potentially) relieve a lot of the burden of distribution from the posting site. If you're on site 'A' and make a post, site 'A' just contacts any downstream sites and informs them that there is new content (via a $post_id). The downstream site initiates the actual data transfer. + + + + + +POST https://example.com/post + +{ +'type' => 'post' +'person' => $guuid +'address' => $gduid +'post' => $post_id +} + +Returns: +{ +'success' => 'true' +'message' => 'OK' +} + + +-------- +Callback +-------- + +POST https://macgirvin.com/post + +{ +'type' => 'retrieve' +'retrieve' => $post_id +'challenge' => you_encrypt('abc123') +'verify' => me_encrypt('xyz456' . '.' . $gduid) +} + +Returns: + +{ +'success' => 'true' +'message' => 'OK' +'response' => 'abc123' +'data' => encrypted or raw structured post +} + + diff --git a/develop/de/spec/protocol/zot.txt b/develop/de/spec/protocol/zot.txt new file mode 100644 index 0000000..2c3bbb1 --- /dev/null +++ b/develop/de/spec/protocol/zot.txt @@ -0,0 +1,362 @@ +This is the Zot! social communications protocol. + +Specification revision: 1 +2 October 2011 + +Mike Macgirvin +This specification is public domain. + +Zot is a framework for secure delivery of messages on the web based on +webfinger and encapsulating salmon. + +First read the salmon and salmon magic envelope specifications. Zot also +makes use of webfinger and ActivityStreams and several concepts from RFC822 +(email). Zot encompasses the zot delivery framework and the zid remote +access protocol. + +The current specification revision (1) is frozen until a reference +implementation is available. After that, any protocol changes will require a +change to the revision number. + +**************** +* Zot delivery * +**************** + +Format of a zot wrapper. This completely encapsulates a salmon magic envelope +and provides privacy protection, while defining a delivery envelope - a +concept familiar to email systems. All addresses in zot are webfinger +resolvable addresses containing zot endpoints and salmon public keys (zot +is a superset of salmon). + + + + + ((key)) + ((iv)) + ((env_key)) + ((env_iv)) + ((envelope)) + ((sender signature)) + AES-256-CBC + ((salmon)) + + + +zot:key +******* + +A suitable randomly generated encyption key of length 32 octets for encrypting +the salmon packet. This is then encrypted with the sender's private key and +base64url encoded. + +zot:iv +****** + +A suitable randomly generated initialisation vector of length 16 octets for +encrypting the salmon packet. This is then encrypted with the sender's private +key and base64url encoded. + +zot:env_key +*********** + +A suitable randomly generated encyption key of length 32 octets for encrypting +the envelope. This is then encrypted with the recipient's public key and +base64url encoded. For bulk deliveries, it is encrypted with the site bulk +delivery public key. + + +zot:env_iv +********** + +A suitable randomly generated initialisation vector of length 16 octets for +encrypting the envelope. This is then encrypted with the recipient's public +key and base64url encoded. For bulk deliveries, it is encrypted with the site +bulk delivery public key. + + +zot:env +******* + +This consists of RFC822-style header fields representing the sender and +recipient(s). Line lengths have no defined limit and RFC822 continuation +lines are not supported. If an inbound server is not able to process an +envelope or post due to size constraints, it SHOULD return a +"413 Entity too large" HTTP response. + +Example: + +Z-From: zot:bob@example.com +Z-Sender: zot:bob@example.com +Z-To: zot:alice@example.com + +Both "Z-From:" and "Z-Sender:" MUST be provided, and represent a single +webfinger address of the author and sender respectively. The webfinger +address for the From address MUST contain a discoverable salmon public key +which is needed to verify the enclosed salmon data. Sender is used to indicate +the webfinger identity responsible for transmitting this message. From +indicates the message author. + +In web-based social systems, a reply to a message SHOULD be conveyed to all of +the original message participants. Only the author of the original message +may know all the recipients (such as those contained in Bcc: elements). The +author of a message always provides 'From'. They MUST duplicate this +information as 'Sender' when posting a followup message. + +A reply to a given message MUST be sent to the From address of the original +post, and MAY be sent to any additional addresses in the recipient list. The +original post author MUST send the reply to all known recipients of the +original message, with their webfinger identity as Sender, and the +comment/reply author as From. + +Receiving agents SHOULD validate the From identity as the signer of the salmon +magic envelope, and MAY reject it. They SHOULD also verify the Sender signature +of the zot packet if it is different than the salmon signature. They MAY +reject the message if the Sender is not allowed in their "friend list", or if +they do not have a suitable relationship with the Sender, or if either +signature fails to validate. Rejected messages for one of these reasons SHOULD +be indicated with a "400 Bad Request" HTTP response. + + +Z-To: * + +indicates a public message with no specifically enumerated recipients. + +The fields Z-To: and/or Z-Bcc: MAY be present. At least one recipient field +MUST be present. + +Z-To: zot:bob@example.com, zot:alice@example.com, mailto:dave@example.com +Z-Bcc: zot:https://example.com/profile/richard + +are valid entries. Adresses are comma separated and individual entries MUST NOT +contain commas. There MAY be any number of ASCII space characters between +entries for legibility. Header lines are terminated with a linefeed character +(ASCII 0x0A). + +This specification provides the following protocol address prefixes +for use in Z-To: or Z-Bcc: elements: + +zot: - normal zot delivery using webfinger or LRDD resolvable address +dfrn: - legacy DFRN mode delivery using webfinger or LRDD resovable address +ostatus: - normal OStatus delivery using webfinger or LRDD resovable address +diaspora: - Diaspora network delivery using webfinger address +facebook: - Facebook profile page URL +twitter: - Twitter personal page URL without AJAX '#!' fragment +mailto: - email RFC822/ESMTP address + +Examples: + +twitter:http://twitter.com/bjensen +facebook:http://facebook.com/profile.php?id=000000001 + +Foreign protocol addresses which have not been defined in this specification +or future revisions of this specification and which are unknown to the +recipient delivery process MAY be ignored. + +In cases where an address may contain either a webfinger or LRDD address, the +webfinger address SHOULD be used preferentially. + + +Z-Bcc: +****** + +The Z-Bcc element may contain one or more addresses which are hidden from end +user presentation. A zot receiving system MUST NOT store or allow for +the display of the Bcc information. Implementations which require extreme +privacy SHOULD send individual posts to each of the Bcc: recipients containing +only a single address. They MAY send all Bcc: posts using bulk delivery, +however this may have privacy implications as there is no guarantee a +receiving system will not log, store, or otherwise reveal the contents of the +Bcc recipient list. + +Z-To: addresses MAY be shown to an end user. + + +Envelope encryption +******************* + + +The entire envelope is encrypted using alg with env_key and env_iv and +base64url encoded for transmission. + +The zot envelope MAY include remote addresses. A zot inbound delivery agent +MUST parse the envelope and determine whether a delivery address to the +current endpoint is valid. This may be the result of: + + 1. An address contains the public message wildcard '*' + + 2. The current endpoint is a personal endpoint and one of the recipients +listed in the Z-To: or Z-Bcc: addresses matches the webfinger address of +the "owner" of the endpoint. + + 3. The current endpoint is a bulk delivery endpoint. The bulk delivery +endpoint is defined elsewhere in this document. The bulk delivery agent +will deliver to all local addresses found in the address lists. + +zot:sig +******* + +The Sender of the message signs the underlying salmon data in the manner +prescribed by salmon. If the Sender and From address are identical, the +signature will be identical to the signature of the underlying salmon packet. +If they are different, this signature is verified with the Sender's public +key to verify the Sender. + +zot:alg +******* + +Currently the only valid choice for alg is "AES-256-CBC". + + +zot:data +******** + +The data field is a salmon magic envelope. This is encrypted with alg using +key and iv. The result is then base64url encoded for transmission. + +For the first release of this specification, the data format of the enclosed +salmon SHOULD be 'application/atom+xml' representing an Atom formatted +ActivityStream. Future revisions MAY allow other alternate data formats. +All acceptable formats MUST be listed in an XRD property (described elsewhere +in this document). + + +Delivery +******** + +The zot message is then POSTed to the zot endpoint URL as +application/text+xml and can be decoded/decrypted by the recipient using +their private key. + +The normal salmon endpoint for a service MAY be used as an alternate +delivery method for non-encrypted (e.g. public) messages. + +Discover of the zot endpoint is based on webfinger XRD: + + + + +Bulk Delivery +************* + +A site MAY provide a bulk delivery endpoint, which MAY be used to avoid +multiple encryptions of the same data for a single destination. +This is discoverable by providing a zot endpoint with a corresponding +salmon public key in the site's .well-known/host-meta file. +A delivery to this endpoint will deliver to all local recipients provided +within the zot envelope. + + +Extensibility +************* + +This specification is subject to change. The current version which is in +effect at a given site may be noted by XRD properties. The following +properties MUST be present in the XRD providing the relevant endpoint: + +1 +application/atom+xml + + +Version is specified in this document and indicates the current revision. +Version is an increasing non-zero integer value. There are no minor versions. +Implementations MAY provide compatibility to multiple incompatible versions +by using this version indication. The "accept" indicates a range of document +content types which may be enclosed in the underlying salmon magic envelope. +We anticipate this specification will in the future allow for a close variant +of "message/rfc822" and which may include MIME. This may also be used to +embed alternate message formats and protocols such as +"application/x-diaspora+xml". If a delivery agent is unable to provide any +acceptable data format to the remote system, the delivery to that system MUST +be terminated/cancelled. + +Foreign Messages +**************** + +Messages MAY be imported from other networks and systems which have no +knowledge of salmon signatures. The salmon signature in this case MUST be the +exact string 'NOTSIGNED' to indicate that the author (From address) cannot be +validated using salmon verification. This message MUST be relayed by a Sender +who can provide a valid salmon signature of the message via zot:sig. Delivery +systems MAY reject foreign messages. + + + + + +******************************* +* Zid (Zot-ID) authentication * +******************************* + +This section of the document is considered separate from the delivery +specification precding it and represents a different protocol, which is +currently incomplete. This will be split off into another document in the +future, but is presented here as a synergistic component of the Zot network +model. + + +URLs may be present within a zot message which refer to private and/or +protected resources. Zid uses OpenID to gain access to these protected +resources. These could be private photos or profile information - or *any* +web accessible resource. Using zid, these can have access controls which +extends to any resolvable webfinger address. + +Zid authentication relies on the presence of an OpenID provider element in +webfinger, and a URL template which is applied to protected resources within +a zot message. + +The template is designated with the characters "{zid=}" within a URL of a zot +message. When the page is rendered for viewing to an observer, this template +is replaced with the webfinger address of the viewer (if known), or an empty +string if the webfinger address of the viewer cannot be determined. + +For example in a message body: + +http://example.com/photos/bob/picture.jpg?{zid=} + +refers to a private photo which is only visible to alice@example.com. + +If Alice is viewing the page, the link is rendered with + +http://example.com/photos/bob/picture.jpg?zid=alice@example.com + +If the page viewer is unknown, it is rendered as + +http://example.com/photos/bob/picture.jpg?zid= + + +When the link is visited, the web server at example.com notes the presence of +the zid parameter and uses information from webfinger to locate the OpenID +provider for the zid webfinger address. It then redirects to the OpenID +server and requests authentication of the given person. If this is successful, +access to the protected resource is granted. + +A browser cookie may be provided to avoid future authentication redirects +and allow authenticated browsing to other resources on the website. + +Only authentication via OpenID is defined in this version of the specification. + +This can be used to provide access control of any web resource to any +webfinger identity on the internet. + + +********* +* Links * +********* + +Salmon Protocol + http://www.salmon-protocol.org/salmon-protocol-summary + +Salmon Magic Envelope + http://salmon-protocol.googlecode.com/svn/trunk/draft-panzer-magicsig-01.html + +Atom Activity Stream Draft + http://activitystrea.ms/specs/atom/1.0/ + +Activty Stream Base Schema + http://activitystrea.ms/head/activity-schema.html + +WebFinger Protocol + http://code.google.com/p/webfinger/wiki/WebFingerProtocol + + diff --git a/develop/de/user/accesskeys/index.html b/develop/de/user/accesskeys/index.html new file mode 100644 index 0000000..7bfa1b4 --- /dev/null +++ b/develop/de/user/accesskeys/index.html @@ -0,0 +1,3520 @@ + + + + + + + + + + + + + + + + + + + + + + Accesskeys - Friendica Dokumentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + +
    + + + + +
    + + +
    + +
    + + + + + + + + + +
    +
    + + + +
    +
    +
    + + + + + + +
    +
    +
    + + + +
    +
    +
    + + + +
    +
    +
    + + +
    +
    + + + + + + + + + + + + + +

    Access-keys reference#

    +

    Access keys are keyboard shortcuts that allow you to easily navigate the user interface. +Access keys are currently not available with the Frio theme.

    +

    The specific key combinations depend on how your browser's the modifier key setting. +For an overview of modifier keys in different browsers, have a look at Wikipedia article. +For example, for moving to profile page in Firefox, press these three keys simultaneously.

    +

    [Shift] [Alt] [p]

    +

    General#

    +
      +
    • p - Profile
    • +
    • n - Network
    • +
    • c - Community
    • +
    • s - Search
    • +
    • a - Admin
    • +
    • f - Notifications
    • +
    • u - User menu
    • +
    +

    community#

    +
      +
    • l - Local community
    • +
    • g - Global community
    • +
    +

    profile#

    +
      +
    • m - Status Messages and Posts
    • +
    • r - Profile Details
    • +
    • h - Photo Albums
    • +
    • d - Media
    • +
    • e - Events and Calendar
    • +
    • t - Personal Notes
    • +
    • o - Scheduled Posts
    • +
    • k - View Contacts
    • +
    +

    contacts (contact list)#

    +
      +
    • g - Suggestions
    • +
    • l - Show all Contacts
    • +
    • o - Only show unblocked contacts
    • +
    • b - Only show blocked contacts
    • +
    • i - Only show ignored contacts
    • +
    • y - Only show archived contacts
    • +
    • h - Only show hidden contacts
    • +
    • e - Edit contact groups
    • +
    +

    contact (single contact view)#

    +
      +
    • m - Status messages
    • +
    • p - Posts and Comments
    • +
    • d - Media
    • +
    • o - Profile
    • +
    • t - Contacts
    • +
    • r - Advanced
    • +
    +

    message#

    +
      +
    • m - New message
    • +
    +

    network#

    +
      +
    • e - Sort by Comment Date
    • +
    • t - Sort by Receipt Date
    • +
    • q - Sort by Creation Date
    • +
    • r - Conversation (Posts that mention or involve you)
    • +
    • w - New posts
    • +
    • m - Favourite Posts
    • +
    +

    notifications#

    +
      +
    • y - System
    • +
    • w - Network
    • +
    • r - Personal
    • +
    • h - Home
    • +
    • i - Introductions
    • +
    +

    settings#

    +
      +
    • o - Account
    • +
    • 2 - Two-factor authentication
    • +
    • p - Profiles
    • +
    • t - Additional features
    • +
    • w - Social Networks
    • +
    • l - Addons
    • +
    • d - Delegations
    • +
    • b - Connected apps
    • +
    • e - Export personal data
    • +
    • r - Remove account
    • +
    + + +
    + +
    +
    + + + +
    + + + +
    +
    +
    +
    + + + + + + + + \ No newline at end of file diff --git a/develop/de/user/account-basics/index.html b/develop/de/user/account-basics/index.html new file mode 100644 index 0000000..d091db3 --- /dev/null +++ b/develop/de/user/account-basics/index.html @@ -0,0 +1,3620 @@ + + + + + + + + + + + + + + + + + + + + + + Grundlagen - Friendica Dokumentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + +
    + + + + +
    + + +
    + +
    + + + + + + + + + +
    +
    + + + +
    +
    +
    + + + + + + +
    +
    +
    + + + +
    +
    + +
    +
    + + +
    +
    + + + + + + + + + + + + + +

    Account - Basics#

    +

    Registrierung#

    +

    Viele, aber nicht alle Friendica-Knoten (Server) bieten die Möglichkeit zur Registrierung an. +Falls der Friendica-Knoten, den Du besuchst, keine Registrierung anbietet, oder Du glaubst, dass Dir ein anderer Knoten möglicherweise besser gefällt, dann findest Du hier eine Liste von öffentlichen Friendica-Knoten, aus der Du Dir einen netten Knoten heraussuchen kannst.

    +

    Auf der Startseite des Knotens wird unter dem Login-Feld ein "Registrieren"-Link angezeigt. +Dieser Link führt dann direkt auf das Registrierungsformular.

    +

    OpenID#

    +

    Falls du keine OpenID-Adresse hast, kannst du diesen Punkt ignorieren.

    +

    Solltest du eine OpenID Adresse haben, kannst du sie im ersten Feld eintragen und "Registrieren" klicken. +Friendica wird versuchen, so viele Informationen wie möglich von deinem OpenID-Provider zu übernehmen, um diese in dein Profil auf dieser Seite einzutragen.

    +

    Dein vollständiger Name#

    +

    Bitte trage bei "vollständiger Name" Deinen gewünschten Namen ein, wie er über deinen Beiträgen angezeigt werden soll. +Du kannst deinen echten Namen eintragen, kannst dir aber auch einen Namen ausdenken. Einen Zwang zu dem sogenannten Klarnamen gibt es nicht.

    +

    Email-Adresse#

    +

    Bitte trage eine richtige E-Mail-Adresse ein. +Dies ist die einzige persönliche Information, die korrekt sein muss. +Deine E-Mail-Adresse wird niemals veröffentlicht.

    +

    Wir benötigen diese, um Dir Account-Informationen, das Initialpasswort und die Login-Daten zu schicken. Oder z.B. Dein Passwort zurückzusetzen.

    +

    Du erhältst zudem von Zeit zu Zeit Benachrichtigungen über eingegangene Nachrichten oder Punkte, die Deine Aufmerksamkeit benötigen. +Diese Nachrichten sind in den Einstellungen jederzeit an- oder abschaltbar.

    +

    Spitzname/Nickname#

    +

    Der Spitzname wird benötigt, um eine Webadresse (Profiladresse) für viele Deiner persönlichen Seiten zu erstellen. +Auch wird dieser wie eine E-Mail-Adresse genutzt, wenn eine Verbindung zu anderen Personen hergestellt werden soll. +Durch die Art, wie der Spitzname genutzt wird, gibt es bestimmte Einschränkungen:

    +
      +
    • er muss mit einem Buchstaben beginnen
    • +
    • er darf nur US-ASCII-Textzeichen und Nummern enthalten
    • +
    • er muss einzigartig auf diesem Friendica-Knoten sein
    • +
    • er kann später nicht mehr geändert werden
    • +
    +

    Dieser Spitzname wird an vielen Stellen genutzt, um Deinen Account zu identifizieren, daher ist es nicht möglich ihn später zu ändern.

    +

    Verzeichnis-Eintrag#

    +

    Das Registrierungsformular erlaubt es dir, direkt auszuwählen, ob du im Onlineverzeichnis (Friendica Directory) aufgelistet wirst oder nicht. +Das ist wie ein Telefonbuch und du entscheidest, ob du darin eingetragen werden möchtest.

    +
      +
    • Wir bitten dich, "Ja" zu wählen, damit Andere dich finden können, so wie du sie finden kannst
    • +
    • Wählst Du "Nein", bist Du für Andere nicht einfach auffindbar
    • +
    +

    Was auch immer Du wählst, kann jederzeit nach dem Login in Deinen Account-Einstellungen geändert werden.

    +

    Registrierung#

    +

    Sobald Du die nötigen Informationen eingegeben hast, klicke auf "Registrieren". +Eine E-Mail mit den Registrierungsdetails und Deinem Initialpasswort wird an die hinterlegte E-Mail-Adresse geschickt. +Bitte prüfe den Posteingang (inklusive dem Spam-Ordner).

    +

    Login-Seite#

    +

    Gib auf der "Login"-Seite die Informationen ein, die Du mit der oben genannten E-Mail erhalten hast. +Du kannst entweder Deinen Spitznamen oder die E-Mail-Adresse als Login-Namen nutzen.

    +

    Wenn Dein Account OpenID nutzt, dann kannst Du Deine OpenID-Adresse als Login-Name nutzen und das Passwort-Feld frei lassen. +Du wirst zu Deinem OpenID-Anbieter weitergeleitet, wo Du Deine Anmeldung abschließt.

    +

    Wenn Du OpenID nicht nutzt, dann gib Dein Passwort ein, das Du mit der Registrierungsmail erhalten hast. +Das Passwort muss genau so geschrieben werden, wie es in der E-Mail steht; Groß- und Kleinschreibung wird beachtet. +Falls Du Schwierigkeiten beim Login hast, prüfe bitte, ob z. B. Deine Feststelltaste aktiv ist.

    +

    Passwort ändern#

    +

    Besuche nach Deinem ersten Login bitte die Einstellungsseite und wechsle das Passwort in eines, dass Du Dir merken kannst.

    +

    Die ersten Schritte#

    +

    Persönliche Daten exportieren#

    +

    Du solltest dir als Erstes deinen neu erstellen "Account exportieren" unter Einstellungen/persönliche Daten exportieren und an einem sicheren Ort verwahren. +In diesem Export (JSON-Datei) sind enthalten

    +
      +
    • Deine Identität, die mit kryptographischen Schlüsseln ausgestattet ist
    • +
    • Deine Kontakte
    • +
    +

    Dies ist z.B. dann nützlich, wenn du mit deinem Account auf einen anderen Friendica Knoten umziehen willst, oder musst.

    +

    Hilfe für Neulinge#

    +

    Ein 'Tipp für neue Mitglieder' (https://your-site.info/newmember)-Link zeigt sich in den ersten beiden Wochen auf Deiner Startseite, um Dir erste Informationen zum Start zu bieten.

    +

    Schau Dir ebenfalls folgende Seiten an#

    + +

    Der eigene Friendica-Knoten#

    +

    Wenn Du Deinen eigenen Friendica-Knoten auf einem Server aufsetzen willst, kannst Du das ebenfalls machen. +Besuche die Friendica-Webseite, um den Code mit den Installationsanleitungen herunterzuladen. +Es ist ein einfacher Installationsprozess, den jeder mit ein wenig technischen Erfahrungen im Webseiten-Hosting oder mit grundlegenden Linux-Erfahrungen handhaben kann.

    + + +
    + +
    +
    + + + +
    + + + +
    +
    +
    +
    + + + + + + + + \ No newline at end of file diff --git a/develop/de/user/bbcode/index.html b/develop/de/user/bbcode/index.html new file mode 100644 index 0000000..aa368a1 --- /dev/null +++ b/develop/de/user/bbcode/index.html @@ -0,0 +1,4058 @@ + + + + + + + + + + + + + + + + + + + + + + BBCode - Friendica Dokumentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + +
    + + + + +
    + + +
    + +
    + + + + + + + + + +
    +
    + + + +
    +
    +
    + + + + + + +
    +
    +
    + + + +
    +
    +
    + + + +
    +
    +
    + + +
    +
    + + + + + + + + + + + + + +

    Referenz der Friendica BBCode Tags#

    +

    Inline#

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    BBCodeErgebnis
    [b]fett[/b]fett
    [i]kursiv[/i]kursiv
    [u]unterstrichen[/u]unterstrichen
    [s]durchgestrichen[/s]durchgestrichen
    [o]überstrichen[/o]überstrichen
    [color=red]rot[/color]rot
    [url=https://friendi.ca]Friendica[/url]Friendica
    [img]https://raw.githubusercontent.com/friendica/friendica/stable/images/friendica-32.png[/img]
    [img=https://raw.githubusercontent.com/friendica/friendica/stable/images/friendica-32.png]Das Friendica Logo[/img]Das Friendica Logo
    [img=64x32]https://raw.githubusercontent.com/friendica/friendica/stable/images/friendica.svg[/img]
    +
    Note: provided height is simply discarded.
    [size=xx-small]kleiner Text[/size]kleiner Text
    [size=xx-large]großer Text[/size]großer Text
    [size=20]exakte Größe[/size] (die Größe kann beliebig in Pixeln gewählt werden)exakte Größe
    [font=serif]Serife Schriftart[/font]Serife Schriftart
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    BBCodeErgebnis
    [url]https://friendi.ca[/url]https://friendi.ca
    [url=https://friendi.ca.com]Friendica[/url]Friendica
    [bookmark]https://friendi.ca[/bookmark]

    +#^[url]https://friendi.ca[/url]

    Friendica: https://friendi.ca

    [bookmark=https://friendi.ca]Lesezeichen[/bookmark]

    +#^[url=https://friendi.ca]Lesezeichen[/url]

    +#[url=https://friendi.ca]^[/url][url=https://friendi.ca]Lesezeichen[/url]

    Friendica: Lesezeichen

    [url=/posts/f16d77b0630f0134740c0cc47a0ea02a]Diaspora Beitrag mit GUID[/url]Diaspora Beitrag mit GUID
    #Friendica#Friendica
    @Erwähnung@Erwähnung
    acct:account@friendica.host.com (WebFinger)acct:account@friendica.host.com
    [mail]user@mail.example.com[/mail]user@mail.example.com
    [mail=user@mail.example.com]Eine E-Mail senden[/mail]Eine E-Mail senden
    + +

    Blocks#

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    BBCodeErgebnis
    [p]Ein Absatz mit Text[/p]

    Ein Absatz mit Text

    Eingebetteter [code]Programmcode[/code] im TextEingebetteter Programmcode im Text
    [code]Programmcode
    über
    mehrere
    Zeilen[/code]
    Programmcode +über +mehrere +Zeilen
    [code=php]function text_highlight($s,$lang)[/code]
    1. function text_highlight($s,$lang)
    [quote]Zitat[/quote]
    Zitat
    [quote=Autor]Autor? Ich? Nein, niemals...[/quote]Autor hat geschrieben:
    Autor? Ich? Nein, niemals...
    [center]zentrierter Text[/center]
    zentrierter Text
    Du solltest nicht weiter lesen, wenn du das Ende des Films nicht vorher erfahren willst. [spoiler]Es gibt ein Happy End.[/spoiler] +
    + Du solltest nicht weiter lesen, wenn du das Ende des Films nicht vorher erfahren willst.
    + Zum öffnen/schließen klicken + +
    +
    +
    [spoiler=Autor]Spoiler Alarm[/spoiler] +
    + Autor hat geschrieben
    + Zum öffnen/schließen klicken + +
    +
    +
    [hr] (horizontale Linie)
    + +

    Überschriften#

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    BBCodeErgebnis
    [h1]Titel 1[/h1]

    Titel 1

    [h2]Titel 2[/h2]

    Titel 2

    [h3]Titel 3[/h3]

    Titel 3

    [h4]Titel 4[/h4]

    Titel 4

    [h5]Titel 5[/h5]
    Titel 5
    [h6]Titel 6[/h6]
    Titel 6
    + +

    Tabellen#

    + + + + + + + + + + + + + + + + + +
    BBCodeErgebnis
    [table]
    + [tr]
    + [th]Kopfzeile 1[/th]
    + [th]Kopfzeile 2[/th]
    + [th]Kopfzeile 2[/th]
    + [/tr]
    + [tr]
    + [td]Zelle 1[/td]
    + [td]Zelle 2[/td]
    + [td]Zelle 3[/td]
    + [/tr]
    + [tr]
    + [td]Zelle 4[/td]
    + [td]Zelle 5[/td]
    + [td]Zelle 6[/td]
    + [/tr]
    +[/table]
    + + + + + + + + + + + + + + + + + + +
    Kopfzeile 1Kopfzeile 2Kopfzeile 3
    Zelle 1Zelle 2Zelle 3
    Zelle 4Zelle 5Zelle 6
    +
    [table border=0] + + + + + + + + + + + + + + + + + + +
    Kopfzeile 1Kopfzeile 2Kopfzeile 3
    Zelle 1Zelle 2Zelle 3
    Zelle 4Zelle 5Zelle 6
    +
    [table border=1] + + + + + + + + + + + + + + + + + + +
    Kopfzeile 1Kopfzeile 2Kopfzeile 3
    Zelle 1Zelle 2Zelle 3
    Zelle 4Zelle 5Zelle 6
    +
    + +

    Listen#

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    BBCodeErgebnis
    [ul]
    + [li] Erstes Listenelement
    + [li] Zweites Listenelement
    +[/ul]
    +[list]
    + [*] Erstes Listenelement
    + [*] Zweites Listenelement
    +[/list]
    +
      +
    • Erstes Listenelement
    • +
    • Zweites Listenelement
    • +
    +
    [ol]
    + [*] Erstes Listenelement
    + [*] Zweites Listenelement
    +[/ol]
    +[list=1]
    + [*] Erstes Listenelement
    + [*] Zweites Listenelement
    +[/list]
    +
      +
    • Erstes Listenelement
    • +
    • Zweites Listenelement
    • +
    +
    [list=]
    + [*] Erstes Listenelement
    + [*] Zweites Listenelement
    +[/list]
    +
      +
    • Erstes Listenelement
    • +
    • Zweites Listenelement
    • +
    +
    [list=i]
    + [*] Erstes Listenelement
    + [*] Zweites Listenelement
    +[/list]
    +
      +
    • Erstes Listenelement
    • +
    • Zweites Listenelement
    • +
    +
    [list=I]
    + [*] Erstes Listenelement
    + [*] Zweites Listenelement
    +[/list]
    +
      +
    • Erstes Listenelement
    • +
    • Zweites Listenelement
    • +
    +
    [list=a]
    + [*] Erstes Listenelement
    + [*] Zweites Listenelement
    +[/list]
    +
      +
    • Erstes Listenelement
    • +
    • Zweites Listenelement
    • +
    +
    [list=A]
    + [*] Erstes Listenelement
    + [*] Zweites Listenelement
    +[/list]
    +
      +
    • Erstes Listenelement
    • +
    • Zweites Listenelement
    • +
    +
    + +

    Einbetten#

    +

    Du kannst Videos, Musikdateien und weitere Dinge in Beiträgen einbinden.

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    BBCodeErgebnis
    [video]url[/video]Wobei die *url* eine URL von youtube, vimeo, soundcloud oder einer anderen Plattform sein kann, die die opengraph Spezifikationen unterstützt.
    [video]URL der Videodatei[/video] +[audio]URL der Musikdatei[/audio]Die komplette URL einer ogg/ogv/oga/ogm/webm/mp4/mp3 Datei angeben, diese wird dann mit einem HTML5-Player angezeigt.
    [youtube]Youtube URL[/youtube]Youtube Video mittels OEmbed anzeigen. Kann u.U, den Player nicht einbetten.
    [youtube]Youtube video ID[/youtube]Youtube-Player im iframe einbinden.
    [vimeo]Vimeo URL[/vimeo]Vimeo Video mittels OEmbed anzeigen. Kann u.U, den Player nicht einbetten.
    [vimeo]Vimeo video ID[/vimeo]Vimeo-Player im iframe einbinden.
    [embed]URL[/embed]OEmbed rich content einbetten.
    [url]*url*[/url]Wenn *url* die OEmbed- oder Opengraph-Spezifikationen unterstützt, wird das Objekt eingebettet (z.B. Dokumente von scribd). + Ansonsten wird der Titel der Seite mit der URL verlinkt.
    + +

    Karten#

    +

    Das Einbetten von Karten benötigt das "OpenStreetMap" oder das "Google Maps" Addon. +Wenn keines der Addons aktiv ist, werden stattdeßen die Koordinaten angezeigt-

    + + + + + + + + + + + + + + + + + +
    BBCodeErgebnis
    [map]Adresse[/map]Bindet eine Karte ein, auf der die angegebene Adresse zentriert ist.
    [map=lat,long]Bindet eine Karte ein, die auf die angegebenen Koordinaten zentriert ist.
    [map]Bindet eine Karte ein, die auf die Position des Beitrags zentriert ist.
    + +

    Zusammenfassungen für lange Beiträge#

    +

    Wenn du deine Beiträge auf anderen Netzwerken von Drittanbietern verbreiten möchtest, z.B. Twitter, könntest du Probleme mit deren Zeichenbegrenzung haben.

    +

    Friendica verwendet einen semi-intelligenten Mechanismus, um passende Zusammenfassungen zu erstellen. +Du kannst allerdings auch selbst die Zusammenfassungen erstellen, die auf den unterschiedlichen Netzwerken angezeigt werden. +Um dies zu tun, verwendest du den [abstract]-Tag.

    + + + + + + + + + +
    BBCodeErgebnis
    [abstract]Unglaublich interessant! Muss man gesehen haben! Unbedingt dem Link folgen![/abstract]
    +Ich möchte euch eine unglaublich langweilige Geschichte erzählen, die ihr sicherlich niemals hören wolltet.
    Auf Twitter würde folgender Text veröffentlicht werden
    Unglaublich interessant! Muss man gesehen haben! Unbedingt dem Link folgen!
    +Wohingegen auf Friendica folgendes stehen würde
    Ich möchte euch eine unglaublich langweilige Geschichte erzählen, die ihr sicherlich niemals hören wolltet.
    + +

    Wenn du magst, kannst du auch unterschiedliche Zusammenfassungen für die unterschiedlichen Netzwerke verwenden.

    + + + + + + + + + +
    BBCodeErgebnis
    +[abstract]Hey Leute, hier sind meines neuesten Bilder![/abstract]
    +[abstract=twit]Hallo liebe Twitter Follower. Wollt ihr meine neuesten Bilder sehen?[/abstract]
    +[abstract=apdn]Moin liebe Follower auf ADN. Ich habe einige neue Bilder gemacht, die ich euch gerne zeigen will.[/abstract]
    +Heute war ich im Wald unterwegs und habe einige wirklich schöne Bilder gemacht...
    Für Twitter und App.net wird Friendica in diesem Fall die speziell definierten Zusammenfassungen Verwenden. Für andere Netzwerke (wie z.B. bei der Verwendung des GNU Social Konnektors zum Veröffentlichen auf deinen GNU Social Account) würde die allgemeine Zusammenfassung verwenden.
    + +

    Wenn du beispielsweise den "buffer"-Konnektor verwendest um Beiträge nach Facebook und Google+ zu senden, dort aber nicht den gesamten Blogbeitrag posten willst, sondern nur einen Anreißer, kannst du dies mit dem [abstract]-Tag realisieren.

    +

    Bei Netzwerken wie Facebook oder Google+, die selbst kein Zeichenlimit haben wird das [abstract]-Element allerdings nicht grundsätzlich verwendet. +Daher müssen diese Netzwerke explizit genannt werden.

    + + + + + + + + + +
    BBCodeErgebnis
    +[abstract]Dieser Tage hatte ich eine ungewöhnliche Begegnung...[/abstract]
    +[abstract=goog]Hey liebe Google+ Follower. Habt ihr schon meinen neuesten Blog-Beitrag gelesen?[/abstract]
    +[abstract=face]Hallo liebe Facebook Freunde. Letztens ist mir etwas wirklich schönes paßiert.[/abstract]
    +Als ich die Bilder im Wald aufgenommen habe, hatte ich eine wirklich ungewöhnliche Begegnung...
    Auf Google und Facebook würde nun die entsprechende Zusammenfassung verbreitet. Für andere Netzwerke würde die allgemeine Zusammenfassung verwendet werden.
    +
    Auf Friendica wird weiterhin keine Zusammenfassung angezeigt.
    + +

    Für Verbindungen zu Netzwerken, zu denen Friendica den HTML Code postet, wie Tumblr, Wordpress oder Pump.io wird das [abstract] Element nicht verwendet. +Bei nativen Verbindungen; das heißt zu z.B. Friendica, Hubzilla, Diaspora oder GNU Social Kontakten; wird der ungekürzte Beitrag übertragen. +Die Instanz des Kontakts kümmert sich um die Darstellung.

    +

    Wird ein Beitrag über das ActivityPub Protokoll übermittelt, wird der Text des Abstracts für das "summary" (Zusammenfassung) Feld verwendet. +Dieses Feld wird von Mastodon für die Inhaltswarnung (content warning) verwendet.

    +

    Special#

    + + + + + + + + + + + + + + + + + + + + + +
    BBCodeErgebnis
    Wenn du verhindern möchtest, daß der BBCode in einer Nachricht interpretiert wird, kannst du die [noparse], [nobb] oder [pre] Tag verwenden:
    +
      +
    • [noparse][b]fett[/b][/noparse]
    • +
    • [nobb][b]fett[/b][/nobb]
    • +
    • [pre][b]fett[/b][/pre]
    • +
    +
    [b]fett[/b]
    [nosmile] kann verwendet werden, um für einen Beitrag das Umsetzen von Smilies zu verhindern.
    +
    + [nosmile] ;-) :-O +
    ;-) :-O
    Benutzerdefinierte Block-Styles
    +
    +[style=text-shadow: 0 0 4px #CC0000;]Du kannst alle CSS-Eigenschaften eines Blocks ändern-[/style]
    Du kannst alle CSS-Eigenschaften eines Blocks ändern.
    Benutzerdefinierte Inline-Styles
    +
    +[style=text-shadow: 0 0 4px #CC0000;]Du kannst alle CSS-Eigenschaften eines Blocks ändern-[/style]
    Du kannst alle CSS-Eigenschaften dieses Inline-Textes ändern-
    + + +
    + +
    +
    + + + +
    + + + +
    +
    +
    +
    + + + + + + + + \ No newline at end of file diff --git a/develop/de/user/chats/index.html b/develop/de/user/chats/index.html new file mode 100644 index 0000000..faf9f38 --- /dev/null +++ b/develop/de/user/chats/index.html @@ -0,0 +1,3413 @@ + + + + + + + + + + + + + + + + + + + + + + Chats - Friendica Dokumentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + +
    + + + + +
    + + +
    + +
    + + + + + + + + + +
    +
    + + + +
    +
    +
    + + + + + + +
    +
    +
    + + + +
    +
    +
    + + + +
    +
    +
    + + +
    +
    + + + + + + + + + + + + + +

    Chats#

    +

    du hast derzeit zwei Möglichkeiten, einen Chat auf deiner Friendica-Seite zu betreiben

    +
      +
    • IRC - Internet Relay Chat
    • +
    • Jappix
    • +
    +

    IRC Addon#

    +

    Sobald das Addon aktiviert ist, kannst du den Chat unter https://deineSeite.de/irc finden. +Beachte aber, dass dieser Chat auch ohne Anmeldung auf deiner Seite zugänglich ist und somit auch Fremde diesen Chat mitnutzen können.

    +

    Wenn du dem Link folgst, dann kommst du zum Anmeldefenster des IR-Chats. +Wähle nun einen Spitznamen (Nickname) und wähle einen Raum aus, in dem du chatten willst. +Hier kannst du jeden Namen eingeben. +Es kann also auch #tollerChatdessenNamenurichkenne sein. +Gib als Nächstes noch die Captchas ein, um zu zeigen, dass es sich bei dir um einen Menschen handelt und klicke auf "Connect".

    +

    Im nächsten Fenster siehst du zunächst viel Text beim Verbindungsaufbau, der allerdings für dich nicht weiter von Bedeutung ist. +Anschließend öffnet sich das Chat-Fenster. +In den ersten Zeilen wird dir dein Name und deine aktuelle IP-Adresse angezeigt. +Rechts im Fenster siehst du alle Teilnehmer des Chats. +Unten hast du ein Eingabefeld, um Beiträge zu schreiben.

    +

    Weiter Informationen zu IRC findest du zum Beispiel auf ubuntuusers.de, in Wikipedia oder bei icrhelp.org (in Englisch).

    +

    Jappix Mini#

    +

    Das Jappix Mini Addon erlaubt das Erstellen einer Chatbox für Jabber/XMPP-Kontakte. +Ein Jabber/XMPP Account sollte vor der Installation bereits vorhanden sein. +Die ausführliche Anleitung dazu und eine Kontrolle, ob du nicht sogar schon über deinen E-Mail-Anbieter einen Jabber-Account hast, findest du unter einfachjabber.de.

    +

    Einige Server zum Anmelden eines neuen Accounts:

    + +

    1. Grundsätzliches#

    +

    Als Erstes musst du die aktuellste Version herunterladen:

    +

    Per Git: +

    cd /var/www/<Pfad zu deiner friendica-Installation>/addon
    +git pull
    +

    +

    oder als normaler Download von hier: https://github.com/friendica/friendica-addons/blob/stable/jappixmini.tgz (auf „view raw“ klicken)

    +

    Entpacke diese Datei (ggf. den entpackten Ordner in „jappixmini“ umbenennen) und lade sowohl den entpackten Ordner komplett als auch die .tgz Datei in den Addon Ordner deiner Friendica Installation hoch.

    +

    Nach dem Upload gehts in den Friendica Adminbereich und dort zu den Addons. +Aktiviere das Jappixmini Addon und gehe anschließend über die Addons Seitenleiste (dort wo auch die Twitter-, Impressums-, GNU Social-, usw. Einstellungen gemacht werden) zu den Jappix Grundeinstellungen.

    +

    Setze hier den Haken zur Aktivierung des BOSH Proxys. +Weiter gehts in den Einstellungen deines Friendica Accounts.

    +

    2. Einstellungen#

    +

    Gehe bitte zu den Addon-Einstellungen in deinen Konto-Einstellungen (Account Settings). +Scrolle ein Stück hinunter bis zu den Jappix Mini Addon settings.

    +

    Aktiviere hier zuerst das Addon.

    +

    Trage nun deinen Jabber/XMPP Namen ein, ebenfalls die entsprechende Domain bzw. den Server (ohne http, also z. B. einfach so: jappix.com). +Um das JavaScript zum Chatten im Browser verwenden zu können, benötigst du einen BOSH Proxy. +Entweder betreibst du deinen eigenen (s. Dokumentation deines XMPP Servers) oder du verwendest einen öffentlichen BOSH Proxy. +Beachte aber, dass der Betreiber dieses Proxies den kompletten Datenverkehr über den Proxy mitlesen kann. +Siehe dazu auch die „Configuration Help“ unter den Eingabefeldern. +Gebe danach noch dein Passwort an, und damit ist eigentlich schon fast alles geschafft. +Die weiteren Einstellmöglichkeiten bleiben dir überlassen, sind also optional. +Jetzt noch auf „senden“ klicken und fertig.

    +

    deine Chatbox sollte jetzt irgendwo unten rechts im Browserfenster „kleben“. +Falls du manuell Kontakte hinzufügen möchtest, einfach den „Add Contact“-Knopf nutzen.

    +

    Viel Spass beim Chatten!

    + + +
    + +
    +
    + + + +
    + + + +
    +
    +
    +
    + + + + + + + + \ No newline at end of file diff --git a/develop/de/user/connectors/index.html b/develop/de/user/connectors/index.html new file mode 100644 index 0000000..65cab7e --- /dev/null +++ b/develop/de/user/connectors/index.html @@ -0,0 +1,3308 @@ + + + + + + + + + + + + + + + + + + + + + + Konnektoren - Friendica Dokumentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + +
    + + + + +
    + + +
    + +
    + + + + + + + + + +
    +
    + + + +
    +
    +
    + + + + + + +
    +
    +
    + + + +
    +
    +
    + + + +
    +
    +
    + + +
    +
    + + + + + + + + + + + + + +

    Konnektoren (Connectors)#

    +

    Konnektoren erlauben es dir, dich mit anderen sozialen Netzwerken zu verbinden. +Konnektoren werden nur bei bestehenden Twitter und GNU Social-Accounts benötigt. +Außerdem gibt es einen Konnektor, um deinen E-Mail-Posteingang zu nutzen. +Wenn du keinen eigenen Knoten betreibst und wissen willst, ob der server deiner Wahl diese Konnektoren installiert hat, kannst du dich darüber auf der Seite '<domain_des_friendica-servers>/friendica' informieren.

    +

    Sind die Netzwerk-Konnektoren auf deinem System installiert sind, kannst du mit den folgenden Links die Einstellungsseiten besuchen und für deinen Account konfigurieren:

    +
      +
    • Twitter
    • +
    • GNU Social
    • +
    • Email
    • +
    +

    Anleitung#

    +

    um sich mit Personen in bestimmten Netzwerken zu verbinden.

    +

    Friendica#

    +

    du kannst dich verbinden, indem du die Adresse deiner Identität (<dein_nick>@<dein_friendica-host>) auf der "Verbinden"-Seite des Friendica-Nutzers eingibst. +Ebenso kannst du deren Identitäts-Adresse in der "Verbinden"-Box auf deiner "Kontakt"-Seite eingeben.

    +

    Diaspora#

    +

    Füge die Diaspora-Identitäts-Adresse (z.B. name@diasporapod.com)auf deiner "Kontakte"-Seite in das Feld "Neuen Kontakt hinzufügen" ein.

    +

    GNU Social#

    +

    Dieses Netzwerk wird als "federated social web" bzw. "OStatus"-Kontakte bezeichnet.

    +

    Bitte beachte, dass es keine Einstellungen zur Privatsphäre im OStatus-Netzwerk gibt. +Jede Nachricht, die an eines dieser OStatus-Mitglieder verschickt wird, ist für jeden auf der Welt sichtbar; alle Privatsphäreneinstellungen verlieren ihre Wirkung. +Diese Nachrichten erscheinen ebenfalls in öffentlichen Suchergebnissen.

    +

    Da die OStatus-Kommunikation keine Authentifizierung benutzt, können OStatus-Nutzer keine Nachrichten empfangen, wenn du in deinen Privatsphäreneinstellungen "Profil und Nachrichten vor Unbekannten verbergen" wählst.

    +

    Um dich mit einem OStatus-Mitglied zu verbinden, trage deren Profil-URL oder Identitäts-Adresse auf deiner "Kontakte"-Seite in das Feld "Neuen Kontakt hinzufügen" ein.

    +

    Der GNU Social-Konnektor kann genutzt werden, wenn du Beiträge schreiben willst, die auf einer OStatus-Seite über einen existierenden OStatus-Account erscheinen sollen.

    +

    Das ist nicht notwendig, wenn du OStatus-Mitgliedern von Friendica aus folgst und diese dir auch folgen, indem sie auf deiner Kontaktseite ihre eigene Identitäts-Adresse eingeben.

    +

    Blogger, Wordpress, RSS feeds, andere Webseiten#

    +

    Trage die URL auf deiner "Kontakte"-Seite (https://your-site.info/contacts) in das Feld "Neuen Kontakt hinzufügen" ein. +du hast keine Möglichkeit, diesen Kontakten zu antworten.

    +

    Das erlaubt dir, dich mit Millionen von Seiten im Internet zu verbinden. +Alles, was dafür nötig ist, ist, dass die Seite einen Feed im RSS- oder Atom Syndication-Format nutzt und welches einen Autor und ein Bild zur Seite liefert.

    +

    Twitter#

    +

    Um einem Twitter-Nutzer zu folgen, trage die URL der Hauptseite des Twitter-Accounts auf deiner "Kontakte"-Seite in das Feld "Neuen Kontakt hinzufügen" ein. +Um zu antworten, musst du den Twitter-Konnektor installieren und über deinen eigenen Status-Editor antworten. +Beginne deine Nachricht mit @twitternutzer, ersetze das aber durch den richtigen Twitter-Namen.

    +

    Email#

    +

    Konfiguriere den E-Mail-Konnektor auf deiner Einstellungsseite (https://your-site.info/settings). +Wenn du das gemacht hast, kannst du auf deiner "Kontakte"-Seite die E-Mail-Adresse in das Feld "Neuen Kontakt hinzufügen" eintragen. +Diese E-Mail-Adresse muss jedoch bereits mit einer Nachricht in deinem E-Mail-Posteingang auf dem Server liegen. +du hast die Möglichkeit, E-Mail-Kontakte in deine privaten Unterhaltungen einzubeziehen.

    + + +
    + +
    +
    + + + +
    + + + +
    +
    +
    +
    + + + + + + + + \ No newline at end of file diff --git a/develop/de/user/events/index.html b/develop/de/user/events/index.html new file mode 100644 index 0000000..f6af6d4 --- /dev/null +++ b/develop/de/user/events/index.html @@ -0,0 +1,3416 @@ + + + + + + + + + + + + + + + + + + + + + + Veranstaltungen - Friendica Dokumentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + +
    + + + + +
    + + +
    + +
    + + + + + + + + + +
    +
    + + + +
    +
    +
    + + + + + + +
    +
    +
    + + + +
    +
    +
    + + + +
    +
    +
    + + +
    +
    + + + + + + + + + + + + + +

    Veranstaltungen#

    +

    Veranstaltungen sind spezielle Postings. +Die Veranstaltungen, die du und deine Kontakte teilen, können unter https://your-site.info/events auf deiner Instanz aufgefunden werden. +Um da hinzukommen, gehe über den Tab "Veranstaltungen", abhängig von dem Theme, das du benutzt, ist der eventuell ein zusätzlicher Link im Navigationsmenü der Seite vorhanden.

    +

    Veranstaltungsübersicht#

    +

    Die Übersichtsseite zeigt den Kalender des aktuellen Monats an, plus einige Tage am Beginn und am Ende. +Angezeigt werden alle Veranstaltungen des aktuellen Monats, die von dir angelegt wurden oder die von deinen Kontakten geteilt wurden. +Dies beinhaltet auch Geburtstagserinnerungen, welche mit dir geteilt wurden.

    +

    Es gibt Buttons mit denen die Ansicht zwischen monatlich/wöchentlich und täglich wechseln kann. +Um eine neue Veranstaltung anzulegen, kannst du dem Link "Veranstaltung erstellen" folgen oder einen Doppelklick in das Kalenderfeld, in dem die Veranstaltung stehen soll, machen. +Mit einem Klick auf eine bereits existierende Veranstaltung, öffnet sich ein Pop-up-Fenster, welches dir die Veranstaltung anzeigt.

    +

    Erstelle eine neue Veranstaltung#

    +

    Folge einer der oben beschriebenen Methoden, dann erreichst du das Formular, um die Veranstaltungsdaten einzutragen. Felder mit *** müssen ausgefüllt werden.

    +
      +
    • Veranstaltungsbeginn: trage den Anfang der Veranstaltung ein.
    • +
    • Veranstaltungsende: trage das Ende der Veranstaltung ein.
    • +
    +

    Wenn du in eines dieser Felder klickst, wird sich ein Popup Fester öffnen, wo du Tag und Uhrzeit eintragen kannst. +Wenn du einen Doppelklick auf die Box im Kalender machst, werden diese Felder automatisch ausgefüllt. +Das Veranstaltungsende muss nach dem Veranstaltungsanfang liegen. +Aber du musst es nicht angeben. Wenn eine Veranstaltung ein offenes Ende hat oder das Veranstaltungsende nicht wichtig ist, klicke in die Box zwei Felder darunter.

    +
      +
    • An Zeitzone des Betrachters anpassen: Wenn du die Box anklickst, wird die Anfangs- und Endzeit der Veranstaltung automatisch an die lokale Zeitzone, wie in den Zeitzoneneinstellungen angepasst.
    • +
    +

    Dies vermeidet verfrühte Geburtstagsglückwünsche, oder die Befürchtung, dass du den Geburtstag deines Freundes auf der anderen Seite der Welt vergisst und ähnliche Ereignisse.

    +
      +
    • Titel: Titel der Veranstaltung
    • +
    • Beschreibung: eine längere Beschreibung der Veranstaltung
    • +
    • Ort: Ort, wo die Veranstaltung stattfinden wird
    • +
    +

    Diese 3 Felder beschreiben deine Veranstaltung. +Im Beschreibungs- und Ortsfeld kannst du BBCode zum Formatieren des Textes verwenden.

    +
      +
    • Veranstaltung teilen: wenn diese Box aktiviert wird, kannst du in der ACL auswählen, mit wem du diese Veranstaltung teilen willst. Dies funktioniert wie die Kontrolle jedes anderen Postings.
    • +
    +

    Wenn du Veranstaltungen teilst, werden diese auf deine Pinnwand gepostet mit den Zugriffsberechtigungen, die du ausgewählt hast. Bevor du das machst, kannst du dir die Veranstaltung als Vorschau in einem Pop-up-Fenster anzeigen lassen.

    +

    Interaktionen mit Veranstaltungen#

    +

    Wenn du eine Veranstaltung veröffentlichst, kannst du auswählen, wer sie bekommen wird, wie bei einem normalen Posting. +Die Empfänger sehen deine Veranstaltung in einem Posting in ihrem Network-Stream. +Zusätzlich wird die Veranstaltung zu ihrem Kalender hinzugefügt und somit in ihrer Veranstaltungsübersicht angezeigt.

    +

    Empfänger von einem Veranstaltungsposting, können dies kommentieren oder dis-/liken, wie bei einem normalen Posting. +Zusätzlich können sie sagen, ob sie teilnehmen, oder vielleicht teilnehmen an der Veranstaltung mit einem einzigen Klick.

    +

    Kalender exportieren#

    +

    Wenn du deine öffentlichen Kalender Einträge exportieren möchtest, kannst du dies in den Einstellungen aktivieren (Einstellungen → Zusätzliche Features → Allgemeine Features → Öffentlichen Kalender exportieren). +Im Anschluss sind auf der Veranstaltungsübersichtsseite zwei Links zu den exportierten Einträgen (im ical oder csv Format) verfügbar.

    + + +
    + +
    +
    + + + +
    + + + +
    +
    +
    +
    + + + + + + + + \ No newline at end of file diff --git a/develop/de/user/export-import-contacts/index.html b/develop/de/user/export-import-contacts/index.html new file mode 100644 index 0000000..bd17904 --- /dev/null +++ b/develop/de/user/export-import-contacts/index.html @@ -0,0 +1,3371 @@ + + + + + + + + + + + + + + + + + + + + + + Import/Export Kontakte - Friendica Dokumentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + +
    + + + + +
    + + +
    + +
    + + + + + + + + + +
    +
    + + + +
    +
    +
    + + + + + + +
    +
    +
    + + + +
    +
    +
    + + + +
    +
    +
    + + +
    +
    + + + + + + + + + + + + + +

    Export / Import von gefolgten Kontakte#

    +

    Zusätzlich zum Umziehen des Accounts kannst du die Liste der von dir gefolgten Kontakte exportieren und importieren. +Die exportierte Liste wird als CSV Datei in einem zu anderen Plattformen, z.B. Mastodon, Misskey oder Pleroma, kompatiblen Format gespeichert.

    +

    Export der gefolgten Kontakte#

    +

    Um die Liste der Kontakte denen du folgst zu exportieren, geht die Einstellungen persönliche Daten exportieren (https://your-site.info/settings/userexport) und klicke den Exportiere Kontakte als CSV (https://your-site.info/settings/userexport/contact) an.

    +

    Import der gefolgten Kontakte#

    +

    Um die Kontakt CSV Datei zu importieren, gehe in die Einstellungen. +Am Ende der Einstellungen zum Nutzerkonto findest du den Abschnitt "Kontakte Importieren". +Hier kannst du die CSV Datei auswählen und hoch laden.

    +

    Unterstütztes Datei Format#

    +

    Die CSV Datei muss mindestens eine Spalte beinhalten. +In der ersten Spalte der Tabelle sollte entweder das Handle oder die URL des gefolgten Kontakts. +(Ein Kontakt pro Zeile.) +alle anderen Spalten der CSV Datei werden beim Importieren ignoriert.

    + + +
    + +
    +
    + + + +
    + + + +
    +
    +
    +
    + + + + + + + + \ No newline at end of file diff --git a/develop/de/user/faq/index.html b/develop/de/user/faq/index.html new file mode 100644 index 0000000..868c4b6 --- /dev/null +++ b/develop/de/user/faq/index.html @@ -0,0 +1,3765 @@ + + + + + + + + + + + + + + + + + + + + + + FAQ - Friendica Dokumentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + +
    + + + + +
    + + +
    + +
    + + + + + + + + + +
    +
    + + + +
    +
    +
    + + + + + + +
    +
    +
    + + + + + + +
    +
    + + + + + + + + + + + + + +

    Häufig gestellte Fragen - FAQ#

    +

    Wo finde ich Hilfe?#

    +

    Wenn Du Probleme mit Deiner Friendica-Seite hast, dann kannst Du die Community in der Friendica-Support-Gruppe fragen. +Wenn Du Deinen Account nicht nutzen kannst, kannst Du einen Account auf einer öffentlichen Seite (Liste) nutzen.

    +

    Wenn du dir keinen weiteren Friendica Account einrichten willst, kannst du auch gerne über einen der folgenden alternativen Kanäle Hilfe suchen:

    + +

    Warum erhalte ich Warnungen über fehlende Zertifikate?#

    +

    Manchmal erhältst Du eine Browser-Warnung über fehlende Zertifikate. +Diese Warnungen können drei Gründe haben:

    +
      +
    1. der Server, mit dem Du verbunden bist, nutzt kein SSL;
    2. +
    3. der Server hat ein selbst-signiertes Zertifikat (nicht empfohlen)
    4. +
    5. das Zertifikat ist nicht mehr gültig.
    6. +
    +

    (SSL (Secure Socket Layer) ist eine Technologie, die Daten auf ihrem Weg zwischen zwei Computern verschlüsselt.)

    +

    Wenn Du noch kein SSL-Zertifikat hast, dann gibt es drei Wege, eines zu erhalten: kauf Dir eines, hole Dir ein kostenloses (z.B. bei StartSSL, WoSign, hoffentlich bald auch LetsEncrypt) oder kreiere Dein eigenes (nicht empfohlen). +Weitere Informationen über die Einrichtung von SSL und warum es schlecht ist, selbst-signierte Zertifikate zu nutzen, findest Du hier.

    +

    Sei Dir bewusst, dass Browser-Warnungen über Sicherheitslücken etwas sind, wodurch neue Nutzer schnell das Vertrauen in das gesamte Friendica-Projekt verlieren können. +Aus diesem Grund wird Friendica Red nur SSL-Zertifikate eines anerkannten Anbieters (CA, certificate authority) akzeptieren und nicht zu Seiten verbinden, die kein SSL nutzen. +Unabhängig von den negativen Aspekten von SSL handelt es sich hierbei um eine notwendige Lösung, solange keine etablierte Alternative existiert.

    +

    Abgesehen davon kann es ohne SSL auch Probleme mit der Verbindung zu Diaspora geben, da einige Diaspora-Pods eine zertifizierte Verbindung benötigen.

    +

    Wenn Du Friendica nur für eine bestimmte Gruppe von Leuten auf einem einzelnen Server nutzt, bei dem keine Verbindung zum restlichen Netzwerk besteht, dann benötigst Du kein SSL. +Ebenso benötigst Du SSL nicht, wenn Du ausschließlich öffentliche Beiträge auf Deiner Seite veröffentlichst bzw. empfängst.

    +

    Wenn Du zum jetzigen Zeitpunkt noch keinen Server aufgesetzt hast, ist es sinnvoll, die verschiedenen Anbieter in Bezug auf SSL zu vergleichen. +Einige erlauben die Nutzung von freien Zertifikaten oder lassen Dich ihre eigenen Zertifikate mitnutzen. +Andere erlauben nur kostenpflichtige Zertifikate als eigenes Angebot bzw. von anderen Anbietern.

    + +

    Bilder können direkt im Beitragseditor vom Computer hochgeladen werden. +Eine Übersicht aller Bilder, die auf Deinem Server liegen, findest Du unter deineSeite.de/photos/profilname. +Dort kannst Du auch direkt Bilder hochladen und festlegen, ob Deine Kontakte eine Nachricht über das neue Bild bekommen.

    +

    Alle Arten von Dateien können grundsätzlich als Anhang in Friendica hochgeladen werden. +Dafür verwendest Du das Büroklammersymbol im Editor. +Sie sind dann direkt an den Beitrag geknüpft, können von den Betrachtern heruntergeladen werden, aber werden nicht als Vorschau angezeigt. +Deshalb eignet sich diese Methode vor allem für Office-Dateien oder gepackte Dateien wie ZIPs, aber weniger für Multimediadateien. +Wer hingegen Dateien über Dropbox, über eine auf dem eigenen Server installierte Owncloud oder über einen anderen Filehoster einfügen will, verwendet den Link-Button.

    +

    Wenn Du mit dem Link-Button (Ketten-Symbol) URLs zu anderen Seiten einfügst, versucht Friendica eine kurze Zusammenfassung als Vorschau abzurufen. +Manchmal klappt das nicht ... dann verlinke den Beitrag einfach per [url=http://example.com]freigewählter Name[/url] im Editor.

    +

    Video- und Audiodateien können zwar in Beiträge eingebunden werden, allerdings geht das nicht über einen direkten Upload im Editor wie bei Fotos. +Du hast zwei Möglichkeiten:

    +
      +
    1. Du kannst bei dem Video- oder Audiobutton die URL von einem Hoster eingeben (Youtube, Vimeo, Soundcloud und alle anderen mit oembed/opengraph-Unterstützung). Bei Videos zeigt Friendica dann ein Vorschaubild in Deinem Beitrag an, nach einem Klick öffnet sich ein eingebetter Player. Bei Soundcloud wird der Player direkt eingebunden.
    2. +
    3. Wenn Du Zugang zu einem eigenen Server hast, kannst Deine Multimediadatei per FTP dort hochladen und beim Video-/Audiobutton diese URL angeben. Dann wird das Video oder die Audiodatei direkt mit einem Player in Deinem Beitrag angezeigt. +Friendica verwendet zur Einbettung HTML5. Das bedeutet, dass je nach Browser und Betriebssystem andere Formate unterstützt werden, darunter WebM, MP4, MP3 und Ogg. Eine Tabelle findest Du bei Wikipedia (Video, Audio).
    4. +
    +

    Zum Konvertieren von Videos in das lizenzfreie Videoformat WebM gibt es unter Windows das kostenlose Programm Xmedia-Recode.

    +

    Ist es möglich, bei mehreren Profilen verschiedene Avatare (Nutzerbilder) zu haben?#

    +

    Ja. +Auf Deiner "Profile verwalten/editieren"-Seite wählst Du zunächst das gewünschte Profil aus. +Anschließend siehst Du eine Seite mit allen Informationen zu diesem Profil. +Klicke nun oben auf den Link "Profilbild ändern" und lade im nächsten Fenster ein Bild von Deinem PC hoch. +Um Deine privaten Daten zu schützen, wird in Beiträgen nur das Bild aus Deinem öffentlichen Profil angezeigt.

    +

    Wie kann ich Friendica in einer bestimmten Sprache ansehen?#

    +

    Die Sprache des Friendica Interfaces kann durch den lang Parameter un der URL beeinflusst werden. +Das Argument des Parameters ist ein ISO 639-1 Code. +Zwischen der URL und dem Parameter muss ein Fragezeichen als Trennzeichen verwendet werden.

    +

    Ein Beispiel:

    +
     https://social.example.com/profile/example
    +
    +

    auf Deutsch:

    +
     https://social.example.com/profile/example?lang=de.
    +
    +

    Was ist der Unterschied zwischen blockierten|ignorierten|archivierten|versteckten Kontakten?#

    +

    Wir verhindern direkte Kommunikation mit blockierten Kontakten. +Sie gehören nicht zu den Empfängern beim Versand von Beiträgen und deren Beiträge werden auch nicht importiert. +Trotzdem werden deren Unterhaltungen mit Deinen Freunden in Deinem Stream sichtbar sein. +Wenn Du einen Kontakt komplett löschst, können sie Dir eine neue Freundschaftsanfrage schicken. +Blockierte Kontakte können das nicht machen. +Sie können nicht mit Dir direkt kommunizieren, nur über Freunde.

    +

    Ignorierte Kontakte können weiterhin Beiträge und private Nachrichten von Dir erhalten. +Deren Beiträge und private Nachrichten werden allerdings nicht importiert. +Wie bei blockierten Beiträgen siehst Du auch hier weiterhin die Kommentare dieser Person zu anderen Beiträgen Deiner Freunde.

    +

    [Ein Erweiterung namens "blockem" kann installiert werden, um alle Beiträge einer bestimmten Person in Deinem Stream zu verstecken bzw. zu verkürzen. +Dabei werden auch Kommentare dieser Person in Beiträgen Deiner Freunde blockiert.]

    +

    Ein archivierter Kontakt bedeutet, dass Kommunikation nicht möglich ist und auch nicht versucht wird (das ist z.B. sinnvoll, wenn eine Person zu einem neuen Server gewechselt ist und das alte Profil gelöscht hat). +Anders als beim Blockieren werden existierende Beiträge, die vor der Archivierung erstellt wurden, weiterhin angezeigt.

    +

    Ein versteckter Kontakt wird in keiner "Freundesliste" erscheinen (außer für dich). +Trotzdem wird ein versteckter Kontakt normal in Unterhaltungen angezeigt - was für andere Kontakte ein Hinweis sein kann, dass diese Person als versteckter Kontakt in deiner Liste ist.

    +

    Was passiert, wenn ein Account gelöscht ist? Ist dieser richtig gelöscht?#

    +

    Wenn Du Deinen Account löschst, wird sofort der gesamte Inhalt auf Deinem Server gelöscht und ein Löschbefehl an alle Deine Kontakte verschickt. +Dadurch wirst Du ebenfalls aus dem globalen Verzeichnis gelöscht. +Dieses Vorgehen setzt voraus, dass Dein Profil für 24 Stunden weiterhin "teilweise" verfügbar sein wird, um eine Verbindung zu allen Deinen Kontakten ermöglicht. +Wir können also Dein Profil blockieren und es so erscheinen lassen, als wären alle Daten sofort gelöscht, allerdings warten wir 24 Stunden (bzw. bis alle Deine Kontakte informiert wurden), bevor wir die Daten auch physikalisch löschen.

    +

    Kann ich einem Hashtag folgen?#

    +

    Ja. +Füge die Tags zu Deinen gespeicherten Suchen hinzu, sie werden automatisch auf der Netzwerk-Seite auftauchen. +Bitte beachte, dass Deine Antworten auf solche Posts aus technischen Gründen nicht unter dem "Persönlich"-Reiter auf der Netzwerk-Seite und der gesamte Thread nicht per API zu sehen sind.

    +

    Wie kann ich einen RSS-Feed meiner Netzwerkseite (Stream) erstellen?#

    +

    Wenn Du die Beiträge Deines Accounts mit RSS teilen willst, dann kannst Du einen der folgenden Links nutzen:

    +

    RSS-Feed Deiner Beiträge#

    +
    deineSeite.de/feed/[profilname]/posts
    +
    +

    Beispiel: Friendica Support

    +
    https://forum.friendi.ca/feed/helpers/posts
    +
    +

    RSS-Feed all deiner Beiträge und Antworten#

    +
    deineSeite.de/dfrn_poll/feed/[profilname]/comments
    +
    +

    Beispiel: Friendica Support

    +
    https://forum.friendi.ca/feeds/helpers/comments
    +
    +

    RSS-Feed all deiner Aktivitäten#

    +
    deineSeite.de/feed/[profilname]/
    +
    +

    Gibt es Clients für Friendica?#

    +

    Friendica unterstützt Mastodon API [EN] und Twitter API | gnusocial [EN]. +Das bedeutet, du kannst einge der Mastodon und Twitter Clients für Friendica verwenden. +Die verfügbaren Features sind Abhängig vom Client, sodass diese teils unterschiedlich sein können.

    +

    Android#

    + +

    SailfishOS#

    + +

    iOS#

    + +

    Linux#

    + +

    macOS#

    + +

    Web#

    + + + +
    + +
    +
    + + + +
    + + + +
    +
    +
    +
    + + + + + + + + \ No newline at end of file diff --git a/develop/de/user/forums/index.html b/develop/de/user/forums/index.html new file mode 100644 index 0000000..7f6e676 --- /dev/null +++ b/develop/de/user/forums/index.html @@ -0,0 +1,3368 @@ + + + + + + + + + + + + + + + + + + + + + + Foren - Friendica Dokumentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + +
    + + + + +
    + + +
    + +
    + + + + + + + + + +
    +
    + + + +
    +
    +
    + + + + + + +
    +
    +
    + + + +
    +
    +
    + + + +
    +
    +
    + + +
    +
    + + + + + + + + + + + + + +

    Foren#

    +

    In Friendica kannst du auch Foren und/oder Prominenten-Seiten erstellen.

    +

    Jede Seite in Friendica hat einen einmaligen Spitznamen. +Das gilt für alle Seiten, unabhängig davon, ob es sich um normale Profile oder Forenseiten handelt.

    +

    Das Erste, was du machen musst, um ein neues Forum zu kreieren, ist einen neuen Account zu erstellen. +Bitte beachte, dass der Seitenadministrator die Registrierung neuer Accounts sperren oder an Bedingungen knüpfen kann.

    +

    Wenn du einen zweiten Account in einem System erstellst und die gleiche E-Mail-Adresse oder den gleichen OpenID-Account nutzt, kannst du dich zukünftig nur noch mit deinem Spitznamen anmelden.

    +

    Gehe im neuen Account auf die "Einstellungs"-Seite und dort am Ende der Seite auf "Erweiterte Konto-/Seitentyp-Einstellungen". +Normalerweise nutzt du "Normales Konto" für einen normalen, persönlichen Account. +Das ist die Standardeinstellung. +Gruppenseiten bieten die Möglichkeit, Leute als Freund/Fan ohne Kontaktbestätigung zuzulassen.

    +

    Die Auswahl der Einstellung, die du wählst, hängt davon ab, wie du mit anderen Leuten auf deiner Seite interagieren willst. +Die "Marktschreier"-Einstellung (Soapbox) lässt den Seitenbesitzer die gesamte Kommunikation kontrollieren. +Alles, was du schreibst, geht an alle Seitennutzer, aber es gibt keine Möglichkeit, zu interagieren. +Diese Seite wird normalerweise für Ankündigungen oder die Kommunikation von Gemeinschaften genutzt.

    +

    Die normalste Einstellung ist das "Forum-/Promi-Konto". +Diese erstellt eine Gruppenseite, in der alle Mitglieder frei miteinander interagieren können.

    +

    Der "Automatische Freunde Seite"-Account ist typischerweise für persönliche Profile, bei denen du alle Freundschaftsanfragen automatisch bestätigen willst.

    +

    Multiple Foren verwalten#

    +

    Wir schlagen vor, dass du ein Gruppenforum mit der gleichen E-Mail-Adresse und dem gleichen Passwort wie bei deinem normalen Account nutzt. +Wenn du das machst, findest du einen neuen "Verwalten"-Link in der Menüleiste, über den du einfach zwischen den Identitäten wechseln kannst. +du musst das nicht machen, die Alternative ist allerdings, dich immer wieder aus- und wieder einzuloggen. +Und das kann umständlich sein, wenn du mehrere verschiedene Foren/Identitäten verwaltest.

    +

    du kannst ebenso jemanden wählen, der dein Forum verwaltet. +Mach das, indem du die Delegations-Setup-Seite besuchst. +Dort wird dir eine Liste an "Potenziellen Bevollmächtigen" angezeigt. +Die Auswahl einer oder mehrerer Personen gibt diesen die Möglichkeit, dein Forum zu verwalten. +Sie können Kontakte, Profile und alle Inhalte deines Accounts/deiner Seite bearbeiten. +Bitte nutze diese Einstellung mit Vorsicht. +Delegierte haben allerdings keine Möglichkeit, grundlegende Account-Einstellungen wie das Passwort oder den Seitentypen zu ändern bzw. den Account zu löschen.

    +

    Beiträge auf Community-Foren#

    +

    Wenn du Mitglied eines Community-Forums bist, kannst du das Forum in einem Beitrag hinzufügen/erwähnen, wenn du den @-Tag nutzt. +Zum Beispiel würde @Fahrrad deinen Beitrag neben den sonst ausgewählten Nutzern an alle Nutzer schicken, die in der Gruppe "Fahrrad" sind. +Wenn dein Beitrag privat ist, musst du diese Gruppe explizit in den Zugriffsrechten des Beitrags auswählen und sie mit dem @-Tag erwähnen (was den Beitrag auf die Gruppenmitglieder erweitert).

    +

    Zusätzlich ist es möglich, Foren mit dem Ausrufezeichen zu adressieren. +Im obigen Beispiel bedeutet dies, dass du das Fahrrad-Forum per !Fahrrad erreichen würdest. +Der Unterschied zum @ besteht darin, dass der Beitrag ausschließlich über das Forum verbreitet wird und nicht an weitere Nutzer. +Dies bedeutet auch, dass es nicht sinnvoll ist, mehrere Foren per ! in einem Beitrag zu adressieren, da nur eines der Foren den Beitrag verbreiten wird.

    +

    du kannst außerdem via "Wall zu Wall" einen Beitrag auf der Community-Seite bzw. in dem Community-Forum erstellen.

    +

    Kommentare, die du an ein Community-Forum schickst, werden dem Originalbeitrag hinzugefügt. +Ein weiteres Forum mit dem @-Tag zu erwähnen, leitet den Beitrag nicht an dieses weiter, da die Verteilung der Kommentare komplett vom Originalbeitrag bestimmt wird.

    + + +
    + +
    +
    + + + +
    + + + +
    +
    +
    +
    + + + + + + + + \ No newline at end of file diff --git a/develop/de/user/groups-and-privacy/index.html b/develop/de/user/groups-and-privacy/index.html new file mode 100644 index 0000000..a04c62a --- /dev/null +++ b/develop/de/user/groups-and-privacy/index.html @@ -0,0 +1,3479 @@ + + + + + + + + + + + + + + + + + + + + + + Gruppen & Privatsphäre - Friendica Dokumentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + +
    + + + + +
    + + +
    + +
    + + + + + + + + + +
    +
    + + + +
    +
    +
    + + + + + + +
    +
    +
    + + + +
    +
    +
    + + + +
    +
    +
    + + +
    +
    + + + + + + + + + + + + + +

    Gruppen und Privatsphäre#

    +

    Gruppen sind nur eine Ansammlung von Freunden. +Aber Friendica nutzt diese, um sehr mächtige Features zur Verfügung zu stellen.

    +

    Gruppen erstellen#

    +

    Um eine Gruppe zu erstellen, besuche deine "Kontakte"-Seite und wähle "Neue Gruppe erstellen" (je nach Design nur als Pluszeichen angezeigt). +Gib deiner Gruppe einen Namen.

    +

    Das führt dich zu einer Seite, auf der du die Gruppenmitglieder auswählen kannst.

    +

    Du hast zwei Boxen auf der Seite. +Die obere Box ist die Übersicht der aktuellen Mitglieder. +Die untere beinhaltet alle Freunde, die nicht Mitglied dieser Gruppe sind.

    +

    Wenn du auf das Foto einer Person klickst, die nicht in der Gruppe ist, wird diese in die Gruppe verschoben. +Wenn du auf das Foto einer Person klickst, die bereits in der Gruppe ist, dann wird diese Person daraus entfernt.

    +

    Zugriffskontrolle#

    +

    Sobald du eine Gruppe erstellt hast, kannst du diese auf jeder Zugriffsrechteliste nutzen. +Damit ist das kleine Schloss neben deinem Statuseditor auf deiner Startseite gemeint. +Wenn du darauf klickst, kannst du auswählen, wer deinen Beitrag sehen kann und wer nicht. +Dabei kann es sich um eine einzelne Person oder eine ganze Gruppe handeln.

    +

    Auf deiner "Netzwerk"-Seite ("Unterhaltungen deiner Kontakte") findest du Beiträge und Gespräche aller deiner Kontakte in deinem Netzwerk. +Du kannst aber auch eine einzelne Gruppe auswählen und nur Beiträge dieser Gruppenmitglieder anzeigen lassen.

    +

    Aber stopp, es gibt noch mehr...

    +

    Wenn du auf deiner "Netzwerk"-Seite eine bestimmte Gruppe ausgewählt hast, dann findest du im Statuseditor neben dem Schloss ein Ausrufezeichen. +Dies dient dazu, deine Aufmerksamkeit auf das Schloss zu richten. +Klicke auf das Schloss. +Dort siehst du, dass dein Status-Update in dieser Ansicht standardmäßig nur für diese Gruppe freigegeben ist. +Das hilft dir, deinen zukünftigen Mitarbeitern nicht das Gleiche zu schreiben wie deinen Trinkfreunden. +Du kannst diese Einstellung natürlich auch überschreiben.

    +

    Standardmäßige Zugriffsrechte von Beiträgen#

    +

    Standardmäßig geht Friendica davon aus, dass alle deine Beiträge privat sein sollen. +Aus diesem Grund erstellt Friendica nach der Anmeldung eine Gruppe, in die automatisch alle deine Kontakte hinzugefügt werden. +Alle deine Beiträge sind nur auf diese Gruppe beschränkt.

    +

    Beachte, dass diese Einstellung von deinem Seiten-Administrator überschrieben werden kann, was bedeutet, dass alle deine Beiträge standardmäßig "öffentlich" sind (beispielsweise für das gesamte Internet).

    +

    Wenn du deine Beiträge standardmäßig "öffentlich" haben willst, dann kannst du deine Standardzugriffsrechte auf deiner Einstellungsseite ändern. +Dort kannst du außerdem festlegen, welchen Gruppen standardmäßig deine Beiträge erhalten oder in welche Gruppe deine neuen Kontakte standardmäßig eingeordnet werden.

    +

    Fragen der Privatsphäre, die zu beachten sind

    +

    Diese privaten Gespräche funktionieren am besten, wenn deine Freunde Friendica-Mitglieder sind. +So wissen wir, wer sonst noch deine Gespräche sehen kann - niemand, solange deine Freunde deine Nachrichten nicht kopieren und an andere verschicken.

    +

    Dies ist eine Vertrauensfrage, die du beachten musst. +Keine Software der Welt kann deine Freunde davon abhalten, die privaten Unterhaltungen zu veröffentlichen. +Nur eine gute Auswahl deiner Freunde.

    +

    Bei GNu Social und anderen Netzwerk-Anbietern ist es nicht so gesichert. +Du musst sehr vorsichtig sein, wenn du Mitglieder anderer Netzwerke in einer deiner Gruppen hast, da es möglich ist, dass deine privaten Nachrichten in einem öffentlichen Stream enden. +Wenn du auf die "Kontakt bearbeiten"-Seite einer Person gehst, zeigen wir dir, ob sie Mitglied eines unsicheren Netzwerks ist oder nicht.

    +

    Sobald du einen Post erstellt hast, kannst du die Zugriffsrechte nicht mehr ändern. +Innerhalb von Sekunden ist dieser an viele verschiedene Personen verschickt worden - möglicherweise bereits an alle Adressierten. +Wenn du versehentlich eine Nachricht erstellt hast und sie zurücknehmen willst, dann ist es das beste, diese zu löschen. +Wir senden eine Löschmitteilung an jeden, der deine Nachricht erhalten hat - und das sollte die Nachricht genauso schnell löschen, wie sie zunächst erstellt wurde. +In vielen Fällen wird sie in weniger als einer Minute aus dem Internet gelöscht. +Nochmals: das gilt für Friendica-Netzwerke. +Sobald eine Nachricht an ein anderes Netzwerk geschickt wurde, kann es nicht mehr so schnell gelöscht werden und in manchen Fällen auch gar nicht mehr.

    +

    Wenn du das bisher noch nicht wusstest, dann empfehlen wir dir, deine Freunde dazu zu ermutigen, auch Friendica zu nutzen, da alle diese Privatsphären-Einstellungen innerhalb eines privatsphärenbewussten Netzwerks viel besser funktionieren. +Viele andere Netzwerke, mit denen sich Friendica verbinden kann, bieten keine Kontrolle über die Privatsphäre.

    +

    Profile, Fotos und die Privatsphäre#

    +

    Die dezentralisierte Natur von Friendica (statt eine Webseite zu haben, die alles kontrolliert, gibt es viele Webseiten, die Information austauschen) hat in der Kommunikation mit anderen Seiten einige Konsequenzen. +Du solltest dir über einige Dinge bewusst sein, um am besten entscheiden zu können, wie du mit deiner Privatsphäre umgehst.

    +

    Fotos#

    +

    Fotos privat zu verteilen ist ein Problem. +Wir können Fotos nur mit Friendica-Nutzern privat austauschen. +Um mit anderen Leuten Fotos zu teilen, müssen wir erkennen, wer sie sind. +Wir können die Identität von Friendica-Nutzern prüfen, da es hierfür einen Mechanismus gibt. +Deine Freunde anderer Netzwerke werden deine privaten Fotos nicht sehen können, da wir deren Identität nicht überprüfen können.

    +

    Unsere Entwickler arbeiten an einer Lösung, um deinen Freunden den Zugriff zu ermöglichen - unabhängig, zu welchem Netzwerk sie gehören. +Wir nehmen hingegen Privatsphäre ernst und agieren nicht wie andere Netzwerke, die nur so tun, als ob deine Fotos privat sind, sie aber trotzdem anderen ohne Identitätsprüfung zeigen.

    +

    Profile#

    +

    Dein Profil und deine "Wall" sollen vielleicht auch von Freunden anderer Netzwerke besucht werden können. +Wenn du diese Seiten allerdings für Webbesucher sperrst, die Friendica nicht kennt, kann das auch Freunde anderer Netzwerke blockieren.

    +

    Das kann möglicherweise ungewollte Ergebnisse produzieren, wenn du lange Statusbeiträge z.B. für Twitter oder Facebook schreibst. +Wenn Friendica einen Beitrag an diese Netzwerke schickt und nur eine bestimmte Nachrichtenlänge erlaubt ist, dann verkürzen wir diesen und erstellen einen Link, der zum Originalbeitrag führt. +Der Originallink führt zurück zu deinem Friendica-Profil. +Da Friendica nicht bestätigen kann, um wen es sich handelt, kann es passieren, dass diese Leute den Beitrag nicht komplett lesen können.

    +

    Für Leute, die davon betroffen sind, schlagen wir vor, eine Zusammenfassung in Twitter-Länge zu erstellen mit mehr Details für Freunde, die den ganzen Beitrag sehen können.

    +

    Dein Profil oder deine gesamte Friendica-Seite zu blockieren, hat außerdem ernsthafte Einflüsse auf deine Kommunikation mit GNU Social-Nutzern. +Diese Netzwerke kommunizieren mit anderen über öffentliche Protokolle, die nicht authentifiziert werden. +Um deine Beiträge zu sehen, müssen diese Netzwerke deine Beiträge als "unbekannte Webbesucher" ansehen. +Wenn wir das erlauben, würde es dazu führen, das absolut jeder deine Beiträge sehen. +Und du hast Friendica so eingestellt, das nicht zuzulassen. +Beachte also, dass das Blockieren von unbekannten Besuchern auch dazu führen kann, dass öffentliche Netzwerke (wie GNU Social) und Newsfeed-Reader auch geblockt werden.

    + + +
    + +
    +
    + + + +
    + + + +
    +
    +
    +
    + + + + + + + + \ No newline at end of file diff --git a/develop/de/user/keyboard-shortcuts/index.html b/develop/de/user/keyboard-shortcuts/index.html new file mode 100644 index 0000000..8177ca8 --- /dev/null +++ b/develop/de/user/keyboard-shortcuts/index.html @@ -0,0 +1,3327 @@ + + + + + + + + + + + + + + + + + + + + + + Shortcuts - Friendica Dokumentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + +
    + + + + +
    + + +
    + +
    + + + + + + + + + +
    +
    + + + +
    +
    +
    + + + + + + +
    +
    +
    + + + +
    +
    +
    + + + +
    +
    +
    + + +
    +
    + + + + + + + + + + + + + +

    Keyboard shortcuts in Friendica#

    +

    General#

    +
      +
    • j: Scroll to next thread
    • +
    • k: Scroll to previous thread
    • +
    + + +
    + +
    +
    + + + +
    + + + +
    +
    +
    +
    + + + + + + + + \ No newline at end of file diff --git a/develop/de/user/making-friends/index.html b/develop/de/user/making-friends/index.html new file mode 100644 index 0000000..49c261d --- /dev/null +++ b/develop/de/user/making-friends/index.html @@ -0,0 +1,3394 @@ + + + + + + + + + + + + + + + + + + + + + + Freunde finden - Friendica Dokumentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + +
    + + + + +
    + + +
    + +
    + + + + + + + + + +
    +
    + + + +
    +
    +
    + + + + + + +
    +
    +
    + + + +
    +
    +
    + + + +
    +
    +
    + + +
    +
    + + + + + + + + + + + + + +

    Freunde finden#

    +

    Freundschaft kann in Friendica viele verschiedene Bedeutungen annehmen. +Aber lasst es uns einfach halten, du willst einfach mit jemandem befreundet sein. +Wie machst du das?

    +

    Schau dir das Verzeichnis an. +Das Verzeichnis ist in zwei Teile aufgeteilt. +Wenn du auf den "Verzeichnis"-Button klickst, wirst du zunächst alle Mitglieder deines Servers sehen, die sich dazu entschlossen haben, angezeigt zu werden. +Außerdem siehst du dort einen Link zum globalen Verzeichnis. +Wenn du dich durch das globale Verzeichnis klickst, siehst du alle Nutzer weltweit auf allen Servern, die sich entschlossen haben, im Verzeichnis zu erscheinen. +Du wirst zudem den Link "Show Community Forums" sehen, welcher dich zu Gruppen, Foren und Fan-Seiten führt. +Du verbindest dich mit Personen, Gruppen und Foren auf die gleiche Art, wobei Gruppen und Foren deine Anfrage automatisch annehmen, wohingegen ein Mensch dich erst manuell bestätigen muss.

    +

    Mit anderen Friendica-Nutzern verbinden#

    +

    Besuche ihr Profil. +Direkt unter dem Profilfoto ist das Wort "Verbinden" (bzw. "Connect" in einem englischsprachigem Profil). +Klicke darauf und du gelangst zur "Verbinden"-Seite. +Dort wirst du nach deiner Identitätsadresse gefragt. +Das ist nötig, damit die Seite dein Profil finden kann.

    +

    Was kommt in die Box?#

    +

    Wenn deine Friendica-Seite "demo.friendica.com" heißt und dein Nutzername/Spitzname auf der Seite "bob" ist, dann wäre es "bob@demo.friendica.com". +Wie du siehst, sieht es wie eine E-Mail-Adresse aus. +Das ist beabsichtigt, da sich die Leute das so leichter merken können. +Du kannst auch die URL deiner Startseite eintragen, wie z.B. "http://demo.friendica.com/profile/bob", aber der Email-Adressen-Stil ist einfacher.

    +

    Wenn du die "Verbinden"-Seite bestätigt hast, kommst du zurück zu deiner Seite, um dort die Anfrage zu bestätigen. +Wenn du das gemacht hast, können beide Seiten miteinander kommunizieren, um den Prozess abzuschließen (sobald dein neuer Freund die Anfrage bestätigt hat).

    +

    Wenn du bereits die Identitäts-Adresse einer Person kennst, kannst du diese auch direkt in das "Verbinden"-Feld auf deiner "Kontakte"-Seite eintragen. +Dies wird dich durch einen ähnlichen Prozess leiten.

    +

    Alternative Netzwerke#

    +

    Du kannst deine oder andere Identitäts-Adressen ebenfalls nutzen, um über verschiedene Netzwerke hinweg Freundschaften aufzubauen. +Die Liste möglicher Netzwerke steigt immer weiter. +Wenn du z.B. "bob" auf quitter.se (eine GNU Social-Seite) kennst, dann kannst du bob@quitter.se auf deiner "Kontakt"-Seite verbinden. (Oder du kannst die URL von Bobs quitter.se-Seite eintragen, wenn du es wünscht). +Tatsächlich kannst du jedem und jeder Website folgen, der/die einen Syndication-Feed (RSS/Atom etc.) zur Verfügung stellt. +Wenn wir einen Informationsstrom und einen Namen dazu finden, können wir auch versuchen, uns damit zu verbinden.

    +

    Wenn du deine E-Mail-Postfachverbindung auf deiner Einstellungsseite konfiguriert hast, dann kannst du die E-Mail-Adresse jeder Person eintragen, die dir schon eine Nachricht an dein Postfach geschickt hat und bereits in deinem sozialen Stream erscheint. +Du kannst diesen Personen außerdem von Friendica aus antworten.

    +

    Leute können sich ebenfalls von anderen Netzwerken aus mit dir befreunden. +Ein Freund von dir hat einen GNU Social-Account und kann sich mit dir befreunden, indem er deine Identitäts-Adresse in seine GNU Social-Verbinden-Dialogbox einträgt. +Ein ähnlicher Mechanismus ist für Diaspora-Nutzer vorhanden, indem deine Identitäts-Adresse in ihre Suchleiste eingegeben wird.

    +

    Beachte: Manche GNU Social-Versionen benötigen die volle URL deines Profils und funktionieren möglicherweise nicht mit der Identitäts-Adresse.

    +

    Wenn jemand eine Freundschaftsanfrage schickt, erhältst du eine Benachrichtigung. +Du musst dann diese Anfrage bestätigen, um die Freundschaftsanfrage abzuschließen.

    +

    Einige Netzwerke erlauben es, Nachrichten zu schicken, ohne befreundet zu sein oder deine Bestätigung zu benötigen. +Friendica erlaubt dies in der Standardeinstellung nicht, da es zu Spam führen kann.

    +

    Wenn du eine Freundschaftsanfrage von einem anderen Friendica-Nutzer erhältst, dann hast du die Möglichkeit, diesen als "Fan" oder "Freund" einzutragen. +Ein Fan kann sehen, was du schreibst und auch private Kommunikation sehen, die du zu diesen sendest, aber nicht umgekehrt. +Als Freund kannst du in beide Richtungen kommunizieren.

    +

    Diaspora nutzt eine andere Terminologie mit der Unterteilung in "mit dir teilen" und "Freund".

    +

    Sobald ihr Freunde geworden seid, dir die Person aber permanent Spam oder sinnlose Informationen schickt, dann kannst du diese "ignorieren" - ohne die Freundschaft komplett zu beenden oder denjenigen zu zeigen, dass du nicht daran interessiert bist, was diese Person sagt. +In verschiedener Hinsicht sind diese Personen wie "Fans", aber sie wissen es nicht. +Sie denken, sie sind als Freunde eingetragen.

    +

    Du kannst auch eine Person "blocken". +Das blockt die komplette Kommunikation mit dieser Person. +Sie können zwar weiterhin öffentliche Beiträge sehen, wie auch jeder andere in der Welt, allerdings können sie nicht direkt mit dir kommunizieren.

    +

    Du kannst Freunde löschen, egal wie der Freundschaftsstatus ist, was dazu führt, dass alles, was mit dieser Person verbunden ist, von deiner Webseite gelöscht wird.

    + + +
    + +
    +
    + + + +
    + + + +
    +
    +
    +
    + + + + + + + + \ No newline at end of file diff --git a/develop/de/user/move-account/index.html b/develop/de/user/move-account/index.html new file mode 100644 index 0000000..c62a99b --- /dev/null +++ b/develop/de/user/move-account/index.html @@ -0,0 +1,3335 @@ + + + + + + + + + + + + + + + + + + + + + + Accounts umziehen - Friendica Dokumentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + +
    + + + + +
    + + +
    + +
    + + + + + + + + + +
    +
    + + + +
    +
    +
    + + + + + + +
    +
    +
    + + + +
    +
    +
    + + + +
    +
    +
    + + +
    +
    + + + + + + + + + + + + + +

    Accounts Umziehen#

    +

    ! Dies ist ein experimentelles Feature

    +

    Wie man einen Account von einem Server zu einem anderen umzieht.#

    +

    Unter "Einstellungen" -> "Persönliche Daten exportieren" aufrufen. +"Account exportieren" anklicken und die Daten speichern. +Diese Datei enthält Details über dich, deine Kontakte, Gruppen und persönliche Einstellungen. +Außerdem enthält sie deinen geheimen Schlüssel, mit dem du dich deinen Kontakten gegenüber ausweist.

    +

    Speichere diese Datei an einem sicheren Ort!

    +

    Rufe nun dem neuen Server die Seite http://newserver.com/uimport auf (es gibt derzeit keinen direkten Link auf diese Seite).

    +

    Bitte beachte, dass dies nur auf Servern möglich ist, an denen man sich offen anmelden kann. +Bei Servern, bei denen der Administrator Accounts freigeben muss, ist das Hochladen nicht möglich. +Hier kann dies nur der Administrator selber durchführen.

    +

    Lege auf dem neuen Server auf keinen Fall einen gleichnamigen Account an! +uimport muss anstelle des Registrierens verwendet werden.

    +

    Wähle die gesicherte Account-Datei aus und klicke "Importieren".

    +

    Friendica wird nun deinen Account auf dem neuen Server wiederherstellen, mit all deinen Friendica Kontakten und Gruppen. +An deine Friendica Kontakte wird außerdem eine Nachricht gesendet um sie über deine neue Adresse zu informieren. +Wenn deine Kontakte ihren Account auf einem aktuellen Server haben, werden deine Kontaktdetails automatisch aktualisiert.

    +

    Neuere Diaspora Server unterstützen ebenfalls eine Umzugsbenachrichtigung.

    +

    Kontakte auf GNU Social werden archiviert, da wir ihnen keine Information über deinen Umzug zukommen lassen können. +Du solltest sie persönlich anschreiben deinen Eintrag aus ihren Kontaktlisten zu entfernen und dich neu hinzuzufügen, anschließend solltest du da gleiche mit ihren Accounts tun.

    +

    Nach dem Umzug wird dein Account auf dem alten Server nicht mehr zuverlässig funktionieren und sollte deshalb gelöscht werden.

    + + +
    + +
    +
    + + + +
    + + + +
    +
    +
    +
    + + + + + + + + \ No newline at end of file diff --git a/develop/de/user/quick-start/finally/index.html b/develop/de/user/quick-start/finally/index.html new file mode 100644 index 0000000..a94a4bb --- /dev/null +++ b/develop/de/user/quick-start/finally/index.html @@ -0,0 +1,3345 @@ + + + + + + + + + + + + + + + + + + + + + + Zuletzt - Friendica Dokumentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + +
    + + + + +
    + + +
    + +
    + + + + + + + + + +
    +
    + + + +
    +
    +
    + + + + + + +
    +
    +
    + + + +
    +
    +
    + + + +
    +
    +
    + + +
    + + +
    +
    + + + +
    + + + +
    +
    +
    +
    + + + + + + + + \ No newline at end of file diff --git a/develop/de/user/quick-start/groups-and-pages/index.html b/develop/de/user/quick-start/groups-and-pages/index.html new file mode 100644 index 0000000..7423169 --- /dev/null +++ b/develop/de/user/quick-start/groups-and-pages/index.html @@ -0,0 +1,3297 @@ + + + + + + + + + + + + + + + + + + + + + + Gruppen & Seiten - Friendica Dokumentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + +
    + + + + +
    + + +
    + +
    + + + + + + + + + +
    +
    + + + +
    +
    +
    + + + + + + +
    +
    +
    + + + +
    +
    +
    + + + +
    +
    +
    + + +
    +
    + + + + + + + + + + + + + +

    Gruppen und Seiten#

    +

    Hier siehst du das globale Verzeichnis. +Wenn du dich mal verirrt hast, kannst du diesen Link klicken und wieder hierherkommen.

    +

    Auf dieser Seite findest du eine Zusammenstellung von Gruppen, Foren und Promi-Seiten. +Gruppen sind keine realen Personen. +Sich mit diesen zu verbinden ist, als wenn man jemanden auf Facebook "liked" ("gefällt mir") oder wenn man sich in einem Forum anmeldet. +du musst nicht unsicher sein, ob du jemandem zu nahe trittst, wenn du dich so ohne weiteres mit einer Gruppe verbindest; es handelt sich eben nicht um reale Personen.

    +

    Wenn du dich mit einer Gruppe verbindest, erscheinen alle Nachrichten der Gruppe in deinem "Netzwerk"-Tab. +du kannst diese Beiträge kommentieren oder selbst in der Gruppe schreiben, ohne eines der Gruppenmitglieder persönlich hinzuzufügen. +Das ist ein großartiger Weg, dynamisch neue Freunde zu gewinnen. +du findest Personen deines Interesses, anstatt Fremde hinzuzufügen. +Suche dir einfach eine Gruppe und füge sie so hinzu, wie du auch normale Freunde hinzufügst. +Es gibt eine Menge Gruppen. +Solltest du beim Stöbern durch die vielen Gruppen nicht wieder hierher zurückfinden, so nutze einfach den Link oben auf dieser Seite.

    +

    Wenn du einige Gruppen hinzugefügt hast, gehe weiter zum nächsten Schritt.

    + + + +
    + +
    +
    + + + +
    + + + +
    +
    +
    +
    + + + + + + + + \ No newline at end of file diff --git a/develop/de/user/quick-start/guide/index.html b/develop/de/user/quick-start/guide/index.html new file mode 100644 index 0000000..92eb989 --- /dev/null +++ b/develop/de/user/quick-start/guide/index.html @@ -0,0 +1,3302 @@ + + + + + + + + + + + + + + + + + + + + + + Start - Friendica Dokumentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + +
    + + + + +
    + + +
    + +
    + + + + + + + + + +
    +
    + + + +
    +
    +
    + + + + + + +
    +
    +
    + + + +
    +
    +
    + + + +
    +
    +
    + + +
    +
    + + + + + + + + + + + + + +

    Erste Schritte...#

    +

    Als Erstes: Gehe sicher, dass du eingeloggt bist. +Wenn du noch nicht eingeloggt bist, kannst du das in dem Fenster unten machen.

    +

    Sobald dies geschehen ist, schaust du auf die Netzwerkseite deines Profils. +Klicke auf den Reiter "Pinnwand".

    +

    Hier sieht es ein wenig wie auf (D)einer Facebook-Seite aus. +du findest hier alle deine Statusmeldungen und Nachrichten deiner Freunde, die direkt auf deine "Pinnwand" ("Wall") geschrieben haben. +Um deinen Status einzutragen, klicke einfach auf die Box oben, in der "Teilen" steht. +Wenn du das machst, vergrößert sich die Box, und du kannst nun deinen Text eintragen, mithilfe einiger Formatierungsoptionen wie fett, kursiv, unterstrichen formatieren und ebenfalls Bilder und Links hinzufügen. +Unten findest du in diesem Feld weitere Knöpfe, mit denen du Bilder und Dateien von deinem Computer hochladen, Webseiten mit einem Kurztext teilen und Video- und Audiodateien aus dem Internet einfügen kannst. +Außerdem kannst du hier eintragen, wo du gerade bist.

    +

    Wenn du deinen Beitrag ("Post") geschrieben hast, kannst du auf das "Schloss"-Symbol klicken und festlegen, wer deinen Beitrag sehen kann. +Wenn du dieses Symbol nicht anklickst, ist dein Beitrag öffentlich, bzw. es werden deine Grundeinstellungen verwendet, wenn diese nicht öffentlich sind.

    +

    Ein öffentlicher Beitrag ist sichtbar für

    +
      +
    • Besucher deines Profils
    • +
    • Besucher deiner "Gemeinschafts"-Seite
    • +
    • Besucher der Profile deiner Kontakte
    • +
    • Suchmaschinen
    • +
    +

    Auch wenn du deinen Server so konfiguriert hast, dass der Zugriff von außerhalb des Friendica-Netzwerks theoretisch nicht möglich ist, so ist dein Beitrag über die Profile deiner Kontakte sichtbar, wenn deren Knoten solche Zugriffe zulassen.

    + + +

    Probiere es doch einfach mal aus. Wenn du fertig bist, schauen wir uns den "Netzwerk"-Tab an.

    + + + +
    + +
    +
    + + + +
    + + + +
    +
    +
    +
    + + + + + + + + \ No newline at end of file diff --git a/develop/de/user/quick-start/making-new-friends/index.html b/develop/de/user/quick-start/making-new-friends/index.html new file mode 100644 index 0000000..44d20d8 --- /dev/null +++ b/develop/de/user/quick-start/making-new-friends/index.html @@ -0,0 +1,3293 @@ + + + + + + + + + + + + + + + + + + + + + + Neue Freunde - Friendica Dokumentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + +
    + + + + +
    + + +
    + +
    + + + + + + + + + +
    +
    + + + +
    +
    +
    + + + + + + +
    +
    +
    + + + +
    +
    +
    + + + +
    +
    +
    + + +
    +
    + + + + + + + + + + + + + +

    Neue Freunde finden#

    +

    Hier siehst du die Kontaktvorschläge. +Wenn du dich mal verirrt hast, kannst du diesen Link klicken und wieder hierherkommen.

    +

    Diese Seite funktioniert in etwa wie die Seite für Kontaktvorschläge in Facebook. +Jeder auf dieser Liste hat zugestimmt, als Kontaktvorschlag zu erscheinen. +Das bedeutet, das sie Anfragen meist nicht ablehnen, da sie neue Leute kennenlernen wollen.

    +

    Siehst du jemanden, der dir interessant erscheint? +Klicke auf den "Verbinden"-Knopf beim Foto. +Als Nächstes kommst du zur Seite "Freundschafts-/Kontaktanfrage". +Fülle das Formular wie vorgegeben aus und trage optional eine kleine Notiz ein. +Nun musst du nur noch auf die Bestätigung warten. +Beachte dabei, dass es sich um reale Personen handelt und es somit etwas dauern kann.

    +

    Jetzt, nachdem du jemanden hinzugefügt hast, weißt du vielleicht nicht mehr, wie du zurückkommst. +Klicke einfach auf den Link oben auf dieser Seite und du gelangst zur Seite mit den Kontaktvorschlägen zurück, um weitere Personen hinzuzufügen.

    +

    du willst nicht einfach Personen hinzufügen, die du nicht kennst? +Kein Problem - an dieser Stelle kommen wir zu den Gruppen und Seiten.

    + + +
    + +
    +
    + + + +
    + + + +
    +
    +
    +
    + + + + + + + + \ No newline at end of file diff --git a/develop/de/user/quick-start/network/index.html b/develop/de/user/quick-start/network/index.html new file mode 100644 index 0000000..73cc316 --- /dev/null +++ b/develop/de/user/quick-start/network/index.html @@ -0,0 +1,3287 @@ + + + + + + + + + + + + + + + + + + + + + + Netzwerk - Friendica Dokumentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + +
    + + + + +
    + + +
    + +
    + + + + + + + + + +
    +
    + + + +
    +
    +
    + + + + + + +
    +
    +
    + + + +
    +
    +
    + + + +
    +
    +
    + + +
    +
    + + + + + + + + + + + + + +

    deine "Netzwerk"-Seite#

    +

    Dies ist dein "Netzwerk"-Tab. +Wenn du dich mal verirrt hast, kannst du diesen Link klicken, um wieder hierherzukommen.

    +

    Diese Seite ist ein wenig wie die News-Seite in Facebook oder der Stream in Diaspora. +Hier findest du alle Beiträge deiner Kontakte, Gruppen und Feeds, die du eingetragen hast. +Wenn du neu bist, siehst du hier noch nichts, falls du an deinem Status im letzten Schritt noch nichts geändert haben solltest. +Wenn du bereits ein paar Freunde gefunden hast, so findest du hier ihre Beiträge. +du kannst ihre Beiträge von hier aus kommentieren, mitteilen, dass du den Beitrag magst oder ablehnst (Daumen hoch, Daumen runter) oder die Profile durch einen Klick auf deren Namen besuchen und dort auf deren "Pinnwand" ("Wall") Nachrichten schreiben.

    +

    Nun wollen wir diese Seite mit Inhalt füllen. +Der erste Schritt ist es, Leute zu deinem Account hinzuzufügen.

    + + +
    + +
    +
    + + + +
    + + + +
    +
    +
    +
    + + + + + + + + \ No newline at end of file diff --git a/develop/de/user/remove-account/index.html b/develop/de/user/remove-account/index.html new file mode 100644 index 0000000..9c7f95d --- /dev/null +++ b/develop/de/user/remove-account/index.html @@ -0,0 +1,3280 @@ + + + + + + + + + + + + + + + + + + + + + + Accounts löschen - Friendica Dokumentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + +
    + + + + +
    + + +
    + +
    + + + + + + + + + +
    +
    + + + +
    +
    +
    + + + + + + +
    +
    +
    + + + +
    +
    +
    + + + +
    +
    +
    + + +
    +
    + + + + + + + + + + + + + +

    Accounts löschen#

    +

    Wir freuen uns nicht, wenn Leute Friendica verlassen, aber wenn du deinen Account löschen willst, dann besuche die folgende URL

    +

    Lösche mich -> http://NamederSeite/removeme

    +

    in deinem Webbrowser. Du musst dabei eingeloggt sein.

    +

    Du wirst nach deinem Passwort gefragt, um die Anfrage zu bestätigen. +Wenn dieses mit deinem gespeichertem Passwort übereinstimmt, dann wird dein Account als "gelöscht" markiert. +Dies passiert sofort und kann nicht rückgängig gemacht werden. +Die meisten deiner Inhalte und Benutzerdaten werden kurzfristig danach durch Hintergrundprozesse gelöscht.

    +

    Parallel dazu senden wir eine Mitteilung an die Server deiner Kontakte, damit sie deine dort vorliegenden Daten ebenfalls löschen. +Wir haben keinen Einfluss darauf, wie sorgfältig und ob überhaupt diese Systeme der Löschaufforderung nachgehen.

    +

    Aus technischen Gründen benötigen wir für die Übertragung dieser Mitteilung ein paar Benutzerdaten. +Diese Daten werden dann nach einer Frist von etwa sieben Tagen ebenfalls gelöscht.

    +

    Wir speichern deinen Benutzernamen dauerhaft, damit sich niemand einen Account unter deinem Spitznamen anlegen kann.

    + + +
    + +
    +
    + + + +
    + + + +
    +
    +
    +
    + + + + + + + + \ No newline at end of file diff --git a/develop/de/user/tags-and-mentions/index.html b/develop/de/user/tags-and-mentions/index.html new file mode 100644 index 0000000..dde6803 --- /dev/null +++ b/develop/de/user/tags-and-mentions/index.html @@ -0,0 +1,3356 @@ + + + + + + + + + + + + + + + + + + + + + + Tags & Erwähnungen - Friendica Dokumentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + +
    + + + + +
    + + +
    + +
    + + + + + + + + + +
    +
    + + + +
    +
    +
    + + + + + + +
    +
    +
    + + + +
    +
    +
    + + + +
    +
    +
    + + +
    +
    + + + + + + + + + + + + + +

    Tags und Erwähnungen#

    +

    Wie viele andere soziale Netzwerke benutzt auch Friendica eine spezielle Schreibweise in seinen Nachrichten, um Tags oder kontextbezogene Links zu anderen Beiträgen hervorzuheben.

    +

    Erwähnungen#

    +

    Personen werden "getaggt", indem du das @-Zeichen vor den Namen schreibst.

    +

    Personen in deiner Kontaktliste werden „getaggt“, indem du das @-Zeichen vor den Namen schreibst.

    +
      +
    • @mike - deutet auf eine Person hin, die im Netzwerk den Namen "mike" nutzt
    • +
    • @mike_macgirvin - deutet auf eine Person hin, die sich im Netzwerk "Mike Macgirvin" nennt. Beachte, dass Leerzeichen in Tags nicht genutzt werden können.
    • +
    • @mike+151 - diese Schreibweise deutet auf eine Person hin, die "mike" heißt und deren Kontakt-Identitäts-Nummer 151 ist. Bei der Eingabe erscheint direkt ein Auswahlmenü, sodass du diese Nummer nicht selbst kennen musst.
    • +
    +

    Personen, die in einem anderen Netzwerk sind oder die sich NICHT in deiner Kontaktliste befinden, werden wie folgt getaggt:

    +
      +
    • @mike@macgirvin.com - diese Schreibweise wird "Fernerwähnung" (remote mention)genannt und kann nur im E-Mail-Stil geschrieben werden, nicht als Internetadresse/URL.
    • +
    +

    Wenn das System ungewollte Erwähnungen nicht blockiert, erhält diese Person eine Mitteilung oder nimmt direkt an der Diskussion teil, wenn es sich um einen öffentlichen Beitrag handelt. +Bitte beachte, dass Friendica eingehende "Erwähnungs"-Nachrichten von Personen blockt, die du nicht zu deinem Profil hinzugefügt hast. +Diese Maßnahme dient dazu, Spam zu vermeiden.

    +

    "Fernerwähnungen" werden durch das OStatus-Protokoll übermittelt. +Dieses Protokoll wird von Friendica, GNU Social und anderen Systemen genutzt, ist allerdings derzeit nicht in Diaspora eingebaut.

    +

    Friendica unterscheidet bei Tags nicht zwischen Personen und Gruppen (einige andere Netzwerke nutzen "!gruppe", um solche zu markieren).

    +

    Thematische Tags#

    +

    Thematische Tags werden durch eine "#" gekennzeichnet. +Dieses Zeichen erstellen einen Link zur allgemeinen Seitensuche mit dem ausgewählten Begriff. +So wird z.B. #Autos zu einer Suche führen, die alle Beiträge deiner Seite umfasst, die dieses Wort erwähnen. +Thematische Tags haben generell eine Mindestlänge von 3 Stellen. +Kürzere Suchbegriffe finden meist keine Suchergebnisse, wobei dieses abhängig von der Datenbankeinstellung ist. +Tags mit einem Leerzeichen werden, wie es auch bei Namen der Fall ist, durch einen Unterstrich gekennzeichnet. +Es ist hingegen nicht möglich, Tags zu erstellen, deren gesuchtes Wort einen Unterstrich enthält.

    +

    Thematische Tags werden auch dann nicht verlinkt, wenn sie nur aus Nummern bestehen, wie z.B. #1. Wenn du einen numerischen Tag nutzen willst, füge bitte einen Beschreibungstext hinzu wie z.B. #2012_Wahl.

    + + +
    + +
    +
    + + + +
    + + + +
    +
    +
    +
    + + + + + + + + \ No newline at end of file diff --git a/develop/de/user/text-comment/index.html b/develop/de/user/text-comment/index.html new file mode 100644 index 0000000..a4ed069 --- /dev/null +++ b/develop/de/user/text-comment/index.html @@ -0,0 +1,3361 @@ + + + + + + + + + + + + + + + + + + + + + + Kommentare - Friendica Dokumentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + +
    + + + + +
    + + +
    + +
    + + + + + + + + + +
    +
    + + + +
    +
    +
    + + + + + + +
    +
    +
    + + + +
    +
    +
    + + + +
    +
    +
    + + +
    +
    + + + + + + + + + + + + + +

    Beiträge kommentieren, einordnen und löschen#

    +

    Hier findest du eine Übersicht über die verschiedenen Möglichkeiten, bestehende Beiträge einzuordnen und zu kommentieren. + +Achtung: für dieses Beispiel wurde das Thema "Diabook" genutzt. +Wenn du ein anderes Design benutzt, wirst du manche dieser Symbole gar nicht oder in anderer Form vorfinden. +

    +

    Diabook

    +

    Die einzelnen Symbole

    +

    post_thumbs_up.png Mit diesem Symbol kannst du zeigen, dass dir ein Beitrag gefällt. +Falls du diese Eingabe zurücknehmen willst, klicke einfach ein zweites Mal auf das Symbol.

    +

    + +

    post_thumbs_down.png Mit diesem Symbol kannst du zeigen, dass dir ein Beitrag nicht gefällt. +Falls du diese Eingabe zurücknehmen willst, klicke einfach ein zweites Mal auf das Symbol.

    +

    + +

    post_share.png Mit diesem Symbol kannst du einen Beitrag weiter verteilen. +Einfach anklicken und sofort erscheint der Beitrag in deinem Beitragseditor. +Am Ende des eingefügten Beitrags erscheint ein Link zum Originalbeitrag.

    +

    + +

    post_mark.png Mit diesem Symbol kannst du einen Beitrag für dich markieren. +Markierte Beiträge erscheinen in deiner Netzwerk-Seite unter "Markierte". +Wenn du die Markierung entfernen willst, klicke einfach ein zweites Mal auf das Symbol.

    +

    + +

    post_tag.png Mit diesem Symbol kannst du einen tag zum Beitrag hinzufügen und diesen so einem bestimmten Schlagwort zuzuordnen. +Anschließend kannst du auf diesen tag klicken und alle Beiträge mit diesem tag ansehen. +ACHTUNG: tags können nicht mehr entfernt werden.

    +

    + +

    post_categorize.png Mit diesem Symbol ist es möglich, die Beiträge in bestimmte Gruppen einzuordnen. +Dies dient dazu, gewählte Beiträge nach eigenen Vorstellungen zu sortieren und wiederzufinden. +Wähle eine vorhandene Gruppe oder gib einen neuen Namen ein. Die erstellten Gruppen findest du unter "Gespeicherte Ordner" in der Netzwerk-Ansicht.

    +

    + +

    post_delete.png Mit diesem Symbol löschst du deinen eigenen Beitrag bzw. entfernst einen Beitrag einer anderen Person aus deinem Stream.

    +

    + +

    post_choose.png Mit diesem Symbol kannst du mehrere Beiträge auswählen und gesammelt löschen. +Hierfür gehst du nach dem Markieren aller gewünschten Beiträge auf "Lösche die markierten Beiträge" am Ende der Seite mit allen Beiträgen.

    +

    + +

    Im Folgenden findest du Symbole weiterer Themen#

    +

    Darkbubble darkbubble.png

    +

    Darkzero darkzero.png

    +

    (inklusive weiterer "zero"-Themen, slackr, comix, easterbunny, facepark)

    +

    Dispy dispy.png (inklusive smoothly, testbubble)

    + + +
    + +
    +
    + + + +
    + + + +
    +
    +
    +
    + + + + + + + + \ No newline at end of file diff --git a/develop/de/user/text-editor/index.html b/develop/de/user/text-editor/index.html new file mode 100644 index 0000000..b882caf --- /dev/null +++ b/develop/de/user/text-editor/index.html @@ -0,0 +1,3372 @@ + + + + + + + + + + + + + + + + + + + + + + Beiträge erstellen - Friendica Dokumentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + +
    + + + + +
    + + +
    + +
    + + + + + + + + + +
    +
    + + + +
    +
    +
    + + + + + + +
    +
    +
    + + + +
    +
    +
    + + + +
    +
    +
    + + +
    +
    + + + + + + + + + + + + + +

    Beiträge erstellen#

    +

    Hier findest du eine Übersicht über die verschiedenen Möglichkeiten, deinen Beitrag zu bearbeiten. + +Achtung: für dieses Beispiel wurde das Thema "Diabook" genutzt. +Wenn du ein anderes Design benutzt, wirst du manche dieser Symbole gar nicht oder in anderer Form vorfinden. +

    +

    Die einzelnen Symbole#

    +

    editor Wenn du auf dieses Symbol klickst, dann kannst du ein Bild von deinem Computer hinzufügen. +Wenn du eine Internetadresse (URL) eingeben willst, dann kannst du das "Baum"-Symbol im oberen Teil des Editors nutzen. +Wenn du ein Bild ausgewählt hast, dann erscheint eine Miniaturdarstellung des Bildes im Editor.*

    +

    + +

    paper_clip Wenn du dieses Symbol anklickst, dann kannst du weitere Dateien von deinem Computer einfügen. Eine Vorschau des Dateiinhalts erfolgt nicht.*

    +

    + +

    chain Wenn du die Kette anklickst, dann kannst du eine Internetadresse (URL) einfügen. +Im Editor erscheint automatisch eine kurze Information zum eingefügten Link.*

    +

    + +

    video Mit dieser Funktion kannst du die Internetadresse (URL) einer Videodatei einfügen. +Das Video erscheint dann mit einem Player in deinem Beitrag. +Da Friendica zur Einbindung HTML5 verwendet, werden je nach Browser verschiedene Videoformate unterstützt (z.B. WebM oder MP4). +Außerdem kannst du hier die URLs von Videos auf Youtube, Vimeo und manchen anderen Videohostern eingeben. +Die Videos werden dann mit Vorschaubild angezeigt, nach einem Klick öffnet sich ein eingebetteter Player.*

    +

    + +

    mic Mit dieser Funktion kannst du die Internetadresse (URL) einer Sound-Datei einfügen. +Da Friendica zur Einbindung HTML5 verwendet, werden je nach Browser und Betriebssystem MP3, Ogg oder AAC unterstützt. +Außerdem kannst du hier auch URLs von manchen Audiohostern wie Soundcloud eingeben, um eine dort gespeicherte Audiodatei mit Player in deinem Beitrag anzuzeigen.*

    +

    + +

    globe Wenn du dieses Symbol wählst, dann kannst du deinen Standort festlegen. +Hier reicht schon eine Angabe wie "Berlin" oder "10775". +Dieser Eintrag führt anschließend zu einer Suchanfrage bei Google Maps.

    +

    + +
      +
    • wie du Dateien hochladen kannst, erfährst du hier
    • +
    +

    Im Folgenden findest du Symbole weiterer Themen#

    +

    Cleanzero cleanzero.png

    +

    (inkl. weiterer "zero"-Themen, comix, easterbunny, facepark, slackr

    +

    Darkbubble darkbubble.png (inkl. smoothly, testbubble)

    +

    Frio frio.png

    +

    Vier vier.png (inkl. dispy)

    + + +
    + +
    +
    + + + +
    + + + +
    +
    +
    +
    + + + + + + + + \ No newline at end of file diff --git a/develop/de/user/two-factor-authentication/index.html b/develop/de/user/two-factor-authentication/index.html new file mode 100644 index 0000000..ab2661e --- /dev/null +++ b/develop/de/user/two-factor-authentication/index.html @@ -0,0 +1,3476 @@ + + + + + + + + + + + + + + + + + + + + + + 2FA - Friendica Dokumentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + +
    + + + + +
    + + +
    + +
    + + + + + + + + + +
    +
    + + + +
    +
    +
    + + + + + + +
    +
    +
    + + + + + + +
    +
    + + + + + + + + + + + + + +

    Configuring two-factor authentication#

    +

    You can configure two-factor authentication using a mobile app. +A time-based one-time password (TOTP) application automatically generates an authentication code that changes after a certain period of time.

    +

    Tip: To configure authentication via TOTP on multiple devices, during setup, scan the QR code using each device at the same time. +If 2FA is already enabled, and you want to add another device, you must re-configure 2FA from your security settings.

    +

    Enabling two-factor authentication#

    +

    1. Download an authenticator app#

    +

    Any authenticator app should work with Friendica. +Nonetheless, we recommend:

    + +

    2. Record your one-use recovery codes#

    +

    From your two-factor authentication user settings (https://your-site.info/settings/2fa), enter your password and click on "Enable two-factor authentication".

    +

    You will be presented with a list of one-use recovery codes. +Please save those in the same place you are saving your Friendica password (ideally, in a password manager like KeePass).

    +

    When you're done, click on "Next".

    +

    3. Set up your authenticator app#

    +

    You have three methods to set up your authenticator app:

    +
      +
    1. Scan the QR Code with your device camera. + This will automatically configure your account on the app.
    2. +
    3. Click/tap on the provided totp:// URl. + Ideally your authenticator app should be called with this URL and set up your account.
    4. +
    5. Enter your account settings manually. + Friendica is using default settings for token type, code digit count and hashing algorithm, but you may be required to enter them in your app.
    6. +
    +

    Tip: If you have multiple devices, configure them all at this point.

    +

    Then verify your app is correctly configured by submitting a code provided by your app. +This will conclude two-factor authentication configuration.

    +

    Note: If you leave this screen at any point without having submitted a verification code, two-factor authentication won't be enabled on your account. +To complete the configuration, just come back to your two-factor authentication user settings and click on "Finish configuration" after entering your current password.

    +

    Disabling two-factor authentication#

    +

    You can disable two-factor authentication at any time by going to your two-factor authentication user settings and click on "Disable two-factor authentication" after entering your current password.

    +

    You should remove your Friendica account from your authenticator app as it won't work again even if you reenable two-factor authentication. +In this case you will have to configure your authenticator app again using the process above.

    +

    Managing your one-time recovery codes#

    +

    When two-factor authentication is enabled, you can show your recovery codes, including the ones you've already used.

    +

    You can freely regenerate a new set of fresh recovery codes, just be sure to replace the previous ones where you saved them as they won't be active anymore.

    +

    Third-party applications and API#

    +

    Third-party applications using the Friendica API can't accept two-factor time-based authentication codes. +Instead, if you enabled two-factor authentication, you have to generate app-specific randomly generated long passwords to use in your apps instead of your regular account password.

    +

    Note: Your regular password won't work at all when prompted in third-party apps if you enabled two-factor authentication.

    +

    You can generate as many app-specific passwords as you want, they will be shown once to you just after you generated it. +Just copy and paste it in your third-party app in the Friendica account password input field at this point. +We recommend generating a single app-specific password for each separate third-party app you are using, using a meaningful description of the target app (like "Frienqa on my Fairphone 2").

    +

    You can also revoke any and all app-specific password you generated this way. +This may log you out of the third-party application(s) you used the revoked app-specific password to log in with.

    +

    Trusted browsers#

    +

    As a convenience, during two-factor authentication it is possible to identify a browser as trusted. +This will skip all further two-factor authentication prompt on this browser.

    +

    You can remove any or all of these trusted browsers in the two-factor authentication settings.

    + + +
    + +
    +
    + + + +
    + + + +
    +
    +
    +
    + + + + + + + + \ No newline at end of file diff --git a/develop/developer/addon-storage-backend/index.html b/develop/developer/addon-storage-backend/index.html new file mode 100644 index 0000000..e2db8b2 --- /dev/null +++ b/develop/developer/addon-storage-backend/index.html @@ -0,0 +1,3778 @@ + + + + + + + + + + + + + + + + + + + + + + Addon Storage Backend - Friendica documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + +
    + + + + +
    + + +
    + +
    + + + + + + + + + +
    +
    + + + +
    +
    +
    + + + + + + +
    +
    +
    + + + +
    +
    +
    + + + +
    +
    +
    + + +
    +
    + + + + + + + + + + + + + +

    Friendica Storage Backend Addon development#

    +

    Storage backends can be added via addons. +A storage backend is implemented as a class, and the plugin register the class to make it available to the system.

    +

    The Storage Backend Class#

    +

    The class must live in Friendica\Addon\youraddonname namespace, where youraddonname the folder name of your addon.

    +

    There are two different interfaces you need to implement.

    +

    ICanWriteToStorage#

    +

    The class must implement Friendica\Core\Storage\Capability\ICanWriteToStorage interface. All method in the interface must be implemented:

    +
    <?php
    +namespace Friendica\Core\Storage\Capability\ICanWriteToStorage;
    +
    +interface ICanWriteToStorage
    +{
    +    public function get(string $reference);
    +    public function put(string $data, string $reference = '');
    +    public function delete(string $reference);
    +    public function __toString();
    +    public static function getName();
    +}
    +
    +
      +
    • get(string $reference) returns data pointed by $reference
    • +
    • put(string $data, string $reference) saves data in $data to position $reference, or a new position if $reference is empty.
    • +
    • delete(string $reference) delete data pointed by $reference
    • +
    +

    ICanConfigureStorage#

    +

    Each storage backend can have options the admin can set in admin page. +To make the options possible, you need to implement the Friendica\Core\Storage\Capability\ICanConfigureStorage interface.

    +

    All methods in the interface must be implemented:

    +
    <?php
    +namespace Friendica\Core\Storage\Capability\ICanConfigureStorage;
    +
    +interface ICanConfigureStorage
    +{
    +    public function getOptions();
    +    public function saveOptions(array $data);
    +}
    +
    +
      +
    • getOptions() returns an array with details about each option to build the interface.
    • +
    • saveOptions(array $data) get $data from admin page, validate it and save it.
    • +
    +

    The array returned by getOptions() is defined as:

    +
    [
    +    'option1name' => [ ..info.. ],
    +    'option2name' => [ ..info.. ],
    +    ...
    +]
    +
    +

    An empty array can be returned if backend doesn't have any options.

    +

    The info array for each option is defined as:

    +
    [
    +    'type',
    +
    +

    define the field used in form, and the type of data. +one of 'checkbox', 'combobox', 'custom', 'datetime', 'input', 'intcheckbox', 'password', 'radio', 'richtext', 'select', 'select_raw', 'textarea'

    +
        'label',
    +
    +

    Translatable label of the field. This label will be shown in admin page

    +
        value,
    +
    +

    Current value of the option

    +
        'help text',
    +
    +

    Translatable description for the field. Will be shown in admin page

    +
        extra data
    +
    +

    Optional. Depends on which 'type' this option is:

    +
      +
    • 'select': array [ value => label ] of choices
    • +
    • 'intcheckbox': value of input element
    • +
    • 'select_raw': prebuild html string of <option > tags
    • +
    +

    Each label should be translatable

    +
    ];
    +
    +

    See doxygen documentation of IWritableStorage interface for details about each method.

    +

    Register a storage backend class#

    +

    Each backend must be registered in the system when the plugin is installed, to be aviable.

    +

    DI::facStorage()->register(string $class) is used to register the backend class.

    +

    When the plugin is uninstalled, registered backends must be unregistered using +DI::facStorage()->unregister(string $class).

    +

    You have to register a new hook in your addon, listening on storage_instance(App $a, array $data). +In case $data['name'] is your storage class name, you have to instance a new instance of your Friendica\Core\Storage\Capability\ICanReadFromStorage class. +Set the instance of your class as $data['storage'] to pass it back to the backend.

    +

    This is necessary because it isn't always clear, if you need further construction arguments.

    +

    Adding tests#

    +

    Currently testing is limited to core Friendica only, this shows theoretically how tests should work in the future

    +

    Each new Storage class should be added to the test-environment at Storage Tests.

    +

    Add a new test class which's naming convention is StorageClassTest, which extend the StorageTest in the same directory.

    +

    Override the two necessary instances:

    +
    use Friendica\Core\Storage\Capability\ICanWriteToStorage;
    +
    +abstract class StorageTest 
    +{
    +    // returns an instance of your newly created storage class
    +    abstract protected function getInstance();
    +
    +    // Assertion for the option array you return for your new StorageClass
    +    abstract protected function assertOption(ICanWriteToStorage $storage);
    +} 
    +
    +

    Exception handling#

    +

    There are two intended types of exceptions for storages

    +

    ReferenceStorageExecption#

    +

    This storage exception should be used in case the caller tries to use an invalid references. +This could happen in case the caller tries to delete or update an unknown reference. +The implementation of the storage backend must not ignore invalid references.

    +

    Avoid throwing the common StorageExecption instead of the ReferenceStorageException at this particular situation!

    +

    StorageException#

    +

    This is the common exception in case unexpected errors happen using the storage backend. +If there's a predecessor to this exception (e.g. you caught an exception and are throwing this execption), you should add the predecessor for transparency reasons.

    +

    Example:

    +
    use Friendica\Core\Storage\Capability\ICanWriteToStorage;
    +
    +class ExampleStorage implements ICanWriteToStorage 
    +{
    +    public function get(string $reference) : string
    +    {
    +        try {
    +            throw new Exception('a real bad exception');
    +        } catch (Exception $exception) {
    +            throw new \Friendica\Core\Storage\Exception\StorageException(sprintf('The Example Storage throws an exception for reference %s', $reference), 500, $exception);
    +        }
    +    }
    +} 
    +
    +

    Example#

    +

    Here is a hypothetical addon which register a useless storage backend. +Let's call it samplestorage.

    +

    This backend will discard all data we try to save and will return always the same image when we ask for some data. +The image returned can be set by the administrator in admin page.

    +

    First, the backend class. +The file will be addon/samplestorage/SampleStorageBackend.php:

    +
    <?php
    +namespace Friendica\Addon\samplestorage;
    +
    +use Friendica\Core\Storage\Capability\ICanWriteToStorage;
    +
    +use Friendica\Core\Config\Capability\IManageConfigValues;
    +use Friendica\Core\L10n;
    +
    +class SampleStorageBackend implements ICanWriteToStorage
    +{
    +    const NAME = 'Sample Storage';
    +
    +    /** @var string */
    +    private $filename;
    +
    +    /**
    +      * SampleStorageBackend constructor.
    +      * 
    +      * You can add here every dynamic class as dependency you like and add them to a private field
    +      * Friendica automatically creates these classes and passes them as argument to the constructor                                       
    +      */
    +    public function __construct(string $filename) 
    +    {
    +        $this->filename = $filename;
    +    }
    +
    +    public function get(string $reference)
    +    {
    +        // we return always the same image data. Which file we load is defined by
    +        // a config key
    +        return file_get_contents($this->filename);
    +    }
    +
    +    public function put(string $data, string $reference = '')
    +    {
    +        if ($reference === '') {
    +            $reference = 'sample';
    +        }
    +        // we don't save $data !
    +        return $reference;
    +    }
    +
    +    public function delete(string $reference)
    +    {
    +        // we pretend to delete the data
    +        return true;
    +    }
    +
    +    public function __toString()
    +    {
    +        return self::NAME;
    +    }
    +
    +    public static function getName()
    +    {
    +        return self::NAME;
    +    }
    +}
    +
    +
    <?php
    +namespace Friendica\Addon\samplestorage;
    +
    +use Friendica\Core\Storage\Capability\ICanConfigureStorage;
    +
    +use Friendica\Core\Config\Capability\IManageConfigValues;
    +use Friendica\Core\L10n;
    +
    +class SampleStorageBackendConfig implements ICanConfigureStorage
    +{
    +    /** @var \Friendica\Core\Config\Capability\IManageConfigValues */
    +    private $config;
    +    /** @var L10n */
    +    private $l10n;
    +
    +    /**
    +      * SampleStorageBackendConfig constructor.
    +      * 
    +      * You can add here every dynamic class as dependency you like and add them to a private field
    +      * Friendica automatically creates these classes and passes them as argument to the constructor                                       
    +      */
    +    public function __construct(IManageConfigValues $config, L10n $l10n) 
    +    {
    +        $this->config = $config;
    +        $this->l10n   = $l10n;
    +    }
    +
    +    public function getFileName(): string
    +    {
    +        return $this->config->get('storage', 'samplestorage', 'sample.jpg');
    +    }
    +
    +    public function getOptions()
    +    {
    +        $filename = $this->config->get('storage', 'samplestorage', 'sample.jpg');
    +        return [
    +            'filename' => [
    +                'input',    // will use a simple text input
    +                $this->l10n->t('The file to return'),   // the label
    +                $filename,  // the current value
    +                $this->l10n->t('Enter the path to a file'), // the help text
    +                // no extra data for 'input' type..
    +            ],
    +        ];
    +    }
    +
    +    public function saveOptions(array $data)
    +    {
    +        // the keys in $data are the same keys we defined in getOptions()
    +        $newfilename = trim($data['filename']);
    +
    +        // this function should always validate the data.
    +        // in this example we check if file exists
    +        if (!file_exists($newfilename)) {
    +            // in case of error we return an array with
    +            // ['optionname' => 'error message']
    +            return ['filename' => 'The file doesn\'t exists'];
    +        }
    +
    +        $this->config->set('storage', 'samplestorage', $newfilename);
    +
    +        // no errors, return empty array
    +        return [];
    +    }
    +
    +}
    +
    +

    Now the plugin main file. Here we register and unregister the backend class.

    +

    The file is addon/samplestorage/samplestorage.php

    +
    <?php
    +/**
    + * Name: Sample Storage Addon
    + * Description: A sample addon which implements an unusefull storage backend
    + * Version: 1.0.0
    + * Author: Alice <https://alice.social/~alice>
    + */
    +
    +use Friendica\Addon\samplestorage\SampleStorageBackend;
    +use Friendica\Addon\samplestorage\SampleStorageBackendConfig;
    +use Friendica\DI;
    +
    +function samplestorage_install()
    +{
    +    Hook::register('storage_instance' , __FILE__, 'samplestorage_storage_instance');
    +    Hook::register('storage_config' , __FILE__, 'samplestorage_storage_config');
    +    DI::storageManager()->register(SampleStorageBackend::class);
    +}
    +
    +function samplestorage_storage_uninstall()
    +{
    +    DI::storageManager()->unregister(SampleStorageBackend::class);
    +}
    +
    +function samplestorage_storage_instance(App $a, array &$data)
    +{
    +    $config          = new SampleStorageBackendConfig(DI::l10n(), DI::config());
    +    $data['storage'] = new SampleStorageBackendConfig($config->getFileName());
    +}
    +
    +function samplestorage_storage_config(App $a, array &$data)
    +{
    +    $data['storage_config'] = new SampleStorageBackendConfig(DI::l10n(), DI::config());
    +}
    +
    +

    **Theoretically - until tests for Addons are enabled too - create a test class with the name addon/tests/SampleStorageTest.php:

    +
    use Friendica\Core\Storage\Capability\ICanWriteToStorage;
    +use Friendica\Test\src\Core\Storage\StorageTest;
    +
    +class SampleStorageTest extends StorageTest 
    +{
    +    // returns an instance of your newly created storage class
    +    protected function getInstance()
    +    {
    +        // create a new SampleStorageBackend instance with all it's dependencies
    +        // Have a look at DatabaseStorageTest or FilesystemStorageTest for further insights
    +        return new SampleStorageBackend();
    +    }
    +
    +    // Assertion for the option array you return for your new StorageClass
    +    protected function assertOption(ICanWriteToStorage $storage)
    +    {
    +        $this->assertEquals([
    +            'filename' => [
    +                'input',
    +                'The file to return',
    +                'sample.jpg',
    +                'Enter the path to a file'
    +            ],
    +        ], $storage->getOptions());
    +    }
    +} 
    +
    + + +
    + +
    +
    + + + +
    + + + +
    +
    +
    +
    + + + + + + + + \ No newline at end of file diff --git a/develop/developer/addons/index.html b/develop/developer/addons/index.html new file mode 100644 index 0000000..abc0e3a --- /dev/null +++ b/develop/developer/addons/index.html @@ -0,0 +1,6278 @@ + + + + + + + + + + + + + + + + + + + + + + Addons - Friendica documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + +
    + + + + +
    + + +
    + +
    + + + + + + + + + +
    +
    + + + +
    +
    +
    + + + + + + +
    +
    +
    + + + +
    +
    +
    + + + +
    +
    +
    + + +
    +
    + + + + + + + + + + + + + +

    Friendica Addon development#

    +

    Please see the sample addon 'randplace' for a working example of using some of these features. +Addons work by intercepting event hooks - which must be registered. +Modules work by intercepting specific page requests (by URL path).

    +

    Naming#

    +

    Addon names are used in file paths and functions names, and as such: +- Can't contain spaces or punctuation. +- Can't start with a number.

    +

    Metadata#

    +

    You can provide human-readable information about your addon in the first multi-line comment of your addon file.

    +

    Here's the structure:

    +
    /**
    + * Name: {Human-readable name}
    + * Description: {Short description}
    + * Version: 1.0
    + * Author: {Author1 Name}
    + * Author: {Author2 Name} <{Author profile link}>
    + * Maintainer: {Maintainer1 Name}
    + * Maintainer: {Maintainer2 Name} <{Maintainer profile link}>
    + * Status: {Unsupported|Arbitrary status}
    + */
    +
    +

    You can also provide a longer documentation in a README or README.md file. +The latter will be converted from Markdown to HTML in the addon detail page.

    +

    Install/Uninstall#

    +

    If your addon uses hooks, they have to be registered in a <addon>_install() function. +This function also allows to perform arbitrary actions your addon needs to function properly.

    +

    Uninstalling an addon automatically unregisters any hook it registered, but if you need to provide specific uninstallation steps, you can add them in a <addon>_uninstall() function.

    +

    The installation and uninstallation functions will be called (i.e. re-installed) if the addon changes after installation. +Therefore, your uninstall should not destroy data and install should consider that data may already exist. +Future extensions may provide for "setup" amd "remove".

    +

    PHP addon hooks#

    +

    Register your addon hooks during installation.

    +
    \Friendica\Core\Hook::register($hookname, $file, $function);
    +
    +

    $hookname is a string and corresponds to a known Friendica PHP hook.

    +

    $file is a pathname relative to the top-level Friendica directory. +This should be 'addon/addon_name/addon_name.php' in most cases and can be shortened to __FILE__.

    +

    $function is a string and is the name of the function which will be executed when the hook is called.

    +

    Arguments#

    +

    Your hook callback functions will be called with at least one and possibly two arguments

    +
    function <addon>_<hookname>(App $a, &$b) {
    +
    +}
    +
    +

    If you wish to make changes to the calling data, you must declare them as reference variables (with &) during function declaration.

    +

    $a#

    +

    $a is the Friendica App class. +It contains a wealth of information about the current state of Friendica:

    +
      +
    • which module has been called,
    • +
    • configuration information,
    • +
    • the page contents at the point the hook was invoked,
    • +
    • profile and user information, etc.
    • +
    +

    It is recommended you call this $a to match its usage elsewhere.

    +

    $b#

    +

    $b can be called anything you like. +This is information specific to the hook currently being processed, and generally contains information that is being immediately processed or acted on that you can use, display, or alter. +Remember to declare it with & if you wish to alter it.

    +

    Admin settings#

    +

    Your addon can provide user-specific settings via the addon_settings PHP hook, but it can also provide node-wide settings in the administration page of your addon.

    +

    Simply declare a <addon>_addon_admin(App $a) function to display the form and a <addon>_addon_admin_post(App $a) function to process the data from the form.

    +

    Global stylesheets#

    +

    If your addon requires adding a stylesheet on all pages of Friendica, add the following hook:

    +
    function <addon>_install()
    +{
    +    \Friendica\Core\Hook::register('head', __FILE__, '<addon>_head');
    +    ...
    +}
    +
    +
    +function <addon>_head(App $a)
    +{
    +    \Friendica\DI::page()->registerStylesheet(__DIR__ . '/relative/path/to/addon/stylesheet.css');
    +}
    +
    +

    __DIR__ is the folder path of your addon.

    +

    JavaScript#

    +

    Global scripts#

    +

    If your addon requires adding a script on all pages of Friendica, add the following hook:

    +
    function <addon>_install()
    +{
    +    \Friendica\Core\Hook::register('footer', __FILE__, '<addon>_footer');
    +    ...
    +}
    +
    +function <addon>_footer(App $a)
    +{
    +    \Friendica\DI::page()->registerFooterScript(__DIR__ . '/relative/path/to/addon/script.js');
    +}
    +
    +

    __DIR__ is the folder path of your addon.

    +

    JavaScript hooks#

    +

    The main Friendica script provides hooks via events dispatched on the document property. +In your Javascript file included as described above, add your event listener like this:

    +
    document.addEventListener(name, callback);
    +
    +
      +
    • name is the name of the hook and corresponds to a known Friendica JavaScript hook.
    • +
    • callback is a JavaScript anonymous function to execute.
    • +
    +

    More info about Javascript event listeners: https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/addEventListener

    +

    Current JavaScript hooks#

    +
    postprocess_liveupdate#
    +

    Called at the end of the live update process (XmlHttpRequest) and on a post preview. +No additional data is provided.

    +

    Modules#

    +

    Addons may also act as "modules" and intercept all page requests for a given URL path. +In order for an addon to act as a module it needs to declare an empty function <addon>_module().

    +

    If this function exists, you will now receive all page requests for https://my.web.site/<addon> - with any number of URL components as additional arguments. +These are parsed into the App\Arguments object. +So https://my.web.site/addon/arg1/arg2 would give this: +

    DI::args()->getArgc(); // = 3
    +DI::args()->get(0); // = 'addon'
    +DI::args()->get(1); // = 'arg1'
    +DI::args()->get(2); // = 'arg2'
    +

    +

    To display a module page, you need to declare the function <addon>_content(App $a), which defines and returns the page body content. +They may also contain <addon>_post(App $a) which is called before the <addon>_content function and typically handles the results of POST forms. +You may also have <addon>_init(App $a) which is called before <addon>_content and should include common logic to your module.

    +

    Templates#

    +

    If your addon needs some template, you can use the Friendica template system. +Friendica uses smarty3 as a template engine.

    +

    Put your tpl files in the templates/ sub-folder of your addon.

    +

    In your code, like in the function addon_name_content(), load the template file and execute it passing needed values:

    +
    use Friendica\Core\Renderer;
    +
    +# load template file. first argument is the template name,
    +# second is the addon path relative to friendica top folder
    +$tpl = Renderer::getMarkupTemplate('mytemplate.tpl', __DIR__);
    +
    +# apply template. first argument is the loaded template,
    +# second an array of 'name' => 'values' to pass to template
    +$output = Renderer::replaceMacros($tpl, array(
    +    'title' => 'My beautiful addon',
    +));
    +
    +

    See also the wiki page Quick Template Guide.

    +

    Current PHP hooks#

    +

    authenticate#

    +

    Called when a user attempts to login. +$b is an array containing:

    +
      +
    • username: the supplied username
    • +
    • password: the supplied password
    • +
    • authenticated: set this to non-zero to authenticate the user.
    • +
    • user_record: successful authentication must also return a valid user record from the database
    • +
    +

    logged_in#

    +

    Called after a user has successfully logged in. +$b contains the $a->user array.

    +

    display_item#

    +

    Called when formatting a post for display. +$b is an array:

    +
      +
    • item: The item (array) details pulled from the database
    • +
    • output: the (string) HTML representation of this item prior to adding it to the page
    • +
    +

    post_local#

    +

    Called when a status post or comment is entered on the local system. +$b is the item array of the information to be stored in the database. +Please note: body contents are bbcode - not HTML.

    +

    post_local_end#

    +

    Called when a local status post or comment has been stored on the local system. +$b is the item array of the information which has just been stored in the database. +Please note: body contents are bbcode - not HTML

    +

    post_remote#

    +

    Called when receiving a post from another source. This may also be used to post local activity or system generated messages. +$b is the item array of information to be stored in the database and the item body is bbcode.

    +

    addon_settings#

    +

    Called when generating the HTML for the addon settings page. +$data is an array containing:

    +
      +
    • addon (output): Required. The addon folder name.
    • +
    • title (output): Required. The addon settings panel title.
    • +
    • href (output): Optional. If set, will reduce the panel to a link pointing to this URL, can be relative. Incompatible with the following keys.
    • +
    • html (output): Optional. Raw HTML of the addon form elements. Both the <form> tags and the submit buttons are taken care of elsewhere.
    • +
    • submit (output): Optional. If unset, a default submit button with name="<addon name>-submit" will be generated. + Can take different value types:
    • +
    • string: The label to replace the default one.
    • +
    • associative array: A list of submit button, the key is the value of the name attribute, the value is the displayed label. + The first submit button in this list is considered the main one and themes might emphasize its display.
    • +
    +

    Examples#

    + +
    $data = [
    +    'addon' => 'advancedcontentfilter',
    +    'title' => DI::l10n()->t('Advanced Content Filter'),
    +    'href'  => 'advancedcontentfilter',
    +];
    +
    +
    With default submit button#
    +
    $data = [
    +    'addon' => 'fromapp',
    +    'title' => DI::l10n()->t('FromApp Settings'),
    +    'html'  => $html,
    +];
    +
    +
    With no HTML, just a submit button#
    +
    $data = [
    +    'addon'  => 'opmlexport',
    +    'title'  => DI::l10n()->t('OPML Export'),
    +    'submit' => DI::l10n()->t('Export RSS/Atom contacts'),
    +];
    +
    +
    With multiple submit buttons#
    +
    $data = [
    +    'addon'  => 'catavar',
    +    'title'  => DI::l10n()->t('Cat Avatar Settings'),
    +    'html'   => $html,
    +    'submit' => [
    +        'catavatar-usecat'   => DI::l10n()->t('Use Cat as Avatar'),
    +        'catavatar-morecat'  => DI::l10n()->t('Another random Cat!'),
    +        'catavatar-emailcat' => DI::pConfig()->get(local_user(), 'catavatar', 'seed', false) ? DI::l10n()->t('Reset to email Cat') : null,
    +    ],
    +];
    +
    +

    addon_settings_post#

    +

    Called when the Addon Settings pages are submitted. +$b is the $_POST array.

    +

    connector_settings#

    +

    Called when generating the HTML for a connector addon settings page. +$data is an array containing:

    +
      +
    • connector (output): Required. The addon folder name.
    • +
    • title (output): Required. The addon settings panel title.
    • +
    • image (output): Required. The relative path of the logo image of the platform/protocol this addon is connecting to, max size 48x48px.
    • +
    • enabled (output): Optional. If set to a falsy value, the connector image will be dimmed.
    • +
    • html (output): Optional. Raw HTML of the addon form elements. Both the <form> tags and the submit buttons are taken care of elsewhere.
    • +
    • submit (output): Optional. If unset, a default submit button with name="<addon name>-submit" will be generated. + Can take different value types:
        +
      • string: The label to replace the default one.
      • +
      • associative array: A list of submit button, the key is the value of the name attribute, the value is the displayed label. + The first submit button in this list is considered the main one and themes might emphasize its display.
      • +
      +
    • +
    +

    Examples#

    +
    With default submit button#
    +
    $data = [
    +    'connector' => 'diaspora',
    +    'title'     => DI::l10n()->t('Diaspora Export'),
    +    'image'     => 'images/diaspora-logo.png',
    +    'enabled'   => $enabled,
    +    'html'      => $html,
    +];
    +
    +
    With custom submit button label and no logo dim#
    +
    $data = [
    +    'connector' => 'ifttt',
    +    'title'     => DI::l10n()->t('IFTTT Mirror'),
    +    'image'     => 'addon/ifttt/ifttt.png',
    +    'html'      => $html,
    +    'submit'    => DI::l10n()->t('Generate new key'),
    +];
    +
    +
    With conditional submit buttons#
    +
    $submit = ['pumpio-submit' => DI::l10n()->t('Save Settings')];
    +if ($oauth_token && $oauth_token_secret) {
    +    $submit['pumpio-delete'] = DI::l10n()->t('Delete this preset');
    +}
    +
    +$data = [
    +    'connector' => 'pumpio',
    +    'title'     => DI::l10n()->t('Pump.io Import/Export/Mirror'),
    +    'image'     => 'images/pumpio.png',
    +    'enabled'   => $enabled,
    +    'html'      => $html,
    +    'submit'    => $submit,
    +];
    +
    +

    profile_post#

    +

    Called when posting a profile page. +$b is the $_POST array.

    +

    profile_edit#

    +

    Called prior to output of profile edit page. +$b is an array containing:

    +
      +
    • profile: profile (array) record from the database
    • +
    • entry: the (string) HTML of the generated entry
    • +
    +

    profile_advanced#

    +

    Called when the HTML is generated for the Advanced profile, corresponding to the Profile tab within a person's profile page. +$b is the HTML string representation of the generated profile. +The profile array details are in $a->profile.

    +

    directory_item#

    +

    Called from the Directory page when formatting an item for display. +$b is an array:

    +
      +
    • contact: contact record array for the person from the database
    • +
    • entry: the HTML string of the generated entry
    • +
    +

    profile_sidebar_enter#

    +

    Called prior to generating the sidebar "short" profile for a page. +$b is the person's profile array

    +

    profile_sidebar#

    +

    Called when generating the sidebar "short" profile for a page. +$b is an array:

    +
      +
    • profile: profile record array for the person from the database
    • +
    • entry: the HTML string of the generated entry
    • +
    +

    contact_block_end#

    +

    Called when formatting the block of contacts/friends on a profile sidebar has completed. +$b is an array:

    +
      +
    • contacts: array of contacts
    • +
    • output: the generated HTML string of the contact block
    • +
    +

    bbcode#

    +

    Called after conversion of bbcode to HTML. +$b is an HTML string converted text.

    +

    html2bbcode#

    +

    Called after tag conversion of HTML to bbcode (e.g. remote message posting) +$b is a string converted text

    + +

    Called when building the <head> sections. +Stylesheets should be registered using this hook. +$b is an HTML string of the <head> tag.

    + +

    Called after building the page navigation section. +$b is a string HTML of nav region.

    +

    personal_xrd#

    +

    Called prior to output of personal XRD file. +$b is an array:

    +
      +
    • user: the user record array for the person
    • +
    • xml: the complete XML string to be output
    • +
    +

    home_content#

    +

    Called prior to output home page content, shown to unlogged users. +$b is the HTML string of section region.

    +

    contact_edit#

    +

    Called when editing contact details on an individual from the Contacts page. +$b is an array:

    +
      +
    • contact: contact record (array) of target contact
    • +
    • output: the (string) generated HTML of the contact edit page
    • +
    +

    contact_edit_post#

    +

    Called when posting the contact edit page. +$b is the $_POST array

    +

    init_1#

    +

    Called just after DB has been opened and before session start. +No hook data.

    +

    page_end#

    +

    Called after HTML content functions have completed. +$b is (string) HTML of content div.

    + +

    Called after HTML content functions have completed. +Deferred Javascript files should be registered using this hook. +$b is (string) HTML of footer div/element.

    +

    avatar_lookup#

    +

    Called when looking up the avatar. $b is an array:

    +
      +
    • size: the size of the avatar that will be looked up
    • +
    • email: email to look up the avatar for
    • +
    • url: the (string) generated URL of the avatar
    • +
    +

    emailer_send_prepare#

    +

    Called from Emailer::send() before building the mime message. +$b is an array of params to Emailer::send().

    +
      +
    • fromName: name of the sender
    • +
    • fromEmail: email fo the sender
    • +
    • replyTo: replyTo address to direct responses
    • +
    • toEmail: destination email address
    • +
    • messageSubject: subject of the message
    • +
    • htmlVersion: html version of the message
    • +
    • textVersion: text only version of the message
    • +
    • additionalMailHeader: additions to the smtp mail header
    • +
    • sent: default false, if set to true in the hook, the default mailer will be skipped.
    • +
    +

    emailer_send#

    +

    Called before calling PHP's mail(). +$b is an array of params to mail().

    +
      +
    • to
    • +
    • subject
    • +
    • body
    • +
    • headers
    • +
    • sent: default false, if set to true in the hook, the default mailer will be skipped.
    • +
    +

    load_config#

    +

    Called during App initialization to allow addons to load their own configuration file(s) with App::loadConfigFile().

    + +

    Called after the navigational menu is build in include/nav.php. +$b is an array containing $nav from include/nav.php.

    +

    template_vars#

    +

    Called before vars are passed to the template engine to render the page. +The registered function can add,change or remove variables passed to template. +$b is an array with:

    +
      +
    • template: filename of template
    • +
    • vars: array of vars passed to the template
    • +
    +

    acl_lookup_end#

    +

    Called after the other queries have passed. +The registered function can add, change or remove the acl_lookup() variables.

    +
      +
    • results: array of the acl_lookup() vars
    • +
    +

    prepare_body_init#

    +

    Called at the start of prepare_body +Hook data:

    +
      +
    • item (input/output): item array
    • +
    +

    prepare_body_content_filter#

    +

    Called before the HTML conversion in prepare_body. If the item matches a content filter rule set by an addon, it should +just add the reason to the filter_reasons element of the hook data. +Hook data:

    +
      +
    • item: item array (input)
    • +
    • filter_reasons (input/output): reasons array
    • +
    +

    prepare_body#

    +

    Called after the HTML conversion in prepare_body(). +Hook data:

    +
      +
    • item (input): item array
    • +
    • html (input/output): converted item body
    • +
    • is_preview (input): post preview flag
    • +
    • filter_reasons (input): reasons array
    • +
    +

    prepare_body_final#

    +

    Called at the end of prepare_body(). +Hook data:

    +
      +
    • item: item array (input)
    • +
    • html: converted item body (input/output)
    • +
    +

    put_item_in_cache#

    +

    Called after prepare_text() in put_item_in_cache(). +Hook data:

    +
      +
    • item (input): item array
    • +
    • rendered-html (input/output): final item body HTML
    • +
    • rendered-hash (input/output): original item body hash
    • +
    +

    magic_auth_success#

    +

    Called when a magic-auth was successful. +Hook data:

    +
    visitor => array with the contact record of the visitor
    +url => the query string
    +
    +

    jot_networks#

    +

    Called when displaying the post permission screen. +Hook data is a list of form fields that need to be displayed along the ACL. +Form field array structure is:

    +
      +
    • type: checkbox or select.
    • +
    • field: Standard field data structure to be used by field_checkbox.tpl and field_select.tpl.
    • +
    +

    For checkbox, field is: + - [0] (String): Form field name; Mandatory. + - [1]: (String): Form field label; Optional, default is none. + - [2]: (Boolean): Whether the checkbox should be checked by default; Optional, default is false. + - [3]: (String): Additional help text; Optional, default is none. + - [4]: (String): Additional HTML attributes; Optional, default is none.

    +

    For select, field is: + - [0] (String): Form field name; Mandatory. + - [1] (String): Form field label; Optional, default is none. + - [2] (Boolean): Default value to be selected by default; Optional, default is none. + - [3] (String): Additional help text; Optional, default is none. + - [4] (Array): Associative array of options. Item key is option value, item value is option label; Mandatory.

    +

    route_collection#

    +

    Called just before dispatching the router. +Hook data is a \FastRoute\RouterCollector object that should be used to add addon routes pointing to classes.

    +

    Notice: The class whose name is provided in the route handler must be reachable via autoloader.

    +

    probe_detect#

    +

    Called before trying to detect the target network of a URL. +If any registered hook function sets the result key of the hook data array, it will be returned immediately. +Hook functions should also return immediately if the hook data contains an existing result.

    +

    Hook data:

    +
      +
    • uri (input): the profile URI.
    • +
    • network (input): the target network (can be empty for auto-detection).
    • +
    • uid (input): the user to return the contact data for (can be empty for public contacts).
    • +
    • result (output): Leave null if address isn't relevant to the connector, set to contact array if probe is successful, false otherwise.
    • +
    + +

    Called when trying to probe an item from a given URI. +If any registered hook function sets the item_id key of the hook data array, it will be returned immediately. +Hook functions should also return immediately if the hook data contains an existing item_id.

    +

    Hook data: +- uri (input): the item URI. +- uid (input): the user to return the item data for (can be empty for public contacts). +- item_id (output): Leave null if URI isn't relevant to the connector, set to created item array if probe is successful, false otherwise.

    +

    support_follow#

    +

    Called to assert whether a connector addon provides follow capabilities.

    +

    Hook data: +- protocol (input): shorthand for the protocol. List of values is available in src/Core/Protocol.php. +- result (output): should be true if the connector provides follow capabilities, left alone otherwise.

    +

    support_revoke_follow#

    +

    Called to assert whether a connector addon provides follow revocation capabilities.

    +

    Hook data: +- protocol (input): shorthand for the protocol. List of values is available in src/Core/Protocol.php. +- result (output): should be true if the connector provides follow revocation capabilities, left alone otherwise.

    +

    follow#

    +

    Called before adding a new contact for a user to handle non-native network remote contact (like Twitter).

    +

    Hook data:

    +
      +
    • url (input): URL of the remote contact.
    • +
    • contact (output): should be filled with the contact (with uid = user creating the contact) array if follow was successful.
    • +
    +

    unfollow#

    +

    Called when unfollowing a remote contact on a non-native network (like Twitter)

    +

    Hook data: +- contact (input): the target public contact (uid = 0) array. +- uid (input): the id of the source local user. +- result (output): whether the unfollowing is successful or not.

    +

    revoke_follow#

    +

    Called when making a remote contact on a non-native network (like Twitter) unfollow you.

    +

    Hook data: +- contact (input): the target public contact (uid = 0) array. +- uid (input): the id of the source local user. +- result (output): a boolean value indicating whether the operation was successful or not.

    +

    block#

    +

    Called when blocking a remote contact on a non-native network (like Twitter).

    +

    Hook data: +- contact (input): the remote contact (uid = 0) array. +- uid (input): the user id to issue the block for. +- result (output): a boolean value indicating whether the operation was successful or not.

    +

    unblock#

    +

    Called when unblocking a remote contact on a non-native network (like Twitter).

    +

    Hook data: +- contact (input): the remote contact (uid = 0) array. +- uid (input): the user id to revoke the block for. +- result (output): a boolean value indicating whether the operation was successful or not.

    +

    storage_instance#

    +

    Called when a custom storage is used (e.g. webdav_storage)

    +

    Hook data: +- name (input): the name of the used storage backend +- data['storage'] (output): the storage instance to use (must implement \Friendica\Core\Storage\IWritableStorage)

    +

    storage_config#

    +

    Called when the admin of the node wants to configure a custom storage (e.g. webdav_storage)

    +

    Hook data: +- name (input): the name of the used storage backend +- data['storage_config'] (output): the storage configuration instance to use (must implement \Friendica\Core\Storage\Capability\IConfigureStorage)

    +

    Complete list of hook callbacks#

    +

    Here is a complete list of all hook callbacks with file locations (as of 24-Sep-2018). Please see the source for details of any hooks not documented above.

    +

    index.php#

    +
    Hook::callAll('init_1');
    +Hook::callAll('app_menu', $arr);
    +Hook::callAll('page_content_top', DI::page()['content']);
    +Hook::callAll($a->module.'_mod_init', $placeholder);
    +Hook::callAll($a->module.'_mod_init', $placeholder);
    +Hook::callAll($a->module.'_mod_post', $_POST);
    +Hook::callAll($a->module.'_mod_content', $arr);
    +Hook::callAll($a->module.'_mod_aftercontent', $arr);
    +Hook::callAll('page_end', DI::page()['content']);
    +
    +

    include/api.php#

    +
    Hook::callAll('logged_in', $a->user);
    +Hook::callAll('authenticate', $addon_auth);
    +Hook::callAll('logged_in', $a->user);
    +
    +

    include/enotify.php#

    +
    Hook::callAll('enotify', $h);
    +Hook::callAll('enotify_store', $datarray);
    +Hook::callAll('enotify_mail', $datarray);
    +Hook::callAll('check_item_notification', $notification_data);
    +
    +

    src/Content/Conversation.php#

    +
    Hook::callAll('conversation_start', $cb);
    +Hook::callAll('render_location', $locate);
    +Hook::callAll('display_item', $arr);
    +Hook::callAll('display_item', $arr);
    +Hook::callAll('item_photo_menu', $args);
    +Hook::callAll('jot_tool', $jotplugins);
    +
    +

    mod/directory.php#

    +
    Hook::callAll('directory_item', $arr);
    +
    +

    mod/xrd.php#

    +
    Hook::callAll('personal_xrd', $arr);
    +
    +

    mod/parse_url.php#

    +
    Hook::callAll("parse_link", $arr);
    +
    +

    src/Module/Delegation.php#

    +
    Hook::callAll('home_init', $ret);
    +
    +

    mod/acl.php#

    +
    Hook::callAll('acl_lookup_end', $results);
    +
    +

    mod/network.php#

    +
    Hook::callAll('network_content_init', $arr);
    +Hook::callAll('network_tabs', $arr);
    +
    +

    mod/friendica.php#

    +
    Hook::callAll('about_hook', $o);
    +
    +

    mod/profiles.php#

    +
    Hook::callAll('profile_post', $_POST);
    +Hook::callAll('profile_edit', $arr);
    +
    +

    mod/settings.php#

    +
    Hook::callAll('addon_settings_post', $_POST);
    +Hook::callAll('connector_settings_post', $_POST);
    +Hook::callAll('display_settings_post', $_POST);
    +Hook::callAll('addon_settings', $settings_addons);
    +Hook::callAll('connector_settings', $settings_connectors);
    +Hook::callAll('display_settings', $o);
    +
    +

    mod/photos.php#

    +
    Hook::callAll('photo_post_init', $_POST);
    +Hook::callAll('photo_post_file', $ret);
    +Hook::callAll('photo_post_end', $foo);
    +Hook::callAll('photo_post_end', $foo);
    +Hook::callAll('photo_post_end', $foo);
    +Hook::callAll('photo_post_end', $foo);
    +Hook::callAll('photo_post_end', intval($item_id));
    +Hook::callAll('photo_upload_form', $ret);
    +
    +

    mod/profile.php#

    +
    Hook::callAll('profile_advanced', $o);
    +
    +

    mod/home.php#

    +
    Hook::callAll('home_init', $ret);
    +Hook::callAll("home_content", $content);
    +
    +

    mod/poke.php#

    +
    Hook::callAll('post_local_end', $arr);
    +
    +

    mod/contacts.php#

    +
    Hook::callAll('contact_edit_post', $_POST);
    +Hook::callAll('contact_edit', $arr);
    +
    +

    mod/tagger.php#

    +
    Hook::callAll('post_local_end', $arr);
    +
    +

    mod/uexport.php#

    +
    Hook::callAll('uexport_options', $options);
    +
    +

    mod/register.php#

    +
    Hook::callAll('register_post', $arr);
    +Hook::callAll('register_form', $arr);
    +
    +

    mod/item.php#

    +
    Hook::callAll('post_local_start', $_REQUEST);
    +Hook::callAll('post_local', $datarray);
    +Hook::callAll('post_local_end', $datarray);
    +
    +

    mod/editpost.php#

    +
    Hook::callAll('jot_tool', $jotplugins);
    +
    +

    src/Render/FriendicaSmartyEngine.php#

    +
    Hook::callAll("template_vars", $arr);
    +
    +

    src/App.php#

    +
    Hook::callAll('load_config');
    +Hook::callAll('head');
    +Hook::callAll('footer');
    +Hook::callAll('route_collection');
    +
    +

    src/Model/Item.php#

    +
    Hook::callAll('post_local', $item);
    +Hook::callAll('post_remote', $item);
    +Hook::callAll('post_local_end', $posted_item);
    +Hook::callAll('post_remote_end', $posted_item);
    +Hook::callAll('tagged', $arr);
    +Hook::callAll('post_local_end', $new_item);
    +Hook::callAll('put_item_in_cache', $hook_data);
    +Hook::callAll('prepare_body_init', $item);
    +Hook::callAll('prepare_body_content_filter', $hook_data);
    +Hook::callAll('prepare_body', $hook_data);
    +Hook::callAll('prepare_body_final', $hook_data);
    +
    +

    src/Model/Contact.php#

    +
    Hook::callAll('contact_photo_menu', $args);
    +Hook::callAll('follow', $arr);
    +
    +

    src/Model/Profile.php#

    +
    Hook::callAll('profile_sidebar_enter', $profile);
    +Hook::callAll('profile_sidebar', $arr);
    +Hook::callAll('profile_tabs', $arr);
    +Hook::callAll('zrl_init', $arr);
    +Hook::callAll('magic_auth_success', $arr);
    +
    +

    src/Model/Event.php#

    +
    Hook::callAll('event_updated', $event['id']);
    +Hook::callAll("event_created", $event['id']);
    +
    +

    src/Model/Register.php#

    +
    Hook::callAll('authenticate', $addon_auth);
    +
    +

    src/Model/User.php#

    +
    Hook::callAll('authenticate', $addon_auth);
    +Hook::callAll('register_account', $uid);
    +Hook::callAll('remove_user', $user);
    +
    +

    src/Module/Notifications/Ping.php#

    +
    Hook::callAll('network_ping', $arr);
    +
    +

    src/Module/PermissionTooltip.php#

    +
    Hook::callAll('lockview_content', $item);
    +
    +

    src/Module/Settings/Delegation.php#

    +
    Hook::callAll('authenticate', $addon_auth);
    +
    +

    src/Module/Settings/TwoFactor/Index.php#

    +
    Hook::callAll('authenticate', $addon_auth);
    +
    +

    src/Security/Authenticate.php#

    +
    Hook::callAll('authenticate', $addon_auth);
    +
    +

    src/Security/ExAuth.php#

    +
    Hook::callAll('authenticate', $addon_auth);
    +
    +

    src/Content/ContactBlock.php#

    +
    Hook::callAll('contact_block_end', $arr);
    +
    +

    src/Content/Text/BBCode.php#

    +
    Hook::callAll('bbcode', $text);
    +Hook::callAll('bb2diaspora', $text);
    +
    +

    src/Content/Text/HTML.php#

    +
    Hook::callAll('html2bbcode', $message);
    +
    +

    src/Content/Smilies.php#

    +
    Hook::callAll('smilie', $params);
    +
    +

    src/Content/Feature.php#

    +
    Hook::callAll('isEnabled', $arr);
    +Hook::callAll('get', $arr);
    +
    +

    src/Content/ContactSelector.php#

    +
    Hook::callAll('network_to_name', $nets);
    +
    +

    src/Content/OEmbed.php#

    +
    Hook::callAll('oembed_fetch_url', $embedurl, $j);
    +
    +

    src/Content/Nav.php#

    +
    Hook::callAll('page_header', DI::page()['nav']);
    +Hook::callAll('nav_info', $nav);
    +
    +

    src/Core/Authentication.php#

    +
    Hook::callAll('logged_in', $a->user);
    +
    +

    src/Core/Protocol.php#

    +
    Hook::callAll('support_follow', $hook_data);
    +Hook::callAll('support_revoke_follow', $hook_data);
    +Hook::callAll('unfollow', $hook_data);
    +Hook::callAll('revoke_follow', $hook_data);
    +Hook::callAll('block', $hook_data);
    +Hook::callAll('unblock', $hook_data);
    +
    +

    src/Core/StorageManager#

    +
    Hook::callAll('storage_instance', $data);
    +Hook::callAll('storage_config', $data);
    +
    +

    src/Worker/Directory.php#

    +
    Hook::callAll('globaldir_update', $arr);
    +
    +

    src/Worker/Notifier.php#

    +
    Hook::callAll('notifier_end', $target_item);
    +
    +

    src/Module/Login.php#

    +
    Hook::callAll('login_hook', $o);
    +
    +

    src/Module/Logout.php#

    +
    Hook::callAll("logging_out");
    +
    +

    src/Object/Post.php#

    +
    Hook::callAll('render_location', $locate);
    +Hook::callAll('display_item', $arr);
    +
    +

    src/Core/ACL.php#

    +
    Hook::callAll('contact_select_options', $x);
    +Hook::callAll($a->module.'_pre_'.$selname, $arr);
    +Hook::callAll($a->module.'_post_'.$selname, $o);
    +Hook::callAll($a->module.'_pre_'.$selname, $arr);
    +Hook::callAll($a->module.'_post_'.$selname, $o);
    +Hook::callAll('jot_networks', $jotnets);
    +
    +

    src/Core/Authentication.php#

    +
    Hook::callAll('logged_in', $a->user);
    +Hook::callAll('authenticate', $addon_auth);
    +
    +

    src/Core/Hook.php#

    +
    self::callSingle(self::getApp(), 'hook_fork', $fork_hook, $hookdata);
    +
    +

    src/Core/L10n/L10n.php#

    +
    Hook::callAll('poke_verbs', $arr);
    +
    +

    src/Core/Worker.php#

    +
    Hook::callAll("proc_run", $arr);
    +
    +

    src/Util/Emailer.php#

    +
    Hook::callAll('emailer_send_prepare', $params);
    +Hook::callAll("emailer_send", $hookdata);
    +
    +

    src/Util/Map.php#

    +
    Hook::callAll('generate_map', $arr);
    +Hook::callAll('generate_named_map', $arr);
    +Hook::callAll('Map::getCoordinates', $arr);
    +
    +

    src/Util/Network.php#

    +
    Hook::callAll('avatar_lookup', $avatar);
    +
    +

    src/Util/ParseUrl.php#

    +
    Hook::callAll("getsiteinfo", $siteinfo);
    +
    +

    src/Protocol/DFRN.php#

    +
    Hook::callAll('atom_feed_end', $atom);
    +Hook::callAll('atom_feed_end', $atom);
    +
    +

    src/Protocol/Email.php#

    +
    Hook::callAll('email_getmessage', $message);
    +Hook::callAll('email_getmessage_end', $ret);
    +
    +

    view/js/main.js#

    +
    document.dispatchEvent(new Event('postprocess_liveupdate'));
    +
    + + +
    + +
    +
    + + + +
    + + + +
    +
    +
    +
    + + + + + + + + \ No newline at end of file diff --git a/develop/developer/autoloader/index.html b/develop/developer/autoloader/index.html new file mode 100644 index 0000000..32414e1 --- /dev/null +++ b/develop/developer/autoloader/index.html @@ -0,0 +1,3513 @@ + + + + + + + + + + + + + + + + + + + + + + Autoloader - Friendica documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + +
    + + + + +
    + + +
    + +
    + + + + + + + + + +
    +
    + + + +
    +
    +
    + + + + + + +
    +
    +
    + + + +
    +
    +
    + + + +
    +
    +
    + + +
    +
    + + + + + + + + + + + + + +

    Autoloader with Composer#

    +

    Friendica uses Composer to manage dependencies libraries and the class autoloader both for libraries and namespaced Friendica classes.

    +

    It's a command-line tool that downloads required libraries into the vendor folder and makes any namespaced class in src available through the whole application through boot.php.

    + +

    A quick introduction to class auto-loading#

    +

    The autoloader dynamically includes the file defining a class when it is first referenced, either by instantiating an object or simply making sure that it is available, without the need to explicitly use "require_once".

    +

    Once it is set up you don't have to directly use it, you can directly use any class that is covered by the autoloader (currently vendor and src)

    +

    Under the hood, Composer registers a callback with spl_autoload_register() that receives a class name as an argument and includes the corresponding class definition file. +For more info about PHP autoloading, please refer to the official PHP documentation.

    +

    Example#

    +

    Let's say you have a PHP file in src/ that define a very useful class:

    +
    // src/ItemsManager.php
    +<?php
    +namespace Friendica;
    +
    +class ItemsManager {
    +    public function getAll() { ... }
    +    public function getByID($id) { ... }
    +}
    +
    +

    The class ItemsManager has been declared in the Friendica namespace. +Namespaces are useful to keep classes separated and avoid names conflicts (could be that a library you want to use also defines a class named ItemsManager, but as long as it is in another namespace, you don't have any problem)

    +

    Let's say now that you need to load some items in a view, maybe in a fictional mod/network.php. +In order for the Composer autoloader to work, it must first be included. +In Friendica this is already done at the top of boot.php, with require_once('vendor/autoload.php');.

    +

    The code will be something like:

    +
    // mod/network.php
    +<?php
    +
    +use Friendica\App;
    +
    +function network_content(App $a) {
    +    $itemsmanager = new \Friendica\ItemsManager();
    +    $items = $itemsmanager->getAll();
    +
    +    // pass $items to template
    +    // return result
    +}
    +
    +

    That's a quite simple example, but look: no require()! +If you need to use a class, you can simply use it, and you don't need to do anything else.

    +

    Going further: now we have a bunch of *Manager classes that cause some code duplication. +Let's define a BaseManager class, where we move all common code between all managers:

    +
    // src/BaseManager.php
    +<?php
    +namespace Friendica;
    +
    +class BaseManager {
    +    public function thatFunctionEveryManagerUses() { ... }
    +}
    +
    +

    and then let's change the ItemsManager class to use this code

    +
    // src/ItemsManager.php
    +<?php
    +namespace Friendica;
    +
    +class ItemsManager extends BaseManager {
    +    public function getAll() { ... }
    +    public function getByID($id) { ... }
    +}
    +
    +

    Even though we didn't explicitly include the src/BaseManager.php file, the autoloader will when this class is first defined, because it is referenced as a parent class. +It works with the "BaseManager" example here, and it works when we need to call static methods:

    +
    // src/Dfrn.php
    +<?php
    +namespace Friendica;
    +
    +class Dfrn {
    +    public static function  mail($item, $owner) { ... }
    +}
    +
    +
    // mod/mail.php
    +<?php
    +
    +mail_post($a){
    +    ...
    +    Friendica\Protocol\DFRN::mail($item, $owner);
    +    ...
    +}
    +
    +

    If your code is in same namespace as the class you need, you don't need to prepend it:

    +
    // include/delivery.php
    +<?php
    +
    +namespace Friendica;
    +
    +use Friendica\Protocol\DFRN;
    +
    +// this is the same content of current include/delivery.php,
    +// but has been declared to be in "Friendica" namespace
    +
    +[...]
    +switch($contact['network']) {
    +    case NETWORK_DFRN:
    +        if ($mail) {
    +            $item['body'] = ...
    +            $atom = DFRN::mail($item, $owner);
    +        } elseif ($fsuggest) {
    +            $atom = DFRN::fsuggest($item, $owner);
    +            q("DELETE FROM `fsuggest` WHERE `id` = %d LIMIT 1", intval($item['id']));
    +        } elseif ($relocate)
    +            $atom = DFRN::relocate($owner, $uid);
    +[...]
    +
    +

    This is the current code of include/delivery.php, and since the code is declared to be in the "Friendica" namespace, you don't need to write it when you need to use the "Dfrn" class. +But if you want to use classes from another library, you need to use the full namespace, e.g.

    +
    // src/Diaspora.php
    +<?php
    +
    +namespace Friendica;
    +
    +class Diaspora {
    +    public function md2bbcode() {
    +        $html = \Michelf\MarkdownExtra::defaultTransform($text);
    +    }
    +}
    +
    +

    if you use that class in many places of the code, and you don't want to write the full path to the class every time, you can use the "use" PHP keyword

    +
    // src/Diaspora.php
    +<?php
    +namespace Friendica;
    +
    +use \Michelf\MarkdownExtra;
    +
    +class Diaspora {
    +    public function md2bbcode() {
    +        $html = MarkdownExtra::defaultTransform($text);
    +    }
    +}
    +
    +

    Note that namespaces are like paths in filesystem, separated by "\", with the first "\" being the global scope. +You can go deeper if you want to, like:

    +
    // src/Network/Dfrn.php
    +<?php
    +namespace Friendica\Network;
    +
    +class Dfrn {
    +}
    +
    +

    Please note that the location of the file defining the class must be placed in the appropriate sub-folders of src if the namespace isn't plain Friendica.

    +

    or

    +
    // src/Dba/Mysql
    +<?php
    +namespace Friendica\Dba;
    +
    +class Mysql {
    +}
    +
    +

    So you can think of namespaces as folders in a Unix file system, with global scope as the root ("\").

    + + + + +
    + +
    +
    + + + +
    + + + +
    +
    +
    +
    + + + + + + + + \ No newline at end of file diff --git a/develop/developer/composer/index.html b/develop/developer/composer/index.html new file mode 100644 index 0000000..59fa40a --- /dev/null +++ b/develop/developer/composer/index.html @@ -0,0 +1,3599 @@ + + + + + + + + + + + + + + + + + + + + + + Composer - Friendica documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + +
    + + + + +
    + + +
    + +
    + + + + + + + + + +
    +
    + + + +
    +
    +
    + + + + + + +
    +
    +
    + + + + + + +
    +
    + + + + + + + + + + + + + +

    Using Composer#

    +

    Friendica uses Composer to manage dependencies libraries and the class autoloader both for libraries and namespaced Friendica classes.

    +

    It's a command-line tool that downloads required libraries into the vendor folder and makes any namespaced class in src available through the whole application through boot.php.

    + +

    How to use Composer#

    +

    If you don't have Composer installed on your system, Friendica ships with a copy of it at bin/composer.phar. +For the purpose of this help, all examples will use this path to run Composer commands, however feel free to replace them with your own way of calling Composer. +Composer requires PHP CLI and the following examples assume it's available system-wide.

    +

    Installing/Updating Friendica#

    +

    From Archive#

    +

    If you just unpacked a Friendica release archive, you don't have to use Composer at all, all the required libraries are already bundled in the archive.

    +

    Installing with Git#

    +

    If you prefer to use git, you will have to run Composer to fetch the required libraries and build the autoloader before you can run Friendica. +Here are the typical commands you will have to run to do so:

    +
    ~> git clone https://github.com/friendica/friendica.git friendica
    +~/friendica> cd friendica
    +~/friendica> bin/composer.phar install
    +
    +

    That's it! Composer will take care of fetching all the required libraries in the vendor folder and build the autoloader to make those libraries available to Friendica.

    +

    Updating with Git#

    +

    Updating Friendica to the current stable or the latest develop version is easy with Git, just remember to run Composer after every branch pull.

    +
    ~> cd friendica
    +~/friendica> git pull
    +~/friendica> bin/composer.phar install
    +
    +

    And that's it. If any library used by Friendica has been upgraded, Composer will fetch the version currently used by Friendica and refresh the autoloader to ensure the best performances.

    +

    Developing Friendica#

    +

    First, thanks for contributing to Friendica! +Composer is meant to be used by developers to maintain third-party libraries required by Friendica. +If you don't need to use any third-party library, then you don't need to use Composer beyond what is above to install/update Friendica.

    +

    Adding a third-party library to Friendica#

    +

    Does your shiny new Addon need to rely on a third-party library not required by Friendica yet? +First, this library should be available on Packagist so that Composer knows how to fetch it directly just by mentioning its name in composer.json.

    +

    This file is the configuration of Friendica for Composer. It lists details about the Friendica project, but also a list of required dependencies and their target version. +Here's a simplified version of the one we currently use on Friendica:

    +
    {
    +    "name": "friendica/friendica",
    +    "description": "A decentralized social network part of The Federation",
    +    "type": "project",
    +    [...]
    +    "require": {
    +        "ezyang/htmlpurifier": "~4.7.0",
    +        "mobiledetect/mobiledetectlib": "2.8.*"
    +    },
    +    [...]
    +}
    +
    +

    The important part is under the require key, this is a list of all the libraries Friendica may need to run. +As you can see, at the moment we only require two, HTMLPurifier and MobileDetect. +Each library has a different target version, and per Composer documentation about version constraints, this means that:

    +
      +
    • We will update HTMLPurifier up to version 4.8.0 excluded
    • +
    • We will update MobileDetect up to version 2.9.0 excluded
    • +
    +

    There are other operators you can use to allow Composer to update the package up to the next major version excluded. +Or you can specify the exact version of the library if you code requires it, and Composer will never update it, although it isn't recommended.

    +

    To add a library, just add its Packagist identifier to the require list and set a target version string.

    +

    Then you should run bin/composer.phar update to add it to your local vendor folder and update the composer.lock file that specifies the current versions of the dependencies.

    +

    Updating an existing dependency#

    +

    If a package needs to be updated, whether to the next minor version or to the next major version provided you changed the adequate code in Friendica, simply edit composer.json to update the target version string of the relevant library.

    +

    Then you should run bin/composer.phar update to update it in your local vendor folder and update the composer.lock file that specifies the current versions of the dependencies.

    +

    Please note that you should commit both composer.json and composer.lock with your work every time you make a change to the former.

    +

    Composer FAQ#

    +

    I used the composer command and got a warning about not to run it as root.#

    +

    See https://getcomposer.org/root. +Composer should be run as the web server user, usually www-data with Apache or http with nginx. +If you can't switch to the web server user using su - [web user], you can directly run the Composer command with sudo -u [web user].

    +

    Running Composer with sudo complains about not being able to create the composer cache directory in /root/.composer#

    +

    This is because sudo doesn't always change the HOME environment variable, which means that the command is run as the web server user but the system still uses root home directory. +However, you can temporarily change environment variable for the execution of a single command. +For Composer, this would be: +

    $> COMPOSER_HOME=/var/tmp/composer sudo -u [web user] bin/composer.phar [mode]
    +

    + + + + +
    + +
    +
    + + + +
    + + + +
    +
    +
    +
    + + + + + + + + \ No newline at end of file diff --git a/develop/developer/domain-driven-design/index.html b/develop/developer/domain-driven-design/index.html new file mode 100644 index 0000000..6014eeb --- /dev/null +++ b/develop/developer/domain-driven-design/index.html @@ -0,0 +1,3619 @@ + + + + + + + + + + + + + + + + + + + + + + DDD - Friendica documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + +
    + + + + +
    + + +
    + +
    + + + + + + + + + +
    +
    + + + +
    +
    +
    + + + + + + +
    +
    +
    + + + +
    +
    +
    + + + +
    +
    +
    + + +
    +
    + + + + + + + + + + + + + +

    Domain-Driven-Design#

    +

    Friendica uses class structures inspired by Domain-Driven-Design programming patterns. +This page is meant to explain what it means in practical terms for Friendica development.

    +

    Inspiration#

    +
      +
    • https://designpatternsphp.readthedocs.io/en/latest/Structural/DependencyInjection/README.html
    • +
    • https://designpatternsphp.readthedocs.io/en/latest/Creational/SimpleFactory/README.html
    • +
    • https://designpatternsphp.readthedocs.io/en/latest/More/Repository/README.html
    • +
    • https://designpatternsphp.readthedocs.io/en/latest/Creational/FactoryMethod/README.html
    • +
    • https://designpatternsphp.readthedocs.io/en/latest/Creational/Prototype/README.html
    • +
    +

    Core concepts#

    +

    Models and Collections#

    +

    Instead of anonymous arrays of database field values, we have Models and collections to take full advantage of PHP type hints.

    +

    Before: +

    <?php
    +
    +function doSomething(array $intros)
    +{
    +    foreach ($intros as $intro) {
    +        $introId = $intro['id'];
    +    }
    +}
    +
    +$intros = \Friendica\Database\DBA::selectToArray('intros', [], ['uid' => local_user()]);
    +
    +doSomething($intros);
    +

    +

    After:

    +
    <?php
    +
    +function doSomething(\Friendica\Contact\Introductions\Collection\Introductions $intros)
    +{
    +    foreach ($intros as $intro) {
    +        /** @var $intro \Friendica\Contact\Introductions\Entity\Introduction */
    +        $introId = $intro->id;
    +    }
    +}
    +
    +/** @var $intros \Friendica\Contact\Introductions\Collection\Introductions */
    +$intros = \Friendica\DI::intro()->selecForUser(local_user());
    +
    +doSomething($intros);
    +
    +

    Dependency Injection#

    +

    Under this concept, we want class objects to carry with them the dependencies they will use. +Instead of calling global/static function/methods, objects use their own class members.

    +

    Before: +

    <?php
    +
    +class Model
    +{
    +    public $id;
    +
    +    function save()
    +    {
    +        return \Friendica\Database\DBA::update('table', get_object_vars($this), ['id' => $this->id]);
    +    }
    +}
    +

    +

    After: +

    <?php
    +
    +class Model
    +{
    +    /**
    +     * @var \Friendica\Database\Database
    +     */
    +    protected $dba;
    +
    +    public $id;
    +
    +    function __construct(\Friendica\Database\Database $dba)
    +    {
    +        $this->dba = $dba;
    +    }
    +
    +    function save()
    +    {
    +        return $this->dba->update('table', get_object_vars($this), ['id' => $this->id]);
    +    }
    +}
    +

    +

    The main advantage is testability. +Another one is avoiding dependency circles and avoid implicit initializing. +In the first example the method save() has to be tested with the DBA::update() method, which may or may not have dependencies itself.

    +

    In the second example we can mock \Friendica\Database\Database, e.g. overload the class by replacing its methods by placeholders, which allows us to test only Model::save() and nothing else implicitly.

    +

    The main drawback is lengthy constructors for dependency-heavy classes. +To alleviate this issue we are using DiCe to simplify the instantiation of the higher level objects Friendica uses.

    +

    We also added a convenience factory named \Friendica\DI that creates some of the most common objects used in modules.

    +

    Factories#

    +

    Since we added a bunch of parameters to class constructors, instantiating objects has become cumbersome. +To keep it simple, we are using Factories. +Factories are classes used to generate other objects, centralizing the dependencies required in their constructor. +Factories encapsulate more or less complex creation of objects and create them redundancy free.

    +

    Before: +

    <?php
    +
    +$model = new Model(\Friendica\DI::dba());
    +$model->id = 1;
    +$model->key = 'value';
    +
    +$model->save();
    +

    +

    After: +

    <?php
    +
    +class Factory
    +{
    +    /**
    +     * @var \Friendica\Database\Database
    +     */
    +    protected $dba;
    +
    +    function __construct(\Friendica\Database\Database $dba)
    +    {
    +        $this->dba;
    +    }
    +
    +    public function create()
    +    {
    +        return new Model($this->dba);    
    +    }
    +}
    +
    +$model = \Friendica\DI::factory()->create();
    +$model->id = 1;
    +$model->key = 'value';
    +
    +$model->save();
    +

    +

    Here, DI::factory() returns an instance of Factory that can then be used to create a Model object without having to care about its dependencies.

    +

    Repositories#

    +

    Last building block of our code architecture, repositories are meant as the interface between models and how they are stored. +In Friendica they are stored in a relational database but repositories allow models not to have to care about it. +Repositories also act as factories for the Model they are managing.

    +

    Before: +

    <?php
    +
    +class Model
    +{
    +    /**
    +     * @var \Friendica\Database\Database
    +     */
    +    protected $dba;
    +
    +    public $id;
    +
    +    function __construct(\Friendica\Database\Database $dba)
    +    {
    +        $this->dba = $dba;
    +    }
    +
    +    function save()
    +    {
    +        return $this->dba->update('table', get_object_vars($this), ['id' => $this->id]);
    +    }
    +}
    +
    +class Factory
    +{
    +    /**
    +     * @var \Friendica\Database\Database
    +     */
    +    protected $dba;
    +
    +    function __construct(\Friendica\Database\Database $dba)
    +    {
    +        $this->dba;
    +    }
    +
    +    public function create()
    +    {
    +        return new Model($this->dba);    
    +    }
    +}
    +
    +
    +$model = \Friendica\DI::factory()->create();
    +$model->id = 1;
    +$model->key = 'value';
    +
    +$model->save();
    +

    +

    After: +

    <?php
    +
    +class Model {
    +    public $id;
    +}
    +
    +class Repository extends Factory
    +{
    +    /**
    +     * @var \Friendica\Database\Database
    +     */
    +    protected $dba;
    +
    +    function __construct(\Friendica\Database\Database $dba)
    +    {
    +        $this->dba;
    +    }
    +
    +    public function create()
    +    {
    +        return new Model($this->dba);    
    +    }
    +
    +    public function save(Model $model)
    +    {
    +        return $this->dba->update('table', get_object_vars($model), ['id' => $model->id]);
    +    }
    +}
    +
    +$model = \Friendica\DI::repository()->create();
    +$model->id = 1;
    +$model->key = 'value';
    +
    +\Friendica\DI::repository()->save($model);
    +

    + + +
    + +
    +
    + + + +
    + + + +
    +
    +
    +
    + + + + + + + + \ No newline at end of file diff --git a/develop/developer/github/index.html b/develop/developer/github/index.html new file mode 100644 index 0000000..e4f2d88 --- /dev/null +++ b/develop/developer/github/index.html @@ -0,0 +1,3441 @@ + + + + + + + + + + + + + + + + + + + + + + GitHub - Friendica documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + +
    + + + + +
    + + +
    + +
    + + + + + + + + + +
    +
    + + + +
    +
    +
    + + + + + + +
    +
    +
    + + + +
    +
    + +
    +
    + + +
    +
    + + + + + + + + + + + + + +

    Friendica on GitHub#

    +

    Here is how you can work on the code with us. If you have any questions please write to the Friendica developers' forum.

    +

    Introduction to the workflow with our GitHub repository#

    +
      +
    1. Install git on the system you will be developing on.
    2. +
    3. Create your own GitHub account.
    4. +
    5. Fork the Friendica repository from https://github.com/friendica/friendica.git.
    6. +
    7. Clone your fork from your GitHub account to your machine. +Follow the instructions provided here: http://help.github.com/fork-a-repo/ to create and use your own tracking fork on GitHub
    8. +
    9. Run bin/composer.phar install in Friendica's folder.
    10. +
    11. Commit your changes to your fork. +Then go to your GitHub page and create a "Pull request" to notify us to merge your work.
    12. +
    +

    Our Git Branches#

    +

    There are two relevant branches in the main repo on GitHub:

    +
      +
    1. stable: This branch contains stable releases only.
    2. +
    3. develop: This branch contains the latest code. +This is what you want to work with.
    4. +
    +

    Fast-forwarding#

    +

    Fast forwarding is enabled by default in git. +When you merge with fast-forwarding it does not add a new commit to mark when you've performed the merge and how. +This means in your commit history you can't know exactly what happened in terms of merges. +It's best to turn off fast-forwarding. +This is done by running "git merge --no-ff". +Here is an explanation on how to configure git to turn off fast-forwarding by default. +You can find some more background reading here.

    +

    Release branches#

    +

    A release branch is created when the develop branch contains all features it should have. +A release branch is used for a few things.

    +
      +
    1. It allows last-minute bug fixing before the release goes to stable branch.
    2. +
    3. It allows meta-data changes (README, CHANGELOG, etc.) for version bumps and documentation changes.
    4. +
    5. It makes sure the develop branch can receive new features that are not part of this release.
    6. +
    +

    That last point is important because... +The moment a release branch is created, develop is now intended for the version after this release. +So please don't ever merge develop into a release! +An example: If a release branch "release-3.4" is created, "develop" becomes either 3.5 or 4.0. +If you were to merge develop into release-3.4 at this point, features and bug-fixes intended for 3.5 or 4.0 might leak into this release branch. +This might introduce new bugs, too. +Which defeats the purpose of the release branch.

    +

    Some important reminders#

    +
      +
    1. +

      Please pull in any changes from the project repository and merge them with your work before issuing a pull request. +We reserve the right to reject any patch which results in a large number of merge conflicts. +This is especially true in the case of language translations - where we may not be able to understand the subtle differences between conflicting versions.

      +
    2. +
    3. +

      Test your changes. +Don't assume that a simple fix won't break anything else. +If possible get an experienced Friendica developer to review the code. +Don't hesitate to ask us in case of doubt.

      +
    4. +
    5. +

      Check your code for typos. +There is a console command called typo for this.

      +
    6. +
    +
    $> php bin/console.php typo
    +
    +

    Check out how to work with our Vagrant to save a lot of setup time!

    + + +
    + +
    +
    + + + +
    + + + +
    +
    +
    +
    + + + + + + + + \ No newline at end of file diff --git a/develop/developer/how-to-move-classes-to-src/index.html b/develop/developer/how-to-move-classes-to-src/index.html new file mode 100644 index 0000000..af25b68 --- /dev/null +++ b/develop/developer/how-to-move-classes-to-src/index.html @@ -0,0 +1,3461 @@ + + + + + + + + + + + + + + + + + + + + + + src Migration - Friendica documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + +
    + + + + +
    + + +
    + +
    + + + + + + + + + +
    +
    + + + +
    +
    +
    + + + + + + +
    +
    +
    + + + +
    +
    +
    + + + +
    +
    +
    + + +
    +
    + + + + + + + + + + + + + +

    How To Move Classes to src#

    +

    Friendica uses Composer to manage autoloading. +This means that all the PHP class files moved to the src folder will be automatically included when the class it defines is first used in the flow. +This is an improvement over the current require usage since files will be included on an actual usage basis instead of the presence of a require call.

    +

    However, there are a significant number of items to check when moving a class file from the include folder to the src folder, and this page is there to list them.

    +

    Decide the namespace#

    +

    This isn't the most technical decision of them all, but it has long-lasting consequences as it will be the name that will be used to refer to this class from now on. +There is a shared Ethercalc sheet to suggest namespace/class names that lists all the already moved class files for inspiration.

    +

    A few pointers though: +* Friendica is the base namespace for all classes in the src folder +* Namespaces match the directory structure, with Friendica namespace being the base src directory. The Config class set in the Friendica\Core namespace is expected to be found at src/Core/Config.php. +* Namespaces can help group classes with a similar purpose or relevant to a particular feature

    +

    When you're done deciding the namespace, it's time to use it. +Let's say we choose Friendica\Core for the Config class.

    +

    Use the namespace#

    +

    To declare the namespace, the file src/Core/Config.php must start with the following statement:

    +
    namespace Friendica\Core;
    +
    +

    From now on, the Config class can be referred to as Friendica\Core\Config, however it isn't very practical, especially when the class was previously used as Config. +Thankfully, PHP provides namespace shortcuts through use.

    +

    This language construct just provides a different naming scheme for a namespace or a class, but doesn't trigger the autoload-mechanism on its own. +Here are the different ways you can use use:

    +

    // No use
    +$config = new Friendica\Core\Config();
    +
    +
    // Namespace shortcut
    +use Friendica\Core;
    +
    +$config = new Core\Config();
    +
    +
    // Class name shortcut
    +use Friendica\Core\Config;
    +
    +$config = new Config();
    +
    +
    // Aliasing
    +use Friendica\Core\Config as Cfg;
    +
    +$config = new Cfg();
    +

    +

    Whatever the style chosen, a repository-wide search has to be done to find all the class name usage and either use the fully-qualified class name (including the namespace) or add a use statement at the start of each relevant file.

    +

    Escape non-namespace classes#

    +

    The class file you just moved is now in the Friendica namespace, but it probably isn't the case for all the classes referenced in this file. +Since we added a namespace Friendica\Core; to the file, all the class names still declared in include will be implicitly understood as Friendica\Core\ClassName, which is rarely what we expect.

    +

    To avoid Class Friendica\Core\ClassName not found errors, all the include-declared class names have to be prepended with a \, it tells the autoloader not to look for the class in the namespace but in the global space where non-namespaced classes are set. +If there are only a handful of references to a single non-namespaced class, just prepending \ is enough. However, if there are many instance, we can use use again.

    +

    namespace Friendica\Core;
    +...
    +if (\DBM::is_result($r)) {
    +    ...
    +}
    +
    +
    namespace Friendica\Core;
    +
    +use Friendica\Database\DBM;
    +
    +if (DBM::is_result($r)) {
    +    ...
    +}
    +

    +

    Remove any useless require#

    +

    Now that you successfully moved your class to the autoloaded src folder, there's no need to include this file anywhere in the app ever again. +Please remove all the require_once mentions of the former file, as they will provoke a Fatal Error even if the class isn't used.

    +

    Miscellaneous tips#

    +

    When you are done with moving the class, please run php bin/console.php typo from the Friendica base directory to check for obvious mistakes. +However, this tool isn't bullet-proof, and a staging install of Friendica is recommended to test your class move without impairing your production server if you host one.

    +

    Most of Friendica processes are run in the background, so make sure to turn on your debug log to check for errors that wouldn't show up while simply browsing Friendica.

    +

    Check the class file for any magic constant __FILE__ or __DIR__, as their value changed since you moved the class in the file tree. +Most of the time it's used for debugging purposes but there can be instances where it's used to create cache folders for example.

    + + + + +
    + +
    +
    + + + +
    + + + +
    +
    +
    +
    + + + + + + + + \ No newline at end of file diff --git a/develop/developer/index.html b/develop/developer/index.html new file mode 100644 index 0000000..2f8c981 --- /dev/null +++ b/develop/developer/index.html @@ -0,0 +1,3632 @@ + + + + + + + + + + + + + + + + + + + + + + Get Started - Friendica documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + +
    + + + + +
    + + +
    + +
    + + + + + + + + + +
    +
    + + + +
    +
    +
    + + + + + + +
    +
    +
    + + + + + + +
    +
    + + + + + + + + + + + + + +

    Where to get started to help improve Friendica#

    +

    Do you want to help us improve Friendica? +Here we have compiled some hints on how to get started and some tasks to help you choose. +A project like Friendica is the sum of many contributions. +Very different skills are required to make good software, not all of them involve coding! +We are looking for helpers in all areas, whether you write text or code, whether you spread the word to convince people or design new icons. +Whether you feel like an expert or like a newbie - join us with your ideas!

    +

    Contact us#

    +

    The discussion of Friendica development takes place in the following Friendica forums:

    + +

    Help other users#

    +

    Remember the questions you had when you first tried Friendica? +A good place to start can be to help new people find their way around Friendica in the general support forum. +Welcome them, answer their questions, point them to documentation or ping other helpers directly if you can't help but think you know who can.

    +

    Translation#

    +

    The documentation contains help on how to translate Friendica at Transifex where the UI is translated. +If you don't want to translate the UI, or it is already done to your satisfaction, you might want to work on the translation of the /help files?

    +

    Design#

    +

    Are you good at designing things? +If you have seen Friendica you probably have ideas to improve it, haven't you?

    +
      +
    • If you would like to work with us on enhancing the user interface, please join the forum for Friendica development.
    • +
    • Make plans for a better Friendica interface design and share them with us.
    • +
    • Tell us if you are able to realize your ideas or what kind of help you need. + We can't promise we have the right skills in the group, but we'll try.
    • +
    • Choose a thing to start with, e.g. work on the icon set of your favorite theme
    • +
    +

    Programming#

    +

    Friendica uses an implementation of Domain-Driven-Design, please make sure to check out the provided links for hints at the expected code architecture.

    +

    Composer#

    +

    Friendica uses Composer to manage dependencies libraries and the class autoloader both for libraries and namespaced Friendica classes.

    +

    It's a command-line tool that downloads required libraries into the vendor folder and makes any namespaced class in src available through the whole application through boot.php.

    +

    If you want to have git automatically update the dependencies with composer, you can use the post-merge git-hook with a script similar to this one:

    +
    #!/usr/bin/env bash
    +# MIT © Sindre Sorhus - sindresorhus.com
    +# forked by Gianluca Guarini
    +# phponly by Ivo Bathke ;)
    +# modified for Friendica by Tobias Diekershoff
    +changed_files="$(git diff-tree -r --name-only --no-commit-id ORIG_HEAD HEAD)"
    +check_run() {
    +        echo "$changed_files" | grep --quiet "$1" && eval "$2"
    +}
    +# `composer install` if the `composer.lock` file gets changed
    +# to update all the php dependencies
    +check_run composer.lock "bin/composer.phar install --no-dev"
    +
    +

    just place it into .git/hooks/post-merge and make it executable.

    + +

    Coding standards#

    +

    For the sake of consistency between contribution and general code readability, Friendica follows the widespread PSR-2 coding standards to the exception to a few rules. +Here's a few primers if you are new to Friendica or to the PSR-2 coding standards:

    +
      +
    • Indentation is tabs, period (not PSR-2).
    • +
    • By default, strings are enclosed in single quotes, but feel free to use double quotes if it makes more sense (SQL queries, adding tabs and line feeds).
    • +
    • Operators are wrapped by spaces, e.g. $var === true, $var = 1 + 2 and 'string' . $concat . 'enation'
    • +
    • Braces are mandatory in conditions
    • +
    • Boolean operators are && and || for PHP conditions, AND and OR for SQL queries
    • +
    • No closing PHP tag
    • +
    • No trailing spaces
    • +
    • Array declarations use the new square brackets syntax
    • +
    • Quoting style is single quotes by default, except for needed string interpolation, SQL query strings by convention and comments that should stay in natural language.
    • +
    +

    Don't worry, you don't have to know by heart the PSR-2 coding standards to start contributing to Friendica. +There are a few tools you can use to check or fix your files before you commit.

    +

    For documentation, we use the standard of one sentence per line for the md files in the /doc and /doc/$lng subdirectories.

    +

    Check with PHP Code Sniffer#

    +

    This tool checks your files against a variety of coding standards, including PSR-2, and outputs a report of all the standard violations. +You can simply install it through PEAR: pear install PHP_CodeSniffer +Once it is installed and available in your PATH, here's the command to run before committing your work:

    +
    $> phpcs --standard=ruleset.xml <file or directory>
    +
    +

    The output is a list of all the coding standards violations that you should fix before committing your work. +Additionally, phpcs integrates with a few IDEs (Eclipse, Netbeans, PHPStorm...) so that you don't have to fiddle with the command line.

    +

    Fix with PHP Code Beautifier and Fixer (phpbcf) included in PHP Code Sniffer#

    +

    If you're getting a massive list of standards violations when running phpcs, it can be annoying to fix all the violations by hand. +Thankfully, PHP Code Sniffer is shipped with an automatic code fixer that can take care of the tedious task for you. +Here's the command to automatically fix the files you created/modified:

    +
    $> phpcbf --standard=ruleset.xml <file or directory>
    +
    +

    If the command-line tools diff and patch are unavailable for you, phpcbf can use slightly slower PHP equivalents by using the --no-patch argument.

    +

    Code documentation#

    +

    If you are interested in having the documentation of the Friendica code outside the code files, you can use Doxygen to generate it. +The configuration file for Doxygen is located in the base directory of the project sources. +Run

    +
    $> doxygen Doxyfile
    +
    +

    to generate the files which will be located in the doc/html subdirectory in the Friendica directory. +You can browse these files with any browser.

    +

    If you find missing documentation, don't hesitate to contact us and write it down to enhance the code documentation.

    +

    Issues#

    +

    Have a look at our issue tracker on GitHub!

    +
      +
    • Try to reproduce a bug that needs more inquiries and write down what you find out.
    • +
    • If a bug looks fixed, ask the bug reporters for feedback to find out if the bug can be closed.
    • +
    • Fix a bug if you can. Please make the pull request against the develop branch of the repository.
    • +
    • There is a Junior Job label for issues we think might be a good point to start with. + But you don't have to limit yourself to those issues.
    • +
    +

    Web interface#

    +

    The thing many people want most is a better interface, preferably a responsive Friendica theme. +This is a piece of work! +If you want to get involved here:

    +
      +
    • Look at the first steps that were made (e.g. the clean theme). + Ask us to find out whom to talk to about their experiences.
    • +
    • Talk to design people if you know any.
    • +
    • Let us know about your plans in the dev forum + Do not worry about cross-posting.
    • +
    +

    Client software#

    +

    As Friendica is using a Twitter/GNU Social compatible API any of the clients for those platforms should work with Friendica as well. +Furthermore, there are several client projects, especially for use with Friendica. +If you are interested in improving those clients, please contact the developers of the clients directly.

    + + + +
    + +
    +
    + + + +
    + + + +
    +
    +
    +
    + + + + + + + + \ No newline at end of file diff --git a/develop/developer/smarty3-templates/index.html b/develop/developer/smarty3-templates/index.html new file mode 100644 index 0000000..b701461 --- /dev/null +++ b/develop/developer/smarty3-templates/index.html @@ -0,0 +1,3652 @@ + + + + + + + + + + + + + + + + + + + + + + Smarty3 - Friendica documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + +
    + + + + +
    + + +
    + +
    + + + + + + + + + +
    +
    + + + +
    +
    +
    + + + + + + +
    +
    +
    + + + + + + +
    +
    + + + + + + + + + + + + + +

    Friendica Templating Documentation#

    +

    Friendica uses Smarty 3 as PHP templating engine. +The main templates are found in

    +
        /view/templates
    +
    +

    theme authors may overwrite the default templates by putting a files with the same name into the

    +
        /view/themes/$themename/templates
    +
    +

    directory.

    +

    Templates that are only used by addons shall be placed in the

    +
        /addon/$addonname/templates
    +
    +

    directory.

    +

    To render a template use the function getMarkupTemplate to load the template and replaceMacros to replace the macros/variables in the just loaded template file.

    +
    $tpl = Renderer::getMarkupTemplate('install_settings.tpl');
    +$o .= Renderer::replaceMacros($tpl, array( ... ));
    +
    +

    the array consists of an association of an identifier and the value for that identifier, i.e.

    +
        '$title' => $install_title,
    +
    +

    where the value may as well be an array by its own.

    +

    Form Templates#

    +

    To guarantee a consistent look and feel for input forms, i.e. in the settings sections, there are templates for the basic form fields. +They are initialized with an array of data, depending on the style of the field.

    +

    All of these take an array holding the values, e.g. for a one line text input field, which is required and should be used to type email addresses use something along the lines of:

    +
        '$adminmail' => array('adminmail', DI::l10n()->t('Site administrator email address'), $adminmail, DI::l10n()->t('Your account email address must match this in order to use the web admin panel.'), 'required', '', 'email'),
    +
    +

    To evaluate the input value, you can then use the $_POST array, more precisely the $_POST['adminemail'] variable.

    +

    Listed below are the template file names, the general purpose of the template and their field parameters.

    +

    field_checkbox.tpl#

    +

    A checkbox. +If the checkbox is checked its value is 1. +Field parameter:

    +
      +
    1. Name of the checkbox,
    2. +
    3. Label for the checkbox,
    4. +
    5. State checked? if true then the checkbox will be marked as checked,
    6. +
    7. Help text for the checkbox.
    8. +
    +

    field_combobox.tpl#

    +

    A combobox, combining a pull down selection and a textual input field. +Field parameter:

    +
      +
    1. Name of the combobox,
    2. +
    3. Label for the combobox,
    4. +
    5. Current value of the variable,
    6. +
    7. Help text for the combobox,
    8. +
    9. Array holding the possible values for the textual input,
    10. +
    11. Array holding the possible values for the pull down selection.
    12. +
    +

    field_custom.tpl#

    +

    A customizable template to include a custom element in the form with the usual surroundings, +Field parameter:

    +
      +
    1. Name of the field,
    2. +
    3. Label for the field,
    4. +
    5. the field,
    6. +
    7. Help text for the field.
    8. +
    +

    field_input.tpl#

    +

    A single line input field for any type of input. +Field parameter:

    +
      +
    1. Name of the field,
    2. +
    3. Label for the input box,
    4. +
    5. Current value of the variable,
    6. +
    7. Help text for the input box,
    8. +
    9. Should be set to the translation of "Required" to mark this field as required,
    10. +
    11. if set to "autofocus" modern browser will put the cursor into this box once the page is loaded,
    12. +
    13. if set, it will be used for the input type, default is text (possible types: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input#%3Cinput%3E_types).
    14. +
    +

    field_intcheckbox.tpl#

    +

    A checkbox (see above) but you can define the value of it. +Field parameter:

    +
      +
    1. Name of the checkbox,
    2. +
    3. Label for the checkbox,
    4. +
    5. State checked? if true then the checkbox will be marked as checked,
    6. +
    7. Value of the checkbox,
    8. +
    9. Help text for the checkbox.
    10. +
    +

    field_openid.tpl#

    +

    An input box (see above) but prepared for special CSS styling for openID input. +Field parameter:

    +
      +
    1. Name of the field,
    2. +
    3. Label for the input box,
    4. +
    5. Current value of the variable,
    6. +
    7. Help text for the input field.
    8. +
    +

    field_password.tpl#

    +

    A single line input field (see above) for textual input. +The characters typed in will not be shown by the browser. +Field parameter:

    +
      +
    1. Name of the field,
    2. +
    3. Label for the field,
    4. +
    5. Value for the field, e.g. the old password,
    6. +
    7. Help text for the input field,
    8. +
    9. Should be set to the translation of "Required" to mark this field as required,
    10. +
    11. if set to "autofocus" modern browser will put the cursor automatically into this input field.
    12. +
    +

    field_radio.tpl#

    +

    A radio button. +Field parameter:

    +
      +
    1. Name of the radio button,
    2. +
    3. Label for the radio button,
    4. +
    5. Current value of the variable,
    6. +
    7. Help text for the button,
    8. +
    9. if set, the radio button will be checked.
    10. +
    +

    field_richtext.tpl#

    +

    A multi-line input field for rich textual content. +Field parameter:

    +
      +
    1. Name of the input field,
    2. +
    3. Label for the input box,
    4. +
    5. Current text for the box,
    6. +
    7. Help text for the input box.
    8. +
    +

    field_select.tpl#

    +

    A drop-down selection box. +Field parameter:

    +
      +
    1. Name of the field,
    2. +
    3. Label of the selection box,
    4. +
    5. Current selected value,
    6. +
    7. Help text for the selection box,
    8. +
    9. Array holding the possible values of the selection drop-down.
    10. +
    +

    field_select_raw.tpl#

    +

    A drop-down selection box (see above) but you have to prepare the values yourself. +Field parameter:

    +
      +
    1. Name of the field,
    2. +
    3. Label of the selection box,
    4. +
    5. Current selected value,
    6. +
    7. Help text for the selection box,
    8. +
    9. Possible values of the selection drop-down.
    10. +
    +

    field_textarea.tpl#

    +

    A multi-line input field for (plain) textual content. +Field parameter:

    +
      +
    1. Name of the input field,
    2. +
    3. Label for the input box,
    4. +
    5. Current text for the box,
    6. +
    7. Help text for the input box,
    8. +
    9. Should be set to the translation of "Required" to mark this field as required.
    10. +
    + + +
    + +
    +
    + + + +
    + + + +
    +
    +
    +
    + + + + + + + + \ No newline at end of file diff --git a/develop/developer/tests/index.html b/develop/developer/tests/index.html new file mode 100644 index 0000000..c1b87e1 --- /dev/null +++ b/develop/developer/tests/index.html @@ -0,0 +1,3280 @@ + + + + + + + + + + + + + + + + + + + + + + Tests - Friendica documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + +
    + + + + +
    + + +
    + +
    + + + + + + + + + +
    +
    + + + +
    +
    +
    + + + + + + +
    +
    +
    + + + +
    +
    +
    + + + +
    +
    +
    + + +
    +
    + + + + + + + + + + + + + +

    Tests#

    +

    You can run unit tests with PHPUnit:

    +
    phpunit
    +
    +

    Some tests require access to a MySQL database. +You can specify the database credentials in environment variables:

    +
    USER=database_user PASS=database_password DB=database_name phpunit
    +
    +

    Warning: This will empty all the tables! Never use this on a production database.

    + + +
    + +
    +
    + + + +
    + + + +
    +
    +
    +
    + + + + + + + + \ No newline at end of file diff --git a/develop/developer/themes/index.html b/develop/developer/themes/index.html new file mode 100644 index 0000000..f7858c7 --- /dev/null +++ b/develop/developer/themes/index.html @@ -0,0 +1,3757 @@ + + + + + + + + + + + + + + + + + + + + + + Themes - Friendica documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + +
    + + + + +
    + + +
    + +
    + + + + + + + + + +
    +
    + + + +
    +
    +
    + + + + + + +
    +
    +
    + + + +
    +
    +
    + + + +
    +
    +
    + + +
    +
    + + + + + + + + + + + + + +

    Themes#

    +

    To change the look of friendica you have to touch the themes. +The current default theme is Vier but there are numerous others. +Have a look at github.com/bkil/friendica-themes for an overview of the existing themes. +In case none of them suits your needs, there are several ways to change a theme.

    +

    So, how to work on the UI of friendica.

    +

    You can either directly edit an existing theme. +But you might lose your changes when the theme is updated by the friendica team.

    +

    If you are almost happy with an existing theme, the easiest way to cover your needs is to create a new theme, inheritance most of the properties of the parent theme and change just minor stuff. +The below for a more detailed description of theme heritage.

    +

    Some themes also allow users to select variants of the theme. +Those theme variants most often contain an additional CSS file to override some styling of the default theme values. +From the themes in the main repository duepunto zero and vier are using these methods for variations. +Quattro is using a slightly different approach.

    +

    Third you can start your theme from scratch. +Which is the most complex way to change friendicas look. +But it leaves you the most freedom. +So below for a detailed description and the meaning of some special files.

    +

    Styling#

    +

    If you want to change the styling of a theme, have a look at the themes CSS file. +In most cases, you can find these in

    +
    /view/theme/**your-theme-name**/style.css
    +
    +

    sometimes, there is also a file called style.php in the theme directory. +This is only needed if the theme allows the user to change certain things of the theme dynamically. +Say the font size or set a background image.

    +

    Templates#

    +

    If you want to change the structure of the theme, you need to change the templates used by the theme. +Friendica themes are using SMARTY3 for templating. +The default template can be found in

    +
    /view/templates
    +
    +

    if you want to override any template within your theme create your version of the template in

    +
    /view/theme/**your-theme-name**/templates
    +
    +

    any template that exists there will be used instead of the default one.

    +

    Javascript#

    +

    The same rule applies to the JavaScript files found in

    +
    /js
    +
    +

    they will be overwritten by files in

    +
    /view/theme/**your-theme-name**/js.
    +
    +

    Expand an existing Theme#

    +

    Theme Variations#

    +

    Many themes are more theme families than only one theme. +duepunto zero and vier allow easily to add new theme variation. +We will go through the process of creating a new variation for duepunto zero. +The same (well almost, some names change) procedure applies to the vier theme. +And similar steps are needed for quattro but this theme is using lesscss to maintain the CSS files.

    +

    In

    +
    /view/theme/duepuntozero/deriv
    +
    +

    you find a couple of CSS files that define color derivations from the duepunto theme. +These resemble some now as unsupported marked themes, that were inherited by the duepunto theme. +Darkzero and Easter Bunny for example.

    +

    The selection of the colorset is done in a combination of a template for a new form in the settings and some functions in the theme.php file. +The template (theme_settings.tpl)

    +
    {{ '{{include file="field_select.tpl" field=$colorset}}' }}
    +<div class="settings-submit-wrapper">
    +    {{ '<input type="submit" value="{{$submit}}" class="settings-submit" name="duepuntozero-settings-submit" />' }}
    +</div>
    +
    +

    defines a formular consisting of a select pull-down which contains all available variants and s submit button. +See the documentation about SMARTY3 templates for a summary of friendica specific blocks other than the select element. +But we don't really need to change anything at the template itself.

    +

    The template alone won't work though. +You make friendica aware of its existence and tell it how to use the template file, by defining a config.php file. +It needs to define at least the following functions

    +
      +
    • theme_content
    • +
    • theme_post
    • +
    +

    and may also define functions for the admin interface

    +
      +
    • theme_admin
    • +
    • theme_admin_post.
    • +
    +

    theme_content and theme_admin are used to make the form available in the settings, respectively the admin panel. +The _post functions handle the processing of the send-form, in this case they save to selected variant in friendicas database.

    +

    To make your own variation appear in the menu, all you need to do is to create a new CSS file in the deriv-directory and include it in the array in the config.php:

    +
    $colorset = array(
    +    'default'=>DI::l10n()->t('default'),
    +    'greenzero'=>DI::l10n()->t('greenzero'),
    +    'purplezero'=>DI::l10n()->t('purplezero'),
    +    'easterbunny'=>DI::l10n()->t('easterbunny'),
    +    'darkzero'=>DI::l10n()->t('darkzero'),
    +    'comix'=>DI::l10n()->t('comix'),
    +    'slackr'=>DI::l10n()->t('slackr'),
    +);
    + ```
    +
    +the 1st part of the line is the name of the CSS file (without the .css) the 2nd part is the common name of the variant.
    +Calling the DI::l10n()->t() function with the common name makes the string translatable.
    +The selected 1st part will be saved in the database by the theme_post function.
    +
    +```php
    +<?php
    +function theme_post(App $a){
    +    // non-local users shall not pass
    +    if (! local_user()) {
    +        return;
    +    }
    +    // if the one specific submit button was pressed then proceed
    +    if (isset($_POST['duepuntozero-settings-submit'])){
    +        // and save the selection key into the personal config of the user
    +        DI::pConfig()->set(local_user(), 'duepuntozero', 'colorset', $_POST['duepuntozero_colorset']);
    +    }
    +}
    +
    +

    Now that this information is set in the database, what should friendica do with it? +For this, have a look at the theme.php file of the duepunto zero. +There you'll find something alike

    +
    <?php
    +$colorset = DI::pConfig()->get( local_user(), 'duepuntozero','colorset');
    +if (!$colorset)
    +    $colorset = DI::config()->get('duepuntozero', 'colorset');
    +if ($colorset) {
    +    if ($colorset == 'greenzero')
    +        DI::page()['htmlhead'] .= '<link rel="stylesheet" href="view/theme/duepuntozero/deriv/greenzero.css" type="text/css" media="screen" />'."\n";
    +    /* some more variants */
    +}
    +
    +

    which tells friendica to get the personal config of a user. +Check if it is set and if not look for the global config. +And finally if a config for the colorset was found, apply it by adding a link to the CSS file into the HTML header of the page. +So you'll just need to add an if selection, fitting your variant keyword and link to the CSS file of it.

    +

    Done. +Now you can use the variant on your system. +But remember once the theme.php or the config.php you have to re-add your variant to them. +If you think your color variation could be beneficial for other friendica users as well, feel free to generate a pull request at GitHub, so we can include your work into the repository.

    +

    Inheritance#

    +

    Say, you like the duepuntozero, but you want to have the content of the outer columns left and right exchanged. +That would be not a color variation as shown above. +Instead, we will create a new theme, duepuntozero_lr, inherit the properties of duepuntozero and make small changes to the underlying php files.

    +

    So create a directory called duepunto_lr and create a file called theme.php with your favorite text editor. +The content of this file should be something like

    +
    <?php
    +/* meta information for the theme, see below */
    +use Friendica\App;
    +
    +function duepuntozero_lr_init(App $a) {
    +    $a->setThemeInfoValue('extends', 'duepuntozero');
    +
    +    $a->set_template_engine('smarty3');
    +    /* and more stuff e.g. the JavaScript function for the header */
    +}
    +
    +

    Next take the default.php file found in the /view directory and exchange the aside and right_aside elements. +So the central part of the file now looks like this:

    +
    <body>
    +    <?php if(!empty($page['nav'])) echo $page['nav']; ?>
    +    <aside><?php if(!empty($page['right_aside'])) echo $page['right_aside']; ?></aside>
    +    <section><?php if(!empty($page['content'])) echo $page['content']; ?>
    +            <div id="page-footer"></div>
    +    </section>
    +    <right_aside><?php if(!empty($page['aside'])) echo $page['aside']; ?></right_aside>
    +    <footer><?php if(!empty($page['footer'])) echo $page['footer']; ?></footer>
    +</body>
    +
    +

    Finally, we need a style.css file, inheriting the definitions from the parent theme and containing out changes for the new theme. +Note:You need to create the style.css and at lest import the base CSS file from the parent theme.

    +
    @import url('../duepuntozero/style.css');
    +
    +

    Done. +But I agree it is not really useful at this state. +Nevertheless, to use it, you just need to activate in the admin panel. +That done, you can select it in the settings like any other activated theme.

    +

    Creating a Theme from Scratch#

    +

    Keep patient. +Basically what you have to do is identify which template you have to change, so it looks more like what you want. +Adopt the CSS of the theme accordingly. +And iterate the process until you have the theme the way you want it.

    +

    Use the source Luke. and don't hesitate to ask in @developers or @helpers.

    +

    Special Files#

    +

    unsupported#

    +

    If a file with this name (which might be empty) exists in the theme directory, the theme is marked as unsupported. +An unsupported theme may not be selected by a user in the settings. +Users who are already using it won't notice anything.

    +

    README(.md)#

    +

    The contents of this file, with or without the .md which indicates Markdown syntax, will be displayed at most repository hosting services and in the theme page within the admin panel of friendica.

    +

    This file should contain information you want to let others know about your theme.

    +

    screenshot.[png|jpg]#

    +

    If you want to have a preview image of your theme displayed in the settings you should take a screenshot and save it with this name. +Supported formats are PNG and JPEG.

    +

    theme.php#

    +

    This is the main definition file of the theme. +In the header of that file, some meta information is stored. +For example, have a look at the theme.php of the quattro theme:

    +
    <?php
    +/**
    + * Name: Quattro
    + * Version: 0.6
    + * Author: Fabio <https://kirgroup.com/profile/fabrixxm>
    + * Maintainer: Fabio <https://kirgroup.com/profile/fabrixxm>
    + * Maintainer: Tobias <https://f.diekershoff.de/profile/tobias>
    + */
    + ```
    +
    +You see the definition of the theme's name, it's version and the initial author of the theme.
    +These three pieces of information should be listed.
    +If the original author is no longer working on the theme, but a maintainer has taken over, the maintainer should be listed as well.
    +The information from the theme header will be displayed in the admin panel.
    +
    +The first thing in file is to import the `App` class from `\Friendica\` namespace.
    +
    +```php
    +use Friendica\App;
    +
    +

    This will make our job a little easier, as we don't have to specify the full name every time we need to use the App class.

    +

    The next crucial part of the theme.php file is a definition of an init function. +The name of the function is _init. +So in the case of quattro it is

    +
    <?php
    +function quattro_init(App $a) {
    +  $a->theme_info = array();
    +  $a->set_template_engine('smarty3');
    +}
    +
    +

    Here we have set the basic theme information, in this case they are empty. +But the array needs to be set. +And we have set the template engine that should be used by friendica for this theme. +At the moment you should use the smarty3 engine. +There once was a friendica specific templating engine as well but that is not used anymore. +If you like to use another templating engine, please implement it.

    +

    When you want to inherit stuff from another theme you have to announce this in the theme_info:

    +
    $a->setThemeInfoValue('extends', 'duepuntozero');
    +
    +

    which declares duepuntozero as parent of the theme.

    +

    If you want to add something to the HTML header of the theme, one way to do so is by adding it to the theme.php file. +To do so, add something alike

    +
    DI::page()['htmlhead'] .= <<< EOT
    +/* stuff you want to add to the header */
    +EOT;
    +
    +

    The $a variable holds the friendica application. +So you can access the properties of this friendica session from the theme.php file as well.

    +

    default.php#

    +

    This file covers the structure of the underlying HTML layout. +The default file is in

    +
    /view/default.php
    +
    +

    if you want to change it, say adding a 4th column for banners of your favourite FLOSS projects, place a new default.php file in your theme directory. +As with the theme.php file, you can use the properties of the $a variable with holds the friendica application to decide what content is displayed.

    + + +
    + +
    +
    + + + +
    + + + +
    +
    +
    +
    + + + + + + + + \ No newline at end of file diff --git a/develop/developer/translations/index.html b/develop/developer/translations/index.html new file mode 100644 index 0000000..30450e5 --- /dev/null +++ b/develop/developer/translations/index.html @@ -0,0 +1,3540 @@ + + + + + + + + + + + + + + + + + + + + + + Translations - Friendica documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + +
    + + + + +
    + + +
    + +
    + + + + + + + + + +
    +
    + + + +
    +
    +
    + + + + + + +
    +
    +
    + + + +
    +
    +
    + + + +
    +
    +
    + + +
    +
    + + + + + + + + + + + + + +

    Friendica translations#

    +

    Overview#

    +

    The Friendica translation process is based on gettext PO files.

    +

    Basic workflow: +1. xgettext is used to collect translation strings across the project in the authoritative PO file located in view/lang/C/messages.po. +2. This file makes translations strings available at the Transifex Friendica page. +3. The translation itself is done at Transifex by volunteers. +4. The resulting PO files by languages are manually updated in view/lang/<language>/messages.po. +5. PO files are converted to PHP arrays in view/lang/<language>/strings.php that are ultimately used by Friendica to display the translations.

    +

    Translate Friendica in your favorite language#

    +

    Thank you for your interest in improving Friendica's translation! +Please register a free Transifex account and ask over at the Transifex Friendica page to join the translation team for your favorite language.

    +

    As a rule of thumb, we add support for a language in Friendica when at least 50% of the strings have been translated to avoid a scattered experience. +For addons, we add support for a language when if we already support the language in Friendica.

    +

    Add new translation strings#

    +

    Core#

    +

    Once you have added new translation strings in your code changes, please run bin/run_xgettext.sh from the base Friendica directory and commit the updated view/lang/C/messages.po to your branch.

    +

    Addon#

    +

    If you have the friendica-addons repository in the addon directory of your Friendica cloned repository, just run bin/run_xgettext.sh -a <addon_name> from the base Friendica directory.

    +

    Otherwise:

    +
    cd /path/to/friendica-addons/<addon_name>
    +/path/to/friendica/bin/run_xgettext.sh -s
    +
    +

    In either case, you need to commit the updated <addon_name>/lang/C/messages.po to your working branch.

    +

    Update translations from Transifex#

    +

    Please download the Transifex file "for use" in view/lang/<language>/messages.po.

    +

    Then run bin/console po2php view/lang/<language>/messages.po to update the related strings.php file and commit both files to your working branch.

    +

    Using the Transifex client#

    +

    Transifex has a client program which allows you to sync files between your cloned Friendica repository and Transifex. +Help for the client can be found at the Transifex Help Center. +Here we will only cover basic usage.

    +

    After installation of the client, you should have a tx command available on your system. +To use it, first create a configuration file with your credentials. +On Linux this file should be placed into your home directory ~/.transifexrc. +The content of the file should be something like the following:

    +
    [https://www.transifex.com]
    +username = user
    +token =
    +password = p@ssw0rd
    +hostname = https://www.transifex.com
    +
    +

    Since Friendica version 3.5.1 we ship configuration files for the Transifex client in the core repository and the addon repository in .tx/config. +To update the PO files after you have translated strings of e.g. Esperanto on the Transifex website you can use tx to download the updated PO-file in the right location.

    +
    tx pull -l eo
    +
    +

    Then run bin/console po2php view/lang/<language>/messages.po to update the related strings.php file and commit both files to your working branch.

    +

    Translation functions usage#

    +

    Basic usage#

    +
      +
    • Friendica\DI::l10n()->t('Label') => Label
    • +
    • Friendica\DI::l10n()->t('Label %s', 'test') => Label test
    • +
    +

    Plural#

    +
      +
    • Friendica\DI::l10n()->tt('Label', 'Labels', 1) => Label
    • +
    • Friendica\DI::l10n()->tt('Label', 'Labels', 3) => Labels
    • +
    • Friendica\DI::l10n()->tt('%d Label', '%d Labels', 1) => 1 Label
    • +
    • Friendica\DI::l10n()->tt('%d Label', '%d Labels', 3) => 3 Labels
    • +
    • Friendica\DI::l10n()->tt('%d Label', 'Labels %2%s %3%s', 1, 'test', 'test2') => Label test test2
    • +
    • Friendica\DI::l10n()->tt('%d Label', 'Labels %2%s %3%s', 3, 'test', 'test2') => Labels test test2
    • +
    + + +
    + +
    +
    + + + +
    + + + +
    +
    +
    +
    + + + + + + + + \ No newline at end of file diff --git a/develop/developer/vagrant/index.html b/develop/developer/vagrant/index.html new file mode 100644 index 0000000..ae6abed --- /dev/null +++ b/develop/developer/vagrant/index.html @@ -0,0 +1,3396 @@ + + + + + + + + + + + + + + + + + + + + + + Vagrant - Friendica documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + +
    + + + + +
    + + +
    + +
    + + + + + + + + + +
    +
    + + + +
    +
    +
    + + + + + + +
    +
    +
    + + + +
    +
    +
    + + + +
    +
    +
    + + +
    +
    + + + + + + + + + + + + + +

    Vagrant for Friendica Developers#

    +

    Getting started#

    +

    Vagrant is a virtualization solution for developers. +No need to set up a webserver, database etc. before actually starting. +Vagrant creates a virtual machine for you that you can just run inside VirtualBox and start to work directly on Friendica.

    +

    It brings a Debian Bullseye with PHP 7.4 and MariaDB 10.5.11.

    +

    What you need to do:

    +
      +
    1. Install VirtualBox and vagrant. +Please use an up-to-date vagrant version from https://www.vagrantup.com/downloads.html.
    2. +
    3. Git clone your Friendica repository. +Inside, you'll find a Vagrantfile and some scripts in the bin/dev folder. +Pull the PHP requirements with bin/composer install.
    4. +
    5. Run vagrant up from inside the friendica clone. +This will start the virtual machine. +Be patient: When it runs for the first time, it downloads a Debian Server image and installs Friendica.
    6. +
    7. Run vagrant ssh to log into the virtual machine to log in to the VM in case you need to debug something on the server.
    8. +
    9. Open you test installation in a browser. +Go to friendica.local (or 192.168.22.10). +friendica.local is using a self-signed TLS certificate, so you will need to add an exception to trust the certificate the first time you are visiting the page. +The mysql database is called "friendica", the mysql user and password both are "friendica".
    10. +
    11. Work on Friendica's code in your git clone on your machine (not in the VM). +Your local working directory is set up as a shared directory with the VM (/vagrant).
    12. +
    13. Check the changes in your browser in the VM. +Find the Friendica log file /vagrant/logfile.out on the VM or in the logfile.out in you local Friendica directory.
    14. +
    15. Commit and push your changes directly back to GitHub.
    16. +
    +

    If you want to stop vagrant after finishing your work, run the following command

    +
    vagrant halt
    +
    +

    in the development directory. +This will not delete the virtual machine. +9. To ultimately delete the virtual machine run

    +
    vagrant destroy
    +rm /vagrant/config/local.config.php
    +
    +

    to make sure that you can start from scratch with another "vagrant up".

    +

    Default User Accounts#

    +

    By default, the provision script will set up two user accounts.

    +
      +
    • admin, password admin
    • +
    • friendica, password friendica
    • +
    +

    Trouble Shooting#

    +

    If you see a version mismatch for the VirtualBox Guest Additions between host and guest during the initial setup of the Vagrant VM, you will need to install an addon to Vagrant (ref. Stack Overflow). +Stop the Vagrant VM and run the following command:

    +
    vagrant plugin install vagrant-vbguest
    +
    +

    On the next Vagrant up, the version problem should be fixed.

    +

    If friendica.local is not resolved, you may need to add an entry to the /etc/hosts file (or similar configuration depending on the OS you are using).

    +

    For further documentation of vagrant, please see the vagrantdocs.

    + + +
    + +
    +
    + + + +
    + + + +
    +
    +
    +
    + + + + + + + + \ No newline at end of file diff --git a/develop/en/admin/config/index.html b/develop/en/admin/config/index.html new file mode 100644 index 0000000..e3d7e23 --- /dev/null +++ b/develop/en/admin/config/index.html @@ -0,0 +1,3731 @@ + + + + + + + + + + + + + + + + + + + + + + Config Values - Friendica documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + +
    + + + + +
    + + +
    + +
    + + + + + + + + + +
    +
    + + + +
    +
    +
    + + + + + + +
    +
    +
    + + + + + + +
    +
    + + + + + + + + + + + + + +

    Config values that can only be set in config/local.config.php#

    +

    Friendica's configuration is done in two places: in PHP array configuration files and in the config database table. +Database config values overwrite the same file config values.

    +

    File configuration#

    +

    The configuration format for file configuration is an array returned from a PHP file. +This prevents your webserver from displaying your private configuration. It interprets the configuration files and displays nothing.

    +

    A typical configuration file looks like this:

    +
    <?php
    +
    +/*
    + * Comment block
    + */
    +
    +return [
    +    'section1' => [
    +        // Comment line
    +        'key' => 'value',
    +    ],
    +    'section2' => [
    +        'array' => ['value0', 'value1', 'value2'],
    +    ],
    +];
    +
    +

    Configuration location#

    +

    The config directory holds key configuration files and can have different config files. +All of them have to end with .config.php and must not include -sample in their name.

    +

    Some examples of common known configuration files: +- local.config.php holds the current node custom configuration. +- addon.config.php is optional and holds the custom configuration for specific addons.

    +

    Addons can define their own default configuration values in addon/[addon]/config/[addon].config.php which is loaded when the addon is activated.

    +

    If needed, an alternative config path can be used by using the FRIENDICA_CONFIG_DIR environment variable (full path required!). +This is useful in case of hardening the system by separating configuration from program binaries.

    +

    Static Configuration location#

    +

    The static directory holds the codebase default configurations files. +They must not be changed by users, because they can get changed from release to release.

    +

    Currently, the following configurations are included: +- defaults.config.php holds the default values for all the configuration keys that can only be set in local.config.php. +- settings.config.php holds the default values for some configuration keys that are set through the admin settings page.

    +

    Migrating from .htconfig.php to config/local.config.php#

    +

    The legacy .htconfig.php configuration file is still supported, but is deprecated and will be removed in a subsequent Friendica release.

    +

    The migration is pretty straightforward: +If you had any addon-specific configuration in your .htconfig.php, just copy config/addon-sample.config.php to config/addon.config.php and move your configuration values. +Afterwards, copy config/local-sample.config.php to config/local.config.php, move the remaining configuration values to it according to the following conversion chart, then rename your .htconfig.php to check your node is working as expected before deleting it.

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    .htconfig.phpconfig/local.config.php
    +$db_host = 'localhost';
    +$db_user = 'mysqlusername';
    +$db_pass = 'mysqlpassword';
    +$db_data = 'mysqldatabasename';
    +$a->config["system"]["db_charset"] = 'utf8mb4';
    +
    +'database' => [
    +    'hostname' => 'localhost',
    +    'username' => 'mysqlusername',
    +    'password' => 'mysqlpassword',
    +    'database' => 'database',
    +    'charset' => 'utf8mb4',
    +],
    +
    +$a->config["section"]["key"] = "value";
    +
    +'section' => [
    +    'key' => 'value',
    +],
    +
    +$a->config["section"]["key"] = array(
    +    "value1",
    +    "value2",
    +    "value3"
    +);
    +
    +'section' => [
    +    'key' => ['value1', 'value2', 'value3'],
    +],
    +
    +$a->config["key"] = "value";
    +
    +'config' => [
    +    'key' => 'value',
    +],
    +
    +$a->config['register_policy'] = REGISTER_CLOSED;
    +
    +'config' => [
    +    'register_policy' => \Friendica\Module\Register::CLOSED,
    +],
    +
    +$a->path = "value";
    +
    +'system' => [
    +    'urlpath' => 'value',
    +],
    +
    +$default_timezone = "value";
    +
    +'system' => [
    +    'default_timezone' => 'value',
    +],
    +
    +$pidfile = "value";
    +
    +'system' => [
    +    'pidfile' => 'value',
    +],
    +
    +$lang = "value";
    +
    +'system' => [
    +    'language' => 'value',
    +],
    +
    + +

    Migrating from config/local.ini.php to config/local.config.php#

    +

    The legacy config/local.ini.php configuration file is still supported, but is deprecated and will be removed in a subsequent Friendica release.

    +

    The migration is pretty straightforward: +If you had any addon-specific configuration in your config/addon.ini.php, just copy config/addon-sample.config.php to config/addon.config.php and move your configuration values. +Afterwards, copy config/local-sample.config.php to config/local.config.php, move the remaining configuration values to it according to the following conversion chart, then rename your config/local.ini.php file to check your node is working as expected before deleting it.

    + + + + + + + + + + + + + + + + + + + + + + + + + +
    config/local.ini.phpconfig/local.config.php
    +[database]
    +hostname = localhost
    +username = mysqlusername
    +password = mysqlpassword
    +database = mysqldatabasename
    +charset = utf8mb4
    +
    +'database' => [
    +    'hostname' => 'localhost',
    +    'username' => 'mysqlusername',
    +    'password' => 'mysqlpassword',
    +    'database' => 'database',
    +    'charset' => 'utf8mb4',
    +],
    +
    +[section]
    +key = value
    +
    +'section' => [
    +    'key' => 'value',
    +],
    +
    +[config]
    +register_policty = REGISTER_CLOSED
    +
    +'config' => [
    +    'register_policy' => \Friendica\Module\Register::CLOSED,
    +],
    +
    +[section]
    +key[] = value1
    +key[] = value2
    +key[] = value3
    +
    +'section' => [
    +    'key' => ['value1', 'value2', 'value3'],
    +],
    +
    + +

    Database Settings#

    +

    The configuration variables database.hostname, database.username, database.password, database.database and database.charset are holding your credentials for the database connection. +If you need to specify a port to access the database, you can do so by appending :portnumber to the database.hostname variable.

    +
    'database' => [
    +    'hostname' => 'your.mysqlhost.com:123456',
    +]
    +
    +

    If all the following environment variables are set, Friendica will use them instead of the previously configured variables for the db:

    +
    MYSQL_HOST
    +MYSQL_PORT
    +MYSQL_USERNAME
    +MYSQL_PASSWORD
    +MYSQL_DATABASE
    +
    +

    Config values that can only be set in config/local.config.php#

    +

    There are some config values that haven't found their way into the administration page. +This has several reasons. +Maybe they are part of a current development that isn't considered stable and will be added later in the administration page when it is considered safe. +Or it triggers something that isn't expected to be of public interest. +Or it is for testing purposes only.

    +

    Attention: Please be warned that you shouldn't use one of these values without the knowledge what it could trigger. +Especially don't do that with undocumented values.

    +

    These configurations keys and their default value are listed in static/defaults.config.php and should be overwritten in config/local.config.php.

    +

    Administrator Options#

    +

    Enabling the admin panel for an account, and thus making the account holder admin of the node, is done by setting the variable

    +
    'config' => [
    +    'admin_email' => 'someone@example.com',
    +]
    +
    +

    Where you have to match the email address used for the account with the one you enter to the config/local.config.php file. +If more than one account should be able to access the admin panel, separate the email addresses with a comma.

    +
    'config' => [
    +    'admin_email' => 'someone@example.com,someoneelse@example.com',
    +]
    +
    +

    If you want to have a more personalized closing line for the notification emails you can set a variable for the admin_name.

    +
    'config' => [
    +    'admin_name' => 'Marvin',
    +]
    +
    + + +
    + +
    +
    + + + +
    + + + +
    +
    +
    +
    + + + + + + + + \ No newline at end of file diff --git a/develop/en/admin/faq/index.html b/develop/en/admin/faq/index.html new file mode 100644 index 0000000..8e217a1 --- /dev/null +++ b/develop/en/admin/faq/index.html @@ -0,0 +1,3392 @@ + + + + + + + + + + + + + + + + + + + + + + FAQ - Friendica documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + +
    + + + + +
    + + +
    + +
    + + + + + + + + + +
    +
    + + + +
    +
    +
    + + + + + + +
    +
    +
    + + + + + + +
    +
    + + + + + + + + + + + + + +

    Frequently Asked Questions (Admin) - FAQ#

    +

    Can I configure multiple domains with the same code instance?#

    +

    No, this function is no longer supported as of Friendica 3.3 onwards.

    +

    Where can I find the source code of friendica, addons and themes?#

    +

    You can find the main repository here. +There you will always find the current stable version of friendica.

    +

    Addons are listed at this page.

    +

    If you are searching for new themes, you can find them at github.com/bkil/friendica-themes

    +

    I've changed my email address now the admin panel is gone?#

    +

    Have a look into your config/local.config.php and fix your email address there.

    +

    Can there be more than one admin for a node?#

    +

    Yes. +You just have to list more than one email address in the config/local.config.php file. +The listed emails need to be separated by a comma.

    +

    The Database structure seems not to be updated. What can I do?#

    +

    Please have a look at the Admin panel under DB updates (/admin/dbsync/) and follow the link to check database structure. +This will start a background process to check if the structure is up to the current definition.

    +

    You can manually execute the structure update from the CLI in the base directory of your Friendica installation by running the following command:

    +
    bin/console dbstructure update
    +
    +

    if there occur any errors, please contact the support forum.

    + + +
    + +
    +
    + + + +
    + + + +
    +
    +
    +
    + + + + + + + + \ No newline at end of file diff --git a/develop/en/admin/improve-performance/index.html b/develop/en/admin/improve-performance/index.html new file mode 100644 index 0000000..a652ad2 --- /dev/null +++ b/develop/en/admin/improve-performance/index.html @@ -0,0 +1,3518 @@ + + + + + + + + + + + + + + + + + + + + + + Improve Performance - Friendica documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + +
    + + + + +
    + + +
    + +
    + + + + + + + + + +
    +
    + + + +
    +
    +
    + + + + + + +
    +
    +
    + + + +
    +
    +
    + + + +
    +
    +
    + + +
    +
    + + + + + + + + + + + + + +

    How to improve the performance of a Friendica site#

    +

    Feel free to ask in the Friendica support forum if you need some clarification about the following instructions or if you need help in any other way.

    +

    System configuration#

    +

    Please go to /admin/site/ on your system and change the following values:

    +
    Set "JPEG image quality" to 50.
    +
    +

    This value reduces the data that is sent from the server to the client. 50 is a value that doesn't influence image quality too much.

    +
    Set "OStatus conversation completion interval" to "never".
    +
    +

    If you have many OStatus contacts then completing of conversations can take some time. Since you will miss several comments in OStatus threads, you maybe should consider the option "At post arrival" instead.

    +
    Enable "Use MySQL full text engine"
    +
    +

    When using MyISAM (default) or InnoDB on MariaDB 10 this speeds up search.

    +

    Addons#

    +

    Active the following addons:

    +
    rendertime
    +
    +

    rendertime#

    +

    This addon doesn't speed up your system. +It helps to analyze your bottlenecks.

    +

    When enabled you see some values at the bottom of every page. +They show your performance problems.

    +
    Performance: Database: 0.244, Network: 0.002, Rendering: 0.044, Parser: 0.001, I/O: 0.021, Other: 0.237, Total: 0.548
    +
    +Database: This is the time for all database queries
    +Network: Time that is needed to fetch content from external sites
    +Rendering: Time for theme rendering
    +Parser: The time that the BBCode parser needed to create the output
    +I/O: Time for local file access
    +Others: Everything else :)
    +Total: The sum of all above values
    +
    +

    Apache Webserver#

    +

    The following Apache modules are recommended:

    +

    Cache-Control#

    +

    This module tells the client to cache the content of static files so that they aren't fetched with every request. +Enable the module mod_expires by typing in a2enmod expires as root. +Please add the following lines to your site configuration in the "directory" context.

    +
    ExpiresActive on ExpiresDefault "access plus 1 week"
    +
    +

    Also see the Apache 2.2 / 2.4 documentation.

    +

    Compress content#

    +

    This module compresses the traffic between the web server and the client. +Enable the module mod_deflate by typing in a2enmod deflate as root.

    +

    Also see the Apache 2.2 / 2.4 documentation.

    +

    PHP#

    +

    FCGI#

    +

    When using Apache think about using FCGI. +In a Debian-based distribution you will need to install the packages named php5-cgi and libapache2-mod-fcgid.

    +

    Please refer to external documentation for a more detailed explanation how to set up a system based upon FCGI.

    +

    Database#

    +

    There are scripts like tuning-primer.sh and mysqltuner.pl that analyze your database server and give hints on values that could be changed.

    +

    Please enable the slow query log. This helps to find performance problems.

    + + +
    + +
    +
    + + + +
    + + + +
    +
    +
    +
    + + + + + + + + \ No newline at end of file diff --git a/develop/en/admin/install-ejabberd/index.html b/develop/en/admin/install-ejabberd/index.html new file mode 100644 index 0000000..b7bbcbc --- /dev/null +++ b/develop/en/admin/install-ejabberd/index.html @@ -0,0 +1,3381 @@ + + + + + + + + + + + + + + + + + + + + + + Install ejabberd - Friendica documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + +
    + + + + +
    + + +
    + +
    + + + + + + + + + +
    +
    + + + +
    +
    +
    + + + + + + +
    +
    +
    + + + +
    +
    +
    + + + +
    +
    +
    + + +
    +
    + + + + + + + + + + + + + +

    Install an ejabberd with synchronized credentials#

    +

    Ejabberd is a chat server that uses XMPP as messaging protocol that you can use with a large amount of clients. +In conjunction with the "xmpp" addon it can be used for a web based chat solution for your users.

    +

    Installation#

    +
      +
    • +

      Change its owner to whichever user is running the server, i.e. ejabberd

      +
      $ chown ejabberd:ejabberd /path/to/friendica/bin/auth_ejabberd.php
      +
      +
    • +
    • +

      Change the access mode, so it is readable only to the user ejabberd and has exec

      +
      $ chmod 700 /path/to/friendica/bin/auth_ejabberd.php
      +
      +
    • +
    • +

      Edit your ejabberd.cfg file, comment out your auth_method and add:

      +
      {auth_method, external}.
      +{extauth_program, "/path/to/friendica/bin/auth_ejabberd.php"}.
      +
      +
    • +
    • +

      Disable the module "mod_register" and disable the registration:

      +
      {access, register, [{deny, all}]}.
      +
      +
    • +
    • +

      Enable BOSH:

      +
    • +
    • Enable the module "mod_http_bind"
    • +
    • +

      Edit this line:

      +
      {5280, ejabberd_http,    [captcha, http_poll, http_bind]}
      +
      +
    • +
    • +

      In your apache configuration for your site add this line:

      +
      ProxyPass /http-bind http://127.0.0.1:5280/http-bind retry=0
      +
      +
    • +
    • +

      Restart your ejabberd service, you should be able to log in with your friendica credentials

      +
    • +
    +

    Other hints#

    +
      +
    • if a user has a space or a @ in the nickname, the user has to replace these characters:
    • +
    • " " (space) is replaced with "%20"
    • +
    • "@" is replaced with "(a)"
    • +
    + + +
    + +
    +
    + + + +
    + + + +
    +
    +
    +
    + + + + + + + + \ No newline at end of file diff --git a/develop/en/admin/install/index.html b/develop/en/admin/install/index.html new file mode 100644 index 0000000..179684c --- /dev/null +++ b/develop/en/admin/install/index.html @@ -0,0 +1,4227 @@ + + + + + + + + + + + + + + + + + + + + + + Installation - Friendica documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + +
    + + + + +
    + + +
    + +
    + + + + + + + + + +
    +
    + + + +
    +
    +
    + + + + + + +
    +
    +
    + + + +
    +
    +
    + + + +
    +
    +
    + + +
    +
    + + + + + + + + + + + + + +

    Friendica Installation#

    +

    We've tried very hard to ensure that Friendica will run on commodity hosting platforms - such as those used to host WordPress blogs and Drupal websites. +We offer a manual and an automatic installation. +But be aware that Friendica is more than a simple web application.

    +

    It is a complex communications system which more closely resembles an email server than a web server. +For reliability and performance, messages are delivered in the background and are queued for later delivery when sites are down. +This kind of functionality requires a bit more of the host system than the typical blog.

    +

    Not every PHP/MySQL hosting provider will be able to support Friendica. +Many will.

    +

    But please review the requirements and confirm these with your hosting provider prior to installation.

    +

    Support#

    +

    If you encounter installation issues, please let us know via the helper or the developer forum or file an issue.

    +

    Please be as clear as you can about your operating environment and provide as much detail as possible about any error messages you may see, so that we can prevent it from happening in the future. +Due to the large variety of operating systems and PHP platforms in existence we may have only limited ability to debug your PHP installation or acquire any missing modules - but we will do our best to solve any general code issues.

    +

    Prerequisites#

    +
      +
    • Choose a domain name or subdomain name for your server. Put some thought into this. While changing it after installation is supported, things still might break.
    • +
    • Setup HTTPS on your domain.
    • +
    +

    Requirements#

    +
      +
    • Apache with mod-rewrite enabled and "Options All" so you can use a local .htaccess file
    • +
    • PHP 7.3+ (PHP8 is not fully supported yet)
    • +
    • PHP command line access with register_argc_argv set to true in the php.ini file
    • +
    • Curl, GD, GMP, PDO, mbstrings, MySQLi, hash, xml, zip and OpenSSL extensions
    • +
    • The POSIX module of PHP needs to be activated (e.g. RHEL, CentOS have disabled it)
    • +
    • Some form of email server or email gateway such that PHP mail() works. + If you cannot set up your own email server, you can use the phpmailer addon and use a remote SMTP server.
    • +
    • MySQL 5.6+ or an equivalent alternative for MySQL (MariaDB, Percona Server etc.)
    • +
    • ability to schedule jobs with cron (Linux/Mac) or Scheduled Tasks (Windows)
    • +
    • installation into a top-level domain or subdomain (without a directory/path component in the URL) is RECOMMENDED. Directory paths will not be as convenient to use and have not been thoroughly tested. This is REQUIRED if you wish to communicate with the Diaspora network.
    • +
    +

    If your hosting provider doesn't allow Unix shell access, you might have trouble getting everything to work.

    +

    For alternative server configurations (such as Nginx server and MariaDB database engine), refer to the Friendica wiki.

    +

    Optional#

    +
      +
    • PHP ImageMagick extension (php-imagick) for animated GIF support.
    • +
    +

    Installation procedure#

    +

    Alternative Installation Methods#

    +

    This guide will walk you through the manual installation process of Friendica. +If this is nothing for you, you might be interested in

    + +

    Get Friendica#

    +

    Download the full archive of the stable release of Friendica core and the addons from the project homepage. +Make sure that the version of the Friendica archive and the addons match. +Unpack the Friendica files into the root of your web server document area.

    +

    If you copy the directory tree to your webserver, make sure that you also copy .htaccess-dist - as "dot" files are often hidden and aren't normally copied.

    +

    OR

    +

    Clone the friendica/friendica GitHub repository and import dependencies. +This makes the software much easier to update.

    +

    The Linux commands to clone the repository into a directory "mywebsite" would be

    +
    git clone https://github.com/friendica/friendica.git -b stable mywebsite
    +cd mywebsite
    +bin/composer.phar install --no-dev
    +
    +

    Make sure the folder view/smarty3 exists and is writable by the webserver user, in this case www-data

    +
    mkdir -p view/smarty3
    +chown www-data:www-data view/smarty3
    +chmod 775 view/smarty3
    +
    +

    Get the addons by going into your website folder.

    +
    cd mywebsite
    +
    +

    Clone the addon repository (separately):

    +
    git clone https://github.com/friendica/friendica-addons.git -b stable addon
    +
    +

    If you want to use the development version of Friendica you can switch to the develop branch in the repository by running

    +
    git checkout develop
    +bin/composer.phar install
    +cd addon
    +git checkout develop
    +
    +

    Be aware that the develop branch is unstable and may break your Friendica node at any time. +You should have a recent backup before updating. +If you encounter a bug, please let us know.

    +

    Create a database#

    +

    Create an empty database and note the access details (hostname, username, password, database name). +Generate a strong password, then enter mysql with:

    +
    mysql
    +
    +

    Then use the following script using the password you just generated:

    +
    CREATE DATABASE friendicadb;
    +CREATE USER 'friendica'@'localhost' IDENTIFIED BY '<<your mysql password here>>';
    +GRANT ALL ON friendicadb.* TO 'friendica'@'localhost';
    +FLUSH PRIVILEGES;
    +EXIT;
    +
    +

    Friendica needs the permission to create and delete fields and tables in its own database.

    +

    Please check the troubleshooting section if running on MySQL 5.7.17 or newer.

    +

    Option A: Run the installer#

    +

    Before you point your web browser to the new site you need to copy .htaccess-dist to .htaccess for Apache installs. +Follow the instructions. +Please note any error messages and correct these before continuing.

    +

    If you need to specify a port for the connection to the database, you can do so in the host name setting for the database.

    +

    If the manual installation fails for any reason, check the following:

    +
      +
    • Does config/local.config.php exist? If not, edit config/local-sample.config.php and change the system settings.
    • +
    • Rename to config/local.config.php.
    • +
    • Is the database populated? If not, import the contents of database.sql with phpmyadmin or the mysql command line.
    • +
    +

    At this point visit your website again, and register your personal account. +Registration errors should all be recoverable automatically. +If you get any critical failure at this point, it generally indicates the database was not installed correctly. +You might wish to move/rename config/local.config.php to another name and empty (called 'dropping') the database tables, so that you can start fresh.

    +

    Option B: Run the automatic installation script#

    +

    You have the following options to automatically install Friendica: +- creating a prepared config file (f.e. prepared.config.php) +- using environment variables (f.e. MYSQL_HOST) +- using options (f.e. --dbhost <host>)

    +

    You can combine environment variables and options, but be aware that options are prioritized over environment variables.

    +

    For more information during the installation, you can use this command line option

    +
    bin/console autoinstall -v
    +
    +

    If you wish to include all optional checks, use -a like this statement:

    +
    bin/console autoinstall -a
    +
    +

    If the automatic installation fails for any reason, check the following:

    +
      +
    • Does config/local.config.php already exist? If yes, the automatic installation won't start
    • +
    • Are the options in the config/local.config.php correct? If not, edit them directly.
    • +
    • Is the empty MySQL-database created? If not, create it.
    • +
    +

    B.1: Config file#

    +

    You can use a prepared config file like "local-sample.config.php".

    +

    Navigate to the main Friendica directory and execute the following command:

    +
    bin/console autoinstall -f <prepared.config.php>
    +
    +

    B.2: Environment variables#

    +

    There are two types of environment variables. +- those you can use in normal mode too (Currently just database credentials) +- those you can only use during installation (because Friendica will normally ignore it)

    +

    You can use the options during installation too and skip some environment variables.

    +

    Database credentials

    +

    if you don't use the option --savedb during installation, the DB credentials will not be saved in the config/local.config.php.

    +
      +
    • MYSQL_HOST The host of the mysql/mariadb database
    • +
    • MYSQL_PORT The port of the mysql/mariadb database
    • +
    • MYSQL_USERNAME The username of the mysql database login (used for mysql)
    • +
    • MYSQL_USER The username of the mysql database login (used for mariadb)
    • +
    • MYSQL_PASSWORD The password of the mysql/mariadb database login
    • +
    • MYSQL_DATABASE The name of the mysql/mariadb database
    • +
    +

    Friendica settings

    +

    These variables won't be used at normal Friendica runtime. +Instead, they get saved into config/local.config.php.

    +
      +
    • FRIENDICA_URL_PATH The URL path of Friendica (f.e. '/friendica')
    • +
    • FRIENDICA_PHP_PATH The path of the PHP binary
    • +
    • FRIENDICA_ADMIN_MAIL The admin email address of Friendica (this email will be used for admin access)
    • +
    • FRIENDICA_TZ The timezone of Friendica
    • +
    • FRIENDICA_LANG The language of Friendica
    • +
    +

    Navigate to the main Friendica directory and execute the following command:

    +
    bin/console autoinstall [--savedb]
    +
    +

    B.3: Execution options#

    +

    All options will be saved in the config/local.config.php and are overruling the associated environment variables.

    +
      +
    • -H|--dbhost <host> The host of the mysql/mariadb database (env MYSQL_HOST)
    • +
    • -p|--dbport <port> The port of the mysql/mariadb database (env MYSQL_PORT)
    • +
    • -U|--dbuser <username> The username of the mysql/mariadb database login (env MYSQL_USER or MYSQL_USERNAME)
    • +
    • -P|--dbpass <password> The password of the mysql/mariadb database login (env MYSQL_PASSWORD)
    • +
    • -d|--dbdata <database> The name of the mysql/mariadb database (env MYSQL_DATABASE)
    • +
    • -u|--urlpath <url_path> The URL path of Friendica - f.e. '/friendica' (env FRIENDICA_URL_PATH)
    • +
    • -b|--phppath <php_path> The path of the PHP binary (env FRIENDICA_PHP_PATH)
    • +
    • -A|--admin <mail> The admin email address of Friendica (env FRIENDICA_ADMIN_MAIL)
    • +
    • -T|--tz <timezone> The timezone of Friendica (env FRIENDICA_TZ)
    • +
    • -L|--lang <language> The language of Friendica (env FRIENDICA_LANG)
    • +
    +

    Navigate to the main Friendica directory and execute the following command:

    +
    bin/console autoinstall [options]
    +
    +

    Prepare .htaccess file#

    +

    Copy .htaccess-dist to .htaccess (be careful under Windows) to have working mod-rewrite again. If you have installed Friendica into a subdirectory, like /friendica/ set this path in RewriteBase accordingly.

    +

    Example:

    +
    cp .htacces-dist .htaccess
    +
    +

    Note: Do not rename the .htaccess-dist file as it is tracked by GIT and renaming will cause a dirty working directory.

    +

    Verify the "host-meta" page is working#

    +

    Friendica should respond automatically to important addresses under the /.well-known/ rewrite path. +One critical URL would look like, for example: https://example.com/.well-known/host-meta
    +It must be visible to the public and must respond with an XML file that is automatically customized to your site.

    +

    If that URL is not working, it is possible that some other software is using the /.well-known/ path. +Other symptoms may include an error message in the Admin settings that says "host-meta is not reachable on your system. +This is a severe configuration issue that prevents server to server communication." +Another common error related to host-meta is the "Invalid profile URL."

    +

    Check for a .well-known directory that did not come with Friendica. +The preferred configuration is to remove the directory, however this is not always possible. +If there is any /.well-known/.htaccess file, it could interfere with this Friendica core requirement. +You should remove any RewriteRules from that file, or remove that whole file if appropriate. +It may be necessary to chmod the /.well-known/.htaccess file if you were not given write permissions by default.

    +

    Register the admin account#

    +

    At this point visit your website again, and register your personal account with the same email as in the config.admin_email config value. +Registration errors should all be recoverable automatically.

    +

    If you get any critical failure at this point, it generally indicates the database was not installed correctly. +You might wish to delete/rename config/local.config.php to another name and drop all the database tables so that you can start fresh.

    +

    Post Install Configuration#

    +

    (REQUIRED) Background tasks#

    +

    Set up a cron job or scheduled task to run the worker once every 5-10 minutes in order to perform background processing. +Example:

    +
    cd /base/directory; /path/to/php bin/worker.php
    +
    +

    Change "/base/directory", and "/path/to/php" as appropriate for your situation.

    +

    cron job for worker#

    +

    If you are using a Linux server, run "crontab -e" and add a line like the +one shown, substituting for your unique paths and settings:

    +
    */10 * * * * cd /home/myname/mywebsite; /usr/bin/php bin/worker.php
    +
    +

    You can generally find the location of PHP by executing "which php". +If you run into trouble with this section please contact your hosting provider for assistance. +Friendica will not work correctly if you cannot perform this step.

    +

    If it is not possible to set up a cron job then please activate the "frontend worker" in the administration interface.

    +

    Once you have installed Friendica and created an admin account as part of the process, you can access the admin panel of your installation and do most of the server wide configuration from there.

    +

    worker alternative: daemon#

    +

    Otherwise, you’ll need to use the command line on your remote server and start the Friendica daemon (background task) using the following command:

    +
    cd /path/to/friendica; php bin/daemon.php start
    +
    +

    Once started, you can check the daemon status using the following command:

    +
    cd /path/to/friendica; php bin/daemon.php status
    +
    +

    After a server restart or any other failure, the daemon needs to be restarted. +This could be achieved by a cronjob.

    + +

    At this point it is recommended that you set up logging and logrotate. +To do so please visit Settings and search the 'Logs' section for more information.

    + +

    Bad things will happen. +Let there be a hardware failure, a corrupted database or whatever you can think of. +So once the installation of your Friendica node is done, you should make yourself a backup plan.

    +

    The most important file is the config/local.config.php file. +As it stores all your data, you should also have a recent dump of your Friendica database at hand, should you have to recover your node.

    +

    (OPTIONAL) Reverse-proxying and HTTPS#

    +

    Friendica looks for some well-known HTTP headers indicating a reverse-proxy +terminating an HTTPS connection. +While the standard from RFC 7239 specifies the use of the Forwarded header.

    +
    Forwarded: for=192.0.2.1; proto=https; by=192.0.2.2
    +
    +

    Friendica also supports a number on non-standard headers in common use.

    +
    X-Forwarded-Proto: https
    +
    +Front-End-Https: on
    +
    +X-Forwarded-Ssl: on
    +
    +

    It is however preferable to use the standard approach if configuring a new server.

    +

    Troubleshooting#

    +

    "System is currently unavailable. Please try again later"#

    +

    Check your database settings. +It usually means your database could not be opened or accessed. +If the database resides on the same machine, check that the database server name is "localhost".

    +

    500 Internal Error#

    +

    This could be the result of one of our Apache directives not being supported by your version of Apache. Examine your apache server logs. +You might remove the line "Options -Indexes" from the .htaccess file if you are using a Windows server as this has been known to cause problems. +Also check your file permissions. Your website and all contents must generally be world-readable.

    +

    It is likely that your web server reported the source of the problem in its error log files. +Please review these system error logs to determine what caused the issue. +Often this will need to be resolved with your hosting provider or (if self-hosted) your web server configuration.

    +

    400 and 4xx "File not found" errors#

    +

    First check your file permissions. +Your website and all contents must generally be world-readable.

    +

    Ensure that mod-rewrite is installed and working, and that your .htaccess file +is being used. To verify the latter, create a file test.out containing the +word "test" in the top directory of Friendica, make it world readable and point +your web browser to

    +
    http://yoursitenamehere.com/test.out
    +
    +

    This file should be blocked. You should get a permission denied message.

    +

    If you see the word "test" your Apache configuration is not allowing your +.htaccess file to be used (there are rules in this file to block access to any +file with .out at the end, as these are typically used for system logs).

    +

    Make certain the .htaccess file exists and is readable by everybody, then look +for the existence of "AllowOverride None" in the Apache server configuration for your site. +This will need to be changed to "AllowOverride All".

    +

    If you do not see the word "test", your .htaccess is working, but it is likely +that mod-rewrite is not installed in your web server or is not working.

    +

    On most Linux flavors:

    +
    % a2enmod rewrite
    +% /etc/init.d/apache2 restart
    +
    +

    Consult your hosting provider, experts on your particular Linux distribution or +(if Windows) the provider of your Apache server software if you need to change +either of these and can not figure out how. There is a lot of help available on +the web. Search "mod-rewrite" along with the name of your operating system +distribution or Apache package (if using Windows).

    +

    Unable to write the file config/local.config.php due to permissions issues#

    +

    Create an empty config/local.config.phpfile and apply world-write permission.

    +

    On Linux:

    +
    % touch config/local.config.php
    +% chmod 664 config/local.config.php
    +
    +

    Retry the installation. As soon as the database has been created,

    +

    * this is important ***

    +
    % chmod 644 config/local.config.php
    +
    +

    Suhosin issues#

    +

    Some configurations with "suhosin" security are configured without an ability to +run external processes. Friendica requires this ability. Following are some notes +provided by one of our members.

    +
    +

    On my server I use the php protection system Suhosin [http://www.hardened-php.net/suhosin/]. +One of the things it does is to block certain functions like proc_open, as +configured in /etc/php5/conf.d/suhosin.ini:

    +
    suhosin.executor.func.blacklist = proc_open, ...
    +
    +

    For those sites like Friendica that really need these functions they can be +enabled, e.g. in /etc/apache2/sites-available/friendica:

    +

    + php_admin_value suhosin.executor.func.blacklist none + php_admin_value suhosin.executor.eval.blacklist none +

    +

    This enables every function for Friendica if accessed via browser, but not for +the cronjob that is called via php command line. I attempted to enable it for +cron by using something like:

    +

    /10 * * * cd /var/www/friendica/friendica/ && sudo -u www-data /usr/bin/php \ + -d suhosin.executor.func.blacklist=none \ + -d suhosin.executor.eval.blacklist=none -f bin/worker.php

    +

    This worked well for simple test cases, but the friendica-cron still failed +with a fatal error:

    +

    suhosin[22962]: ALERT - function within blacklist called: proc_open() + (attacker 'REMOTE_ADDR not set', file '/var/www/friendica/friendica/boot.php', + line 1341)

    +

    After a while I noticed, that bin/worker.php calls further PHP scripts via proc_open. +These scripts themselves also use proc_open and fail, because they are NOT +called with -d suhosin.executor.func.blacklist=none.

    +

    So the simple solution is to put the correct parameters into config/local.config.php:

    +

    'config' => [ + //Location of PHP command line processor + 'php_path' => '/usr/bin/php -d suhosin.executor.func.blacklist=none \ + -d suhosin.executor.eval.blacklist=none', + ],

    +

    This is obvious as soon as you notice that the friendica-cron uses proc_open +to execute PHP scripts that also use proc_open, but it took me quite some time to find that out. +I hope this saves some time for other people using suhosin with function blocklists.

    +
    +

    Unable to create all mysql tables on MySQL 5.7.17 or newer#

    +

    If the setup fails to create all the database tables and/or manual creation from +the command line fails, with this error:

    +
    ERROR 1067 (42000) at line XX: Invalid default value for 'created'
    +
    +

    You need to adjust your my.cnf and add the following setting under the [mysqld] +section:

    +
    sql_mode = '';
    +
    +

    After that, restart mysql and try again.

    +

    Your worker never or rarely runs#

    +

    Friendica is coded to always play nice. It checks whether the host machine is idle enough and if it seems to be overloaded, it intermittently refuses to process the worker queue.

    +

    Such checks originate from the days of single-user single-core machines and involves thresholds that you should adjust based on the number of exclusive CPU cores you have. See this issue for more information:

    +
      +
    • https://github.com/friendica/friendica/issues/10131
    • +
    +

    If you want to be neighborly and are using a shared web hosting PaaS provider, especially within the free tier, you need to set maxloadavg to say twice the maximum value of /proc/loadavg during peak hours.

    +

    If you have the whole (virtual) machine for yourself such as in case of an IaaS VPS, you can set it to orders of magnitude higher than its commonly observed value, such as 1000.

    +

    You should instead enact limits in your web server configuration based on the number of entry processes to cap the concurrent memory usage of your PHP processes. +See RLimitMEM, RLimitCPU, RLimitNPROC, StartServers, ServerLimit, MaxRequestsPerChild, pm.max_children, pm.start_servers and related options in your server.

    +

    Error uploading even small image files#

    +

    You tried to upload an image up to 100kB, and it failed.

    +

    You may not have the ownership or file mode set correctly if you are using the file system storage backend.

    +

    Change the backend to database. If this solves it, that is what needs to be fixed.

    +

    Error uploading large files#

    +

    You may find 413 Request Entity Too Large or 500 Internal Error in the network inspector of the browser if the file is too large, for example if it is a video.

    +

    First try to upload a very small file, up to 100kB. If that succeeds, you will need to increase limits at multiple places, including on any web proxy that you are using.

    +

    In your PHP ini:

    +
      +
    • upload_max_filesize: defaults to 2MB
    • +
    • post_max_size: defaults to 8MB, must be greater than upload_max_filesize
    • +
    • memory_limit: defaults to 128MB, must be greater than post_max_size
    • +
    +

    You should verify whether you changed them in the right file by checking the web interface at the end of the overview on the Admin panel.

    +

    For Apache2:

    +
      +
    • LimitRequestBody: defaults to unlimited
    • +
    • SSLRenegBufferSize: defaults to 128kB, only if your site uses TLS and perhaps only when using SSLVerifyClient or SSLVerifyDepth
    • +
    +

    For nginx:

    +
      +
    • client_max_body_size: defaults to 1MB
    • +
    +

    If you are using the database backend for storage, increase this in your SQL configuration:

    +
      +
    • max_allowed_packet: defaults to 32MB
    • +
    +

    If you use the ModSecurity WAF:

    +
      +
    • SecRequestBodyLimit: defaults to 12MB
    • +
    • SecRequestBodyNoFilesLimit: defaults to 128kB, should not apply to Friendica
    • +
    + + +
    + +
    +
    + + + +
    + + + +
    +
    +
    +
    + + + + + + + + \ No newline at end of file diff --git a/develop/en/admin/installing-connectors/index.html b/develop/en/admin/installing-connectors/index.html new file mode 100644 index 0000000..561fddd --- /dev/null +++ b/develop/en/admin/installing-connectors/index.html @@ -0,0 +1,3482 @@ + + + + + + + + + + + + + + + + + + + + + + Connectors - Friendica documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + +
    + + + + +
    + + +
    + +
    + + + + + + + + + +
    +
    + + + +
    +
    +
    + + + + + + +
    +
    +
    + + + +
    +
    +
    + + + +
    +
    +
    + + +
    +
    + + + + + + + + + + + + + +

    Installing Connectors (Twitter/GNU Social)#

    +

    Friendica uses addons to provide connectivity to some networks, such as Twitter.

    +

    There is also an addon to post through to an existing account on a GNU Social service. +You only need this to post to an already existing GNU Social account, but not to communicate with GNU Social members in general.

    +

    All three addons require an account on the target network. +In addition, you (or typically the server administrator) will need to obtain an API key to provide authenticated access to your Friendica server.

    +

    Site Configuration#

    +

    Addons must be installed by the site administrator before they can be used. +This is accomplished through the site administration panel.

    +

    Each of the connectors also requires an "API key" from the service you wish to connect with. +Some addons allow you to enter this information in the site administration pages, while others may require you to edit your configuration file (config/local.config.php). +The ways to obtain these keys vary between the services, but they all require an existing account on the target service. +Once installed, these API keys can usually be shared by all site members.

    +

    The details of configuring each service follow (much of this information comes directly from the addon source files):

    +

    Twitter Addon for Friendica#

    +
      +
    • Author: Tobias Diekershoff
    • +
    • tobias.diekershoff@gmx.net
    • +
    • License: 3-clause BSD license
    • +
    +

    Configuration#

    +

    To use this addon you need a OAuth Consumer key pair (key & secret). +You can get it from Twitter.

    +

    Register your Friendica site as "Client" application with "Read & Write" access. +We do not need "Twitter as login". +When you've registered the app you get a key pair with an OAuth Consumer key and a secret key for your application/site. +Add this key pair to your config/local.config.php:

    +
    [twitter]
    +consumerkey = your consumer_key here
    +consumersecret = your consumer_secret here
    +
    +

    After this, your users can configure their Twitter account settings from "Settings -> Connector Settings".

    +

    More documentation#

    +

    Find the author's documentation here: http://diekershoff.homeunix.net/redmine/wiki/friendikaplugin/Twitter_Plugin

    +

    GNU Social Addon for Friendica#

    +
      +
    • Author: Tobias Diekershoff
    • +
    • tobias.diekershoff@gmx.net
    • +
    • License: 3-clause BSD license
    • +
    +

    Configuration#

    +

    When the addon is activated the user has to acquire the following in order to connect to the GNU Social account of choice.

    +
      +
    • The base URL for the GNU Social API, for quitter.se this is https://quitter.se/api/
    • +
    • OAuth Consumer key & secret
    • +
    +

    To get the OAuth Consumer key pair the user has to

    +

    1 ask her Friendica admin if a pair already exists or +2 has to register the Friendica server as a client application on the GNU Social server.

    +

    This can be done from the account settings under "Settings -> Connections -> Register an OAuth client application -> Register a new application" on the GNU Social server.

    +

    During the registration of the OAuth client remember the following:

    +
      +
    • Application names must be unique on the GNU Social site, so we recommend a Name of 'friendica-nnnn', replace 'nnnn' with a random number or your website name.
    • +
    • there is no callback url
    • +
    • register a desktop client
    • +
    • with read & write access
    • +
    • the Source URL should be the URL of your Friendica server
    • +
    +

    After the required credentials for the application are stored in the configuration you have to actually connect your Friendica account with GNU Social. +This is done from the Settings -> Connector Settings page. +Follow the Sign in with GNU Social button, allow access and then copy the security code into the box provided. +Friendica will then try to acquire the final OAuth credentials from the API.

    +

    If successful, the addon settings will allow you to select to post your public messages to your GNU Social account (have a look behind the little lock symbol beneath the status "editor" on your Home or Network pages).

    + + +
    + +
    +
    + + + +
    + + + +
    +
    +
    +
    + + + + + + + + \ No newline at end of file diff --git a/develop/en/admin/migrate/index.html b/develop/en/admin/migrate/index.html new file mode 100644 index 0000000..1c78d50 --- /dev/null +++ b/develop/en/admin/migrate/index.html @@ -0,0 +1,3584 @@ + + + + + + + + + + + + + + + + + + + + + + Migrate - Friendica documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + +
    + + + + +
    + + +
    + +
    + + + + + + + + + +
    +
    + + + +
    +
    +
    + + + + + + +
    +
    +
    + + + +
    +
    +
    + + + +
    +
    +
    + + +
    +
    + + + + + + + + + + + + + +

    Migrating to a new server installation#

    +

    Preparation#

    +

    New server#

    +

    Set up your new server as described here; follow the installation procedure until you have created a database.

    +

    Heads up to users#

    +

    Inform your users of an upcoming interruption to your service. +To ensure data consistency, your server needs to be offline during some steps of the migration processes.

    +

    You may also find these addons useful for communicating with your users prior to the migration process: +* blackout +* notifyall

    +

    Storage#

    +

    Check your storage backend with bin/console storage list in the root folder. +The output should look like this: +

    Sel | Name
    +-----------------------
    +     | Filesystem
    + *   | Database
    +

    +

    If you are not using Database run the following commands: +1. bin/console storage set Database to activate the database backend. +2. bin/console storage move to initiate moving the stored image files.

    +

    This process may take a long time depending on the size of your storage and your server's capacity. +Prior to initiating this process, you may want to check the number of files in the storage with the following command: tree -if -I index.html /path/to/storage/.

    +

    Cleaning up#

    +

    Before transferring your database, you may want to clean it up; ensure the expiration of database items is set to a reasonable value and activated via the administrator panel. +Admin > Site > Performance > Enable "Clean up database" +After adjusting these settings, the database cleaning up processes will be initiated according to your configured daily cron job.

    +

    To review the size of your database, log into MySQL with mysql -p run the following query: +

    SELECT table_schema AS "Database", SUM(data_length + index_length) / 1024 / 1024 / 1024 AS "Size (GB)" FROM information_schema.TABLES GROUP BY table_schema;
    +

    +

    You should see an output like this: +

    +--------------------+----------------+
    +| Database           | Size (GB)      |
    ++--------------------+----------------+
    +| friendica_db       | 8.054092407227 |
    +| [..........]       | [...........]  |
    ++--------------------+----------------+
    +

    +

    Finally, you may also want to optimise your database with the following command: mysqloptimize -p friendica-db

    +

    Going offline#

    +

    Stop background tasks and put your server in maintenance mode. +1. If you had set up a worker cron job like this */10 * * * * cd /var/www/friendica; /usr/bin/php bin/worker.php run crontab -e and comment out this line. Alternatively if you deploy a worker daemon, disable this instead. +2. Put your server into maintenance mode: bin/console maintenance 1 "We are currently upgrading our system and will be back soon."

    +

    Dumping DB#

    +

    Export your database: mysqldump -p friendica_db > friendica_db-$(date +%Y%m%d).sql and possibly compress it.

    +

    Transferring to new server#

    +

    Transfer your database and a copy of your configuration file config/local.config.php.copy to your new server installation.

    +

    Restoring your DB#

    +

    Import your database on your new server: mysql -p friendica_db < your-friendica_db-file.sql

    +

    Completing migration#

    +

    Configuration file#

    +

    Copy your old server's configuration file to config/local.config.php. +Ensure the newly created database credentials are identical to the setting in the configuration file; otherwise update them accordingly.

    +

    Cron job for worker#

    +

    Set up the required daily cron job. +Run crontab -e and add the following line according to your system specification +

    */10 * * * * cd /var/www/friendica; /usr/bin/php bin/worker.php
    +

    +

    DNS settings#

    +

    Adjust your DNS records by pointing them to your new server.

    +

    Troubleshooting#

    +

    If you are unable to log in to your newly migrated Friendica installation, check your web server's error and access logs and mysql logs for obvious issues.

    +

    If still unable to resolve the problem, it's likely an issue with your installation. +In this case, you may try to an entirely new Friendica installation on your new server, but use a different FQDN and DNS name. +Once you have this up and running, take it offline and purge the database and configuration file and try migrating to this installation.

    + + +
    + +
    +
    + + + +
    + + + +
    +
    +
    +
    + + + + + + + + \ No newline at end of file diff --git a/develop/en/admin/settings/index.html b/develop/en/admin/settings/index.html new file mode 100644 index 0000000..df603f0 --- /dev/null +++ b/develop/en/admin/settings/index.html @@ -0,0 +1,4372 @@ + + + + + + + + + + + + + + + + + + + + + + Settings - Friendica documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + +
    + + + + +
    + + +
    + +
    + + + + + + + + + +
    +
    + + + +
    +
    +
    + + + + + + +
    +
    +
    + + + +
    +
    +
    + + + +
    +
    +
    + + +
    +
    + + + + + + + + + + + + + +

    Settings#

    +

    If you are the admin of a Friendica node, you have access to the Admin Panel where you can configure your Friendica node.

    +

    Overview#

    +

    In the main page of the admin panel you will see an information summary about your node.

    +

    Queues#

    +

    The three numbers shown are respectively: +- The retry queue: These outgoing messages couldn't be received by the remote host, and will be resent at longer intervals before being dropped entirely after 30 days. +- The deferred queue: These internal tasks failed and will be retried at most 14 times. +- The task queue: These internal tasks are queued for execution during the next background worker run.

    +

    Additional information#

    +

    Then you get an overview of the accounts on your node, which can be moderated in the "Users" section of the panel. +As well as an overview of the currently active addons. +The list is linked, so you can have quick access to the Addon settings. +And finally you are informed about the version of Friendica you have installed. +If you contact the developers with a bug or problem, please also mention the version of your node.

    +

    The admin panel is separated into subsections accessible from the sidebar of the panel.

    +

    Site#

    +

    This section of the admin panel contains the main configuration of your Friendica node. +It is separated into several subsection beginning with the basic settings at the top, advancing towards the bottom of the page.

    +

    Most configuration options have a help text in the admin panel. +Therefore, this document does not yet cover all the options

    +

    Basic Settings#

    + +

    Set the content for the site banner. +The default logo is the Friendica logo and name. +You may wish to provide HTML/CSS to style and/or position this content, as it may not be themed by default.

    +

    Language#

    +

    This option will set the default language for the node. +It is used as fall back setting should Friendica fail to recognize the visitors preferences and can be overwritten by user settings.

    +

    The Friendica community offers some translations. +Some more complete than others. +See this help page for more information about the translation process.

    +

    System Theme#

    +

    Choose a theme to be the default system theme. +This can be over-ridden by user profiles. +Default theme is vier at the moment.

    +

    You may also want to set a special theme for mobile interfaces. +Which may or may not be necessary depending on the mobile friendliness of the desktop theme you have chosen. +The vier theme for instance is mobile friendly.

    +

    Registration#

    +

    Register policy#

    +

    With this dropdown selector you can set the nodes' registration policy. +You can choose between the following modes:

    +
      +
    • open: Everybody can register a new account and start using it right away.
    • +
    • requires approval: Everybody can register a new account, but the admin has to approve it before it can be used.
    • +
    • closed: No new registrations are possible.
    • +
    +
    Invitation based registry#
    +

    Additionally, to the setting in the admin panel, you can decide if registrations are only possible using an invitation code or not. +To enable invitation based registration, you have to set the invitation_only setting to true in the system section of the config/local.config.php file. +If you want to use this method, the registration policy has to be set to either open or requires approval.

    +

    Check Full Names#

    +

    You may find a lot of spammers trying to register on your site. +During testing we discovered that since these registrations were automatic, the "Full Name" field was often set to just an account name with no space between first and last name. +If you would like to support people with only one name as their full name, you may change this setting to true. +Default is false.

    +

    OpenID#

    +

    By default, OpenID may be used for both registration and logins. +If you do not wish to make OpenID facilities available on your system (at all), set 'no_openid' to true. +Default is false.

    +

    Multiple Registrations#

    +

    The ability to create "Pages" requires a person to register more than once. +Your site configuration can block registration (or require approval to register). +By default, logged-in users can register additional accounts for use as pages. +These will still require approval if the registration policy is set to require approval +You may prohibit logged-in users from creating additional accounts by setting block multiple registrations to true. +Default is false.

    +

    File upload#

    +

    File storage backend#

    +

    Set the backend used by Friendica to store uploaded file data. +Two storage backends are available with Friendica:

    +
      +
    • Database : Data is stored in a dedicated table in database (storage)
    • +
    • Filesystem : Data is stored as file on the filesystem.
    • +
    +

    More storage backends can be available from third-party addons. +If you use those, please refer to the documentation of those addons for further information.

    +

    Default value is 'Database (legacy)': it's the legacy way used to store data directly in database.

    +

    Existing data can be moved to the current active backend using the 'storage move' console command

    +

    If selected backend has configurable options, new fields are shown here.

    +
    Filesystem: Storage base path#
    +

    The base path where Filesystem storage backend saves data.

    +

    For maximum security, this path should be outside the folder tree served by the web server: this way files can't be downloaded bypassing the privacy checks.

    +

    Default value is storage, that is the storage folder in Friendica code root folder.

    +

    Maximum Image Size#

    +

    Maximum size in bytes of uploaded images. +The default is set to 0, which means no limits.

    +

    Policies#

    +

    Global Directory#

    +

    This configures the URL to update the global directory, and is supplied in the default configuration. +The undocumented part is that if this is not set, the global directory is completely unavailable to the application. +This allows a private community to be completely isolated from the global network.

    +

    Force Publish#

    +

    By default, each user can choose on their Settings page whether to have their profile published in the site directory. +This setting forces all profiles on this site to be listed in the site directory and there is no option provided to the user to change it. +Default is false.

    +

    Block Public#

    +

    Set to true to block public access to all otherwise public personal pages on this site unless you are currently logged in. +This blocks the viewing of profiles, friends, photos, the site directory and search pages to unauthorised persons. +A side effect is that entries from this site will not appear in the global directory. +We recommend specifically disabling that also (setting is described elsewhere on this page). +Note: this is specifically for sites that desire to be "standalone" and do not wish to be connected to any other Friendica sites. +Unauthorised persons will also not be able to request friendship with site members. +Default is false. +Available in version 2.2 or greater.

    +

    Community pages for Visitors#

    +

    The community pages show all public postings, separated by their origin being local or the entire network. +With this setting you can select which community pages will be shown to visitors of your Friendica node. +Your local users will always have access to both pages.

    +

    Note: Several settings, like users hiding their contacts from the public will prevent the postings to show up on the global community page.

    +

    Allowed Friend Domains#

    +

    Comma separated list of domains which are allowed to establish friendships with this site. +Wildcards are accepted. +By default, any (valid) domain may establish friendships with this site.

    +

    This is useful if you want to set up a closed network for educational groups, cooperatives and similar communities that don't want to communicate with the rest of the network.

    +

    Allowed Email Domains#

    +

    Comma separated list of domains which are allowed in email addresses for registrations to this site. +This can lockout those who are not part of this organisation from registering here. +Wildcards are accepted. +By default, any (valid) email address is allowed in registrations.

    +

    Allow Users to set remote_self#

    +

    If you enable the Allow Users to set remote_self users can select Atom feeds from their contact list being their remote self in the contact settings. +Which means that postings by the remote self are automatically reposted by Friendica in their names.

    +

    This feature can be used to let the user mirror e.g. blog postings into their Friendica postings. +It is disabled by default, as it causes additional load on the server and may be misused to distribute SPAM.

    +

    As admin of the node you can also set this flag directly in the database. +Before doing so, you should be sure you know what you do and have a backup of the database.

    +

    Explicit Content#

    +

    If you are running a node with explicit content, you can announce this with this option. +When checked an information flag will be set in the published information about your node. +(Should Publish Server Information be enabled.)

    +

    Additionally, a note will be displayed on the registration page for new users.

    +

    Advanced#

    +

    Proxy Configuration Settings#

    +

    If your site uses a proxy to connect to the internet, you may use these settings to communicate with the outside world. +The outside world still needs to be able to see your website, or this will not be very useful.

    +

    Network Timeout#

    +

    How long to wait on a network communication before timing out. +Value is in seconds. +Default is 60 seconds. +Set to 0 for unlimited (not recommended).

    +

    Verify SSL Certificates#

    +

    By default, Friendica allows SSL communication between websites that have "self-signed" SSL certificates. +For the widest compatibility with browsers and other networks we do not recommend using self-signed certificates, but we will not prevent you from using them. +SSL encrypts all the data transmitted between sites (and to your browser). +This allows you to have completely encrypted communications, and also protect your login session from hijacking. +Self-signed certificates can be generated for free, without paying top-dollar for a website SSL certificate. +However, these aren't looked upon favourably in the security community because they can be subject to so-called "man-in-the-middle" attacks. +If you wish, you can turn on strict certificate checking. +This will mean you cannot connect (at all) to self-signed SSL sites.

    +

    Check upstream version#

    +

    If this option is enabled your Friendica node will check the upstream version once per day from the GitHub repository. +You can select if the stable version or the development version should be checked out. +If there is a new version published, you will get notified in the admin panel summary page.

    +

    Auto Discovered Contact Directory#

    +

    Performance#

    +

    Worker#

    +

    This section allows you to configure the background process that is triggered by the cron job that was created during the installation. +The process does check the available system resources before creating a new worker for a task. +Because of this, it may happen that the maximum number of worker processes you allow will not be reached.

    +

    The tasks for the background process have priorities. +To guarantee that important tasks are executed even though the system has a lot of work to do, it is useful to enable the fastlane.

    +

    Relocate#

    +

    Users#

    +

    This section of the panel let the admin control the users registered on the node.

    +

    If you have selected "Requires approval" for the Register policy in the general nodes' configuration, new registrations will be listed at the top of the page. +There the admin can then approve or disapprove the request.

    +

    Below the new registration block the current accounts on the Friendica node are listed. +You can sort the user list by name, email, registration date, date of last login, date of last posting and the account type. +Here the admin can also block/unblock users from accessing the node or delete the accounts entirely.

    +

    In the last section of the page admins can create new accounts on the node. +The password for the new account will be sent by email to the chosen email address.

    +

    Addons#

    +

    This page is for selecting and configuration of extensions for Friendica which have to be placed into the /addon subdirectory of your Friendica installation. +You are presented with a long list of available addons. +The name of each addon is linked to a separate page for that addon which offers more information and configuration possibilities. +Also shown is the version of the addon and an indicator if the addon is currently active or not.

    +

    When you update your node and the addons they may have to be reloaded. +To simplify this process there is a button at the top of the page to reload all active Addons.

    +

    Themes#

    +

    The Themes' section of the admin panel works similar to the Addons section but let you control the themes on your Friendica node. +Each theme has a dedicated subpage showing the current status, some information about the theme and a screenshot of the Friendica interface using the theme. +Should the theme offer special settings, admins can set a global default value here.

    +

    You can activate and deactivate themes on their dedicated sub-pages thus making them available for the users of the node. +To select a default theme for the Friendica node, see the Site section of the admin panel.

    +

    Additional Features#

    +

    There are several optional features in Friendica like the dislike button. +In this section of the admin panel you can select a default setting for your node and eventually fix it, so users cannot change the setting anymore.

    +

    DB Updates#

    +

    Should the database structure of Friendica change, it will apply the changes automatically. +In case you are suspecting the update might not have worked, you can use this section of the admin panel to check the situation.

    +

    Inspect Queue#

    +

    In the admin panel summary there are two numbers for the message queues. +The second number represents messages which could not be delivered and are queued for later retry. +If this number goes sky-rocking you might ask yourself which recipient is not receiving.

    +

    Behind the inspect queue section of the admin panel you will find a list of the messages that could not be delivered. +The listing is sorted by the recipient name so identifying potential broken communication lines should be simple. +These lines might be broken for various reasons. +The receiving end might be off-line, there might be a high system load and so on.

    +

    Don't panic! +Friendica will not queue messages for all time but will sort out dead nodes automatically after a while and remove messages from the queue then.

    +

    Server Blocklist#

    +

    This page allows to block all communications (inbound and outbound) with a specific domain name. +Each blocked domain entry requires a reason that will be displayed on the friendica (/friendica) page. +Matching is exact, blocking a domain doesn't block subdomains.

    +

    Federation Statistics#

    +

    The federation statistics page gives you a short summery of the nodes/servers/pods of the decentralized social network federation your node knows. +These numbers are not complete and only contain nodes from networks Friendica federates directly with.

    +

    Delete Item#

    +

    Using this page an admin can delete postings and eventually associated discussion threads from their Friendica node. +To do so, they need to know the GUID of the posting. +This can be found on the /display page of the posting, it is the last part of the URL displayed in the browsers' navigation bar. +You can get to the /display page by following the Link to source.

    +

    Addon Features#

    +

    Some addons you can install for your Friendica node have settings which have to be set by the admin. +All those addons will be listed in this area of the admin panels sidebar with their names.

    +

    Logs#

    +

    The log section of the admin panel is separated into two pages. +On the first, following the "log" link, you can configure how much Friendica shall log. +And on the second you can read the log.

    +

    You should not place your logs into any directory that is accessible from the web. +If you have to, and you are using the default configuration from Apache, you should choose a name for the logfile ending in .log or .out. +Should you use another web server, please make sure that you have the correct access rules in place so that your log files are not accessible.

    +

    There are five different log levels: Normal, Trace, Debug, Data and All. +Specifying different verbosity of information and data written out to the log file. +Normally you should not need to log at all. +The DEBUG level will show a good deal of information about system activity but will not include detailed data. +In the ALL level Friendica will log everything to the file. +But due to the volume of information we recommend only enabling this when you are tracking down a specific problem.

    +

    The amount of data can grow the filesize of the logfile quickly. +You should set up some kind of log rotation to keep the log file from growing too big.

    +

    Known Issues: The filename friendica.log can cause problems depending on your server configuration (see issue 2209).

    +

    By default, PHP warnings and error messages are suppressed. +If you want to enable those, you have to activate them in the config/local.config.php file. +Use the following settings to redirect PHP errors to a file.

    +

    Config:

    +
    <?php
    +error_reporting(E_ERROR | E_WARNING | E_PARSE );
    +ini_set('error_log','php.out');
    +ini_set('log_errors','1');
    +ini_set('display_errors', '0');
    +
    +

    This will put all PHP errors in the file php.out (which must be writeable by the webserver). +Undeclared variables are occasionally referenced in the program, and therefore we do not recommend using E_NOTICE or E_ALL. +The vast majority of issues reported at these levels are completely harmless. +Please report to the developers any errors you encounter in the logs using the recommended settings above. +They generally indicate issues which need to be resolved.

    +

    If you encounter a blank (white) page when using the application, view the PHP logs - as this almost always indicates an error has occurred.

    +

    Diagnostics#

    +

    In this section of the admin panel you find two tools to investigate what Friendica sees for certain resources. +These tools can help to clarify communication problems.

    +

    For the probe address Friendica will display information for the address provided.

    +

    With the second tool check webfinger you can request information about the thing identified by a webfinger (someone@example.com).

    +

    Exceptions to the rule#

    +

    There are four exceptions to the rule, that all the config will be read from the database. +These are the database settings, the admin account settings, the path of PHP and information about an eventual installation of the node in a subdirectory of the (sub)domain.

    +

    DB Settings#

    +

    With the following settings, you specify the database server, the username and password for Friendica and the database to use.

    +
    'database' => [
    +    'hostname' => 'localhost',
    +    'username' => 'mysqlusername',
    +    'password' => 'mysqlpassword',
    +    'database' => 'mysqldatabasename',
    +    'charset' => 'utf8mb4',
    +],
    +
    +

    Admin users#

    +

    You can set one, or more, accounts to be Admin. +By default, this will be the one account you create during the installation process. +But you can expand the list of email addresses by any used email address you want. +Registration of new accounts with a listed email address is not possible.

    +
    'config' => [
    +    'admin_email' => 'you@example.com, buddy@example.com',
    +],
    +
    +

    PHP Path#

    +

    Some of Friendica's processes are running in the background. +For this you need to specify the path to the PHP binary to be used.

    +
    'config' => [
    +    'php_path' => '/usr/bin/php',
    +],
    +
    +

    Subdirectory configuration#

    +

    It is possible to install Friendica into a subdirectory of your web server. +We strongly discourage you from doing so, as this will break federation to other networks (e.g. Diaspora, GNU Social, Hubzilla) +Say you have a subdirectory for tests and put Friendica into a further subdirectory, the config would be:

    +
    'system' => [
    +    'urlpath' => 'tests/friendica',
    +],
    +
    +

    Other exceptions#

    +

    Furthermore, there are some experimental settings, you can read-up in the Config values that can only be set in config/local.config.php section of the documentation.

    + + +
    + +
    +
    + + + +
    + + + +
    +
    +
    +
    + + + + + + + + \ No newline at end of file diff --git a/develop/en/admin/ssl/index.html b/develop/en/admin/ssl/index.html new file mode 100644 index 0000000..48925c8 --- /dev/null +++ b/develop/en/admin/ssl/index.html @@ -0,0 +1,3539 @@ + + + + + + + + + + + + + + + + + + + + + + SSL - Friendica documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + +
    + + + + +
    + + +
    + +
    + + + + + + + + + +
    +
    + + + +
    +
    +
    + + + + + + +
    +
    +
    + + + +
    +
    +
    + + + +
    +
    +
    + + +
    +
    + + + + + + + + + + + + + +

    Using SSL with Friendica#

    +

    Disclaimer#

    +

    This document has been updated in November 2016. +SSL encryption is relevant for security. +This means that recommended settings change fast. +Keep your setup up to date and do not rely on this document being updated as fast as technologies change!

    +

    Intro#

    +

    If you are running your own Friendica site, you may want to use SSL (https) to encrypt communication between servers and between yourself and your server.

    +

    There are basically two sorts of SSL certificates: Self-signed certificates and certificates signed by a certificate authority (CA). +Technically, both provide the same valid encryption. +There is a problem with self-signed certificates though: +They are neither installed in browsers nor on other servers. +That is why they provoke warnings about "mistrusted certificates". +This is confusing and disturbing.

    +

    For this reason, we recommend to get a certificate signed by a CA. +Normally, you have to pay for them - and they are valid for a limited period of time (e.g. a year or two).

    +

    There are ways to get a trusted certificate for free.

    +

    Choose your domain name#

    +

    Your SSL certificate will be valid for a domain or even only for a subdomain. +Make your final decision about your domain resp. subdomain before ordering the certificate. +Once you have it, changing the domain name means getting a new certificate.

    +

    Shared hosts#

    +

    If your Friendica instance is running on a shared hosting platform, you should first check with your hosting provider. +They have instructions for you on how to do it there. +You can always order a paid certificate with your provider. +They will either install it for you or provide an easy way to upload the certificate and the key via a web interface. +With some providers, you have to send them your certificate. +They need the certificate, the key and the CA's intermediate certificate. +To be sure, send those three files. +You should send them to your provider via an encrypted channel!

    +

    Own server#

    +

    If you run your own server, we recommend to check out the "Let's Encrypt" initiative. +Not only do they offer free SSL certificates, but also a way to automate their renewal. +You need to install a client software on your server to use it. +Instructions for the official client are here. +Depending on your needs, you might want to look at the list of alternative LetsEncrypt clients.

    +

    Web server settings#

    +

    Visit the Mozilla's wiki for instructions on how to configure a secure webserver. +They provide recommendations for different web servers.

    +

    Test your SSL settings#

    +

    When you are done, visit the test site SSL Labs to have them check if you succeeded.

    +

    Configure Friendica#

    +

    If you can successfully access your Friendica instance through https, there are a number of steps you can take to ensure your users will use SSL to access your instance.

    +

    Web server redirection#

    +

    This is the simplest way to enforce site-wide secure access. +Every time a user tries to access any Friendica page by any mean (manual address bar entry or link), the web server issues a Permanent Redirect response with the secure protocol prepended to the requested URL.

    +

    With Apache, enable the modules rewrite and ssl (with a shared hosting provider, this should be enabled already):

    +
    sudo a2enmod rewrite ssl
    +
    +

    Add the following lines to the .htaccess file in the root folder of your Friendica instance (thanks to AlfredSK):

    +
    RewriteEngine On
    +RewriteCond %{SERVER_PORT} 80
    +RewriteRule ^(.*)$ https://your.friendica.domain/$1 [R=301,L]
    +
    +

    With nginx, configure your server directive this way (documentation):

    +
    server {
    +     listen 80;
    +     server_name your.friendica.domain;
    +     return 301 https://$server_name$request_uri;
    +}
    +
    +

    SSL Settings#

    +

    In the Admin Settings, there are three SSL-related settings:

    +
      +
    1. SSL link policy: this affects how Friendica generates internal links. If your SSL installation was successful, we recommend "Force all links to SSL" just in case your web server configuration can't be altered like described above.
    2. +
    3. Force SSL: This forces all external links to HTTPS, which may solve Mixed-Content issues, but not all websites support HTTPS yet. Use at your own risk.
    4. +
    5. Verify SSL: Enabling this will prevent Friendica to interact with self-signed SSL sites. We recommend you leave it on as a self-signed SSL certificate can be a vector for a man-in-the-middle attack.
    6. +
    + + +
    + +
    +
    + + + +
    + + + +
    +
    +
    +
    + + + + + + + + \ No newline at end of file diff --git a/develop/en/admin/tools/index.html b/develop/en/admin/tools/index.html new file mode 100644 index 0000000..4664ab8 --- /dev/null +++ b/develop/en/admin/tools/index.html @@ -0,0 +1,3433 @@ + + + + + + + + + + + + + + + + + + + + + + Tools - Friendica documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + +
    + + + + +
    + + +
    + +
    + + + + + + + + + +
    +
    + + + +
    +
    +
    + + + + + + +
    +
    +
    + + + +
    +
    +
    + + + +
    +
    +
    + + +
    +
    + + + + + + + + + + + + + +

    Admin Tools#

    +

    Friendica Tools#

    +

    Friendica has a build in command console you can find in the bin directory. +The console provides the following commands:

    +
      +
    • cache: Manage node cache
    • +
    • config: Edit site config
    • +
    • createdoxygen: Generate Doxygen headers
    • +
    • dbstructure: Do database updates
    • +
    • docbloxerrorchecker: Check the file tree for DocBlox errors
    • +
    • extract: Generate translation string file for the Friendica project (deprecated)
    • +
    • globalcommunityblock: Block remote profile from interacting with this node
    • +
    • globalcommunitysilence: Silence remote profile from global community page
    • +
    • archivecontact: Archive a contact when you know that it isn't existing anymore
    • +
    • help: Show help about a command, e.g (bin/console help config)
    • +
    • autoinstall: Starts automatic installation of friendica based on values from htconfig.php
    • +
    • maintenance: Set maintenance mode for this node
    • +
    • newpassword: Set a new password for a given user
    • +
    • php2po: Generate a messages.po file from a strings.php file
    • +
    • po2php: Generate a strings.php file from a messages.po file
    • +
    • typo: Checks for parse errors in Friendica files
    • +
    • postupdate: Execute pending post update scripts (can last days)
    • +
    • storage: Manage storage backend
    • +
    • relay: Manage ActivityPub relay servers
    • +
    +

    Please consult bin/console help on the command line interface of your server for details about the commands.

    +

    3rd Party Tools#

    +

    In addition to the tools Friendica includes, some 3rd party tools can make your admin days easier.

    +

    Fail2ban#

    +

    Fail2ban is an intrusion prevention framework (see Wikipedia) that you can use to forbid access to a server under certain conditions, e.g. 3 failed attempts to log in, for a certain amount of time.

    +

    The following configuration was provided by Steffen K9 using Debian. +You need to adjust the logpath in the jail.local file and the bantime (value is in seconds).

    +

    In /etc/fail2ban/jail.local create a section for Friendica:

    +
    [friendica]
    +enabled = true
    +findtime = 300
    +bantime  = 900
    +filter = friendica
    +port = http,https
    +logpath = /var/log/friend.log
    +logencoding = utf-8
    +
    +

    And create a filter definition in /etc/fail2ban/filter.d/friendica.conf:

    +
    [Definition]
    +failregex = ^.*authenticate\: failed login attempt.*\"ip\"\:\"<HOST>\".*$
    +ignoreregex =
    +
    +

    Additionally, you have to define the number of failed logins before the ban should be activated. +This is done either in the global configuration or for each jail separately. +You should inform your users about the number of failed login attempts you grant them. +Otherwise, you'll get many reports about the server not functioning if the number is too low.

    +

    Log rotation#

    +

    If you have activated the logs in Friendica, be aware that they can grow to a significant size. +To keep them in control you should add them to the automatic log rotation, e.g. using the logrotate command.

    +

    In /etc/logrotate.d/ add a file called friendica that contains the configuration. +The following will compress /var/log/friendica (assuming this is the location of the log file) on a daily basis and keep 2 days of back-log.

    +
    /var/log/friendica.log {
    +    compress
    +    daily
    +    rotate 2
    +}
    +
    + + +
    + +
    +
    + + + +
    + + + +
    +
    +
    +
    + + + + + + + + \ No newline at end of file diff --git a/develop/en/admin/update/index.html b/develop/en/admin/update/index.html new file mode 100644 index 0000000..a80c3b6 --- /dev/null +++ b/develop/en/admin/update/index.html @@ -0,0 +1,3507 @@ + + + + + + + + + + + + + + + + + + + + + + Update - Friendica documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + +
    + + + + +
    + + +
    + +
    + + + + + + + + + +
    +
    + + + +
    +
    +
    + + + + + + +
    +
    +
    + + + +
    +
    +
    + + + +
    +
    +
    + + +
    +
    + + + + + + + + + + + + + +

    Updating Friendica#

    +

    Using a Friendica archive#

    +

    If you installed Friendica in the path/to/friendica folder:

    +
      +
    1. Unpack the new Friendica archive in path/to/friendica_new.
    2. +
    3. Copy the following items from path/to/friendica to path/to/friendica_new:
    4. +
    5. config/local.config.php
    6. +
    7. proxy/
      +The following items only need to be copied if they are located inside your friendica path:
    8. +
    9. your storage folder as set in Admin -> Site -> File Upload -> Storage base path
    10. +
    11. your item cache as set in Admin -> Site -> Performance -> Path to item cache
    12. +
    13. your temp folder as set in Admin -> Site -> Advanced -> Temp path
    14. +
    15. Rename the path/to/friendica folder to path/to/friendica_old.
    16. +
    17. Rename the path/to/friendica_new folder to path/to/friendica.
    18. +
    19. Check your site. Note: it may go into maintenance mode to update the database schema.
    20. +
    21. If everything works, just delete the path/to/friendica_old folder.
    22. +
    +

    To update Addons from an archive, simply delete the path/to/friendica/addon and replace it with the provided archive.

    +

    Using Git#

    +

    You can get the latest changes at any time with

    +
    cd path/to/friendica
    +git pull
    +bin/composer.phar install --no-dev
    +
    +

    The addon tree has to be updated separately like so:

    +
    cd path/to/friendica/addon
    +git pull
    +
    +

    For both repositories: +The default branch to use is the stable branch, which is the stable version of Friendica. +It is updated about four times a year on a fixed schedule.

    +

    If you want to use and test bleeding edge code please check out the develop branch. +The new features and fixes will be merged from develop into stable after a release candidate period before each release.

    +

    Warning: The develop branch is unstable, and breaks on average once a month for at most 24 hours until a patch is submitted and merged. +Be sure to pull frequently if you choose the develop branch.

    +

    Considerations before upgrading Friendica#

    +

    MySQL >= 5.7.4#

    +

    Starting from MySQL version 5.7.4, the IGNORE keyword in ALTER TABLE statements is ignored. +This prevents automatic table deduplication if a UNIQUE index is added to a Friendica table's structure. +If a DB update fails for you while creating a UNIQUE index, make sure to manually deduplicate the table before trying the update again.

    +

    Manual deduplication#

    +

    There are two main ways of doing it, either by manually removing the duplicates or by recreating the table. +Manually removing the duplicates is usually faster if they're not too numerous. +To manually remove the duplicates, you need to know the UNIQUE index columns available in database.sql.

    +
    SELECT GROUP_CONCAT(id), <index columns>, count(*) as count FROM users
    +GROUP BY <index columns> HAVING count >= 2;
    +
    +/* delete or merge duplicate from above query */;
    +
    +

    If there are too many rows to handle manually, you can create a new table with the same structure as the table with duplicates and insert the existing content with INSERT IGNORE. +To recreate the table you need to know the table structure available in database.sql.

    +
    CREATE TABLE <table_name>_new <rest of the CREATE TABLE>;
    +INSERT IGNORE INTO <table_name>_new SELECT * FROM <table_name>;
    +DROP TABLE <table_name>;
    +RENAME TABLE <table_name>_new TO <table_name>;
    +
    +

    This method is slower overall, but it is better suited for large numbers of duplicates.

    +

    Resolving Possible Database Issues Post Upgrading#

    +

    Foreign Keys#

    +

    Some updates include the use of foreign keys now that will bump into issues with previous versions, which would sometimes shove bad data into tables, preventing, causing errors such as below.

    +
    Error 1452 occurred during database update:
    +Cannot add or update a child row: a foreign key constraint fails (`friendica`.`#sql-10ea6_5a6d`, CONSTRAINT `#sql-10ea6_5a6d_ibfk_1` FOREIGN KEY (`contact-id`) REFERENCES `contact` (`id`))
    +ALTER TABLE `thread` ADD FOREIGN KEY (`iid`) REFERENCES `item` (`id`) ON UPDATE RESTRICT ON DELETE CASCADE; 
    +
    +

    All current known fixes for possible items that can go wrong are as below.

    +
    DELETE FROM `item` WHERE `owner-id` NOT IN (SELECT `id` FROM `contact`);
    +DELETE FROM `item` WHERE `contact-id` NOT IN (SELECT `id` FROM `contact`);
    +DELETE FROM `notify` WHERE `uri-id` NOT IN (SELECT `id` FROM `item-uri`);
    +DELETE FROM `photo` WHERE `contact-id` NOT IN (SELECT `id` FROM `contact`);
    +DELETE FROM `thread` WHERE `iid` NOT IN (SELECT `id` FROM `item`);
    +DELETE FROM `item` WHERE `author-id` NOT IN (SELECT `id` FROM `contact`);
    +DELETE FROM `diaspora-interaction` WHERE `uri-id` NOT IN (SELECT `id` FROM `item-uri`);
    +
    +

    This all has been compiled as of currently from issue #9746, #9753, and #9878.

    + + +
    + +
    +
    + + + +
    + + + +
    +
    +
    +
    + + + + + + + + \ No newline at end of file diff --git a/develop/en/assets/images/acl_win.png b/develop/en/assets/images/acl_win.png new file mode 100644 index 0000000..559493d Binary files /dev/null and b/develop/en/assets/images/acl_win.png differ diff --git a/develop/en/assets/images/camera.png b/develop/en/assets/images/camera.png new file mode 100644 index 0000000..89acdbd Binary files /dev/null and b/develop/en/assets/images/camera.png differ diff --git a/develop/en/assets/images/chain.png b/develop/en/assets/images/chain.png new file mode 100644 index 0000000..6537c07 Binary files /dev/null and b/develop/en/assets/images/chain.png differ diff --git a/develop/en/assets/images/darkbubble.png b/develop/en/assets/images/darkbubble.png new file mode 100644 index 0000000..08c7a93 Binary files /dev/null and b/develop/en/assets/images/darkbubble.png differ diff --git a/develop/en/assets/images/darkzero.png b/develop/en/assets/images/darkzero.png new file mode 100644 index 0000000..00dc3ee Binary files /dev/null and b/develop/en/assets/images/darkzero.png differ diff --git a/develop/en/assets/images/diabook.png b/develop/en/assets/images/diabook.png new file mode 100644 index 0000000..1a1f8d9 Binary files /dev/null and b/develop/en/assets/images/diabook.png differ diff --git a/develop/en/assets/images/dispy.png b/develop/en/assets/images/dispy.png new file mode 100644 index 0000000..476fa33 Binary files /dev/null and b/develop/en/assets/images/dispy.png differ diff --git a/develop/en/assets/images/editor_darkbubble.png b/develop/en/assets/images/editor_darkbubble.png new file mode 100644 index 0000000..d766648 Binary files /dev/null and b/develop/en/assets/images/editor_darkbubble.png differ diff --git a/develop/en/assets/images/editor_dpzero.png b/develop/en/assets/images/editor_dpzero.png new file mode 100644 index 0000000..79f0cb3 Binary files /dev/null and b/develop/en/assets/images/editor_dpzero.png differ diff --git a/develop/en/assets/images/editor_frio.png b/develop/en/assets/images/editor_frio.png new file mode 100644 index 0000000..d969f26 Binary files /dev/null and b/develop/en/assets/images/editor_frio.png differ diff --git a/develop/en/assets/images/editor_vier.png b/develop/en/assets/images/editor_vier.png new file mode 100644 index 0000000..7ffac7f Binary files /dev/null and b/develop/en/assets/images/editor_vier.png differ diff --git a/develop/en/assets/images/editor_zero.png b/develop/en/assets/images/editor_zero.png new file mode 100644 index 0000000..a1ee37b Binary files /dev/null and b/develop/en/assets/images/editor_zero.png differ diff --git a/develop/en/assets/images/friendica-32.png b/develop/en/assets/images/friendica-32.png new file mode 100644 index 0000000..e025e4c Binary files /dev/null and b/develop/en/assets/images/friendica-32.png differ diff --git a/develop/en/assets/images/friendica.svg b/develop/en/assets/images/friendica.svg new file mode 100644 index 0000000..180fe2a --- /dev/null +++ b/develop/en/assets/images/friendica.svg @@ -0,0 +1,4 @@ + + + + diff --git a/develop/en/assets/images/friendica_rich_editor.png b/develop/en/assets/images/friendica_rich_editor.png new file mode 100644 index 0000000..170b8ec Binary files /dev/null and b/develop/en/assets/images/friendica_rich_editor.png differ diff --git a/develop/en/assets/images/frio_location.png b/develop/en/assets/images/frio_location.png new file mode 100644 index 0000000..8850c80 Binary files /dev/null and b/develop/en/assets/images/frio_location.png differ diff --git a/develop/en/assets/images/globe.png b/develop/en/assets/images/globe.png new file mode 100644 index 0000000..7a563ab Binary files /dev/null and b/develop/en/assets/images/globe.png differ diff --git a/develop/en/assets/images/lock.png b/develop/en/assets/images/lock.png new file mode 100644 index 0000000..b9a1cef Binary files /dev/null and b/develop/en/assets/images/lock.png differ diff --git a/develop/en/assets/images/mic.png b/develop/en/assets/images/mic.png new file mode 100644 index 0000000..c8d4c0e Binary files /dev/null and b/develop/en/assets/images/mic.png differ diff --git a/develop/en/assets/images/padlock.png b/develop/en/assets/images/padlock.png new file mode 100644 index 0000000..40f60cf Binary files /dev/null and b/develop/en/assets/images/padlock.png differ diff --git a/develop/en/assets/images/paper_clip.png b/develop/en/assets/images/paper_clip.png new file mode 100644 index 0000000..97aea40 Binary files /dev/null and b/develop/en/assets/images/paper_clip.png differ diff --git a/develop/en/assets/images/post_categorize.png b/develop/en/assets/images/post_categorize.png new file mode 100644 index 0000000..86aee53 Binary files /dev/null and b/develop/en/assets/images/post_categorize.png differ diff --git a/develop/en/assets/images/post_choose.png b/develop/en/assets/images/post_choose.png new file mode 100644 index 0000000..762cea8 Binary files /dev/null and b/develop/en/assets/images/post_choose.png differ diff --git a/develop/en/assets/images/post_delete.png b/develop/en/assets/images/post_delete.png new file mode 100644 index 0000000..3d0a92e Binary files /dev/null and b/develop/en/assets/images/post_delete.png differ diff --git a/develop/en/assets/images/post_link.png b/develop/en/assets/images/post_link.png new file mode 100644 index 0000000..cd7df78 Binary files /dev/null and b/develop/en/assets/images/post_link.png differ diff --git a/develop/en/assets/images/post_mark.png b/develop/en/assets/images/post_mark.png new file mode 100644 index 0000000..7781b8c Binary files /dev/null and b/develop/en/assets/images/post_mark.png differ diff --git a/develop/en/assets/images/post_share.png b/develop/en/assets/images/post_share.png new file mode 100644 index 0000000..a75ce2a Binary files /dev/null and b/develop/en/assets/images/post_share.png differ diff --git a/develop/en/assets/images/post_tag.png b/develop/en/assets/images/post_tag.png new file mode 100644 index 0000000..fa9fca6 Binary files /dev/null and b/develop/en/assets/images/post_tag.png differ diff --git a/develop/en/assets/images/post_thumbs_down.png b/develop/en/assets/images/post_thumbs_down.png new file mode 100644 index 0000000..0117779 Binary files /dev/null and b/develop/en/assets/images/post_thumbs_down.png differ diff --git a/develop/en/assets/images/post_thumbs_up.png b/develop/en/assets/images/post_thumbs_up.png new file mode 100644 index 0000000..aea54ab Binary files /dev/null and b/develop/en/assets/images/post_thumbs_up.png differ diff --git a/develop/en/assets/images/posts_define.png b/develop/en/assets/images/posts_define.png new file mode 100644 index 0000000..1d2cb08 Binary files /dev/null and b/develop/en/assets/images/posts_define.png differ diff --git a/develop/en/assets/images/video.png b/develop/en/assets/images/video.png new file mode 100644 index 0000000..3ee2d12 Binary files /dev/null and b/develop/en/assets/images/video.png differ diff --git a/develop/en/assets/images/vier_icons.png b/develop/en/assets/images/vier_icons.png new file mode 100644 index 0000000..b880e95 Binary files /dev/null and b/develop/en/assets/images/vier_icons.png differ diff --git a/develop/en/assets/stylesheets/friendica.css b/develop/en/assets/stylesheets/friendica.css new file mode 100644 index 0000000..3a07af4 --- /dev/null +++ b/develop/en/assets/stylesheets/friendica.css @@ -0,0 +1,91 @@ +:root > * { + + // Default color shades + --md-default-fg-color: hsla(0, 0%, 0%, 0.87); + --md-default-fg-color--light: hsla(0, 0%, 0%, 0.54); + --md-default-fg-color--lighter: hsla(0, 0%, 0%, 0.32); + --md-default-fg-color--lightest: hsla(0, 0%, 0%, 0.07); + --md-default-bg-color: hsla(0, 0%, 100%, 1); + --md-default-bg-color--light: hsla(0, 0%, 100%, 0.7); + --md-default-bg-color--lighter: hsla(0, 0%, 100%, 0.3); + --md-default-bg-color--lightest: hsla(0, 0%, 100%, 0.12); + + // Primary color shades + --md-primary-fg-color: hsla(#{hex2hsl($clr-indigo-500)}, 1); + --md-primary-fg-color--light: hsla(#{hex2hsl($clr-indigo-400)}, 1); + --md-primary-fg-color--dark: hsla(#{hex2hsl($clr-indigo-700)}, 1); + --md-primary-bg-color: hsla(0, 0%, 100%, 1); + --md-primary-bg-color--light: hsla(0, 0%, 100%, 0.7); + + // Accent color shades + --md-accent-fg-color: hsla(#{hex2hsl($clr-indigo-a200)}, 1); + --md-accent-fg-color--transparent: hsla(#{hex2hsl($clr-indigo-a200)}, 0.1); + --md-accent-bg-color: hsla(0, 0%, 100%, 1); + --md-accent-bg-color--light: hsla(0, 0%, 100%, 0.7); + + // Code color shades + --md-code-fg-color: hsla(200, 18%, 26%, 1); + --md-code-bg-color: hsla(0, 0%, 96%, 1); + + // Code highlighting color shades + --md-code-hl-color: hsla(#{hex2hsl($clr-yellow-a200)}, 0.5); + --md-code-hl-number-color: hsla(0, 67%, 50%, 1); + --md-code-hl-special-color: hsla(340, 83%, 47%, 1); + --md-code-hl-function-color: hsla(291, 45%, 50%, 1); + --md-code-hl-constant-color: hsla(250, 63%, 60%, 1); + --md-code-hl-keyword-color: hsla(219, 54%, 51%, 1); + --md-code-hl-string-color: hsla(150, 63%, 30%, 1); + --md-code-hl-name-color: var(--md-code-fg-color); + --md-code-hl-operator-color: var(--md-default-fg-color--light); + --md-code-hl-punctuation-color: var(--md-default-fg-color--light); + --md-code-hl-comment-color: var(--md-default-fg-color--light); + --md-code-hl-generic-color: var(--md-default-fg-color--light); + --md-code-hl-variable-color: var(--md-default-fg-color--light); + + // Typeset color shades + --md-typeset-color: var(--md-default-fg-color); + + // Typeset `a` color shades + --md-typeset-a-color: var(--md-primary-fg-color); + + // Typeset `mark` color shades + --md-typeset-mark-color: hsla(#{hex2hsl($clr-yellow-a200)}, 0.5); + + // Typeset `del` and `ins` color shades + --md-typeset-del-color: hsla(6, 90%, 60%, 0.15); + --md-typeset-ins-color: hsla(150, 90%, 44%, 0.15); + + // Typeset `kbd` color shades + --md-typeset-kbd-color: hsla(0, 0%, 98%, 1); + --md-typeset-kbd-accent-color: hsla(0, 100%, 100%, 1); + --md-typeset-kbd-border-color: hsla(0, 0%, 72%, 1); + + // Typeset `table` color shades + --md-typeset-table-color: hsla(0, 0%, 0%, 0.12); + + // Admonition color shades + --md-admonition-fg-color: var(--md-default-fg-color); + --md-admonition-bg-color: var(--md-default-bg-color); + + // Footer color shades + --md-footer-fg-color: hsla(0, 0%, 100%, 1); + --md-footer-fg-color--light: hsla(0, 0%, 100%, 0.7); + --md-footer-fg-color--lighter: hsla(0, 0%, 100%, 0.3); + --md-footer-bg-color: hsla(0, 0%, 0%, 0.87); + --md-footer-bg-color--dark: hsla(0, 0%, 0%, 0.32); + + // Shadow depth 1 + --md-shadow-z1: + 0 #{px2rem(4px)} #{px2rem(10px)} hsla(0, 0%, 0%, 0.05), + 0 0 #{px2rem(1px)} hsla(0, 0%, 0%, 0.1); + + // Shadow depth 2 + --md-shadow-z2: + 0 #{px2rem(4px)} #{px2rem(10px)} hsla(0, 0%, 0%, 0.1), + 0 0 #{px2rem(1px)} hsla(0, 0%, 0%, 0.25); + + // Shadow depth 3 + --md-shadow-z3: + 0 #{px2rem(4px)} #{px2rem(10px)} hsla(0, 0%, 0%, 0.2), + 0 0 #{px2rem(1px)} hsla(0, 0%, 0%, 0.35); + } diff --git a/develop/en/bugs-and-issues/index.html b/develop/en/bugs-and-issues/index.html new file mode 100644 index 0000000..8588810 --- /dev/null +++ b/develop/en/bugs-and-issues/index.html @@ -0,0 +1,3269 @@ + + + + + + + + + + + + + + + + + + + + + + Bugs and Issues - Friendica documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + +
    + + + + +
    + + +
    + +
    + + + + + + + + + +
    +
    + + + +
    +
    +
    + + + + + + +
    +
    +
    + + + +
    +
    +
    + + + +
    +
    +
    + + +
    +
    + + + + + + + + + + + + + +

    Bugs and Issues#

    +

    If your server has a support page, you should report any bugs/issues you encounter there first. +Reporting to your support page before reporting to the developers makes their job easier, as they don't have to deal with bug reports that might not have anything to do with them. +Reducing the workload in this way helps us get new features faster. +You can also contact the friendica support forum and report your problem there. +Bugs are rarely limited to one person, and the chances are somebody from another node has encountered the issue too, and will be able to help you.

    +

    If you're a technical user, or your site doesn't have a support page, you'll need to use the Bug Tracker. +This is also used for issues with addons. +Please perform a search to see if there's already an open bug that matches yours before submitting anything.

    +

    Try to provide as much information as you can about the bug, including the full text of any error messages or notices, and any steps required to replicate the problem in as much detail as possible. +It's generally better to provide too much information than not enough.

    +

    See this article to learn more about submitting good bug reports. The better your bug report, the more likely we are to be able to actually fix it.

    +

    And last but not least: It is better to report an issue you encountered even if you can't write the perfect bug report!

    + + +
    + +
    +
    + + + +
    + + + +
    +
    +
    +
    + + + + + + + + \ No newline at end of file diff --git a/develop/en/developer/addon-storage-backend/index.html b/develop/en/developer/addon-storage-backend/index.html new file mode 100644 index 0000000..35d08d8 --- /dev/null +++ b/develop/en/developer/addon-storage-backend/index.html @@ -0,0 +1,3778 @@ + + + + + + + + + + + + + + + + + + + + + + Addon Storage Backend - Friendica documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + +
    + + + + +
    + + +
    + +
    + + + + + + + + + +
    +
    + + + +
    +
    +
    + + + + + + +
    +
    +
    + + + +
    +
    +
    + + + +
    +
    +
    + + +
    +
    + + + + + + + + + + + + + +

    Friendica Storage Backend Addon development#

    +

    Storage backends can be added via addons. +A storage backend is implemented as a class, and the plugin register the class to make it available to the system.

    +

    The Storage Backend Class#

    +

    The class must live in Friendica\Addon\youraddonname namespace, where youraddonname the folder name of your addon.

    +

    There are two different interfaces you need to implement.

    +

    ICanWriteToStorage#

    +

    The class must implement Friendica\Core\Storage\Capability\ICanWriteToStorage interface. All method in the interface must be implemented:

    +
    <?php
    +namespace Friendica\Core\Storage\Capability\ICanWriteToStorage;
    +
    +interface ICanWriteToStorage
    +{
    +    public function get(string $reference);
    +    public function put(string $data, string $reference = '');
    +    public function delete(string $reference);
    +    public function __toString();
    +    public static function getName();
    +}
    +
    +
      +
    • get(string $reference) returns data pointed by $reference
    • +
    • put(string $data, string $reference) saves data in $data to position $reference, or a new position if $reference is empty.
    • +
    • delete(string $reference) delete data pointed by $reference
    • +
    +

    ICanConfigureStorage#

    +

    Each storage backend can have options the admin can set in admin page. +To make the options possible, you need to implement the Friendica\Core\Storage\Capability\ICanConfigureStorage interface.

    +

    All methods in the interface must be implemented:

    +
    <?php
    +namespace Friendica\Core\Storage\Capability\ICanConfigureStorage;
    +
    +interface ICanConfigureStorage
    +{
    +    public function getOptions();
    +    public function saveOptions(array $data);
    +}
    +
    +
      +
    • getOptions() returns an array with details about each option to build the interface.
    • +
    • saveOptions(array $data) get $data from admin page, validate it and save it.
    • +
    +

    The array returned by getOptions() is defined as:

    +
    [
    +    'option1name' => [ ..info.. ],
    +    'option2name' => [ ..info.. ],
    +    ...
    +]
    +
    +

    An empty array can be returned if backend doesn't have any options.

    +

    The info array for each option is defined as:

    +
    [
    +    'type',
    +
    +

    define the field used in form, and the type of data. +one of 'checkbox', 'combobox', 'custom', 'datetime', 'input', 'intcheckbox', 'password', 'radio', 'richtext', 'select', 'select_raw', 'textarea'

    +
        'label',
    +
    +

    Translatable label of the field. This label will be shown in admin page

    +
        value,
    +
    +

    Current value of the option

    +
        'help text',
    +
    +

    Translatable description for the field. Will be shown in admin page

    +
        extra data
    +
    +

    Optional. Depends on which 'type' this option is:

    +
      +
    • 'select': array [ value => label ] of choices
    • +
    • 'intcheckbox': value of input element
    • +
    • 'select_raw': prebuild html string of <option > tags
    • +
    +

    Each label should be translatable

    +
    ];
    +
    +

    See doxygen documentation of IWritableStorage interface for details about each method.

    +

    Register a storage backend class#

    +

    Each backend must be registered in the system when the plugin is installed, to be aviable.

    +

    DI::facStorage()->register(string $class) is used to register the backend class.

    +

    When the plugin is uninstalled, registered backends must be unregistered using +DI::facStorage()->unregister(string $class).

    +

    You have to register a new hook in your addon, listening on storage_instance(App $a, array $data). +In case $data['name'] is your storage class name, you have to instance a new instance of your Friendica\Core\Storage\Capability\ICanReadFromStorage class. +Set the instance of your class as $data['storage'] to pass it back to the backend.

    +

    This is necessary because it isn't always clear, if you need further construction arguments.

    +

    Adding tests#

    +

    Currently testing is limited to core Friendica only, this shows theoretically how tests should work in the future

    +

    Each new Storage class should be added to the test-environment at Storage Tests.

    +

    Add a new test class which's naming convention is StorageClassTest, which extend the StorageTest in the same directory.

    +

    Override the two necessary instances:

    +
    use Friendica\Core\Storage\Capability\ICanWriteToStorage;
    +
    +abstract class StorageTest 
    +{
    +    // returns an instance of your newly created storage class
    +    abstract protected function getInstance();
    +
    +    // Assertion for the option array you return for your new StorageClass
    +    abstract protected function assertOption(ICanWriteToStorage $storage);
    +} 
    +
    +

    Exception handling#

    +

    There are two intended types of exceptions for storages

    +

    ReferenceStorageExecption#

    +

    This storage exception should be used in case the caller tries to use an invalid references. +This could happen in case the caller tries to delete or update an unknown reference. +The implementation of the storage backend must not ignore invalid references.

    +

    Avoid throwing the common StorageExecption instead of the ReferenceStorageException at this particular situation!

    +

    StorageException#

    +

    This is the common exception in case unexpected errors happen using the storage backend. +If there's a predecessor to this exception (e.g. you caught an exception and are throwing this execption), you should add the predecessor for transparency reasons.

    +

    Example:

    +
    use Friendica\Core\Storage\Capability\ICanWriteToStorage;
    +
    +class ExampleStorage implements ICanWriteToStorage 
    +{
    +    public function get(string $reference) : string
    +    {
    +        try {
    +            throw new Exception('a real bad exception');
    +        } catch (Exception $exception) {
    +            throw new \Friendica\Core\Storage\Exception\StorageException(sprintf('The Example Storage throws an exception for reference %s', $reference), 500, $exception);
    +        }
    +    }
    +} 
    +
    +

    Example#

    +

    Here is a hypothetical addon which register a useless storage backend. +Let's call it samplestorage.

    +

    This backend will discard all data we try to save and will return always the same image when we ask for some data. +The image returned can be set by the administrator in admin page.

    +

    First, the backend class. +The file will be addon/samplestorage/SampleStorageBackend.php:

    +
    <?php
    +namespace Friendica\Addon\samplestorage;
    +
    +use Friendica\Core\Storage\Capability\ICanWriteToStorage;
    +
    +use Friendica\Core\Config\Capability\IManageConfigValues;
    +use Friendica\Core\L10n;
    +
    +class SampleStorageBackend implements ICanWriteToStorage
    +{
    +    const NAME = 'Sample Storage';
    +
    +    /** @var string */
    +    private $filename;
    +
    +    /**
    +      * SampleStorageBackend constructor.
    +      * 
    +      * You can add here every dynamic class as dependency you like and add them to a private field
    +      * Friendica automatically creates these classes and passes them as argument to the constructor                                       
    +      */
    +    public function __construct(string $filename) 
    +    {
    +        $this->filename = $filename;
    +    }
    +
    +    public function get(string $reference)
    +    {
    +        // we return always the same image data. Which file we load is defined by
    +        // a config key
    +        return file_get_contents($this->filename);
    +    }
    +
    +    public function put(string $data, string $reference = '')
    +    {
    +        if ($reference === '') {
    +            $reference = 'sample';
    +        }
    +        // we don't save $data !
    +        return $reference;
    +    }
    +
    +    public function delete(string $reference)
    +    {
    +        // we pretend to delete the data
    +        return true;
    +    }
    +
    +    public function __toString()
    +    {
    +        return self::NAME;
    +    }
    +
    +    public static function getName()
    +    {
    +        return self::NAME;
    +    }
    +}
    +
    +
    <?php
    +namespace Friendica\Addon\samplestorage;
    +
    +use Friendica\Core\Storage\Capability\ICanConfigureStorage;
    +
    +use Friendica\Core\Config\Capability\IManageConfigValues;
    +use Friendica\Core\L10n;
    +
    +class SampleStorageBackendConfig implements ICanConfigureStorage
    +{
    +    /** @var \Friendica\Core\Config\Capability\IManageConfigValues */
    +    private $config;
    +    /** @var L10n */
    +    private $l10n;
    +
    +    /**
    +      * SampleStorageBackendConfig constructor.
    +      * 
    +      * You can add here every dynamic class as dependency you like and add them to a private field
    +      * Friendica automatically creates these classes and passes them as argument to the constructor                                       
    +      */
    +    public function __construct(IManageConfigValues $config, L10n $l10n) 
    +    {
    +        $this->config = $config;
    +        $this->l10n   = $l10n;
    +    }
    +
    +    public function getFileName(): string
    +    {
    +        return $this->config->get('storage', 'samplestorage', 'sample.jpg');
    +    }
    +
    +    public function getOptions()
    +    {
    +        $filename = $this->config->get('storage', 'samplestorage', 'sample.jpg');
    +        return [
    +            'filename' => [
    +                'input',    // will use a simple text input
    +                $this->l10n->t('The file to return'),   // the label
    +                $filename,  // the current value
    +                $this->l10n->t('Enter the path to a file'), // the help text
    +                // no extra data for 'input' type..
    +            ],
    +        ];
    +    }
    +
    +    public function saveOptions(array $data)
    +    {
    +        // the keys in $data are the same keys we defined in getOptions()
    +        $newfilename = trim($data['filename']);
    +
    +        // this function should always validate the data.
    +        // in this example we check if file exists
    +        if (!file_exists($newfilename)) {
    +            // in case of error we return an array with
    +            // ['optionname' => 'error message']
    +            return ['filename' => 'The file doesn\'t exists'];
    +        }
    +
    +        $this->config->set('storage', 'samplestorage', $newfilename);
    +
    +        // no errors, return empty array
    +        return [];
    +    }
    +
    +}
    +
    +

    Now the plugin main file. Here we register and unregister the backend class.

    +

    The file is addon/samplestorage/samplestorage.php

    +
    <?php
    +/**
    + * Name: Sample Storage Addon
    + * Description: A sample addon which implements an unusefull storage backend
    + * Version: 1.0.0
    + * Author: Alice <https://alice.social/~alice>
    + */
    +
    +use Friendica\Addon\samplestorage\SampleStorageBackend;
    +use Friendica\Addon\samplestorage\SampleStorageBackendConfig;
    +use Friendica\DI;
    +
    +function samplestorage_install()
    +{
    +    Hook::register('storage_instance' , __FILE__, 'samplestorage_storage_instance');
    +    Hook::register('storage_config' , __FILE__, 'samplestorage_storage_config');
    +    DI::storageManager()->register(SampleStorageBackend::class);
    +}
    +
    +function samplestorage_storage_uninstall()
    +{
    +    DI::storageManager()->unregister(SampleStorageBackend::class);
    +}
    +
    +function samplestorage_storage_instance(App $a, array &$data)
    +{
    +    $config          = new SampleStorageBackendConfig(DI::l10n(), DI::config());
    +    $data['storage'] = new SampleStorageBackendConfig($config->getFileName());
    +}
    +
    +function samplestorage_storage_config(App $a, array &$data)
    +{
    +    $data['storage_config'] = new SampleStorageBackendConfig(DI::l10n(), DI::config());
    +}
    +
    +

    **Theoretically - until tests for Addons are enabled too - create a test class with the name addon/tests/SampleStorageTest.php:

    +
    use Friendica\Core\Storage\Capability\ICanWriteToStorage;
    +use Friendica\Test\src\Core\Storage\StorageTest;
    +
    +class SampleStorageTest extends StorageTest 
    +{
    +    // returns an instance of your newly created storage class
    +    protected function getInstance()
    +    {
    +        // create a new SampleStorageBackend instance with all it's dependencies
    +        // Have a look at DatabaseStorageTest or FilesystemStorageTest for further insights
    +        return new SampleStorageBackend();
    +    }
    +
    +    // Assertion for the option array you return for your new StorageClass
    +    protected function assertOption(ICanWriteToStorage $storage)
    +    {
    +        $this->assertEquals([
    +            'filename' => [
    +                'input',
    +                'The file to return',
    +                'sample.jpg',
    +                'Enter the path to a file'
    +            ],
    +        ], $storage->getOptions());
    +    }
    +} 
    +
    + + +
    + +
    +
    + + + +
    + + + +
    +
    +
    +
    + + + + + + + + \ No newline at end of file diff --git a/develop/en/developer/addons/index.html b/develop/en/developer/addons/index.html new file mode 100644 index 0000000..92a866b --- /dev/null +++ b/develop/en/developer/addons/index.html @@ -0,0 +1,6278 @@ + + + + + + + + + + + + + + + + + + + + + + Addons - Friendica documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + +
    + + + + +
    + + +
    + +
    + + + + + + + + + +
    +
    + + + +
    +
    +
    + + + + + + +
    +
    +
    + + + +
    +
    +
    + + + +
    +
    +
    + + +
    +
    + + + + + + + + + + + + + +

    Friendica Addon development#

    +

    Please see the sample addon 'randplace' for a working example of using some of these features. +Addons work by intercepting event hooks - which must be registered. +Modules work by intercepting specific page requests (by URL path).

    +

    Naming#

    +

    Addon names are used in file paths and functions names, and as such: +- Can't contain spaces or punctuation. +- Can't start with a number.

    +

    Metadata#

    +

    You can provide human-readable information about your addon in the first multi-line comment of your addon file.

    +

    Here's the structure:

    +
    /**
    + * Name: {Human-readable name}
    + * Description: {Short description}
    + * Version: 1.0
    + * Author: {Author1 Name}
    + * Author: {Author2 Name} <{Author profile link}>
    + * Maintainer: {Maintainer1 Name}
    + * Maintainer: {Maintainer2 Name} <{Maintainer profile link}>
    + * Status: {Unsupported|Arbitrary status}
    + */
    +
    +

    You can also provide a longer documentation in a README or README.md file. +The latter will be converted from Markdown to HTML in the addon detail page.

    +

    Install/Uninstall#

    +

    If your addon uses hooks, they have to be registered in a <addon>_install() function. +This function also allows to perform arbitrary actions your addon needs to function properly.

    +

    Uninstalling an addon automatically unregisters any hook it registered, but if you need to provide specific uninstallation steps, you can add them in a <addon>_uninstall() function.

    +

    The installation and uninstallation functions will be called (i.e. re-installed) if the addon changes after installation. +Therefore, your uninstall should not destroy data and install should consider that data may already exist. +Future extensions may provide for "setup" amd "remove".

    +

    PHP addon hooks#

    +

    Register your addon hooks during installation.

    +
    \Friendica\Core\Hook::register($hookname, $file, $function);
    +
    +

    $hookname is a string and corresponds to a known Friendica PHP hook.

    +

    $file is a pathname relative to the top-level Friendica directory. +This should be 'addon/addon_name/addon_name.php' in most cases and can be shortened to __FILE__.

    +

    $function is a string and is the name of the function which will be executed when the hook is called.

    +

    Arguments#

    +

    Your hook callback functions will be called with at least one and possibly two arguments

    +
    function <addon>_<hookname>(App $a, &$b) {
    +
    +}
    +
    +

    If you wish to make changes to the calling data, you must declare them as reference variables (with &) during function declaration.

    +

    $a#

    +

    $a is the Friendica App class. +It contains a wealth of information about the current state of Friendica:

    +
      +
    • which module has been called,
    • +
    • configuration information,
    • +
    • the page contents at the point the hook was invoked,
    • +
    • profile and user information, etc.
    • +
    +

    It is recommended you call this $a to match its usage elsewhere.

    +

    $b#

    +

    $b can be called anything you like. +This is information specific to the hook currently being processed, and generally contains information that is being immediately processed or acted on that you can use, display, or alter. +Remember to declare it with & if you wish to alter it.

    +

    Admin settings#

    +

    Your addon can provide user-specific settings via the addon_settings PHP hook, but it can also provide node-wide settings in the administration page of your addon.

    +

    Simply declare a <addon>_addon_admin(App $a) function to display the form and a <addon>_addon_admin_post(App $a) function to process the data from the form.

    +

    Global stylesheets#

    +

    If your addon requires adding a stylesheet on all pages of Friendica, add the following hook:

    +
    function <addon>_install()
    +{
    +    \Friendica\Core\Hook::register('head', __FILE__, '<addon>_head');
    +    ...
    +}
    +
    +
    +function <addon>_head(App $a)
    +{
    +    \Friendica\DI::page()->registerStylesheet(__DIR__ . '/relative/path/to/addon/stylesheet.css');
    +}
    +
    +

    __DIR__ is the folder path of your addon.

    +

    JavaScript#

    +

    Global scripts#

    +

    If your addon requires adding a script on all pages of Friendica, add the following hook:

    +
    function <addon>_install()
    +{
    +    \Friendica\Core\Hook::register('footer', __FILE__, '<addon>_footer');
    +    ...
    +}
    +
    +function <addon>_footer(App $a)
    +{
    +    \Friendica\DI::page()->registerFooterScript(__DIR__ . '/relative/path/to/addon/script.js');
    +}
    +
    +

    __DIR__ is the folder path of your addon.

    +

    JavaScript hooks#

    +

    The main Friendica script provides hooks via events dispatched on the document property. +In your Javascript file included as described above, add your event listener like this:

    +
    document.addEventListener(name, callback);
    +
    +
      +
    • name is the name of the hook and corresponds to a known Friendica JavaScript hook.
    • +
    • callback is a JavaScript anonymous function to execute.
    • +
    +

    More info about Javascript event listeners: https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/addEventListener

    +

    Current JavaScript hooks#

    +
    postprocess_liveupdate#
    +

    Called at the end of the live update process (XmlHttpRequest) and on a post preview. +No additional data is provided.

    +

    Modules#

    +

    Addons may also act as "modules" and intercept all page requests for a given URL path. +In order for an addon to act as a module it needs to declare an empty function <addon>_module().

    +

    If this function exists, you will now receive all page requests for https://my.web.site/<addon> - with any number of URL components as additional arguments. +These are parsed into the App\Arguments object. +So https://my.web.site/addon/arg1/arg2 would give this: +

    DI::args()->getArgc(); // = 3
    +DI::args()->get(0); // = 'addon'
    +DI::args()->get(1); // = 'arg1'
    +DI::args()->get(2); // = 'arg2'
    +

    +

    To display a module page, you need to declare the function <addon>_content(App $a), which defines and returns the page body content. +They may also contain <addon>_post(App $a) which is called before the <addon>_content function and typically handles the results of POST forms. +You may also have <addon>_init(App $a) which is called before <addon>_content and should include common logic to your module.

    +

    Templates#

    +

    If your addon needs some template, you can use the Friendica template system. +Friendica uses smarty3 as a template engine.

    +

    Put your tpl files in the templates/ sub-folder of your addon.

    +

    In your code, like in the function addon_name_content(), load the template file and execute it passing needed values:

    +
    use Friendica\Core\Renderer;
    +
    +# load template file. first argument is the template name,
    +# second is the addon path relative to friendica top folder
    +$tpl = Renderer::getMarkupTemplate('mytemplate.tpl', __DIR__);
    +
    +# apply template. first argument is the loaded template,
    +# second an array of 'name' => 'values' to pass to template
    +$output = Renderer::replaceMacros($tpl, array(
    +    'title' => 'My beautiful addon',
    +));
    +
    +

    See also the wiki page Quick Template Guide.

    +

    Current PHP hooks#

    +

    authenticate#

    +

    Called when a user attempts to login. +$b is an array containing:

    +
      +
    • username: the supplied username
    • +
    • password: the supplied password
    • +
    • authenticated: set this to non-zero to authenticate the user.
    • +
    • user_record: successful authentication must also return a valid user record from the database
    • +
    +

    logged_in#

    +

    Called after a user has successfully logged in. +$b contains the $a->user array.

    +

    display_item#

    +

    Called when formatting a post for display. +$b is an array:

    +
      +
    • item: The item (array) details pulled from the database
    • +
    • output: the (string) HTML representation of this item prior to adding it to the page
    • +
    +

    post_local#

    +

    Called when a status post or comment is entered on the local system. +$b is the item array of the information to be stored in the database. +Please note: body contents are bbcode - not HTML.

    +

    post_local_end#

    +

    Called when a local status post or comment has been stored on the local system. +$b is the item array of the information which has just been stored in the database. +Please note: body contents are bbcode - not HTML

    +

    post_remote#

    +

    Called when receiving a post from another source. This may also be used to post local activity or system generated messages. +$b is the item array of information to be stored in the database and the item body is bbcode.

    +

    addon_settings#

    +

    Called when generating the HTML for the addon settings page. +$data is an array containing:

    +
      +
    • addon (output): Required. The addon folder name.
    • +
    • title (output): Required. The addon settings panel title.
    • +
    • href (output): Optional. If set, will reduce the panel to a link pointing to this URL, can be relative. Incompatible with the following keys.
    • +
    • html (output): Optional. Raw HTML of the addon form elements. Both the <form> tags and the submit buttons are taken care of elsewhere.
    • +
    • submit (output): Optional. If unset, a default submit button with name="<addon name>-submit" will be generated. + Can take different value types:
    • +
    • string: The label to replace the default one.
    • +
    • associative array: A list of submit button, the key is the value of the name attribute, the value is the displayed label. + The first submit button in this list is considered the main one and themes might emphasize its display.
    • +
    +

    Examples#

    + +
    $data = [
    +    'addon' => 'advancedcontentfilter',
    +    'title' => DI::l10n()->t('Advanced Content Filter'),
    +    'href'  => 'advancedcontentfilter',
    +];
    +
    +
    With default submit button#
    +
    $data = [
    +    'addon' => 'fromapp',
    +    'title' => DI::l10n()->t('FromApp Settings'),
    +    'html'  => $html,
    +];
    +
    +
    With no HTML, just a submit button#
    +
    $data = [
    +    'addon'  => 'opmlexport',
    +    'title'  => DI::l10n()->t('OPML Export'),
    +    'submit' => DI::l10n()->t('Export RSS/Atom contacts'),
    +];
    +
    +
    With multiple submit buttons#
    +
    $data = [
    +    'addon'  => 'catavar',
    +    'title'  => DI::l10n()->t('Cat Avatar Settings'),
    +    'html'   => $html,
    +    'submit' => [
    +        'catavatar-usecat'   => DI::l10n()->t('Use Cat as Avatar'),
    +        'catavatar-morecat'  => DI::l10n()->t('Another random Cat!'),
    +        'catavatar-emailcat' => DI::pConfig()->get(local_user(), 'catavatar', 'seed', false) ? DI::l10n()->t('Reset to email Cat') : null,
    +    ],
    +];
    +
    +

    addon_settings_post#

    +

    Called when the Addon Settings pages are submitted. +$b is the $_POST array.

    +

    connector_settings#

    +

    Called when generating the HTML for a connector addon settings page. +$data is an array containing:

    +
      +
    • connector (output): Required. The addon folder name.
    • +
    • title (output): Required. The addon settings panel title.
    • +
    • image (output): Required. The relative path of the logo image of the platform/protocol this addon is connecting to, max size 48x48px.
    • +
    • enabled (output): Optional. If set to a falsy value, the connector image will be dimmed.
    • +
    • html (output): Optional. Raw HTML of the addon form elements. Both the <form> tags and the submit buttons are taken care of elsewhere.
    • +
    • submit (output): Optional. If unset, a default submit button with name="<addon name>-submit" will be generated. + Can take different value types:
        +
      • string: The label to replace the default one.
      • +
      • associative array: A list of submit button, the key is the value of the name attribute, the value is the displayed label. + The first submit button in this list is considered the main one and themes might emphasize its display.
      • +
      +
    • +
    +

    Examples#

    +
    With default submit button#
    +
    $data = [
    +    'connector' => 'diaspora',
    +    'title'     => DI::l10n()->t('Diaspora Export'),
    +    'image'     => 'images/diaspora-logo.png',
    +    'enabled'   => $enabled,
    +    'html'      => $html,
    +];
    +
    +
    With custom submit button label and no logo dim#
    +
    $data = [
    +    'connector' => 'ifttt',
    +    'title'     => DI::l10n()->t('IFTTT Mirror'),
    +    'image'     => 'addon/ifttt/ifttt.png',
    +    'html'      => $html,
    +    'submit'    => DI::l10n()->t('Generate new key'),
    +];
    +
    +
    With conditional submit buttons#
    +
    $submit = ['pumpio-submit' => DI::l10n()->t('Save Settings')];
    +if ($oauth_token && $oauth_token_secret) {
    +    $submit['pumpio-delete'] = DI::l10n()->t('Delete this preset');
    +}
    +
    +$data = [
    +    'connector' => 'pumpio',
    +    'title'     => DI::l10n()->t('Pump.io Import/Export/Mirror'),
    +    'image'     => 'images/pumpio.png',
    +    'enabled'   => $enabled,
    +    'html'      => $html,
    +    'submit'    => $submit,
    +];
    +
    +

    profile_post#

    +

    Called when posting a profile page. +$b is the $_POST array.

    +

    profile_edit#

    +

    Called prior to output of profile edit page. +$b is an array containing:

    +
      +
    • profile: profile (array) record from the database
    • +
    • entry: the (string) HTML of the generated entry
    • +
    +

    profile_advanced#

    +

    Called when the HTML is generated for the Advanced profile, corresponding to the Profile tab within a person's profile page. +$b is the HTML string representation of the generated profile. +The profile array details are in $a->profile.

    +

    directory_item#

    +

    Called from the Directory page when formatting an item for display. +$b is an array:

    +
      +
    • contact: contact record array for the person from the database
    • +
    • entry: the HTML string of the generated entry
    • +
    +

    profile_sidebar_enter#

    +

    Called prior to generating the sidebar "short" profile for a page. +$b is the person's profile array

    +

    profile_sidebar#

    +

    Called when generating the sidebar "short" profile for a page. +$b is an array:

    +
      +
    • profile: profile record array for the person from the database
    • +
    • entry: the HTML string of the generated entry
    • +
    +

    contact_block_end#

    +

    Called when formatting the block of contacts/friends on a profile sidebar has completed. +$b is an array:

    +
      +
    • contacts: array of contacts
    • +
    • output: the generated HTML string of the contact block
    • +
    +

    bbcode#

    +

    Called after conversion of bbcode to HTML. +$b is an HTML string converted text.

    +

    html2bbcode#

    +

    Called after tag conversion of HTML to bbcode (e.g. remote message posting) +$b is a string converted text

    + +

    Called when building the <head> sections. +Stylesheets should be registered using this hook. +$b is an HTML string of the <head> tag.

    + +

    Called after building the page navigation section. +$b is a string HTML of nav region.

    +

    personal_xrd#

    +

    Called prior to output of personal XRD file. +$b is an array:

    +
      +
    • user: the user record array for the person
    • +
    • xml: the complete XML string to be output
    • +
    +

    home_content#

    +

    Called prior to output home page content, shown to unlogged users. +$b is the HTML string of section region.

    +

    contact_edit#

    +

    Called when editing contact details on an individual from the Contacts page. +$b is an array:

    +
      +
    • contact: contact record (array) of target contact
    • +
    • output: the (string) generated HTML of the contact edit page
    • +
    +

    contact_edit_post#

    +

    Called when posting the contact edit page. +$b is the $_POST array

    +

    init_1#

    +

    Called just after DB has been opened and before session start. +No hook data.

    +

    page_end#

    +

    Called after HTML content functions have completed. +$b is (string) HTML of content div.

    + +

    Called after HTML content functions have completed. +Deferred Javascript files should be registered using this hook. +$b is (string) HTML of footer div/element.

    +

    avatar_lookup#

    +

    Called when looking up the avatar. $b is an array:

    +
      +
    • size: the size of the avatar that will be looked up
    • +
    • email: email to look up the avatar for
    • +
    • url: the (string) generated URL of the avatar
    • +
    +

    emailer_send_prepare#

    +

    Called from Emailer::send() before building the mime message. +$b is an array of params to Emailer::send().

    +
      +
    • fromName: name of the sender
    • +
    • fromEmail: email fo the sender
    • +
    • replyTo: replyTo address to direct responses
    • +
    • toEmail: destination email address
    • +
    • messageSubject: subject of the message
    • +
    • htmlVersion: html version of the message
    • +
    • textVersion: text only version of the message
    • +
    • additionalMailHeader: additions to the smtp mail header
    • +
    • sent: default false, if set to true in the hook, the default mailer will be skipped.
    • +
    +

    emailer_send#

    +

    Called before calling PHP's mail(). +$b is an array of params to mail().

    +
      +
    • to
    • +
    • subject
    • +
    • body
    • +
    • headers
    • +
    • sent: default false, if set to true in the hook, the default mailer will be skipped.
    • +
    +

    load_config#

    +

    Called during App initialization to allow addons to load their own configuration file(s) with App::loadConfigFile().

    + +

    Called after the navigational menu is build in include/nav.php. +$b is an array containing $nav from include/nav.php.

    +

    template_vars#

    +

    Called before vars are passed to the template engine to render the page. +The registered function can add,change or remove variables passed to template. +$b is an array with:

    +
      +
    • template: filename of template
    • +
    • vars: array of vars passed to the template
    • +
    +

    acl_lookup_end#

    +

    Called after the other queries have passed. +The registered function can add, change or remove the acl_lookup() variables.

    +
      +
    • results: array of the acl_lookup() vars
    • +
    +

    prepare_body_init#

    +

    Called at the start of prepare_body +Hook data:

    +
      +
    • item (input/output): item array
    • +
    +

    prepare_body_content_filter#

    +

    Called before the HTML conversion in prepare_body. If the item matches a content filter rule set by an addon, it should +just add the reason to the filter_reasons element of the hook data. +Hook data:

    +
      +
    • item: item array (input)
    • +
    • filter_reasons (input/output): reasons array
    • +
    +

    prepare_body#

    +

    Called after the HTML conversion in prepare_body(). +Hook data:

    +
      +
    • item (input): item array
    • +
    • html (input/output): converted item body
    • +
    • is_preview (input): post preview flag
    • +
    • filter_reasons (input): reasons array
    • +
    +

    prepare_body_final#

    +

    Called at the end of prepare_body(). +Hook data:

    +
      +
    • item: item array (input)
    • +
    • html: converted item body (input/output)
    • +
    +

    put_item_in_cache#

    +

    Called after prepare_text() in put_item_in_cache(). +Hook data:

    +
      +
    • item (input): item array
    • +
    • rendered-html (input/output): final item body HTML
    • +
    • rendered-hash (input/output): original item body hash
    • +
    +

    magic_auth_success#

    +

    Called when a magic-auth was successful. +Hook data:

    +
    visitor => array with the contact record of the visitor
    +url => the query string
    +
    +

    jot_networks#

    +

    Called when displaying the post permission screen. +Hook data is a list of form fields that need to be displayed along the ACL. +Form field array structure is:

    +
      +
    • type: checkbox or select.
    • +
    • field: Standard field data structure to be used by field_checkbox.tpl and field_select.tpl.
    • +
    +

    For checkbox, field is: + - [0] (String): Form field name; Mandatory. + - [1]: (String): Form field label; Optional, default is none. + - [2]: (Boolean): Whether the checkbox should be checked by default; Optional, default is false. + - [3]: (String): Additional help text; Optional, default is none. + - [4]: (String): Additional HTML attributes; Optional, default is none.

    +

    For select, field is: + - [0] (String): Form field name; Mandatory. + - [1] (String): Form field label; Optional, default is none. + - [2] (Boolean): Default value to be selected by default; Optional, default is none. + - [3] (String): Additional help text; Optional, default is none. + - [4] (Array): Associative array of options. Item key is option value, item value is option label; Mandatory.

    +

    route_collection#

    +

    Called just before dispatching the router. +Hook data is a \FastRoute\RouterCollector object that should be used to add addon routes pointing to classes.

    +

    Notice: The class whose name is provided in the route handler must be reachable via autoloader.

    +

    probe_detect#

    +

    Called before trying to detect the target network of a URL. +If any registered hook function sets the result key of the hook data array, it will be returned immediately. +Hook functions should also return immediately if the hook data contains an existing result.

    +

    Hook data:

    +
      +
    • uri (input): the profile URI.
    • +
    • network (input): the target network (can be empty for auto-detection).
    • +
    • uid (input): the user to return the contact data for (can be empty for public contacts).
    • +
    • result (output): Leave null if address isn't relevant to the connector, set to contact array if probe is successful, false otherwise.
    • +
    + +

    Called when trying to probe an item from a given URI. +If any registered hook function sets the item_id key of the hook data array, it will be returned immediately. +Hook functions should also return immediately if the hook data contains an existing item_id.

    +

    Hook data: +- uri (input): the item URI. +- uid (input): the user to return the item data for (can be empty for public contacts). +- item_id (output): Leave null if URI isn't relevant to the connector, set to created item array if probe is successful, false otherwise.

    +

    support_follow#

    +

    Called to assert whether a connector addon provides follow capabilities.

    +

    Hook data: +- protocol (input): shorthand for the protocol. List of values is available in src/Core/Protocol.php. +- result (output): should be true if the connector provides follow capabilities, left alone otherwise.

    +

    support_revoke_follow#

    +

    Called to assert whether a connector addon provides follow revocation capabilities.

    +

    Hook data: +- protocol (input): shorthand for the protocol. List of values is available in src/Core/Protocol.php. +- result (output): should be true if the connector provides follow revocation capabilities, left alone otherwise.

    +

    follow#

    +

    Called before adding a new contact for a user to handle non-native network remote contact (like Twitter).

    +

    Hook data:

    +
      +
    • url (input): URL of the remote contact.
    • +
    • contact (output): should be filled with the contact (with uid = user creating the contact) array if follow was successful.
    • +
    +

    unfollow#

    +

    Called when unfollowing a remote contact on a non-native network (like Twitter)

    +

    Hook data: +- contact (input): the target public contact (uid = 0) array. +- uid (input): the id of the source local user. +- result (output): whether the unfollowing is successful or not.

    +

    revoke_follow#

    +

    Called when making a remote contact on a non-native network (like Twitter) unfollow you.

    +

    Hook data: +- contact (input): the target public contact (uid = 0) array. +- uid (input): the id of the source local user. +- result (output): a boolean value indicating whether the operation was successful or not.

    +

    block#

    +

    Called when blocking a remote contact on a non-native network (like Twitter).

    +

    Hook data: +- contact (input): the remote contact (uid = 0) array. +- uid (input): the user id to issue the block for. +- result (output): a boolean value indicating whether the operation was successful or not.

    +

    unblock#

    +

    Called when unblocking a remote contact on a non-native network (like Twitter).

    +

    Hook data: +- contact (input): the remote contact (uid = 0) array. +- uid (input): the user id to revoke the block for. +- result (output): a boolean value indicating whether the operation was successful or not.

    +

    storage_instance#

    +

    Called when a custom storage is used (e.g. webdav_storage)

    +

    Hook data: +- name (input): the name of the used storage backend +- data['storage'] (output): the storage instance to use (must implement \Friendica\Core\Storage\IWritableStorage)

    +

    storage_config#

    +

    Called when the admin of the node wants to configure a custom storage (e.g. webdav_storage)

    +

    Hook data: +- name (input): the name of the used storage backend +- data['storage_config'] (output): the storage configuration instance to use (must implement \Friendica\Core\Storage\Capability\IConfigureStorage)

    +

    Complete list of hook callbacks#

    +

    Here is a complete list of all hook callbacks with file locations (as of 24-Sep-2018). Please see the source for details of any hooks not documented above.

    +

    index.php#

    +
    Hook::callAll('init_1');
    +Hook::callAll('app_menu', $arr);
    +Hook::callAll('page_content_top', DI::page()['content']);
    +Hook::callAll($a->module.'_mod_init', $placeholder);
    +Hook::callAll($a->module.'_mod_init', $placeholder);
    +Hook::callAll($a->module.'_mod_post', $_POST);
    +Hook::callAll($a->module.'_mod_content', $arr);
    +Hook::callAll($a->module.'_mod_aftercontent', $arr);
    +Hook::callAll('page_end', DI::page()['content']);
    +
    +

    include/api.php#

    +
    Hook::callAll('logged_in', $a->user);
    +Hook::callAll('authenticate', $addon_auth);
    +Hook::callAll('logged_in', $a->user);
    +
    +

    include/enotify.php#

    +
    Hook::callAll('enotify', $h);
    +Hook::callAll('enotify_store', $datarray);
    +Hook::callAll('enotify_mail', $datarray);
    +Hook::callAll('check_item_notification', $notification_data);
    +
    +

    src/Content/Conversation.php#

    +
    Hook::callAll('conversation_start', $cb);
    +Hook::callAll('render_location', $locate);
    +Hook::callAll('display_item', $arr);
    +Hook::callAll('display_item', $arr);
    +Hook::callAll('item_photo_menu', $args);
    +Hook::callAll('jot_tool', $jotplugins);
    +
    +

    mod/directory.php#

    +
    Hook::callAll('directory_item', $arr);
    +
    +

    mod/xrd.php#

    +
    Hook::callAll('personal_xrd', $arr);
    +
    +

    mod/parse_url.php#

    +
    Hook::callAll("parse_link", $arr);
    +
    +

    src/Module/Delegation.php#

    +
    Hook::callAll('home_init', $ret);
    +
    +

    mod/acl.php#

    +
    Hook::callAll('acl_lookup_end', $results);
    +
    +

    mod/network.php#

    +
    Hook::callAll('network_content_init', $arr);
    +Hook::callAll('network_tabs', $arr);
    +
    +

    mod/friendica.php#

    +
    Hook::callAll('about_hook', $o);
    +
    +

    mod/profiles.php#

    +
    Hook::callAll('profile_post', $_POST);
    +Hook::callAll('profile_edit', $arr);
    +
    +

    mod/settings.php#

    +
    Hook::callAll('addon_settings_post', $_POST);
    +Hook::callAll('connector_settings_post', $_POST);
    +Hook::callAll('display_settings_post', $_POST);
    +Hook::callAll('addon_settings', $settings_addons);
    +Hook::callAll('connector_settings', $settings_connectors);
    +Hook::callAll('display_settings', $o);
    +
    +

    mod/photos.php#

    +
    Hook::callAll('photo_post_init', $_POST);
    +Hook::callAll('photo_post_file', $ret);
    +Hook::callAll('photo_post_end', $foo);
    +Hook::callAll('photo_post_end', $foo);
    +Hook::callAll('photo_post_end', $foo);
    +Hook::callAll('photo_post_end', $foo);
    +Hook::callAll('photo_post_end', intval($item_id));
    +Hook::callAll('photo_upload_form', $ret);
    +
    +

    mod/profile.php#

    +
    Hook::callAll('profile_advanced', $o);
    +
    +

    mod/home.php#

    +
    Hook::callAll('home_init', $ret);
    +Hook::callAll("home_content", $content);
    +
    +

    mod/poke.php#

    +
    Hook::callAll('post_local_end', $arr);
    +
    +

    mod/contacts.php#

    +
    Hook::callAll('contact_edit_post', $_POST);
    +Hook::callAll('contact_edit', $arr);
    +
    +

    mod/tagger.php#

    +
    Hook::callAll('post_local_end', $arr);
    +
    +

    mod/uexport.php#

    +
    Hook::callAll('uexport_options', $options);
    +
    +

    mod/register.php#

    +
    Hook::callAll('register_post', $arr);
    +Hook::callAll('register_form', $arr);
    +
    +

    mod/item.php#

    +
    Hook::callAll('post_local_start', $_REQUEST);
    +Hook::callAll('post_local', $datarray);
    +Hook::callAll('post_local_end', $datarray);
    +
    +

    mod/editpost.php#

    +
    Hook::callAll('jot_tool', $jotplugins);
    +
    +

    src/Render/FriendicaSmartyEngine.php#

    +
    Hook::callAll("template_vars", $arr);
    +
    +

    src/App.php#

    +
    Hook::callAll('load_config');
    +Hook::callAll('head');
    +Hook::callAll('footer');
    +Hook::callAll('route_collection');
    +
    +

    src/Model/Item.php#

    +
    Hook::callAll('post_local', $item);
    +Hook::callAll('post_remote', $item);
    +Hook::callAll('post_local_end', $posted_item);
    +Hook::callAll('post_remote_end', $posted_item);
    +Hook::callAll('tagged', $arr);
    +Hook::callAll('post_local_end', $new_item);
    +Hook::callAll('put_item_in_cache', $hook_data);
    +Hook::callAll('prepare_body_init', $item);
    +Hook::callAll('prepare_body_content_filter', $hook_data);
    +Hook::callAll('prepare_body', $hook_data);
    +Hook::callAll('prepare_body_final', $hook_data);
    +
    +

    src/Model/Contact.php#

    +
    Hook::callAll('contact_photo_menu', $args);
    +Hook::callAll('follow', $arr);
    +
    +

    src/Model/Profile.php#

    +
    Hook::callAll('profile_sidebar_enter', $profile);
    +Hook::callAll('profile_sidebar', $arr);
    +Hook::callAll('profile_tabs', $arr);
    +Hook::callAll('zrl_init', $arr);
    +Hook::callAll('magic_auth_success', $arr);
    +
    +

    src/Model/Event.php#

    +
    Hook::callAll('event_updated', $event['id']);
    +Hook::callAll("event_created", $event['id']);
    +
    +

    src/Model/Register.php#

    +
    Hook::callAll('authenticate', $addon_auth);
    +
    +

    src/Model/User.php#

    +
    Hook::callAll('authenticate', $addon_auth);
    +Hook::callAll('register_account', $uid);
    +Hook::callAll('remove_user', $user);
    +
    +

    src/Module/Notifications/Ping.php#

    +
    Hook::callAll('network_ping', $arr);
    +
    +

    src/Module/PermissionTooltip.php#

    +
    Hook::callAll('lockview_content', $item);
    +
    +

    src/Module/Settings/Delegation.php#

    +
    Hook::callAll('authenticate', $addon_auth);
    +
    +

    src/Module/Settings/TwoFactor/Index.php#

    +
    Hook::callAll('authenticate', $addon_auth);
    +
    +

    src/Security/Authenticate.php#

    +
    Hook::callAll('authenticate', $addon_auth);
    +
    +

    src/Security/ExAuth.php#

    +
    Hook::callAll('authenticate', $addon_auth);
    +
    +

    src/Content/ContactBlock.php#

    +
    Hook::callAll('contact_block_end', $arr);
    +
    +

    src/Content/Text/BBCode.php#

    +
    Hook::callAll('bbcode', $text);
    +Hook::callAll('bb2diaspora', $text);
    +
    +

    src/Content/Text/HTML.php#

    +
    Hook::callAll('html2bbcode', $message);
    +
    +

    src/Content/Smilies.php#

    +
    Hook::callAll('smilie', $params);
    +
    +

    src/Content/Feature.php#

    +
    Hook::callAll('isEnabled', $arr);
    +Hook::callAll('get', $arr);
    +
    +

    src/Content/ContactSelector.php#

    +
    Hook::callAll('network_to_name', $nets);
    +
    +

    src/Content/OEmbed.php#

    +
    Hook::callAll('oembed_fetch_url', $embedurl, $j);
    +
    +

    src/Content/Nav.php#

    +
    Hook::callAll('page_header', DI::page()['nav']);
    +Hook::callAll('nav_info', $nav);
    +
    +

    src/Core/Authentication.php#

    +
    Hook::callAll('logged_in', $a->user);
    +
    +

    src/Core/Protocol.php#

    +
    Hook::callAll('support_follow', $hook_data);
    +Hook::callAll('support_revoke_follow', $hook_data);
    +Hook::callAll('unfollow', $hook_data);
    +Hook::callAll('revoke_follow', $hook_data);
    +Hook::callAll('block', $hook_data);
    +Hook::callAll('unblock', $hook_data);
    +
    +

    src/Core/StorageManager#

    +
    Hook::callAll('storage_instance', $data);
    +Hook::callAll('storage_config', $data);
    +
    +

    src/Worker/Directory.php#

    +
    Hook::callAll('globaldir_update', $arr);
    +
    +

    src/Worker/Notifier.php#

    +
    Hook::callAll('notifier_end', $target_item);
    +
    +

    src/Module/Login.php#

    +
    Hook::callAll('login_hook', $o);
    +
    +

    src/Module/Logout.php#

    +
    Hook::callAll("logging_out");
    +
    +

    src/Object/Post.php#

    +
    Hook::callAll('render_location', $locate);
    +Hook::callAll('display_item', $arr);
    +
    +

    src/Core/ACL.php#

    +
    Hook::callAll('contact_select_options', $x);
    +Hook::callAll($a->module.'_pre_'.$selname, $arr);
    +Hook::callAll($a->module.'_post_'.$selname, $o);
    +Hook::callAll($a->module.'_pre_'.$selname, $arr);
    +Hook::callAll($a->module.'_post_'.$selname, $o);
    +Hook::callAll('jot_networks', $jotnets);
    +
    +

    src/Core/Authentication.php#

    +
    Hook::callAll('logged_in', $a->user);
    +Hook::callAll('authenticate', $addon_auth);
    +
    +

    src/Core/Hook.php#

    +
    self::callSingle(self::getApp(), 'hook_fork', $fork_hook, $hookdata);
    +
    +

    src/Core/L10n/L10n.php#

    +
    Hook::callAll('poke_verbs', $arr);
    +
    +

    src/Core/Worker.php#

    +
    Hook::callAll("proc_run", $arr);
    +
    +

    src/Util/Emailer.php#

    +
    Hook::callAll('emailer_send_prepare', $params);
    +Hook::callAll("emailer_send", $hookdata);
    +
    +

    src/Util/Map.php#

    +
    Hook::callAll('generate_map', $arr);
    +Hook::callAll('generate_named_map', $arr);
    +Hook::callAll('Map::getCoordinates', $arr);
    +
    +

    src/Util/Network.php#

    +
    Hook::callAll('avatar_lookup', $avatar);
    +
    +

    src/Util/ParseUrl.php#

    +
    Hook::callAll("getsiteinfo", $siteinfo);
    +
    +

    src/Protocol/DFRN.php#

    +
    Hook::callAll('atom_feed_end', $atom);
    +Hook::callAll('atom_feed_end', $atom);
    +
    +

    src/Protocol/Email.php#

    +
    Hook::callAll('email_getmessage', $message);
    +Hook::callAll('email_getmessage_end', $ret);
    +
    +

    view/js/main.js#

    +
    document.dispatchEvent(new Event('postprocess_liveupdate'));
    +
    + + +
    + +
    +
    + + + +
    + + + +
    +
    +
    +
    + + + + + + + + \ No newline at end of file diff --git a/develop/en/developer/autoloader/index.html b/develop/en/developer/autoloader/index.html new file mode 100644 index 0000000..cd8e8d1 --- /dev/null +++ b/develop/en/developer/autoloader/index.html @@ -0,0 +1,3513 @@ + + + + + + + + + + + + + + + + + + + + + + Autoloader - Friendica documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + +
    + + + + +
    + + +
    + +
    + + + + + + + + + +
    +
    + + + +
    +
    +
    + + + + + + +
    +
    +
    + + + +
    +
    +
    + + + +
    +
    +
    + + +
    +
    + + + + + + + + + + + + + +

    Autoloader with Composer#

    +

    Friendica uses Composer to manage dependencies libraries and the class autoloader both for libraries and namespaced Friendica classes.

    +

    It's a command-line tool that downloads required libraries into the vendor folder and makes any namespaced class in src available through the whole application through boot.php.

    + +

    A quick introduction to class auto-loading#

    +

    The autoloader dynamically includes the file defining a class when it is first referenced, either by instantiating an object or simply making sure that it is available, without the need to explicitly use "require_once".

    +

    Once it is set up you don't have to directly use it, you can directly use any class that is covered by the autoloader (currently vendor and src)

    +

    Under the hood, Composer registers a callback with spl_autoload_register() that receives a class name as an argument and includes the corresponding class definition file. +For more info about PHP autoloading, please refer to the official PHP documentation.

    +

    Example#

    +

    Let's say you have a PHP file in src/ that define a very useful class:

    +
    // src/ItemsManager.php
    +<?php
    +namespace Friendica;
    +
    +class ItemsManager {
    +    public function getAll() { ... }
    +    public function getByID($id) { ... }
    +}
    +
    +

    The class ItemsManager has been declared in the Friendica namespace. +Namespaces are useful to keep classes separated and avoid names conflicts (could be that a library you want to use also defines a class named ItemsManager, but as long as it is in another namespace, you don't have any problem)

    +

    Let's say now that you need to load some items in a view, maybe in a fictional mod/network.php. +In order for the Composer autoloader to work, it must first be included. +In Friendica this is already done at the top of boot.php, with require_once('vendor/autoload.php');.

    +

    The code will be something like:

    +
    // mod/network.php
    +<?php
    +
    +use Friendica\App;
    +
    +function network_content(App $a) {
    +    $itemsmanager = new \Friendica\ItemsManager();
    +    $items = $itemsmanager->getAll();
    +
    +    // pass $items to template
    +    // return result
    +}
    +
    +

    That's a quite simple example, but look: no require()! +If you need to use a class, you can simply use it, and you don't need to do anything else.

    +

    Going further: now we have a bunch of *Manager classes that cause some code duplication. +Let's define a BaseManager class, where we move all common code between all managers:

    +
    // src/BaseManager.php
    +<?php
    +namespace Friendica;
    +
    +class BaseManager {
    +    public function thatFunctionEveryManagerUses() { ... }
    +}
    +
    +

    and then let's change the ItemsManager class to use this code

    +
    // src/ItemsManager.php
    +<?php
    +namespace Friendica;
    +
    +class ItemsManager extends BaseManager {
    +    public function getAll() { ... }
    +    public function getByID($id) { ... }
    +}
    +
    +

    Even though we didn't explicitly include the src/BaseManager.php file, the autoloader will when this class is first defined, because it is referenced as a parent class. +It works with the "BaseManager" example here, and it works when we need to call static methods:

    +
    // src/Dfrn.php
    +<?php
    +namespace Friendica;
    +
    +class Dfrn {
    +    public static function  mail($item, $owner) { ... }
    +}
    +
    +
    // mod/mail.php
    +<?php
    +
    +mail_post($a){
    +    ...
    +    Friendica\Protocol\DFRN::mail($item, $owner);
    +    ...
    +}
    +
    +

    If your code is in same namespace as the class you need, you don't need to prepend it:

    +
    // include/delivery.php
    +<?php
    +
    +namespace Friendica;
    +
    +use Friendica\Protocol\DFRN;
    +
    +// this is the same content of current include/delivery.php,
    +// but has been declared to be in "Friendica" namespace
    +
    +[...]
    +switch($contact['network']) {
    +    case NETWORK_DFRN:
    +        if ($mail) {
    +            $item['body'] = ...
    +            $atom = DFRN::mail($item, $owner);
    +        } elseif ($fsuggest) {
    +            $atom = DFRN::fsuggest($item, $owner);
    +            q("DELETE FROM `fsuggest` WHERE `id` = %d LIMIT 1", intval($item['id']));
    +        } elseif ($relocate)
    +            $atom = DFRN::relocate($owner, $uid);
    +[...]
    +
    +

    This is the current code of include/delivery.php, and since the code is declared to be in the "Friendica" namespace, you don't need to write it when you need to use the "Dfrn" class. +But if you want to use classes from another library, you need to use the full namespace, e.g.

    +
    // src/Diaspora.php
    +<?php
    +
    +namespace Friendica;
    +
    +class Diaspora {
    +    public function md2bbcode() {
    +        $html = \Michelf\MarkdownExtra::defaultTransform($text);
    +    }
    +}
    +
    +

    if you use that class in many places of the code, and you don't want to write the full path to the class every time, you can use the "use" PHP keyword

    +
    // src/Diaspora.php
    +<?php
    +namespace Friendica;
    +
    +use \Michelf\MarkdownExtra;
    +
    +class Diaspora {
    +    public function md2bbcode() {
    +        $html = MarkdownExtra::defaultTransform($text);
    +    }
    +}
    +
    +

    Note that namespaces are like paths in filesystem, separated by "\", with the first "\" being the global scope. +You can go deeper if you want to, like:

    +
    // src/Network/Dfrn.php
    +<?php
    +namespace Friendica\Network;
    +
    +class Dfrn {
    +}
    +
    +

    Please note that the location of the file defining the class must be placed in the appropriate sub-folders of src if the namespace isn't plain Friendica.

    +

    or

    +
    // src/Dba/Mysql
    +<?php
    +namespace Friendica\Dba;
    +
    +class Mysql {
    +}
    +
    +

    So you can think of namespaces as folders in a Unix file system, with global scope as the root ("\").

    + + + + +
    + +
    +
    + + + +
    + + + +
    +
    +
    +
    + + + + + + + + \ No newline at end of file diff --git a/develop/en/developer/composer/index.html b/develop/en/developer/composer/index.html new file mode 100644 index 0000000..045a1ad --- /dev/null +++ b/develop/en/developer/composer/index.html @@ -0,0 +1,3599 @@ + + + + + + + + + + + + + + + + + + + + + + Composer - Friendica documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + +
    + + + + +
    + + +
    + +
    + + + + + + + + + +
    +
    + + + +
    +
    +
    + + + + + + +
    +
    +
    + + + + + + +
    +
    + + + + + + + + + + + + + +

    Using Composer#

    +

    Friendica uses Composer to manage dependencies libraries and the class autoloader both for libraries and namespaced Friendica classes.

    +

    It's a command-line tool that downloads required libraries into the vendor folder and makes any namespaced class in src available through the whole application through boot.php.

    + +

    How to use Composer#

    +

    If you don't have Composer installed on your system, Friendica ships with a copy of it at bin/composer.phar. +For the purpose of this help, all examples will use this path to run Composer commands, however feel free to replace them with your own way of calling Composer. +Composer requires PHP CLI and the following examples assume it's available system-wide.

    +

    Installing/Updating Friendica#

    +

    From Archive#

    +

    If you just unpacked a Friendica release archive, you don't have to use Composer at all, all the required libraries are already bundled in the archive.

    +

    Installing with Git#

    +

    If you prefer to use git, you will have to run Composer to fetch the required libraries and build the autoloader before you can run Friendica. +Here are the typical commands you will have to run to do so:

    +
    ~> git clone https://github.com/friendica/friendica.git friendica
    +~/friendica> cd friendica
    +~/friendica> bin/composer.phar install
    +
    +

    That's it! Composer will take care of fetching all the required libraries in the vendor folder and build the autoloader to make those libraries available to Friendica.

    +

    Updating with Git#

    +

    Updating Friendica to the current stable or the latest develop version is easy with Git, just remember to run Composer after every branch pull.

    +
    ~> cd friendica
    +~/friendica> git pull
    +~/friendica> bin/composer.phar install
    +
    +

    And that's it. If any library used by Friendica has been upgraded, Composer will fetch the version currently used by Friendica and refresh the autoloader to ensure the best performances.

    +

    Developing Friendica#

    +

    First, thanks for contributing to Friendica! +Composer is meant to be used by developers to maintain third-party libraries required by Friendica. +If you don't need to use any third-party library, then you don't need to use Composer beyond what is above to install/update Friendica.

    +

    Adding a third-party library to Friendica#

    +

    Does your shiny new Addon need to rely on a third-party library not required by Friendica yet? +First, this library should be available on Packagist so that Composer knows how to fetch it directly just by mentioning its name in composer.json.

    +

    This file is the configuration of Friendica for Composer. It lists details about the Friendica project, but also a list of required dependencies and their target version. +Here's a simplified version of the one we currently use on Friendica:

    +
    {
    +    "name": "friendica/friendica",
    +    "description": "A decentralized social network part of The Federation",
    +    "type": "project",
    +    [...]
    +    "require": {
    +        "ezyang/htmlpurifier": "~4.7.0",
    +        "mobiledetect/mobiledetectlib": "2.8.*"
    +    },
    +    [...]
    +}
    +
    +

    The important part is under the require key, this is a list of all the libraries Friendica may need to run. +As you can see, at the moment we only require two, HTMLPurifier and MobileDetect. +Each library has a different target version, and per Composer documentation about version constraints, this means that:

    +
      +
    • We will update HTMLPurifier up to version 4.8.0 excluded
    • +
    • We will update MobileDetect up to version 2.9.0 excluded
    • +
    +

    There are other operators you can use to allow Composer to update the package up to the next major version excluded. +Or you can specify the exact version of the library if you code requires it, and Composer will never update it, although it isn't recommended.

    +

    To add a library, just add its Packagist identifier to the require list and set a target version string.

    +

    Then you should run bin/composer.phar update to add it to your local vendor folder and update the composer.lock file that specifies the current versions of the dependencies.

    +

    Updating an existing dependency#

    +

    If a package needs to be updated, whether to the next minor version or to the next major version provided you changed the adequate code in Friendica, simply edit composer.json to update the target version string of the relevant library.

    +

    Then you should run bin/composer.phar update to update it in your local vendor folder and update the composer.lock file that specifies the current versions of the dependencies.

    +

    Please note that you should commit both composer.json and composer.lock with your work every time you make a change to the former.

    +

    Composer FAQ#

    +

    I used the composer command and got a warning about not to run it as root.#

    +

    See https://getcomposer.org/root. +Composer should be run as the web server user, usually www-data with Apache or http with nginx. +If you can't switch to the web server user using su - [web user], you can directly run the Composer command with sudo -u [web user].

    +

    Running Composer with sudo complains about not being able to create the composer cache directory in /root/.composer#

    +

    This is because sudo doesn't always change the HOME environment variable, which means that the command is run as the web server user but the system still uses root home directory. +However, you can temporarily change environment variable for the execution of a single command. +For Composer, this would be: +

    $> COMPOSER_HOME=/var/tmp/composer sudo -u [web user] bin/composer.phar [mode]
    +

    + + + + +
    + +
    +
    + + + +
    + + + +
    +
    +
    +
    + + + + + + + + \ No newline at end of file diff --git a/develop/en/developer/domain-driven-design/index.html b/develop/en/developer/domain-driven-design/index.html new file mode 100644 index 0000000..4bc17fd --- /dev/null +++ b/develop/en/developer/domain-driven-design/index.html @@ -0,0 +1,3619 @@ + + + + + + + + + + + + + + + + + + + + + + DDD - Friendica documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + +
    + + + + +
    + + +
    + +
    + + + + + + + + + +
    +
    + + + +
    +
    +
    + + + + + + +
    +
    +
    + + + +
    +
    +
    + + + +
    +
    +
    + + +
    +
    + + + + + + + + + + + + + +

    Domain-Driven-Design#

    +

    Friendica uses class structures inspired by Domain-Driven-Design programming patterns. +This page is meant to explain what it means in practical terms for Friendica development.

    +

    Inspiration#

    +
      +
    • https://designpatternsphp.readthedocs.io/en/latest/Structural/DependencyInjection/README.html
    • +
    • https://designpatternsphp.readthedocs.io/en/latest/Creational/SimpleFactory/README.html
    • +
    • https://designpatternsphp.readthedocs.io/en/latest/More/Repository/README.html
    • +
    • https://designpatternsphp.readthedocs.io/en/latest/Creational/FactoryMethod/README.html
    • +
    • https://designpatternsphp.readthedocs.io/en/latest/Creational/Prototype/README.html
    • +
    +

    Core concepts#

    +

    Models and Collections#

    +

    Instead of anonymous arrays of database field values, we have Models and collections to take full advantage of PHP type hints.

    +

    Before: +

    <?php
    +
    +function doSomething(array $intros)
    +{
    +    foreach ($intros as $intro) {
    +        $introId = $intro['id'];
    +    }
    +}
    +
    +$intros = \Friendica\Database\DBA::selectToArray('intros', [], ['uid' => local_user()]);
    +
    +doSomething($intros);
    +

    +

    After:

    +
    <?php
    +
    +function doSomething(\Friendica\Contact\Introductions\Collection\Introductions $intros)
    +{
    +    foreach ($intros as $intro) {
    +        /** @var $intro \Friendica\Contact\Introductions\Entity\Introduction */
    +        $introId = $intro->id;
    +    }
    +}
    +
    +/** @var $intros \Friendica\Contact\Introductions\Collection\Introductions */
    +$intros = \Friendica\DI::intro()->selecForUser(local_user());
    +
    +doSomething($intros);
    +
    +

    Dependency Injection#

    +

    Under this concept, we want class objects to carry with them the dependencies they will use. +Instead of calling global/static function/methods, objects use their own class members.

    +

    Before: +

    <?php
    +
    +class Model
    +{
    +    public $id;
    +
    +    function save()
    +    {
    +        return \Friendica\Database\DBA::update('table', get_object_vars($this), ['id' => $this->id]);
    +    }
    +}
    +

    +

    After: +

    <?php
    +
    +class Model
    +{
    +    /**
    +     * @var \Friendica\Database\Database
    +     */
    +    protected $dba;
    +
    +    public $id;
    +
    +    function __construct(\Friendica\Database\Database $dba)
    +    {
    +        $this->dba = $dba;
    +    }
    +
    +    function save()
    +    {
    +        return $this->dba->update('table', get_object_vars($this), ['id' => $this->id]);
    +    }
    +}
    +

    +

    The main advantage is testability. +Another one is avoiding dependency circles and avoid implicit initializing. +In the first example the method save() has to be tested with the DBA::update() method, which may or may not have dependencies itself.

    +

    In the second example we can mock \Friendica\Database\Database, e.g. overload the class by replacing its methods by placeholders, which allows us to test only Model::save() and nothing else implicitly.

    +

    The main drawback is lengthy constructors for dependency-heavy classes. +To alleviate this issue we are using DiCe to simplify the instantiation of the higher level objects Friendica uses.

    +

    We also added a convenience factory named \Friendica\DI that creates some of the most common objects used in modules.

    +

    Factories#

    +

    Since we added a bunch of parameters to class constructors, instantiating objects has become cumbersome. +To keep it simple, we are using Factories. +Factories are classes used to generate other objects, centralizing the dependencies required in their constructor. +Factories encapsulate more or less complex creation of objects and create them redundancy free.

    +

    Before: +

    <?php
    +
    +$model = new Model(\Friendica\DI::dba());
    +$model->id = 1;
    +$model->key = 'value';
    +
    +$model->save();
    +

    +

    After: +

    <?php
    +
    +class Factory
    +{
    +    /**
    +     * @var \Friendica\Database\Database
    +     */
    +    protected $dba;
    +
    +    function __construct(\Friendica\Database\Database $dba)
    +    {
    +        $this->dba;
    +    }
    +
    +    public function create()
    +    {
    +        return new Model($this->dba);    
    +    }
    +}
    +
    +$model = \Friendica\DI::factory()->create();
    +$model->id = 1;
    +$model->key = 'value';
    +
    +$model->save();
    +

    +

    Here, DI::factory() returns an instance of Factory that can then be used to create a Model object without having to care about its dependencies.

    +

    Repositories#

    +

    Last building block of our code architecture, repositories are meant as the interface between models and how they are stored. +In Friendica they are stored in a relational database but repositories allow models not to have to care about it. +Repositories also act as factories for the Model they are managing.

    +

    Before: +

    <?php
    +
    +class Model
    +{
    +    /**
    +     * @var \Friendica\Database\Database
    +     */
    +    protected $dba;
    +
    +    public $id;
    +
    +    function __construct(\Friendica\Database\Database $dba)
    +    {
    +        $this->dba = $dba;
    +    }
    +
    +    function save()
    +    {
    +        return $this->dba->update('table', get_object_vars($this), ['id' => $this->id]);
    +    }
    +}
    +
    +class Factory
    +{
    +    /**
    +     * @var \Friendica\Database\Database
    +     */
    +    protected $dba;
    +
    +    function __construct(\Friendica\Database\Database $dba)
    +    {
    +        $this->dba;
    +    }
    +
    +    public function create()
    +    {
    +        return new Model($this->dba);    
    +    }
    +}
    +
    +
    +$model = \Friendica\DI::factory()->create();
    +$model->id = 1;
    +$model->key = 'value';
    +
    +$model->save();
    +

    +

    After: +

    <?php
    +
    +class Model {
    +    public $id;
    +}
    +
    +class Repository extends Factory
    +{
    +    /**
    +     * @var \Friendica\Database\Database
    +     */
    +    protected $dba;
    +
    +    function __construct(\Friendica\Database\Database $dba)
    +    {
    +        $this->dba;
    +    }
    +
    +    public function create()
    +    {
    +        return new Model($this->dba);    
    +    }
    +
    +    public function save(Model $model)
    +    {
    +        return $this->dba->update('table', get_object_vars($model), ['id' => $model->id]);
    +    }
    +}
    +
    +$model = \Friendica\DI::repository()->create();
    +$model->id = 1;
    +$model->key = 'value';
    +
    +\Friendica\DI::repository()->save($model);
    +

    + + +
    + +
    +
    + + + +
    + + + +
    +
    +
    +
    + + + + + + + + \ No newline at end of file diff --git a/develop/en/developer/github/index.html b/develop/en/developer/github/index.html new file mode 100644 index 0000000..3712e21 --- /dev/null +++ b/develop/en/developer/github/index.html @@ -0,0 +1,3441 @@ + + + + + + + + + + + + + + + + + + + + + + GitHub - Friendica documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + +
    + + + + +
    + + +
    + +
    + + + + + + + + + +
    +
    + + + +
    +
    +
    + + + + + + +
    +
    +
    + + + +
    +
    + +
    +
    + + +
    +
    + + + + + + + + + + + + + +

    Friendica on GitHub#

    +

    Here is how you can work on the code with us. If you have any questions please write to the Friendica developers' forum.

    +

    Introduction to the workflow with our GitHub repository#

    +
      +
    1. Install git on the system you will be developing on.
    2. +
    3. Create your own GitHub account.
    4. +
    5. Fork the Friendica repository from https://github.com/friendica/friendica.git.
    6. +
    7. Clone your fork from your GitHub account to your machine. +Follow the instructions provided here: http://help.github.com/fork-a-repo/ to create and use your own tracking fork on GitHub
    8. +
    9. Run bin/composer.phar install in Friendica's folder.
    10. +
    11. Commit your changes to your fork. +Then go to your GitHub page and create a "Pull request" to notify us to merge your work.
    12. +
    +

    Our Git Branches#

    +

    There are two relevant branches in the main repo on GitHub:

    +
      +
    1. stable: This branch contains stable releases only.
    2. +
    3. develop: This branch contains the latest code. +This is what you want to work with.
    4. +
    +

    Fast-forwarding#

    +

    Fast forwarding is enabled by default in git. +When you merge with fast-forwarding it does not add a new commit to mark when you've performed the merge and how. +This means in your commit history you can't know exactly what happened in terms of merges. +It's best to turn off fast-forwarding. +This is done by running "git merge --no-ff". +Here is an explanation on how to configure git to turn off fast-forwarding by default. +You can find some more background reading here.

    +

    Release branches#

    +

    A release branch is created when the develop branch contains all features it should have. +A release branch is used for a few things.

    +
      +
    1. It allows last-minute bug fixing before the release goes to stable branch.
    2. +
    3. It allows meta-data changes (README, CHANGELOG, etc.) for version bumps and documentation changes.
    4. +
    5. It makes sure the develop branch can receive new features that are not part of this release.
    6. +
    +

    That last point is important because... +The moment a release branch is created, develop is now intended for the version after this release. +So please don't ever merge develop into a release! +An example: If a release branch "release-3.4" is created, "develop" becomes either 3.5 or 4.0. +If you were to merge develop into release-3.4 at this point, features and bug-fixes intended for 3.5 or 4.0 might leak into this release branch. +This might introduce new bugs, too. +Which defeats the purpose of the release branch.

    +

    Some important reminders#

    +
      +
    1. +

      Please pull in any changes from the project repository and merge them with your work before issuing a pull request. +We reserve the right to reject any patch which results in a large number of merge conflicts. +This is especially true in the case of language translations - where we may not be able to understand the subtle differences between conflicting versions.

      +
    2. +
    3. +

      Test your changes. +Don't assume that a simple fix won't break anything else. +If possible get an experienced Friendica developer to review the code. +Don't hesitate to ask us in case of doubt.

      +
    4. +
    5. +

      Check your code for typos. +There is a console command called typo for this.

      +
    6. +
    +
    $> php bin/console.php typo
    +
    +

    Check out how to work with our Vagrant to save a lot of setup time!

    + + +
    + +
    +
    + + + +
    + + + +
    +
    +
    +
    + + + + + + + + \ No newline at end of file diff --git a/develop/en/developer/how-to-move-classes-to-src/index.html b/develop/en/developer/how-to-move-classes-to-src/index.html new file mode 100644 index 0000000..d50f502 --- /dev/null +++ b/develop/en/developer/how-to-move-classes-to-src/index.html @@ -0,0 +1,3461 @@ + + + + + + + + + + + + + + + + + + + + + + src Migration - Friendica documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + +
    + + + + +
    + + +
    + +
    + + + + + + + + + +
    +
    + + + +
    +
    +
    + + + + + + +
    +
    +
    + + + +
    +
    +
    + + + +
    +
    +
    + + +
    +
    + + + + + + + + + + + + + +

    How To Move Classes to src#

    +

    Friendica uses Composer to manage autoloading. +This means that all the PHP class files moved to the src folder will be automatically included when the class it defines is first used in the flow. +This is an improvement over the current require usage since files will be included on an actual usage basis instead of the presence of a require call.

    +

    However, there are a significant number of items to check when moving a class file from the include folder to the src folder, and this page is there to list them.

    +

    Decide the namespace#

    +

    This isn't the most technical decision of them all, but it has long-lasting consequences as it will be the name that will be used to refer to this class from now on. +There is a shared Ethercalc sheet to suggest namespace/class names that lists all the already moved class files for inspiration.

    +

    A few pointers though: +* Friendica is the base namespace for all classes in the src folder +* Namespaces match the directory structure, with Friendica namespace being the base src directory. The Config class set in the Friendica\Core namespace is expected to be found at src/Core/Config.php. +* Namespaces can help group classes with a similar purpose or relevant to a particular feature

    +

    When you're done deciding the namespace, it's time to use it. +Let's say we choose Friendica\Core for the Config class.

    +

    Use the namespace#

    +

    To declare the namespace, the file src/Core/Config.php must start with the following statement:

    +
    namespace Friendica\Core;
    +
    +

    From now on, the Config class can be referred to as Friendica\Core\Config, however it isn't very practical, especially when the class was previously used as Config. +Thankfully, PHP provides namespace shortcuts through use.

    +

    This language construct just provides a different naming scheme for a namespace or a class, but doesn't trigger the autoload-mechanism on its own. +Here are the different ways you can use use:

    +

    // No use
    +$config = new Friendica\Core\Config();
    +
    +
    // Namespace shortcut
    +use Friendica\Core;
    +
    +$config = new Core\Config();
    +
    +
    // Class name shortcut
    +use Friendica\Core\Config;
    +
    +$config = new Config();
    +
    +
    // Aliasing
    +use Friendica\Core\Config as Cfg;
    +
    +$config = new Cfg();
    +

    +

    Whatever the style chosen, a repository-wide search has to be done to find all the class name usage and either use the fully-qualified class name (including the namespace) or add a use statement at the start of each relevant file.

    +

    Escape non-namespace classes#

    +

    The class file you just moved is now in the Friendica namespace, but it probably isn't the case for all the classes referenced in this file. +Since we added a namespace Friendica\Core; to the file, all the class names still declared in include will be implicitly understood as Friendica\Core\ClassName, which is rarely what we expect.

    +

    To avoid Class Friendica\Core\ClassName not found errors, all the include-declared class names have to be prepended with a \, it tells the autoloader not to look for the class in the namespace but in the global space where non-namespaced classes are set. +If there are only a handful of references to a single non-namespaced class, just prepending \ is enough. However, if there are many instance, we can use use again.

    +

    namespace Friendica\Core;
    +...
    +if (\DBM::is_result($r)) {
    +    ...
    +}
    +
    +
    namespace Friendica\Core;
    +
    +use Friendica\Database\DBM;
    +
    +if (DBM::is_result($r)) {
    +    ...
    +}
    +

    +

    Remove any useless require#

    +

    Now that you successfully moved your class to the autoloaded src folder, there's no need to include this file anywhere in the app ever again. +Please remove all the require_once mentions of the former file, as they will provoke a Fatal Error even if the class isn't used.

    +

    Miscellaneous tips#

    +

    When you are done with moving the class, please run php bin/console.php typo from the Friendica base directory to check for obvious mistakes. +However, this tool isn't bullet-proof, and a staging install of Friendica is recommended to test your class move without impairing your production server if you host one.

    +

    Most of Friendica processes are run in the background, so make sure to turn on your debug log to check for errors that wouldn't show up while simply browsing Friendica.

    +

    Check the class file for any magic constant __FILE__ or __DIR__, as their value changed since you moved the class in the file tree. +Most of the time it's used for debugging purposes but there can be instances where it's used to create cache folders for example.

    + + + + +
    + +
    +
    + + + +
    + + + +
    +
    +
    +
    + + + + + + + + \ No newline at end of file diff --git a/develop/en/developer/index.html b/develop/en/developer/index.html new file mode 100644 index 0000000..4d80a98 --- /dev/null +++ b/develop/en/developer/index.html @@ -0,0 +1,3632 @@ + + + + + + + + + + + + + + + + + + + + + + Get Started - Friendica documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + +
    + + + + +
    + + +
    + +
    + + + + + + + + + +
    +
    + + + +
    +
    +
    + + + + + + +
    +
    +
    + + + + + + +
    +
    + + + + + + + + + + + + + +

    Where to get started to help improve Friendica#

    +

    Do you want to help us improve Friendica? +Here we have compiled some hints on how to get started and some tasks to help you choose. +A project like Friendica is the sum of many contributions. +Very different skills are required to make good software, not all of them involve coding! +We are looking for helpers in all areas, whether you write text or code, whether you spread the word to convince people or design new icons. +Whether you feel like an expert or like a newbie - join us with your ideas!

    +

    Contact us#

    +

    The discussion of Friendica development takes place in the following Friendica forums:

    + +

    Help other users#

    +

    Remember the questions you had when you first tried Friendica? +A good place to start can be to help new people find their way around Friendica in the general support forum. +Welcome them, answer their questions, point them to documentation or ping other helpers directly if you can't help but think you know who can.

    +

    Translation#

    +

    The documentation contains help on how to translate Friendica at Transifex where the UI is translated. +If you don't want to translate the UI, or it is already done to your satisfaction, you might want to work on the translation of the /help files?

    +

    Design#

    +

    Are you good at designing things? +If you have seen Friendica you probably have ideas to improve it, haven't you?

    +
      +
    • If you would like to work with us on enhancing the user interface, please join the forum for Friendica development.
    • +
    • Make plans for a better Friendica interface design and share them with us.
    • +
    • Tell us if you are able to realize your ideas or what kind of help you need. + We can't promise we have the right skills in the group, but we'll try.
    • +
    • Choose a thing to start with, e.g. work on the icon set of your favorite theme
    • +
    +

    Programming#

    +

    Friendica uses an implementation of Domain-Driven-Design, please make sure to check out the provided links for hints at the expected code architecture.

    +

    Composer#

    +

    Friendica uses Composer to manage dependencies libraries and the class autoloader both for libraries and namespaced Friendica classes.

    +

    It's a command-line tool that downloads required libraries into the vendor folder and makes any namespaced class in src available through the whole application through boot.php.

    +

    If you want to have git automatically update the dependencies with composer, you can use the post-merge git-hook with a script similar to this one:

    +
    #!/usr/bin/env bash
    +# MIT © Sindre Sorhus - sindresorhus.com
    +# forked by Gianluca Guarini
    +# phponly by Ivo Bathke ;)
    +# modified for Friendica by Tobias Diekershoff
    +changed_files="$(git diff-tree -r --name-only --no-commit-id ORIG_HEAD HEAD)"
    +check_run() {
    +        echo "$changed_files" | grep --quiet "$1" && eval "$2"
    +}
    +# `composer install` if the `composer.lock` file gets changed
    +# to update all the php dependencies
    +check_run composer.lock "bin/composer.phar install --no-dev"
    +
    +

    just place it into .git/hooks/post-merge and make it executable.

    + +

    Coding standards#

    +

    For the sake of consistency between contribution and general code readability, Friendica follows the widespread PSR-2 coding standards to the exception to a few rules. +Here's a few primers if you are new to Friendica or to the PSR-2 coding standards:

    +
      +
    • Indentation is tabs, period (not PSR-2).
    • +
    • By default, strings are enclosed in single quotes, but feel free to use double quotes if it makes more sense (SQL queries, adding tabs and line feeds).
    • +
    • Operators are wrapped by spaces, e.g. $var === true, $var = 1 + 2 and 'string' . $concat . 'enation'
    • +
    • Braces are mandatory in conditions
    • +
    • Boolean operators are && and || for PHP conditions, AND and OR for SQL queries
    • +
    • No closing PHP tag
    • +
    • No trailing spaces
    • +
    • Array declarations use the new square brackets syntax
    • +
    • Quoting style is single quotes by default, except for needed string interpolation, SQL query strings by convention and comments that should stay in natural language.
    • +
    +

    Don't worry, you don't have to know by heart the PSR-2 coding standards to start contributing to Friendica. +There are a few tools you can use to check or fix your files before you commit.

    +

    For documentation, we use the standard of one sentence per line for the md files in the /doc and /doc/$lng subdirectories.

    +

    Check with PHP Code Sniffer#

    +

    This tool checks your files against a variety of coding standards, including PSR-2, and outputs a report of all the standard violations. +You can simply install it through PEAR: pear install PHP_CodeSniffer +Once it is installed and available in your PATH, here's the command to run before committing your work:

    +
    $> phpcs --standard=ruleset.xml <file or directory>
    +
    +

    The output is a list of all the coding standards violations that you should fix before committing your work. +Additionally, phpcs integrates with a few IDEs (Eclipse, Netbeans, PHPStorm...) so that you don't have to fiddle with the command line.

    +

    Fix with PHP Code Beautifier and Fixer (phpbcf) included in PHP Code Sniffer#

    +

    If you're getting a massive list of standards violations when running phpcs, it can be annoying to fix all the violations by hand. +Thankfully, PHP Code Sniffer is shipped with an automatic code fixer that can take care of the tedious task for you. +Here's the command to automatically fix the files you created/modified:

    +
    $> phpcbf --standard=ruleset.xml <file or directory>
    +
    +

    If the command-line tools diff and patch are unavailable for you, phpcbf can use slightly slower PHP equivalents by using the --no-patch argument.

    +

    Code documentation#

    +

    If you are interested in having the documentation of the Friendica code outside the code files, you can use Doxygen to generate it. +The configuration file for Doxygen is located in the base directory of the project sources. +Run

    +
    $> doxygen Doxyfile
    +
    +

    to generate the files which will be located in the doc/html subdirectory in the Friendica directory. +You can browse these files with any browser.

    +

    If you find missing documentation, don't hesitate to contact us and write it down to enhance the code documentation.

    +

    Issues#

    +

    Have a look at our issue tracker on GitHub!

    +
      +
    • Try to reproduce a bug that needs more inquiries and write down what you find out.
    • +
    • If a bug looks fixed, ask the bug reporters for feedback to find out if the bug can be closed.
    • +
    • Fix a bug if you can. Please make the pull request against the develop branch of the repository.
    • +
    • There is a Junior Job label for issues we think might be a good point to start with. + But you don't have to limit yourself to those issues.
    • +
    +

    Web interface#

    +

    The thing many people want most is a better interface, preferably a responsive Friendica theme. +This is a piece of work! +If you want to get involved here:

    +
      +
    • Look at the first steps that were made (e.g. the clean theme). + Ask us to find out whom to talk to about their experiences.
    • +
    • Talk to design people if you know any.
    • +
    • Let us know about your plans in the dev forum + Do not worry about cross-posting.
    • +
    +

    Client software#

    +

    As Friendica is using a Twitter/GNU Social compatible API any of the clients for those platforms should work with Friendica as well. +Furthermore, there are several client projects, especially for use with Friendica. +If you are interested in improving those clients, please contact the developers of the clients directly.

    + + + +
    + +
    +
    + + + +
    + + + +
    +
    +
    +
    + + + + + + + + \ No newline at end of file diff --git a/develop/en/developer/smarty3-templates/index.html b/develop/en/developer/smarty3-templates/index.html new file mode 100644 index 0000000..3c22e6a --- /dev/null +++ b/develop/en/developer/smarty3-templates/index.html @@ -0,0 +1,3652 @@ + + + + + + + + + + + + + + + + + + + + + + Smarty3 - Friendica documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + +
    + + + + +
    + + +
    + +
    + + + + + + + + + +
    +
    + + + +
    +
    +
    + + + + + + +
    +
    +
    + + + + + + +
    +
    + + + + + + + + + + + + + +

    Friendica Templating Documentation#

    +

    Friendica uses Smarty 3 as PHP templating engine. +The main templates are found in

    +
        /view/templates
    +
    +

    theme authors may overwrite the default templates by putting a files with the same name into the

    +
        /view/themes/$themename/templates
    +
    +

    directory.

    +

    Templates that are only used by addons shall be placed in the

    +
        /addon/$addonname/templates
    +
    +

    directory.

    +

    To render a template use the function getMarkupTemplate to load the template and replaceMacros to replace the macros/variables in the just loaded template file.

    +
    $tpl = Renderer::getMarkupTemplate('install_settings.tpl');
    +$o .= Renderer::replaceMacros($tpl, array( ... ));
    +
    +

    the array consists of an association of an identifier and the value for that identifier, i.e.

    +
        '$title' => $install_title,
    +
    +

    where the value may as well be an array by its own.

    +

    Form Templates#

    +

    To guarantee a consistent look and feel for input forms, i.e. in the settings sections, there are templates for the basic form fields. +They are initialized with an array of data, depending on the style of the field.

    +

    All of these take an array holding the values, e.g. for a one line text input field, which is required and should be used to type email addresses use something along the lines of:

    +
        '$adminmail' => array('adminmail', DI::l10n()->t('Site administrator email address'), $adminmail, DI::l10n()->t('Your account email address must match this in order to use the web admin panel.'), 'required', '', 'email'),
    +
    +

    To evaluate the input value, you can then use the $_POST array, more precisely the $_POST['adminemail'] variable.

    +

    Listed below are the template file names, the general purpose of the template and their field parameters.

    +

    field_checkbox.tpl#

    +

    A checkbox. +If the checkbox is checked its value is 1. +Field parameter:

    +
      +
    1. Name of the checkbox,
    2. +
    3. Label for the checkbox,
    4. +
    5. State checked? if true then the checkbox will be marked as checked,
    6. +
    7. Help text for the checkbox.
    8. +
    +

    field_combobox.tpl#

    +

    A combobox, combining a pull down selection and a textual input field. +Field parameter:

    +
      +
    1. Name of the combobox,
    2. +
    3. Label for the combobox,
    4. +
    5. Current value of the variable,
    6. +
    7. Help text for the combobox,
    8. +
    9. Array holding the possible values for the textual input,
    10. +
    11. Array holding the possible values for the pull down selection.
    12. +
    +

    field_custom.tpl#

    +

    A customizable template to include a custom element in the form with the usual surroundings, +Field parameter:

    +
      +
    1. Name of the field,
    2. +
    3. Label for the field,
    4. +
    5. the field,
    6. +
    7. Help text for the field.
    8. +
    +

    field_input.tpl#

    +

    A single line input field for any type of input. +Field parameter:

    +
      +
    1. Name of the field,
    2. +
    3. Label for the input box,
    4. +
    5. Current value of the variable,
    6. +
    7. Help text for the input box,
    8. +
    9. Should be set to the translation of "Required" to mark this field as required,
    10. +
    11. if set to "autofocus" modern browser will put the cursor into this box once the page is loaded,
    12. +
    13. if set, it will be used for the input type, default is text (possible types: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input#%3Cinput%3E_types).
    14. +
    +

    field_intcheckbox.tpl#

    +

    A checkbox (see above) but you can define the value of it. +Field parameter:

    +
      +
    1. Name of the checkbox,
    2. +
    3. Label for the checkbox,
    4. +
    5. State checked? if true then the checkbox will be marked as checked,
    6. +
    7. Value of the checkbox,
    8. +
    9. Help text for the checkbox.
    10. +
    +

    field_openid.tpl#

    +

    An input box (see above) but prepared for special CSS styling for openID input. +Field parameter:

    +
      +
    1. Name of the field,
    2. +
    3. Label for the input box,
    4. +
    5. Current value of the variable,
    6. +
    7. Help text for the input field.
    8. +
    +

    field_password.tpl#

    +

    A single line input field (see above) for textual input. +The characters typed in will not be shown by the browser. +Field parameter:

    +
      +
    1. Name of the field,
    2. +
    3. Label for the field,
    4. +
    5. Value for the field, e.g. the old password,
    6. +
    7. Help text for the input field,
    8. +
    9. Should be set to the translation of "Required" to mark this field as required,
    10. +
    11. if set to "autofocus" modern browser will put the cursor automatically into this input field.
    12. +
    +

    field_radio.tpl#

    +

    A radio button. +Field parameter:

    +
      +
    1. Name of the radio button,
    2. +
    3. Label for the radio button,
    4. +
    5. Current value of the variable,
    6. +
    7. Help text for the button,
    8. +
    9. if set, the radio button will be checked.
    10. +
    +

    field_richtext.tpl#

    +

    A multi-line input field for rich textual content. +Field parameter:

    +
      +
    1. Name of the input field,
    2. +
    3. Label for the input box,
    4. +
    5. Current text for the box,
    6. +
    7. Help text for the input box.
    8. +
    +

    field_select.tpl#

    +

    A drop-down selection box. +Field parameter:

    +
      +
    1. Name of the field,
    2. +
    3. Label of the selection box,
    4. +
    5. Current selected value,
    6. +
    7. Help text for the selection box,
    8. +
    9. Array holding the possible values of the selection drop-down.
    10. +
    +

    field_select_raw.tpl#

    +

    A drop-down selection box (see above) but you have to prepare the values yourself. +Field parameter:

    +
      +
    1. Name of the field,
    2. +
    3. Label of the selection box,
    4. +
    5. Current selected value,
    6. +
    7. Help text for the selection box,
    8. +
    9. Possible values of the selection drop-down.
    10. +
    +

    field_textarea.tpl#

    +

    A multi-line input field for (plain) textual content. +Field parameter:

    +
      +
    1. Name of the input field,
    2. +
    3. Label for the input box,
    4. +
    5. Current text for the box,
    6. +
    7. Help text for the input box,
    8. +
    9. Should be set to the translation of "Required" to mark this field as required.
    10. +
    + + +
    + +
    +
    + + + +
    + + + +
    +
    +
    +
    + + + + + + + + \ No newline at end of file diff --git a/develop/en/developer/tests/index.html b/develop/en/developer/tests/index.html new file mode 100644 index 0000000..340dfdf --- /dev/null +++ b/develop/en/developer/tests/index.html @@ -0,0 +1,3280 @@ + + + + + + + + + + + + + + + + + + + + + + Tests - Friendica documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + +
    + + + + +
    + + +
    + +
    + + + + + + + + + +
    +
    + + + +
    +
    +
    + + + + + + +
    +
    +
    + + + +
    +
    +
    + + + +
    +
    +
    + + +
    +
    + + + + + + + + + + + + + +

    Tests#

    +

    You can run unit tests with PHPUnit:

    +
    phpunit
    +
    +

    Some tests require access to a MySQL database. +You can specify the database credentials in environment variables:

    +
    USER=database_user PASS=database_password DB=database_name phpunit
    +
    +

    Warning: This will empty all the tables! Never use this on a production database.

    + + +
    + +
    +
    + + + +
    + + + +
    +
    +
    +
    + + + + + + + + \ No newline at end of file diff --git a/develop/en/developer/themes/index.html b/develop/en/developer/themes/index.html new file mode 100644 index 0000000..7db82c9 --- /dev/null +++ b/develop/en/developer/themes/index.html @@ -0,0 +1,3757 @@ + + + + + + + + + + + + + + + + + + + + + + Themes - Friendica documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + +
    + + + + +
    + + +
    + +
    + + + + + + + + + +
    +
    + + + +
    +
    +
    + + + + + + +
    +
    +
    + + + +
    +
    +
    + + + +
    +
    +
    + + +
    +
    + + + + + + + + + + + + + +

    Themes#

    +

    To change the look of friendica you have to touch the themes. +The current default theme is Vier but there are numerous others. +Have a look at github.com/bkil/friendica-themes for an overview of the existing themes. +In case none of them suits your needs, there are several ways to change a theme.

    +

    So, how to work on the UI of friendica.

    +

    You can either directly edit an existing theme. +But you might lose your changes when the theme is updated by the friendica team.

    +

    If you are almost happy with an existing theme, the easiest way to cover your needs is to create a new theme, inheritance most of the properties of the parent theme and change just minor stuff. +The below for a more detailed description of theme heritage.

    +

    Some themes also allow users to select variants of the theme. +Those theme variants most often contain an additional CSS file to override some styling of the default theme values. +From the themes in the main repository duepunto zero and vier are using these methods for variations. +Quattro is using a slightly different approach.

    +

    Third you can start your theme from scratch. +Which is the most complex way to change friendicas look. +But it leaves you the most freedom. +So below for a detailed description and the meaning of some special files.

    +

    Styling#

    +

    If you want to change the styling of a theme, have a look at the themes CSS file. +In most cases, you can find these in

    +
    /view/theme/**your-theme-name**/style.css
    +
    +

    sometimes, there is also a file called style.php in the theme directory. +This is only needed if the theme allows the user to change certain things of the theme dynamically. +Say the font size or set a background image.

    +

    Templates#

    +

    If you want to change the structure of the theme, you need to change the templates used by the theme. +Friendica themes are using SMARTY3 for templating. +The default template can be found in

    +
    /view/templates
    +
    +

    if you want to override any template within your theme create your version of the template in

    +
    /view/theme/**your-theme-name**/templates
    +
    +

    any template that exists there will be used instead of the default one.

    +

    Javascript#

    +

    The same rule applies to the JavaScript files found in

    +
    /js
    +
    +

    they will be overwritten by files in

    +
    /view/theme/**your-theme-name**/js.
    +
    +

    Expand an existing Theme#

    +

    Theme Variations#

    +

    Many themes are more theme families than only one theme. +duepunto zero and vier allow easily to add new theme variation. +We will go through the process of creating a new variation for duepunto zero. +The same (well almost, some names change) procedure applies to the vier theme. +And similar steps are needed for quattro but this theme is using lesscss to maintain the CSS files.

    +

    In

    +
    /view/theme/duepuntozero/deriv
    +
    +

    you find a couple of CSS files that define color derivations from the duepunto theme. +These resemble some now as unsupported marked themes, that were inherited by the duepunto theme. +Darkzero and Easter Bunny for example.

    +

    The selection of the colorset is done in a combination of a template for a new form in the settings and some functions in the theme.php file. +The template (theme_settings.tpl)

    +
    {{ '{{include file="field_select.tpl" field=$colorset}}' }}
    +<div class="settings-submit-wrapper">
    +    {{ '<input type="submit" value="{{$submit}}" class="settings-submit" name="duepuntozero-settings-submit" />' }}
    +</div>
    +
    +

    defines a formular consisting of a select pull-down which contains all available variants and s submit button. +See the documentation about SMARTY3 templates for a summary of friendica specific blocks other than the select element. +But we don't really need to change anything at the template itself.

    +

    The template alone won't work though. +You make friendica aware of its existence and tell it how to use the template file, by defining a config.php file. +It needs to define at least the following functions

    +
      +
    • theme_content
    • +
    • theme_post
    • +
    +

    and may also define functions for the admin interface

    +
      +
    • theme_admin
    • +
    • theme_admin_post.
    • +
    +

    theme_content and theme_admin are used to make the form available in the settings, respectively the admin panel. +The _post functions handle the processing of the send-form, in this case they save to selected variant in friendicas database.

    +

    To make your own variation appear in the menu, all you need to do is to create a new CSS file in the deriv-directory and include it in the array in the config.php:

    +
    $colorset = array(
    +    'default'=>DI::l10n()->t('default'),
    +    'greenzero'=>DI::l10n()->t('greenzero'),
    +    'purplezero'=>DI::l10n()->t('purplezero'),
    +    'easterbunny'=>DI::l10n()->t('easterbunny'),
    +    'darkzero'=>DI::l10n()->t('darkzero'),
    +    'comix'=>DI::l10n()->t('comix'),
    +    'slackr'=>DI::l10n()->t('slackr'),
    +);
    + ```
    +
    +the 1st part of the line is the name of the CSS file (without the .css) the 2nd part is the common name of the variant.
    +Calling the DI::l10n()->t() function with the common name makes the string translatable.
    +The selected 1st part will be saved in the database by the theme_post function.
    +
    +```php
    +<?php
    +function theme_post(App $a){
    +    // non-local users shall not pass
    +    if (! local_user()) {
    +        return;
    +    }
    +    // if the one specific submit button was pressed then proceed
    +    if (isset($_POST['duepuntozero-settings-submit'])){
    +        // and save the selection key into the personal config of the user
    +        DI::pConfig()->set(local_user(), 'duepuntozero', 'colorset', $_POST['duepuntozero_colorset']);
    +    }
    +}
    +
    +

    Now that this information is set in the database, what should friendica do with it? +For this, have a look at the theme.php file of the duepunto zero. +There you'll find something alike

    +
    <?php
    +$colorset = DI::pConfig()->get( local_user(), 'duepuntozero','colorset');
    +if (!$colorset)
    +    $colorset = DI::config()->get('duepuntozero', 'colorset');
    +if ($colorset) {
    +    if ($colorset == 'greenzero')
    +        DI::page()['htmlhead'] .= '<link rel="stylesheet" href="view/theme/duepuntozero/deriv/greenzero.css" type="text/css" media="screen" />'."\n";
    +    /* some more variants */
    +}
    +
    +

    which tells friendica to get the personal config of a user. +Check if it is set and if not look for the global config. +And finally if a config for the colorset was found, apply it by adding a link to the CSS file into the HTML header of the page. +So you'll just need to add an if selection, fitting your variant keyword and link to the CSS file of it.

    +

    Done. +Now you can use the variant on your system. +But remember once the theme.php or the config.php you have to re-add your variant to them. +If you think your color variation could be beneficial for other friendica users as well, feel free to generate a pull request at GitHub, so we can include your work into the repository.

    +

    Inheritance#

    +

    Say, you like the duepuntozero, but you want to have the content of the outer columns left and right exchanged. +That would be not a color variation as shown above. +Instead, we will create a new theme, duepuntozero_lr, inherit the properties of duepuntozero and make small changes to the underlying php files.

    +

    So create a directory called duepunto_lr and create a file called theme.php with your favorite text editor. +The content of this file should be something like

    +
    <?php
    +/* meta information for the theme, see below */
    +use Friendica\App;
    +
    +function duepuntozero_lr_init(App $a) {
    +    $a->setThemeInfoValue('extends', 'duepuntozero');
    +
    +    $a->set_template_engine('smarty3');
    +    /* and more stuff e.g. the JavaScript function for the header */
    +}
    +
    +

    Next take the default.php file found in the /view directory and exchange the aside and right_aside elements. +So the central part of the file now looks like this:

    +
    <body>
    +    <?php if(!empty($page['nav'])) echo $page['nav']; ?>
    +    <aside><?php if(!empty($page['right_aside'])) echo $page['right_aside']; ?></aside>
    +    <section><?php if(!empty($page['content'])) echo $page['content']; ?>
    +            <div id="page-footer"></div>
    +    </section>
    +    <right_aside><?php if(!empty($page['aside'])) echo $page['aside']; ?></right_aside>
    +    <footer><?php if(!empty($page['footer'])) echo $page['footer']; ?></footer>
    +</body>
    +
    +

    Finally, we need a style.css file, inheriting the definitions from the parent theme and containing out changes for the new theme. +Note:You need to create the style.css and at lest import the base CSS file from the parent theme.

    +
    @import url('../duepuntozero/style.css');
    +
    +

    Done. +But I agree it is not really useful at this state. +Nevertheless, to use it, you just need to activate in the admin panel. +That done, you can select it in the settings like any other activated theme.

    +

    Creating a Theme from Scratch#

    +

    Keep patient. +Basically what you have to do is identify which template you have to change, so it looks more like what you want. +Adopt the CSS of the theme accordingly. +And iterate the process until you have the theme the way you want it.

    +

    Use the source Luke. and don't hesitate to ask in @developers or @helpers.

    +

    Special Files#

    +

    unsupported#

    +

    If a file with this name (which might be empty) exists in the theme directory, the theme is marked as unsupported. +An unsupported theme may not be selected by a user in the settings. +Users who are already using it won't notice anything.

    +

    README(.md)#

    +

    The contents of this file, with or without the .md which indicates Markdown syntax, will be displayed at most repository hosting services and in the theme page within the admin panel of friendica.

    +

    This file should contain information you want to let others know about your theme.

    +

    screenshot.[png|jpg]#

    +

    If you want to have a preview image of your theme displayed in the settings you should take a screenshot and save it with this name. +Supported formats are PNG and JPEG.

    +

    theme.php#

    +

    This is the main definition file of the theme. +In the header of that file, some meta information is stored. +For example, have a look at the theme.php of the quattro theme:

    +
    <?php
    +/**
    + * Name: Quattro
    + * Version: 0.6
    + * Author: Fabio <https://kirgroup.com/profile/fabrixxm>
    + * Maintainer: Fabio <https://kirgroup.com/profile/fabrixxm>
    + * Maintainer: Tobias <https://f.diekershoff.de/profile/tobias>
    + */
    + ```
    +
    +You see the definition of the theme's name, it's version and the initial author of the theme.
    +These three pieces of information should be listed.
    +If the original author is no longer working on the theme, but a maintainer has taken over, the maintainer should be listed as well.
    +The information from the theme header will be displayed in the admin panel.
    +
    +The first thing in file is to import the `App` class from `\Friendica\` namespace.
    +
    +```php
    +use Friendica\App;
    +
    +

    This will make our job a little easier, as we don't have to specify the full name every time we need to use the App class.

    +

    The next crucial part of the theme.php file is a definition of an init function. +The name of the function is _init. +So in the case of quattro it is

    +
    <?php
    +function quattro_init(App $a) {
    +  $a->theme_info = array();
    +  $a->set_template_engine('smarty3');
    +}
    +
    +

    Here we have set the basic theme information, in this case they are empty. +But the array needs to be set. +And we have set the template engine that should be used by friendica for this theme. +At the moment you should use the smarty3 engine. +There once was a friendica specific templating engine as well but that is not used anymore. +If you like to use another templating engine, please implement it.

    +

    When you want to inherit stuff from another theme you have to announce this in the theme_info:

    +
    $a->setThemeInfoValue('extends', 'duepuntozero');
    +
    +

    which declares duepuntozero as parent of the theme.

    +

    If you want to add something to the HTML header of the theme, one way to do so is by adding it to the theme.php file. +To do so, add something alike

    +
    DI::page()['htmlhead'] .= <<< EOT
    +/* stuff you want to add to the header */
    +EOT;
    +
    +

    The $a variable holds the friendica application. +So you can access the properties of this friendica session from the theme.php file as well.

    +

    default.php#

    +

    This file covers the structure of the underlying HTML layout. +The default file is in

    +
    /view/default.php
    +
    +

    if you want to change it, say adding a 4th column for banners of your favourite FLOSS projects, place a new default.php file in your theme directory. +As with the theme.php file, you can use the properties of the $a variable with holds the friendica application to decide what content is displayed.

    + + +
    + +
    +
    + + + +
    + + + +
    +
    +
    +
    + + + + + + + + \ No newline at end of file diff --git a/develop/en/developer/translations/index.html b/develop/en/developer/translations/index.html new file mode 100644 index 0000000..d35666c --- /dev/null +++ b/develop/en/developer/translations/index.html @@ -0,0 +1,3540 @@ + + + + + + + + + + + + + + + + + + + + + + Translations - Friendica documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + +
    + + + + +
    + + +
    + +
    + + + + + + + + + +
    +
    + + + +
    +
    +
    + + + + + + +
    +
    +
    + + + +
    +
    +
    + + + +
    +
    +
    + + +
    +
    + + + + + + + + + + + + + +

    Friendica translations#

    +

    Overview#

    +

    The Friendica translation process is based on gettext PO files.

    +

    Basic workflow: +1. xgettext is used to collect translation strings across the project in the authoritative PO file located in view/lang/C/messages.po. +2. This file makes translations strings available at the Transifex Friendica page. +3. The translation itself is done at Transifex by volunteers. +4. The resulting PO files by languages are manually updated in view/lang/<language>/messages.po. +5. PO files are converted to PHP arrays in view/lang/<language>/strings.php that are ultimately used by Friendica to display the translations.

    +

    Translate Friendica in your favorite language#

    +

    Thank you for your interest in improving Friendica's translation! +Please register a free Transifex account and ask over at the Transifex Friendica page to join the translation team for your favorite language.

    +

    As a rule of thumb, we add support for a language in Friendica when at least 50% of the strings have been translated to avoid a scattered experience. +For addons, we add support for a language when if we already support the language in Friendica.

    +

    Add new translation strings#

    +

    Core#

    +

    Once you have added new translation strings in your code changes, please run bin/run_xgettext.sh from the base Friendica directory and commit the updated view/lang/C/messages.po to your branch.

    +

    Addon#

    +

    If you have the friendica-addons repository in the addon directory of your Friendica cloned repository, just run bin/run_xgettext.sh -a <addon_name> from the base Friendica directory.

    +

    Otherwise:

    +
    cd /path/to/friendica-addons/<addon_name>
    +/path/to/friendica/bin/run_xgettext.sh -s
    +
    +

    In either case, you need to commit the updated <addon_name>/lang/C/messages.po to your working branch.

    +

    Update translations from Transifex#

    +

    Please download the Transifex file "for use" in view/lang/<language>/messages.po.

    +

    Then run bin/console po2php view/lang/<language>/messages.po to update the related strings.php file and commit both files to your working branch.

    +

    Using the Transifex client#

    +

    Transifex has a client program which allows you to sync files between your cloned Friendica repository and Transifex. +Help for the client can be found at the Transifex Help Center. +Here we will only cover basic usage.

    +

    After installation of the client, you should have a tx command available on your system. +To use it, first create a configuration file with your credentials. +On Linux this file should be placed into your home directory ~/.transifexrc. +The content of the file should be something like the following:

    +
    [https://www.transifex.com]
    +username = user
    +token =
    +password = p@ssw0rd
    +hostname = https://www.transifex.com
    +
    +

    Since Friendica version 3.5.1 we ship configuration files for the Transifex client in the core repository and the addon repository in .tx/config. +To update the PO files after you have translated strings of e.g. Esperanto on the Transifex website you can use tx to download the updated PO-file in the right location.

    +
    tx pull -l eo
    +
    +

    Then run bin/console po2php view/lang/<language>/messages.po to update the related strings.php file and commit both files to your working branch.

    +

    Translation functions usage#

    +

    Basic usage#

    +
      +
    • Friendica\DI::l10n()->t('Label') => Label
    • +
    • Friendica\DI::l10n()->t('Label %s', 'test') => Label test
    • +
    +

    Plural#

    +
      +
    • Friendica\DI::l10n()->tt('Label', 'Labels', 1) => Label
    • +
    • Friendica\DI::l10n()->tt('Label', 'Labels', 3) => Labels
    • +
    • Friendica\DI::l10n()->tt('%d Label', '%d Labels', 1) => 1 Label
    • +
    • Friendica\DI::l10n()->tt('%d Label', '%d Labels', 3) => 3 Labels
    • +
    • Friendica\DI::l10n()->tt('%d Label', 'Labels %2%s %3%s', 1, 'test', 'test2') => Label test test2
    • +
    • Friendica\DI::l10n()->tt('%d Label', 'Labels %2%s %3%s', 3, 'test', 'test2') => Labels test test2
    • +
    + + +
    + +
    +
    + + + +
    + + + +
    +
    +
    +
    + + + + + + + + \ No newline at end of file diff --git a/develop/en/developer/vagrant/index.html b/develop/en/developer/vagrant/index.html new file mode 100644 index 0000000..63dc2f2 --- /dev/null +++ b/develop/en/developer/vagrant/index.html @@ -0,0 +1,3396 @@ + + + + + + + + + + + + + + + + + + + + + + Vagrant - Friendica documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + +
    + + + + +
    + + +
    + +
    + + + + + + + + + +
    +
    + + + +
    +
    +
    + + + + + + +
    +
    +
    + + + +
    +
    +
    + + + +
    +
    +
    + + +
    +
    + + + + + + + + + + + + + +

    Vagrant for Friendica Developers#

    +

    Getting started#

    +

    Vagrant is a virtualization solution for developers. +No need to set up a webserver, database etc. before actually starting. +Vagrant creates a virtual machine for you that you can just run inside VirtualBox and start to work directly on Friendica.

    +

    It brings a Debian Bullseye with PHP 7.4 and MariaDB 10.5.11.

    +

    What you need to do:

    +
      +
    1. Install VirtualBox and vagrant. +Please use an up-to-date vagrant version from https://www.vagrantup.com/downloads.html.
    2. +
    3. Git clone your Friendica repository. +Inside, you'll find a Vagrantfile and some scripts in the bin/dev folder. +Pull the PHP requirements with bin/composer install.
    4. +
    5. Run vagrant up from inside the friendica clone. +This will start the virtual machine. +Be patient: When it runs for the first time, it downloads a Debian Server image and installs Friendica.
    6. +
    7. Run vagrant ssh to log into the virtual machine to log in to the VM in case you need to debug something on the server.
    8. +
    9. Open you test installation in a browser. +Go to friendica.local (or 192.168.22.10). +friendica.local is using a self-signed TLS certificate, so you will need to add an exception to trust the certificate the first time you are visiting the page. +The mysql database is called "friendica", the mysql user and password both are "friendica".
    10. +
    11. Work on Friendica's code in your git clone on your machine (not in the VM). +Your local working directory is set up as a shared directory with the VM (/vagrant).
    12. +
    13. Check the changes in your browser in the VM. +Find the Friendica log file /vagrant/logfile.out on the VM or in the logfile.out in you local Friendica directory.
    14. +
    15. Commit and push your changes directly back to GitHub.
    16. +
    +

    If you want to stop vagrant after finishing your work, run the following command

    +
    vagrant halt
    +
    +

    in the development directory. +This will not delete the virtual machine. +9. To ultimately delete the virtual machine run

    +
    vagrant destroy
    +rm /vagrant/config/local.config.php
    +
    +

    to make sure that you can start from scratch with another "vagrant up".

    +

    Default User Accounts#

    +

    By default, the provision script will set up two user accounts.

    +
      +
    • admin, password admin
    • +
    • friendica, password friendica
    • +
    +

    Trouble Shooting#

    +

    If you see a version mismatch for the VirtualBox Guest Additions between host and guest during the initial setup of the Vagrant VM, you will need to install an addon to Vagrant (ref. Stack Overflow). +Stop the Vagrant VM and run the following command:

    +
    vagrant plugin install vagrant-vbguest
    +
    +

    On the next Vagrant up, the version problem should be fixed.

    +

    If friendica.local is not resolved, you may need to add an entry to the /etc/hosts file (or similar configuration depending on the OS you are using).

    +

    For further documentation of vagrant, please see the vagrantdocs.

    + + +
    + +
    +
    + + + +
    + + + +
    +
    +
    +
    + + + + + + + + \ No newline at end of file diff --git a/develop/en/html/index.php b/develop/en/html/index.php new file mode 100644 index 0000000..ab83759 --- /dev/null +++ b/develop/en/html/index.php @@ -0,0 +1,16 @@ + + + + $Projectname Doxygen API Documentation + + +

    $Projectname Doxygen API Documentation not rendered

    + +To get the Doxygen API Documentation you must render it with the program Doxygen (included in most distributions). +
    +$ doxygen Doxyfile
    +
    +
    +back + + diff --git a/develop/en/index.html b/develop/en/index.html new file mode 100644 index 0000000..f1a70f6 --- /dev/null +++ b/develop/en/index.html @@ -0,0 +1,3418 @@ + + + + + + + + + + + + + + + + + + + + + + Home - Friendica documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + +
    + + + + +
    + + +
    + +
    + + + + + + + + + +
    +
    + + + +
    +
    +
    + + + + + + +
    +
    +
    + + + +
    +
    +
    + + + +
    +
    +
    + + +
    +
    + + + + + + + + + + + +

    Friendica Documentation and Resources#

    +

    User Manual#

    + +

    Admin Manual#

    + +

    Developer Manual#

    + +

    External Resources#

    + + + +
    + +
    +
    + + + +
    + + + +
    +
    +
    +
    + + + + + + + + \ No newline at end of file diff --git a/develop/en/spec/api/entities/index.html b/develop/en/spec/api/entities/index.html new file mode 100644 index 0000000..d1acc83 --- /dev/null +++ b/develop/en/spec/api/entities/index.html @@ -0,0 +1,4926 @@ + + + + + + + + + + + + + + + + + + + + + + Entities - Friendica documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + +
    + + + + +
    + + +
    + +
    + + + + + + + + + +
    +
    + + + +
    +
    +
    + + + + + + +
    +
    +
    + + + +
    +
    +
    + + + +
    +
    +
    + + +
    +
    + + + + + + + + + + + + + +

    Friendica API entities#

    +

    Activities#

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    AttributeTypeNullable
    likeList of [Contacts](./entities.md#contact)No
    dislikeList of [Contacts](./entities.md#contact)No
    attendyesList of [Contacts](./entities.md#contact)No
    attendnoList of [Contacts](./entities.md#contact)No
    attendmaybeList of [Contacts](./entities.md#contact)No
    + +

    Attachment#

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    AttributeTypeNullable
    urlString (URL)No
    mimetypeStringNo
    sizeInteger (bytes)No
    + +

    Contact#

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    AttributeTypeNullable
    idIntegerNo
    id_strStringNo
    nameStringNo
    screen_nameStringNo
    locationStringNo
    descriptionStringNo
    profile_image_urlString (URL)No
    profile_image_url_httpsString (URL)No
    profile_image_url_profile_sizeString (URL)No
    profile_image_url_largeString (URL)No
    urlString (URL)No
    protectedBooleanNo
    followers_countIntegerNo
    friends_countIntegerNo
    listed_countIntegerNo
    favourites_countIntegerNo
    statuses_countIntegerNo
    created_atString (Date)
    +Ex: Wed May 23 06:01:13 +0000 2007 +
    No
    utc_offsetIntegerNo
    time_zoneStringNo
    geo_enabledBooleanNo
    verifiedBooleanNo
    langStringNo
    contributors_enabledBooleanNo
    is_translatorBooleanNo
    is_translation_enabledBooleanNo
    followingBooleanNo
    follow_request_sentBooleanNo
    statusnet_blockingBooleanNo
    notificationsBooleanNo
    statusnet_profile_urlString (URL)No
    uidIntegerNo
    cidIntegerNo
    pidIntegerNo
    selfIntegerNo
    networkStringNo
    + +

    Entities#

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    AttributeTypeNullable
    hashtagsList of [Hashtags](./entities.md#hashtag)No
    symbolsList of [Symbols](./entities.md#symbol)No
    urlsList of [URLs](./entities.md#url)No
    user_mentionsList of [User mentions](./entities.md#user+mention)No
    mediaList of [Medias](./entities.md#media)No
    + +

    Event#

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    AttributeTypeDescription
    idInteger
    uidIntegerOwner User Id
    cidIntegerTarget Contact Id
    uriStringItem unique URI string
    nameString (Plaintext)Title
    descString (HTML)Description
    startTimeString (UTC YYYY-MM-DD HH:II:SS))
    endTimeString (UTC YYYY-MM-DD HH:II:SS))Optional (null date is 0001-01-01 00:00:00
    typeString (event or birthday)
    nofinishBooleanOngoing event
    placeStringOptional. Location.
    ignoreBoolean???
    allow_cidString (angle-brackets escaped integers)Optional. List of allowed contact ids
    allow_gidString (angle-brackets escaped integers)Optional. List of allowed group ids
    deny_cidString (angle-brackets escaped integers)Optional. List of disallowed contact ids
    deny_gidString (angle-brackets escaped integers)Optional. List of disallowed group ids
    + +

    Hashtag#

    +

    Unused

    +

    Item#

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    AttributeTypeNullable
    textString (Plaintext)No
    truncatedBooleanNo
    created_atString (Date)
    +Ex: Wed May 23 06:01:13 +0000 2007 +
    No
    in_reply_to_status_idIntegerNo
    in_reply_to_status_id_strStringNo
    sourceStringNo
    idIntegerNo
    id_strStringNo
    in_reply_to_user_idIntegerNo
    in_reply_to_user_id_strStringNo
    in_reply_to_screen_nameStringNo
    geoStringYes
    favoritedBooleanNo
    user[Contact](./entities.md#contact)No
    friendica_author[Contact](./entities.md#contact)No
    friendica_owner + +[Contact](./entities.md#contact)No
    friendica_privateBooleanNo
    statusnet_htmlString (HTML)No
    statusnet_conversation_idIntegerNo
    external_urlString (URL)No
    friendica_activities[Activities](./entities.md#activities)No
    friendica_titleString (Plaintext)No
    friendica_htmlString (HTML)No
    attachmentsList of [Attachments](./entities.md#attachment)Yes
    entities[Entities](./entities.md#entities)Yes
    + +

    Media#

    +

    Identical to the Twitter Media Object.

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    AttributeTypeNullable
    idIntegerNo
    id_strStringNo
    indicesList of IntegerNo
    media_urlString (URL)No
    media_url_httpsString (URL)No
    urlString (URL)No
    display_urlString (URL)No
    expanded_urlString (URL)No
    ext_alt_textStringNo
    typeStringNo
    sizes[Sizes](./entities.md#sizes)No
    + +

    Notification#

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    AttributeTypeDescription
    idInteger
    hashString
    typeInteger
      +
    • 1: Inbound follow request
    • +
    • 2: Outbound follow request confirmation
    • +
    • 4: Wall-to-wall post
    • +
    • 8: Reply
    • +
    • 16: Private message
    • +
    • 32: Friend suggestion
    • +
    • 64: Unused
    • +
    • 128: Mention
    • +
    • 256: Tag added to a post
    • +
    • 512: Poke
    • +
    • 1024: New post
    • +
    • 16384: System email
    • +
    • 32768: System event
    • +
    nameStringFull name of the contact subject
    urlString (URL)Profile page URL of the contact subject
    photoString (URL)Profile photo URL of the contact subject
    dateString (Date)YYYY-MM-DD hh:mm:ss local server time
    msgString (BBCode)
    uidIntegerOwner User Id
    linkString (URL)Notification URL
    iidIntegerItem Id
    parentIntegerParent Item Id
    seenInteger (Boolean)Whether the notification was read or not.
    verbString (URL)[Activity Streams](http://activitystrea.ms) Verb URL
    seenInteger (Boolean)Whether the notification was read or not.
    otypeEnumSubject type (`item`, `intro` or `mail`)
    name_cacheString (HTML)Full name of the contact subject
    msg_cacheString (Plaintext)Plaintext version of the notification text with a placeholder (`{0}`) for the subject contact's name.
    timestampIntegerUnix timestamp
    date_relStringTime since the note was posted, eg "1 hour ago"
    msg_htmlString (HTML)
    msg_plainString (Plaintext)
    + +

    Photo#

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    AttributeTypeDescription
    idStringResource ID (32 hex chars)
    createdString (Date)Format YYYY-MM-DD HH:MM:SS
    editedString (Date)Format YYYY-MM-DD HH:MM:SS
    titleString
    descString (Plaintext)Picture caption
    albumStringAlbum name
    filenameStringOriginal image filename
    typeStringMIME Type
    heightIntegerImage height in pixels
    widthIntegerImage width in pixels
    profileInteger1 if it is a profile photo
    allow_cidString (ACL)List of contact ids wrapped in angle brackets allowed to access the photo.
    allow_gidString (ACL)List of contact group ids wrapped in angle brackets allowed to access the photo.
    deny_cidString (ACL)List of contact ids wrapped in angle brackets forbidden to access the photo.
    deny_gidString (ACL)List of contact group ids wrapped in angle brackets forbidden to access the photo.
    linkArray of Strings (URL) +URLs to the different scales indexed by scale number if no specific scale was requested. +Mutually exclusive with data datasize. +
    datasizeInteger +Picture size in bytes if a single scale was requested. +Mutually exclusive with link. +
    dataString +Base64-encoded image data if a single scale was requested. +Mutually exclusive with link. +
    friendica_activities[Activities](./entities.md#activities)
    friendica_commentsList of [Items](./entities.md#item)
    rights_mismatchBooleanTrue if the ACL differs between the picture and the associated item.
    + +

    Photo List Item#

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    AttributeTypeDescription
    idStringResource ID (32 hex chars)
    albumStringAlbum name
    filenameStringOriginal image filename
    typeStringMIME Type
    createdString (Date)Format YYYY-MM-DD HH:MM:SS
    editedString (Date)Format YYYY-MM-DD HH:MM:SS
    descString (Plaintext)Picture caption
    thumbString (URL)URL of the smallest scale version of the picture.
    + +

    Private message#

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    AttributeTypeDescription
    idInteger
    sender_idIntegerSender Contact Id
    textStringCan be HTML or plaintext depending on the API call parameter `getText`.
    recipient_idIntegerRecipient Contact Id
    created_atString (Date)Ex: Wed May 23 06:01:13 +0000 2007
    sender_screen_nameString
    recipient_screen_nameString
    sender[Contact](./entities.md#contact)
    recipient[Contact](./entities.md#contact)
    titleStringEmpty if the API call parameter `getText` is empty or absent.
    friendica_seenInteger (Boolean)Whether the private message has been read or not.
    friendica_parent_uriString
    + +

    Profile#

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    AttributeTypeDescription
    profile_idInteger
    profile_nameString
    is_defaultBoolean
    hide_friendsBooleanWhether the user chose to hide their contact list on their profile.
    profile_photoString (URL)Largest size profile picture URL.
    profile_thumbString (URL)Smallest size profile picture URL.
    publishBooleanWhether the user chose to publish their profile in the local directory.
    net_publishBooleanWhether the user chose to publish their profile in the global directory.
    descriptionString
    date_of_birthString
    addressString
    cityString
    regionString
    postal_codeString
    countryString
    public_keywordsStringComma-separated list of words meant to be displayed as hashtags.
    private_keywordsStringComma-separated list of words meant to be used for search only.
    homepageString (URL)
    + +

    Size#

    + + + + + + + + + +
    AttributeTypeNullable
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    AttributeTypeNullable
    wIntegerNo
    hIntegerNo
    resizeEnum (fit, crop)Yes
    + + +## Sizes + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    AttributeTypeNullable
    medium[Size](./entities.md#size)No
    large[Size](./entities.md#size)Yes
    thumb[Size](./entities.md#size)Yes
    small[Size](./entities.md#size)Yes
    + +## Symbol + +Unused + +## URL + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    AttributeTypeNullable
    urlString (URL)No
    expanded_urlString (URL)No
    display_urlString (URL)No
    indicesList of IntegerNo
    + +## User Mention + +Unused + + +
    + +
    +
    + + + +
    + + + +
    +
    +
    +
    + + + + + + + + \ No newline at end of file diff --git a/develop/en/spec/api/friendica/index.html b/develop/en/spec/api/friendica/index.html new file mode 100644 index 0000000..26ebd7f --- /dev/null +++ b/develop/en/spec/api/friendica/index.html @@ -0,0 +1,5418 @@ + + + + + + + + + + + + + + + + + + + + + + Friendica - Friendica documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + +
    + + + + +
    + + +
    + +
    + + + + + + + + + +
    +
    + + + +
    +
    +
    + + + + + + +
    +
    +
    + + + +
    +
    +
    + + + +
    +
    +
    + + +
    +
    + + + + + + + + + + + + + +

    Friendica API#

    +

    Overview#

    +

    Friendica provides the following specific endpoints.

    +

    Authentication is the same as described in Using the APIs.

    +

    Entities#

    +

    These endpoints use the Friendica API entities.

    +

    Endpoints#

    +

    GET api/friendica/events#

    +

    Returns a list of Event entities for the current logged-in user.

    +

    Parameters#

    +
      +
    • since_id: (optional) minimum event id for pagination
    • +
    • count: maximum number of items returned, default 20
    • +
    +

    GET api/externalprofile/show#

    +

    Returns a Contact entity for the provided profile URL.

    +

    Parameters#

    +
      +
    • profileurl: Profile URL
    • +
    +

    GET api/statuses/public_timeline#

    +

    Returns a list of public Items posted on this node. +Equivalent of the local community page.

    +

    Parameters#

    +
      +
    • count: Items per page (default: 20)
    • +
    • page: page number
    • +
    • since_id: minimum id
    • +
    • max_id: maximum id
    • +
    • exclude_replies: don't show replies (default: false)
    • +
    • conversation_id: Shows all statuses of a given conversation.
    • +
    • include_entities: "true" shows entities for pictures and links (Default: false)
    • +
    +

    Unsupported parameters#

    +
      +
    • trim_user
    • +
    +

    GET api/statuses/networkpublic_timeline#

    +

    Returns a list of public Items this node is aware of. +Equivalent of the global community page.

    +

    Parameters#

    +
      +
    • count: Items per page (default: 20)
    • +
    • page: page number
    • +
    • since_id: minimum id
    • +
    • max_id: maximum id
    • +
    • exclude_replies: don't show replies (default: false)
    • +
    • conversation_id: Shows all statuses of a given conversation.
    • +
    • include_entities: "true" shows entities for pictures and links (Default: false)
    • +
    +

    GET api/statuses/replies#

    +

    Parameters#

    +
      +
    • count: Items per page (default: 20)
    • +
    • page: page number
    • +
    • since_id: minimum id
    • +
    • max_id: maximum id
    • +
    • include_entities: "true" shows entities for pictures and links (Default: false)
    • +
    +

    Unsupported parameters#

    +
      +
    • include_rts
    • +
    • trim_user
    • +
    • contributor_details
    • +
    +
    +

    GET api/conversation/show#

    +

    Unofficial Twitter command. It shows all direct answers (excluding the original post) to a given id.

    +

    Parameters#

    +
      +
    • id: id of the post
    • +
    • count: Items per page (default: 20)
    • +
    • page: page number
    • +
    • since_id: minimum id
    • +
    • max_id: maximum id
    • +
    • include_entities: "true" shows entities for pictures and links (Default: false)
    • +
    +

    Unsupported parameters#

    +
      +
    • include_rts
    • +
    • trim_user
    • +
    • contributor_details
    • +
    +

    GET api/statusnet/conversation#

    +

    Alias of api/conversation/show.

    +

    GET api/statusnet/config#

    +

    Returns the public Friendica node configuration.

    +

    GET api/gnusocial/config#

    +

    Alias of api/statusnet/config.

    +

    GET api/statusnet/version#

    +

    Returns a fake static StatusNet protocol version.

    +

    GET api/gnusocial/version#

    +

    Alias of api/statusnet/version.

    +
    +

    POST api/friendica/activity/[verb]#

    +

    Add or remove an activity from an item. +'verb' can be one of:

    +
      +
    • like
    • +
    • dislike
    • +
    • attendyes
    • +
    • attendno
    • +
    • attendmaybe
    • +
    +

    To remove an activity, prepend the verb with "un", eg. "unlike" or "undislike" +Attend verbs disable eachother: that means that if "attendyes" was added to an item, adding "attendno" remove previous "attendyes". +Attend verbs should be used only with event-related items (there is no check at the moment).

    +

    Parameters#

    +
      +
    • id: item id
    • +
    +

    Return values#

    +

    On success: +json:

    +

    "ok"

    +

    xml:

    +

    <ok>true</ok>

    +

    On error: +HTTP 400 BadRequest

    +
    +

    GET api/direct_messages#

    +

    Deprecated Twitter received direct message list endpoint.

    +

    Parameters#

    +
      +
    • count: Items per page (default: 20)
    • +
    • page: page number
    • +
    • since_id: minimum id
    • +
    • max_id: maximum id
    • +
    • getText: Defines the format of the status field. Can be "html" or "plain"
    • +
    • include_entities: "true" shows entities for pictures and links (Default: false)
    • +
    • friendica_verbose: "true" enables different error returns (default: "false")
    • +
    +

    Unsupported parameters#

    +
      +
    • skip_status
    • +
    +

    GET api/direct_messages/all#

    +

    Returns all Private Messages.

    +

    Parameters#

    +
      +
    • count: Items per page (default: 20)
    • +
    • page: page number
    • +
    • since_id: minimum id
    • +
    • max_id: maximum id
    • +
    • getText: Defines the format of the status field. Can be "html" or "plain"
    • +
    • friendica_verbose: "true" enables different error returns (default: "false")
    • +
    +

    GET api/direct_messages/conversation#

    +

    Returns all replies of a single private message conversation. Returns Private Messages

    +

    Parameters#

    +
      +
    • count: Items per page (default: 20)
    • +
    • page: page number
    • +
    • since_id: minimum id
    • +
    • max_id: maximum id
    • +
    • getText: Defines the format of the status field. Can be "html" or "plain"
    • +
    • uri: URI of the conversation
    • +
    • friendica_verbose: "true" enables different error returns (default: "false")
    • +
    +

    GET api/direct_messages/sent#

    +

    Deprecated Twitter sent direct message list endpoint. Returns Private Messages.

    +

    Parameters#

    +
      +
    • count: Items per page (default: 20)
    • +
    • page: page number
    • +
    • since_id: minimum id
    • +
    • max_id: maximum id
    • +
    • getText: Defines the format of the status field. Can be "html" or "plain"
    • +
    • include_entities: "true" shows entities for pictures and links (Default: false)
    • +
    • friendica_verbose: "true" enables different error returns (default: "false")
    • +
    +

    POST api/direct_messages/new#

    +

    Deprecated Twitter direct message submission endpoint.

    +

    Parameters#

    +
      +
    • user_id: id of the user
    • +
    • screen_name: screen name (for technical reasons, this value is not unique!)
    • +
    • text: The message
    • +
    • replyto: ID of the replied direct message
    • +
    • title: Title of the direct message
    • +
    +

    POST api/direct_messages/destroy#

    +

    Deprecated Twitter direct message deletion endpoint.

    +

    Parameters#

    +
      +
    • id: id of the message to be deleted
    • +
    • include_entities: optional, currently not yet implemented
    • +
    • friendica_parenturi: optional, can be used for increased safety to delete only intended messages
    • +
    • friendica_verbose: "true" enables different error returns (default: "false")
    • +
    +

    Return values#

    +

    On success:

    +
      +
    • JSON return as defined for Twitter API not yet implemented
    • +
    • on friendica_verbose=true: JSON return {"result":"ok","message":"message deleted"}
    • +
    +

    On error: +HTTP 400 BadRequest

    +
      +
    • on friendica_verbose=true: different JSON returns {"result":"error","message":"xyz"}
    • +
    +

    GET api/friendica/direct_messages_setseen#

    +

    Parameters#

    +
      +
    • id: id of the message to be updated as seen
    • +
    +

    Return values#

    +

    On success:

    +
      +
    • JSON return {"result": "ok", "message": "message set to seen"}
    • +
    +

    On error:

    +
      +
    • different JSON returns {"result": "error", "message": "xyz"}
    • +
    +

    GET api/friendica/direct_messages_search (GET; AUTH)#

    +

    Returns Private Messages matching the provided search string.

    +

    Parameters#

    +
      +
    • searchstring: string for which the API call should search as '%searchstring%' in field 'body' of all messages of the authenticated user (caption ignored)
    • +
    • getText (optional): plain|html If ommited, the title is prepended to the plaintext body in the text attribute of the private message objects.
    • +
    • getUserObjects (optional): true|false If false, the sender and recipient attributes of the private message object are absent.
    • +
    +

    Return values#

    +

    Returns only tested with JSON, XML might work as well.

    +

    On success:

    +
      +
    • JSON return {"success":"true", "search_results": array of found messages}
    • +
    • JSOn return {"success":"false", "search_results": "nothing found"}
    • +
    +

    On error:

    +
      +
    • different JSON returns {"result": "error", "message": "searchstring not specified"}
    • +
    +
    +

    GET api/friendica/group_show#

    +

    Return all or a specified group of the user with the containing contacts as array.

    +

    Parameters#

    +
      +
    • gid: optional, if not given, API returns all groups of the user
    • +
    +

    Return values#

    +

    Array of:

    +
      +
    • name: name of the group
    • +
    • gid: id of the group
    • +
    • user: array of Contacts
    • +
    +

    POST api/friendica/group_create#

    +

    Create the group with the posted array of contacts as members.

    +

    Parameters#

    +
      +
    • name: name of the group to be created
    • +
    +

    POST data#

    +

    JSON data as Array like the result of GET api/friendica/group_show:

    + +

    Return values#

    +

    Array of:

    +
      +
    • success: true if successfully created or reactivated
    • +
    • gid: gid of the created group
    • +
    • name: name of the created group
    • +
    • status: "missing user" | "reactivated" | "ok"
    • +
    • wrong users: array of users, which were not available in the contact table
    • +
    +

    POST api/friendica/group_update#

    +

    Update the group with the posted array of contacts as members (post all members of the group to the call; function will remove members not posted).

    +

    Parameters#

    +
      +
    • gid: id of the group to be changed
    • +
    • name: name of the group to be changed
    • +
    +

    POST data#

    +

    JSON data as array like the result of GET api/friendica/group_show:

    + +

    Return values#

    +

    Array of:

    +
      +
    • success: true if successfully updated
    • +
    • gid: gid of the changed group
    • +
    • name: name of the changed group
    • +
    • status: "missing user" | "ok"
    • +
    • wrong users: array of users, which were not available in the contact table
    • +
    +

    POST api/friendica/group_delete#

    +

    Delete the specified group of contacts; API call need to include the correct gid AND name of the group to be deleted.

    +

    Parameters#

    +
      +
    • gid: id of the group to be deleted
    • +
    • name: name of the group to be deleted
    • +
    +

    Return values#

    +

    Array of:

    +
      +
    • success: true if successfully deleted
    • +
    • gid: gid of the deleted group
    • +
    • name: name of the deleted group
    • +
    • status: "deleted" if successfully deleted
    • +
    • wrong users: empty array
    • +
    +
    +

    GET api/friendica/notifications#

    +

    Return last 50 Notifications for the current user, ordered by date with unseen item on top.

    +

    Parameters#

    +

    none

    +

    POST api/friendica/notifications/seen#

    +

    Set notification as seen.

    +

    Parameters#

    +
      +
    • id: id of the notification to set seen
    • +
    +

    Return values#

    +

    If the note is linked to an item, returns an Item.

    +

    Otherwise, a success status is returned:

    +
      +
    • success (json) | <status>success</status> (xml)
    • +
    +
    +

    GET api/friendica/photo#

    +

    Returns a Photo.

    +

    Parameters#

    +
      +
    • photo_id: Resource id of a photo.
    • +
    • scale: (optional) scale value of the photo
    • +
    +

    Returns data of a picture with the given resource. +If 'scale' isn't provided, returned data include full url to each scale of the photo. +If 'scale' is set, returned data include image data base64 encoded.

    +

    possibile scale value are:

    +
      +
    • 0: original or max size by server settings
    • +
    • 1: image with or height at <= 640
    • +
    • 2: image with or height at <= 320
    • +
    • 3: thumbnail 160x160
    • +
    • 4: Profile image at 300x300
    • +
    • 5: Profile image at 80x80
    • +
    • 6: Profile image at 48x48
    • +
    +

    An image used as profile image has only scaled 4-6, other images only 0-3

    +

    Return values#

    +

    json:

    +
        {
    +        "id": "photo id",
    +        "created": "date(YYYY-MM-DD HH:MM:SS)",
    +        "edited": "date(YYYY-MM-DD HH:MM:SS)",
    +        "title": "photo title",
    +        "desc": "photo description",
    +        "album": "album name",
    +        "filename": "original file name",
    +        "type": "mime type",
    +        "height": "number",
    +        "width": "number",
    +        "profile": "1 if is profile photo",
    +        "link": {
    +            "<scale>": "url to image",
    +            ...
    +        },
    +        // if 'scale' is set
    +        "datasize": "size in byte",
    +        "data": "base64 encoded image data"
    +    }
    +
    +

    xml:

    +
        <photo>
    +        <id>photo id</id>
    +        <created>date(YYYY-MM-DD HH:MM:SS)</created>
    +        <edited>date(YYYY-MM-DD HH:MM:SS)</edited>
    +        <title>photo title</title>
    +        <desc>photo description</desc>
    +        <album>album name</album>
    +        <filename>original file name</filename>
    +        <type>mime type</type>
    +        <height>number</height>
    +        <width>number</width>
    +        <profile>1 if is profile photo</profile>
    +        <links type="array">
    +        <link type="mime type" scale="scale number" href="image url"/>
    +            ...
    +        </links>
    +    </photo>
    +
    +

    GET api/friendica/photos/list#

    +

    Returns the API user's Photo List Items.

    +

    Return values#

    +

    json:

    +
        [
    +        {
    +            "id": "resource_id",
    +            "album": "album name",
    +            "filename": "original file name",
    +            "type": "image mime type",
    +            "thumb": "url to thumb sized image"
    +        },
    +        ...
    +    ]
    +
    +

    xml:

    +
        <photos type="array">
    +        <photo id="resource_id"
    +        album="album name"
    +        filename="original file name"
    +        type="image mime type">
    +            "url to thumb sized image"
    +        </photo>
    +        ...
    +    </photos>
    +
    +

    POST api/friendica/photo/create#

    +

    Alias of api/friendica/photo/update

    +

    POST api/friendica/photo/update#

    +

    Saves data for the scales 0-2 to database (see above for scale description). +Call adds non-public entries to items table to enable authenticated contacts to comment/like the photo. +Client should pay attention to the fact that updated access rights are not transferred to the contacts. i.e. public photos remain publicly visible if they have been commented/liked before setting visibility back to a limited group. +Currently, it is best to inform user that updating rights is not the right way to do this, and offer a solution to add photo as a new photo with the new rights instead.

    +

    Parameters#

    +
      +
    • photo_id (optional): if specified the photo with this id will be updated
    • +
    • media (optional): image data as base64, only optional if photo_id is specified (new upload must have media)
    • +
    • desc (optional): description for the photo, updated when photo_id is specified
    • +
    • album: name of the album to be deleted (always necessary)
    • +
    • album_new (optional): can be used to change the album of a single photo if photo_id is specified
    • +
    • allow_cid/allow_gid/deny_cid/deny_gid (optional):
        +
      • on create: empty string or omitting = public photo, specify in format <x><y><z>for private photo
      • +
      • on update: keys need to be present with empty values for changing a private photo to public
      • +
      +
    • +
    +

    Return values#

    +

    On success:

    +
      +
    • new photo uploaded: JSON return with photo data (see GET api/friendica/photo)
    • +
    • photo updated - changed photo data: JSON return with photo data (see GET api/friendica/photo)
    • +
    • photo updated - changed info: JSON return {"result": "updated", "message":"Image id 'xyz' has been updated."}
    • +
    • photo updated - nothing changed: JSON return {"result": "cancelled","message": "Nothing to update for image id 'xyz'."}
    • +
    +

    On error:

    +
      +
    • 403 FORBIDDEN: if not authenticated
    • +
    • 400 BADREQUEST: "no albumname specified", "no media data submitted", "photo not available", "acl data invalid"
    • +
    • 500 INTERNALSERVERERROR: "image size exceeds PHP config settings, file was rejected by server", + "image size exceeds Friendica Config setting (uploaded size: x)", + "unable to process image data", + "image upload failed", + "unknown error - uploading photo failed, see Friendica log for more information", + "unknown error - update photo entry in database failed", + "unknown error - this error on uploading or updating a photo should never happen"
    • +
    +

    POST api/friendica/photo/delete#

    +

    Deletes a single image with the specified id, is not reversible -> ensure that client is asking user for being sure to do this +Sets item table entries for this photo to deleted = 1.

    +

    Parameters#

    +
      +
    • photo_id: id of the photo to be deleted
    • +
    +

    Return values#

    +

    On success:

    +
      +
    • JSON return
    • +
    +
    {
    +    "result": "deleted",
    +    "message": "photo with id 'xyz' has been deleted from server."
    +}
    +
    +

    On error:

    +
      +
    • 403 FORBIDDEN: if not authenticated
    • +
    • 400 BADREQUEST: "no photo_id specified", "photo not available"
    • +
    • 500 INTERNALSERVERERROR: "unknown error on deleting photo", "problem with deleting items occurred"
    • +
    +
    +

    POST api/friendica/photoalbum/delete#

    +

    Deletes all images with the specified album name, is not reversible -> ensure that client is asking user for being sure to do this.

    +

    Parameters#

    +
      +
    • album: name of the album to be deleted
    • +
    +

    Return values#

    +

    On success:

    +
      +
    • JSON return
    • +
    +
    {
    +    "result": "deleted",
    +    "message": "album 'xyz' with all containing photos has been deleted."
    +}
    +
    +

    On error:

    +
      +
    • 403 FORBIDDEN: if not authenticated
    • +
    • 400 BADREQUEST: "no albumname specified", "album not available"
    • +
    • 500 INTERNALSERVERERROR: "problem with deleting item occured", "unknown error - deleting from database failed"
    • +
    +

    POST api/friendica/photoalbum/update#

    +

    Changes the album name to album_new for all photos in album.

    +

    Parameters#

    +
      +
    • album: name of the album to be updated
    • +
    • album_new: new name of the album
    • +
    +

    Return values#

    +

    On success:

    +
      +
    • JSON return
    • +
    +
    {
    +    "result": "updated",
    +    "message":"album 'abc' with all containing photos has been renamed to 'xyz'."
    +}
    +
    +

    On error:

    +
      +
    • 403 FORBIDDEN: if not authenticated
    • +
    • 400 BADREQUEST: "no albumname specified", "no new albumname specified", "album not available"
    • +
    • 500 INTERNALSERVERERROR: "unknown error - updating in database failed"
    • +
    +
    +

    GET api/friendica/profile/show#

    +

    Returns the Profile data of the authenticated user.

    +

    Return values#

    +

    On success: Array of:

    +
      +
    • global_dir: URL of the global directory set in server settings
    • +
    • friendica_owner: user data of the authenticated user
    • +
    • profiles: array of the profile data
    • +
    +

    On error: +HTTP 403 Forbidden: when no authentication was provided +HTTP 400 Bad Request: if given profile_id is not in the database or is not assigned to the authenticated user

    +

    General description of profile data in API returns: +- hide_friends: true if friends are hidden +- profile_photo +- profile_thumb +- publish: true if published on the server's local directory +- net_publish: true if published to global_dir +- fullname +- date_of_birth +- description +- xmpp +- homepage +- address +- locality +- region +- postal_code +- country +- pub_keywords +- custom_fields: list of public custom fields

    +
    +

    Deprecated endpoints#

    +
      +
    • POST api/statuses/mediap
    • +
    + + +
    + +
    +
    + + + +
    + + + +
    +
    +
    +
    + + + + + + + + \ No newline at end of file diff --git a/develop/en/spec/api/gnu-social/index.html b/develop/en/spec/api/gnu-social/index.html new file mode 100644 index 0000000..f212162 --- /dev/null +++ b/develop/en/spec/api/gnu-social/index.html @@ -0,0 +1,3441 @@ + + + + + + + + + + + + + + + + + + + + + + GNU Social - Friendica documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + +
    + + + + +
    + + +
    + +
    + + + + + + + + + +
    +
    + + + +
    +
    +
    + + + + + + +
    +
    +
    + + + +
    +
    +
    + + + +
    +
    +
    + + +
    +
    + + + + + + + + + + + + + +

    GNU Social API#

    +

    Overview#

    +

    Friendica provides the following endpoints defined in the official GNU Social Twitter-like API reference.

    +

    Authentication is the same as described in Using the APIs.

    +

    Entities#

    +

    These endpoints use the Friendica API entities.

    +

    Implemented endpoints#

    +
      +
    • GET api/account/rate_limit_status
    • +
    • POST api/account/update_profile_image
    • +
    • +

      GET api/account/verify_credentials

      +
    • +
    • +

      GET api/direct_messages

      +
    • +
    • POST/DELETE api/direct_messages/destroy
    • +
    • POST api/direct_messages/new
    • +
    • GET api/direct_messages/sent
    • +
    • GET api/favorites
    • +
    • POST api/favorites/create/:id
    • +
    • POST api/favorites/destroy/:id
    • +
    • GET api/followers/ids
    • +
    • POST api/friendships/destroy
    • +
    • GET api/friends/ids
    • +
    • GET/POST api/help/test
    • +
    • GET api/search
    • +
    • GET api/statuses/show/:id
    • +
    • POST api/statuses/destroy/:id
    • +
    • GET api/statuses/followers
    • +
    • GET api/statuses/friends
    • +
    • GET api/statuses/friends_timeline
    • +
    • GET api/statuses/friends_timeline/:username
    • +
    • GET api/statuses/home_timeline
    • +
    • GET api/statuses/mentions
    • +
    • GET api/statuses/replies
    • +
    • GET api/statuses/replies/:username
    • +
    • POST api/statuses/retweet/:id
    • +
    • GET api/statuses/public_timeline
    • +
    • POST api/statuses/update
    • +
    • GET api/statuses/user_timeline
    • +
    • GET api/users/show
    • +
    +

    Non-implemented endpoints#

    +
      +
    • statuses/retweeted_to_me
    • +
    • statuses/retweeted_by_me
    • +
    • statuses/retweets_of_me
    • +
    • friendships/create
    • +
    • friendships/exists
    • +
    • friendships/show
    • +
    • account/end_session
    • +
    • account/update_delivery_device
    • +
    • account/update_profile_background_image
    • +
    • notifications/follow
    • +
    • notifications/leave
    • +
    • blocks/create
    • +
    • blocks/destroy
    • +
    • blocks/exists
    • +
    • blocks/blocking
    • +
    • oauth/authorize
    • +
    • oauth/access_token
    • +
    • oauth/request_token
    • +
    • statusnet/groups/timeline
    • +
    • statusnet/groups/show
    • +
    • statusnet/groups/create
    • +
    • statusnet/groups/join
    • +
    • statusnet/groups/leave
    • +
    • statusnet/groups/list
    • +
    • statusnet/groups/list_all
    • +
    • statusnet/groups/membership
    • +
    • statusnet/groups/is_member
    • +
    • statusnet/tags/timeline
    • +
    • statusnet/media/upload
    • +
    • statusnet/config
    • +
    + + +
    + +
    +
    + + + +
    + + + +
    +
    +
    +
    + + + + + + + + \ No newline at end of file diff --git a/develop/en/spec/api/index.html b/develop/en/spec/api/index.html new file mode 100644 index 0000000..0bc055e --- /dev/null +++ b/develop/en/spec/api/index.html @@ -0,0 +1,3479 @@ + + + + + + + + + + + + + + + + + + + + + + Usage - Friendica documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + +
    + + + + +
    + + +
    + +
    + + + + + + + + + +
    +
    + + + +
    +
    +
    + + + + + + +
    +
    +
    + + + +
    +
    +
    + + + +
    +
    +
    + + +
    +
    + + + + + + + + + + + + + +

    Using the APIs#

    + + +

    Friendica offers multiple API endpoints to interface with third-party applications:

    + +

    Usage#

    +

    HTTP Method#

    +

    API endpoints can restrict the HTTP method used to request them. +Using an invalid method results in HTTP error 405 "Method Not Allowed".

    +

    Authentication#

    +

    Friendica supports basic HTTP Auth and OAuth to authenticate the user to the APIs.

    +

    Errors#

    +

    When an error occurs in API call, an HTTP error code is returned, with an error message +Usually:

    +
      +
    • 400 Bad Request: if parameters are missing or items can't be found
    • +
    • 403 Forbidden: if the authenticated user is missing
    • +
    • 405 Method Not Allowed: if API was called with an invalid method, e.g. GET when API require POST
    • +
    • 501 Not Implemented: if the requested API doesn't exist
    • +
    • 500 Internal Server Error: on other error conditions
    • +
    +

    Error body is

    +

    json:

    +
    {
    +    "error": "Specific error message",
    +    "request": "API path requested",
    +    "code": "HTTP error code"
    +}
    +
    +

    xml:

    +
    <status>
    +    <error>Specific error message</error>
    +    <request>API path requested</request>
    +    <code>HTTP error code</code>
    +</status>
    +
    +

    Usage Examples#

    +

    BASH / cURL#

    +
    /usr/bin/curl -u USER:PASS https://YOUR.FRIENDICA.TLD/api/statuses/update.xml -d source="some source id" -d status="the status you want to post"
    +
    +

    Python#

    +

    The RSStoFriendika code can be used as an example of how to use the API with python. +The lines for posting are located at line 21 and following.

    +

    def tweet(server, message, group_allow=None): +url = server + '/api/statuses/update' +urllib2.urlopen(url, urllib.urlencode({'status': message,'group_allow[]':group_allow}, doseq=True))

    +

    There is also a module for python 3 for using the API.

    + + +
    + +
    +
    + + + +
    + + + +
    +
    +
    +
    + + + + + + + + \ No newline at end of file diff --git a/develop/en/spec/api/mastodon/index.html b/develop/en/spec/api/mastodon/index.html new file mode 100644 index 0000000..b43b2cb --- /dev/null +++ b/develop/en/spec/api/mastodon/index.html @@ -0,0 +1,3683 @@ + + + + + + + + + + + + + + + + + + + + + + Mastodon - Friendica documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + +
    + + + + +
    + + +
    + +
    + + + + + + + + + +
    +
    + + + +
    +
    +
    + + + + + + +
    +
    +
    + + + +
    +
    +
    + + + +
    +
    +
    + + +
    +
    + + + + + + + + + + + + + +

    Mastodon API#

    +

    Overview#

    +

    Friendica provides the following endpoints defined in the official Mastodon API reference.

    +

    Authentication is the same as described in Using the APIs.

    +

    Clients#

    +

    Supported apps#

    +

    For supported apps please have a look at the FAQ

    +

    Unsupported apps#

    +

    Android#

    +
      +
    • Fedilab Automatically uses the legacy API, see issue: https://framagit.org/tom79/fedilab/-/issues/520
    • +
    • Mammut There are problems with the token request, see issue https://github.com/jamiesanson/Mammut/issues/19
    • +
    +

    iOS#

    +
      +
    • Mast Doesn't accept the entered instance name. Claims that it is invalid (Message is: "Not a valid instance (maybe closed or dead)")
    • +
    • Toot!
    • +
    +

    Entities#

    +

    These endpoints use the Mastodon API entities.

    +

    Implemented endpoints#

    + +

    Currently unimplemented endpoints#

    +

    These endpoints are planned to be implemented somewhere in the future.

    + +

    Dummy endpoints#

    +

    These endpoints are returning empty data to avoid error messages when using third party clients. +They refer to features that don't exist in Friendica yet.

    + +

    Non supportable endpoints#

    +

    These endpoints won't be implemented at the moment. +They refer to features or data that don't exist in Friendica yet.

    + + + +
    + +
    +
    + + + +
    + + + +
    +
    +
    +
    + + + + + + + + \ No newline at end of file diff --git a/develop/en/spec/api/twitter/index.html b/develop/en/spec/api/twitter/index.html new file mode 100644 index 0000000..e798a8d --- /dev/null +++ b/develop/en/spec/api/twitter/index.html @@ -0,0 +1,3828 @@ + + + + + + + + + + + + + + + + + + + + + + Twitter - Friendica documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + +
    + + + + +
    + + +
    + +
    + + + + + + + + + +
    +
    + + + +
    +
    +
    + + + + + + +
    +
    +
    + + + +
    +
    +
    + + + +
    +
    +
    + + +
    +
    + + + + + + + + + + + + + +

    Twitter API#

    +

    Overview#

    +

    Friendica provides the following endpoints defined in the official Twitter API reference.

    +

    Authentication is the same as described in Using the APIs.

    +

    Entities#

    +

    These endpoints use the Friendica API entities.

    +

    Different behaviour#

    +
      +
    • screen_name: The nickname in Friendica is only unique in each network but not for all networks. The users are searched in the following priority: Friendica, StatusNet/GNU Social, Diaspora, pump.io, Twitter. If no contact was found by this way, then the first contact is taken.
    • +
    • include_entities: Default is "false". If set to "true" then the plain text is formatted so that links are having descriptions.
    • +
    +

    Friendica-specific return values#

    +
      +
    • cid: Contact id of the user (important for "contact_allow" and "contact_deny")
    • +
    • network: network of the user
    • +
    +

    Unsupported parameters#

    +
      +
    • cursor
    • +
    • trim_user
    • +
    • contributor_details
    • +
    • place_id
    • +
    • display_coordinates
    • +
    • include_rts: To-Do
    • +
    • include_my_retweet: Retweets in Friendica are implemented in a different way
    • +
    +

    Implemented endpoints#

    + +

    Non-implemented endpoints#

    + + + +
    + +
    +
    + + + +
    + + + +
    +
    +
    +
    + + + + + + + + \ No newline at end of file diff --git a/develop/en/spec/database/db_2fa_app_specific_password/index.html b/develop/en/spec/database/db_2fa_app_specific_password/index.html new file mode 100644 index 0000000..c69878d --- /dev/null +++ b/develop/en/spec/database/db_2fa_app_specific_password/index.html @@ -0,0 +1,3460 @@ + + + + + + + + + + + + + + + + + + + + + + 2fa_app_specific_password - Friendica documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + +
    + + + + +
    + + +
    + +
    + + + + + + + + + +
    +
    + + + +
    +
    +
    + + + + + + +
    +
    +
    + + + +
    +
    +
    + + + +
    +
    +
    + + +
    +
    + + + + + + + + + + + + + +

    Table 2fa_app_specific_password#

    +

    Two-factor app-specific _password

    +

    Fields#

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    FieldDescriptionTypeNullKeyDefaultExtra
    idPassword ID for revocationmediumint unsignedNOPRINULLauto_increment
    uidUser IDmediumint unsignedNONULL
    descriptionDescription of the usage of the passwordvarchar(255)YESNULL
    hashed_passwordHashed passwordvarchar(255)NONULL
    generatedDatetime the password was generateddatetimeNONULL
    last_usedDatetime the password was last useddatetimeYESNULL
    +

    Indexes#

    + + + + + + + + + + + + + + + + + +
    NameFields
    PRIMARYid
    uid_descriptionuid, description(190)
    +

    Foreign Keys#

    + + + + + + + + + + + + + + + +
    FieldTarget TableTarget Field
    uiduseruid
    +

    Return to database documentation

    + + +
    + +
    +
    + + + +
    + + + +
    +
    +
    +
    + + + + + + + + \ No newline at end of file diff --git a/develop/en/spec/database/db_2fa_recovery_codes/index.html b/develop/en/spec/database/db_2fa_recovery_codes/index.html new file mode 100644 index 0000000..ef6461a --- /dev/null +++ b/develop/en/spec/database/db_2fa_recovery_codes/index.html @@ -0,0 +1,3438 @@ + + + + + + + + + + + + + + + + + + + + + + 2fa_recovery_codes - Friendica documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + +
    + + + + +
    + + +
    + +
    + + + + + + + + + +
    +
    + + + +
    +
    +
    + + + + + + +
    +
    +
    + + + +
    +
    +
    + + + +
    +
    +
    + + +
    +
    + + + + + + + + + + + + + +

    Table 2fa_recovery_codes#

    +

    Two-factor authentication recovery codes

    +

    Fields#

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    FieldDescriptionTypeNullKeyDefaultExtra
    uidUser IDmediumint unsignedNOPRINULL
    codeRecovery code stringvarchar(50)NOPRINULL
    generatedDatetime the code was generateddatetimeNONULL
    usedDatetime the code was useddatetimeYESNULL
    +

    Indexes#

    + + + + + + + + + + + + + +
    NameFields
    PRIMARYuid, code
    +

    Foreign Keys#

    + + + + + + + + + + + + + + + +
    FieldTarget TableTarget Field
    uiduseruid
    +

    Return to database documentation

    + + +
    + +
    +
    + + + +
    + + + +
    +
    +
    +
    + + + + + + + + \ No newline at end of file diff --git a/develop/en/spec/database/db_2fa_trusted_browser/index.html b/develop/en/spec/database/db_2fa_trusted_browser/index.html new file mode 100644 index 0000000..2b64a05 --- /dev/null +++ b/develop/en/spec/database/db_2fa_trusted_browser/index.html @@ -0,0 +1,3460 @@ + + + + + + + + + + + + + + + + + + + + + + 2fa_trusted_browser - Friendica documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + +
    + + + + +
    + + +
    + +
    + + + + + + + + + +
    +
    + + + +
    +
    +
    + + + + + + +
    +
    +
    + + + +
    +
    +
    + + + +
    +
    +
    + + +
    +
    + + + + + + + + + + + + + +

    Table 2fa_trusted_browser#

    +

    Two-factor authentication trusted browsers

    +

    Fields#

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    FieldDescriptionTypeNullKeyDefaultExtra
    cookie_hashTrusted cookie hashvarchar(80)NOPRINULL
    uidUser IDmediumint unsignedNONULL
    user_agentUser agent stringtextYESNULL
    trustedWhenever this browser should be trusted or notbooleanNO1
    createdDatetime the trusted browser was recordeddatetimeNONULL
    last_usedDatetime the trusted browser was last useddatetimeYESNULL
    +

    Indexes#

    + + + + + + + + + + + + + + + + + +
    NameFields
    PRIMARYcookie_hash
    uiduid
    +

    Foreign Keys#

    + + + + + + + + + + + + + + + +
    FieldTarget TableTarget Field
    uiduseruid
    +

    Return to database documentation

    + + +
    + +
    +
    + + + +
    + + + +
    +
    +
    +
    + + + + + + + + \ No newline at end of file diff --git a/develop/en/spec/database/db_addon/index.html b/develop/en/spec/database/db_addon/index.html new file mode 100644 index 0000000..3a4dd72 --- /dev/null +++ b/develop/en/spec/database/db_addon/index.html @@ -0,0 +1,3442 @@ + + + + + + + + + + + + + + + + + + + + + + addon - Friendica documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + +
    + + + + +
    + + +
    + +
    + + + + + + + + + +
    +
    + + + +
    +
    +
    + + + + + + +
    +
    +
    + + + +
    +
    +
    + + + +
    +
    +
    + + +
    +
    + + + + + + + + + + + + + +

    Table addon#

    +

    registered addons

    +

    Fields#

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    FieldDescriptionTypeNullKeyDefaultExtra
    idint unsignedNOPRINULLauto_increment
    nameaddon base (file)namevarchar(50)NO
    versioncurrently unusedvarchar(50)NO
    installedcurrently always 1booleanNO0
    hiddencurrently unusedbooleanNO0
    timestampfile timestamp to check for reloadsint unsignedNO0
    plugin_admin1 = has admin config, 0 = has no admin configbooleanNO0
    +

    Indexes#

    + + + + + + + + + + + + + + + + + + + + + +
    NameFields
    PRIMARYid
    installed_nameinstalled, name
    nameUNIQUE, name
    +

    Return to database documentation

    + + +
    + +
    +
    + + + +
    + + + +
    +
    +
    +
    + + + + + + + + \ No newline at end of file diff --git a/develop/en/spec/database/db_apcontact/index.html b/develop/en/spec/database/db_apcontact/index.html new file mode 100644 index 0000000..e214684 --- /dev/null +++ b/develop/en/spec/database/db_apcontact/index.html @@ -0,0 +1,3714 @@ + + + + + + + + + + + + + + + + + + + + + + apcontact - Friendica documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + +
    + + + + +
    + + +
    + +
    + + + + + + + + + +
    +
    + + + +
    +
    +
    + + + + + + +
    +
    +
    + + + +
    +
    +
    + + + +
    +
    +
    + + +
    +
    + + + + + + + + + + + + + +

    Table apcontact#

    +

    ActivityPub compatible contacts - used in the ActivityPub implementation

    +

    Fields#

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    FieldDescriptionTypeNullKeyDefaultExtra
    urlURL of the contactvarbinary(255)NOPRINULL
    uri-idId of the item-uri table entry that contains the apcontact urlint unsignedYESNULL
    uuidvarchar(255)YESNULL
    typevarchar(20)NONULL
    followingvarchar(255)YESNULL
    followersvarchar(255)YESNULL
    inboxvarchar(255)NONULL
    outboxvarchar(255)YESNULL
    sharedinboxvarchar(255)YESNULL
    featuredAddress for the collection of featured postsvarchar(255)YESNULL
    featured-tagsAddress for the collection of featured tagsvarchar(255)YESNULL
    manually-approvebooleanYESNULL
    discoverableMastodon extension: true if profile is published in their directorybooleanYESNULL
    nickvarchar(255)NO
    namevarchar(255)YESNULL
    abouttextYESNULL
    xmppXMPP addressvarchar(255)YESNULL
    matrixMatrix addressvarchar(255)YESNULL
    photovarchar(255)YESNULL
    headerHeader picturevarchar(255)YESNULL
    addrvarchar(255)YESNULL
    aliasvarchar(255)YESNULL
    pubkeytextYESNULL
    subscribevarchar(255)YESNULL
    baseurlbaseurl of the ap contactvarchar(255)YESNULL
    gsidGlobal Server IDint unsignedYESNULL
    generatorName of the contact's systemvarchar(255)YESNULL
    following_countNumber of following contactsint unsignedYES0
    followers_countNumber of followersint unsignedYES0
    statuses_countNumber of postsint unsignedYES0
    updateddatetimeNO0001-01-01 00:00:00
    +

    Indexes#

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    NameFields
    PRIMARYurl
    addraddr(32)
    aliasalias(190)
    followersfollowers(190)
    baseurlbaseurl(190)
    sharedinboxsharedinbox(190)
    gsidgsid
    uri-idUNIQUE, uri-id
    +

    Foreign Keys#

    + + + + + + + + + + + + + + + + + + + + +
    FieldTarget TableTarget Field
    uri-iditem-uriid
    gsidgserverid
    +

    Return to database documentation

    + + +
    + +
    +
    + + + +
    + + + +
    +
    +
    +
    + + + + + + + + \ No newline at end of file diff --git a/develop/en/spec/database/db_application-marker/index.html b/develop/en/spec/database/db_application-marker/index.html new file mode 100644 index 0000000..a45479a --- /dev/null +++ b/develop/en/spec/database/db_application-marker/index.html @@ -0,0 +1,3465 @@ + + + + + + + + + + + + + + + + + + + + + + application-marker - Friendica documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + +
    + + + + +
    + + +
    + +
    + + + + + + + + + +
    +
    + + + +
    +
    +
    + + + + + + +
    +
    +
    + + + +
    +
    +
    + + + +
    +
    +
    + + +
    +
    + + + + + + + + + + + + + +

    Table application-marker#

    +

    Timeline marker

    +

    Fields#

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    FieldDescriptionTypeNullKeyDefaultExtra
    application-idint unsignedNOPRINULL
    uidOwner User idmediumint unsignedNOPRINULL
    timelineMarker (home, notifications)varchar(64)NOPRINULL
    last_read_idMarker id for the timelinevarchar(255)YESNULL
    versionVersion numbersmallint unsignedYESNULL
    updated_atcreation timedatetimeYESNULL
    +

    Indexes#

    + + + + + + + + + + + + + + + + + +
    NameFields
    PRIMARYapplication-id, uid, timeline
    uid_iduid
    +

    Foreign Keys#

    + + + + + + + + + + + + + + + + + + + + +
    FieldTarget TableTarget Field
    application-idapplicationid
    uiduseruid
    +

    Return to database documentation

    + + +
    + +
    +
    + + + +
    + + + +
    +
    +
    +
    + + + + + + + + \ No newline at end of file diff --git a/develop/en/spec/database/db_application-token/index.html b/develop/en/spec/database/db_application-token/index.html new file mode 100644 index 0000000..490567a --- /dev/null +++ b/develop/en/spec/database/db_application-token/index.html @@ -0,0 +1,3501 @@ + + + + + + + + + + + + + + + + + + + + + + application-token - Friendica documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + +
    + + + + +
    + + +
    + +
    + + + + + + + + + +
    +
    + + + +
    +
    +
    + + + + + + +
    +
    +
    + + + +
    +
    +
    + + + +
    +
    +
    + + +
    +
    + + + + + + + + + + + + + +

    Table application-token#

    +

    OAuth user token

    +

    Fields#

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    FieldDescriptionTypeNullKeyDefaultExtra
    application-idint unsignedNOPRINULL
    uidOwner User idmediumint unsignedNOPRINULL
    codevarchar(64)NONULL
    access_tokenvarchar(64)NONULL
    created_atcreation timedatetimeNONULL
    scopesvarchar(255)YESNULL
    readRead scopebooleanYESNULL
    writeWrite scopebooleanYESNULL
    followFollow scopebooleanYESNULL
    pushPush scopebooleanYESNULL
    +

    Indexes#

    + + + + + + + + + + + + + + + + + +
    NameFields
    PRIMARYapplication-id, uid
    uid_iduid, application-id
    +

    Foreign Keys#

    + + + + + + + + + + + + + + + + + + + + +
    FieldTarget TableTarget Field
    application-idapplicationid
    uiduseruid
    +

    Return to database documentation

    + + +
    + +
    +
    + + + +
    + + + +
    +
    +
    +
    + + + + + + + + \ No newline at end of file diff --git a/develop/en/spec/database/db_application/index.html b/develop/en/spec/database/db_application/index.html new file mode 100644 index 0000000..a562142 --- /dev/null +++ b/develop/en/spec/database/db_application/index.html @@ -0,0 +1,3474 @@ + + + + + + + + + + + + + + + + + + + + + + application - Friendica documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + +
    + + + + +
    + + +
    + +
    + + + + + + + + + +
    +
    + + + +
    +
    +
    + + + + + + +
    +
    +
    + + + +
    +
    +
    + + + +
    +
    +
    + + +
    +
    + + + + + + + + + + + + + +

    Table application#

    +

    OAuth application

    +

    Fields#

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    FieldDescriptionTypeNullKeyDefaultExtra
    idgenerated indexint unsignedNOPRINULLauto_increment
    client_idvarchar(64)NONULL
    client_secretvarchar(64)NONULL
    namevarchar(255)NONULL
    redirect_urivarchar(255)NONULL
    websitevarchar(255)YESNULL
    scopesvarchar(255)YESNULL
    readRead scopebooleanYESNULL
    writeWrite scopebooleanYESNULL
    followFollow scopebooleanYESNULL
    pushPush scopebooleanYESNULL
    +

    Indexes#

    + + + + + + + + + + + + + + + + + +
    NameFields
    PRIMARYid
    client_idUNIQUE, client_id
    +

    Return to database documentation

    + + +
    + +
    +
    + + + +
    + + + +
    +
    +
    +
    + + + + + + + + \ No newline at end of file diff --git a/develop/en/spec/database/db_attach/index.html b/develop/en/spec/database/db_attach/index.html new file mode 100644 index 0000000..5201c60 --- /dev/null +++ b/develop/en/spec/database/db_attach/index.html @@ -0,0 +1,3541 @@ + + + + + + + + + + + + + + + + + + + + + + attach - Friendica documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + +
    + + + + +
    + + +
    + +
    + + + + + + + + + +
    +
    + + + +
    +
    +
    + + + + + + +
    +
    +
    + + + +
    +
    +
    + + + +
    +
    +
    + + +
    +
    + + + + + + + + + + + + + +

    Table attach#

    +

    file attachments

    +

    Fields#

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    FieldDescriptionTypeNullKeyDefaultExtra
    idgenerated indexint unsignedNOPRINULLauto_increment
    uidOwner User idmediumint unsignedNO0
    hashhashvarchar(64)NO
    filenamefilename of originalvarchar(255)NO
    filetypemimetypevarchar(64)NO
    filesizesize in bytesint unsignedNO0
    datafile datalongblobNONULL
    createdcreation timedatetimeNO0001-01-01 00:00:00
    editedlast edit timedatetimeNO0001-01-01 00:00:00
    allow_cidAccess Control - list of allowed contact.id '<19><78>mediumtextYESNULL
    allow_gidAccess Control - list of allowed groupsmediumtextYESNULL
    deny_cidAccess Control - list of denied contact.idmediumtextYESNULL
    deny_gidAccess Control - list of denied groupsmediumtextYESNULL
    backend-classStorage backend classtinytextYESNULL
    backend-refStorage backend data referencetextYESNULL
    +

    Indexes#

    + + + + + + + + + + + + + + + + + +
    NameFields
    PRIMARYid
    uiduid
    +

    Foreign Keys#

    + + + + + + + + + + + + + + + +
    FieldTarget TableTarget Field
    uiduseruid
    +

    Return to database documentation

    + + +
    + +
    +
    + + + +
    + + + +
    +
    +
    +
    + + + + + + + + \ No newline at end of file diff --git a/develop/en/spec/database/db_cache/index.html b/develop/en/spec/database/db_cache/index.html new file mode 100644 index 0000000..ad41d07 --- /dev/null +++ b/develop/en/spec/database/db_cache/index.html @@ -0,0 +1,3411 @@ + + + + + + + + + + + + + + + + + + + + + + cache - Friendica documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + +
    + + + + +
    + + +
    + +
    + + + + + + + + + +
    +
    + + + +
    +
    +
    + + + + + + +
    +
    +
    + + + +
    +
    +
    + + + +
    +
    +
    + + +
    +
    + + + + + + + + + + + + + +

    Table cache#

    +

    Stores temporary data

    +

    Fields#

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    FieldDescriptionTypeNullKeyDefaultExtra
    kcache keyvarbinary(255)NOPRINULL
    vcached serialized valuemediumtextYESNULL
    expiresdatetime of cache expirationdatetimeNO0001-01-01 00:00:00
    updateddatetime of cache insertiondatetimeNO0001-01-01 00:00:00
    +

    Indexes#

    + + + + + + + + + + + + + + + + + +
    NameFields
    PRIMARYk
    k_expiresk, expires
    +

    Return to database documentation

    + + +
    + +
    +
    + + + +
    + + + +
    +
    +
    +
    + + + + + + + + \ No newline at end of file diff --git a/develop/en/spec/database/db_config/index.html b/develop/en/spec/database/db_config/index.html new file mode 100644 index 0000000..32f63ec --- /dev/null +++ b/develop/en/spec/database/db_config/index.html @@ -0,0 +1,3411 @@ + + + + + + + + + + + + + + + + + + + + + + config - Friendica documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + +
    + + + + +
    + + +
    + +
    + + + + + + + + + +
    +
    + + + +
    +
    +
    + + + + + + +
    +
    +
    + + + +
    +
    +
    + + + +
    +
    +
    + + +
    +
    + + + + + + + + + + + + + +

    Table config#

    +

    main configuration storage

    +

    Fields#

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    FieldDescriptionTypeNullKeyDefaultExtra
    idint unsignedNOPRINULLauto_increment
    catvarbinary(50)NO
    kvarbinary(50)NO
    vmediumtextYESNULL
    +

    Indexes#

    + + + + + + + + + + + + + + + + + +
    NameFields
    PRIMARYid
    cat_kUNIQUE, cat, k
    +

    Return to database documentation

    + + +
    + +
    +
    + + + +
    + + + +
    +
    +
    +
    + + + + + + + + \ No newline at end of file diff --git a/develop/en/spec/database/db_contact-relation/index.html b/develop/en/spec/database/db_contact-relation/index.html new file mode 100644 index 0000000..5f9ba33 --- /dev/null +++ b/develop/en/spec/database/db_contact-relation/index.html @@ -0,0 +1,3456 @@ + + + + + + + + + + + + + + + + + + + + + + contact-relation - Friendica documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + +
    + + + + +
    + + +
    + +
    + + + + + + + + + +
    +
    + + + +
    +
    +
    + + + + + + +
    +
    +
    + + + +
    +
    +
    + + + +
    +
    +
    + + +
    +
    + + + + + + + + + + + + + +

    Table contact-relation#

    +

    Contact relations

    +

    Fields#

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    FieldDescriptionTypeNullKeyDefaultExtra
    cidcontact the related contact had interacted withint unsignedNOPRI0
    relation-cidrelated contact who had interacted with the contactint unsignedNOPRI0
    last-interactionDate of the last interactiondatetimeNO0001-01-01 00:00:00
    follow-updatedDate of the last update of the contact relationshipdatetimeNO0001-01-01 00:00:00
    followsbooleanNO0
    +

    Indexes#

    + + + + + + + + + + + + + + + + + +
    NameFields
    PRIMARYcid, relation-cid
    relation-cidrelation-cid
    +

    Foreign Keys#

    + + + + + + + + + + + + + + + + + + + + +
    FieldTarget TableTarget Field
    cidcontactid
    relation-cidcontactid
    +

    Return to database documentation

    + + +
    + +
    +
    + + + +
    + + + +
    +
    +
    +
    + + + + + + + + \ No newline at end of file diff --git a/develop/en/spec/database/db_contact/index.html b/develop/en/spec/database/db_contact/index.html new file mode 100644 index 0000000..f50f1c6 --- /dev/null +++ b/develop/en/spec/database/db_contact/index.html @@ -0,0 +1,4243 @@ + + + + + + + + + + + + + + + + + + + + + + contact - Friendica documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + +
    + + + + +
    + + +
    + +
    + + + + + + + + + +
    +
    + + + +
    +
    +
    + + + + + + +
    +
    +
    + + + +
    +
    +
    + + + +
    +
    +
    + + +
    +
    + + + + + + + + + + + + + +

    Table contact#

    +

    contact table

    +

    Fields#

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    FieldDescriptionTypeNullKeyDefaultExtra
    idsequential IDint unsignedNOPRINULLauto_increment
    uidOwner User idmediumint unsignedNO0
    createddatetimeNO0001-01-01 00:00:00
    updatedDate of last contact updatedatetimeYES0001-01-01 00:00:00
    networkNetwork of the contactchar(4)NO
    nameName that this contact is known byvarchar(255)NO
    nickNick- and user name of the contactvarchar(255)NO
    locationvarchar(255)YES
    abouttextYESNULL
    keywordspublic keywords (interests) of the contacttextYESNULL
    xmppXMPP addressvarchar(255)NO
    matrixMatrix addressvarchar(255)NO
    avatarvarchar(255)NO
    headerHeader picturevarchar(255)YESNULL
    urlvarchar(255)NO
    nurlvarchar(255)NO
    uri-idId of the item-uri table entry that contains the contact urlint unsignedYESNULL
    addrvarchar(255)NO
    aliasvarchar(255)NO
    pubkeyRSA public key 4096 bittextYESNULL
    prvkeyRSA private key 4096 bittextYESNULL
    batchvarchar(255)NO
    notifyvarchar(255)YESNULL
    pollvarchar(255)YESNULL
    subscribevarchar(255)YESNULL
    last-updateDate of the last try to update the contact infodatetimeNO0001-01-01 00:00:00
    success_updateDate of the last successful contact updatedatetimeNO0001-01-01 00:00:00
    failure_updateDate of the last failed updatedatetimeNO0001-01-01 00:00:00
    failedConnection failedbooleanYESNULL
    term-datedatetimeNO0001-01-01 00:00:00
    last-itemdate of the last postdatetimeNO0001-01-01 00:00:00
    last-discoverydate of the last follower discoverydatetimeNO0001-01-01 00:00:00
    blockedNode-wide block statusbooleanNO1
    block_reasonNode-wide block reasontextYESNULL
    readonlyposts of the contact are readonlybooleanNO0
    contact-typePerson, organisation, news, community, relaytinyintNO0
    manually-approveContact requests have to be approved manuallybooleanYESNULL
    archivebooleanNO0
    unsearchableContact prefers to not be searchablebooleanNO0
    sensitiveContact posts sensitive contentbooleanNO0
    baseurlbaseurl of the contactvarchar(255)YES
    gsidGlobal Server IDint unsignedYESNULL
    bddateNO0001-01-01
    reasontextYESNULL
    self1 if the contact is the user him/her selfbooleanNO0
    remote_selfbooleanNO0
    relThe kind of the relation between the user and the contacttinyint unsignedNO0
    protocolProtocol of the contactchar(4)NO
    subhubbooleanNO0
    hub-verifyvarchar(255)NO
    ratingAutomatically detected feed poll frequencytinyintNO0
    priorityFeed poll prioritytinyint unsignedNO0
    attagvarchar(255)NO
    hiddenbooleanNO0
    pendingContact request is pendingbooleanNO1
    deletedContact has been deletedbooleanNO0
    infomediumtextYESNULL
    notify_new_postsbooleanNO0
    fetch_further_informationtinyint unsignedNO0
    ffi_keyword_denylisttextYESNULL
    photoLink to the profile photo of the contactvarchar(255)YES
    thumbLink to the profile photo (thumb size)varchar(255)YES
    microLink to the profile photo (micro size)varchar(255)YES
    name-datedatetimeNO0001-01-01 00:00:00
    uri-datedatetimeNO0001-01-01 00:00:00
    avatar-datedatetimeNO0001-01-01 00:00:00
    requestvarchar(255)YESNULL
    confirmvarchar(255)YESNULL
    pocovarchar(255)YESNULL
    writablebooleanNO0
    forumcontact is a forum. Deprecated, use 'contact-type' = 'community' and 'manually-approve' = false insteadbooleanNO0
    prvcontact is a private group. Deprecated, use 'contact-type' = 'community' and 'manually-approve' = true insteadbooleanNO0
    bdyearvarchar(4)NO
    site-pubkeyDeprecatedtextYESNULL
    genderDeprecatedvarchar(32)NO
    duplexDeprecatedbooleanNO0
    issued-idDeprecatedvarchar(255)NO
    dfrn-idDeprecatedvarchar(255)NO
    aes_allowDeprecatedbooleanNO0
    ret-aesDeprecatedbooleanNO0
    usehubDeprecatedbooleanNO0
    closenessDeprecatedtinyint unsignedNO99
    profile-idDeprecatedint unsignedYESNULL
    +

    Indexes#

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    NameFields
    PRIMARYid
    uid_nameuid, name(190)
    self_uidself, uid
    alias_uidalias(128), uid
    pending_uidpending, uid
    blocked_uidblocked, uid
    uid_rel_network_polluid, rel, network, poll(64), archive
    uid_network_batchuid, network, batch(64)
    batch_contact-typebatch(64), contact-type
    addr_uidaddr(128), uid
    nurl_uidnurl(128), uid
    nick_uidnick(128), uid
    attag_uidattag(96), uid
    network_uid_lastupdatenetwork, uid, last-update
    uid_network_self_lastupdateuid, network, self, last-update
    uid_lastitemuid, last-item
    baseurlbaseurl(64)
    uid_contact-typeuid, contact-type
    uid_self_contact-typeuid, self, contact-type
    self_network_uidself, network, uid
    gsidgsid
    uri-iduri-id
    +

    Foreign Keys#

    + + + + + + + + + + + + + + + + + + + + + + + + + +
    FieldTarget TableTarget Field
    uiduseruid
    uri-iditem-uriid
    gsidgserverid
    +

    Return to database documentation

    + + +
    + +
    +
    + + + +
    + + + +
    +
    +
    +
    + + + + + + + + \ No newline at end of file diff --git a/develop/en/spec/database/db_conv/index.html b/develop/en/spec/database/db_conv/index.html new file mode 100644 index 0000000..de30e8e --- /dev/null +++ b/develop/en/spec/database/db_conv/index.html @@ -0,0 +1,3478 @@ + + + + + + + + + + + + + + + + + + + + + + conv - Friendica documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + +
    + + + + +
    + + +
    + +
    + + + + + + + + + +
    +
    + + + +
    +
    +
    + + + + + + +
    +
    +
    + + + +
    +
    +
    + + + +
    +
    +
    + + +
    +
    + + + + + + + + + + + + + +

    Table conv#

    +

    private messages

    +

    Fields#

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    FieldDescriptionTypeNullKeyDefaultExtra
    idsequential IDint unsignedNOPRINULLauto_increment
    guidA unique identifier for this conversationvarchar(255)NO
    recipssender_handle;recipient_handletextYESNULL
    uidOwner User idmediumint unsignedNO0
    creatorhandle of creatorvarchar(255)NO
    createdcreation timestampdatetimeNO0001-01-01 00:00:00
    updatededited timestampdatetimeNO0001-01-01 00:00:00
    subjectsubject of initial messagetextYESNULL
    +

    Indexes#

    + + + + + + + + + + + + + + + + + +
    NameFields
    PRIMARYid
    uiduid
    +

    Foreign Keys#

    + + + + + + + + + + + + + + + +
    FieldTarget TableTarget Field
    uiduseruid
    +

    Return to database documentation

    + + +
    + +
    +
    + + + +
    + + + +
    +
    +
    +
    + + + + + + + + \ No newline at end of file diff --git a/develop/en/spec/database/db_conversation/index.html b/develop/en/spec/database/db_conversation/index.html new file mode 100644 index 0000000..0d03bcc --- /dev/null +++ b/develop/en/spec/database/db_conversation/index.html @@ -0,0 +1,3451 @@ + + + + + + + + + + + + + + + + + + + + + + conversation - Friendica documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + +
    + + + + +
    + + +
    + +
    + + + + + + + + + +
    +
    + + + +
    +
    +
    + + + + + + +
    +
    +
    + + + +
    +
    +
    + + + +
    +
    +
    + + +
    +
    + + + + + + + + + + + + + +

    Table conversation#

    +

    Raw data and structure information for messages

    +

    Fields#

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    FieldDescriptionTypeNullKeyDefaultExtra
    item-uriOriginal URI of the item - unrelated to the table with the same namevarbinary(255)NOPRINULL
    reply-to-uriURI to which this item is a replyvarbinary(255)NO
    conversation-uriGNU Social conversation URIvarbinary(255)NO
    conversation-hrefGNU Social conversation linkvarbinary(255)NO
    protocolThe protocol of the itemtinyint unsignedNO255
    directionHow the message arrived here: 1=push, 2=pulltinyint unsignedNO0
    sourceOriginal sourcemediumtextYESNULL
    receivedReceiving datedatetimeNO0001-01-01 00:00:00
    +

    Indexes#

    + + + + + + + + + + + + + + + + + + + + + +
    NameFields
    PRIMARYitem-uri
    conversation-uriconversation-uri
    receivedreceived
    +

    Return to database documentation

    + + +
    + +
    +
    + + + +
    + + + +
    +
    +
    +
    + + + + + + + + \ No newline at end of file diff --git a/develop/en/spec/database/db_delayed-post/index.html b/develop/en/spec/database/db_delayed-post/index.html new file mode 100644 index 0000000..73a2064 --- /dev/null +++ b/develop/en/spec/database/db_delayed-post/index.html @@ -0,0 +1,3460 @@ + + + + + + + + + + + + + + + + + + + + + + delayed-post - Friendica documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + +
    + + + + +
    + + +
    + +
    + + + + + + + + + +
    +
    + + + +
    +
    +
    + + + + + + +
    +
    +
    + + + +
    +
    +
    + + + +
    +
    +
    + + +
    +
    + + + + + + + + + + + + + +

    Table delayed-post#

    +

    Posts that are about to be distributed at a later time

    +

    Fields#

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    FieldDescriptionTypeNullKeyDefaultExtra
    idint unsignedNOPRINULLauto_increment
    uriURI of the post that will be distributed latervarchar(255)YESNULL
    uidOwner User idmediumint unsignedYESNULL
    delayeddelay timedatetimeYESNULL
    widWorkerqueue idint unsignedYESNULL
    +

    Indexes#

    + + + + + + + + + + + + + + + + + + + + + +
    NameFields
    PRIMARYid
    uid_uriUNIQUE, uid, uri(190)
    widwid
    +

    Foreign Keys#

    + + + + + + + + + + + + + + + + + + + + +
    FieldTarget TableTarget Field
    uiduseruid
    widworkerqueueid
    +

    Return to database documentation

    + + +
    + +
    +
    + + + +
    + + + +
    +
    +
    +
    + + + + + + + + \ No newline at end of file diff --git a/develop/en/spec/database/db_diaspora-interaction/index.html b/develop/en/spec/database/db_diaspora-interaction/index.html new file mode 100644 index 0000000..a5af561 --- /dev/null +++ b/develop/en/spec/database/db_diaspora-interaction/index.html @@ -0,0 +1,3420 @@ + + + + + + + + + + + + + + + + + + + + + + diaspora-interaction - Friendica documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + +
    + + + + +
    + + +
    + +
    + + + + + + + + + +
    +
    + + + +
    +
    +
    + + + + + + +
    +
    +
    + + + +
    +
    +
    + + + +
    +
    +
    + + +
    +
    + + + + + + + + + + + + + +

    Table diaspora-interaction#

    +

    Signed Diaspora Interaction

    +

    Fields#

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    FieldDescriptionTypeNullKeyDefaultExtra
    uri-idId of the item-uri table entry that contains the item uriint unsignedNOPRINULL
    interactionThe Diaspora interactionmediumtextYESNULL
    +

    Indexes#

    + + + + + + + + + + + + + +
    NameFields
    PRIMARYuri-id
    +

    Foreign Keys#

    + + + + + + + + + + + + + + + +
    FieldTarget TableTarget Field
    uri-iditem-uriid
    +

    Return to database documentation

    + + +
    + +
    +
    + + + +
    + + + +
    +
    +
    +
    + + + + + + + + \ No newline at end of file diff --git a/develop/en/spec/database/db_endpoint/index.html b/develop/en/spec/database/db_endpoint/index.html new file mode 100644 index 0000000..3783b4d --- /dev/null +++ b/develop/en/spec/database/db_endpoint/index.html @@ -0,0 +1,3433 @@ + + + + + + + + + + + + + + + + + + + + + + endpoint - Friendica documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + +
    + + + + +
    + + +
    + +
    + + + + + + + + + +
    +
    + + + +
    +
    +
    + + + + + + +
    +
    +
    + + + +
    +
    +
    + + + +
    +
    +
    + + +
    +
    + + + + + + + + + + + + + +

    Table endpoint#

    +

    ActivityPub endpoints - used in the ActivityPub implementation

    +

    Fields#

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    FieldDescriptionTypeNullKeyDefaultExtra
    urlURL of the contactvarbinary(255)NOPRINULL
    typevarchar(20)NONULL
    owner-uri-idId of the item-uri table entry that contains the apcontact urlint unsignedYESNULL
    +

    Indexes#

    + + + + + + + + + + + + + + + + + +
    NameFields
    PRIMARYurl
    owner-uri-id_typeUNIQUE, owner-uri-id, type
    +

    Foreign Keys#

    + + + + + + + + + + + + + + + +
    FieldTarget TableTarget Field
    owner-uri-iditem-uriid
    +

    Return to database documentation

    + + +
    + +
    +
    + + + +
    + + + +
    +
    +
    +
    + + + + + + + + \ No newline at end of file diff --git a/develop/en/spec/database/db_event/index.html b/develop/en/spec/database/db_event/index.html new file mode 100644 index 0000000..a4ca321 --- /dev/null +++ b/develop/en/spec/database/db_event/index.html @@ -0,0 +1,3604 @@ + + + + + + + + + + + + + + + + + + + + + + event - Friendica documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + +
    + + + + +
    + + +
    + +
    + + + + + + + + + +
    +
    + + + +
    +
    +
    + + + + + + +
    +
    +
    + + + +
    +
    +
    + + + +
    +
    +
    + + +
    +
    + + + + + + + + + + + + + +

    Table event#

    +

    Events

    +

    Fields#

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    FieldDescriptionTypeNullKeyDefaultExtra
    idsequential IDint unsignedNOPRINULLauto_increment
    guidvarchar(255)NO
    uidOwner User idmediumint unsignedNO0
    cidcontact_id (ID of the contact in contact table)int unsignedNO0
    urivarchar(255)NO
    uri-idId of the item-uri table entry that contains the event uriint unsignedYESNULL
    createdcreation timedatetimeNO0001-01-01 00:00:00
    editedlast edit timedatetimeNO0001-01-01 00:00:00
    startevent start timedatetimeNO0001-01-01 00:00:00
    finishevent end timedatetimeNO0001-01-01 00:00:00
    summaryshort description or title of the eventtextYESNULL
    descevent descriptiontextYESNULL
    locationevent locationtextYESNULL
    typeevent or birthdayvarchar(20)NO
    nofinishif event does have no end this is 1booleanNO0
    ignore0 or 1booleanNO0
    allow_cidAccess Control - list of allowed contact.id '<19><78>'mediumtextYESNULL
    allow_gidAccess Control - list of allowed groupsmediumtextYESNULL
    deny_cidAccess Control - list of denied contact.idmediumtextYESNULL
    deny_gidAccess Control - list of denied groupsmediumtextYESNULL
    +

    Indexes#

    + + + + + + + + + + + + + + + + + + + + + + + + + +
    NameFields
    PRIMARYid
    uid_startuid, start
    cidcid
    uri-iduri-id
    +

    Foreign Keys#

    + + + + + + + + + + + + + + + + + + + + + + + + + +
    FieldTarget TableTarget Field
    uiduseruid
    cidcontactid
    uri-iditem-uriid
    +

    Return to database documentation

    + + +
    + +
    +
    + + + +
    + + + +
    +
    +
    +
    + + + + + + + + \ No newline at end of file diff --git a/develop/en/spec/database/db_fcontact/index.html b/develop/en/spec/database/db_fcontact/index.html new file mode 100644 index 0000000..f18dfaf --- /dev/null +++ b/develop/en/spec/database/db_fcontact/index.html @@ -0,0 +1,3603 @@ + + + + + + + + + + + + + + + + + + + + + + fcontact - Friendica documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + +
    + + + + +
    + + +
    + +
    + + + + + + + + + +
    +
    + + + +
    +
    +
    + + + + + + +
    +
    +
    + + + +
    +
    +
    + + + +
    +
    +
    + + +
    +
    + + + + + + + + + + + + + +

    Table fcontact#

    +

    Diaspora compatible contacts - used in the Diaspora implementation

    +

    Fields#

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    FieldDescriptionTypeNullKeyDefaultExtra
    idsequential IDint unsignedNOPRINULLauto_increment
    guidunique idvarchar(255)NO
    urlvarchar(255)NO
    uri-idId of the item-uri table entry that contains the fcontact urlint unsignedYESNULL
    namevarchar(255)NO
    photovarchar(255)NO
    requestvarchar(255)NO
    nickvarchar(255)NO
    addrvarchar(255)NO
    batchvarchar(255)NO
    notifyvarchar(255)NO
    pollvarchar(255)NO
    confirmvarchar(255)NO
    prioritytinyint unsignedNO0
    networkchar(4)NO
    aliasvarchar(255)NO
    pubkeytextYESNULL
    updateddatetimeNO0001-01-01 00:00:00
    interacting_countNumber of contacts this contact interactes withint unsignedYES0
    interacted_countNumber of contacts that interacted with this contactint unsignedYES0
    post_countNumber of posts and commentsint unsignedYES0
    +

    Indexes#

    + + + + + + + + + + + + + + + + + + + + + + + + + +
    NameFields
    PRIMARYid
    addraddr(32)
    urlUNIQUE, url(190)
    uri-idUNIQUE, uri-id
    +

    Foreign Keys#

    + + + + + + + + + + + + + + + +
    FieldTarget TableTarget Field
    uri-iditem-uriid
    +

    Return to database documentation

    + + +
    + +
    +
    + + + +
    + + + +
    +
    +
    +
    + + + + + + + + \ No newline at end of file diff --git a/develop/en/spec/database/db_fsuggest/index.html b/develop/en/spec/database/db_fsuggest/index.html new file mode 100644 index 0000000..d9ca053 --- /dev/null +++ b/develop/en/spec/database/db_fsuggest/index.html @@ -0,0 +1,3496 @@ + + + + + + + + + + + + + + + + + + + + + + fsuggest - Friendica documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + +
    + + + + +
    + + +
    + +
    + + + + + + + + + +
    +
    + + + +
    +
    +
    + + + + + + +
    +
    +
    + + + +
    +
    +
    + + + +
    +
    +
    + + +
    +
    + + + + + + + + + + + + + +

    Table fsuggest#

    +

    friend suggestion stuff

    +

    Fields#

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    FieldDescriptionTypeNullKeyDefaultExtra
    idint unsignedNOPRINULLauto_increment
    uidUser idmediumint unsignedNO0
    cidint unsignedNO0
    namevarchar(255)NO
    urlvarchar(255)NO
    requestvarchar(255)NO
    photovarchar(255)NO
    notetextYESNULL
    createddatetimeNO0001-01-01 00:00:00
    +

    Indexes#

    + + + + + + + + + + + + + + + + + + + + + +
    NameFields
    PRIMARYid
    cidcid
    uiduid
    +

    Foreign Keys#

    + + + + + + + + + + + + + + + + + + + + +
    FieldTarget TableTarget Field
    uiduseruid
    cidcontactid
    +

    Return to database documentation

    + + +
    + +
    +
    + + + +
    + + + +
    +
    +
    +
    + + + + + + + + \ No newline at end of file diff --git a/develop/en/spec/database/db_group/index.html b/develop/en/spec/database/db_group/index.html new file mode 100644 index 0000000..b45bf5a --- /dev/null +++ b/develop/en/spec/database/db_group/index.html @@ -0,0 +1,3469 @@ + + + + + + + + + + + + + + + + + + + + + + group - Friendica documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + +
    + + + + +
    + + +
    + +
    + + + + + + + + + +
    +
    + + + +
    +
    +
    + + + + + + +
    +
    +
    + + + +
    +
    +
    + + + +
    +
    +
    + + +
    +
    + + + + + + + + + + + + + +

    Table group#

    +

    privacy groups, group info

    +

    Fields#

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    FieldDescriptionTypeNullKeyDefaultExtra
    idsequential IDint unsignedNOPRINULLauto_increment
    uidOwner User idmediumint unsignedNO0
    visible1 indicates the member list is not privatebooleanNO0
    deleted1 indicates the group has been deletedbooleanNO0
    cidContact id of forum. When this field is filled then the members are synced automatically.int unsignedYESNULL
    namehuman readable name of groupvarchar(255)NO
    +

    Indexes#

    + + + + + + + + + + + + + + + + + + + + + +
    NameFields
    PRIMARYid
    uiduid
    cidcid
    +

    Foreign Keys#

    + + + + + + + + + + + + + + + + + + + + +
    FieldTarget TableTarget Field
    uiduseruid
    cidcontactid
    +

    Return to database documentation

    + + +
    + +
    +
    + + + +
    + + + +
    +
    +
    +
    + + + + + + + + \ No newline at end of file diff --git a/develop/en/spec/database/db_group_member/index.html b/develop/en/spec/database/db_group_member/index.html new file mode 100644 index 0000000..0694061 --- /dev/null +++ b/develop/en/spec/database/db_group_member/index.html @@ -0,0 +1,3442 @@ + + + + + + + + + + + + + + + + + + + + + + group_member - Friendica documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + +
    + + + + +
    + + +
    + +
    + + + + + + + + + +
    +
    + + + +
    +
    +
    + + + + + + +
    +
    +
    + + + +
    +
    +
    + + + +
    +
    +
    + + +
    +
    + + + + + + + + + + + + + +

    Table group_member#

    +

    privacy groups, member info

    +

    Fields#

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    FieldDescriptionTypeNullKeyDefaultExtra
    idsequential IDint unsignedNOPRINULLauto_increment
    gidgroups.id of the associated groupint unsignedNO0
    contact-idcontact.id of the member assigned to the associated groupint unsignedNO0
    +

    Indexes#

    + + + + + + + + + + + + + + + + + + + + + +
    NameFields
    PRIMARYid
    contactidcontact-id
    gid_contactidUNIQUE, gid, contact-id
    +

    Foreign Keys#

    + + + + + + + + + + + + + + + + + + + + +
    FieldTarget TableTarget Field
    gidgroupid
    contact-idcontactid
    +

    Return to database documentation

    + + +
    + +
    +
    + + + +
    + + + +
    +
    +
    +
    + + + + + + + + \ No newline at end of file diff --git a/develop/en/spec/database/db_gserver-tag/index.html b/develop/en/spec/database/db_gserver-tag/index.html new file mode 100644 index 0000000..5bf9a70 --- /dev/null +++ b/develop/en/spec/database/db_gserver-tag/index.html @@ -0,0 +1,3424 @@ + + + + + + + + + + + + + + + + + + + + + + gserver-tag - Friendica documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + +
    + + + + +
    + + +
    + +
    + + + + + + + + + +
    +
    + + + +
    +
    +
    + + + + + + +
    +
    +
    + + + +
    +
    +
    + + + +
    +
    +
    + + +
    +
    + + + + + + + + + + + + + +

    Table gserver-tag#

    +

    Tags that the server has subscribed

    +

    Fields#

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    FieldDescriptionTypeNullKeyDefaultExtra
    gserver-idThe id of the gserverint unsignedNOPRI0
    tagTag that the server has subscribedvarchar(100)NOPRI
    +

    Indexes#

    + + + + + + + + + + + + + + + + + +
    NameFields
    PRIMARYgserver-id, tag
    tagtag
    +

    Foreign Keys#

    + + + + + + + + + + + + + + + +
    FieldTarget TableTarget Field
    gserver-idgserverid
    +

    Return to database documentation

    + + +
    + +
    +
    + + + +
    + + + +
    +
    +
    +
    + + + + + + + + \ No newline at end of file diff --git a/develop/en/spec/database/db_gserver/index.html b/develop/en/spec/database/db_gserver/index.html new file mode 100644 index 0000000..d9db93e --- /dev/null +++ b/develop/en/spec/database/db_gserver/index.html @@ -0,0 +1,3635 @@ + + + + + + + + + + + + + + + + + + + + + + gserver - Friendica documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + +
    + + + + +
    + + +
    + +
    + + + + + + + + + +
    +
    + + + +
    +
    +
    + + + + + + +
    +
    +
    + + + +
    +
    +
    + + + +
    +
    +
    + + +
    +
    + + + + + + + + + + + + + +

    Table gserver#

    +

    Global servers

    +

    Fields#

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    FieldDescriptionTypeNullKeyDefaultExtra
    idsequential IDint unsignedNOPRINULLauto_increment
    urlvarchar(255)NO
    nurlvarchar(255)NO
    versionvarchar(255)NO
    site_namevarchar(255)NO
    infotextYESNULL
    register_policytinyintNO0
    registered-usersNumber of registered usersint unsignedNO0
    active-week-usersNumber of active users in the last weekint unsignedYESNULL
    active-month-usersNumber of active users in the last monthint unsignedYESNULL
    active-halfyear-usersNumber of active users in the last six monthint unsignedYESNULL
    local-postsNumber of local postsint unsignedYESNULL
    local-commentsNumber of local commentsint unsignedYESNULL
    directory-typeType of directory service (Poco, Mastodon)tinyintYES0
    pocovarchar(255)NO
    noscrapevarchar(255)NO
    networkchar(4)NO
    protocolThe protocol of the servertinyint unsignedYESNULL
    platformvarchar(255)NO
    relay-subscribeHas the server subscribed to the relay systembooleanNO0
    relay-scopeThe scope of messages that the server wants to getvarchar(10)NO
    detection-methodMethod that had been used to detect that servertinyint unsignedYESNULL
    createddatetimeNO0001-01-01 00:00:00
    last_poco_querydatetimeYES0001-01-01 00:00:00
    last_contactLast successful connection requestdatetimeYES0001-01-01 00:00:00
    last_failureLast failed connection requestdatetimeYES0001-01-01 00:00:00
    failedConnection failedbooleanYESNULL
    next_contactNext connection requestdatetimeYES0001-01-01 00:00:00
    +

    Indexes#

    + + + + + + + + + + + + + + + + + + + + + + + + + +
    NameFields
    PRIMARYid
    nurlUNIQUE, nurl(190)
    next_contactnext_contact
    networknetwork
    +

    Return to database documentation

    + + +
    + +
    +
    + + + +
    + + + +
    +
    +
    +
    + + + + + + + + \ No newline at end of file diff --git a/develop/en/spec/database/db_hook/index.html b/develop/en/spec/database/db_hook/index.html new file mode 100644 index 0000000..c844010 --- /dev/null +++ b/develop/en/spec/database/db_hook/index.html @@ -0,0 +1,3424 @@ + + + + + + + + + + + + + + + + + + + + + + hook - Friendica documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + +
    + + + + +
    + + +
    + +
    + + + + + + + + + +
    +
    + + + +
    +
    +
    + + + + + + +
    +
    +
    + + + +
    +
    +
    + + + +
    +
    +
    + + +
    +
    + + + + + + + + + + + + + +

    Table hook#

    +

    addon hook registry

    +

    Fields#

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    FieldDescriptionTypeNullKeyDefaultExtra
    idsequential IDint unsignedNOPRINULLauto_increment
    hookname of hookvarbinary(100)NO
    filerelative filename of hook handlervarbinary(200)NO
    functionfunction name of hook handlervarbinary(200)NO
    prioritynot yet implemented - can be used to sort conflicts in hook handling by calling handlers in priority ordersmallint unsignedNO0
    +

    Indexes#

    + + + + + + + + + + + + + + + + + + + + + +
    NameFields
    PRIMARYid
    prioritypriority
    hook_file_functionUNIQUE, hook, file, function
    +

    Return to database documentation

    + + +
    + +
    +
    + + + +
    + + + +
    +
    +
    +
    + + + + + + + + \ No newline at end of file diff --git a/develop/en/spec/database/db_inbox-entry-receiver/index.html b/develop/en/spec/database/db_inbox-entry-receiver/index.html new file mode 100644 index 0000000..06c44bf --- /dev/null +++ b/develop/en/spec/database/db_inbox-entry-receiver/index.html @@ -0,0 +1,3429 @@ + + + + + + + + + + + + + + + + + + + + + + inbox-entry-receiver - Friendica documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + +
    + + + + +
    + + +
    + +
    + + + + + + + + + +
    +
    + + + +
    +
    +
    + + + + + + +
    +
    +
    + + + +
    +
    +
    + + + +
    +
    +
    + + +
    +
    + + + + + + + + + + + + + +

    Table inbox-entry-receiver#

    +

    Receiver for the incoming activity

    +

    Fields#

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    FieldDescriptionTypeNullKeyDefaultExtra
    queue-idint unsignedNOPRINULL
    uidUser idmediumint unsignedNOPRINULL
    +

    Indexes#

    + + + + + + + + + + + + + + + + + +
    NameFields
    PRIMARYqueue-id, uid
    uiduid
    +

    Foreign Keys#

    + + + + + + + + + + + + + + + + + + + + +
    FieldTarget TableTarget Field
    queue-idinbox-entryid
    uiduseruid
    +

    Return to database documentation

    + + +
    + +
    +
    + + + +
    + + + +
    +
    +
    +
    + + + + + + + + \ No newline at end of file diff --git a/develop/en/spec/database/db_inbox-entry/index.html b/develop/en/spec/database/db_inbox-entry/index.html new file mode 100644 index 0000000..c42ad51 --- /dev/null +++ b/develop/en/spec/database/db_inbox-entry/index.html @@ -0,0 +1,3544 @@ + + + + + + + + + + + + + + + + + + + + + + inbox-entry - Friendica documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + +
    + + + + +
    + + +
    + +
    + + + + + + + + + +
    +
    + + + +
    +
    +
    + + + + + + +
    +
    +
    + + + +
    +
    +
    + + + +
    +
    +
    + + +
    +
    + + + + + + + + + + + + + +

    Table inbox-entry#

    +

    Incoming activity

    +

    Fields#

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    FieldDescriptionTypeNullKeyDefaultExtra
    idsequential IDint unsignedNOPRINULLauto_increment
    activity-idid of the incoming activityvarbinary(255)YESNULL
    object-idvarbinary(255)YESNULL
    in-reply-to-idvarbinary(255)YESNULL
    conversationvarbinary(255)YESNULL
    typeType of the activityvarchar(64)YESNULL
    object-typeType of the object activityvarchar(64)YESNULL
    object-object-typeType of the object's object activityvarchar(64)YESNULL
    receivedReceiving datedatetimeYESNULL
    activityThe JSON activitymediumtextYESNULL
    signervarchar(255)YESNULL
    pushIs the entry pushed or have pulled it?booleanYESNULL
    trustDo we trust this entry?booleanYESNULL
    widWorkerqueue idint unsignedYESNULL
    +

    Indexes#

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    NameFields
    PRIMARYid
    activity-idUNIQUE, activity-id
    object-idobject-id
    receivedreceived
    widwid
    +

    Foreign Keys#

    + + + + + + + + + + + + + + + +
    FieldTarget TableTarget Field
    widworkerqueueid
    +

    Return to database documentation

    + + +
    + +
    +
    + + + +
    + + + +
    +
    +
    +
    + + + + + + + + \ No newline at end of file diff --git a/develop/en/spec/database/db_inbox-status/index.html b/develop/en/spec/database/db_inbox-status/index.html new file mode 100644 index 0000000..ae229be --- /dev/null +++ b/develop/en/spec/database/db_inbox-status/index.html @@ -0,0 +1,3478 @@ + + + + + + + + + + + + + + + + + + + + + + inbox-status - Friendica documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + +
    + + + + +
    + + +
    + +
    + + + + + + + + + +
    +
    + + + +
    +
    +
    + + + + + + +
    +
    +
    + + + +
    +
    +
    + + + +
    +
    +
    + + +
    +
    + + + + + + + + + + + + + +

    Table inbox-status#

    +

    Status of ActivityPub inboxes

    +

    Fields#

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    FieldDescriptionTypeNullKeyDefaultExtra
    urlURL of the inboxvarbinary(255)NOPRINULL
    uri-idItem-uri id of inbox urlint unsignedYESNULL
    createdCreation date of this entrydatetimeNO0001-01-01 00:00:00
    successDate of the last successful deliverydatetimeNO0001-01-01 00:00:00
    failureDate of the last failed deliverydatetimeNO0001-01-01 00:00:00
    previousPrevious delivery datedatetimeNO0001-01-01 00:00:00
    archiveIs the inbox archived?booleanNO0
    sharedIs it a shared inbox?booleanNO0
    +

    Indexes#

    + + + + + + + + + + + + + + + + + +
    NameFields
    PRIMARYurl
    uri-iduri-id
    +

    Foreign Keys#

    + + + + + + + + + + + + + + + +
    FieldTarget TableTarget Field
    uri-iditem-uriid
    +

    Return to database documentation

    + + +
    + +
    +
    + + + +
    + + + +
    +
    +
    +
    + + + + + + + + \ No newline at end of file diff --git a/develop/en/spec/database/db_intro/index.html b/develop/en/spec/database/db_intro/index.html new file mode 100644 index 0000000..995d96d --- /dev/null +++ b/develop/en/spec/database/db_intro/index.html @@ -0,0 +1,3531 @@ + + + + + + + + + + + + + + + + + + + + + + intro - Friendica documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + +
    + + + + +
    + + +
    + +
    + + + + + + + + + +
    +
    + + + +
    +
    +
    + + + + + + +
    +
    +
    + + + +
    +
    +
    + + + +
    +
    +
    + + +
    +
    + + + + + + + + + + + + + +

    Table intro#

    +

    Fields#

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    FieldDescriptionTypeNullKeyDefaultExtra
    idsequential IDint unsignedNOPRINULLauto_increment
    uidUser idmediumint unsignedNO0
    fiddeprecatedint unsignedYESNULL
    contact-idint unsignedNO0
    suggest-cidSuggested contactint unsignedYESNULL
    knowyoubooleanNO0
    duplexdeprecatedbooleanNO0
    notetextYESNULL
    hashvarchar(255)NO
    datetimedatetimeNO0001-01-01 00:00:00
    blockeddeprecatedbooleanNO0
    ignorebooleanNO0
    +

    Indexes#

    + + + + + + + + + + + + + + + + + + + + + + + + + +
    NameFields
    PRIMARYid
    contact-idcontact-id
    suggest-cidsuggest-cid
    uiduid
    +

    Foreign Keys#

    + + + + + + + + + + + + + + + + + + + + + + + + + +
    FieldTarget TableTarget Field
    uiduseruid
    contact-idcontactid
    suggest-cidcontactid
    +

    Return to database documentation

    + + +
    + +
    +
    + + + +
    + + + +
    +
    +
    +
    + + + + + + + + \ No newline at end of file diff --git a/develop/en/spec/database/db_item-uri/index.html b/develop/en/spec/database/db_item-uri/index.html new file mode 100644 index 0000000..c750b83 --- /dev/null +++ b/develop/en/spec/database/db_item-uri/index.html @@ -0,0 +1,3406 @@ + + + + + + + + + + + + + + + + + + + + + + item-uri - Friendica documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + +
    + + + + +
    + + +
    + +
    + + + + + + + + + +
    +
    + + + +
    +
    +
    + + + + + + +
    +
    +
    + + + +
    +
    +
    + + + +
    +
    +
    + + +
    +
    + + + + + + + + + + + + + +

    Table item-uri#

    +

    URI and GUID for items

    +

    Fields#

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    FieldDescriptionTypeNullKeyDefaultExtra
    idint unsignedNOPRINULLauto_increment
    uriURI of an itemvarbinary(255)NONULL
    guidA unique identifier for an itemvarbinary(255)YESNULL
    +

    Indexes#

    + + + + + + + + + + + + + + + + + + + + + +
    NameFields
    PRIMARYid
    uriUNIQUE, uri
    guidguid
    +

    Return to database documentation

    + + +
    + +
    +
    + + + +
    + + + +
    +
    +
    +
    + + + + + + + + \ No newline at end of file diff --git a/develop/en/spec/database/db_locks/index.html b/develop/en/spec/database/db_locks/index.html new file mode 100644 index 0000000..4683b2b --- /dev/null +++ b/develop/en/spec/database/db_locks/index.html @@ -0,0 +1,3419 @@ + + + + + + + + + + + + + + + + + + + + + + locks - Friendica documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + +
    + + + + +
    + + +
    + +
    + + + + + + + + + +
    +
    + + + +
    +
    +
    + + + + + + +
    +
    +
    + + + +
    +
    +
    + + + +
    +
    +
    + + +
    +
    + + + + + + + + + + + + + +

    Table locks#

    +

    Fields#

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    FieldDescriptionTypeNullKeyDefaultExtra
    idsequential IDint unsignedNOPRINULLauto_increment
    namevarchar(128)NO
    lockedbooleanNO0
    pidProcess IDint unsignedNO0
    expiresdatetime of cache expirationdatetimeNO0001-01-01 00:00:00
    +

    Indexes#

    + + + + + + + + + + + + + + + + + +
    NameFields
    PRIMARYid
    name_expiresname, expires
    +

    Return to database documentation

    + + +
    + +
    +
    + + + +
    + + + +
    +
    +
    +
    + + + + + + + + \ No newline at end of file diff --git a/develop/en/spec/database/db_mail/index.html b/develop/en/spec/database/db_mail/index.html new file mode 100644 index 0000000..53fd8c6 --- /dev/null +++ b/develop/en/spec/database/db_mail/index.html @@ -0,0 +1,3656 @@ + + + + + + + + + + + + + + + + + + + + + + mail - Friendica documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + +
    + + + + +
    + + +
    + +
    + + + + + + + + + +
    +
    + + + +
    +
    +
    + + + + + + +
    +
    +
    + + + +
    +
    +
    + + + +
    +
    +
    + + +
    +
    + + + + + + + + + + + + + +

    Table mail#

    +

    private messages

    +

    Fields#

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    FieldDescriptionTypeNullKeyDefaultExtra
    idsequential IDint unsignedNOPRINULLauto_increment
    uidOwner User idmediumint unsignedNO0
    guidA unique identifier for this private messagevarchar(255)NO
    from-namename of the sendervarchar(255)NO
    from-photocontact photo link of the sendervarchar(255)NO
    from-urlprofile linke of the sendervarchar(255)NO
    contact-idcontact.idvarchar(255)YESNULL
    author-idLink to the contact table with uid=0 of the author of the mailint unsignedYESNULL
    convidconv.idint unsignedYESNULL
    titlevarchar(255)NO
    bodymediumtextYESNULL
    seenif message visited it is 1booleanNO0
    replybooleanNO0
    repliedbooleanNO0
    unknownif sender not in the contact table this is 1booleanNO0
    urivarchar(255)NO
    uri-idItem-uri id of the related mailint unsignedYESNULL
    parent-urivarchar(255)NO
    parent-uri-idItem-uri id of the parent of the related mailint unsignedYESNULL
    thr-parentvarchar(255)YESNULL
    thr-parent-idId of the item-uri table that contains the thread parent uriint unsignedYESNULL
    createdcreation time of the private messagedatetimeNO0001-01-01 00:00:00
    +

    Indexes#

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    NameFields
    PRIMARYid
    uid_seenuid, seen
    convidconvid
    uriuri(64)
    parent-uriparent-uri(64)
    contactidcontact-id(32)
    author-idauthor-id
    uri-iduri-id
    parent-uri-idparent-uri-id
    thr-parent-idthr-parent-id
    +

    Foreign Keys#

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    FieldTarget TableTarget Field
    uiduseruid
    author-idcontactid
    uri-iditem-uriid
    parent-uri-iditem-uriid
    thr-parent-iditem-uriid
    +

    Return to database documentation

    + + +
    + +
    +
    + + + +
    + + + +
    +
    +
    +
    + + + + + + + + \ No newline at end of file diff --git a/develop/en/spec/database/db_mailacct/index.html b/develop/en/spec/database/db_mailacct/index.html new file mode 100644 index 0000000..3f86e4a --- /dev/null +++ b/develop/en/spec/database/db_mailacct/index.html @@ -0,0 +1,3523 @@ + + + + + + + + + + + + + + + + + + + + + + mailacct - Friendica documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + +
    + + + + +
    + + +
    + +
    + + + + + + + + + +
    +
    + + + +
    +
    +
    + + + + + + +
    +
    +
    + + + +
    +
    +
    + + + +
    +
    +
    + + +
    +
    + + + + + + + + + + + + + +

    Table mailacct#

    +

    Mail account data for fetching mails

    +

    Fields#

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    FieldDescriptionTypeNullKeyDefaultExtra
    idsequential IDint unsignedNOPRINULLauto_increment
    uidUser idmediumint unsignedNO0
    servervarchar(255)NO
    portsmallint unsignedNO0
    ssltypevarchar(16)NO
    mailboxvarchar(255)NO
    uservarchar(255)NO
    passtextYESNULL
    reply_tovarchar(255)NO
    actiontinyint unsignedNO0
    movetofoldervarchar(255)NO
    pubmailbooleanNO0
    last_checkdatetimeNO0001-01-01 00:00:00
    +

    Indexes#

    + + + + + + + + + + + + + + + + + +
    NameFields
    PRIMARYid
    uiduid
    +

    Foreign Keys#

    + + + + + + + + + + + + + + + +
    FieldTarget TableTarget Field
    uiduseruid
    +

    Return to database documentation

    + + +
    + +
    +
    + + + +
    + + + +
    +
    +
    +
    + + + + + + + + \ No newline at end of file diff --git a/develop/en/spec/database/db_manage/index.html b/develop/en/spec/database/db_manage/index.html new file mode 100644 index 0000000..6d42d58 --- /dev/null +++ b/develop/en/spec/database/db_manage/index.html @@ -0,0 +1,3442 @@ + + + + + + + + + + + + + + + + + + + + + + manage - Friendica documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + +
    + + + + +
    + + +
    + +
    + + + + + + + + + +
    +
    + + + +
    +
    +
    + + + + + + +
    +
    +
    + + + +
    +
    +
    + + + +
    +
    +
    + + +
    +
    + + + + + + + + + + + + + +

    Table manage#

    +

    table of accounts that can manage each other

    +

    Fields#

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    FieldDescriptionTypeNullKeyDefaultExtra
    idsequential IDint unsignedNOPRINULLauto_increment
    uidUser idmediumint unsignedNO0
    midUser idmediumint unsignedNO0
    +

    Indexes#

    + + + + + + + + + + + + + + + + + + + + + +
    NameFields
    PRIMARYid
    uid_midUNIQUE, uid, mid
    midmid
    +

    Foreign Keys#

    + + + + + + + + + + + + + + + + + + + + +
    FieldTarget TableTarget Field
    uiduseruid
    miduseruid
    +

    Return to database documentation

    + + +
    + +
    +
    + + + +
    + + + +
    +
    +
    +
    + + + + + + + + \ No newline at end of file diff --git a/develop/en/spec/database/db_notification/index.html b/develop/en/spec/database/db_notification/index.html new file mode 100644 index 0000000..0f15353 --- /dev/null +++ b/develop/en/spec/database/db_notification/index.html @@ -0,0 +1,3536 @@ + + + + + + + + + + + + + + + + + + + + + + notification - Friendica documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + +
    + + + + +
    + + +
    + +
    + + + + + + + + + +
    +
    + + + +
    +
    +
    + + + + + + +
    +
    +
    + + + +
    +
    +
    + + + +
    +
    +
    + + +
    +
    + + + + + + + + + + + + + +

    Table notification#

    +

    notifications

    +

    Fields#

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    FieldDescriptionTypeNullKeyDefaultExtra
    idsequential IDint unsignedNOPRINULLauto_increment
    uidOwner User idmediumint unsignedYESNULL
    vidId of the verb table entry that contains the activity verbssmallint unsignedYESNULL
    typesmallint unsignedYESNULL
    actor-idLink to the contact table with uid=0 of the actor that caused the notificationint unsignedYESNULL
    target-uri-idItem-uri id of the related postint unsignedYESNULL
    parent-uri-idItem-uri id of the parent of the related postint unsignedYESNULL
    createddatetimeYESNULL
    seenSeen on the desktopbooleanYES0
    dismissedDismissed via the APIbooleanYES0
    +

    Indexes#

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    NameFields
    PRIMARYid
    uid_vid_type_actor-id_target-uri-idUNIQUE, uid, vid, type, actor-id, target-uri-id
    vidvid
    actor-idactor-id
    target-uri-idtarget-uri-id
    parent-uri-idparent-uri-id
    seen_uidseen, uid
    +

    Foreign Keys#

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    FieldTarget TableTarget Field
    uiduseruid
    vidverbid
    actor-idcontactid
    target-uri-iditem-uriid
    parent-uri-iditem-uriid
    +

    Return to database documentation

    + + +
    + +
    +
    + + + +
    + + + +
    +
    +
    +
    + + + + + + + + \ No newline at end of file diff --git a/develop/en/spec/database/db_notify-threads/index.html b/develop/en/spec/database/db_notify-threads/index.html new file mode 100644 index 0000000..e2c6a44 --- /dev/null +++ b/develop/en/spec/database/db_notify-threads/index.html @@ -0,0 +1,3477 @@ + + + + + + + + + + + + + + + + + + + + + + notify-threads - Friendica documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + +
    + + + + +
    + + +
    + +
    + + + + + + + + + +
    +
    + + + +
    +
    +
    + + + + + + +
    +
    +
    + + + +
    +
    +
    + + + +
    +
    +
    + + +
    +
    + + + + + + + + + + + + + +

    Table notify-threads#

    +

    Fields#

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    FieldDescriptionTypeNullKeyDefaultExtra
    idsequential IDint unsignedNOPRINULLauto_increment
    notify-idint unsignedNO0
    master-parent-itemDeprecatedint unsignedYESNULL
    master-parent-uri-idItem-uri id of the parent of the related postint unsignedYESNULL
    parent-itemint unsignedNO0
    receiver-uidUser idmediumint unsignedNO0
    +

    Indexes#

    + + + + + + + + + + + + + + + + + + + + + + + + + +
    NameFields
    PRIMARYid
    master-parent-uri-idmaster-parent-uri-id
    receiver-uidreceiver-uid
    notify-idnotify-id
    +

    Foreign Keys#

    + + + + + + + + + + + + + + + + + + + + + + + + + +
    FieldTarget TableTarget Field
    notify-idnotifyid
    master-parent-uri-iditem-uriid
    receiver-uiduseruid
    +

    Return to database documentation

    + + +
    + +
    +
    + + + +
    + + + +
    +
    +
    +
    + + + + + + + + \ No newline at end of file diff --git a/develop/en/spec/database/db_notify/index.html b/develop/en/spec/database/db_notify/index.html new file mode 100644 index 0000000..e3fa5d0 --- /dev/null +++ b/develop/en/spec/database/db_notify/index.html @@ -0,0 +1,3594 @@ + + + + + + + + + + + + + + + + + + + + + + notify - Friendica documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + +
    + + + + +
    + + +
    + +
    + + + + + + + + + +
    +
    + + + +
    +
    +
    + + + + + + +
    +
    +
    + + + +
    +
    +
    + + + +
    +
    +
    + + +
    +
    + + + + + + + + + + + + + +

    Table notify#

    +

    [Deprecated] User notifications

    +

    Fields#

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    FieldDescriptionTypeNullKeyDefaultExtra
    idsequential IDint unsignedNOPRINULLauto_increment
    typesmallint unsignedNO0
    namevarchar(255)NO
    urlvarchar(255)NO
    photovarchar(255)NO
    datedatetimeNO0001-01-01 00:00:00
    msgmediumtextYESNULL
    uidOwner User idmediumint unsignedNO0
    linkvarchar(255)NO
    iidint unsignedYESNULL
    parentint unsignedYESNULL
    uri-idItem-uri id of the related postint unsignedYESNULL
    parent-uri-idItem-uri id of the parent of the related postint unsignedYESNULL
    seenbooleanNO0
    verbvarchar(100)NO
    otypevarchar(10)NO
    name_cacheCached bbcode parsing of nametinytextYESNULL
    msg_cacheCached bbcode parsing of msgmediumtextYESNULL
    +

    Indexes#

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    NameFields
    PRIMARYid
    seen_uid_dateseen, uid, date
    uid_dateuid, date
    uid_type_linkuid, type, link(190)
    uri-iduri-id
    parent-uri-idparent-uri-id
    +

    Foreign Keys#

    + + + + + + + + + + + + + + + + + + + + + + + + + +
    FieldTarget TableTarget Field
    uiduseruid
    uri-iditem-uriid
    parent-uri-iditem-uriid
    +

    Return to database documentation

    + + +
    + +
    +
    + + + +
    + + + +
    +
    +
    +
    + + + + + + + + \ No newline at end of file diff --git a/develop/en/spec/database/db_oembed/index.html b/develop/en/spec/database/db_oembed/index.html new file mode 100644 index 0000000..6ae7853 --- /dev/null +++ b/develop/en/spec/database/db_oembed/index.html @@ -0,0 +1,3411 @@ + + + + + + + + + + + + + + + + + + + + + + oembed - Friendica documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + +
    + + + + +
    + + +
    + +
    + + + + + + + + + +
    +
    + + + +
    +
    +
    + + + + + + +
    +
    +
    + + + +
    +
    +
    + + + +
    +
    +
    + + +
    +
    + + + + + + + + + + + + + +

    Table oembed#

    +

    cache for OEmbed queries

    +

    Fields#

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    FieldDescriptionTypeNullKeyDefaultExtra
    urlpage urlvarbinary(255)NOPRINULL
    maxwidthMaximum width passed to Oembedmediumint unsignedNOPRINULL
    contentOEmbed data of the pagemediumtextYESNULL
    createddatetime of creationdatetimeNO0001-01-01 00:00:00
    +

    Indexes#

    + + + + + + + + + + + + + + + + + +
    NameFields
    PRIMARYurl, maxwidth
    createdcreated
    +

    Return to database documentation

    + + +
    + +
    +
    + + + +
    + + + +
    +
    +
    +
    + + + + + + + + \ No newline at end of file diff --git a/develop/en/spec/database/db_openwebauth-token/index.html b/develop/en/spec/database/db_openwebauth-token/index.html new file mode 100644 index 0000000..ac4ffda --- /dev/null +++ b/develop/en/spec/database/db_openwebauth-token/index.html @@ -0,0 +1,3460 @@ + + + + + + + + + + + + + + + + + + + + + + openwebauth-token - Friendica documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + +
    + + + + +
    + + +
    + +
    + + + + + + + + + +
    +
    + + + +
    +
    +
    + + + + + + +
    +
    +
    + + + +
    +
    +
    + + + +
    +
    +
    + + +
    +
    + + + + + + + + + + + + + +

    Table openwebauth-token#

    +

    Store OpenWebAuth token to verify contacts

    +

    Fields#

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    FieldDescriptionTypeNullKeyDefaultExtra
    idsequential IDint unsignedNOPRINULLauto_increment
    uidUser id - currently unusedmediumint unsignedNO0
    typeVerify typevarchar(32)NO
    tokenA generated tokenvarchar(255)NO
    metavarchar(255)NO
    createddatetime of creationdatetimeNO0001-01-01 00:00:00
    +

    Indexes#

    + + + + + + + + + + + + + + + + + +
    NameFields
    PRIMARYid
    uiduid
    +

    Foreign Keys#

    + + + + + + + + + + + + + + + +
    FieldTarget TableTarget Field
    uiduseruid
    +

    Return to database documentation

    + + +
    + +
    +
    + + + +
    + + + +
    +
    +
    +
    + + + + + + + + \ No newline at end of file diff --git a/develop/en/spec/database/db_parsed_url/index.html b/develop/en/spec/database/db_parsed_url/index.html new file mode 100644 index 0000000..77d9ef0 --- /dev/null +++ b/develop/en/spec/database/db_parsed_url/index.html @@ -0,0 +1,3442 @@ + + + + + + + + + + + + + + + + + + + + + + parsed_url - Friendica documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + +
    + + + + +
    + + +
    + +
    + + + + + + + + + +
    +
    + + + +
    +
    +
    + + + + + + +
    +
    +
    + + + +
    +
    +
    + + + +
    +
    +
    + + +
    +
    + + + + + + + + + + + + + +

    Table parsed_url#

    +

    cache for 'parse_url' queries

    +

    Fields#

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    FieldDescriptionTypeNullKeyDefaultExtra
    url_hashpage url hashbinary(64)NOPRINULL
    guessingis the 'guessing' mode active?booleanNOPRI0
    oembedis the data the result of oembed?booleanNOPRI0
    urlpage urltextNONULL
    contentpage datamediumtextYESNULL
    createddatetime of creationdatetimeNO0001-01-01 00:00:00
    expiresdatetime of expirationdatetimeNO0001-01-01 00:00:00
    +

    Indexes#

    + + + + + + + + + + + + + + + + + + + + + +
    NameFields
    PRIMARYurl_hash, guessing, oembed
    createdcreated
    expiresexpires
    +

    Return to database documentation

    + + +
    + +
    +
    + + + +
    + + + +
    +
    +
    +
    + + + + + + + + \ No newline at end of file diff --git a/develop/en/spec/database/db_pconfig/index.html b/develop/en/spec/database/db_pconfig/index.html new file mode 100644 index 0000000..4451ec1 --- /dev/null +++ b/develop/en/spec/database/db_pconfig/index.html @@ -0,0 +1,3451 @@ + + + + + + + + + + + + + + + + + + + + + + pconfig - Friendica documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + +
    + + + + +
    + + +
    + +
    + + + + + + + + + +
    +
    + + + +
    +
    +
    + + + + + + +
    +
    +
    + + + +
    +
    +
    + + + +
    +
    +
    + + +
    +
    + + + + + + + + + + + + + +

    Table pconfig#

    +

    personal (per user) configuration storage

    +

    Fields#

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    FieldDescriptionTypeNullKeyDefaultExtra
    idPrimary keyint unsignedNOPRINULLauto_increment
    uidUser idmediumint unsignedNO0
    catCategoryvarchar(50)NO
    kKeyvarchar(100)NO
    vValuemediumtextYESNULL
    +

    Indexes#

    + + + + + + + + + + + + + + + + + +
    NameFields
    PRIMARYid
    uid_cat_kUNIQUE, uid, cat, k
    +

    Foreign Keys#

    + + + + + + + + + + + + + + + +
    FieldTarget TableTarget Field
    uiduseruid
    +

    Return to database documentation

    + + +
    + +
    +
    + + + +
    + + + +
    +
    +
    +
    + + + + + + + + \ No newline at end of file diff --git a/develop/en/spec/database/db_permissionset/index.html b/develop/en/spec/database/db_permissionset/index.html new file mode 100644 index 0000000..ebc1d3d --- /dev/null +++ b/develop/en/spec/database/db_permissionset/index.html @@ -0,0 +1,3459 @@ + + + + + + + + + + + + + + + + + + + + + + permissionset - Friendica documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + +
    + + + + +
    + + +
    + +
    + + + + + + + + + +
    +
    + + + +
    +
    +
    + + + + + + +
    +
    +
    + + + +
    +
    +
    + + + +
    +
    +
    + + +
    +
    + + + + + + + + + + + + + +

    Table permissionset#

    +

    Fields#

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    FieldDescriptionTypeNullKeyDefaultExtra
    idsequential IDint unsignedNOPRINULLauto_increment
    uidOwner id of this permission setmediumint unsignedNO0
    allow_cidAccess Control - list of allowed contact.id '<19><78>'mediumtextYESNULL
    allow_gidAccess Control - list of allowed groupsmediumtextYESNULL
    deny_cidAccess Control - list of denied contact.idmediumtextYESNULL
    deny_gidAccess Control - list of denied groupsmediumtextYESNULL
    +

    Indexes#

    + + + + + + + + + + + + + + + + + +
    NameFields
    PRIMARYid
    uid_allow_cid_allow_gid_deny_cid_deny_giduid, allow_cid(50), allow_gid(30), deny_cid(50), deny_gid(30)
    +

    Foreign Keys#

    + + + + + + + + + + + + + + + +
    FieldTarget TableTarget Field
    uiduseruid
    +

    Return to database documentation

    + + +
    + +
    +
    + + + +
    + + + +
    +
    +
    +
    + + + + + + + + \ No newline at end of file diff --git a/develop/en/spec/database/db_photo/index.html b/develop/en/spec/database/db_photo/index.html new file mode 100644 index 0000000..3962f77 --- /dev/null +++ b/develop/en/spec/database/db_photo/index.html @@ -0,0 +1,3687 @@ + + + + + + + + + + + + + + + + + + + + + + photo - Friendica documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + +
    + + + + +
    + + +
    + +
    + + + + + + + + + +
    +
    + + + +
    +
    +
    + + + + + + +
    +
    +
    + + + +
    +
    +
    + + + +
    +
    +
    + + +
    +
    + + + + + + + + + + + + + +

    Table photo#

    +

    photo storage

    +

    Fields#

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    FieldDescriptionTypeNullKeyDefaultExtra
    idsequential IDint unsignedNOPRINULLauto_increment
    uidOwner User idmediumint unsignedNO0
    contact-idcontact.idint unsignedNO0
    guidA unique identifier for this photochar(16)NO
    resource-idchar(32)NO
    hashhash value of the photochar(32)YESNULL
    createdcreation datedatetimeNO0001-01-01 00:00:00
    editedlast edited datedatetimeNO0001-01-01 00:00:00
    titlevarchar(255)NO
    desctextYESNULL
    albumThe name of the album to which the photo belongsvarchar(255)NO
    photo-typeUser avatar, user banner, contact avatar, contact banner or defaulttinyint unsignedYESNULL
    filenamevarchar(255)NO
    typevarchar(30)NOimage/jpeg
    heightsmallint unsignedNO0
    widthsmallint unsignedNO0
    datasizeint unsignedNO0
    datamediumblobNONULL
    scaletinyint unsignedNO0
    profilebooleanNO0
    allow_cidAccess Control - list of allowed contact.id '<19><78>'mediumtextYESNULL
    allow_gidAccess Control - list of allowed groupsmediumtextYESNULL
    deny_cidAccess Control - list of denied contact.idmediumtextYESNULL
    deny_gidAccess Control - list of denied groupsmediumtextYESNULL
    accessibleMake photo publicly accessible, ignoring permissionsbooleanNO0
    backend-classStorage backend classtinytextYESNULL
    backend-refStorage backend data referencetextYESNULL
    updateddatetimeNO0001-01-01 00:00:00
    +

    Indexes#

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    NameFields
    PRIMARYid
    contactidcontact-id
    uid_contactiduid, contact-id
    uid_profileuid, profile
    uid_album_scale_createduid, album(32), scale, created
    uid_album_resource-id_createduid, album(32), resource-id, created
    resource-idresource-id
    uid_photo-typeuid, photo-type
    +

    Foreign Keys#

    + + + + + + + + + + + + + + + + + + + + +
    FieldTarget TableTarget Field
    uiduseruid
    contact-idcontactid
    +

    Return to database documentation

    + + +
    + +
    +
    + + + +
    + + + +
    +
    +
    +
    + + + + + + + + \ No newline at end of file diff --git a/develop/en/spec/database/db_post-category/index.html b/develop/en/spec/database/db_post-category/index.html new file mode 100644 index 0000000..b8b12eb --- /dev/null +++ b/develop/en/spec/database/db_post-category/index.html @@ -0,0 +1,3456 @@ + + + + + + + + + + + + + + + + + + + + + + post-category - Friendica documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + +
    + + + + +
    + + +
    + +
    + + + + + + + + + +
    +
    + + + +
    +
    +
    + + + + + + +
    +
    +
    + + + +
    +
    +
    + + + +
    +
    +
    + + +
    +
    + + + + + + + + + + + + + +

    Table post-category#

    +

    post relation to categories

    +

    Fields#

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    FieldDescriptionTypeNullKeyDefaultExtra
    uri-idId of the item-uri table entry that contains the item uriint unsignedNOPRINULL
    uidUser idmediumint unsignedNOPRI0
    typetinyint unsignedNOPRI0
    tidint unsignedNOPRI0
    +

    Indexes#

    + + + + + + + + + + + + + + + + + + + + + +
    NameFields
    PRIMARYuri-id, uid, type, tid
    tidtid
    uid_uri-iduid, uri-id
    +

    Foreign Keys#

    + + + + + + + + + + + + + + + + + + + + + + + + + +
    FieldTarget TableTarget Field
    uri-iditem-uriid
    uiduseruid
    tidtagid
    +

    Return to database documentation

    + + +
    + +
    +
    + + + +
    + + + +
    +
    +
    +
    + + + + + + + + \ No newline at end of file diff --git a/develop/en/spec/database/db_post-collection/index.html b/develop/en/spec/database/db_post-collection/index.html new file mode 100644 index 0000000..f0b5d4d --- /dev/null +++ b/develop/en/spec/database/db_post-collection/index.html @@ -0,0 +1,3424 @@ + + + + + + + + + + + + + + + + + + + + + + post-collection - Friendica documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + +
    + + + + +
    + + +
    + +
    + + + + + + + + + +
    +
    + + + +
    +
    +
    + + + + + + +
    +
    +
    + + + +
    +
    +
    + + + +
    +
    +
    + + +
    +
    + + + + + + + + + + + + + +

    Table post-collection#

    +

    Collection of posts

    +

    Fields#

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    FieldDescriptionTypeNullKeyDefaultExtra
    uri-idId of the item-uri table entry that contains the item uriint unsignedNOPRINULL
    type0 - Featuredtinyint unsignedNOPRI0
    +

    Indexes#

    + + + + + + + + + + + + + + + + + +
    NameFields
    PRIMARYuri-id, type
    typetype
    +

    Foreign Keys#

    + + + + + + + + + + + + + + + +
    FieldTarget TableTarget Field
    uri-iditem-uriid
    +

    Return to database documentation

    + + +
    + +
    +
    + + + +
    + + + +
    +
    +
    +
    + + + + + + + + \ No newline at end of file diff --git a/develop/en/spec/database/db_post-content/index.html b/develop/en/spec/database/db_post-content/index.html new file mode 100644 index 0000000..3617858 --- /dev/null +++ b/develop/en/spec/database/db_post-content/index.html @@ -0,0 +1,3567 @@ + + + + + + + + + + + + + + + + + + + + + + post-content - Friendica documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + +
    + + + + +
    + + +
    + +
    + + + + + + + + + +
    +
    + + + +
    +
    +
    + + + + + + +
    +
    +
    + + + +
    +
    +
    + + + +
    +
    +
    + + +
    +
    + + + + + + + + + + + + + +

    Table post-content#

    +

    Content for all posts

    +

    Fields#

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    FieldDescriptionTypeNullKeyDefaultExtra
    uri-idId of the item-uri table entry that contains the item uriint unsignedNOPRINULL
    titleitem titlevarchar(255)NO
    content-warningvarchar(255)NO
    bodyitem body contentmediumtextYESNULL
    raw-bodyBody without embedded media linksmediumtextYESNULL
    locationtext location where this item originatedvarchar(255)NO
    coordlongitude/latitude pair representing location where this item originatedvarchar(255)NO
    languageLanguage information about this posttextYESNULL
    appapplication which generated this itemvarchar(255)NO
    rendered-hashvarchar(32)NO
    rendered-htmlitem.body converted to htmlmediumtextYESNULL
    object-typeActivityStreams object typevarchar(100)NO
    objectJSON encoded object structure unless it is an implied object (normal post)textYESNULL
    target-typeActivityStreams target type if applicable (URI)varchar(100)NO
    targetJSON encoded target structure if usedtextYESNULL
    resource-idUsed to link other tables to items, it identifies the linked resource (e.g. photo) and if set must also set resource_typevarchar(32)NO
    plinkpermalink or URL to a displayable copy of the message at its sourcevarchar(255)NO
    +

    Indexes#

    + + + + + + + + + + + + + + + + + + + + + + + + + +
    NameFields
    PRIMARYuri-id
    plinkplink(191)
    resource-idresource-id
    title-content-warning-bodyFULLTEXT, title, content-warning, body
    +

    Foreign Keys#

    + + + + + + + + + + + + + + + +
    FieldTarget TableTarget Field
    uri-iditem-uriid
    +

    Return to database documentation

    + + +
    + +
    +
    + + + +
    + + + +
    +
    +
    +
    + + + + + + + + \ No newline at end of file diff --git a/develop/en/spec/database/db_post-delivery-data/index.html b/develop/en/spec/database/db_post-delivery-data/index.html new file mode 100644 index 0000000..2d20ad6 --- /dev/null +++ b/develop/en/spec/database/db_post-delivery-data/index.html @@ -0,0 +1,3501 @@ + + + + + + + + + + + + + + + + + + + + + + post-delivery-data - Friendica documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + +
    + + + + +
    + + +
    + +
    + + + + + + + + + +
    +
    + + + +
    +
    +
    + + + + + + +
    +
    +
    + + + +
    +
    +
    + + + +
    +
    +
    + + +
    +
    + + + + + + + + + + + + + +

    Table post-delivery-data#

    +

    Delivery data for items

    +

    Fields#

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    FieldDescriptionTypeNullKeyDefaultExtra
    uri-idId of the item-uri table entry that contains the item uriint unsignedNOPRINULL
    postoptsExternal post connectors add their network name to this comma-separated string to identify that they should be delivered to these networks during deliverytextYESNULL
    informAdditional receivers of the linked itemmediumtextYESNULL
    queue_countInitial number of delivery recipients, used as item.delivery_queue_countmediumintNO0
    queue_doneNumber of successful deliveries, used as item.delivery_queue_donemediumintNO0
    queue_failedNumber of unsuccessful deliveries, used as item.delivery_queue_failedmediumintNO0
    activitypubNumber of successful deliveries via ActivityPubmediumintNO0
    dfrnNumber of successful deliveries via DFRNmediumintNO0
    legacy_dfrnNumber of successful deliveries via legacy DFRNmediumintNO0
    diasporaNumber of successful deliveries via DiasporamediumintNO0
    ostatusNumber of successful deliveries via OStatusmediumintNO0
    +

    Indexes#

    + + + + + + + + + + + + + +
    NameFields
    PRIMARYuri-id
    +

    Foreign Keys#

    + + + + + + + + + + + + + + + +
    FieldTarget TableTarget Field
    uri-iditem-uriid
    +

    Return to database documentation

    + + +
    + +
    +
    + + + +
    + + + +
    +
    +
    +
    + + + + + + + + \ No newline at end of file diff --git a/develop/en/spec/database/db_post-delivery/index.html b/develop/en/spec/database/db_post-delivery/index.html new file mode 100644 index 0000000..8c1ca16 --- /dev/null +++ b/develop/en/spec/database/db_post-delivery/index.html @@ -0,0 +1,3483 @@ + + + + + + + + + + + + + + + + + + + + + + post-delivery - Friendica documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + +
    + + + + +
    + + +
    + +
    + + + + + + + + + +
    +
    + + + +
    +
    +
    + + + + + + +
    +
    +
    + + + +
    +
    +
    + + + +
    +
    +
    + + +
    +
    + + + + + + + + + + + + + +

    Table post-delivery#

    +

    Delivery data for posts for the batch processing

    +

    Fields#

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    FieldDescriptionTypeNullKeyDefaultExtra
    uri-idId of the item-uri table entry that contains the item uriint unsignedNOPRINULL
    inbox-idItem-uri id of inbox urlint unsignedNOPRINULL
    uidDelivering usermediumint unsignedYESNULL
    createddatetimeYES0001-01-01 00:00:00
    commandvarbinary(32)YESNULL
    failedNumber of times the delivery has failedtinyintYES0
    receiversJSON encoded array with the receiving contactsmediumtextYESNULL
    +

    Indexes#

    + + + + + + + + + + + + + + + + + + + + + +
    NameFields
    PRIMARYuri-id, inbox-id
    inbox-id_createdinbox-id, created
    uiduid
    +

    Foreign Keys#

    + + + + + + + + + + + + + + + + + + + + + + + + + +
    FieldTarget TableTarget Field
    uri-iditem-uriid
    inbox-iditem-uriid
    uiduseruid
    +

    Return to database documentation

    + + +
    + +
    +
    + + + +
    + + + +
    +
    +
    +
    + + + + + + + + \ No newline at end of file diff --git a/develop/en/spec/database/db_post-history/index.html b/develop/en/spec/database/db_post-history/index.html new file mode 100644 index 0000000..22aa789 --- /dev/null +++ b/develop/en/spec/database/db_post-history/index.html @@ -0,0 +1,3564 @@ + + + + + + + + + + + + + + + + + + + + + + post-history - Friendica documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + +
    + + + + +
    + + +
    + +
    + + + + + + + + + +
    +
    + + + +
    +
    +
    + + + + + + +
    +
    +
    + + + +
    +
    +
    + + + +
    +
    +
    + + +
    +
    + + + + + + + + + + + + + +

    Table post-history#

    +

    Post history

    +

    Fields#

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    FieldDescriptionTypeNullKeyDefaultExtra
    uri-idId of the item-uri table entry that contains the item uriint unsignedNOPRINULL
    editedDate of editdatetimeNOPRI0001-01-01 00:00:00
    titleitem titlevarchar(255)NO
    content-warningvarchar(255)NO
    bodyitem body contentmediumtextYESNULL
    raw-bodyBody without embedded media linksmediumtextYESNULL
    locationtext location where this item originatedvarchar(255)NO
    coordlongitude/latitude pair representing location where this item originatedvarchar(255)NO
    languageLanguage information about this posttextYESNULL
    appapplication which generated this itemvarchar(255)NO
    rendered-hashvarchar(32)NO
    rendered-htmlitem.body converted to htmlmediumtextYESNULL
    object-typeActivityStreams object typevarchar(100)NO
    objectJSON encoded object structure unless it is an implied object (normal post)textYESNULL
    target-typeActivityStreams target type if applicable (URI)varchar(100)NO
    targetJSON encoded target structure if usedtextYESNULL
    resource-idUsed to link other tables to items, it identifies the linked resource (e.g. photo) and if set must also set resource_typevarchar(32)NO
    plinkpermalink or URL to a displayable copy of the message at its sourcevarchar(255)NO
    +

    Indexes#

    + + + + + + + + + + + + + +
    NameFields
    PRIMARYuri-id, edited
    +

    Foreign Keys#

    + + + + + + + + + + + + + + + +
    FieldTarget TableTarget Field
    uri-iditem-uriid
    +

    Return to database documentation

    + + +
    + +
    +
    + + + +
    + + + +
    +
    +
    +
    + + + + + + + + \ No newline at end of file diff --git a/develop/en/spec/database/db_post-link/index.html b/develop/en/spec/database/db_post-link/index.html new file mode 100644 index 0000000..a0a1904 --- /dev/null +++ b/develop/en/spec/database/db_post-link/index.html @@ -0,0 +1,3442 @@ + + + + + + + + + + + + + + + + + + + + + + post-link - Friendica documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + +
    + + + + +
    + + +
    + +
    + + + + + + + + + +
    +
    + + + +
    +
    +
    + + + + + + +
    +
    +
    + + + +
    +
    +
    + + + +
    +
    +
    + + +
    +
    + + + + + + + + + + + + + +

    Table post-link#

    +

    Post related external links

    +

    Fields#

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    FieldDescriptionTypeNullKeyDefaultExtra
    idsequential IDint unsignedNOPRINULLauto_increment
    uri-idId of the item-uri table entry that contains the item uriint unsignedNONULL
    urlExternal URLvarbinary(511)NONULL
    mimetypevarchar(60)YESNULL
    +

    Indexes#

    + + + + + + + + + + + + + + + + + +
    NameFields
    PRIMARYid
    uri-id-urlUNIQUE, uri-id, url
    +

    Foreign Keys#

    + + + + + + + + + + + + + + + +
    FieldTarget TableTarget Field
    uri-iditem-uriid
    +

    Return to database documentation

    + + +
    + +
    +
    + + + +
    + + + +
    +
    +
    +
    + + + + + + + + \ No newline at end of file diff --git a/develop/en/spec/database/db_post-media/index.html b/develop/en/spec/database/db_post-media/index.html new file mode 100644 index 0000000..54f1bad --- /dev/null +++ b/develop/en/spec/database/db_post-media/index.html @@ -0,0 +1,3581 @@ + + + + + + + + + + + + + + + + + + + + + + post-media - Friendica documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + +
    + + + + +
    + + +
    + +
    + + + + + + + + + +
    +
    + + + +
    +
    +
    + + + + + + +
    +
    +
    + + + +
    +
    +
    + + + +
    +
    +
    + + +
    +
    + + + + + + + + + + + + + +

    Table post-media#

    +

    Attached media

    +

    Fields#

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    FieldDescriptionTypeNullKeyDefaultExtra
    idsequential IDint unsignedNOPRINULLauto_increment
    uri-idId of the item-uri table entry that contains the item uriint unsignedNONULL
    urlMedia URLvarbinary(1024)NONULL
    typeMedia typetinyint unsignedNO0
    mimetypevarchar(60)YESNULL
    heightHeight of the mediasmallint unsignedYESNULL
    widthWidth of the mediasmallint unsignedYESNULL
    sizeMedia sizebigint unsignedYESNULL
    previewPreview URLvarbinary(512)YESNULL
    preview-heightHeight of the preview picturesmallint unsignedYESNULL
    preview-widthWidth of the preview picturesmallint unsignedYESNULL
    descriptiontextYESNULL
    nameName of the mediavarchar(255)YESNULL
    author-urlURL of the author of the mediavarbinary(255)YESNULL
    author-nameName of the author of the mediavarchar(255)YESNULL
    author-imageImage of the author of the mediavarbinary(255)YESNULL
    publisher-urlURL of the publisher of the mediavarbinary(255)YESNULL
    publisher-nameName of the publisher of the mediavarchar(255)YESNULL
    publisher-imageImage of the publisher of the mediavarbinary(255)YESNULL
    +

    Indexes#

    + + + + + + + + + + + + + + + + + + + + + +
    NameFields
    PRIMARYid
    uri-id-urlUNIQUE, uri-id, url(512)
    uri-id-iduri-id, id
    +

    Foreign Keys#

    + + + + + + + + + + + + + + + +
    FieldTarget TableTarget Field
    uri-iditem-uriid
    +

    Return to database documentation

    + + +
    + +
    +
    + + + +
    + + + +
    +
    +
    +
    + + + + + + + + \ No newline at end of file diff --git a/develop/en/spec/database/db_post-question-option/index.html b/develop/en/spec/database/db_post-question-option/index.html new file mode 100644 index 0000000..799a5cf --- /dev/null +++ b/develop/en/spec/database/db_post-question-option/index.html @@ -0,0 +1,3438 @@ + + + + + + + + + + + + + + + + + + + + + + post-question-option - Friendica documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + +
    + + + + +
    + + +
    + +
    + + + + + + + + + +
    +
    + + + +
    +
    +
    + + + + + + +
    +
    +
    + + + +
    +
    +
    + + + +
    +
    +
    + + +
    +
    + + + + + + + + + + + + + +

    Table post-question-option#

    +

    Question option

    +

    Fields#

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    FieldDescriptionTypeNullKeyDefaultExtra
    idId of the questionint unsignedNOPRINULL
    uri-idId of the item-uri table entry that contains the item uriint unsignedNOPRINULL
    nameName of the optionvarchar(255)YESNULL
    repliesNumber of replies for this question optionint unsignedYESNULL
    +

    Indexes#

    + + + + + + + + + + + + + +
    NameFields
    PRIMARYuri-id, id
    +

    Foreign Keys#

    + + + + + + + + + + + + + + + +
    FieldTarget TableTarget Field
    uri-iditem-uriid
    +

    Return to database documentation

    + + +
    + +
    +
    + + + +
    + + + +
    +
    +
    +
    + + + + + + + + \ No newline at end of file diff --git a/develop/en/spec/database/db_post-question/index.html b/develop/en/spec/database/db_post-question/index.html new file mode 100644 index 0000000..e8be129 --- /dev/null +++ b/develop/en/spec/database/db_post-question/index.html @@ -0,0 +1,3451 @@ + + + + + + + + + + + + + + + + + + + + + + post-question - Friendica documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + +
    + + + + +
    + + +
    + +
    + + + + + + + + + +
    +
    + + + +
    +
    +
    + + + + + + +
    +
    +
    + + + +
    +
    +
    + + + +
    +
    +
    + + +
    +
    + + + + + + + + + + + + + +

    Table post-question#

    +

    Question

    +

    Fields#

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    FieldDescriptionTypeNullKeyDefaultExtra
    idsequential IDint unsignedNOPRINULLauto_increment
    uri-idId of the item-uri table entry that contains the item uriint unsignedNONULL
    multipleMultiple choicebooleanNO0
    votersNumber of voters for this questionint unsignedYESNULL
    end-timeQuestion end timedatetimeYES0001-01-01 00:00:00
    +

    Indexes#

    + + + + + + + + + + + + + + + + + +
    NameFields
    PRIMARYid
    uri-idUNIQUE, uri-id
    +

    Foreign Keys#

    + + + + + + + + + + + + + + + +
    FieldTarget TableTarget Field
    uri-iditem-uriid
    +

    Return to database documentation

    + + +
    + +
    +
    + + + +
    + + + +
    +
    +
    +
    + + + + + + + + \ No newline at end of file diff --git a/develop/en/spec/database/db_post-tag/index.html b/develop/en/spec/database/db_post-tag/index.html new file mode 100644 index 0000000..b034468 --- /dev/null +++ b/develop/en/spec/database/db_post-tag/index.html @@ -0,0 +1,3456 @@ + + + + + + + + + + + + + + + + + + + + + + post-tag - Friendica documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + +
    + + + + +
    + + +
    + +
    + + + + + + + + + +
    +
    + + + +
    +
    +
    + + + + + + +
    +
    +
    + + + +
    +
    +
    + + + +
    +
    +
    + + +
    +
    + + + + + + + + + + + + + +

    Table post-tag#

    +

    post relation to tags

    +

    Fields#

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    FieldDescriptionTypeNullKeyDefaultExtra
    uri-idId of the item-uri table entry that contains the item uriint unsignedNOPRINULL
    typetinyint unsignedNOPRI0
    tidint unsignedNOPRI0
    cidContact id of the mentioned public contactint unsignedNOPRI0
    +

    Indexes#

    + + + + + + + + + + + + + + + + + + + + + +
    NameFields
    PRIMARYuri-id, type, tid, cid
    tidtid
    cidcid
    +

    Foreign Keys#

    + + + + + + + + + + + + + + + + + + + + + + + + + +
    FieldTarget TableTarget Field
    uri-iditem-uriid
    tidtagid
    cidcontactid
    +

    Return to database documentation

    + + +
    + +
    +
    + + + +
    + + + +
    +
    +
    +
    + + + + + + + + \ No newline at end of file diff --git a/develop/en/spec/database/db_post-thread-user/index.html b/develop/en/spec/database/db_post-thread-user/index.html new file mode 100644 index 0000000..c41efa8 --- /dev/null +++ b/develop/en/spec/database/db_post-thread-user/index.html @@ -0,0 +1,3718 @@ + + + + + + + + + + + + + + + + + + + + + + post-thread-user - Friendica documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + +
    + + + + +
    + + +
    + +
    + + + + + + + + + +
    +
    + + + +
    +
    +
    + + + + + + +
    +
    +
    + + + +
    +
    +
    + + + +
    +
    +
    + + +
    +
    + + + + + + + + + + + + + +

    Table post-thread-user#

    +

    Thread related data per user

    +

    Fields#

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    FieldDescriptionTypeNullKeyDefaultExtra
    uri-idId of the item-uri table entry that contains the item uriint unsignedNOPRINULL
    conversation-idId of the item-uri table entry that contains the conversation uriint unsignedYESNULL
    owner-idItem ownerint unsignedNO0
    author-idItem authorint unsignedNO0
    causer-idLink to the contact table with uid=0 of the contact that caused the item creationint unsignedYESNULL
    networkchar(4)NO
    createddatetimeNO0001-01-01 00:00:00
    receiveddatetimeNO0001-01-01 00:00:00
    changedDate that something in the conversation changed, indicating clients should fetch the conversation againdatetimeNO0001-01-01 00:00:00
    commenteddatetimeNO0001-01-01 00:00:00
    uidOwner id which owns this copy of the itemmediumint unsignedNOPRI0
    pinneddeprecatedbooleanNO0
    starredbooleanNO0
    ignoredIgnore updates for this threadbooleanNO0
    wallThis item was posted to the wall of uidbooleanNO0
    mentionbooleanNO0
    pubmailbooleanNO0
    forum_modeDeprecatedtinyint unsignedNO0
    contact-idcontact.idint unsignedNO0
    unseenpost has not been seenbooleanNO1
    hiddenMarker to hide the post from the userbooleanNO0
    originitem originated at this sitebooleanNO0
    psidID of the permission set of this postint unsignedYESNULL
    post-user-idId of the post-user tableint unsignedYESNULL
    +

    Indexes#

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    NameFields
    PRIMARYuid, uri-id
    uri-iduri-id
    conversation-idconversation-id
    owner-idowner-id
    author-idauthor-id
    causer-idcauser-id
    uiduid
    contact-idcontact-id
    psidpsid
    post-user-idpost-user-id
    commentedcommented
    uid_receiveduid, received
    uid_wall_receiveduid, wall, received
    uid_commenteduid, commented
    uid_starreduid, starred
    uid_mentionuid, mention
    +

    Foreign Keys#

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    FieldTarget TableTarget Field
    uri-iditem-uriid
    conversation-iditem-uriid
    owner-idcontactid
    author-idcontactid
    causer-idcontactid
    uiduseruid
    contact-idcontactid
    psidpermissionsetid
    post-user-idpost-userid
    +

    Return to database documentation

    + + +
    + +
    +
    + + + +
    + + + +
    +
    +
    +
    + + + + + + + + \ No newline at end of file diff --git a/develop/en/spec/database/db_post-thread/index.html b/develop/en/spec/database/db_post-thread/index.html new file mode 100644 index 0000000..7842b13 --- /dev/null +++ b/develop/en/spec/database/db_post-thread/index.html @@ -0,0 +1,3536 @@ + + + + + + + + + + + + + + + + + + + + + + post-thread - Friendica documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + +
    + + + + +
    + + +
    + +
    + + + + + + + + + +
    +
    + + + +
    +
    +
    + + + + + + +
    +
    +
    + + + +
    +
    +
    + + + +
    +
    +
    + + +
    +
    + + + + + + + + + + + + + +

    Table post-thread#

    +

    Thread related data

    +

    Fields#

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    FieldDescriptionTypeNullKeyDefaultExtra
    uri-idId of the item-uri table entry that contains the item uriint unsignedNOPRINULL
    conversation-idId of the item-uri table entry that contains the conversation uriint unsignedYESNULL
    owner-idItem ownerint unsignedNO0
    author-idItem authorint unsignedNO0
    causer-idLink to the contact table with uid=0 of the contact that caused the item creationint unsignedYESNULL
    networkchar(4)NO
    createddatetimeNO0001-01-01 00:00:00
    receiveddatetimeNO0001-01-01 00:00:00
    changedDate that something in the conversation changed, indicating clients should fetch the conversation againdatetimeNO0001-01-01 00:00:00
    commenteddatetimeNO0001-01-01 00:00:00
    +

    Indexes#

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    NameFields
    PRIMARYuri-id
    conversation-idconversation-id
    owner-idowner-id
    author-idauthor-id
    causer-idcauser-id
    receivedreceived
    commentedcommented
    +

    Foreign Keys#

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    FieldTarget TableTarget Field
    uri-iditem-uriid
    conversation-iditem-uriid
    owner-idcontactid
    author-idcontactid
    causer-idcontactid
    +

    Return to database documentation

    + + +
    + +
    +
    + + + +
    + + + +
    +
    +
    +
    + + + + + + + + \ No newline at end of file diff --git a/develop/en/spec/database/db_post-user-notification/index.html b/develop/en/spec/database/db_post-user-notification/index.html new file mode 100644 index 0000000..f2e91d4 --- /dev/null +++ b/develop/en/spec/database/db_post-user-notification/index.html @@ -0,0 +1,3438 @@ + + + + + + + + + + + + + + + + + + + + + + post-user-notification - Friendica documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + +
    + + + + +
    + + +
    + +
    + + + + + + + + + +
    +
    + + + +
    +
    +
    + + + + + + +
    +
    +
    + + + +
    +
    +
    + + + +
    +
    +
    + + +
    +
    + + + + + + + + + + + + + +

    Table post-user-notification#

    +

    User post notifications

    +

    Fields#

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    FieldDescriptionTypeNullKeyDefaultExtra
    uri-idId of the item-uri table entry that contains the item uriint unsignedNOPRINULL
    uidOwner id which owns this copy of the itemmediumint unsignedNOPRINULL
    notification-typesmallint unsignedNO0
    +

    Indexes#

    + + + + + + + + + + + + + + + + + +
    NameFields
    PRIMARYuid, uri-id
    uri-iduri-id
    +

    Foreign Keys#

    + + + + + + + + + + + + + + + + + + + + +
    FieldTarget TableTarget Field
    uri-iditem-uriid
    uiduseruid
    +

    Return to database documentation

    + + +
    + +
    +
    + + + +
    + + + +
    +
    +
    +
    + + + + + + + + \ No newline at end of file diff --git a/develop/en/spec/database/db_post-user/index.html b/develop/en/spec/database/db_post-user/index.html new file mode 100644 index 0000000..1f65201 --- /dev/null +++ b/develop/en/spec/database/db_post-user/index.html @@ -0,0 +1,3803 @@ + + + + + + + + + + + + + + + + + + + + + + post-user - Friendica documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + +
    + + + + +
    + + +
    + +
    + + + + + + + + + +
    +
    + + + +
    +
    +
    + + + + + + +
    +
    +
    + + + +
    +
    +
    + + + +
    +
    +
    + + +
    +
    + + + + + + + + + + + + + +

    Table post-user#

    +

    User specific post data

    +

    Fields#

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    FieldDescriptionTypeNullKeyDefaultExtra
    idint unsignedNOPRINULLauto_increment
    uri-idId of the item-uri table entry that contains the item uriint unsignedNONULL
    parent-uri-idId of the item-uri table that contains the parent uriint unsignedYESNULL
    thr-parent-idId of the item-uri table that contains the thread parent uriint unsignedYESNULL
    external-idId of the item-uri table entry that contains the external uriint unsignedYESNULL
    createdCreation timestamp.datetimeNO0001-01-01 00:00:00
    editedDate of last edit (default is created)datetimeNO0001-01-01 00:00:00
    receiveddatetimedatetimeNO0001-01-01 00:00:00
    gravitytinyint unsignedNO0
    networkNetwork from where the item comes fromchar(4)NO
    owner-idLink to the contact table with uid=0 of the owner of this itemint unsignedNO0
    author-idLink to the contact table with uid=0 of the author of this itemint unsignedNO0
    causer-idLink to the contact table with uid=0 of the contact that caused the item creationint unsignedYESNULL
    post-typePost type (personal note, image, article, ...)tinyint unsignedNO0
    post-reasonReason why the post arrived at the usertinyint unsignedNO0
    vidId of the verb table entry that contains the activity verbssmallint unsignedYESNULL
    private0=public, 1=private, 2=unlistedtinyint unsignedNO0
    globalbooleanNO0
    visiblebooleanNO0
    deleteditem has been marked for deletionbooleanNO0
    uidOwner id which owns this copy of the itemmediumint unsignedNONULL
    protocolProtocol used to deliver the item for this usertinyint unsignedYESNULL
    contact-idcontact.idint unsignedNO0
    event-idUsed to link to the event.idint unsignedYESNULL
    unseenpost has not been seenbooleanNO1
    hiddenMarker to hide the post from the userbooleanNO0
    notification-typetinyint unsignedNO0
    wallThis item was posted to the wall of uidbooleanNO0
    originitem originated at this sitebooleanNO0
    psidID of the permission set of this postint unsignedYESNULL
    +

    Indexes#

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    NameFields
    PRIMARYid
    uid_uri-idUNIQUE, uid, uri-id
    uri-iduri-id
    parent-uri-idparent-uri-id
    thr-parent-idthr-parent-id
    external-idexternal-id
    owner-idowner-id
    author-idauthor-id
    causer-idcauser-id
    vidvid
    contact-idcontact-id
    event-idevent-id
    psidpsid
    author-id_uidauthor-id, uid
    author-id_receivedauthor-id, received
    parent-uri-id_uidparent-uri-id, uid
    uid_contactiduid, contact-id
    uid_unseen_contactiduid, unseen, contact-id
    uid_unseenuid, unseen
    uid_hidden_uri-iduid, hidden, uri-id
    +

    Foreign Keys#

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    FieldTarget TableTarget Field
    uri-iditem-uriid
    parent-uri-iditem-uriid
    thr-parent-iditem-uriid
    external-iditem-uriid
    owner-idcontactid
    author-idcontactid
    causer-idcontactid
    vidverbid
    uiduseruid
    contact-idcontactid
    event-ideventid
    psidpermissionsetid
    +

    Return to database documentation

    + + +
    + +
    +
    + + + +
    + + + +
    +
    +
    +
    + + + + + + + + \ No newline at end of file diff --git a/develop/en/spec/database/db_post/index.html b/develop/en/spec/database/db_post/index.html new file mode 100644 index 0000000..cfa6bd8 --- /dev/null +++ b/develop/en/spec/database/db_post/index.html @@ -0,0 +1,3627 @@ + + + + + + + + + + + + + + + + + + + + + + post - Friendica documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + +
    + + + + +
    + + +
    + +
    + + + + + + + + + +
    +
    + + + +
    +
    +
    + + + + + + +
    +
    +
    + + + +
    +
    +
    + + + +
    +
    +
    + + +
    +
    + + + + + + + + + + + + + +

    Table post#

    +

    Structure for all posts

    +

    Fields#

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    FieldDescriptionTypeNullKeyDefaultExtra
    uri-idId of the item-uri table entry that contains the item uriint unsignedNOPRINULL
    parent-uri-idId of the item-uri table that contains the parent uriint unsignedYESNULL
    thr-parent-idId of the item-uri table that contains the thread parent uriint unsignedYESNULL
    external-idId of the item-uri table entry that contains the external uriint unsignedYESNULL
    createdCreation timestamp.datetimeNO0001-01-01 00:00:00
    editedDate of last edit (default is created)datetimeNO0001-01-01 00:00:00
    receiveddatetimedatetimeNO0001-01-01 00:00:00
    gravitytinyint unsignedNO0
    networkNetwork from where the item comes fromchar(4)NO
    owner-idLink to the contact table with uid=0 of the owner of this itemint unsignedNO0
    author-idLink to the contact table with uid=0 of the author of this itemint unsignedNO0
    causer-idLink to the contact table with uid=0 of the contact that caused the item creationint unsignedYESNULL
    post-typePost type (personal note, image, article, ...)tinyint unsignedNO0
    vidId of the verb table entry that contains the activity verbssmallint unsignedYESNULL
    private0=public, 1=private, 2=unlistedtinyint unsignedNO0
    globalbooleanNO0
    visiblebooleanNO0
    deleteditem has been marked for deletionbooleanNO0
    +

    Indexes#

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    NameFields
    PRIMARYuri-id
    parent-uri-idparent-uri-id
    thr-parent-idthr-parent-id
    external-idexternal-id
    owner-idowner-id
    author-idauthor-id
    causer-idcauser-id
    vidvid
    +

    Foreign Keys#

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    FieldTarget TableTarget Field
    uri-iditem-uriid
    parent-uri-iditem-uriid
    thr-parent-iditem-uriid
    external-iditem-uriid
    owner-idcontactid
    author-idcontactid
    causer-idcontactid
    vidverbid
    +

    Return to database documentation

    + + +
    + +
    +
    + + + +
    + + + +
    +
    +
    +
    + + + + + + + + \ No newline at end of file diff --git a/develop/en/spec/database/db_process/index.html b/develop/en/spec/database/db_process/index.html new file mode 100644 index 0000000..7370e7d --- /dev/null +++ b/develop/en/spec/database/db_process/index.html @@ -0,0 +1,3411 @@ + + + + + + + + + + + + + + + + + + + + + + process - Friendica documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + +
    + + + + +
    + + +
    + +
    + + + + + + + + + +
    +
    + + + +
    +
    +
    + + + + + + +
    +
    +
    + + + +
    +
    +
    + + + +
    +
    +
    + + +
    +
    + + + + + + + + + + + + + +

    Table process#

    +

    Currently running system processes

    +

    Fields#

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    FieldDescriptionTypeNullKeyDefaultExtra
    pidThe ID of the processint unsignedNOPRINULL
    hostnameThe name of the host the process is ran onvarchar(32)NOPRINULL
    commandvarbinary(32)NO
    createddatetimeNO0001-01-01 00:00:00
    +

    Indexes#

    + + + + + + + + + + + + + + + + + +
    NameFields
    PRIMARYpid, hostname
    commandcommand
    +

    Return to database documentation

    + + +
    + +
    +
    + + + +
    + + + +
    +
    +
    +
    + + + + + + + + \ No newline at end of file diff --git a/develop/en/spec/database/db_profile/index.html b/develop/en/spec/database/db_profile/index.html new file mode 100644 index 0000000..63700e5 --- /dev/null +++ b/develop/en/spec/database/db_profile/index.html @@ -0,0 +1,3797 @@ + + + + + + + + + + + + + + + + + + + + + + profile - Friendica documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + +
    + + + + +
    + + +
    + +
    + + + + + + + + + +
    +
    + + + +
    +
    +
    + + + + + + +
    +
    +
    + + + +
    +
    +
    + + + +
    +
    +
    + + +
    +
    + + + + + + + + + + + + + +

    Table profile#

    +

    user profiles data

    +

    Fields#

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    FieldDescriptionTypeNullKeyDefaultExtra
    idsequential IDint unsignedNOPRINULLauto_increment
    uidOwner User idmediumint unsignedNO0
    profile-nameDeprecatedvarchar(255)YESNULL
    is-defaultDeprecatedbooleanYESNULL
    hide-friendsHide friend list from viewers of this profilebooleanNO0
    namevarchar(255)NO
    pdescDeprecatedvarchar(255)YESNULL
    dobDay of birthvarchar(32)NO0000-00-00
    addressvarchar(255)NO
    localityvarchar(255)NO
    regionvarchar(255)NO
    postal-codevarchar(32)NO
    country-namevarchar(255)NO
    hometownDeprecatedvarchar(255)YESNULL
    genderDeprecatedvarchar(32)YESNULL
    maritalDeprecatedvarchar(255)YESNULL
    withDeprecatedtextYESNULL
    howlongDeprecateddatetimeYESNULL
    sexualDeprecatedvarchar(255)YESNULL
    politicDeprecatedvarchar(255)YESNULL
    religionDeprecatedvarchar(255)YESNULL
    pub_keywordstextYESNULL
    prv_keywordstextYESNULL
    likesDeprecatedtextYESNULL
    dislikesDeprecatedtextYESNULL
    aboutProfile descriptiontextYESNULL
    summaryDeprecatedvarchar(255)YESNULL
    musicDeprecatedtextYESNULL
    bookDeprecatedtextYESNULL
    tvDeprecatedtextYESNULL
    filmDeprecatedtextYESNULL
    interestDeprecatedtextYESNULL
    romanceDeprecatedtextYESNULL
    workDeprecatedtextYESNULL
    educationDeprecatedtextYESNULL
    contactDeprecatedtextYESNULL
    homepagevarchar(255)NO
    xmppXMPP addressvarchar(255)NO
    matrixMatrix addressvarchar(255)NO
    photovarchar(255)NO
    thumbvarchar(255)NO
    publishpublish default profile in local directorybooleanNO0
    net-publishpublish profile in global directorybooleanNO0
    +

    Indexes#

    + + + + + + + + + + + + + + + + + + + + + +
    NameFields
    PRIMARYid
    uid_is-defaultuid, is-default
    pub_keywordsFULLTEXT, pub_keywords
    +

    Foreign Keys#

    + + + + + + + + + + + + + + + +
    FieldTarget TableTarget Field
    uiduseruid
    +

    Return to database documentation

    + + +
    + +
    +
    + + + +
    + + + +
    +
    +
    +
    + + + + + + + + \ No newline at end of file diff --git a/develop/en/spec/database/db_profile_field/index.html b/develop/en/spec/database/db_profile_field/index.html new file mode 100644 index 0000000..e1baf05 --- /dev/null +++ b/develop/en/spec/database/db_profile_field/index.html @@ -0,0 +1,3491 @@ + + + + + + + + + + + + + + + + + + + + + + profile_field - Friendica documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + +
    + + + + +
    + + +
    + +
    + + + + + + + + + +
    +
    + + + +
    +
    +
    + + + + + + +
    +
    +
    + + + +
    +
    +
    + + + +
    +
    +
    + + +
    +
    + + + + + + + + + + + + + +

    Table profile_field#

    +

    Custom profile fields

    +

    Fields#

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    FieldDescriptionTypeNullKeyDefaultExtra
    idsequential IDint unsignedNOPRINULLauto_increment
    uidOwner user idmediumint unsignedNO0
    orderField ordering per usermediumint unsignedNO1
    psidID of the permission set of this profile field - 0 = publicint unsignedYESNULL
    labelLabel of the fieldvarchar(255)NO
    valueValue of the fieldtextYESNULL
    createdcreation timedatetimeNO0001-01-01 00:00:00
    editedlast edit timedatetimeNO0001-01-01 00:00:00
    +

    Indexes#

    + + + + + + + + + + + + + + + + + + + + + + + + + +
    NameFields
    PRIMARYid
    uiduid
    orderorder
    psidpsid
    +

    Foreign Keys#

    + + + + + + + + + + + + + + + + + + + + +
    FieldTarget TableTarget Field
    uiduseruid
    psidpermissionsetid
    +

    Return to database documentation

    + + +
    + +
    +
    + + + +
    + + + +
    +
    +
    +
    + + + + + + + + \ No newline at end of file diff --git a/develop/en/spec/database/db_push_subscriber/index.html b/develop/en/spec/database/db_push_subscriber/index.html new file mode 100644 index 0000000..6a19ccb --- /dev/null +++ b/develop/en/spec/database/db_push_subscriber/index.html @@ -0,0 +1,3500 @@ + + + + + + + + + + + + + + + + + + + + + + push_subscriber - Friendica documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + +
    + + + + +
    + + +
    + +
    + + + + + + + + + +
    +
    + + + +
    +
    +
    + + + + + + +
    +
    +
    + + + +
    +
    +
    + + + +
    +
    +
    + + +
    +
    + + + + + + + + + + + + + +

    Table push_subscriber#

    +

    Used for OStatus: Contains feed subscribers

    +

    Fields#

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    FieldDescriptionTypeNullKeyDefaultExtra
    idsequential IDint unsignedNOPRINULLauto_increment
    uidUser idmediumint unsignedNO0
    callback_urlvarchar(255)NO
    topicvarchar(255)NO
    nicknamevarchar(255)NO
    pushRetrial countertinyintNO0
    last_updateDate of last successful trialdatetimeNO0001-01-01 00:00:00
    next_tryNext retrial datedatetimeNO0001-01-01 00:00:00
    renewedDate of last subscription renewaldatetimeNO0001-01-01 00:00:00
    secretvarchar(255)NO
    +

    Indexes#

    + + + + + + + + + + + + + + + + + + + + + +
    NameFields
    PRIMARYid
    next_trynext_try
    uiduid
    +

    Foreign Keys#

    + + + + + + + + + + + + + + + +
    FieldTarget TableTarget Field
    uiduseruid
    +

    Return to database documentation

    + + +
    + +
    +
    + + + +
    + + + +
    +
    +
    +
    + + + + + + + + \ No newline at end of file diff --git a/develop/en/spec/database/db_register/index.html b/develop/en/spec/database/db_register/index.html new file mode 100644 index 0000000..b473211 --- /dev/null +++ b/develop/en/spec/database/db_register/index.html @@ -0,0 +1,3469 @@ + + + + + + + + + + + + + + + + + + + + + + register - Friendica documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + +
    + + + + +
    + + +
    + +
    + + + + + + + + + +
    +
    + + + +
    +
    +
    + + + + + + +
    +
    +
    + + + +
    +
    +
    + + + +
    +
    +
    + + +
    +
    + + + + + + + + + + + + + +

    Table register#

    +

    registrations requiring admin approval

    +

    Fields#

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    FieldDescriptionTypeNullKeyDefaultExtra
    idsequential IDint unsignedNOPRINULLauto_increment
    hashvarchar(255)NO
    createddatetimeNO0001-01-01 00:00:00
    uidUser idmediumint unsignedNO0
    passwordvarchar(255)NO
    languagevarchar(16)NO
    notetextYESNULL
    +

    Indexes#

    + + + + + + + + + + + + + + + + + +
    NameFields
    PRIMARYid
    uiduid
    +

    Foreign Keys#

    + + + + + + + + + + + + + + + +
    FieldTarget TableTarget Field
    uiduseruid
    +

    Return to database documentation

    + + +
    + +
    +
    + + + +
    + + + +
    +
    +
    +
    + + + + + + + + \ No newline at end of file diff --git a/develop/en/spec/database/db_search/index.html b/develop/en/spec/database/db_search/index.html new file mode 100644 index 0000000..b36bb41 --- /dev/null +++ b/develop/en/spec/database/db_search/index.html @@ -0,0 +1,3436 @@ + + + + + + + + + + + + + + + + + + + + + + search - Friendica documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + +
    + + + + +
    + + +
    + +
    + + + + + + + + + +
    +
    + + + +
    +
    +
    + + + + + + +
    +
    +
    + + + +
    +
    +
    + + + +
    +
    +
    + + +
    +
    + + + + + + + + + + + + + +

    Table search#

    +

    Fields#

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    FieldDescriptionTypeNullKeyDefaultExtra
    idsequential IDint unsignedNOPRINULLauto_increment
    uidUser idmediumint unsignedNO0
    termvarchar(255)NO
    +

    Indexes#

    + + + + + + + + + + + + + + + + + + + + + +
    NameFields
    PRIMARYid
    uid_termuid, term(64)
    termterm(64)
    +

    Foreign Keys#

    + + + + + + + + + + + + + + + +
    FieldTarget TableTarget Field
    uiduseruid
    +

    Return to database documentation

    + + +
    + +
    +
    + + + +
    + + + +
    +
    +
    +
    + + + + + + + + \ No newline at end of file diff --git a/develop/en/spec/database/db_session/index.html b/develop/en/spec/database/db_session/index.html new file mode 100644 index 0000000..0073a59 --- /dev/null +++ b/develop/en/spec/database/db_session/index.html @@ -0,0 +1,3415 @@ + + + + + + + + + + + + + + + + + + + + + + session - Friendica documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + +
    + + + + +
    + + +
    + +
    + + + + + + + + + +
    +
    + + + +
    +
    +
    + + + + + + +
    +
    +
    + + + +
    +
    +
    + + + +
    +
    +
    + + +
    +
    + + + + + + + + + + + + + +

    Table session#

    +

    web session storage

    +

    Fields#

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    FieldDescriptionTypeNullKeyDefaultExtra
    idsequential IDbigint unsignedNOPRINULLauto_increment
    sidvarbinary(255)NO
    datatextYESNULL
    expireint unsignedNO0
    +

    Indexes#

    + + + + + + + + + + + + + + + + + + + + + +
    NameFields
    PRIMARYid
    sidsid(64)
    expireexpire
    +

    Return to database documentation

    + + +
    + +
    +
    + + + +
    + + + +
    +
    +
    +
    + + + + + + + + \ No newline at end of file diff --git a/develop/en/spec/database/db_storage/index.html b/develop/en/spec/database/db_storage/index.html new file mode 100644 index 0000000..dd996e8 --- /dev/null +++ b/develop/en/spec/database/db_storage/index.html @@ -0,0 +1,3389 @@ + + + + + + + + + + + + + + + + + + + + + + storage - Friendica documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + +
    + + + + +
    + + +
    + +
    + + + + + + + + + +
    +
    + + + +
    +
    +
    + + + + + + +
    +
    +
    + + + +
    +
    +
    + + + +
    +
    +
    + + +
    +
    + + + + + + + + + + + + + +

    Table storage#

    +

    Data stored by Database storage backend

    +

    Fields#

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    FieldDescriptionTypeNullKeyDefaultExtra
    idAuto incremented image data idint unsignedNOPRINULLauto_increment
    datafile datalongblobNONULL
    +

    Indexes#

    + + + + + + + + + + + + + +
    NameFields
    PRIMARYid
    +

    Return to database documentation

    + + +
    + +
    +
    + + + +
    + + + +
    +
    +
    +
    + + + + + + + + \ No newline at end of file diff --git a/develop/en/spec/database/db_subscription/index.html b/develop/en/spec/database/db_subscription/index.html new file mode 100644 index 0000000..97de7b2 --- /dev/null +++ b/develop/en/spec/database/db_subscription/index.html @@ -0,0 +1,3532 @@ + + + + + + + + + + + + + + + + + + + + + + subscription - Friendica documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + +
    + + + + +
    + + +
    + +
    + + + + + + + + + +
    +
    + + + +
    +
    +
    + + + + + + +
    +
    +
    + + + +
    +
    +
    + + + +
    +
    +
    + + +
    +
    + + + + + + + + + + + + + +

    Table subscription#

    +

    Push Subscription for the API

    +

    Fields#

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    FieldDescriptionTypeNullKeyDefaultExtra
    idAuto incremented image data idint unsignedNOPRINULLauto_increment
    application-idint unsignedNONULL
    uidOwner User idmediumint unsignedNONULL
    endpointEndpoint URLvarchar(511)YESNULL
    pubkeyUser agent public keyvarchar(127)YESNULL
    secretAuth secretvarchar(32)YESNULL
    followbooleanYESNULL
    favouritebooleanYESNULL
    reblogbooleanYESNULL
    mentionbooleanYESNULL
    pollbooleanYESNULL
    follow_requestbooleanYESNULL
    statusbooleanYESNULL
    +

    Indexes#

    + + + + + + + + + + + + + + + + + + + + + +
    NameFields
    PRIMARYid
    application-id_uidUNIQUE, application-id, uid
    uid_application-iduid, application-id
    +

    Foreign Keys#

    + + + + + + + + + + + + + + + + + + + + +
    FieldTarget TableTarget Field
    application-idapplicationid
    uiduseruid
    +

    Return to database documentation

    + + +
    + +
    +
    + + + +
    + + + +
    +
    +
    +
    + + + + + + + + \ No newline at end of file diff --git a/develop/en/spec/database/db_tag/index.html b/develop/en/spec/database/db_tag/index.html new file mode 100644 index 0000000..6c3e8dc --- /dev/null +++ b/develop/en/spec/database/db_tag/index.html @@ -0,0 +1,3415 @@ + + + + + + + + + + + + + + + + + + + + + + tag - Friendica documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + +
    + + + + +
    + + +
    + +
    + + + + + + + + + +
    +
    + + + +
    +
    +
    + + + + + + +
    +
    +
    + + + +
    +
    +
    + + + +
    +
    +
    + + +
    +
    + + + + + + + + + + + + + +

    Table tag#

    +

    tags and mentions

    +

    Fields#

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    FieldDescriptionTypeNullKeyDefaultExtra
    idint unsignedNOPRINULLauto_increment
    namevarchar(96)NO
    urlvarbinary(255)NO
    typeType of the tag (Unknown, General Collection, Follower Collection or Account)tinyint unsignedYESNULL
    +

    Indexes#

    + + + + + + + + + + + + + + + + + + + + + +
    NameFields
    PRIMARYid
    type_name_urlUNIQUE, name, url
    urlurl
    +

    Return to database documentation

    + + +
    + +
    +
    + + + +
    + + + +
    +
    +
    +
    + + + + + + + + \ No newline at end of file diff --git a/develop/en/spec/database/db_user-contact/index.html b/develop/en/spec/database/db_user-contact/index.html new file mode 100644 index 0000000..94f5576 --- /dev/null +++ b/develop/en/spec/database/db_user-contact/index.html @@ -0,0 +1,3600 @@ + + + + + + + + + + + + + + + + + + + + + + user-contact - Friendica documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + +
    + + + + +
    + + +
    + +
    + + + + + + + + + +
    +
    + + + +
    +
    +
    + + + + + + +
    +
    +
    + + + +
    +
    +
    + + + +
    +
    +
    + + +
    +
    + + + + + + + + + + + + + +

    Table user-contact#

    +

    User specific public contact data

    +

    Fields#

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    FieldDescriptionTypeNullKeyDefaultExtra
    cidContact id of the linked public contactint unsignedNOPRI0
    uidUser idmediumint unsignedNOPRI0
    uri-idId of the item-uri table entry that contains the contact urlint unsignedYESNULL
    blockedContact is completely blocked for this userbooleanYESNULL
    ignoredPosts from this contact are ignoredbooleanYESNULL
    collapsedPosts from this contact are collapsedbooleanYESNULL
    hiddenThis contact is hidden from the othersbooleanYESNULL
    is-blockedUser is blocked by this contactbooleanYESNULL
    pendingbooleanYESNULL
    relThe kind of the relation between the user and the contacttinyint unsignedYESNULL
    infomediumtextYESNULL
    notify_new_postsbooleanYESNULL
    remote_selfbooleanYESNULL
    fetch_further_informationtinyint unsignedYESNULL
    ffi_keyword_denylisttextYESNULL
    subhubbooleanYESNULL
    hub-verifyvarchar(255)YESNULL
    protocolProtocol of the contactchar(4)YESNULL
    ratingAutomatically detected feed poll frequencytinyintYESNULL
    priorityFeed poll prioritytinyint unsignedYESNULL
    +

    Indexes#

    + + + + + + + + + + + + + + + + + + + + + +
    NameFields
    PRIMARYuid, cid
    cidcid
    uri-id_uidUNIQUE, uri-id, uid
    +

    Foreign Keys#

    + + + + + + + + + + + + + + + + + + + + + + + + + +
    FieldTarget TableTarget Field
    cidcontactid
    uiduseruid
    uri-iditem-uriid
    +

    Return to database documentation

    + + +
    + +
    +
    + + + +
    + + + +
    +
    +
    +
    + + + + + + + + \ No newline at end of file diff --git a/develop/en/spec/database/db_user/index.html b/develop/en/spec/database/db_user/index.html new file mode 100644 index 0000000..225ff6b --- /dev/null +++ b/develop/en/spec/database/db_user/index.html @@ -0,0 +1,3823 @@ + + + + + + + + + + + + + + + + + + + + + + user - Friendica documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + +
    + + + + +
    + + +
    + +
    + + + + + + + + + +
    +
    + + + +
    +
    +
    + + + + + + +
    +
    +
    + + + +
    +
    +
    + + + +
    +
    +
    + + +
    +
    + + + + + + + + + + + + + +

    Table user#

    +

    The local users

    +

    Fields#

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    FieldDescriptionTypeNullKeyDefaultExtra
    uidsequential IDmediumint unsignedNOPRINULLauto_increment
    parent-uidThe parent user that has full control about this usermediumint unsignedYESNULL
    guidA unique identifier for this uservarchar(64)NO
    usernameName that this user is known byvarchar(255)NO
    passwordencrypted passwordvarchar(255)NO
    legacy_passwordIs the password hash double-hashed?booleanNO0
    nicknamenick- and user namevarchar(255)NO
    emailthe users email addressvarchar(255)NO
    openidvarchar(255)NO
    timezonePHP-legal timezonevarchar(128)NO
    languagedefault languagevarchar(32)NOen
    register_datetimestamp of registrationdatetimeNO0001-01-01 00:00:00
    login_datetimestamp of last logindatetimeNO0001-01-01 00:00:00
    default-locationDefault for item.locationvarchar(255)NO
    allow_location1 allows to display the locationbooleanNO0
    themeuser theme preferencevarchar(255)NO
    pubkeyRSA public key 4096 bittextYESNULL
    prvkeyRSA private key 4096 bittextYESNULL
    spubkeytextYESNULL
    sprvkeytextYESNULL
    verifieduser is verified through emailbooleanNO0
    blocked1 for user is blockedbooleanNO0
    blockwallProhibit contacts to post to the profile page of the userbooleanNO0
    hidewallHide profile details from unkown viewersbooleanNO0
    blocktagsProhibit contacts to tag the post of this userbooleanNO0
    unkmailPermit unknown people to send private mails to this userbooleanNO0
    cntunkmailint unsignedNO10
    notify-flagsemail notification optionssmallint unsignedNO65535
    page-flagspage/profile typetinyint unsignedNO0
    account-typetinyint unsignedNO0
    prvnetsbooleanNO0
    pwdresetPassword reset request tokenvarchar(255)YESNULL
    pwdreset_timeTimestamp of the last password reset requestdatetimeYESNULL
    maxreqint unsignedNO10
    expireint unsignedNO0
    account_removedif 1 the account is removedbooleanNO0
    account_expiredbooleanNO0
    account_expires_ontimestamp when account expires and will be deleteddatetimeNO0001-01-01 00:00:00
    expire_notification_senttimestamp of last warning of account expirationdatetimeNO0001-01-01 00:00:00
    def_gidint unsignedNO0
    allow_ciddefault permission for this usermediumtextYESNULL
    allow_giddefault permission for this usermediumtextYESNULL
    deny_ciddefault permission for this usermediumtextYESNULL
    deny_giddefault permission for this usermediumtextYESNULL
    openidservertextYESNULL
    +

    Indexes#

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    NameFields
    PRIMARYuid
    nicknamenickname(32)
    parent-uidparent-uid
    guidguid
    emailemail(64)
    +

    Foreign Keys#

    + + + + + + + + + + + + + + + +
    FieldTarget TableTarget Field
    parent-uiduseruid
    +

    Return to database documentation

    + + +
    + +
    +
    + + + +
    + + + +
    +
    +
    +
    + + + + + + + + \ No newline at end of file diff --git a/develop/en/spec/database/db_userd/index.html b/develop/en/spec/database/db_userd/index.html new file mode 100644 index 0000000..fb6cdf9 --- /dev/null +++ b/develop/en/spec/database/db_userd/index.html @@ -0,0 +1,3393 @@ + + + + + + + + + + + + + + + + + + + + + + userd - Friendica documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + +
    + + + + +
    + + +
    + +
    + + + + + + + + + +
    +
    + + + +
    +
    +
    + + + + + + +
    +
    +
    + + + +
    +
    +
    + + + +
    +
    +
    + + +
    +
    + + + + + + + + + + + + + +

    Table userd#

    +

    Deleted usernames

    +

    Fields#

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    FieldDescriptionTypeNullKeyDefaultExtra
    idsequential IDint unsignedNOPRINULLauto_increment
    usernamevarchar(255)NONULL
    +

    Indexes#

    + + + + + + + + + + + + + + + + + +
    NameFields
    PRIMARYid
    usernameusername(32)
    +

    Return to database documentation

    + + +
    + +
    +
    + + + +
    + + + +
    +
    +
    +
    + + + + + + + + \ No newline at end of file diff --git a/develop/en/spec/database/db_verb/index.html b/develop/en/spec/database/db_verb/index.html new file mode 100644 index 0000000..728c0b9 --- /dev/null +++ b/develop/en/spec/database/db_verb/index.html @@ -0,0 +1,3393 @@ + + + + + + + + + + + + + + + + + + + + + + verb - Friendica documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + +
    + + + + +
    + + +
    + +
    + + + + + + + + + +
    +
    + + + +
    +
    +
    + + + + + + +
    +
    +
    + + + +
    +
    +
    + + + +
    +
    +
    + + +
    +
    + + + + + + + + + + + + + +

    Table verb#

    +

    Activity Verbs

    +

    Fields#

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    FieldDescriptionTypeNullKeyDefaultExtra
    idsmallint unsignedNOPRINULLauto_increment
    namevarchar(100)NO
    +

    Indexes#

    + + + + + + + + + + + + + + + + + +
    NameFields
    PRIMARYid
    namename
    +

    Return to database documentation

    + + +
    + +
    +
    + + + +
    + + + +
    +
    +
    +
    + + + + + + + + \ No newline at end of file diff --git a/develop/en/spec/database/db_worker-ipc/index.html b/develop/en/spec/database/db_worker-ipc/index.html new file mode 100644 index 0000000..ce7339b --- /dev/null +++ b/develop/en/spec/database/db_worker-ipc/index.html @@ -0,0 +1,3389 @@ + + + + + + + + + + + + + + + + + + + + + + worker-ipc - Friendica documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + +
    + + + + +
    + + +
    + +
    + + + + + + + + + +
    +
    + + + +
    +
    +
    + + + + + + +
    +
    +
    + + + +
    +
    +
    + + + +
    +
    +
    + + +
    +
    + + + + + + + + + + + + + +

    Table worker-ipc#

    +

    Inter process communication between the frontend and the worker

    +

    Fields#

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    FieldDescriptionTypeNullKeyDefaultExtra
    keyintNOPRINULL
    jobsFlag for outstanding jobsbooleanYESNULL
    +

    Indexes#

    + + + + + + + + + + + + + +
    NameFields
    PRIMARYkey
    +

    Return to database documentation

    + + +
    + +
    +
    + + + +
    + + + +
    +
    +
    +
    + + + + + + + + \ No newline at end of file diff --git a/develop/en/spec/database/db_workerqueue/index.html b/develop/en/spec/database/db_workerqueue/index.html new file mode 100644 index 0000000..183b43e --- /dev/null +++ b/develop/en/spec/database/db_workerqueue/index.html @@ -0,0 +1,3493 @@ + + + + + + + + + + + + + + + + + + + + + + workerqueue - Friendica documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + +
    + + + + +
    + + +
    + +
    + + + + + + + + + +
    +
    + + + +
    +
    +
    + + + + + + +
    +
    +
    + + + +
    +
    +
    + + + +
    +
    +
    + + +
    +
    + + + + + + + + + + + + + +

    Table workerqueue#

    +

    Background tasks queue entries

    +

    Fields#

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    FieldDescriptionTypeNullKeyDefaultExtra
    idAuto incremented worker task idint unsignedNOPRINULLauto_increment
    commandTask commandvarchar(100)YESNULL
    parameterTask parametermediumtextYESNULL
    priorityTask prioritytinyint unsignedNO0
    createdCreation datedatetimeNO0001-01-01 00:00:00
    pidProcess id of the workerint unsignedNO0
    executedExecution datedatetimeNO0001-01-01 00:00:00
    next_tryNext retrial datedatetimeNO0001-01-01 00:00:00
    retrialRetrial countertinyintNO0
    doneMarked 1 when the task was done - will be deleted laterbooleanNO0
    +

    Indexes#

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    NameFields
    PRIMARYid
    commandcommand
    done_command_parameterdone, command, parameter(64)
    done_executeddone, executed
    done_priority_retrial_createddone, priority, retrial, created
    done_priority_next_trydone, priority, next_try
    done_pid_next_trydone, pid, next_try
    done_pid_retrialdone, pid, retrial
    done_pid_priority_createddone, pid, priority, created
    +

    Return to database documentation

    + + +
    + +
    +
    + + + +
    + + + +
    +
    +
    +
    + + + + + + + + \ No newline at end of file diff --git a/develop/en/spec/database/index.html b/develop/en/spec/database/index.html new file mode 100644 index 0000000..1d5d8c0 --- /dev/null +++ b/develop/en/spec/database/index.html @@ -0,0 +1,3576 @@ + + + + + + + + + + + + + + + + + + + + + + Database Tables - Friendica documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + +
    + + + + +
    + + +
    + +
    + + + + + + + + + +
    +
    + + + +
    +
    +
    + + + + + + +
    +
    +
    + + + +
    +
    +
    + + + +
    +
    +
    + + +
    +
    + + + + + + + + + + + +

    Database Tables#

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    TableComment
    2fa_app_specific_passwordTwo-factor app-specific _password
    2fa_recovery_codesTwo-factor authentication recovery codes
    2fa_trusted_browserTwo-factor authentication trusted browsers
    addonregistered addons
    apcontactActivityPub compatible contacts - used in the ActivityPub implementation
    application-markerTimeline marker
    application-tokenOAuth user token
    applicationOAuth application
    attachfile attachments
    cacheStores temporary data
    configmain configuration storage
    contact-relationContact relations
    contactcontact table
    convprivate messages
    conversationRaw data and structure information for messages
    delayed-postPosts that are about to be distributed at a later time
    diaspora-interactionSigned Diaspora Interaction
    endpointActivityPub endpoints - used in the ActivityPub implementation
    eventEvents
    fcontactDiaspora compatible contacts - used in the Diaspora implementation
    fsuggestfriend suggestion stuff
    groupprivacy groups, group info
    group_memberprivacy groups, member info
    gserver-tagTags that the server has subscribed
    gserverGlobal servers
    hookaddon hook registry
    inbox-entry-receiverReceiver for the incoming activity
    inbox-entryIncoming activity
    inbox-statusStatus of ActivityPub inboxes
    intro
    item-uriURI and GUID for items
    locks
    mailprivate messages
    mailacctMail account data for fetching mails
    managetable of accounts that can manage each other
    notificationnotifications
    notify-threads
    notify[Deprecated] User notifications
    oembedcache for OEmbed queries
    openwebauth-tokenStore OpenWebAuth token to verify contacts
    parsed_urlcache for 'parse_url' queries
    pconfigpersonal (per user) configuration storage
    permissionset
    photophoto storage
    post-categorypost relation to categories
    post-collectionCollection of posts
    post-contentContent for all posts
    post-delivery-dataDelivery data for items
    post-deliveryDelivery data for posts for the batch processing
    post-historyPost history
    post-linkPost related external links
    post-mediaAttached media
    post-question-optionQuestion option
    post-questionQuestion
    post-tagpost relation to tags
    post-thread-userThread related data per user
    post-threadThread related data
    post-user-notificationUser post notifications
    post-userUser specific post data
    postStructure for all posts
    processCurrently running system processes
    profileuser profiles data
    profile_fieldCustom profile fields
    push_subscriberUsed for OStatus: Contains feed subscribers
    registerregistrations requiring admin approval
    search
    sessionweb session storage
    storageData stored by Database storage backend
    subscriptionPush Subscription for the API
    tagtags and mentions
    user-contactUser specific public contact data
    userThe local users
    userdDeleted usernames
    verbActivity Verbs
    worker-ipcInter process communication between the frontend and the worker
    workerqueueBackground tasks queue entries
    + + +
    + +
    +
    + + + +
    + + + +
    +
    +
    +
    + + + + + + + + \ No newline at end of file diff --git a/develop/en/spec/protocol/dfrn-snap2.jpg b/develop/en/spec/protocol/dfrn-snap2.jpg new file mode 100644 index 0000000..5697732 Binary files /dev/null and b/develop/en/spec/protocol/dfrn-snap2.jpg differ diff --git a/develop/en/spec/protocol/dfrn2.odt b/develop/en/spec/protocol/dfrn2.odt new file mode 100644 index 0000000..b95f6cc Binary files /dev/null and b/develop/en/spec/protocol/dfrn2.odt differ diff --git a/develop/en/spec/protocol/dfrn2.pdf b/develop/en/spec/protocol/dfrn2.pdf new file mode 100644 index 0000000..f524c45 Binary files /dev/null and b/develop/en/spec/protocol/dfrn2.pdf differ diff --git a/develop/en/spec/protocol/dfrn2_contact_confirmation.png b/develop/en/spec/protocol/dfrn2_contact_confirmation.png new file mode 100644 index 0000000..e192ebc Binary files /dev/null and b/develop/en/spec/protocol/dfrn2_contact_confirmation.png differ diff --git a/develop/en/spec/protocol/dfrn2_contact_confirmation.svg b/develop/en/spec/protocol/dfrn2_contact_confirmation.svg new file mode 100644 index 0000000..8126850 --- /dev/null +++ b/develop/en/spec/protocol/dfrn2_contact_confirmation.svg @@ -0,0 +1 @@ +Friendica - Contact confirmationbob@example.comkaren@karenhompage.comnotifications.phpnotifications_content()------------------------------------------ This is the page where Karen see Bobs friendship request- the submit form redirects to Karens local dfrn_confirm page($dfrn_id, $contact_id, $intro_id are submitted)dfrn_confirm.phpdfrn_confirm_post()SCENARIO 1 ( no $_POST['source_url'] available)--------------------------------------------------------------------------------- contact data come either form $handsfree (if autoconfirm) orfrom $_POST- get all data about Karen form the user table[Note: Bob have been issued an ID (contact issue-id) when he firstrequested the friendship. Locate Bobs contact record. At thistime, his record will have both pending and blocked set to 1.There won't be any dfrn_id if this is a network follower, so usethe contact_id instead]- search for Bob in the contact table by contact_id, dfrn_id andissued-id not empty (for the uid -> Karens user id)- if network = dfrn-> create a new keypair (prvkey & pubkey) and update thecontact[Note: Generate a key pair for all further communications withthis person. We have a keypair for every contact, and a site keyfor unknown people. This provides a means to carry onrelationships with other people any single key is compromised. Itis a robust key. We're much more worried about key leakagethan anybody cracking it.]-> update Bobs contact record (in the contact table) with thegenerated prvkey-> encrypting the dfrn_id with Karens prvkey (Bob can decrypt iton the other and with Karens site-pubkey) and add it to thetransmit params.-> encrypting Karens profile url with Bobs site-pubkey (Bobcan decrypt it with his own private key) and add it to thetransmit params.-> add the above generated public key to params whichgetting transmitted (if $aes_allow -> encrypt the the public key)-> add duplex state and page-flags to the params-> send params to Bobs dfrn_confirm page ($res =Network::post($dfrn_confirm,$params);dfrn_confirm_post()SCENARIO 2 ( $_POST['source_url'] is available)------------------------------------------------------------------------- get all data about Bob from the user table (prvkey and uid formBob )- decrypt the transmitted source_url (profile url) with Bobsprvkey- get data of Karen from contact table by her source_url (and byher user id)- decrypt the dfrn_id sent by Karen with Karens site-pubkey(taken from contact table)- if possible decrpyt the pubkey sent by Karen with the prvkey ofBob (taken from user table) -> if this is not possible use the rawpubkey- search if the dfrn_id is already present in the contact table (if itis prensent it is a duplicate)- update dfrn-id and pubkey for Karens contact entry in thecontact table-> set the relation for the contact and set pending = 0 andblocked = 0- update the relationship of the contact Karen-> if duplex delete the issued-id-> set blocked = 0 and pending = 0send a notificationdelete the intro of BobNote: this chart respects only dfrncontacts and focuses on key exchange(for other areas it might be veryincomplete) \ No newline at end of file diff --git a/develop/en/spec/protocol/dfrn2_contact_request.png b/develop/en/spec/protocol/dfrn2_contact_request.png new file mode 100644 index 0000000..7ca6bca Binary files /dev/null and b/develop/en/spec/protocol/dfrn2_contact_request.png differ diff --git a/develop/en/spec/protocol/dfrn2_contact_request.svg b/develop/en/spec/protocol/dfrn2_contact_request.svg new file mode 100644 index 0000000..9b75acd --- /dev/null +++ b/develop/en/spec/protocol/dfrn2_contact_request.svg @@ -0,0 +1 @@ +Friendica - Contact requestkarenn@karenhompage.combob@example.comdfrn_request.php-https://karenhompage/dfrn_request/karindfrn_request_post - SCENARIO 1----------------------------------------------- Cleanup old introductions that remain blocked + Cleanupany old email intros - which will have a greater lifetime- Probe::uri Bobs posted dfrn_url and get the network withwebfinger_dfrn- try to select all contact data of Bob (contact table) by theurl ($_POST['dfrn_url] and profile uid ($a->profile['uid'])where self = 0 to look if this contact is already there (ifissued-id or rel is already available return here because itseems that we are already connected)- create a issued-id with $issued_id = Strings::getRandomHex();- if we already found a contact record above update theissued-id with the one we have created- otherwise if Bob is not already in the contact table scrapeBobs profile and create a new contact with this data (e.g.the scraped issued-id / profiles pubkey becomes contactssite-pubkey) in the contact table (blocked = 1, pending = 1)- select this created contact from contact table and createan intro in the intro table (blocked = 1)$_POST['dfrn_url'] is transmited and is Bobs profile urlredirect to Bobs request pagegoaway($parms['dfrn-request'] . "?dfrn_url=$dfrn_url". '&dfrn_version=' .DFRN_PROTOCOL_VERSION. '&confirm_key=' . $hash. (($aes_allow) ? "&aes_allow=1" : ""));http://example.com/dfrn_request/bob?dfrn_url=687474703a2f2f6b6172656e686f6d65706167652e636f6d2f70726f66696c652f6b6172656e&aes_allow=1&confirm_key=”ABC123”dfrn_request.phphttp://example.com/dfrn_request/bob?dfrn_url=687474703a2f2f6b6172656e686f6d65706167652e636f6d2f70726f66696c652f6b6172656e&aes_allow=1&confirm_key=”ABC123”dfrn_request_content()------------------------------------------- copy the posted parameters (dfrn_url, key and so on)to $_POSTdfrn_request_post() - SCENARIO 2($_POST['localconfirm'] == 1)------------------------------------------------------------------------ if(local_user() && ($a->user['nickname'] == $a->argv[1]) && !empty($_POST['dfrn_url']))->- $confirm_key comes from $_POST- get data for contact Karen (contact table) by$dfrn_url (contacts url and nurl) -> if contact Karendoes already have a dfrn-id Bob seems alreadyconnected with Karen (abort here)- if this contact (Karen) isn't available in the contacttabel, scrape Karens profile page to pick up the dfrnlinks, key, fn, and photo- create a contact for Karen in the contact table withthe scraped data with blocked = 1 and pending = 1(Karens pubkey becomes the contact site-pubkey)- Network::fetchUrl($dfrn_request . '?confirm_key=' .$confirm_key);- Network::fetchUrl(http://karenhomepage.com/dfrn_request?confirm_key=”ABC123”)dfrn_request.phphttp://karenhomepage.com/dfrn_request?confirm_key=”ABC123”dfrn_request_content() -elseif (!empty($_GET['confirm_key']))----------------------------------------------------------------------------------------------- select the intro by confirm_key (intro table) -> get contact id- use the intro contact id to get the contact in the contact table- build a notification package ( notification(array.....) )- update intro in intro table (blocked = 0)Bob stays on his Friendica server- goaway($forwardurl);Note: this chart respects only dfrncontacts and focuses on key exchange(for other areas it might be veryincomplete)dfrn_request_content()------------------------------------- the page for the on Katrins server where Bob do a connectionrequest- the form transmit on submit Bobs profile url as dfrn_urlbob wants to make a request and is directed from karens profile page to karens dfrn-request pageredirict to bobs dfrn_request pagehttp://karenhomepage.com/dfrn_request?confirm_key=”ABC123”Bob fills request form and presses submit \ No newline at end of file diff --git a/develop/en/spec/protocol/message-flow/index.html b/develop/en/spec/protocol/message-flow/index.html new file mode 100644 index 0000000..a415ad0 --- /dev/null +++ b/develop/en/spec/protocol/message-flow/index.html @@ -0,0 +1,3416 @@ + + + + + + + + + + + + + + + + + + + + + + Message Flow - Friendica documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + +
    + + + + +
    + + +
    + +
    + + + + + + + + + +
    +
    + + + +
    +
    +
    + + + + + + +
    +
    +
    + + + + + + +
    +
    + + + + + + + + + + + + + +

    Friendica Message Flow#

    +

    This page documents some details of how messages get from one person to another in the Friendica network. +There are multiple paths, using multiple protocols and message formats.

    +

    Those attempting to understand these message flows should become familiar with (at the minimum) the DFRN protocol document and the message passing elements of the OStatus stack (salmon and Pubsubhubbub).

    +

    When a message is posted, all immediate deliveries to all networks are made using include/notifier.php, which chooses how (and to whom) to deliver the message. +This file also invokes the local side of all deliveries including DFRN-notify.

    +

    mod/dfrn_notify.php handles the remote side of DFRN-notify.

    +

    Local feeds are generated by mod/dfrn_poll.php - which also handles the remote side of DFRN-poll protocol.

    +

    Salmon notifications arrive via mod/salmon.php.

    +

    Push (pubsubhubbub) feeds arrive via mod/pubsub.php

    +

    DFRN-poll feed imports arrive via src/Worker/OnePoll.php as a scheduled task, this implements the local side of the DFRN-poll protocol.

    +

    Scenario #1. Bob posts a public status message#

    +

    This is a public message with no conversation members so no private transport is used. +There are two paths it can take - as a bbcode path to DFRN clients, and converted to HTML with the server's PuSH (pubsubhubbub) hubs notified. +When a PuSH hub is operational, dfrn-poll clients prefer to receive their information through the PuSH channel. +They will fall back on a daily poll in case the hub has delivery issues (this is quite common when using the default Google reference hub). +If there is no specified hub or hubs, DFRN clients will poll at a configurable (per-contact) rate at up to 5-minute intervals. +Feeds retrieved via dfrn-poll are bbcode and may also contain private conversations which the worker has permissions to see.

    +

    Scenario #2. Jack replies to Bob's public message. Jack is on the Friendica/DFRN network.#

    +

    Jack uses dfrn-notify to send a direct reply to Bob. +Bob then creates a feed of the conversation and sends it to everybody involved in the conversation using dfrn-notify. +PuSH hubs are notified that new content is available. +The hub or hubs will then retrieve the latest feed and transmit it to all hub subscribers (which may be on different networks).

    +

    Scenario #3. Mary replies to Bob's public message. Mary is on the Friendica/DFRN network.#

    +

    Mary uses dfrn-notify to send a direct reply to Bob. +Bob then creates a feed of the conversation and sends it to everybody involved in the conversation (excluding himself, the conversation is now sent to both Jack and Mary). +Messages are sent using dfrn-notify. +Push hubs are also notified that new content is available. +The hub or hubs will then retrieve the latest feed and transmit it to all hub subscribers (which may be on different networks).

    +

    Scenario #4. William replies to Bob's public message. William is on the OStatus network.#

    +

    William uses salmon to notify Bob of the reply. +Content is html embedded in salmon magic envelope. +Bob then creates a feed of the conversation and sends it to all Friendica participants involved in the conversation using dfrn-notify (excluding himself, the conversation is sent to both Jack and Mary). +Push hubs are notified that new content is available. +The hub or hubs will then retrieve the latest feed and transmit it to all hub subscribers (which may be on different networks).

    +

    Scenario #5. Bob posts a private message to Mary and Jack.#

    +

    Message is delivered immediately to Mary and Jack using dfrn_notify. +Public hubs are not notified. +Requeueing is attempted in case of timeout. +Replies follow the same flow as the public replies except that hubs are not notified and message is never made available in the public feed. +The entire conversation is also made available to Mary and Jack (and nobody else) through their dfrn-poll personalised feed.

    + + +
    + +
    +
    + + + +
    + + + +
    +
    +
    +
    + + + + + + + + \ No newline at end of file diff --git a/develop/en/spec/protocol/protocol/index.html b/develop/en/spec/protocol/protocol/index.html new file mode 100644 index 0000000..6442924 --- /dev/null +++ b/develop/en/spec/protocol/protocol/index.html @@ -0,0 +1,3401 @@ + + + + + + + + + + + + + + + + + + + + + + Protocols - Friendica documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + +
    + + + + +
    + + +
    + +
    + + + + + + + + + +
    +
    + + + +
    +
    +
    + + + + + + +
    +
    +
    + + + +
    +
    +
    + + + +
    +
    +
    + + +
    +
    + + + + + + + + + + + + + +

    Used Protocols#

    +

    Friendicas DFRN Protocol#

    + +

    ActivityStreams#

    +

    Friendica is using ActivityStreams in version 1.0 for its activities and object types. +Additional types are used for non-standard activities.

    + +

    Salmon#

    +

    Salmon is used as a message exchange protocol for replies and mentions.

    + +

    Portable Contacts#

    +

    Portable Contacts is used for friends lists.

    + +

    pubsubhubbub#

    +

    pubsubhubbub is used for OStatus.

    + + + +
    + +
    +
    + + + +
    + + + +
    +
    +
    +
    + + + + + + + + \ No newline at end of file diff --git a/develop/en/spec/protocol/zot-2012.txt b/develop/en/spec/protocol/zot-2012.txt new file mode 100644 index 0000000..3d939ba --- /dev/null +++ b/develop/en/spec/protocol/zot-2012.txt @@ -0,0 +1,182 @@ + +Initial cut at Zot-2012 protocol. This is a very rough draft of some very rough ideas and concepts. +It is not yet intended to be a definitive specification and many things like the security handshakes are yet to be specified precisely. + +All communications are https + + +First create a global unique userid + + +Site userid: +https://macgirvin.com/1 + +$guuid = Strings::base64UrlEncode(hash('whirlpool','https://macgirvin.com/1.' . mt_rand(1000000,9999999),1); + + +Then create a hashed site destination. + +$gduid = Strings::base64UrlEncode(hash('whirlpool', $guuid . 'https://macgirvin.com',1); + +These two keys will identify you as a person+site pair in the future. +You will also obtain a password upon introducing yourself to a site. +This can be used to edit locations in the future. You will always keep your global unique userid + + +The steps to connect with somebody are to first register your location with their site. +Then introduce yourself to the person. This contains flags for the desired relationship. +At some future time, they may confirm and adjust the relationship based on their comfort level. +Lack of confirmation is tantamount to denial. + +You can set either or both of FOLLOW and SHARE which indicates the relationship from your viewpoint. +They may do likewise. + +A relationship is based on you as a person and provided you register new locations with the site you can post from anywhere. +You do not need to register locations with each person, only with the site. + + +Introduce yourself to a site: + + +POST https://example.com/post + +{ +'type' => 'register' +'person' => $guuid +'address' => $gduid +'site' => 'https://macgirvin.com' +'info' => 'mike@macgirvin.com' +} + +Returns: + +{ +'success' => 'true' +'pass' => me_encrypt($random_string) +} + +--- +Add location +--- + +POST https://example.com/post + +{ +'type' => 'location' +'person' => $guuid +'address' => $new_gduid +'site' => 'https://newsite.com' +'info' => 'mike@newsite.com' +'pass' => me_encrypt($gduid . '.' . $pass) +} + +Returns: + +{ +'success' => 'true' +'pass' => me_encrypt($random_string) +} + +--- +Remove location +--- + +POST https://example.com/post + +{ +'type' => 'remove_location' +'person' => $guuid +'address' => $gduid +'pass' => me_encrypt($pass) +} + +Returns: + +{ +'success' => 'true' +'message' => 'OK' +} + + +------------ +Make friends +------------ +This message may be reversed/repeated by the destination site to confirm. +flags is the desired friendship bits. The same message may be used with different flags +to edit or remove a relationship. + + +POST https://example.com/post + +{ +'type' => 'contact' +'person' => $gduid +'address' => $guuid +'target' => 'bobjones@example.com' +'flags' => HIDDEN=0,FOLLOW=1,SHARE=1,NOHIDDEN=1,NOFOLLOW=0,NOSHARE=0 +'confirm' => me_encrypt($guuid . '.' . $pass) +} + +Returns: + +{ +'success' => 'true' +'message' => 'OK' +'flags' => PENDING=1 +} + + + + + + + +------- +Message +------- + +Passing messages is done asynchronously. This may (potentially) relieve a lot of the burden of distribution from the posting site. If you're on site 'A' and make a post, site 'A' just contacts any downstream sites and informs them that there is new content (via a $post_id). The downstream site initiates the actual data transfer. + + + + + +POST https://example.com/post + +{ +'type' => 'post' +'person' => $guuid +'address' => $gduid +'post' => $post_id +} + +Returns: +{ +'success' => 'true' +'message' => 'OK' +} + + +-------- +Callback +-------- + +POST https://macgirvin.com/post + +{ +'type' => 'retrieve' +'retrieve' => $post_id +'challenge' => you_encrypt('abc123') +'verify' => me_encrypt('xyz456' . '.' . $gduid) +} + +Returns: + +{ +'success' => 'true' +'message' => 'OK' +'response' => 'abc123' +'data' => encrypted or raw structured post +} + + diff --git a/develop/en/spec/protocol/zot.txt b/develop/en/spec/protocol/zot.txt new file mode 100644 index 0000000..2c3bbb1 --- /dev/null +++ b/develop/en/spec/protocol/zot.txt @@ -0,0 +1,362 @@ +This is the Zot! social communications protocol. + +Specification revision: 1 +2 October 2011 + +Mike Macgirvin +This specification is public domain. + +Zot is a framework for secure delivery of messages on the web based on +webfinger and encapsulating salmon. + +First read the salmon and salmon magic envelope specifications. Zot also +makes use of webfinger and ActivityStreams and several concepts from RFC822 +(email). Zot encompasses the zot delivery framework and the zid remote +access protocol. + +The current specification revision (1) is frozen until a reference +implementation is available. After that, any protocol changes will require a +change to the revision number. + +**************** +* Zot delivery * +**************** + +Format of a zot wrapper. This completely encapsulates a salmon magic envelope +and provides privacy protection, while defining a delivery envelope - a +concept familiar to email systems. All addresses in zot are webfinger +resolvable addresses containing zot endpoints and salmon public keys (zot +is a superset of salmon). + + + + + ((key)) + ((iv)) + ((env_key)) + ((env_iv)) + ((envelope)) + ((sender signature)) + AES-256-CBC + ((salmon)) + + + +zot:key +******* + +A suitable randomly generated encyption key of length 32 octets for encrypting +the salmon packet. This is then encrypted with the sender's private key and +base64url encoded. + +zot:iv +****** + +A suitable randomly generated initialisation vector of length 16 octets for +encrypting the salmon packet. This is then encrypted with the sender's private +key and base64url encoded. + +zot:env_key +*********** + +A suitable randomly generated encyption key of length 32 octets for encrypting +the envelope. This is then encrypted with the recipient's public key and +base64url encoded. For bulk deliveries, it is encrypted with the site bulk +delivery public key. + + +zot:env_iv +********** + +A suitable randomly generated initialisation vector of length 16 octets for +encrypting the envelope. This is then encrypted with the recipient's public +key and base64url encoded. For bulk deliveries, it is encrypted with the site +bulk delivery public key. + + +zot:env +******* + +This consists of RFC822-style header fields representing the sender and +recipient(s). Line lengths have no defined limit and RFC822 continuation +lines are not supported. If an inbound server is not able to process an +envelope or post due to size constraints, it SHOULD return a +"413 Entity too large" HTTP response. + +Example: + +Z-From: zot:bob@example.com +Z-Sender: zot:bob@example.com +Z-To: zot:alice@example.com + +Both "Z-From:" and "Z-Sender:" MUST be provided, and represent a single +webfinger address of the author and sender respectively. The webfinger +address for the From address MUST contain a discoverable salmon public key +which is needed to verify the enclosed salmon data. Sender is used to indicate +the webfinger identity responsible for transmitting this message. From +indicates the message author. + +In web-based social systems, a reply to a message SHOULD be conveyed to all of +the original message participants. Only the author of the original message +may know all the recipients (such as those contained in Bcc: elements). The +author of a message always provides 'From'. They MUST duplicate this +information as 'Sender' when posting a followup message. + +A reply to a given message MUST be sent to the From address of the original +post, and MAY be sent to any additional addresses in the recipient list. The +original post author MUST send the reply to all known recipients of the +original message, with their webfinger identity as Sender, and the +comment/reply author as From. + +Receiving agents SHOULD validate the From identity as the signer of the salmon +magic envelope, and MAY reject it. They SHOULD also verify the Sender signature +of the zot packet if it is different than the salmon signature. They MAY +reject the message if the Sender is not allowed in their "friend list", or if +they do not have a suitable relationship with the Sender, or if either +signature fails to validate. Rejected messages for one of these reasons SHOULD +be indicated with a "400 Bad Request" HTTP response. + + +Z-To: * + +indicates a public message with no specifically enumerated recipients. + +The fields Z-To: and/or Z-Bcc: MAY be present. At least one recipient field +MUST be present. + +Z-To: zot:bob@example.com, zot:alice@example.com, mailto:dave@example.com +Z-Bcc: zot:https://example.com/profile/richard + +are valid entries. Adresses are comma separated and individual entries MUST NOT +contain commas. There MAY be any number of ASCII space characters between +entries for legibility. Header lines are terminated with a linefeed character +(ASCII 0x0A). + +This specification provides the following protocol address prefixes +for use in Z-To: or Z-Bcc: elements: + +zot: - normal zot delivery using webfinger or LRDD resolvable address +dfrn: - legacy DFRN mode delivery using webfinger or LRDD resovable address +ostatus: - normal OStatus delivery using webfinger or LRDD resovable address +diaspora: - Diaspora network delivery using webfinger address +facebook: - Facebook profile page URL +twitter: - Twitter personal page URL without AJAX '#!' fragment +mailto: - email RFC822/ESMTP address + +Examples: + +twitter:http://twitter.com/bjensen +facebook:http://facebook.com/profile.php?id=000000001 + +Foreign protocol addresses which have not been defined in this specification +or future revisions of this specification and which are unknown to the +recipient delivery process MAY be ignored. + +In cases where an address may contain either a webfinger or LRDD address, the +webfinger address SHOULD be used preferentially. + + +Z-Bcc: +****** + +The Z-Bcc element may contain one or more addresses which are hidden from end +user presentation. A zot receiving system MUST NOT store or allow for +the display of the Bcc information. Implementations which require extreme +privacy SHOULD send individual posts to each of the Bcc: recipients containing +only a single address. They MAY send all Bcc: posts using bulk delivery, +however this may have privacy implications as there is no guarantee a +receiving system will not log, store, or otherwise reveal the contents of the +Bcc recipient list. + +Z-To: addresses MAY be shown to an end user. + + +Envelope encryption +******************* + + +The entire envelope is encrypted using alg with env_key and env_iv and +base64url encoded for transmission. + +The zot envelope MAY include remote addresses. A zot inbound delivery agent +MUST parse the envelope and determine whether a delivery address to the +current endpoint is valid. This may be the result of: + + 1. An address contains the public message wildcard '*' + + 2. The current endpoint is a personal endpoint and one of the recipients +listed in the Z-To: or Z-Bcc: addresses matches the webfinger address of +the "owner" of the endpoint. + + 3. The current endpoint is a bulk delivery endpoint. The bulk delivery +endpoint is defined elsewhere in this document. The bulk delivery agent +will deliver to all local addresses found in the address lists. + +zot:sig +******* + +The Sender of the message signs the underlying salmon data in the manner +prescribed by salmon. If the Sender and From address are identical, the +signature will be identical to the signature of the underlying salmon packet. +If they are different, this signature is verified with the Sender's public +key to verify the Sender. + +zot:alg +******* + +Currently the only valid choice for alg is "AES-256-CBC". + + +zot:data +******** + +The data field is a salmon magic envelope. This is encrypted with alg using +key and iv. The result is then base64url encoded for transmission. + +For the first release of this specification, the data format of the enclosed +salmon SHOULD be 'application/atom+xml' representing an Atom formatted +ActivityStream. Future revisions MAY allow other alternate data formats. +All acceptable formats MUST be listed in an XRD property (described elsewhere +in this document). + + +Delivery +******** + +The zot message is then POSTed to the zot endpoint URL as +application/text+xml and can be decoded/decrypted by the recipient using +their private key. + +The normal salmon endpoint for a service MAY be used as an alternate +delivery method for non-encrypted (e.g. public) messages. + +Discover of the zot endpoint is based on webfinger XRD: + + + + +Bulk Delivery +************* + +A site MAY provide a bulk delivery endpoint, which MAY be used to avoid +multiple encryptions of the same data for a single destination. +This is discoverable by providing a zot endpoint with a corresponding +salmon public key in the site's .well-known/host-meta file. +A delivery to this endpoint will deliver to all local recipients provided +within the zot envelope. + + +Extensibility +************* + +This specification is subject to change. The current version which is in +effect at a given site may be noted by XRD properties. The following +properties MUST be present in the XRD providing the relevant endpoint: + +1 +application/atom+xml + + +Version is specified in this document and indicates the current revision. +Version is an increasing non-zero integer value. There are no minor versions. +Implementations MAY provide compatibility to multiple incompatible versions +by using this version indication. The "accept" indicates a range of document +content types which may be enclosed in the underlying salmon magic envelope. +We anticipate this specification will in the future allow for a close variant +of "message/rfc822" and which may include MIME. This may also be used to +embed alternate message formats and protocols such as +"application/x-diaspora+xml". If a delivery agent is unable to provide any +acceptable data format to the remote system, the delivery to that system MUST +be terminated/cancelled. + +Foreign Messages +**************** + +Messages MAY be imported from other networks and systems which have no +knowledge of salmon signatures. The salmon signature in this case MUST be the +exact string 'NOTSIGNED' to indicate that the author (From address) cannot be +validated using salmon verification. This message MUST be relayed by a Sender +who can provide a valid salmon signature of the message via zot:sig. Delivery +systems MAY reject foreign messages. + + + + + +******************************* +* Zid (Zot-ID) authentication * +******************************* + +This section of the document is considered separate from the delivery +specification precding it and represents a different protocol, which is +currently incomplete. This will be split off into another document in the +future, but is presented here as a synergistic component of the Zot network +model. + + +URLs may be present within a zot message which refer to private and/or +protected resources. Zid uses OpenID to gain access to these protected +resources. These could be private photos or profile information - or *any* +web accessible resource. Using zid, these can have access controls which +extends to any resolvable webfinger address. + +Zid authentication relies on the presence of an OpenID provider element in +webfinger, and a URL template which is applied to protected resources within +a zot message. + +The template is designated with the characters "{zid=}" within a URL of a zot +message. When the page is rendered for viewing to an observer, this template +is replaced with the webfinger address of the viewer (if known), or an empty +string if the webfinger address of the viewer cannot be determined. + +For example in a message body: + +http://example.com/photos/bob/picture.jpg?{zid=} + +refers to a private photo which is only visible to alice@example.com. + +If Alice is viewing the page, the link is rendered with + +http://example.com/photos/bob/picture.jpg?zid=alice@example.com + +If the page viewer is unknown, it is rendered as + +http://example.com/photos/bob/picture.jpg?zid= + + +When the link is visited, the web server at example.com notes the presence of +the zid parameter and uses information from webfinger to locate the OpenID +provider for the zid webfinger address. It then redirects to the OpenID +server and requests authentication of the given person. If this is successful, +access to the protected resource is granted. + +A browser cookie may be provided to avoid future authentication redirects +and allow authenticated browsing to other resources on the website. + +Only authentication via OpenID is defined in this version of the specification. + +This can be used to provide access control of any web resource to any +webfinger identity on the internet. + + +********* +* Links * +********* + +Salmon Protocol + http://www.salmon-protocol.org/salmon-protocol-summary + +Salmon Magic Envelope + http://salmon-protocol.googlecode.com/svn/trunk/draft-panzer-magicsig-01.html + +Atom Activity Stream Draft + http://activitystrea.ms/specs/atom/1.0/ + +Activty Stream Base Schema + http://activitystrea.ms/head/activity-schema.html + +WebFinger Protocol + http://code.google.com/p/webfinger/wiki/WebFingerProtocol + + diff --git a/develop/en/user/accesskeys/index.html b/develop/en/user/accesskeys/index.html new file mode 100644 index 0000000..2177acf --- /dev/null +++ b/develop/en/user/accesskeys/index.html @@ -0,0 +1,3520 @@ + + + + + + + + + + + + + + + + + + + + + + Accesskeys - Friendica documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + +
    + + + + +
    + + +
    + +
    + + + + + + + + + +
    +
    + + + +
    +
    +
    + + + + + + +
    +
    +
    + + + +
    +
    +
    + + + +
    +
    +
    + + +
    +
    + + + + + + + + + + + + + +

    Access-keys reference#

    +

    Access keys are keyboard shortcuts that allow you to easily navigate the user interface. +Access keys are currently not available with the Frio theme.

    +

    The specific key combinations depend on how your browser's the modifier key setting. +For an overview of modifier keys in different browsers, have a look at Wikipedia article. +For example, for moving to profile page in Firefox, press these three keys simultaneously.

    +

    [Shift] [Alt] [p]

    +

    General#

    +
      +
    • p - Profile
    • +
    • n - Network
    • +
    • c - Community
    • +
    • s - Search
    • +
    • a - Admin
    • +
    • f - Notifications
    • +
    • u - User menu
    • +
    +

    community#

    +
      +
    • l - Local community
    • +
    • g - Global community
    • +
    +

    profile#

    +
      +
    • m - Status Messages and Posts
    • +
    • r - Profile Details
    • +
    • h - Photo Albums
    • +
    • d - Media
    • +
    • e - Events and Calendar
    • +
    • t - Personal Notes
    • +
    • o - Scheduled Posts
    • +
    • k - View Contacts
    • +
    +

    contacts (contact list)#

    +
      +
    • g - Suggestions
    • +
    • l - Show all Contacts
    • +
    • o - Only show unblocked contacts
    • +
    • b - Only show blocked contacts
    • +
    • i - Only show ignored contacts
    • +
    • y - Only show archived contacts
    • +
    • h - Only show hidden contacts
    • +
    • e - Edit contact groups
    • +
    +

    contact (single contact view)#

    +
      +
    • m - Status messages
    • +
    • p - Posts and Comments
    • +
    • d - Media
    • +
    • o - Profile
    • +
    • t - Contacts
    • +
    • r - Advanced
    • +
    +

    message#

    +
      +
    • m - New message
    • +
    +

    network#

    +
      +
    • e - Sort by Comment Date
    • +
    • t - Sort by Receipt Date
    • +
    • q - Sort by Creation Date
    • +
    • r - Conversation (Posts that mention or involve you)
    • +
    • w - New posts
    • +
    • m - Favourite Posts
    • +
    +

    notifications#

    +
      +
    • y - System
    • +
    • w - Network
    • +
    • r - Personal
    • +
    • h - Home
    • +
    • i - Introductions
    • +
    +

    settings#

    +
      +
    • o - Account
    • +
    • 2 - Two-factor authentication
    • +
    • p - Profiles
    • +
    • t - Additional features
    • +
    • w - Social Networks
    • +
    • l - Addons
    • +
    • d - Delegations
    • +
    • b - Connected apps
    • +
    • e - Export personal data
    • +
    • r - Remove account
    • +
    + + +
    + +
    +
    + + + +
    + + + +
    +
    +
    +
    + + + + + + + + \ No newline at end of file diff --git a/develop/en/user/account-basics/index.html b/develop/en/user/account-basics/index.html new file mode 100644 index 0000000..7ef07fc --- /dev/null +++ b/develop/en/user/account-basics/index.html @@ -0,0 +1,3549 @@ + + + + + + + + + + + + + + + + + + + + + + Account Basics - Friendica documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + +
    + + + + +
    + + +
    + +
    + + + + + + + + + +
    +
    + + + +
    +
    +
    + + + + + + +
    +
    +
    + + + +
    +
    +
    + + + +
    +
    +
    + + +
    +
    + + + + + + + + + + + + + +

    Account Basics#

    +

    Registration#

    +

    Not all Friendica sites allow open registration. +If registration is allowed, you will see a "Register" link immediately below the login prompt on the site's home page. +Following this link will take you to the site registration page. +The strength of our network is that lots of different sites are all completely compatible with each other. +If the site you're visiting doesn't allow registration, or you think you might prefer another one, there is a list of public servers here, and hopefully you will find one that meets your needs.

    +

    If you'd like to have your own server, you can do that too. +Visit the Friendica website to download the code with setup instructions. +It's a very simple installation process that anybody experienced in hosting websites, or with basic Linux experience can handle easily.

    +

    OpenID#

    +

    The first field on the Registration page is for an OpenID address. +If you do not have an OpenID address or do not wish to use OpenID, leave this field blank. +If you have an OpenID account elsewhere and wish to use it, enter the address into this field and click 'Register'. +Friendica will attempt to extract as much information as possible from your OpenID provider and return to this page with those items already filled in.

    +

    Your Full Name#

    +

    Please provide your full name as you would like it to be displayed on this system. +Most people use their real name for this, but you're under no obligation to do so yourself.

    +

    Email Address#

    +

    Please provide a valid email address. +Your email address is never published. +We need this to send you account information and your login details. +You may also occasionally receive notifications of incoming messages or items requiring your attention, but you have the possibility to completely disable these from your Settings page once you have logged in. +This doesn't have to be your primary email address, but it does need to be a real email address. +You can't get your initial password, or reset a lost password later without it. +This is the only bit of personal information that has to be accurate.

    +

    Nickname#

    +

    A nickname is used to generate web addresses for many of your personal pages, and is also treated like an email address when establishing communications with others. +Due to the way that the nickname is used, it has some limitations. +It must contain only US-ASCII text characters and numbers, and must also start with a text character. +It also must be unique on this system. +This is used in many places to identify your account, and once set it cannot be changed.

    +

    Directory Publishing#

    +

    The registration form also allows you to choose whether to list your account in the online directory of your node. +This is like a "phone book" and you may choose to be unlisted. +We recommend that you select 'Yes' so that other people (friends, family, etc.) will be able to find you. +If you choose 'No', you will essentially be invisible and have few opportunities for interaction. +Whichever you choose, this can be changed any time from your Settings page after you log in.

    +

    Register#

    +

    Once you have provided the necessary details, click the 'Register' button. +An email will be sent to you providing your account login details. +Please check your email (including spam folders) for your registration details and initial password.

    +

    Login Page#

    +

    On the 'Login' page, please enter your login information that was provided during registration. +You may use either your nickname or email address as a Login Name.

    +

    If you use your account to manage other accounts and these all have the same email address, please enter the nickname for the account you wish to manage.

    +

    If your account has been OpenID enabled, you may use your OpenID-address as a login name and leave the password blank. +You will be redirected to your OpenID provider to complete your authorisation.

    +

    Otherwise, enter your password. +This will have been initially provided in your registration email message. +Your password is case-sensitive, so please check your 'Caps Lock' key if you are having difficulty logging in.

    +

    Changing Your Password#

    +

    After your first login, please visit the 'Settings' page from the top menu bar and change your password to something that you will remember.

    +

    Getting Started#

    +

    A link with 'Tips for New Members' (`https://your-site.info/newmember) will show up on your network and home pages for two weeks providing key information for getting started.

    +

    Retrieving Personal Data#

    +

    You can export a copy of your personal data in JSON format from the "Export personal data" link at the top of your settings page.

    +

    You need this file to relocate your Friendica account to another node. +This might be necessary, e.g. if your node suffers a severe hardware problem and is not recoverable.

    +

    See Also#

    + + + +
    + +
    +
    + + + +
    + + + +
    +
    +
    +
    + + + + + + + + \ No newline at end of file diff --git a/develop/en/user/bbcode/index.html b/develop/en/user/bbcode/index.html new file mode 100644 index 0000000..720e981 --- /dev/null +++ b/develop/en/user/bbcode/index.html @@ -0,0 +1,4099 @@ + + + + + + + + + + + + + + + + + + + + + + BBCode - Friendica documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + +
    + + + + +
    + + +
    + +
    + + + + + + + + + +
    +
    + + + +
    +
    +
    + + + + + + +
    +
    +
    + + + +
    +
    +
    + + + +
    +
    +
    + + +
    +
    + + + + + + + + + + + + + +

    Friendica BBCode tags reference#

    +

    Inline#

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    BBCodeResult
    [b]bold[/b]bold
    [i]italic[/i]italic
    [u]underlined[/u]underlined
    [s]strike[/s]strike
    [o]overline[/o]overline
    [color=red]red[/color]red
    [url=https://friendi.ca]Friendica[/url]Friendica
    [img]https://raw.githubusercontent.com/friendica/friendica/stable/images/friendica-32.png[/img]friendica-32
    [img=https://raw.githubusercontent.com/friendica/friendica/stable/images/friendica-32.png]The Friendica Logo[/img]The Friendica Logo
    [img=64x32]https://raw.githubusercontent.com/friendica/friendica/stable/images/friendica.svg[/img]
    +
    Note: provided height is simply discarded.
    [size=xx-small]small text[/size]small text
    [size=xx-large]big text[/size]big text
    [size=20]exact size[/size] (size can be any number, in pixels)exact size
    [font=serif]Serif font[/font]Serif font
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    BBCodeResult
    [url]https://friendi.ca[/url]https://friendi.ca
    [url=https://friendi.ca]Friendica[/url]Friendica
    [bookmark]https://friendi.ca[/bookmark]

    +#^[url]https://friendi.ca[/url]

    Friendica: https://friendi.ca

    [bookmark=https://friendi.ca]Bookmark[/bookmark]

    +#^[url=https://friendi.ca]Bookmark[/url]

    +#[url=https://friendi.ca]^[/url][url=https://friendi.ca]Bookmark[/url]

    Friendica: Bookmark

    [url=/posts/f16d77b0630f0134740c0cc47a0ea02a]Diaspora post with GUID[/url]Diaspora post with GUID
    #Friendica#Friendica
    @Mention@Mention
    acct:account@friendica.host.com (WebFinger)acct:account@friendica.host.com
    [mail]user@mail.example.com[/mail]user@mail.example.com
    [mail=user@mail.example.com]Send an email to User[/mail]Send an email to User
    + +

    Blocks#

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    BBCodeResult
    [p]A paragraph of text[/p]

    A paragraph of text

    Inline [code]code[/code] in a paragraphInline code in a paragraph
    [code]Multi
    line
    code[/code]
    Multi +line +code
    [code=php]function text_highlight($s,$lang)[/code]1
    1. function text_highlight($s,$lang)
    [quote]quote[/quote]
    quote
    [quote=Author]Author? Me? No, no, no...[/quote]Author wrote:
    Author? Me? No, no, no...
    [center]Centered text[/center]
    Centered text
    You should not read any further if you want to be surprised.[spoiler]There is a happy end.[/spoiler] +
    + You should not read any further if you want to be surprised.
    + Click to open/close + +
    +
    +
    [spoiler=Author]Spoiler quote[/spoiler] +
    + Author wrote:
    + Click to open/close + +
    +
    +
    [hr] (horizontal line)
    + +

    1: Supported language parameter values for code highlighting: +- abap +- avrc +- cpp +- css +- diff +- dtd +- html +- java +- javascript +- js +- mysql +- perl +- php +- python +- ruby +- sh +- sql +- vbscript +- xml

    +

    Titles#

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    BBCodeResult
    [h1]Title 1[/h1]

    Title 1

    [h2]Title 2[/h2]

    Title 2

    [h3]Title 3[/h3]

    Title 3

    [h4]Title 4[/h4]

    Title 4

    [h5]Title 5[/h5]
    Title 5
    [h6]Title 6[/h6]
    Title 6
    + +

    Tables#

    + + + + + + + + + + + + + + + + + +
    BBCodeResult
    [table]
    + [tr]
    + [th]Header 1[/th]
    + [th]Header 2[/th]
    + [th]Header 2[/th]
    + [/tr]
    + [tr]
    + [td]Cell 1[/td]
    + [td]Cell 2[/td]
    + [td]Cell 3[/td]
    + [/tr]
    + [tr]
    + [td]Cell 4[/td]
    + [td]Cell 5[/td]
    + [td]Cell 6[/td]
    + [/tr]
    +[/table]
    + + + + + + + + + + + + + + + + + + +
    Header 1Header 2Header 3
    Cell 1Cell 2Cell 3
    Cell 4Cell 5Cell 6
    +
    [table border=0] + + + + + + + + + + + + + + + + + + +
    Header 1Header 2Header 3
    Cell 1Cell 2Cell 3
    Cell 4Cell 5Cell 6
    +
    [table border=1] + + + + + + + + + + + + + + + + + + +
    Header 1Header 2Header 3
    Cell 1Cell 2Cell 3
    Cell 4Cell 5Cell 6
    +
    + +

    Lists#

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    BBCodeResult
    [ul]
    + [li] First list element
    + [li] Second list element
    +[/ul]
    +[list]
    + [*] First list element
    + [*] Second list element
    +[/list]
    +
      +
    • First list element
    • +
    • Second list element
    • +
    +
    [ol]
    + [*] First list element
    + [*] Second list element
    +[/ol]
    +[list=1]
    + [*] First list element
    + [*] Second list element
    +[/list]
    +
      +
    • First list element
    • +
    • Second list element
    • +
    +
    [list=]
    + [*] First list element
    + [*] Second list element
    +[/list]
    +
      +
    • First list element
    • +
    • Second list element
    • +
    +
    [list=i]
    + [*] First list element
    + [*] Second list element
    +[/list]
    +
      +
    • First list element
    • +
    • Second list element
    • +
    +
    [list=I]
    + [*] First list element
    + [*] Second list element
    +[/list]
    +
      +
    • First list element
    • +
    • Second list element
    • +
    +
    [list=a]
    + [*] First list element
    + [*] Second list element
    +[/list]
    +
      +
    • First list element
    • +
    • Second list element
    • +
    +
    [list=A]
    + [*] First list element
    + [*] Second list element
    +[/list]
    +
      +
    • First list element
    • +
    • Second list element
    • +
    +
    + +

    Embed#

    +

    You can embed video, audio and more in a message.

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    BBCodeResult
    [video]url[/video]Where *url* can be an url to YouTube, vimeo, soundcloud, or other sites wich supports oembed or opengraph specifications.
    [video]Video file url[/video] +[audio]Audio file url[/audio]Full URL to an ogg/ogv/oga/ogm/webm/mp4/mp3 file. An HTML5 player will be used to show it.
    [youtube]Youtube URL[/youtube]Youtube video OEmbed display. May not embed an actual player.
    [youtube]Youtube video ID[/youtube]Youtube player iframe embed.
    [vimeo]Vimeo URL[/vimeo]Vimeo video OEmbed display. May not embed an actual player.
    [vimeo]Vimeo video ID[/vimeo]Vimeo player iframe embed.
    [embed]URL[/embed]Embed OEmbed rich content.
    [url]*url*[/url]If *url* supports oembed or opengraph specifications the embedded object will be shown (eg, documents from scribd). +Page title with a link to *url* will be shown.
    + +

    Map#

    +

    This requires "openstreetmap" or "Google Maps" addon version 1.3 or newer. +If the addon isn't activated, the raw coordinates are shown instead.

    + + + + + + + + + + + + + + + + + +
    BBCodeResult
    [map]address[/map]Embeds a map centered on this address.
    [map=lat,long]Embeds a map centered on those coordinates.
    [map]Embeds a map centered on the post's location.
    + +

    Abstract for longer posts#

    +

    If you want to spread your post to several third party networks you may have the problem that these networks have a length limitation like on Twitter.

    +

    Friendica uses a semi-intelligent mechanism to generate a fitting abstract. +But it can be useful to define a custom abstract that will only be displayed on the external network. +This is done with the [abstract]-element.

    + + + + + + + + + +
    BBCodeResult
    [abstract]Totally interesting! A must-see! Please click the link![/abstract]
    +I want to tell you a really boring story that you really never wanted to hear.
    Twitter would display the text
    Totally interesting! A must-see! Please click the link!
    +On Friendica you would only see the text after
    I want to tell you a really ...
    + +

    It is even possible to define abstracts for separate networks:

    + + + + + + + + + +
    BBCodeResult
    +[abstract]Hi friends Here are my newest pictures![/abstract]
    +[abstract=twit]Hi my dear Twitter followers. Do you want to see my new +pictures?[/abstract]
    +[abstract=apdn]Helly my dear followers on ADN. I made sone new pictures +that I wanted to share with you.[/abstract]
    +Today I was in the woods and took some real cool pictures ...
    For Twitter and App.net the system will use the defined abstracts.
    +For other networks (e.g. when you are using the "StatusNet" connector that is used to post to your GNU Social account) the general abstract element will be used.
    + +

    If you use (for example) the "buffer" connector to post to Facebook or Google+ you can use this element to define an abstract for a longer blogpost that you don't want to post completely to these networks.

    +

    Networks like Facebook or Google+ aren't length limited. +For this reason the [abstract] element isn't used. +Instead, you have to name the explicit network:

    + + + + + + + + + +
    BBCodeResult
    +[abstract]These days I had a strange encounter...[/abstract]
    +[abstract=goog]Hello my dear Google+ followers. You have to read my newest blog post![/abstract]
    +[abstract=face]Hello my Facebook friends. These days happened something really cool.[/abstract]
    +While taking pictures in the woods I had a really strange encounter...
    Google and Facebook will show the respective abstracts while the other networks will show the default one.
    +
    Meanwhile, Friendica won't show any of the abstracts.
    + +

    The [abstract] element is not working with connectors where we post HTML directly, like Tumblr, WordPress or Pump.io. +For the native connections--that is to e.g. Friendica, Hubzilla, Diaspora or GNU Social--the full posting is used and the contacts instance will display the posting as desired.

    +

    For postings that are delivered via ActivityPub, the text from the abstract is placed in the summary field. +On Mastodon this field is used for the content warning.

    +

    Special#

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    BBCodeResult
    If you need to put literal BBCode in a message, [noparse], [nobb] or [pre] blocks prevent BBCode conversion: +
      +
    • [noparse][b]bold[/b][/noparse]
    • +
    • [nobb][b]bold[/b][/nobb]
    • +
    • [pre][b]bold[/b][/pre]
    • +
    + Note: [code] has priority over [noparse], [nobb] and [pre] which makes them display as BBCode tags in code blocks instead of being removed. + [code] blocks inside [noparse] will still be converted to a code block. +
    [b]bold[/b]
    Additionally, [noparse] and [pre] blocks prevent mention and hashtag conversion to links: +
      +
    • [noparse]@user@domain.tld #hashtag[/noparse]
    • +
    • [pre]@user@domain.tld #hashtag[/pre]
    • +
    +
    @user@domain.tld #hashtag
    Additionally, [pre] blocks preserve spaces: +
      +
    • [pre] Spaces[/pre]
    • +
    +
    Spaces
    [nosmile] is used to disable smilies on a post by post basis
    +
    + [nosmile] ;-) :-O +
    ;-) :-O
    Custom block styles
    +
    +[style=text-shadow: 0 0 4px #CC0000;]You can change all the CSS properties of this block.[/style]
    You can change all the CSS properties of this block.
    Custom inline styles
    +
    +[style=text-shadow: 0 0 4px #CC0000;]You can change all the CSS properties of this block.[/style]
    You can change all the CSS properties of this inline text.
    + + +
    + +
    +
    + + + +
    + + + +
    +
    +
    +
    + + + + + + + + \ No newline at end of file diff --git a/develop/en/user/chats/index.html b/develop/en/user/chats/index.html new file mode 100644 index 0000000..b37a131 --- /dev/null +++ b/develop/en/user/chats/index.html @@ -0,0 +1,3417 @@ + + + + + + + + + + + + + + + + + + + + + + Chats - Friendica documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + +
    + + + + +
    + + +
    + +
    + + + + + + + + + +
    +
    + + + +
    +
    +
    + + + + + + +
    +
    +
    + + + +
    +
    +
    + + + +
    +
    +
    + + +
    +
    + + + + + + + + + + + + + +

    Chats#

    +

    There are two possibilities to use chat on your friendica site

    +
      +
    • IRC Chat
    • +
    • Jappix
    • +
    +

    IRC-Chat Addon#

    +

    After activating the addon, you can find the chat at https://your-site.info/irc. +Note: you can use this chat without any login at your site so that everyone could use it.

    +

    If you follow the link, you will see the login page of the IRC chat. +Now choose a nickname and a chatroom. +You can choose any name you like for the room, even something like #superchatwhosenameisonlyknownbyme. +Finally, solve the captchas and click the connect button.

    +

    The following window shows some text while connecting. +This text isn't important, just wait for the next window. +The first line shows your name and your current IP address. +The right part of the window shows all users. +The lower part of the window contains an input field.

    +

    Jappix Mini#

    +

    The Jappix Mini Addon creates a chatbox for jabber- and XMPP-contacts. +You should already have a jabber/XMPP-account before setting up the addon. +You can find more information at jabber.org.

    +

    You can use several servers to create an account:

    + +

    1. Basics#

    +

    At first, you have to get the current version. You can either pull it from GitHub like so:

    +
    $> cd /var/www/virtual/YOURSPACE/html/addon; git pull
    +
    +

    Or you can download a tar archive here: jappixmini.tgz (click at „view raw“).

    +

    Just unpack the file and rename the directory to „jappixmini“. +Next, upload this directory and the .tgz-file into your addon directory of your friendica installation.

    +

    Now you can activate the addon globally on the admin pages. +In the addon sidebar, you will find an entry of jappix now (where you can also find twitter, GNU Social and others). +The following page shows the settings of this addon.

    +

    Activate the BOSH proxy.

    +

    2. Settings#

    +

    Go to your user account settings next and choose the addon page. +Scroll down until you find the Jappix Mini addon settings.

    +

    At first, you have to activate the addon.

    +

    Now add your Jabber/XMPP name, the domain/server (without "http"; just "jappix.com"). +For „Jabber BOSH Host“ you could use "https://bind.jappix.com/". +Note that you need another BOSH server if you do not use jappix.com for your XMPP account. +You can find further information in the „Configuration Help“-section below these fields. +At last, you have entered your password (there are some more optional options, you can choose). +Finish these steps with "send" to save the entries. +Now, you should find the chatbox in the lower right corner of your browser window.

    +

    If you want to add contacts manually, you can click "add contact".

    + + +
    + +
    +
    + + + +
    + + + +
    +
    +
    +
    + + + + + + + + \ No newline at end of file diff --git a/develop/en/user/connectors/index.html b/develop/en/user/connectors/index.html new file mode 100644 index 0000000..6d63a9a --- /dev/null +++ b/develop/en/user/connectors/index.html @@ -0,0 +1,3417 @@ + + + + + + + + + + + + + + + + + + + + + + Connectors - Friendica documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + +
    + + + + +
    + + +
    + +
    + + + + + + + + + +
    +
    + + + +
    +
    +
    + + + + + + +
    +
    +
    + + + +
    +
    +
    + + + +
    +
    +
    + + +
    +
    + + + + + + + + + + + + + +

    Connectors#

    +

    Connectors allow you to connect with external social networks and services. +They are only required for posting to existing accounts on Twitter or GNU Social. +There is also a connector for accessing your email INBOX.

    +

    Friendica#

    +

    You can either connect to others by providing your Identity Address on the 'Connect' page of any Friendica member. +Or you can put their Identity Address into the Connect-box on your Contacts page.

    +

    Diaspora#

    +

    Add the Diaspora 'handle' to the 'Connect/Follow' text box on your Contacts page.

    +

    GNU Social#

    +

    This is described as the "federated social web" or OStatus contacts.

    +

    Please note that there are no privacy provisions on the OStatus network. +Any message which is delivered to any OStatus member is visible to anybody in the world and will negate any privacy settings that you have in effect. +These messages will also turn up in public searches.

    +

    Since OStatus communications do not use authentication, if you select the profile privacy option to hide your profile and messages from unknown viewers, OStatus members will not be able to receive your communications.

    +

    To connect with an OStatus member insert their profile URL or Identity address into the Connect-box on your Contacts page.

    +

    The GNU Social connector may be used if you wish posts to appear on an OStatus site using an existing OStatus account. +It is not necessary to do this, as you may 'follow' OStatus members from Friendica, and they may follow you (by placing their own Identity Address into your 'Connect' page).

    +

    Blogger, Wordpress, RSS feeds, arbitrary web pages#

    +

    Put the URL into the Connect-box on your Contacts page. +PLease note that you will not be able to reply to these contacts.

    +

    This feed reader feature will allow you to connect with millions of pages on the internet. +All that the pages need to have is a discoverable feed using either the RSS or Atom syndication format, and which provides an author name and a site image in a form which we can extract.

    +

    Twitter#

    +

    To follow a Twitter member, the Twitter-Connector (Addon) needs to be configured on your node. +If this is the case put the URL of the Twitter member's main page into the Connect-box on your Contacts page. +To reply, you must have the Twitter connector installed, and reply using your own status editor. +Begin the message with @twitterperson replacing with the Twitter username.

    +

    Email#

    +

    If the php module for IMAP support is available on your server, Friendica can connect to email contacts as well. +Configure the email connector from your Settings page. +Once this has been done, you may enter an email address to connect with using the Connect-box on your Contacts page. +They must be the sender of a message which is currently in your INBOX for the connection to succeed. +You may include email contacts in private conversations.

    + + +
    + +
    +
    + + + +
    + + + +
    +
    +
    +
    + + + + + + + + \ No newline at end of file diff --git a/develop/en/user/events/index.html b/develop/en/user/events/index.html new file mode 100644 index 0000000..99325b1 --- /dev/null +++ b/develop/en/user/events/index.html @@ -0,0 +1,3415 @@ + + + + + + + + + + + + + + + + + + + + + + Events - Friendica documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + +
    + + + + +
    + + +
    + +
    + + + + + + + + + +
    +
    + + + +
    +
    +
    + + + + + + +
    +
    +
    + + + +
    +
    +
    + + + +
    +
    +
    + + +
    +
    + + + + + + + + + + + + + +

    Events#

    +

    A special form of postings are events. +The events you and your contacts share can be found at https://your-site.info/events on your node. +To get there go to your wall and follow the tab "events" +Depending on the theme you are using, there might be an additional link from the navigation menu to this page.

    +

    Event Overview#

    +

    The overview page shows the calendar of the current month, plus a few days at the beginning and the end. +Listed are all events for this month, created by you, or shared with you by your contacts, +This includes birthday reminders for contacts who share their birthday with you.

    +

    From the controls, you can switch between month/week/day view. +Flip through the view forwards and backwards. +And return to today.

    +

    To create a new event, you can either follow the link "Create New Event" or double-click on the desired box in the calendar in which the event should take place.

    +

    With a click on an existing event a pop-up box will be opened which shows you the event. +From there you can edit the event or view the event at the source link, if you are the one who created the event.

    +

    Create a new Event#

    +

    Following one of the methods mentioned above you reach a form to enter the event data. +Fields marked with a *** have to be filled.

    +
      +
    • Event Starts: enter the date/time of the start of the event here
    • +
    • Event Finishes: enter the finishing date/time for the event here
    • +
    +

    When you click in one of these fields a pop-up will be opened that allows you to pick the day and the time. +If you double-clicked on the day box in the calendar these fields will be pre-filled for you. +The finishing date/time has to be after the beginning date/time of the event. +But you don't have to specify it. +If the event is open-ended or the finishing date/time does not matter, just select the box below the two first fields.

    +
      +
    • Title: a title for the event
    • +
    • Description: a longer description for the event
    • +
    • Location: the location the event will take place
    • +
    +

    These three fields describe your events. +In the description and location field you can use BBCode to format the text.

    +
      +
    • Share this event: when this box is checked the ACL will be shown to let you select with whom you wish to share the event. This works just like the controls of any other posting.
    • +
    +

    When you Share the event it will be posted to your wall with the access permissions you've selected. +But before you do, you can also preview the event in a pop-up box.

    +

    Interaction with Events#

    +

    When you publish an event, you can choose who shall receive it, as with a regular new posting. +The recipients will see the posting about the event in their network-stream. +Additionally, it will be added to their calendar and thus be shown in their events overview page.

    +

    Recipients of the event-posting can comment or dis-/like the event, as with a regular posting. +Furthermore, they can announce that they will attend, not attend or may-be attend the event with a single click.

    +

    Calendar Export#

    +

    If you want to export your public events to ical or csv, you can activate an additional feature in your user settings (Additional features -> General Features -> Export Public Calendar). +Afterwards a link will be shown in the events' page of your profile to access the calendar.

    + + +
    + +
    +
    + + + +
    + + + +
    +
    +
    +
    + + + + + + + + \ No newline at end of file diff --git a/develop/en/user/export-import-contacts/index.html b/develop/en/user/export-import-contacts/index.html new file mode 100644 index 0000000..49aa568 --- /dev/null +++ b/develop/en/user/export-import-contacts/index.html @@ -0,0 +1,3367 @@ + + + + + + + + + + + + + + + + + + + + + + Import / Export Contacts - Friendica documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + +
    + + + + +
    + + +
    + +
    + + + + + + + + + +
    +
    + + + +
    +
    +
    + + + + + + +
    +
    +
    + + + +
    +
    +
    + + + +
    +
    +
    + + +
    +
    + + + + + + + + + + + + + +

    Import / Export of followed Contacts#

    +

    In addition to move your account you can export and import the list of accounts you follow. +The exported list is stored as CSV file that is compatible to the format used by other platforms as e.g. Mastodon, Misskey or Pleroma.

    +

    Export of followed Contacts#

    +

    To export the list of accounts that you follow, go to the [Settings Export personal date] (https://your-site.info/settings/userexport) and click the [Export Contacts to CSV] (https://your-site.info/settings/userexport/contact).

    +

    Import of followed Contacts#

    +

    To import contacts from a CSV file, go to the Settings page. +At the bottom of the account settings page you'll find the import contacts section. +Upload the CSV file there.

    +

    Supported File Format#

    +

    The CSV file must contain at least one column. +In the first column the table should contain either the handle or URL of a followed account. +(one account per row.) +Other columns in the CSV file will be ignored.

    + + +
    + +
    +
    + + + +
    + + + +
    +
    +
    +
    + + + + + + + + \ No newline at end of file diff --git a/develop/en/user/faq/index.html b/develop/en/user/faq/index.html new file mode 100644 index 0000000..6454c6a --- /dev/null +++ b/develop/en/user/faq/index.html @@ -0,0 +1,3826 @@ + + + + + + + + + + + + + + + + + + + + + + FAQ - Friendica documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + +
    + + + + +
    + + +
    + +
    + + + + + + + + + +
    +
    + + + +
    +
    +
    + + + + + + +
    +
    +
    + + + + + + +
    +
    + + + + + + + + + + + + + +

    Frequently Asked Questions (FAQ)#

    +

    Where I can find help?#

    +

    If this FAQ does not answer your question you can always reach out to the community via the following options:

    + +

    Why do I get warnings about SSL certificates?#

    +

    SSL (Secure Socket Layer) is a technology to encrypt data transfer between computers. +Sometimes your browser warns you about a missing or invalid certificate. +These warnings can have three reasons:

    +
      +
    1. The server you are connected to don't offer SSL encryption.
    2. +
    3. The server has a self-signed certificate (not recommended).
    4. +
    5. The certificate is expired.
    6. +
    +

    We recommend to talk to the admin(s) of the affected friendica server. (Admins, please see the respective section of the admin manual.)

    + +

    You can upload images from your computer using the editor. +An overview of all uploaded images is listed at yourpage.com/photos/profilename. +On that page, you can also upload images directly and choose if your contacts will receive a message about this upload.

    +

    Generally, you can attach any kind of file to a post. +This is possible by using the "paper-clip"-symbol in the editor. +These files will be linked to your post and can be downloaded by your contacts. +But it's not possible to get a preview for these items. +Because of this, this upload method is only recommended for office or zipped files. +If you want to share content from Dropbox, Owncloud or any other filehoster, use the "link"-button (chain-symbol).

    +

    When you're adding URLs of other webpages with the "link"-button, Friendica tries to create a small preview. +If this doesn't work, try to add the link by typing: [url=http://example.com]self-chosen name[/url].

    +

    You can also add video and audio files to Posts. +However, instead of a direct upload you have to use one of the following methods:

    +
      +
    1. Add the video or audio link of a hoster (YouTube, Vimeo, Soundcloud and anyone else with oembed/opengraph-support). Videos will be shown with a preview image you can click on to start. SoundCloud directly inserts a player to your post.
    2. +
    3. If you have your own server, you can upload multimedia files via FTP and insert the URL.
    4. +
    +

    Friendica uses HTML5 for embedding content. +Therefore, the supported files are dependent on your browser and operating system. +Some supported file types are WebM, MP4, MP3 and OGG. +See Wikipedia for more of them (video, audio).

    +

    Is it possible to have different avatars per profile?#

    +

    Yes. +On your Edit/Manage Profiles page, you will find a "change profile photo" link. +Clicking this will take you to a page where you can upload a photograph and select which profile it will be associated with. +To avoid privacy leakage, we only display the photograph associated with your default profile as the avatar in your posts.

    +

    How can I view Friendica in a certain language?#

    +

    You can do this by adding the lang parameter to the url in your url bar. +The data in the parameter is a ISO 639-1 code. +A question mark is required for the separation between url and parameters.

    +

    Example:

    +
    https://social.example.com/profile/example
    +
    +

    in German:

    +
    https://social.example.com/profile/example?lang=de.
    +
    +

    When a certain language is forced, the language remains until session is closed.

    +

    How do blocked, ignored, archived and hidden contacts behave?#

    +

    Blocked#

    +

    Direct communication will be blocked. +Blocked contacts are not included in delivery, and their own posts to you are not imported. +However, their conversations with your friends will still be visible in your stream. +If you remove a contact completely, they can send you another friend request. +Blocked contacts cannot do this. They cannot communicate with you directly, only through friends.

    +

    Ignored#

    +

    Ignored contacts are included in delivery and will receive your posts and private messages. +However, we do not import their posts or private messages to you. +Like blocking, you will still see this person's comments to Posts made by your friends.

    +

    An addon called "blockem" can be installed to collapse/hide all posts from a particular person in your stream if you desire complete blocking of an individual, including their conversations with your other friends.

    +

    Archived#

    +

    Communication is not possible and will not be attempted. +However, unlike blocking, existing posts this person made before being archived will be visible in your stream.

    +

    Hidden#

    +

    Contact not be displayed in your public friend list. +However, a hidden contact will appear normally in conversations and this may expose their hidden status to anybody who can see the conversation.

    +

    What happens when an account is removed?#

    +

    If you remove your account, it will be scheduled for permanent deletion in seven days. +As soon as you activate the deletion process you won't be able to log in anymore. +Only the administrator of your node can halt this process prior to permanent deletion.

    +

    After the elapsed time of seven days, all your posts, messages, photos, and personal information stored on your node will be deleted. +Your node will also issue removal requests to all your contacts; this will also remove your profile from the global directory if you are listed. +Your username cannot be reissued for future sign-ups for security reasons.

    +

    Can I follow a hashtag?#

    +

    Yes. Simply add the hashtag to your saved searches. +The posts will appear on your network page. +For technical reasons, your answers to such posts won't appear on the "personal" tab in the network page and the whole thread isn't accessible via the API.

    +

    How to create an RSS feed of the stream?#

    +

    If you want to share your public page via rss you can use one of the following links:

    +

    RSS feed of your posts#

    +
    basic-url.com//feed/[nickname]/posts
    +
    +

    Example: Friendica Support

    +
    https://forum.friendi.ca/feed/helpers/posts
    +
    +

    RSS feed of the conversations at your site#

    +
    basic-url.com/feed/profilename/comments
    +
    +

    Example: Friendica Support

    +
    https://forum.friendi.ca/feed/helpers/comments
    +
    +

    What friendica clients can I use?#

    +

    Friendica supports Mastodon API and Twitter API | gnusocial. +This means you can use some Mastodon and Twitter clients for Friendica. +The available features are client specific and may differ.

    +

    Android#

    + +

    SailfishOS#

    + +

    iOS#

    + +

    Linux#

    + +

    macOS#

    + +

    Windows#

    + +

    Web Frontend#

    + + + +
    + +
    +
    + + + +
    + + + +
    +
    +
    +
    + + + + + + + + \ No newline at end of file diff --git a/develop/en/user/forums/index.html b/develop/en/user/forums/index.html new file mode 100644 index 0000000..292821e --- /dev/null +++ b/develop/en/user/forums/index.html @@ -0,0 +1,3384 @@ + + + + + + + + + + + + + + + + + + + + + + Forums - Friendica documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + +
    + + + + +
    + + +
    + +
    + + + + + + + + + +
    +
    + + + +
    +
    +
    + + + + + + +
    +
    +
    + + + +
    +
    +
    + + + +
    +
    +
    + + +
    +
    + + + + + + + + + + + + + +

    Forums#

    +

    Friendica also lets you create community forums and other types of accounts that can function as discussion forums, celebrity accounts, announcement channels, news reflectors, or organization pages, depending on how you want to interact with others. Management of these pages can be delegated to other accounts, or a parent account can be designated to easily toggle multiple identities.

    +

    Every page in Friendica has a nickname and these must all be unique. This applies to all forums, whether they are normal profiles or forum profiles.

    +

    Managing Accounts#

    +

    To create a new linked account that can be used as a forum, log in to your normal account and go to Settings > Manage Accounts. +Here you can register additional accounts with new nicknames that will be linked to your primary account.

    +

    You may appoint a delegate to manage your new account (e.g. forum page). +The "Delegates" section of Manage Accounts page will provide you with a list of contacts on this instance under "Potential Delegates". +Selecting one or more persons will give them access to manage your forum. +They will be able to edit contacts, profiles, and all content for this account/page. +Please use this facility wisely. +Delegated managers will not be able to alter basic account settings, such as passwords or page types, or remove the account.

    +

    Additionally, this page is also where you can choose to designate an account as a parent user. +If your primary account is designated as the parent user, you will be able to easily toggle identities and manage your forums or other types of accounts.

    +

    Types of Accounts#

    +

    On the new account, visit the Settings > Account page. +Towards the end of the page is a section for "Advanced account types". +Typically, you would use "Personal Page - Standard" for a normal personal account with manual approval of “friends” and “followers.” +This is the default selection. +On this page you can change the type of account if desired.

    +

    The other subtypes of a Personal Page are “Soapbox” and “Love-all.” +A Soapbox account is an announcement channel that automatically approvals follower requests. +Everything posted by the account will go out to the followers, but there will be no opportunity for interaction. +This setting would typically be used for announcements or corporate communications. +“Love-all” automatically approves contacts as friends.

    +

    In addition to Personal Page, there are options for Organization Page, News Page, and Community Forum. +Organization and New Pages automatically approve contact requests as followers.

    +

    Community Forum provide the ability for people to become friends/fans of the forum without requiring approval. +This creates a forum page where all members can freely interact.

    +

    Posting to Community forums#

    +

    If you are a member of a community forum, you may post to the forum by including an @-tag in the post mentioning the forum. +For example @bicycle would send my post to all members of the group "bicycle" in addition to the normal recipients. +If you mention a forum (you are a member of) in a new posting, the posting will be distributed to all members of the forum, regardless of your privacy settings for the posting. +Also, if the forum is a public forum, your posting will be public for the all internet users. +If your post is private you must also explicitly include the group in the post permissions (to allow the forum "contact" to see the post) and mention it in a tag (which redistributes the post to the forum members). +Posting privately to a public forum, will result in your posting being displayed on the forum wall, but not on yours.

    +

    Additionally, it is possible to address a forum with the exclamation mark. +In the example above this means that you can address the bicycle forum via !bicycle. +The difference to the @ is that the post will only be sent to the addressed forum. +This also means that you shouldn't address multiple forums in a single post in that way since it will only be distributed by one the forums.

    +

    You may also post to a community forum by posting a "wall-to-wall" post using secure cross-site authentication.

    +

    Comments which are relayed to community forums will be relayed back to the original post creator. +Mentioning the forum with an @-tag in a comment does not relay the message, as distribution is controlled entirely by the original post creator.

    + + +
    + +
    +
    + + + +
    + + + +
    +
    +
    +
    + + + + + + + + \ No newline at end of file diff --git a/develop/en/user/groups-and-privacy/index.html b/develop/en/user/groups-and-privacy/index.html new file mode 100644 index 0000000..b95b8d0 --- /dev/null +++ b/develop/en/user/groups-and-privacy/index.html @@ -0,0 +1,3496 @@ + + + + + + + + + + + + + + + + + + + + + + Groups & Privacy - Friendica documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + +
    + + + + +
    + + +
    + +
    + + + + + + + + + +
    +
    + + + +
    +
    +
    + + + + + + +
    +
    +
    + + + +
    +
    +
    + + + +
    +
    +
    + + +
    +
    + + + + + + + + + + + + + +

    Groups and Privacy#

    +

    Groups are merely collections of friends. +But Friendica uses these to unlock some very powerful features.

    +

    Setting Up Groups#

    +

    To create a group, visit your Friendica "Contacts" page and select "Create a new group". +Give the group a name.

    +

    This brings you to a page where you can select the group members.

    +

    You will have two boxes on this page. +The top box is the roster of current group members. +Below that is another box containing all of your friends who are not members of the group.

    +

    If you click on a photo of a person who isn't in the group, they will be put into the group. +If you click on a photo of a person who is in the group, they will be removed from it.

    +

    Access Control#

    +

    Once you have created a group, you may use it in any access control list. +This is the little lock icon beneath the status update box on your home page. +If you click this you can select who can see and who can not see the post you are about to make. +These can be individual people or groups.

    +

    On your "Network" page you will find posts and conversation from everybody in your network. +You may select an individual group on this page to show conversations pertaining only to members of that group.

    +

    But wait, there's more...

    +

    If you look carefully when visiting a group from your Network page, the lock icon under the status update box has an exclamation mark next to it. +This is meant to draw attention to that lock. +Click the lock. +You will see that since you are only viewing a certain group of people, your status updates while on that screen default to only being seen by that same group of people. +This is how you keep your future employers from seeing what you write to your drinking buddies. +You can over-ride this setting, but this makes it easy to separate your conversations into different friend circles.

    +

    Default Post Privacy#

    +

    By default, Friendica assumes that you want all of your posts to be private. +Therefore, when you sign up, Friendica creates a group for you that it will automatically add all of your contacts to. +All of your posts are restricted to that group by default.

    +

    Note that this behaviour can be overridden by your site admin, in which case your posts will be "public" (i.e. visible to the entire Internet) by default.

    +

    If you want your posts to be "public" by default, you can change your default post permissions on your Settings page. +You also have the option there to change which groups you post to by default, or to change which group your new contacts get placed into by default.

    +

    Privacy Concerns To Be Aware Of#

    +

    These private conversations work best when your friends are Friendica members. +We know who else can see the conversations - nobody, unless your friends cut and paste the messages and send them to others.

    +

    This is a trust issue you need to be aware of. +No software in the world can prevent your friends from leaking your confidential and trusted communications. +Only a wise choice of friends.

    +

    But it isn't as clear-cut when dealing with GNU Social and other network providers. +If you look at the Contact Edit page for any person, we will tell you whether they are members of an insecure network where you should exercise caution.

    +

    Once you have created a post, you can not change the permissions assigned. +Within seconds, it has been delivered to lots of people - and perhaps everybody it was addressed to. +If you mistakenly created a message and wish you could take it back, the best you can do is to delete it. +We will send out a deleted notification to everybody who received the message - and this should wipe out the message with the same speed it was initially propagated. +In most cases it will be completely wiped from the Internet - in under a minute. +Again, this applies to Friendica networks. +Once a message spreads to other networks, it may not be removed quickly and in some cases it may not be removed at all.

    +

    In case you haven't yet figured this out, we are encouraging you to encourage your friends to use Friendica - because all these privacy features work much better within a privacy-aware network. +Many of the other social networks Friendica can connect to have no privacy controls.

    +

    Profiles, Photos, and Privacy#

    +

    The decentralised nature of Friendica (many websites exchanging information rather than one website which controls everything) has some implications with privacy as it relates to people on other sites. +There are things you should be aware of, so you can decide best how to interact privately.

    +

    Photos#

    +

    Sharing photos privately is a problem. +We can only share them privately with Friendica members. +In order to share with other people, we need to prove who they are. +We can prove the identity of Friendica members, as we have a mechanism to do so. +Your friends on other networks will be blocked from viewing these private photos because we cannot prove that they should be allowed to see them.

    +

    Our developers are working on solutions to allow access to your friends - no matter what network they are on. +However, we take privacy seriously and don't behave like some networks that pretend your photos are private, but make them available to others without proof of identity.

    +

    Profiles#

    +

    Your profile and "wall" may also be visited by your friends from other networks, and you can block access to these by web visitors that Friendica doesn't know. +Be aware that this could include some of your friends on other networks.

    +

    This may produce undesired results when posting a long status message to (for instance) Twitter. +When Friendica sends a post to these networks which exceeds the service length limit, we truncate it and provide a link to the original. +The original is a link back to your Friendica profile. +As Friendica cannot prove who they are, it may not be possible for these people to view your post in full.

    +

    For people in this situation we would recommend providing a "Twitter-length" summary, with more detail for friends that can see the post in full. +You can do so by including the BBCode tag abstract in your posting.

    +

    Blocking your profile or entire Friendica site from unknown web visitors also has serious implications for communicating with GNU Social members. +These networks communicate with others via public protocols that are not authenticated. +In order to view your posts, these networks have to access them as an "unknown web visitor". +If we allowed this, it would mean anybody could in fact see your posts, and you've instructed Friendica not to allow this. +So be aware that the act of blocking your profile to unknown visitors also has the effect of blocking outbound communication with public networks (such as GNU Social) and feed readers such as Google Reader.

    + + +
    + +
    +
    + + + +
    + + + +
    +
    +
    +
    + + + + + + + + \ No newline at end of file diff --git a/develop/en/user/keyboard-shortcuts/index.html b/develop/en/user/keyboard-shortcuts/index.html new file mode 100644 index 0000000..3357ee0 --- /dev/null +++ b/develop/en/user/keyboard-shortcuts/index.html @@ -0,0 +1,3327 @@ + + + + + + + + + + + + + + + + + + + + + + Shortcuts - Friendica documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + +
    + + + + +
    + + +
    + +
    + + + + + + + + + +
    +
    + + + +
    +
    +
    + + + + + + +
    +
    +
    + + + +
    +
    +
    + + + +
    +
    +
    + + +
    +
    + + + + + + + + + + + + + +

    Keyboard shortcuts in Friendica#

    +

    General#

    +
      +
    • j: Scroll to next thread
    • +
    • k: Scroll to previous thread
    • +
    + + +
    + +
    +
    + + + +
    + + + +
    +
    +
    +
    + + + + + + + + \ No newline at end of file diff --git a/develop/en/user/making-friends/index.html b/develop/en/user/making-friends/index.html new file mode 100644 index 0000000..3b480a5 --- /dev/null +++ b/develop/en/user/making-friends/index.html @@ -0,0 +1,3537 @@ + + + + + + + + + + + + + + + + + + + + + + Making friends - Friendica documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + +
    + + + + +
    + + +
    + +
    + + + + + + + + + +
    +
    + + + +
    +
    +
    + + + + + + +
    +
    +
    + + + + + + +
    +
    + + + + + + + + + + + + + +

    Making Friends#

    +

    Friendship in Friendica can sometimes take on different meaning. +But let's keep it simple; you want to be friends with somebody. +How do you do it?

    +

    The Directories#

    +

    Friendica has two different kinds of "address book". +The directory of the Friendica server you are registered on and a global directory to which your and other Friendica servers submit account information.

    +

    The first thing you can do is look at the Directory. +The directory is split up into two parts. +If you click the directory button, you will be presented with a list of all members (who chose to be listed) on your server.

    +

    You'll also see a link to a Global Directory. +There are several global directories across the globe that regularly exchange information with each other. +The specific global directory that you see usually depends on where your server is located. +If you click through to the global directory, you will be presented with a list of everybody who chooses to be listed across all instances of Friendica. +You will also see a "Show Community Forums" link, which will direct you to Groups, Forums and Fanpages. +You connect to people, groups and forums in the same way, except groups and forums will automatically accept your introduction request, whereas a human will approve you manually.

    +

    Connect to other Friendica users#

    +

    Visit their profile. +Just beneath their profile picture will be the word 'Connect' (we're assuming this is an English language profile). +Click that 'Connect' button, and it will take you to a 'Connect' form.

    +

    The form is going to ask you for your Identity Address. +This is necessary so that this person's website can find yours.

    +

    If your Friendica site is called "demo.friendica.com" and your username/nickname on that site is "bob", you would enter "bob@demo.friendica.com" in this form.

    +

    Notice this looks just like an email address. +It's meant to be that way. +It's easy for people to remember.

    +

    You could also put in the URL of your "home" page, such as "http://demo.friendica.com/profile/bob" instead of the email-style address.

    +

    When you've submitted the connection page, it will take you back to your own site where you must then log in (if necessary) and verify the connection request on your site. +Once you've done this, the two websites can communicate with each other to complete the process (after your new friend has approved the request).

    +

    If you already know somebody's Identity Address, you can enter it in the "connect" box on your "Contacts" page. +This will take you through a similar process.

    +

    Connect to users of alternate networks#

    +

    Across the Federation and Fedivese#

    +

    You can also use your Identity Address or other people's Identity Addresses to become friends across the so-called Federation/Fedivese of open source social media. +Currently, Friendica supports connections with people on diaspora*, Red, Hubzilla, GNU Social, StatusNet, Mastodon, Pleroma, socialhome, and ganggo platforms.

    +

    If you know (for instance) "alice" on gnusocial.net (a GNU Social site) you could put alice@gnusocial.net into your Contact page and become friends across networks. +Likewise, you can put in the URL to Alice's gnusocial.net page, if you wish. +Note: Some versions of GNU Social software may require the full URL to your profile and may not work with the identity address.

    +

    People on these networks can also initiate contact with you, if they know your contact details.

    +

    Other social media#

    +

    If your server provides this functionality, you can also connect with people one +Twitter or important feeds from Tumblr, WordPress, and many more.

    +

    To connect, enter their contact details in the "connect" box on your "Contacts" page.

    +

    Email#

    +

    If you have supplied your mailbox connection information on your Settings page, you can enter the email address of anybody that has sent you a message recently and have their email messages show up in your social stream. +You can also reply to them from within Friendica.

    +

    Create an email contact with for example Alice on Gmail, enter her email in following format "mailto:alice@gmail.no". +In order to avoid abuse or spam, you must have an email from Alice with the correct email address in your email inbox.

    +

    Subscribing to mailing lists is done in the same way, but without the use of the "mailto:" prefix. +To subscribe to a mailing list, enter the email in following example format "mailling-list@list-server.net".

    +

    Syndication feeds#

    +

    You can "follow" almost anybody or any website that produces a syndication feed (RSS/Atom,etc.). +If we can find an information stream and a name to attach to the contact, we'll try to connect with them.

    +

    Notification#

    +

    When somebody requests friendship you will receive a notification. +You will usually need to approve this before the friendship is complete.

    +

    Approval#

    +

    Some networks allow people to send you messages without being friends and without your approval. +Friendica does not allow this by default, as it would open a gateway for spam.

    +

    Unilateral or bilateral friendships#

    +

    When you receive a friendship notification from another Friendica member, you will have the option of allowing them as a "Follower" or as a "Friend". +If they are a follower, they can see what you have to say, including private communications that you send to them, but not vice versa. +As a friend, you can both communicate with each other.

    +

    diaspora* uses a different terminology, and you are given the option of allowing them to "share with you", or being full friends.

    +

    Ignoring, blocking and deleting contacts#

    +

    Once you have become friends, if you find the person constantly sends you spam or worthless information, you can "Ignore" them - without breaking off the friendship or even alerting them to the fact that you aren't interested in anything they are saying. +In many ways they are like a "follower" - but they don't know this. +They think they are a friend.

    +

    You can also "block" a person. +This completely blocks communications with that person. +They may still be able to see your public posts, as can anybody in the world, but they cannot communicate with you directly.

    +

    You can also delete a friend no matter what the friendship status - which completely removes everything relating to that person from your website.

    + + +
    + +
    +
    + + + +
    + + + +
    +
    +
    +
    + + + + + + + + \ No newline at end of file diff --git a/develop/en/user/move-account/index.html b/develop/en/user/move-account/index.html new file mode 100644 index 0000000..e9b4061 --- /dev/null +++ b/develop/en/user/move-account/index.html @@ -0,0 +1,3361 @@ + + + + + + + + + + + + + + + + + + + + + + Move account - Friendica documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + +
    + + + + +
    + + +
    + +
    + + + + + + + + + +
    +
    + + + +
    +
    +
    + + + + + + +
    +
    +
    + + + +
    +
    +
    + + + +
    +
    +
    + + +
    +
    + + + + + + + + + + + + + +

    How to move your account between servers#

    +

    ! This is an experimental feature

    +
      +
    • Go to "Settings" -> "Export personal data"
    • +
    • Click on "Export account" to save your account data.
    • +
    • Save the file in a secure place! It contains your details, your contacts, groups, and personal settings. It also contains your secret keys to authenticate yourself to your contacts.
    • +
    • Go to your new server, and open http://newserver.com/uimport (there is not a direct link to this page at the moment). Please consider that this is only possible on servers with open registration. On other systems only the administrator can add accounts with an uploaded file.
    • +
    • Do NOT create a new account prior to importing your old settings - uimport should be used instead of register.
    • +
    • Load your saved account file and click "Import".
    • +
    • After the move, the account on the old server will not work reliably anymore, and should be not used.
    • +
    +

    Friendica contacts#

    +

    Friendica will recreate your account on the new server, with your contacts and groups. +A message is sent to Friendica contacts, to inform them about your move: +If your contacts are running on an updated server, your details on their side will be automatically updated.

    +

    GNU Social contacts#

    +

    Contacts on GNU Social will be archived, as we can't inform them about your move. +You should ask them to remove your contact from their lists and re-add you, and you should do the same with their contact.

    +

    Diaspora contacts#

    +

    Newer Diaspora servers are able to process "account migration" messages.

    + + +
    + +
    +
    + + + +
    + + + +
    +
    +
    +
    + + + + + + + + \ No newline at end of file diff --git a/develop/en/user/quick-start/finally/index.html b/develop/en/user/quick-start/finally/index.html new file mode 100644 index 0000000..f15db62 --- /dev/null +++ b/develop/en/user/quick-start/finally/index.html @@ -0,0 +1,3345 @@ + + + + + + + + + + + + + + + + + + + + + + Finally - Friendica documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + +
    + + + + +
    + + +
    + +
    + + + + + + + + + +
    +
    + + + +
    +
    +
    + + + + + + +
    +
    +
    + + + +
    +
    +
    + + + +
    +
    +
    + + +
    +
    + + + + + + + + + + + + + +

    QuickStart - Finally#

    +

    And that brings the Quick Start to an end.

    +

    Here are some more things to help get you started:

    +

    Groups#

    + +

    Documentation#

    + + + +
    + +
    +
    + + + +
    + + + +
    +
    +
    +
    + + + + + + + + \ No newline at end of file diff --git a/develop/en/user/quick-start/groups-and-pages/index.html b/develop/en/user/quick-start/groups-and-pages/index.html new file mode 100644 index 0000000..f1fb5b7 --- /dev/null +++ b/develop/en/user/quick-start/groups-and-pages/index.html @@ -0,0 +1,3288 @@ + + + + + + + + + + + + + + + + + + + + + + Groups & Pages - Friendica documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + +
    + + + + +
    + + +
    + +
    + + + + + + + + + +
    +
    + + + +
    +
    +
    + + + + + + +
    +
    +
    + + + +
    +
    +
    + + + +
    +
    +
    + + +
    +
    + + + + + + + + + + + + + +

    Quick Start - Groups and Pages#

    +

    This is the global directory. +If you get lost, you can click this link to bring yourself back here.

    +

    On this page, you'll find a collection of groups, forums and celebrity pages. +Groups are not real people. +Connecting to them is similar to "liking" something on Facebook, or signing up for a new forum. +You don't have to feel awkward about introducing yourself to a new person, because they're not people!

    +

    When you connect to a group, all messages to that group will start appearing in your network tab. +You can comment on these posts, or post to the group yourself without ever having to add any of the groups members. +This is a great way to make friends dynamically - you'll find people you like and add each other naturally instead of adding random strangers. +Simply find a group you're interested in, and connect to it the same way you did with people in the last section. +There are a lot of groups, and you're likely to get lost. +Remember the link at the top of this page will bring you back here.

    +

    Once you've added some groups, move on to the next section.

    + + + +
    + +
    +
    + + + +
    + + + +
    +
    +
    +
    + + + + + + + + \ No newline at end of file diff --git a/develop/en/user/quick-start/guide/index.html b/develop/en/user/quick-start/guide/index.html new file mode 100644 index 0000000..952ed98 --- /dev/null +++ b/develop/en/user/quick-start/guide/index.html @@ -0,0 +1,3277 @@ + + + + + + + + + + + + + + + + + + + + + + Start - Friendica documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + +
    + + + + +
    + + +
    + +
    + + + + + + + + + +
    +
    + + + +
    +
    +
    + + + + + + +
    +
    +
    + + + +
    +
    +
    + + + +
    +
    +
    + + +
    +
    + + + + + + + + + + + +

    Quick Start - Beginning#

    +

    First things first, let's make sure you're logged in to your account. +If you're not already logged in, do so in the frame below.

    +

    Once you've logged in (or if you are already logged in), you'll now be looking at your profile page.

    +

    This is a bit like a Facebook wall. +It's where all your status messages are kept, and where your friends come to post on your wall.

    +

    To write your status, simply click on the Pencil & Paper icon in the top right (in the Frio theme), or click in the box that says "share" (other themes). +When you do this, the posting dialog box will appear or the share box will expand.

    +

    You can see some formatting options such as Bold, Italics and Underline, as well as ways to add links, pictures (dependent on the theme), and a paperclip icon to attach or embed content. +You can use these to upload pictures and files from your computer, share websites with a bit of preview text, or embed video and audio files from elsewhere on the web. +With the Frio theme, the browser tab can be used to upload and post media from your account. +You can also set your post location here.

    +

    Once you've finished writing your post, click on the padlock icon or permissions tab to select who can see it. +If you do not change anything, your post will be public. +This means it will appear to anybody who views your profile, and in the community tab if your site has it enabled, as well as in the network tab of your contacts.

    +

    Play around with this a bit, then when you're ready to move on, we'll take a look at the Network Tab

    + + +
    + +
    +
    + + + +
    + + + +
    +
    +
    +
    + + + + + + + + \ No newline at end of file diff --git a/develop/en/user/quick-start/making-new-friends/index.html b/develop/en/user/quick-start/making-new-friends/index.html new file mode 100644 index 0000000..c54d06d --- /dev/null +++ b/develop/en/user/quick-start/making-new-friends/index.html @@ -0,0 +1,3288 @@ + + + + + + + + + + + + + + + + + + + + + + Making new friends - Friendica documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + +
    + + + + +
    + + +
    + +
    + + + + + + + + + +
    +
    + + + +
    +
    +
    + + + + + + +
    +
    +
    + + + +
    +
    +
    + + + +
    +
    +
    + + +
    +
    + + + + + + + + + + + + + +

    Quick Start - Making new friends#

    +

    This is your Suggested Friends page. +If you get lost, you can click this link to bring yourself back here.

    +

    This is a bit like the Friend Suggestions page of Facebook. +Everybody on this list has agreed that they may be suggested as a friend. +This means they're unlikely to refuse an introduction you send, and they want to meet new people too!

    +

    See somebody you like the look of? +Click the connect button beneath their photograph. +This will bring you to the introductions page. +Fill in the form as instructed, and add a small note (optional). +Now, wait a bit, and they'll accept your request - note that these are real people, and it might take a while. +Now you've added one, you're probably lost. +Click the link at the top of this page to go back to the suggested friends list and add some more.

    +

    Feel uncomfortable adding people you don't know? +Don't worry - that's where Groups and Pages come in!

    + + +
    + +
    +
    + + + +
    + + + +
    +
    +
    +
    + + + + + + + + \ No newline at end of file diff --git a/develop/en/user/quick-start/network/index.html b/develop/en/user/quick-start/network/index.html new file mode 100644 index 0000000..42da836 --- /dev/null +++ b/develop/en/user/quick-start/network/index.html @@ -0,0 +1,3282 @@ + + + + + + + + + + + + + + + + + + + + + + Network - Friendica documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + +
    + + + + +
    + + +
    + +
    + + + + + + + + + +
    +
    + + + +
    +
    +
    + + + + + + +
    +
    +
    + + + +
    +
    +
    + + + +
    +
    +
    + + +
    +
    + + + + + + + + + + + + + +

    Quick Start - Network#

    +

    This is your Network Tab. +If you get lost, you can click this link to bring yourself back here.

    +

    This is a bit like the Newsfeed at Facebook or the Stream at Diaspora. +It's where all the posts from your contacts, groups, and feeds will appear. +If you're new, you won't see anything in this page, unless you posted your status in the last step. +If you've already added a few friends, you'll be able to see their posts. +Here, you can comment, like, or dislike posts, or click on somebody's name to visit their profile page where you can write on their wall.

    +

    Now we need to fill it up, the first step, is to make some new friends.

    + + +
    + +
    +
    + + + +
    + + + +
    +
    +
    +
    + + + + + + + + \ No newline at end of file diff --git a/develop/en/user/remove-account/index.html b/develop/en/user/remove-account/index.html new file mode 100644 index 0000000..f941e9a --- /dev/null +++ b/develop/en/user/remove-account/index.html @@ -0,0 +1,3280 @@ + + + + + + + + + + + + + + + + + + + + + + Remove account - Friendica documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + +
    + + + + +
    + + +
    + +
    + + + + + + + + + +
    +
    + + + +
    +
    +
    + + + + + + +
    +
    +
    + + + +
    +
    +
    + + + +
    +
    +
    + + +
    +
    + + + + + + + + + + + + + +

    Remove Account#

    +

    We don't like to see people leave Friendica, but if you need to remove your account, you should visit the URL

    +

    http://sitename/removeme

    +

    with your web browser. +You will need to be logged in at the time.

    +

    You will be asked for your password to confirm the request. +If this matches your stored password, your account will immediately be marked as deleted. +There is no grace period, this action cannot be reverted. +Most of your content and user data will be deleted shortly in the background.

    +

    We then send out a notification about the account removal to all of your contacts so that they can do the same with their copy of your data.

    +

    For technical reasons some of your user data is still needed to transmit this removal message. +This remaining data will be deleted after a period of around seven days.

    +

    To disallow impersonation we have to save your used nickname, so that it can't be used again to register on this node.

    + + +
    + +
    +
    + + + +
    + + + +
    +
    +
    +
    + + + + + + + + \ No newline at end of file diff --git a/develop/en/user/tags-and-mentions/index.html b/develop/en/user/tags-and-mentions/index.html new file mode 100644 index 0000000..af22ed4 --- /dev/null +++ b/develop/en/user/tags-and-mentions/index.html @@ -0,0 +1,3367 @@ + + + + + + + + + + + + + + + + + + + + + + Tags & Mentions - Friendica documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + +
    + + + + +
    + + +
    + +
    + + + + + + + + + +
    +
    + + + +
    +
    +
    + + + + + + +
    +
    +
    + + + +
    +
    +
    + + + +
    +
    +
    + + +
    +
    + + + + + + + + + + + + + +

    Tags and Mentions#

    +

    Like many other modern social networks, Friendica uses a special notation inside messages to indicate "tags" or contextual links to other entities.

    +

    Mentions#

    +

    People are tagged by preceding their name with the @ character.

    +

    You can tag persons who are in your social circle by adding the "@"-sign in front of the name.

    +
      +
    • @mike - indicates a known contact in your social circle whose nickname is "mike"
    • +
    • @mike_macgirvin - indicates a known contact in your social circle whose full name is "Mike Macgirvin". Note that spaces cannot be used inside tags.
    • +
    • @mike+151 - this form is used by the drop-down tag completion tool. It indicates the contact whose nickname is mike and whose contact identifier number is 151. The drop-down tool may be used to resolve people with duplicate nicknames.
    • +
    +

    You can tag a person on a different network or one that is not in your social circle by using the following notation:

    +
      +
    • @mike@macgirvin.com - This is called a "remote mention" and can only be an email-style locator, not a web URL.
    • +
    +

    Unless their system blocks unsolicited "mentions", the person tagged will likely receive a "Mention" post/activity or become a direct participant in the conversation in the case of public posts. +Friendica blocks incoming “mentions” from people with no relationship to you. +The exception is an ongoing conversation started from a contact of both you and the 3rd person or a conversation in a forum where you are a member of. +This is a spam prevention measure.

    +

    Remote mentions are delivered using the OStatus protocol. +This protocol is used by Friendica and GNU Social and several other systems like Mastodon, but is not currently implemented in Diaspora. +As the OStatus protocol allows this Friendica user can be @-mentioned by users from platforms using this protocol in conversations if the "Enable OStatus support" is activated on the Friendica node. +These @-mentions won't be blocked, even if there is no relationship between the sender and the receiver of the message.

    +

    Friendica makes no distinction between people and forums for the purpose of tagging. +You can use @-mentions for forums like for other accounts to tag the forum. +If you want to post something exclusively to a forum (e.g. the support forum) please use the bang-notation instead of the @tag. +So !helpers will be an exclusive posting to the support forum if you are connected with the forum. +If you select a forum from the ACL a !-mention will be added automatically to your posting.

    +

    If you sort your contacts into groups, you cannot @-mention these groups. +But you can select the group in the access control when creating a new posting, to allow (or disallow) a certain group of people to see the posting. +See Groups and Privacy for more details about grouping your contacts.

    +

    Topical Tags#

    +

    Topical tags are indicated by preceding the tag name with the # character. +This will create a link in the post to a generalised site search for the term provided. +For example, #cars will provide a search link for all posts mentioning 'cars' on your site. +Topical tags are generally a minimum of three characters in length. +Shorter search terms are not likely to yield any search results, although this depends on the database configuration. +The same rules apply as with names that spaces within tags are represented by the underscore character. +It is therefore not possible to create a tag whose target contains an underscore.

    +

    Topical tags are also not linked if they are purely numeric, e.g. #1. +If you wish to use a numeric hashtag, please add some descriptive text such as #2012-elections.

    + + +
    + +
    +
    + + + +
    + + + +
    +
    +
    +
    + + + + + + + + \ No newline at end of file diff --git a/develop/en/user/text-comment/index.html b/develop/en/user/text-comment/index.html new file mode 100644 index 0000000..ac2176d --- /dev/null +++ b/develop/en/user/text-comment/index.html @@ -0,0 +1,3343 @@ + + + + + + + + + + + + + + + + + + + + + + Comments - Friendica documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + +
    + + + + +
    + + +
    + +
    + + + + + + + + + +
    +
    + + + +
    +
    +
    + + + + + + +
    +
    +
    + + + +
    +
    +
    + + + +
    +
    +
    + + +
    +
    + + + + + + + + + + + + + +

    Comment, sort and delete posts#

    +

    Here you can find an overview of the different ways to comment and sort existing posts. +Attention: we've used the "diabook" theme. If you're using another theme, some icons may be different.

    +

    diabook

    +

    The different icons

    +

    post_thumbs_up.png This symbol is used to indicate that you like the post. Click it twice to undo your choice.

    +

    post_thumbs_down.png This symbol is used to indicate that you dislike the post. Click it twice to undo your choice.

    +

    + +

    post_share.png This symbol is used to share a post. A copy of this post will automatically appear in your status editor and add a link to the original post.

    +

    + +

    post_mark.png This symbol is used to mark a post. Marked posts will appear on your network page at the "starred" tab (from "star"). Click it twice to undo your choice.

    +

    + +

    post_tag.png This symbol is used to tag a post with a self-chosen keyword. When you click at the word, you'll get a list of all posts with this tag. Attention: you can't delete the tag once you've set one.

    +

    + +

    post_categorize.png This symbol is used to categorize posts. Choose an existing folder or create a new one. You'll find the created folder on your network page under the "saved folders" tab.

    +

    + +

    post_delete.png This symbol is used to delete your own post or to remove a post of another person from your stream.

    +

    + +

    post_choose.png This symbol is used to choose more than one post to delete in a single step. After selecting all posts, go to the end of the page and click "Delete Selected Items".

    +

    Symbols of other themes#

    +

    Darkbubble darkbubble.png

    +

    Darkzero darkzero.png

    +

    (incl. more "zero"-themes, slackr, comix, easterbunny, facepark)

    +

    Dispy dispy.png (incl. smoothly, testbubble)

    + + +
    + +
    +
    + + + +
    + + + +
    +
    +
    +
    + + + + + + + + \ No newline at end of file diff --git a/develop/en/user/text-editor/index.html b/develop/en/user/text-editor/index.html new file mode 100644 index 0000000..1a1eb23 --- /dev/null +++ b/develop/en/user/text-editor/index.html @@ -0,0 +1,3391 @@ + + + + + + + + + + + + + + + + + + + + + + Text editor - Friendica documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + +
    + + + + +
    + + +
    + +
    + + + + + + + + + +
    +
    + + + +
    +
    +
    + + + + + + +
    +
    +
    + + + +
    +
    +
    + + + +
    +
    +
    + + +
    +
    + + + + + + + + + + + + + +

    Creating posts#

    +

    Here you can find an overview of the different ways to create and edit your post.

    +

    One click on the Pencil & Paper icon in the top right of your Home or Network page, or the "Share" text box, and the post editor shows up. +Below are examples of the post editor in 3 of Friendica's common themes:

    +
    +frio editor +
    Post editor, with the Frio (popular default) theme.
    +
    +

    +
    +vier editor +
    Post editor, with the Vier theme.
    +
    +

    +
    +duepuntozero editor +
    Post editor, with the Duepuntozero theme.
    +
    + +

    Post title is optional, you can set it by clicking on "Set title".

    +

    Posts can optionally be in one or more categories. Write category names separated by a comma to file your new post.

    +

    The Big Empty Textarea is where you write your new post. +You can simply enter your text there and click the "Share" button, and your new post will be public on your profile page and shared to your contact.

    +

    If plain text is not so exciting to you, Friendica understands BBCode to spice up your posts: bold, italic, images, links, lists.. +See BBCode tags reference page to see all what you can do.

    +

    The icons under the text area are there to help you to write posts quickly, but vary depending on the theme:

    +

    With the Frio theme, the Underline, Italics and Bold buttons should be self-explanatory.

    +

    editor Upload a picture from your computer. The image will be uploaded and correct bbcode tag will be added to your post.* In the Frio theme, use the Browser tab instead to Upload and/or attach content to your post.

    +

    + +

    paper_clip This depends on the theme: For Frio, this is to attach remote content - put in a URL to embed in your post, including video or audio content. For other themes: Add files from your computer. Same as picture, but for generic attachment to the post.*

    +

    + +

    chain Add a web address (url). Enter a URL and Friendica will add to your post a link to the url and an excerpt from the web site, if possible.

    +

    + +

    video Add a video. Enter the url to a video (ogg) or to a video page on youtube or vimeo, and it will be embedded in your post with a preview. (In the Frio theme, this is done with the paperclip as mentioned above.) Friendica is using HTML5 for embedding content. Therefore, the supported files are depending on your browser and operating system (OS). Some filetypes are WebM, MP4 and OGG.*

    +

    + +

    mic Add an audio. Same as video, but for audio. Depending on your browser and operation system MP3, OGG and AAC are supported. Additionally, you are able to add URLs from audiohosters like Soundcloud.

    +

    + +

    globe Or location Set your geographic location. This location will be added into a Google Maps search. That's why a note like "New York" or "10004" is already enough.

    +

    +


    +

    + +

    These icons can change depending on the theme. Some examples:

    + + + + + + + + + + + +
    Vier: vier.png 
    Smoothly: darkbubble.png 
    +

    * how to upload files

    +

     

    + +

    lock icon The Lock / Permissions#

    +

    In Frio, the Permissions tab, or in other themes, the Lock button, is the most important feature in Friendica. If the lock is open, your post will be public, and will show up on your profile page when strangers visit it.

    +

    Click on it and the Permission settings window (aka "Access Control Selector" or "ACL Selector") pops up. There you can select who can see the post.

    +
    +Permission settings window +
    Permission settings window with some contact selected
    +
    + +

    Click on "show" under contact name to hide the post to everyone but selected.

    +

    Click on "Visible to everybody" to make the post public again.

    +

    If you have defined some groups, you can check "show" for groups also. All contact in that group will see the post. +If you want to hide the post to one contact of a group selected for "show", click "don't show" under contact name.

    +

    Click again on "show" or "don't show" to switch it off.

    +

    You can search for contacts or groups with the search box.

    +

    See also Group and Privacy

    + + +
    + +
    +
    + + + +
    + + + +
    +
    +
    +
    + + + + + + + + \ No newline at end of file diff --git a/develop/en/user/two-factor-authentication/index.html b/develop/en/user/two-factor-authentication/index.html new file mode 100644 index 0000000..bacbdba --- /dev/null +++ b/develop/en/user/two-factor-authentication/index.html @@ -0,0 +1,3476 @@ + + + + + + + + + + + + + + + + + + + + + + 2FA - Friendica documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + +
    + + + + +
    + + +
    + +
    + + + + + + + + + +
    +
    + + + +
    +
    +
    + + + + + + +
    +
    +
    + + + + + + +
    +
    + + + + + + + + + + + + + +

    Configuring two-factor authentication#

    +

    You can configure two-factor authentication using a mobile app. +A time-based one-time password (TOTP) application automatically generates an authentication code that changes after a certain period of time.

    +

    Tip: To configure authentication via TOTP on multiple devices, during setup, scan the QR code using each device at the same time. +If 2FA is already enabled, and you want to add another device, you must re-configure 2FA from your security settings.

    +

    Enabling two-factor authentication#

    +

    1. Download an authenticator app#

    +

    Any authenticator app should work with Friendica. +Nonetheless, we recommend:

    + +

    2. Record your one-use recovery codes#

    +

    From your two-factor authentication user settings (https://your-site.info/settings/2fa), enter your password and click on "Enable two-factor authentication".

    +

    You will be presented with a list of one-use recovery codes. +Please save those in the same place you are saving your Friendica password (ideally, in a password manager like KeePass).

    +

    When you're done, click on "Next".

    +

    3. Set up your authenticator app#

    +

    You have three methods to set up your authenticator app:

    +
      +
    1. Scan the QR Code with your device camera. + This will automatically configure your account on the app.
    2. +
    3. Click/tap on the provided totp:// URl. + Ideally your authenticator app should be called with this URL and set up your account.
    4. +
    5. Enter your account settings manually. + Friendica is using default settings for token type, code digit count and hashing algorithm, but you may be required to enter them in your app.
    6. +
    +

    Tip: If you have multiple devices, configure them all at this point.

    +

    Then verify your app is correctly configured by submitting a code provided by your app. +This will conclude two-factor authentication configuration.

    +

    Note: If you leave this screen at any point without having submitted a verification code, two-factor authentication won't be enabled on your account. +To complete the configuration, just come back to your two-factor authentication user settings and click on "Finish configuration" after entering your current password.

    +

    Disabling two-factor authentication#

    +

    You can disable two-factor authentication at any time by going to your two-factor authentication user settings and click on "Disable two-factor authentication" after entering your current password.

    +

    You should remove your Friendica account from your authenticator app as it won't work again even if you reenable two-factor authentication. +In this case you will have to configure your authenticator app again using the process above.

    +

    Managing your one-time recovery codes#

    +

    When two-factor authentication is enabled, you can show your recovery codes, including the ones you've already used.

    +

    You can freely regenerate a new set of fresh recovery codes, just be sure to replace the previous ones where you saved them as they won't be active anymore.

    +

    Third-party applications and API#

    +

    Third-party applications using the Friendica API can't accept two-factor time-based authentication codes. +Instead, if you enabled two-factor authentication, you have to generate app-specific randomly generated long passwords to use in your apps instead of your regular account password.

    +

    Note: Your regular password won't work at all when prompted in third-party apps if you enabled two-factor authentication.

    +

    You can generate as many app-specific passwords as you want, they will be shown once to you just after you generated it. +Just copy and paste it in your third-party app in the Friendica account password input field at this point. +We recommend generating a single app-specific password for each separate third-party app you are using, using a meaningful description of the target app (like "Frienqa on my Fairphone 2").

    +

    You can also revoke any and all app-specific password you generated this way. +This may log you out of the third-party application(s) you used the revoked app-specific password to log in with.

    +

    Trusted browsers#

    +

    As a convenience, during two-factor authentication it is possible to identify a browser as trusted. +This will skip all further two-factor authentication prompt on this browser.

    +

    You can remove any or all of these trusted browsers in the two-factor authentication settings.

    + + +
    + +
    +
    + + + +
    + + + +
    +
    +
    +
    + + + + + + + + \ No newline at end of file diff --git a/develop/html/index.php b/develop/html/index.php new file mode 100644 index 0000000..ab83759 --- /dev/null +++ b/develop/html/index.php @@ -0,0 +1,16 @@ + + + + $Projectname Doxygen API Documentation + + +

    $Projectname Doxygen API Documentation not rendered

    + +To get the Doxygen API Documentation you must render it with the program Doxygen (included in most distributions). +
    +$ doxygen Doxyfile
    +
    +
    +back + + diff --git a/develop/index.html b/develop/index.html new file mode 100644 index 0000000..49e3e6a --- /dev/null +++ b/develop/index.html @@ -0,0 +1,3418 @@ + + + + + + + + + + + + + + + + + + + + + + Home - Friendica documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + +
    + + + + +
    + + +
    + +
    + + + + + + + + + +
    +
    + + + +
    +
    +
    + + + + + + +
    +
    +
    + + + +
    +
    +
    + + + +
    +
    +
    + + +
    +
    + + + + + + + + + + + +

    Friendica Documentation and Resources#

    +

    User Manual#

    + +

    Admin Manual#

    + +

    Developer Manual#

    + +

    External Resources#

    + + + +
    + +
    +
    + + + +
    + + + +
    +
    +
    +
    + + + + + + + + \ No newline at end of file diff --git a/develop/search/search_index.json b/develop/search/search_index.json new file mode 100644 index 0000000..499c1f8 --- /dev/null +++ b/develop/search/search_index.json @@ -0,0 +1 @@ +{"config":{"indexing":"full","lang":["en","de"],"min_search_length":3,"prebuild_index":false,"separator":"[\\s\\-]+"},"docs":[{"location":"admin/config/","tags":["admin","configuration"],"text":"Config values that can only be set in config/local.config.php # Friendica's configuration is done in two places: in PHP array configuration files and in the config database table. Database config values overwrite the same file config values. File configuration # The configuration format for file configuration is an array returned from a PHP file. This prevents your webserver from displaying your private configuration. It interprets the configuration files and displays nothing. A typical configuration file looks like this: [ // Comment line 'key' => 'value' , ], 'section2' => [ 'array' => [ 'value0' , 'value1' , 'value2' ], ], ]; Configuration location # The config directory holds key configuration files and can have different config files. All of them have to end with .config.php and must not include -sample in their name. Some examples of common known configuration files: - local.config.php holds the current node custom configuration. - addon.config.php is optional and holds the custom configuration for specific addons. Addons can define their own default configuration values in addon/[addon]/config/[addon].config.php which is loaded when the addon is activated. If needed, an alternative config path can be used by using the FRIENDICA_CONFIG_DIR environment variable (full path required!). This is useful in case of hardening the system by separating configuration from program binaries. Static Configuration location # The static directory holds the codebase default configurations files. They must not be changed by users, because they can get changed from release to release. Currently, the following configurations are included: - defaults.config.php holds the default values for all the configuration keys that can only be set in local.config.php . - settings.config.php holds the default values for some configuration keys that are set through the admin settings page. Migrating from .htconfig.php to config/local.config.php # The legacy .htconfig.php configuration file is still supported, but is deprecated and will be removed in a subsequent Friendica release. The migration is pretty straightforward: If you had any addon-specific configuration in your .htconfig.php , just copy config/addon-sample.config.php to config/addon.config.php and move your configuration values. Afterwards, copy config/local-sample.config.php to config/local.config.php , move the remaining configuration values to it according to the following conversion chart, then rename your .htconfig.php to check your node is working as expected before deleting it. table.config { margin: 1em 0; background-color: #f9f9f9; border: 1px solid #aaa; border-collapse: collapse; color: #000; width: 100%; } table.config > tr > th, table.config > tr > td, table.config > * > tr > th, table.config > * > tr > td { border: 1px solid #aaa; padding: 0.2em 0.4em } table.config > tr > th, table.config > * > tr > th { background-color: #f2f2f2; text-align: center; width: 50% } .htconfig.php config/local.config.php $db_host = 'localhost'; $db_user = 'mysqlusername'; $db_pass = 'mysqlpassword'; $db_data = 'mysqldatabasename'; $a->config[\"system\"][\"db_charset\"] = 'utf8mb4'; 'database' => [ 'hostname' => 'localhost', 'username' => 'mysqlusername', 'password' => 'mysqlpassword', 'database' => 'database', 'charset' => 'utf8mb4', ], $a->config[\"section\"][\"key\"] = \"value\"; 'section' => [ 'key' => 'value', ], $a->config[\"section\"][\"key\"] = array( \"value1\", \"value2\", \"value3\" ); 'section' => [ 'key' => ['value1', 'value2', 'value3'], ], $a->config[\"key\"] = \"value\"; 'config' => [ 'key' => 'value', ], $a->config['register_policy'] = REGISTER_CLOSED; 'config' => [ 'register_policy' => \\Friendica\\Module\\Register::CLOSED, ], $a->path = \"value\"; 'system' => [ 'urlpath' => 'value', ], $default_timezone = \"value\"; 'system' => [ 'default_timezone' => 'value', ], $pidfile = \"value\"; 'system' => [ 'pidfile' => 'value', ], $lang = \"value\"; 'system' => [ 'language' => 'value', ], Migrating from config/local.ini.php to config/local.config.php # The legacy config/local.ini.php configuration file is still supported, but is deprecated and will be removed in a subsequent Friendica release. The migration is pretty straightforward: If you had any addon-specific configuration in your config/addon.ini.php , just copy config/addon-sample.config.php to config/addon.config.php and move your configuration values. Afterwards, copy config/local-sample.config.php to config/local.config.php , move the remaining configuration values to it according to the following conversion chart, then rename your config/local.ini.php file to check your node is working as expected before deleting it. config/local.ini.php config/local.config.php [database] hostname = localhost username = mysqlusername password = mysqlpassword database = mysqldatabasename charset = utf8mb4 'database' => [ 'hostname' => 'localhost', 'username' => 'mysqlusername', 'password' => 'mysqlpassword', 'database' => 'database', 'charset' => 'utf8mb4', ], [section] key = value 'section' => [ 'key' => 'value', ], [config] register_policty = REGISTER_CLOSED 'config' => [ 'register_policy' => \\Friendica\\Module\\Register::CLOSED, ], [section] key[] = value1 key[] = value2 key[] = value3 'section' => [ 'key' => ['value1', 'value2', 'value3'], ], Database Settings # The configuration variables database.hostname , database.username , database.password , database.database and database.charset are holding your credentials for the database connection. If you need to specify a port to access the database, you can do so by appending :portnumber to the database.hostname variable. 'database' => [ 'hostname' => 'your.mysqlhost.com:123456', ] If all the following environment variables are set, Friendica will use them instead of the previously configured variables for the db: MYSQL_HOST MYSQL_PORT MYSQL_USERNAME MYSQL_PASSWORD MYSQL_DATABASE Config values that can only be set in config/local.config.php # There are some config values that haven't found their way into the administration page. This has several reasons. Maybe they are part of a current development that isn't considered stable and will be added later in the administration page when it is considered safe. Or it triggers something that isn't expected to be of public interest. Or it is for testing purposes only. Attention: Please be warned that you shouldn't use one of these values without the knowledge what it could trigger. Especially don't do that with undocumented values. These configurations keys and their default value are listed in static/defaults.config.php and should be overwritten in config/local.config.php . Administrator Options # Enabling the admin panel for an account, and thus making the account holder admin of the node, is done by setting the variable 'config' => [ 'admin_email' => 'someone@example.com', ] Where you have to match the email address used for the account with the one you enter to the config/local.config.php file. If more than one account should be able to access the admin panel, separate the email addresses with a comma. 'config' => [ 'admin_email' => 'someone@example.com,someoneelse@example.com', ] If you want to have a more personalized closing line for the notification emails you can set a variable for the admin_name . 'config' => [ 'admin_name' => 'Marvin', ]","title":"Config Values"},{"location":"admin/config/#config-values-that-can-only-be-set-in-configlocalconfigphp","text":"Friendica's configuration is done in two places: in PHP array configuration files and in the config database table. Database config values overwrite the same file config values.","title":"Config values that can only be set in config/local.config.php"},{"location":"admin/config/#file-configuration","text":"The configuration format for file configuration is an array returned from a PHP file. This prevents your webserver from displaying your private configuration. It interprets the configuration files and displays nothing. A typical configuration file looks like this: [ // Comment line 'key' => 'value' , ], 'section2' => [ 'array' => [ 'value0' , 'value1' , 'value2' ], ], ];","title":"File configuration"},{"location":"admin/config/#configuration-location","text":"The config directory holds key configuration files and can have different config files. All of them have to end with .config.php and must not include -sample in their name. Some examples of common known configuration files: - local.config.php holds the current node custom configuration. - addon.config.php is optional and holds the custom configuration for specific addons. Addons can define their own default configuration values in addon/[addon]/config/[addon].config.php which is loaded when the addon is activated. If needed, an alternative config path can be used by using the FRIENDICA_CONFIG_DIR environment variable (full path required!). This is useful in case of hardening the system by separating configuration from program binaries.","title":"Configuration location"},{"location":"admin/config/#static-configuration-location","text":"The static directory holds the codebase default configurations files. They must not be changed by users, because they can get changed from release to release. Currently, the following configurations are included: - defaults.config.php holds the default values for all the configuration keys that can only be set in local.config.php . - settings.config.php holds the default values for some configuration keys that are set through the admin settings page.","title":"Static Configuration location"},{"location":"admin/config/#migrating-from-htconfigphp-to-configlocalconfigphp","text":"The legacy .htconfig.php configuration file is still supported, but is deprecated and will be removed in a subsequent Friendica release. The migration is pretty straightforward: If you had any addon-specific configuration in your .htconfig.php , just copy config/addon-sample.config.php to config/addon.config.php and move your configuration values. Afterwards, copy config/local-sample.config.php to config/local.config.php , move the remaining configuration values to it according to the following conversion chart, then rename your .htconfig.php to check your node is working as expected before deleting it. table.config { margin: 1em 0; background-color: #f9f9f9; border: 1px solid #aaa; border-collapse: collapse; color: #000; width: 100%; } table.config > tr > th, table.config > tr > td, table.config > * > tr > th, table.config > * > tr > td { border: 1px solid #aaa; padding: 0.2em 0.4em } table.config > tr > th, table.config > * > tr > th { background-color: #f2f2f2; text-align: center; width: 50% } .htconfig.php config/local.config.php $db_host = 'localhost'; $db_user = 'mysqlusername'; $db_pass = 'mysqlpassword'; $db_data = 'mysqldatabasename'; $a->config[\"system\"][\"db_charset\"] = 'utf8mb4'; 'database' => [ 'hostname' => 'localhost', 'username' => 'mysqlusername', 'password' => 'mysqlpassword', 'database' => 'database', 'charset' => 'utf8mb4', ], $a->config[\"section\"][\"key\"] = \"value\"; 'section' => [ 'key' => 'value', ], $a->config[\"section\"][\"key\"] = array( \"value1\", \"value2\", \"value3\" ); 'section' => [ 'key' => ['value1', 'value2', 'value3'], ], $a->config[\"key\"] = \"value\"; 'config' => [ 'key' => 'value', ], $a->config['register_policy'] = REGISTER_CLOSED; 'config' => [ 'register_policy' => \\Friendica\\Module\\Register::CLOSED, ], $a->path = \"value\"; 'system' => [ 'urlpath' => 'value', ], $default_timezone = \"value\"; 'system' => [ 'default_timezone' => 'value', ], $pidfile = \"value\"; 'system' => [ 'pidfile' => 'value', ], $lang = \"value\"; 'system' => [ 'language' => 'value', ],","title":"Migrating from .htconfig.php to config/local.config.php"},{"location":"admin/config/#migrating-from-configlocaliniphp-to-configlocalconfigphp","text":"The legacy config/local.ini.php configuration file is still supported, but is deprecated and will be removed in a subsequent Friendica release. The migration is pretty straightforward: If you had any addon-specific configuration in your config/addon.ini.php , just copy config/addon-sample.config.php to config/addon.config.php and move your configuration values. Afterwards, copy config/local-sample.config.php to config/local.config.php , move the remaining configuration values to it according to the following conversion chart, then rename your config/local.ini.php file to check your node is working as expected before deleting it. config/local.ini.php config/local.config.php [database] hostname = localhost username = mysqlusername password = mysqlpassword database = mysqldatabasename charset = utf8mb4 'database' => [ 'hostname' => 'localhost', 'username' => 'mysqlusername', 'password' => 'mysqlpassword', 'database' => 'database', 'charset' => 'utf8mb4', ], [section] key = value 'section' => [ 'key' => 'value', ], [config] register_policty = REGISTER_CLOSED 'config' => [ 'register_policy' => \\Friendica\\Module\\Register::CLOSED, ], [section] key[] = value1 key[] = value2 key[] = value3 'section' => [ 'key' => ['value1', 'value2', 'value3'], ],","title":"Migrating from config/local.ini.php to config/local.config.php"},{"location":"admin/config/#database-settings","text":"The configuration variables database.hostname , database.username , database.password , database.database and database.charset are holding your credentials for the database connection. If you need to specify a port to access the database, you can do so by appending :portnumber to the database.hostname variable. 'database' => [ 'hostname' => 'your.mysqlhost.com:123456', ] If all the following environment variables are set, Friendica will use them instead of the previously configured variables for the db: MYSQL_HOST MYSQL_PORT MYSQL_USERNAME MYSQL_PASSWORD MYSQL_DATABASE","title":"Database Settings"},{"location":"admin/config/#config-values-that-can-only-be-set-in-configlocalconfigphp_1","text":"There are some config values that haven't found their way into the administration page. This has several reasons. Maybe they are part of a current development that isn't considered stable and will be added later in the administration page when it is considered safe. Or it triggers something that isn't expected to be of public interest. Or it is for testing purposes only. Attention: Please be warned that you shouldn't use one of these values without the knowledge what it could trigger. Especially don't do that with undocumented values. These configurations keys and their default value are listed in static/defaults.config.php and should be overwritten in config/local.config.php .","title":"Config values that can only be set in config/local.config.php"},{"location":"admin/config/#administrator-options","text":"Enabling the admin panel for an account, and thus making the account holder admin of the node, is done by setting the variable 'config' => [ 'admin_email' => 'someone@example.com', ] Where you have to match the email address used for the account with the one you enter to the config/local.config.php file. If more than one account should be able to access the admin panel, separate the email addresses with a comma. 'config' => [ 'admin_email' => 'someone@example.com,someoneelse@example.com', ] If you want to have a more personalized closing line for the notification emails you can set a variable for the admin_name . 'config' => [ 'admin_name' => 'Marvin', ]","title":"Administrator Options"},{"location":"admin/install-ejabberd/","tags":["admin","ejabberd"],"text":"Install an ejabberd with synchronized credentials # Ejabberd is a chat server that uses XMPP as messaging protocol that you can use with a large amount of clients. In conjunction with the \"xmpp\" addon it can be used for a web based chat solution for your users. Installation # Change its owner to whichever user is running the server, i.e. ejabberd $ chown ejabberd:ejabberd /path/to/friendica/bin/auth_ejabberd.php Change the access mode, so it is readable only to the user ejabberd and has exec $ chmod 700 /path/to/friendica/bin/auth_ejabberd.php Edit your ejabberd.cfg file, comment out your auth_method and add: {auth_method, external}. {extauth_program, \"/path/to/friendica/bin/auth_ejabberd.php\"}. Disable the module \"mod_register\" and disable the registration: {access, register, [{deny, all}]}. Enable BOSH: Enable the module \"mod_http_bind\" Edit this line: {5280, ejabberd_http, [captcha, http_poll, http_bind]} In your apache configuration for your site add this line: ProxyPass /http-bind http://127.0.0.1:5280/http-bind retry=0 Restart your ejabberd service, you should be able to log in with your friendica credentials Other hints # if a user has a space or a @ in the nickname, the user has to replace these characters: \" \" (space) is replaced with \"%20\" \"@\" is replaced with \"(a)\"","title":"Install ejabberd"},{"location":"admin/install-ejabberd/#install-an-ejabberd-with-synchronized-credentials","text":"Ejabberd is a chat server that uses XMPP as messaging protocol that you can use with a large amount of clients. In conjunction with the \"xmpp\" addon it can be used for a web based chat solution for your users.","title":"Install an ejabberd with synchronized credentials"},{"location":"admin/install-ejabberd/#installation","text":"Change its owner to whichever user is running the server, i.e. ejabberd $ chown ejabberd:ejabberd /path/to/friendica/bin/auth_ejabberd.php Change the access mode, so it is readable only to the user ejabberd and has exec $ chmod 700 /path/to/friendica/bin/auth_ejabberd.php Edit your ejabberd.cfg file, comment out your auth_method and add: {auth_method, external}. {extauth_program, \"/path/to/friendica/bin/auth_ejabberd.php\"}. Disable the module \"mod_register\" and disable the registration: {access, register, [{deny, all}]}. Enable BOSH: Enable the module \"mod_http_bind\" Edit this line: {5280, ejabberd_http, [captcha, http_poll, http_bind]} In your apache configuration for your site add this line: ProxyPass /http-bind http://127.0.0.1:5280/http-bind retry=0 Restart your ejabberd service, you should be able to log in with your friendica credentials","title":"Installation"},{"location":"admin/install-ejabberd/#other-hints","text":"if a user has a space or a @ in the nickname, the user has to replace these characters: \" \" (space) is replaced with \"%20\" \"@\" is replaced with \"(a)\"","title":"Other hints"},{"location":"admin/installing-connectors/","tags":["admin","connectors","twitter","gnu social"],"text":"Installing Connectors (Twitter/GNU Social) # Friendica uses addons to provide connectivity to some networks, such as Twitter. There is also an addon to post through to an existing account on a GNU Social service. You only need this to post to an already existing GNU Social account, but not to communicate with GNU Social members in general. All three addons require an account on the target network. In addition, you (or typically the server administrator) will need to obtain an API key to provide authenticated access to your Friendica server. Site Configuration # Addons must be installed by the site administrator before they can be used. This is accomplished through the site administration panel. Each of the connectors also requires an \"API key\" from the service you wish to connect with. Some addons allow you to enter this information in the site administration pages, while others may require you to edit your configuration file (config/local.config.php). The ways to obtain these keys vary between the services, but they all require an existing account on the target service. Once installed, these API keys can usually be shared by all site members. The details of configuring each service follow (much of this information comes directly from the addon source files): Twitter Addon for Friendica # Author: Tobias Diekershoff tobias.diekershoff@gmx.net License: 3-clause BSD license Configuration # To use this addon you need a OAuth Consumer key pair (key & secret). You can get it from Twitter . Register your Friendica site as \"Client\" application with \"Read & Write\" access. We do not need \"Twitter as login\". When you've registered the app you get a key pair with an OAuth Consumer key and a secret key for your application/site. Add this key pair to your config/local.config.php: [twitter] consumerkey = your consumer_key here consumersecret = your consumer_secret here After this, your users can configure their Twitter account settings from \"Settings -> Connector Settings\". More documentation # Find the author's documentation here: http://diekershoff.homeunix.net/redmine/wiki/friendikaplugin/Twitter_Plugin GNU Social Addon for Friendica # Author: Tobias Diekershoff tobias.diekershoff@gmx.net License: 3-clause BSD license Configuration # When the addon is activated the user has to acquire the following in order to connect to the GNU Social account of choice. The base URL for the GNU Social API, for quitter.se this is https://quitter.se/api/ OAuth Consumer key & secret To get the OAuth Consumer key pair the user has to 1 ask her Friendica admin if a pair already exists or 2 has to register the Friendica server as a client application on the GNU Social server. This can be done from the account settings under \"Settings -> Connections -> Register an OAuth client application -> Register a new application\" on the GNU Social server. During the registration of the OAuth client remember the following: Application names must be unique on the GNU Social site, so we recommend a Name of 'friendica-nnnn', replace 'nnnn' with a random number or your website name. there is no callback url register a desktop client with read & write access the Source URL should be the URL of your Friendica server After the required credentials for the application are stored in the configuration you have to actually connect your Friendica account with GNU Social. This is done from the Settings -> Connector Settings page. Follow the Sign in with GNU Social button, allow access and then copy the security code into the box provided. Friendica will then try to acquire the final OAuth credentials from the API. If successful, the addon settings will allow you to select to post your public messages to your GNU Social account (have a look behind the little lock symbol beneath the status \"editor\" on your Home or Network pages).","title":"Connectors"},{"location":"admin/installing-connectors/#installing-connectors-twittergnu-social","text":"Friendica uses addons to provide connectivity to some networks, such as Twitter. There is also an addon to post through to an existing account on a GNU Social service. You only need this to post to an already existing GNU Social account, but not to communicate with GNU Social members in general. All three addons require an account on the target network. In addition, you (or typically the server administrator) will need to obtain an API key to provide authenticated access to your Friendica server.","title":"Installing Connectors (Twitter/GNU Social)"},{"location":"admin/installing-connectors/#site-configuration","text":"Addons must be installed by the site administrator before they can be used. This is accomplished through the site administration panel. Each of the connectors also requires an \"API key\" from the service you wish to connect with. Some addons allow you to enter this information in the site administration pages, while others may require you to edit your configuration file (config/local.config.php). The ways to obtain these keys vary between the services, but they all require an existing account on the target service. Once installed, these API keys can usually be shared by all site members. The details of configuring each service follow (much of this information comes directly from the addon source files):","title":"Site Configuration"},{"location":"admin/installing-connectors/#twitter-addon-for-friendica","text":"Author: Tobias Diekershoff tobias.diekershoff@gmx.net License: 3-clause BSD license","title":"Twitter Addon for Friendica"},{"location":"admin/installing-connectors/#configuration","text":"To use this addon you need a OAuth Consumer key pair (key & secret). You can get it from Twitter . Register your Friendica site as \"Client\" application with \"Read & Write\" access. We do not need \"Twitter as login\". When you've registered the app you get a key pair with an OAuth Consumer key and a secret key for your application/site. Add this key pair to your config/local.config.php: [twitter] consumerkey = your consumer_key here consumersecret = your consumer_secret here After this, your users can configure their Twitter account settings from \"Settings -> Connector Settings\".","title":"Configuration"},{"location":"admin/installing-connectors/#more-documentation","text":"Find the author's documentation here: http://diekershoff.homeunix.net/redmine/wiki/friendikaplugin/Twitter_Plugin","title":"More documentation"},{"location":"admin/installing-connectors/#gnu-social-addon-for-friendica","text":"Author: Tobias Diekershoff tobias.diekershoff@gmx.net License: 3-clause BSD license","title":"GNU Social Addon for Friendica"},{"location":"admin/installing-connectors/#configuration_1","text":"When the addon is activated the user has to acquire the following in order to connect to the GNU Social account of choice. The base URL for the GNU Social API, for quitter.se this is https://quitter.se/api/ OAuth Consumer key & secret To get the OAuth Consumer key pair the user has to 1 ask her Friendica admin if a pair already exists or 2 has to register the Friendica server as a client application on the GNU Social server. This can be done from the account settings under \"Settings -> Connections -> Register an OAuth client application -> Register a new application\" on the GNU Social server. During the registration of the OAuth client remember the following: Application names must be unique on the GNU Social site, so we recommend a Name of 'friendica-nnnn', replace 'nnnn' with a random number or your website name. there is no callback url register a desktop client with read & write access the Source URL should be the URL of your Friendica server After the required credentials for the application are stored in the configuration you have to actually connect your Friendica account with GNU Social. This is done from the Settings -> Connector Settings page. Follow the Sign in with GNU Social button, allow access and then copy the security code into the box provided. Friendica will then try to acquire the final OAuth credentials from the API. If successful, the addon settings will allow you to select to post your public messages to your GNU Social account (have a look behind the little lock symbol beneath the status \"editor\" on your Home or Network pages).","title":"Configuration"},{"location":"admin/migrate/","tags":["admin"],"text":"Migrating to a new server installation # Preparation # New server # Set up your new server as described here ; follow the installation procedure until you have created a database. Heads up to users # Inform your users of an upcoming interruption to your service. To ensure data consistency, your server needs to be offline during some steps of the migration processes. You may also find these addons useful for communicating with your users prior to the migration process: * blackout * notifyall Storage # Check your storage backend with bin/console storage list in the root folder. The output should look like this: Sel | Name ----------------------- | Filesystem * | Database If you are not using Database run the following commands: 1. bin/console storage set Database to activate the database backend. 2. bin/console storage move to initiate moving the stored image files. This process may take a long time depending on the size of your storage and your server's capacity. Prior to initiating this process, you may want to check the number of files in the storage with the following command: tree -if -I index.html /path/to/storage/ . Cleaning up # Before transferring your database, you may want to clean it up; ensure the expiration of database items is set to a reasonable value and activated via the administrator panel. Admin > Site > Performance > Enable \"Clean up database\" After adjusting these settings, the database cleaning up processes will be initiated according to your configured daily cron job. To review the size of your database, log into MySQL with mysql -p run the following query: SELECT table_schema AS \"Database\" , SUM ( data_length + index_length ) / 1024 / 1024 / 1024 AS \"Size (GB)\" FROM information_schema . TABLES GROUP BY table_schema ; You should see an output like this: +--------------------+----------------+ | Database | Size (GB) | +--------------------+----------------+ | friendica_db | 8.054092407227 | | [..........] | [...........] | +--------------------+----------------+ Finally, you may also want to optimise your database with the following command: mysqloptimize -p friendica-db Going offline # Stop background tasks and put your server in maintenance mode. 1. If you had set up a worker cron job like this */10 * * * * cd /var/www/friendica; /usr/bin/php bin/worker.php run crontab -e and comment out this line. Alternatively if you deploy a worker daemon, disable this instead. 2. Put your server into maintenance mode: bin/console maintenance 1 \"We are currently upgrading our system and will be back soon.\" Dumping DB # Export your database: mysqldump -p friendica_db > friendica_db-$(date +%Y%m%d).sql and possibly compress it. Transferring to new server # Transfer your database and a copy of your configuration file config/local.config.php.copy to your new server installation. Restoring your DB # Import your database on your new server: mysql -p friendica_db < your-friendica_db-file.sql Completing migration # Configuration file # Copy your old server's configuration file to config/local.config.php . Ensure the newly created database credentials are identical to the setting in the configuration file; otherwise update them accordingly. Cron job for worker # Set up the required daily cron job. Run crontab -e and add the following line according to your system specification */10 * * * * cd /var/www/friendica; /usr/bin/php bin/worker.php DNS settings # Adjust your DNS records by pointing them to your new server. Troubleshooting # If you are unable to log in to your newly migrated Friendica installation, check your web server's error and access logs and mysql logs for obvious issues. If still unable to resolve the problem, it's likely an issue with your installation . In this case, you may try to an entirely new Friendica installation on your new server, but use a different FQDN and DNS name. Once you have this up and running, take it offline and purge the database and configuration file and try migrating to this installation.","title":"Migrate"},{"location":"admin/migrate/#migrating-to-a-new-server-installation","text":"","title":"Migrating to a new server installation"},{"location":"admin/migrate/#preparation","text":"","title":"Preparation"},{"location":"admin/migrate/#new-server","text":"Set up your new server as described here ; follow the installation procedure until you have created a database.","title":"New server"},{"location":"admin/migrate/#heads-up-to-users","text":"Inform your users of an upcoming interruption to your service. To ensure data consistency, your server needs to be offline during some steps of the migration processes. You may also find these addons useful for communicating with your users prior to the migration process: * blackout * notifyall","title":"Heads up to users"},{"location":"admin/migrate/#storage","text":"Check your storage backend with bin/console storage list in the root folder. The output should look like this: Sel | Name ----------------------- | Filesystem * | Database If you are not using Database run the following commands: 1. bin/console storage set Database to activate the database backend. 2. bin/console storage move to initiate moving the stored image files. This process may take a long time depending on the size of your storage and your server's capacity. Prior to initiating this process, you may want to check the number of files in the storage with the following command: tree -if -I index.html /path/to/storage/ .","title":"Storage"},{"location":"admin/migrate/#cleaning-up","text":"Before transferring your database, you may want to clean it up; ensure the expiration of database items is set to a reasonable value and activated via the administrator panel. Admin > Site > Performance > Enable \"Clean up database\" After adjusting these settings, the database cleaning up processes will be initiated according to your configured daily cron job. To review the size of your database, log into MySQL with mysql -p run the following query: SELECT table_schema AS \"Database\" , SUM ( data_length + index_length ) / 1024 / 1024 / 1024 AS \"Size (GB)\" FROM information_schema . TABLES GROUP BY table_schema ; You should see an output like this: +--------------------+----------------+ | Database | Size (GB) | +--------------------+----------------+ | friendica_db | 8.054092407227 | | [..........] | [...........] | +--------------------+----------------+ Finally, you may also want to optimise your database with the following command: mysqloptimize -p friendica-db","title":"Cleaning up"},{"location":"admin/migrate/#going-offline","text":"Stop background tasks and put your server in maintenance mode. 1. If you had set up a worker cron job like this */10 * * * * cd /var/www/friendica; /usr/bin/php bin/worker.php run crontab -e and comment out this line. Alternatively if you deploy a worker daemon, disable this instead. 2. Put your server into maintenance mode: bin/console maintenance 1 \"We are currently upgrading our system and will be back soon.\"","title":"Going offline"},{"location":"admin/migrate/#dumping-db","text":"Export your database: mysqldump -p friendica_db > friendica_db-$(date +%Y%m%d).sql and possibly compress it.","title":"Dumping DB"},{"location":"admin/migrate/#transferring-to-new-server","text":"Transfer your database and a copy of your configuration file config/local.config.php.copy to your new server installation.","title":"Transferring to new server"},{"location":"admin/migrate/#restoring-your-db","text":"Import your database on your new server: mysql -p friendica_db < your-friendica_db-file.sql","title":"Restoring your DB"},{"location":"admin/migrate/#completing-migration","text":"","title":"Completing migration"},{"location":"admin/migrate/#configuration-file","text":"Copy your old server's configuration file to config/local.config.php . Ensure the newly created database credentials are identical to the setting in the configuration file; otherwise update them accordingly.","title":"Configuration file"},{"location":"admin/migrate/#cron-job-for-worker","text":"Set up the required daily cron job. Run crontab -e and add the following line according to your system specification */10 * * * * cd /var/www/friendica; /usr/bin/php bin/worker.php","title":"Cron job for worker"},{"location":"admin/migrate/#dns-settings","text":"Adjust your DNS records by pointing them to your new server.","title":"DNS settings"},{"location":"admin/migrate/#troubleshooting","text":"If you are unable to log in to your newly migrated Friendica installation, check your web server's error and access logs and mysql logs for obvious issues. If still unable to resolve the problem, it's likely an issue with your installation . In this case, you may try to an entirely new Friendica installation on your new server, but use a different FQDN and DNS name. Once you have this up and running, take it offline and purge the database and configuration file and try migrating to this installation.","title":"Troubleshooting"},{"location":"admin/tools/","tags":["admin","tools"],"text":"Admin Tools # Friendica Tools # Friendica has a build in command console you can find in the bin directory. The console provides the following commands: cache: Manage node cache config: Edit site config createdoxygen: Generate Doxygen headers dbstructure: Do database updates docbloxerrorchecker: Check the file tree for DocBlox errors extract: Generate translation string file for the Friendica project (deprecated) globalcommunityblock: Block remote profile from interacting with this node globalcommunitysilence: Silence remote profile from global community page archivecontact: Archive a contact when you know that it isn't existing anymore help: Show help about a command, e.g (bin/console help config) autoinstall: Starts automatic installation of friendica based on values from htconfig.php maintenance: Set maintenance mode for this node newpassword: Set a new password for a given user php2po: Generate a messages.po file from a strings.php file po2php: Generate a strings.php file from a messages.po file typo: Checks for parse errors in Friendica files postupdate: Execute pending post update scripts (can last days) storage: Manage storage backend relay: Manage ActivityPub relay servers Please consult bin/console help on the command line interface of your server for details about the commands. 3rd Party Tools # In addition to the tools Friendica includes, some 3rd party tools can make your admin days easier. Fail2ban # Fail2ban is an intrusion prevention framework ( see Wikipedia ) that you can use to forbid access to a server under certain conditions, e.g. 3 failed attempts to log in, for a certain amount of time. The following configuration was provided by Steffen K9 using Debian. You need to adjust the logpath in the jail.local file and the bantime (value is in seconds). In /etc/fail2ban/jail.local create a section for Friendica: [friendica] enabled = true findtime = 300 bantime = 900 filter = friendica port = http,https logpath = /var/log/friend.log logencoding = utf-8 And create a filter definition in /etc/fail2ban/filter.d/friendica.conf : [Definition] failregex = ^.*authenticate\\: failed login attempt.*\\\"ip\\\"\\:\\\"\\\".*$ ignoreregex = Additionally, you have to define the number of failed logins before the ban should be activated. This is done either in the global configuration or for each jail separately. You should inform your users about the number of failed login attempts you grant them. Otherwise, you'll get many reports about the server not functioning if the number is too low. Log rotation # If you have activated the logs in Friendica, be aware that they can grow to a significant size. To keep them in control you should add them to the automatic log rotation , e.g. using the logrotate command. In /etc/logrotate.d/ add a file called friendica that contains the configuration. The following will compress /var/log/friendica (assuming this is the location of the log file) on a daily basis and keep 2 days of back-log. /var/log/friendica.log { compress daily rotate 2 }","title":"Tools"},{"location":"admin/tools/#admin-tools","text":"","title":"Admin Tools"},{"location":"admin/tools/#friendica-tools","text":"Friendica has a build in command console you can find in the bin directory. The console provides the following commands: cache: Manage node cache config: Edit site config createdoxygen: Generate Doxygen headers dbstructure: Do database updates docbloxerrorchecker: Check the file tree for DocBlox errors extract: Generate translation string file for the Friendica project (deprecated) globalcommunityblock: Block remote profile from interacting with this node globalcommunitysilence: Silence remote profile from global community page archivecontact: Archive a contact when you know that it isn't existing anymore help: Show help about a command, e.g (bin/console help config) autoinstall: Starts automatic installation of friendica based on values from htconfig.php maintenance: Set maintenance mode for this node newpassword: Set a new password for a given user php2po: Generate a messages.po file from a strings.php file po2php: Generate a strings.php file from a messages.po file typo: Checks for parse errors in Friendica files postupdate: Execute pending post update scripts (can last days) storage: Manage storage backend relay: Manage ActivityPub relay servers Please consult bin/console help on the command line interface of your server for details about the commands.","title":"Friendica Tools"},{"location":"admin/tools/#3rd-party-tools","text":"In addition to the tools Friendica includes, some 3rd party tools can make your admin days easier.","title":"3rd Party Tools"},{"location":"admin/tools/#fail2ban","text":"Fail2ban is an intrusion prevention framework ( see Wikipedia ) that you can use to forbid access to a server under certain conditions, e.g. 3 failed attempts to log in, for a certain amount of time. The following configuration was provided by Steffen K9 using Debian. You need to adjust the logpath in the jail.local file and the bantime (value is in seconds). In /etc/fail2ban/jail.local create a section for Friendica: [friendica] enabled = true findtime = 300 bantime = 900 filter = friendica port = http,https logpath = /var/log/friend.log logencoding = utf-8 And create a filter definition in /etc/fail2ban/filter.d/friendica.conf : [Definition] failregex = ^.*authenticate\\: failed login attempt.*\\\"ip\\\"\\:\\\"\\\".*$ ignoreregex = Additionally, you have to define the number of failed logins before the ban should be activated. This is done either in the global configuration or for each jail separately. You should inform your users about the number of failed login attempts you grant them. Otherwise, you'll get many reports about the server not functioning if the number is too low.","title":"Fail2ban"},{"location":"admin/tools/#log-rotation","text":"If you have activated the logs in Friendica, be aware that they can grow to a significant size. To keep them in control you should add them to the automatic log rotation , e.g. using the logrotate command. In /etc/logrotate.d/ add a file called friendica that contains the configuration. The following will compress /var/log/friendica (assuming this is the location of the log file) on a daily basis and keep 2 days of back-log. /var/log/friendica.log { compress daily rotate 2 }","title":"Log rotation"},{"location":"admin/update/","tags":["admin"],"text":"Updating Friendica # Using a Friendica archive # If you installed Friendica in the path/to/friendica folder: Unpack the new Friendica archive in path/to/friendica_new . Copy the following items from path/to/friendica to path/to/friendica_new : config/local.config.php proxy/ The following items only need to be copied if they are located inside your friendica path: your storage folder as set in Admin -> Site -> File Upload -> Storage base path your item cache as set in Admin -> Site -> Performance -> Path to item cache your temp folder as set in Admin -> Site -> Advanced -> Temp path Rename the path/to/friendica folder to path/to/friendica_old . Rename the path/to/friendica_new folder to path/to/friendica . Check your site. Note: it may go into maintenance mode to update the database schema. If everything works, just delete the path/to/friendica_old folder. To update Addons from an archive, simply delete the path/to/friendica/addon and replace it with the provided archive. Using Git # You can get the latest changes at any time with cd path/to/friendica git pull bin/composer.phar install --no-dev The addon tree has to be updated separately like so: cd path/to/friendica/addon git pull For both repositories: The default branch to use is the stable branch, which is the stable version of Friendica. It is updated about four times a year on a fixed schedule. If you want to use and test bleeding edge code please check out the develop branch. The new features and fixes will be merged from develop into stable after a release candidate period before each release. Warning: The develop branch is unstable, and breaks on average once a month for at most 24 hours until a patch is submitted and merged. Be sure to pull frequently if you choose the develop branch. Considerations before upgrading Friendica # MySQL >= 5.7.4 # Starting from MySQL version 5.7.4, the IGNORE keyword in ALTER TABLE statements is ignored. This prevents automatic table deduplication if a UNIQUE index is added to a Friendica table's structure. If a DB update fails for you while creating a UNIQUE index, make sure to manually deduplicate the table before trying the update again. Manual deduplication # There are two main ways of doing it, either by manually removing the duplicates or by recreating the table. Manually removing the duplicates is usually faster if they're not too numerous. To manually remove the duplicates, you need to know the UNIQUE index columns available in database.sql . SELECT GROUP_CONCAT ( id ), < index columns > , count ( * ) as count FROM users GROUP BY < index columns > HAVING count >= 2 ; /* delete or merge duplicate from above query */ ; If there are too many rows to handle manually, you can create a new table with the same structure as the table with duplicates and insert the existing content with INSERT IGNORE. To recreate the table you need to know the table structure available in database.sql . CREATE TABLE < table_name > _new < rest of the CREATE TABLE > ; INSERT IGNORE INTO < table_name > _new SELECT * FROM < table_name > ; DROP TABLE < table_name > ; RENAME TABLE < table_name > _new TO < table_name > ; This method is slower overall, but it is better suited for large numbers of duplicates. Resolving Possible Database Issues Post Upgrading # Foreign Keys # Some updates include the use of foreign keys now that will bump into issues with previous versions, which would sometimes shove bad data into tables, preventing, causing errors such as below. Error 1452 occurred during database update: Cannot add or update a child row: a foreign key constraint fails (`friendica`.`#sql-10ea6_5a6d`, CONSTRAINT `#sql-10ea6_5a6d_ibfk_1` FOREIGN KEY (`contact-id`) REFERENCES `contact` (`id`)) ALTER TABLE `thread` ADD FOREIGN KEY (`iid`) REFERENCES `item` (`id`) ON UPDATE RESTRICT ON DELETE CASCADE; All current known fixes for possible items that can go wrong are as below. DELETE FROM ` item ` WHERE ` owner - id ` NOT IN ( SELECT ` id ` FROM ` contact ` ); DELETE FROM ` item ` WHERE ` contact - id ` NOT IN ( SELECT ` id ` FROM ` contact ` ); DELETE FROM ` notify ` WHERE ` uri - id ` NOT IN ( SELECT ` id ` FROM ` item - uri ` ); DELETE FROM ` photo ` WHERE ` contact - id ` NOT IN ( SELECT ` id ` FROM ` contact ` ); DELETE FROM ` thread ` WHERE ` iid ` NOT IN ( SELECT ` id ` FROM ` item ` ); DELETE FROM ` item ` WHERE ` author - id ` NOT IN ( SELECT ` id ` FROM ` contact ` ); DELETE FROM ` diaspora - interaction ` WHERE ` uri - id ` NOT IN ( SELECT ` id ` FROM ` item - uri ` ); This all has been compiled as of currently from issue #9746, #9753, and #9878.","title":"Update"},{"location":"admin/update/#updating-friendica","text":"","title":"Updating Friendica"},{"location":"admin/update/#using-a-friendica-archive","text":"If you installed Friendica in the path/to/friendica folder: Unpack the new Friendica archive in path/to/friendica_new . Copy the following items from path/to/friendica to path/to/friendica_new : config/local.config.php proxy/ The following items only need to be copied if they are located inside your friendica path: your storage folder as set in Admin -> Site -> File Upload -> Storage base path your item cache as set in Admin -> Site -> Performance -> Path to item cache your temp folder as set in Admin -> Site -> Advanced -> Temp path Rename the path/to/friendica folder to path/to/friendica_old . Rename the path/to/friendica_new folder to path/to/friendica . Check your site. Note: it may go into maintenance mode to update the database schema. If everything works, just delete the path/to/friendica_old folder. To update Addons from an archive, simply delete the path/to/friendica/addon and replace it with the provided archive.","title":"Using a Friendica archive"},{"location":"admin/update/#using-git","text":"You can get the latest changes at any time with cd path/to/friendica git pull bin/composer.phar install --no-dev The addon tree has to be updated separately like so: cd path/to/friendica/addon git pull For both repositories: The default branch to use is the stable branch, which is the stable version of Friendica. It is updated about four times a year on a fixed schedule. If you want to use and test bleeding edge code please check out the develop branch. The new features and fixes will be merged from develop into stable after a release candidate period before each release. Warning: The develop branch is unstable, and breaks on average once a month for at most 24 hours until a patch is submitted and merged. Be sure to pull frequently if you choose the develop branch.","title":"Using Git"},{"location":"admin/update/#considerations-before-upgrading-friendica","text":"","title":"Considerations before upgrading Friendica"},{"location":"admin/update/#mysql-574","text":"Starting from MySQL version 5.7.4, the IGNORE keyword in ALTER TABLE statements is ignored. This prevents automatic table deduplication if a UNIQUE index is added to a Friendica table's structure. If a DB update fails for you while creating a UNIQUE index, make sure to manually deduplicate the table before trying the update again.","title":"MySQL >= 5.7.4"},{"location":"admin/update/#manual-deduplication","text":"There are two main ways of doing it, either by manually removing the duplicates or by recreating the table. Manually removing the duplicates is usually faster if they're not too numerous. To manually remove the duplicates, you need to know the UNIQUE index columns available in database.sql . SELECT GROUP_CONCAT ( id ), < index columns > , count ( * ) as count FROM users GROUP BY < index columns > HAVING count >= 2 ; /* delete or merge duplicate from above query */ ; If there are too many rows to handle manually, you can create a new table with the same structure as the table with duplicates and insert the existing content with INSERT IGNORE. To recreate the table you need to know the table structure available in database.sql . CREATE TABLE < table_name > _new < rest of the CREATE TABLE > ; INSERT IGNORE INTO < table_name > _new SELECT * FROM < table_name > ; DROP TABLE < table_name > ; RENAME TABLE < table_name > _new TO < table_name > ; This method is slower overall, but it is better suited for large numbers of duplicates.","title":"Manual deduplication"},{"location":"admin/update/#resolving-possible-database-issues-post-upgrading","text":"","title":"Resolving Possible Database Issues Post Upgrading"},{"location":"admin/update/#foreign-keys","text":"Some updates include the use of foreign keys now that will bump into issues with previous versions, which would sometimes shove bad data into tables, preventing, causing errors such as below. Error 1452 occurred during database update: Cannot add or update a child row: a foreign key constraint fails (`friendica`.`#sql-10ea6_5a6d`, CONSTRAINT `#sql-10ea6_5a6d_ibfk_1` FOREIGN KEY (`contact-id`) REFERENCES `contact` (`id`)) ALTER TABLE `thread` ADD FOREIGN KEY (`iid`) REFERENCES `item` (`id`) ON UPDATE RESTRICT ON DELETE CASCADE; All current known fixes for possible items that can go wrong are as below. DELETE FROM ` item ` WHERE ` owner - id ` NOT IN ( SELECT ` id ` FROM ` contact ` ); DELETE FROM ` item ` WHERE ` contact - id ` NOT IN ( SELECT ` id ` FROM ` contact ` ); DELETE FROM ` notify ` WHERE ` uri - id ` NOT IN ( SELECT ` id ` FROM ` item - uri ` ); DELETE FROM ` photo ` WHERE ` contact - id ` NOT IN ( SELECT ` id ` FROM ` contact ` ); DELETE FROM ` thread ` WHERE ` iid ` NOT IN ( SELECT ` id ` FROM ` item ` ); DELETE FROM ` item ` WHERE ` author - id ` NOT IN ( SELECT ` id ` FROM ` contact ` ); DELETE FROM ` diaspora - interaction ` WHERE ` uri - id ` NOT IN ( SELECT ` id ` FROM ` item - uri ` ); This all has been compiled as of currently from issue #9746, #9753, and #9878.","title":"Foreign Keys"},{"location":"developer/addon-storage-backend/","tags":["addon","developer"],"text":"Friendica Storage Backend Addon development # Storage backends can be added via addons. A storage backend is implemented as a class, and the plugin register the class to make it available to the system. The Storage Backend Class # The class must live in Friendica\\Addon\\youraddonname namespace, where youraddonname the folder name of your addon. There are two different interfaces you need to implement. ICanWriteToStorage # The class must implement Friendica\\Core\\Storage\\Capability\\ICanWriteToStorage interface. All method in the interface must be implemented: [ ..info.. ], 'option2name' => [ ..info.. ], ... ] An empty array can be returned if backend doesn't have any options. The info array for each option is defined as: [ 'type', define the field used in form, and the type of data. one of 'checkbox', 'combobox', 'custom', 'datetime', 'input', 'intcheckbox', 'password', 'radio', 'richtext', 'select', 'select_raw', 'textarea' 'label', Translatable label of the field. This label will be shown in admin page value, Current value of the option 'help text', Translatable description for the field. Will be shown in admin page extra data Optional. Depends on which 'type' this option is: 'select': array [ value => label ] of choices 'intcheckbox': value of input element 'select_raw': prebuild html string of