Merge branch 'develop' of https://git.pleroma.social/pleroma/pleroma into develop

This commit is contained in:
sadposter 2020-01-29 11:05:19 +00:00
commit 646016c403
93 changed files with 5128 additions and 537 deletions

View file

@ -8,6 +8,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
- **Breaking**: Removed 1.0+ deprecated configurations `Pleroma.Upload, :strip_exif` and `:instance, :dedupe_media` - **Breaking**: Removed 1.0+ deprecated configurations `Pleroma.Upload, :strip_exif` and `:instance, :dedupe_media`
- **Breaking**: OStatus protocol support - **Breaking**: OStatus protocol support
- **Breaking**: MDII uploader - **Breaking**: MDII uploader
- **Breaking**: Using third party engines for user recommendation
### Changed ### Changed
- **Breaking:** Pleroma won't start if it detects unapplied migrations - **Breaking:** Pleroma won't start if it detects unapplied migrations
@ -15,6 +16,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
- **Breaking:** Elixir >=1.8 is now required (was >= 1.7) - **Breaking:** Elixir >=1.8 is now required (was >= 1.7)
- **Breaking:** attachment links (`config :pleroma, :instance, no_attachment_links` and `config :pleroma, Pleroma.Upload, link_name`) disabled by default - **Breaking:** attachment links (`config :pleroma, :instance, no_attachment_links` and `config :pleroma, Pleroma.Upload, link_name`) disabled by default
- **Breaking:** OAuth: defaulted `[:auth, :enforce_oauth_admin_scope_usage]` setting to `true` which demands `admin` OAuth scope to perform admin actions (in addition to `is_admin` flag on User); make sure to use bundled or newer versions of AdminFE & PleromaFE to access admin / moderator features. - **Breaking:** OAuth: defaulted `[:auth, :enforce_oauth_admin_scope_usage]` setting to `true` which demands `admin` OAuth scope to perform admin actions (in addition to `is_admin` flag on User); make sure to use bundled or newer versions of AdminFE & PleromaFE to access admin / moderator features.
- **Breaking:** Dynamic configuration has been rearchitected. The `:pleroma, :instance, dynamic_configuration` setting has been replaced with `config :pleroma, configurable_from_database`. Please backup your configuration to a file and run the migration task to ensure consistency with the new schema.
- Replaced [pleroma_job_queue](https://git.pleroma.social/pleroma/pleroma_job_queue) and `Pleroma.Web.Federator.RetryQueue` with [Oban](https://github.com/sorentwo/oban) (see [`docs/config.md`](docs/config.md) on migrating customized worker / retry settings) - Replaced [pleroma_job_queue](https://git.pleroma.social/pleroma/pleroma_job_queue) and `Pleroma.Web.Federator.RetryQueue` with [Oban](https://github.com/sorentwo/oban) (see [`docs/config.md`](docs/config.md) on migrating customized worker / retry settings)
- Introduced [quantum](https://github.com/quantum-elixir/quantum-core) job scheduler - Introduced [quantum](https://github.com/quantum-elixir/quantum-core) job scheduler
- Enabled `:instance, extended_nickname_format` in the default config - Enabled `:instance, extended_nickname_format` in the default config
@ -26,6 +28,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
- Store status data inside Flag activity - Store status data inside Flag activity
- Deprecated (reorganized as `UserRelationship` entity) User fields with user AP IDs (`blocks`, `mutes`, `muted_reblogs`, `muted_notifications`, `subscribers`). - Deprecated (reorganized as `UserRelationship` entity) User fields with user AP IDs (`blocks`, `mutes`, `muted_reblogs`, `muted_notifications`, `subscribers`).
- Logger: default log level changed from `warn` to `info`. - Logger: default log level changed from `warn` to `info`.
- Config mix task `migrate_to_db` truncates `config` table before migrating the config file.
<details> <details>
<summary>API Changes</summary> <summary>API Changes</summary>
@ -54,6 +57,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
- Static Frontend: Add the ability to render user profiles and notices server-side without requiring JS app. - Static Frontend: Add the ability to render user profiles and notices server-side without requiring JS app.
- Mix task to re-count statuses for all users (`mix pleroma.count_statuses`) - Mix task to re-count statuses for all users (`mix pleroma.count_statuses`)
- Mix task to list all users (`mix pleroma.user list`) - Mix task to list all users (`mix pleroma.user list`)
- Mix task to send a test email (`mix pleroma.email test`)
- Support for `X-Forwarded-For` and similar HTTP headers which used by reverse proxies to pass a real user IP address to the backend. Must not be enabled unless your instance is behind at least one reverse proxy (such as Nginx, Apache HTTPD or Varnish Cache). - Support for `X-Forwarded-For` and similar HTTP headers which used by reverse proxies to pass a real user IP address to the backend. Must not be enabled unless your instance is behind at least one reverse proxy (such as Nginx, Apache HTTPD or Varnish Cache).
- MRF: New module which handles incoming posts based on their age. By default, all incoming posts that are older than 2 days will be unlisted and not shown to their followers. - MRF: New module which handles incoming posts based on their age. By default, all incoming posts that are older than 2 days will be unlisted and not shown to their followers.
- User notification settings: Add `privacy_option` option. - User notification settings: Add `privacy_option` option.
@ -97,6 +101,9 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
- Mastodon API: Add `emoji_reactions` property to Statuses - Mastodon API: Add `emoji_reactions` property to Statuses
- Mastodon API: Change emoji reaction reply format - Mastodon API: Change emoji reaction reply format
- Notifications: Added `pleroma:emoji_reaction` notification type - Notifications: Added `pleroma:emoji_reaction` notification type
- Mastodon API: Change emoji reaction reply format once more
- Configuration: `feed.logo` option for tag feed.
- Tag feed: `/tags/:tag.rss` - list public statuses by hashtag.
</details> </details>
### Fixed ### Fixed

View file

@ -426,14 +426,6 @@
], ],
unfurl_nsfw: false unfurl_nsfw: false
config :pleroma, :suggestions,
enabled: false,
third_party_engine:
"http://vinayaka.distsn.org/cgi-bin/vinayaka-user-match-suggestions-api.cgi?{{host}}+{{user}}",
timeout: 300_000,
limit: 40,
web: "https://vinayaka.distsn.org"
config :pleroma, :http_security, config :pleroma, :http_security,
enabled: true, enabled: true,
sts: false, sts: false,

View file

@ -23,7 +23,7 @@
key: :uploader, key: :uploader,
type: :module, type: :module,
description: "Module which will be used for uploads", description: "Module which will be used for uploads",
suggestions: [Pleroma.Uploaders.Local, Pleroma.Uploaders.MDII, Pleroma.Uploaders.S3] suggestions: [Pleroma.Uploaders.Local, Pleroma.Uploaders.S3]
}, },
%{ %{
key: :filters, key: :filters,
@ -39,7 +39,7 @@
key: :link_name, key: :link_name,
type: :boolean, type: :boolean,
description: description:
"If enabled, a name parameter will be added to the url of the upload. For example `https://instance.tld/media/imagehash.png?name=realname.png`" "If enabled, a name parameter will be added to the url of the upload. For example `https://instance.tld/media/imagehash.png?name=realname.png`."
}, },
%{ %{
key: :base_url, key: :base_url,
@ -53,7 +53,7 @@
key: :proxy_remote, key: :proxy_remote,
type: :boolean, type: :boolean,
description: description:
"If enabled, requests to media stored using a remote uploader will be proxied instead of being redirected." "If enabled, requests to media stored using a remote uploader will be proxied instead of being redirected"
}, },
%{ %{
key: :proxy_opts, key: :proxy_opts,
@ -73,14 +73,14 @@
type: :boolean, type: :boolean,
description: description:
"Redirects the client to the real remote URL if there's any HTTP errors. " <> "Redirects the client to the real remote URL if there's any HTTP errors. " <>
"Any error during body processing will not be redirected as the response is chunked" "Any error during body processing will not be redirected as the response is chunked."
}, },
%{ %{
key: :max_body_length, key: :max_body_length,
type: :integer, type: :integer,
description: description:
"limits the content length to be approximately the " <> "Limits the content length to be approximately the " <>
"specified length. It is validated with the `content-length` header and also verified when proxying" "specified length. It is validated with the `content-length` header and also verified when proxying."
}, },
%{ %{
key: :http, key: :http,
@ -130,7 +130,7 @@
%{ %{
key: :uploads, key: :uploads,
type: :string, type: :string,
description: "Path where user uploads will be saved", description: "Path where user's uploads will be saved",
suggestions: [ suggestions: [
"uploads" "uploads"
] ]
@ -207,7 +207,7 @@
type: :string, type: :string,
description: description:
"Text to replace filenames in links. If no setting, {random}.extension will be used. You can get the original" <> "Text to replace filenames in links. If no setting, {random}.extension will be used. You can get the original" <>
" filename extension by using {extension}, for example custom-file-name.{extension}", " filename extension by using {extension}, for example custom-file-name.{extension}.",
suggestions: [ suggestions: [
"custom-file-name.{extension}" "custom-file-name.{extension}"
] ]
@ -515,6 +515,7 @@
}, },
%{ %{
key: :email, key: :email,
label: "Admin Email Address",
type: :string, type: :string,
description: "Email used to reach an Administrator/Moderator of the instance", description: "Email used to reach an Administrator/Moderator of the instance",
suggestions: [ suggestions: [
@ -523,8 +524,9 @@
}, },
%{ %{
key: :notify_email, key: :notify_email,
label: "Sender Email Address",
type: :string, type: :string,
description: "Email used for notifications", description: "Envelope FROM address for mail sent via Pleroma",
suggestions: [ suggestions: [
"notify@example.com" "notify@example.com"
] ]
@ -635,12 +637,12 @@
%{ %{
key: :registrations_open, key: :registrations_open,
type: :boolean, type: :boolean,
description: "Enable registrations for anyone, invitations can be enabled when false" description: "Enable registrations for anyone, invitations can be enabled when `false`"
}, },
%{ %{
key: :invites_enabled, key: :invites_enabled,
type: :boolean, type: :boolean,
description: "Enable user invitations for admins (depends on registrations_open: false)" description: "Enable user invitations for admins (depends on `registrations_open: false`)"
}, },
%{ %{
key: :account_activation_required, key: :account_activation_required,
@ -658,7 +660,7 @@
type: :integer, type: :integer,
description: description:
"Max. depth of reply-to activities fetching on incoming federation, to prevent out-of-memory situations while" <> "Max. depth of reply-to activities fetching on incoming federation, to prevent out-of-memory situations while" <>
" fetching very long threads. If set to nil, threads of any depth will be fetched. Lower this value if you experience out-of-memory crashes", " fetching very long threads. If set to `nil`, threads of any depth will be fetched. Lower this value if you experience out-of-memory crashes.",
suggestions: [ suggestions: [
100 100
] ]
@ -668,7 +670,7 @@
label: "Fed. reachability timeout days", label: "Fed. reachability timeout days",
type: :integer, type: :integer,
description: description:
"Timeout (in days) of each external federation target being unreachable prior to pausing federating to it", "Timeout (in days) of each external federation target being unreachable prior to pausing federating to it.",
suggestions: [ suggestions: [
7 7
] ]
@ -701,13 +703,13 @@
type: :boolean, type: :boolean,
description: description:
"Makes the client API in authentificated mode-only except for user-profiles." <> "Makes the client API in authentificated mode-only except for user-profiles." <>
" Useful for disabling the Local Timeline and The Whole Known Network" " Useful for disabling the Local Timeline and The Whole Known Network."
}, },
%{ %{
key: :quarantined_instances, key: :quarantined_instances,
type: {:list, :string}, type: {:list, :string},
description: description:
"List of ActivityPub instances where private(DMs, followers-only) activities will not be send", "List of ActivityPub instances where private (DMs, followers-only) activities will not be send",
suggestions: [ suggestions: [
"quarantined.com", "quarantined.com",
"*.quarantined.com" "*.quarantined.com"
@ -750,7 +752,7 @@
label: "MRF transparency exclusions", label: "MRF transparency exclusions",
type: {:list, :string}, type: {:list, :string},
description: description:
"Exclude specific instance names from MRF transparency. The use of the exclusions feature will be disclosed in nodeinfo as a boolean value", "Exclude specific instance names from MRF transparency. The use of the exclusions feature will be disclosed in nodeinfo as a boolean value.",
suggestions: [ suggestions: [
"exclusion.com" "exclusion.com"
] ]
@ -759,13 +761,13 @@
key: :extended_nickname_format, key: :extended_nickname_format,
type: :boolean, type: :boolean,
description: description:
"Set to true to use extended local nicknames format (allows underscores/dashes)." <> "Set to `true` to use extended local nicknames format (allows underscores/dashes)." <>
" This will break federation with older software for theses nicknames" " This will break federation with older software for theses nicknames."
}, },
%{ %{
key: :max_pinned_statuses, key: :max_pinned_statuses,
type: :integer, type: :integer,
description: "The maximum number of pinned statuses. 0 will disable the feature", description: "The maximum number of pinned statuses. 0 will disable the feature.",
suggestions: [ suggestions: [
0, 0,
1, 1,
@ -788,13 +790,13 @@
key: :no_attachment_links, key: :no_attachment_links,
type: :boolean, type: :boolean,
description: description:
"Set to true to disable automatically adding attachment link text to statuses" "Set to `true` to disable automatically adding attachment link text to statuses"
}, },
%{ %{
key: :welcome_message, key: :welcome_message,
type: :string, type: :string,
description: description:
"A message that will be send to a newly registered users as a direct message", "A message that will be sent to a newly registered users as a direct message",
suggestions: [ suggestions: [
"Hi, @username! Welcome on board!" "Hi, @username! Welcome on board!"
] ]
@ -810,7 +812,7 @@
%{ %{
key: :max_report_comment_size, key: :max_report_comment_size,
type: :integer, type: :integer,
description: "The maximum size of the report comment (Default: 1000)", description: "The maximum size of the report comment. Default: 1000.",
suggestions: [ suggestions: [
1_000 1_000
] ]
@ -819,14 +821,14 @@
key: :safe_dm_mentions, key: :safe_dm_mentions,
type: :boolean, type: :boolean,
description: description:
"If set to true, only mentions at the beginning of a post will be used to address people in direct messages." <> "If set to `true`, only mentions at the beginning of a post will be used to address people in direct messages." <>
" This is to prevent accidental mentioning of people when talking about them (e.g. \"@friend hey i really don't like @enemy\")." <> " This is to prevent accidental mentioning of people when talking about them (e.g. \"@admin please keep an eye on @bad_actor\")." <>
" Default: false" " Default: `false`"
}, },
%{ %{
key: :healthcheck, key: :healthcheck,
type: :boolean, type: :boolean,
description: "If set to true, system data will be shown on /api/pleroma/healthcheck" description: "If set to `true`, system data will be shown on /api/pleroma/healthcheck"
}, },
%{ %{
key: :remote_post_retention_days, key: :remote_post_retention_days,
@ -840,7 +842,7 @@
%{ %{
key: :user_bio_length, key: :user_bio_length,
type: :integer, type: :integer,
description: "A user bio maximum length (default: 5000)", description: "A user bio maximum length. Default: 5000.",
suggestions: [ suggestions: [
5_000 5_000
] ]
@ -848,7 +850,7 @@
%{ %{
key: :user_name_length, key: :user_name_length,
type: :integer, type: :integer,
description: "A user name maximum length (default: 100)", description: "A user name maximum length. Default: 100.",
suggestions: [ suggestions: [
100 100
] ]
@ -856,13 +858,13 @@
%{ %{
key: :skip_thread_containment, key: :skip_thread_containment,
type: :boolean, type: :boolean,
description: "Skip filter out broken threads. The default is true" description: "Skip filter out broken threads. Default: `true`"
}, },
%{ %{
key: :limit_to_local_content, key: :limit_to_local_content,
type: [:atom, false], type: [:atom, false],
description: description:
"Limit unauthenticated users to search for local statutes and users only. The default is :unauthenticated ", "Limit unauthenticated users to search for local statutes and users only. Default: `:unauthenticated`.",
suggestions: [ suggestions: [
:unauthenticated, :unauthenticated,
:all, :all,
@ -872,7 +874,7 @@
%{ %{
key: :max_account_fields, key: :max_account_fields,
type: :integer, type: :integer,
description: "The maximum number of custom fields in the user profile (default: 10)", description: "The maximum number of custom fields in the user profile. Default: 10.",
suggestions: [ suggestions: [
10 10
] ]
@ -881,7 +883,7 @@
key: :max_remote_account_fields, key: :max_remote_account_fields,
type: :integer, type: :integer,
description: description:
"The maximum number of custom fields in the remote user profile (default: 20)", "The maximum number of custom fields in the remote user profile. Default: 20.",
suggestions: [ suggestions: [
20 20
] ]
@ -889,7 +891,7 @@
%{ %{
key: :account_field_name_length, key: :account_field_name_length,
type: :integer, type: :integer,
description: "An account field name maximum length (default: 512)", description: "An account field name maximum length. Default: 512.",
suggestions: [ suggestions: [
512 512
] ]
@ -897,7 +899,7 @@
%{ %{
key: :account_field_value_length, key: :account_field_value_length,
type: :integer, type: :integer,
description: "An account field value maximum length (default: 2048)", description: "An account field value maximum length. Default: 2048.",
suggestions: [ suggestions: [
2048 2048
] ]
@ -918,7 +920,7 @@
key: :backends, key: :backends,
type: [:atom, :tuple, :module], type: [:atom, :tuple, :module],
description: description:
"Where logs will be send, :console - send logs to stdout, {ExSyslogger, :ex_syslogger} - to syslog, Quack.Logger - to Slack.", "Where logs will be sent, :console - send logs to stdout, { ExSyslogger, :ex_syslogger } - to syslog, Quack.Logger - to Slack.",
suggestions: [:console, {ExSyslogger, :ex_syslogger}, Quack.Logger] suggestions: [:console, {ExSyslogger, :ex_syslogger}, Quack.Logger]
} }
] ]
@ -945,7 +947,7 @@
%{ %{
key: :format, key: :format,
type: :string, type: :string,
description: "It defaults to \"$date $time [$level] $levelpad$node $metadata $message\"", description: "Default: \"$date $time [$level] $levelpad$node $metadata $message\".",
suggestions: ["$metadata[$level] $message"] suggestions: ["$metadata[$level] $message"]
}, },
%{ %{
@ -970,7 +972,7 @@
%{ %{
key: :format, key: :format,
type: :string, type: :string,
description: "It defaults to \"$date $time [$level] $levelpad$node $metadata $message\"", description: "Default: \"$date $time [$level] $levelpad$node $metadata $message\".",
suggestions: ["$metadata[$level] $message"] suggestions: ["$metadata[$level] $message"]
}, },
%{ %{
@ -1024,7 +1026,7 @@
description: description:
"This form can be used to configure a keyword list that keeps the configuration data for any " <> "This form can be used to configure a keyword list that keeps the configuration data for any " <>
"kind of frontend. By default, settings for pleroma_fe and masto_fe are configured. If you want to " <> "kind of frontend. By default, settings for pleroma_fe and masto_fe are configured. If you want to " <>
"add your own configuration your settings need to be complete as they will override the defaults.", "add your own configuration your settings all fields must be complete.",
children: [ children: [
%{ %{
key: :pleroma_fe, key: :pleroma_fe,
@ -1046,7 +1048,11 @@
hideUserStats: false, hideUserStats: false,
scopeCopy: true, scopeCopy: true,
subjectLineBehavior: "email", subjectLineBehavior: "email",
alwaysShowSubjectInput: true alwaysShowSubjectInput: true,
logoMask: false,
logoMargin: ".1em",
stickers: false,
enableEmojiPicker: false
} }
], ],
children: [ children: [
@ -1074,7 +1080,7 @@
label: "Redirect root no login", label: "Redirect root no login",
type: :string, type: :string,
description: description:
"relative URL which indicates where to redirect when a user isn't logged in", "Relative URL which indicates where to redirect when a user isn't logged in",
suggestions: ["/main/all"] suggestions: ["/main/all"]
}, },
%{ %{
@ -1082,7 +1088,7 @@
label: "Redirect root login", label: "Redirect root login",
type: :string, type: :string,
description: description:
"relative URL which indicates where to redirect when a user is logged in", "Relative URL which indicates where to redirect when a user is logged in",
suggestions: ["/main/friends"] suggestions: ["/main/friends"]
}, },
%{ %{
@ -1095,34 +1101,34 @@
key: :scopeOptionsEnabled, key: :scopeOptionsEnabled,
label: "Scope options enabled", label: "Scope options enabled",
type: :boolean, type: :boolean,
description: "Enable setting an notice visibility and subject/CW when posting" description: "Enable setting a notice visibility and subject/CW when posting"
}, },
%{ %{
key: :formattingOptionsEnabled, key: :formattingOptionsEnabled,
label: "Formatting options enabled", label: "Formatting options enabled",
type: :boolean, type: :boolean,
description: description:
"Enable setting a formatting different than plain-text (ie. HTML, Markdown) when posting, relates to :instance, allowed_post_formats" "Enable setting a formatting different than plain-text (ie. HTML, Markdown) when posting, relates to `:instance`, `allowed_post_formats`"
}, },
%{ %{
key: :collapseMessageWithSubject, key: :collapseMessageWithSubject,
label: "Collapse message with subject", label: "Collapse message with subject",
type: :boolean, type: :boolean,
description: description:
"When a message has a subject(aka Content Warning), collapse it by default" "When a message has a subject (aka Content Warning), collapse it by default"
}, },
%{ %{
key: :hidePostStats, key: :hidePostStats,
label: "Hide post stats", label: "Hide post stats",
type: :boolean, type: :boolean,
description: "Hide notices statistics(repeats, favorites, ...)" description: "Hide notices statistics (repeats, favorites, ...)"
}, },
%{ %{
key: :hideUserStats, key: :hideUserStats,
label: "Hide user stats", label: "Hide user stats",
type: :boolean, type: :boolean,
description: description:
"Hide profile statistics(posts, posts per day, followers, followings, ...)" "Hide profile statistics (posts, posts per day, followers, followings, ...)"
}, },
%{ %{
key: :scopeCopy, key: :scopeCopy,
@ -1135,16 +1141,46 @@
label: "Subject line behavior", label: "Subject line behavior",
type: :string, type: :string,
description: "Allows changing the default behaviour of subject lines in replies. description: "Allows changing the default behaviour of subject lines in replies.
`email`: Copy and preprend re:, as in email, `email`: copy and preprend re:, as in email,
`masto`: Copy verbatim, as in Mastodon, `masto`: copy verbatim, as in Mastodon,
`noop`: Don't copy the subjec", `noop`: don't copy the subject.",
suggestions: ["email", "masto", "noop"] suggestions: ["email", "masto", "noop"]
}, },
%{ %{
key: :alwaysShowSubjectInput, key: :alwaysShowSubjectInput,
label: "Always show subject input", label: "Always show subject input",
type: :boolean, type: :boolean,
description: "When set to false, auto-hide the subject field when it's empty" description: "When set to `false`, auto-hide the subject field when it's empty"
},
%{
key: :logoMask,
label: "Logo mask",
type: :boolean,
description:
"By default it assumes logo used will be monochrome-with-alpha one, this is done to be compatible with both light and dark themes, " <>
"so that white logo designed with dark theme in mind won't be invisible over light theme, this is done via CSS3 Masking. " <>
"Basically - it will take alpha channel of the image and fill non-transparent areas of it with solid color. " <>
"If you really want colorful logo - it can be done by setting logoMask to false."
},
%{
key: :logoMargin,
label: "Logo margin",
type: :string,
description:
"Allows you to adjust vertical margins between logo boundary and navbar borders. " <>
"The idea is that to have logo's image without any extra margins and instead adjust them to your need in layout.",
suggestions: [".1em"]
},
%{
key: :stickers,
type: :boolean,
description: "Enables/disables stickers."
},
%{
key: :enableEmojiPicker,
label: "Emoji picker",
type: :boolean,
description: "Enables/disables emoji picker."
} }
] ]
}, },
@ -1180,7 +1216,7 @@
key: :mascots, key: :mascots,
type: {:keyword, :map}, type: {:keyword, :map},
description: description:
"Keyword of mascots, each element MUST contain both a url and a mime_type key", "Keyword of mascots, each element must contain both an url and a mime_type key",
suggestions: [ suggestions: [
pleroma_fox_tan: %{ pleroma_fox_tan: %{
url: "/images/pleroma-fox-tan-smol.png", url: "/images/pleroma-fox-tan-smol.png",
@ -1196,7 +1232,7 @@
key: :default_mascot, key: :default_mascot,
type: :atom, type: :atom,
description: description:
"This will be used as the default mascot on MastoFE (default: :pleroma_fox_tan)", "This will be used as the default mascot on MastoFE. Default: `:pleroma_fox_tan`",
suggestions: [ suggestions: [
:pleroma_fox_tan :pleroma_fox_tan
] ]
@ -1259,7 +1295,7 @@
key: :media_nsfw, key: :media_nsfw,
label: "Media NSFW", label: "Media NSFW",
type: {:list, :string}, type: {:list, :string},
description: "List of instances to put medias as NSFW(sensitive) from", description: "List of instances to put medias as NSFW (sensitive) from",
suggestions: ["example.com", "*.example.com"] suggestions: ["example.com", "*.example.com"]
}, },
%{ %{
@ -1334,12 +1370,12 @@
key: :allow_followersonly, key: :allow_followersonly,
label: "Allow followers-only", label: "Allow followers-only",
type: :boolean, type: :boolean,
description: "whether to allow followers-only posts" description: "Whether to allow followers-only posts"
}, },
%{ %{
key: :allow_direct, key: :allow_direct,
type: :boolean, type: :boolean,
description: "whether to allow direct messages" description: "Whether to allow direct messages"
} }
] ]
}, },
@ -1355,14 +1391,14 @@
type: :integer, type: :integer,
description: description:
"Number of mentioned users after which the message gets delisted (the message can still be seen, " <> "Number of mentioned users after which the message gets delisted (the message can still be seen, " <>
" but it will not show up in public timelines and mentioned users won't get notifications about it). Set to 0 to disable", " but it will not show up in public timelines and mentioned users won't get notifications about it). Set to 0 to disable.",
suggestions: [10] suggestions: [10]
}, },
%{ %{
key: :reject_threshold, key: :reject_threshold,
type: :integer, type: :integer,
description: description:
"Number of mentioned users after which the messaged gets rejected. Set to 0 to disable", "Number of mentioned users after which the messaged gets rejected. Set to 0 to disable.",
suggestions: [20] suggestions: [20]
} }
] ]
@ -1378,14 +1414,14 @@
key: :reject, key: :reject,
type: [:string, :regex], type: [:string, :regex],
description: description:
"A list of patterns which result in message being rejected, each pattern can be a string or a regular expression", "A list of patterns which result in message being rejected, each pattern can be a string or a regular expression.",
suggestions: ["foo", ~r/foo/iu] suggestions: ["foo", ~r/foo/iu]
}, },
%{ %{
key: :federated_timeline_removal, key: :federated_timeline_removal,
type: [:string, :regex], type: [:string, :regex],
description: description:
"A list of patterns which result in message being removed from federated timelines (a.k.a unlisted), each pattern can be a string or a regular expression", "A list of patterns which result in message being removed from federated timelines (a.k.a unlisted), each pattern can be a string or a regular expression.",
suggestions: ["foo", ~r/foo/iu] suggestions: ["foo", ~r/foo/iu]
}, },
%{ %{
@ -1464,7 +1500,7 @@
key: :base_url, key: :base_url,
type: :string, type: :string,
description: description:
"The base URL to access a user-uploaded file. Useful when you want to proxy the media files via another host/CDN fronts", "The base URL to access a user-uploaded file. Useful when you want to proxy the media files via another host/CDN fronts.",
suggestions: ["https://example.com"] suggestions: ["https://example.com"]
}, },
%{ %{
@ -1485,14 +1521,14 @@
type: :boolean, type: :boolean,
description: description:
"Redirects the client to the real remote URL if there's any HTTP errors. " <> "Redirects the client to the real remote URL if there's any HTTP errors. " <>
"Any error during body processing will not be redirected as the response is chunked" "Any error during body processing will not be redirected as the response is chunked."
}, },
%{ %{
key: :max_body_length, key: :max_body_length,
type: :integer, type: :integer,
description: description:
"limits the content length to be approximately the " <> "Limits the content length to be approximately the " <>
"specified length. It is validated with the `content-length` header and also verified when proxying" "specified length. It is validated with the `content-length` header and also verified when proxying."
}, },
%{ %{
key: :http, key: :http,
@ -1810,9 +1846,9 @@
key: :subject, key: :subject,
type: :string, type: :string,
description: description:
"a mailto link for the administrative contact." <> "A mailto link for the administrative contact." <>
" It's best if this email is not a personal email address, but rather a group email so that if a person leaves an organization," <> " It's best if this email is not a personal email address, but rather a group email so that if a person leaves an organization," <>
" is unavailable for an extended period, or otherwise can't respond, someone else on the list can", " is unavailable for an extended period, or otherwise can't respond, someone else on the list can.",
suggestions: ["Subject"] suggestions: ["Subject"]
}, },
%{ %{
@ -1860,12 +1896,12 @@
type: :group, type: :group,
description: description:
"Kocaptcha is a very simple captcha service with a single API endpoint, the source code is" <> "Kocaptcha is a very simple captcha service with a single API endpoint, the source code is" <>
" here: https://github.com/koto-bank/kocaptcha. The default endpoint https://captcha.kotobank.ch is hosted by the developer", " here: https://github.com/koto-bank/kocaptcha. The default endpoint (https://captcha.kotobank.ch) is hosted by the developer.",
children: [ children: [
%{ %{
key: :endpoint, key: :endpoint,
type: :string, type: :string,
description: "the kocaptcha endpoint to use", description: "The kocaptcha endpoint to use",
suggestions: ["https://captcha.kotobank.ch"] suggestions: ["https://captcha.kotobank.ch"]
} }
] ]
@ -1874,7 +1910,7 @@
group: :pleroma, group: :pleroma,
type: :group, type: :group,
description: description:
"Allows to set a token that can be used to authenticate with the admin api without using an actual user by giving it as the 'admin_token' parameter", "Allows to set a token that can be used to authenticate with the admin api without using an actual user by giving it as the `admin_token` parameter",
children: [ children: [
%{ %{
key: :admin_token, key: :admin_token,
@ -1924,8 +1960,9 @@
}, },
%{ %{
key: :verbose, key: :verbose,
type: :boolean, type: [:atom, false],
description: "Logs verbose mode" description: "Logs verbose mode",
suggestions: [false, :error, :warn, :info, :debug]
}, },
%{ %{
key: :prune, key: :prune,
@ -2040,7 +2077,7 @@
key: :unfurl_nsfw, key: :unfurl_nsfw,
label: "Unfurl NSFW", label: "Unfurl NSFW",
type: :boolean, type: :boolean,
description: "If set to true nsfw attachments will be shown in previews" description: "If set to `true` NSFW attachments will be shown in previews"
} }
] ]
}, },
@ -2084,7 +2121,7 @@
key: :ttl_setters, key: :ttl_setters,
label: "TTL setters", label: "TTL setters",
type: {:list, :module}, type: {:list, :module},
description: "List of rich media ttl setters.", description: "List of rich media TTL setters.",
suggestions: [ suggestions: [
Pleroma.Web.RichMedia.Parser.TTL.AwsSignedUrl Pleroma.Web.RichMedia.Parser.TTL.AwsSignedUrl
] ]
@ -2101,12 +2138,12 @@
key: :enabled, key: :enabled,
type: :boolean, type: :boolean,
description: description:
"if enabled, when a new user is federated with, fetch some of their latest posts" "If enabled, when a new user is federated with, fetch some of their latest posts"
}, },
%{ %{
key: :pages, key: :pages,
type: :integer, type: :integer,
description: "the amount of pages to fetch", description: "The amount of pages to fetch",
suggestions: [5] suggestions: [5]
} }
] ]
@ -2120,24 +2157,24 @@
%{ %{
key: :class, key: :class,
type: [:string, false], type: [:string, false],
description: "specify the class to be added to the generated link. false to clear", description: "Specify the class to be added to the generated link. `False` to clear",
suggestions: ["auto-linker", false] suggestions: ["auto-linker", false]
}, },
%{ %{
key: :rel, key: :rel,
type: [:string, false], type: [:string, false],
description: "override the rel attribute. false to clear", description: "Override the rel attribute. `False` to clear",
suggestions: ["ugc", "noopener noreferrer", false] suggestions: ["ugc", "noopener noreferrer", false]
}, },
%{ %{
key: :new_window, key: :new_window,
type: :boolean, type: :boolean,
description: "set to false to remove target='_blank' attribute" description: "Set to `false` to remove target='_blank' attribute"
}, },
%{ %{
key: :scheme, key: :scheme,
type: :boolean, type: :boolean,
description: "Set to true to link urls with schema http://google.com" description: "Set to `true` to link urls with schema http://google.com"
}, },
%{ %{
key: :truncate, key: :truncate,
@ -2154,7 +2191,7 @@
%{ %{
key: :extra, key: :extra,
type: :boolean, type: :boolean,
description: "link urls with rarely used schemes (magnet, ipfs, irc, etc.)" description: "Link urls with rarely used schemes (magnet, ipfs, irc, etc.)"
} }
] ]
}, },
@ -2168,20 +2205,20 @@
key: :daily_user_limit, key: :daily_user_limit,
type: :integer, type: :integer,
description: description:
"the number of scheduled activities a user is allowed to create in a single day (Default: 25)", "The number of scheduled activities a user is allowed to create in a single day. Default: 25.",
suggestions: [25] suggestions: [25]
}, },
%{ %{
key: :total_user_limit, key: :total_user_limit,
type: :integer, type: :integer,
description: description:
"the number of scheduled activities a user is allowed to create in total (Default: 300)", "The number of scheduled activities a user is allowed to create in total. Default: 300.",
suggestions: [300] suggestions: [300]
}, },
%{ %{
key: :enabled, key: :enabled,
type: :boolean, type: :boolean,
description: "whether scheduled activities are sent to the job queue to be executed" description: "Whether scheduled activities are sent to the job queue to be executed"
} }
] ]
}, },
@ -2194,7 +2231,7 @@
%{ %{
key: :enabled, key: :enabled,
type: :boolean, type: :boolean,
description: "whether expired activities will be sent to the job queue to be deleted" description: "Whether expired activities will be sent to the job queue to be deleted"
} }
] ]
}, },
@ -2216,14 +2253,14 @@
type: :group, type: :group,
description: description:
"Use LDAP for user authentication. When a user logs in to the Pleroma instance, the name and password" <> "Use LDAP for user authentication. When a user logs in to the Pleroma instance, the name and password" <>
" will be verified by trying to authenticate (bind) to an LDAP server." <> " will be verified by trying to authenticate (bind) to a LDAP server." <>
" If a user exists in the LDAP directory but there is no account with the same name yet on the" <> " If a user exists in the LDAP directory but there is no account with the same name yet on the" <>
" Pleroma instance then a new Pleroma account will be created with the same name as the LDAP user name.", " Pleroma instance then a new Pleroma account will be created with the same name as the LDAP user name.",
children: [ children: [
%{ %{
key: :enabled, key: :enabled,
type: :boolean, type: :boolean,
description: "enables LDAP authentication" description: "Enables LDAP authentication"
}, },
%{ %{
key: :host, key: :host,
@ -2241,13 +2278,13 @@
key: :ssl, key: :ssl,
label: "SSL", label: "SSL",
type: :boolean, type: :boolean,
description: "true to use SSL, usually implies the port 636" description: "`True` to use SSL, usually implies the port 636"
}, },
%{ %{
key: :sslopts, key: :sslopts,
label: "SSL options", label: "SSL options",
type: :keyword, type: :keyword,
description: "additional SSL options", description: "Additional SSL options",
suggestions: [cacertfile: "path/to/file/with/PEM/cacerts", verify: :verify_peer], suggestions: [cacertfile: "path/to/file/with/PEM/cacerts", verify: :verify_peer],
children: [ children: [
%{ %{
@ -2268,13 +2305,13 @@
key: :tls, key: :tls,
label: "TLS", label: "TLS",
type: :boolean, type: :boolean,
description: "true to start TLS, usually implies the port 389" description: "`True` to start TLS, usually implies the port 389"
}, },
%{ %{
key: :tlsopts, key: :tlsopts,
label: "TLS options", label: "TLS options",
type: :keyword, type: :keyword,
description: "additional TLS options", description: "Additional TLS options",
suggestions: [cacertfile: "path/to/file/with/PEM/cacerts", verify: :verify_peer], suggestions: [cacertfile: "path/to/file/with/PEM/cacerts", verify: :verify_peer],
children: [ children: [
%{ %{
@ -2325,23 +2362,23 @@
key: :auth_template, key: :auth_template,
type: :string, type: :string,
description: description:
"authentication form template. By default it's show.html which corresponds to lib/pleroma/web/templates/o_auth/o_auth/show.html.ee", "Authentication form template. By default it's `show.html` which corresponds to `lib/pleroma/web/templates/o_auth/o_auth/show.html.ee`.",
suggestions: ["show.html"] suggestions: ["show.html"]
}, },
%{ %{
key: :oauth_consumer_template, key: :oauth_consumer_template,
type: :string, type: :string,
description: description:
"OAuth consumer mode authentication form template. By default it's consumer.html which corresponds to" <> "OAuth consumer mode authentication form template. By default it's `consumer.html` which corresponds to" <>
" lib/pleroma/web/templates/o_auth/o_auth/consumer.html.eex", " `lib/pleroma/web/templates/o_auth/o_auth/consumer.html.eex`.",
suggestions: ["consumer.html"] suggestions: ["consumer.html"]
}, },
%{ %{
key: :oauth_consumer_strategies, key: :oauth_consumer_strategies,
type: {:list, :string}, type: {:list, :string},
description: description:
"the list of enabled OAuth consumer strategies; by default it's set by OAUTH_CONSUMER_STRATEGIES environment variable." <> "The list of enabled OAuth consumer strategies; by default it's set by OAUTH_CONSUMER_STRATEGIES environment variable." <>
" Each entry in this space-delimited string should be of format <strategy> or <strategy>:<dependency>" <> " Each entry in this space-delimited string should be of format \"strategy\" or \"strategy:dependency\"" <>
" (e.g. twitter or keycloak:ueberauth_keycloak_strategy in case dependency is named differently than ueberauth_<strategy>).", " (e.g. twitter or keycloak:ueberauth_keycloak_strategy in case dependency is named differently than ueberauth_<strategy>).",
suggestions: ["twitter", "keycloak:ueberauth_keycloak_strategy"] suggestions: ["twitter", "keycloak:ueberauth_keycloak_strategy"]
} }
@ -2370,13 +2407,13 @@
%{ %{
key: :active, key: :active,
type: :boolean, type: :boolean,
description: "globally enable or disable digest emails" description: "Globally enable or disable digest emails"
}, },
%{ %{
key: :schedule, key: :schedule,
type: :string, type: :string,
description: description:
"When to send digest email, in crontab format. \"0 0 0\" is the default, meaning \"once a week at midnight on Sunday morning\"", "When to send digest email, in crontab format. \"0 0 0\" is the default, meaning \"once a week at midnight on Sunday morning\".",
suggestions: ["0 0 * * 0"] suggestions: ["0 0 * * 0"]
}, },
%{ %{
@ -2404,7 +2441,7 @@
%{ %{
key: :logo, key: :logo,
type: :string, type: :string,
description: "a path to a custom logo. Set it to nil to use the default Pleroma logo", description: "A path to a custom logo. Set it to `nil` to use the default Pleroma logo.",
suggestions: ["some/path/logo.png"] suggestions: ["some/path/logo.png"]
}, },
%{ %{
@ -2477,13 +2514,13 @@
%{ %{
key: :clean_expired_tokens, key: :clean_expired_tokens,
type: :boolean, type: :boolean,
description: "Enable a background job to clean expired oauth tokens. Defaults to false" description: "Enable a background job to clean expired oauth tokens. Default: `false`."
}, },
%{ %{
key: :clean_expired_tokens_interval, key: :clean_expired_tokens_interval,
type: :integer, type: :integer,
description: description:
"Interval to run the job to clean expired tokens. Defaults to 86_400_000 (24 hours).", "Interval to run the job to clean expired tokens. Default: 86_400_000 (24 hours).",
suggestions: [86_400_000] suggestions: [86_400_000]
} }
] ]
@ -2496,7 +2533,7 @@
%{ %{
key: :shortcode_globs, key: :shortcode_globs,
type: {:list, :string}, type: {:list, :string},
description: "Location of custom emoji files. * can be used as a wildcard", description: "Location of custom emoji files. * can be used as a wildcard.",
suggestions: ["/emoji/custom/**/*.png"] suggestions: ["/emoji/custom/**/*.png"]
}, },
%{ %{
@ -2510,8 +2547,8 @@
key: :groups, key: :groups,
type: {:keyword, :string, {:list, :string}}, type: {:keyword, :string, {:list, :string}},
description: description:
"Emojis are ordered in groups (tags). This is an array of key-value pairs where the key is the groupname" <> "Emojis are ordered in groups (tags). This is an array of key-value pairs where the key is the group name" <>
" and the value the location or array of locations. * can be used as a wildcard", " and the value is the location or array of locations. * can be used as a wildcard.",
suggestions: [ suggestions: [
Custom: ["/emoji/*.png", "/emoji/**/*.png"] Custom: ["/emoji/*.png", "/emoji/**/*.png"]
] ]
@ -2521,7 +2558,7 @@
type: :string, type: :string,
description: description:
"Location of the JSON-manifest. This manifest contains information about the emoji-packs you can download." <> "Location of the JSON-manifest. This manifest contains information about the emoji-packs you can download." <>
" Currently only one manifest can be added (no arrays)", " Currently only one manifest can be added (no arrays).",
suggestions: ["https://git.pleroma.social/pleroma/emoji-index/raw/master/index.json"] suggestions: ["https://git.pleroma.social/pleroma/emoji-index/raw/master/index.json"]
}, },
%{ %{
@ -2544,7 +2581,7 @@
%{ %{
key: :rum_enabled, key: :rum_enabled,
type: :boolean, type: :boolean,
description: "If RUM indexes should be used. Defaults to false" description: "If RUM indexes should be used. Default: `false`"
} }
] ]
}, },
@ -2558,45 +2595,45 @@
%{ %{
key: :search, key: :search,
type: [:tuple, {:list, :tuple}], type: [:tuple, {:list, :tuple}],
description: "for the search requests (account & status search etc.)", description: "For the search requests (account & status search etc.)",
suggestions: [{1000, 10}, [{10_000, 10}, {10_000, 50}]] suggestions: [{1000, 10}, [{10_000, 10}, {10_000, 50}]]
}, },
%{ %{
key: :app_account_creation, key: :app_account_creation,
type: [:tuple, {:list, :tuple}], type: [:tuple, {:list, :tuple}],
description: "for registering user accounts from the same IP address", description: "For registering user accounts from the same IP address",
suggestions: [{1000, 10}, [{10_000, 10}, {10_000, 50}]] suggestions: [{1000, 10}, [{10_000, 10}, {10_000, 50}]]
}, },
%{ %{
key: :relations_actions, key: :relations_actions,
type: [:tuple, {:list, :tuple}], type: [:tuple, {:list, :tuple}],
description: "for actions on relations with all users (follow, unfollow)", description: "For actions on relations with all users (follow, unfollow)",
suggestions: [{1000, 10}, [{10_000, 10}, {10_000, 50}]] suggestions: [{1000, 10}, [{10_000, 10}, {10_000, 50}]]
}, },
%{ %{
key: :relation_id_action, key: :relation_id_action,
type: [:tuple, {:list, :tuple}], type: [:tuple, {:list, :tuple}],
description: "for actions on relation with a specific user (follow, unfollow)", description: "For actions on relation with a specific user (follow, unfollow)",
suggestions: [{1000, 10}, [{10_000, 10}, {10_000, 50}]] suggestions: [{1000, 10}, [{10_000, 10}, {10_000, 50}]]
}, },
%{ %{
key: :statuses_actions, key: :statuses_actions,
type: [:tuple, {:list, :tuple}], type: [:tuple, {:list, :tuple}],
description: description:
"for create / delete / fav / unfav / reblog / unreblog actions on any statuses", "For create / delete / fav / unfav / reblog / unreblog actions on any statuses",
suggestions: [{1000, 10}, [{10_000, 10}, {10_000, 50}]] suggestions: [{1000, 10}, [{10_000, 10}, {10_000, 50}]]
}, },
%{ %{
key: :status_id_action, key: :status_id_action,
type: [:tuple, {:list, :tuple}], type: [:tuple, {:list, :tuple}],
description: description:
"for fav / unfav or reblog / unreblog actions on the same status by the same user", "For fav / unfav or reblog / unreblog actions on the same status by the same user",
suggestions: [{1000, 10}, [{10_000, 10}, {10_000, 50}]] suggestions: [{1000, 10}, [{10_000, 10}, {10_000, 50}]]
}, },
%{ %{
key: :authentication, key: :authentication,
type: [:tuple, {:list, :tuple}], type: [:tuple, {:list, :tuple}],
description: "for authentication create / password check / user existence check requests", description: "For authentication create / password check / user existence check requests",
suggestions: [{60_000, 15}] suggestions: [{60_000, 15}]
} }
] ]
@ -2611,12 +2648,12 @@
%{ %{
key: :enabled, key: :enabled,
type: :boolean, type: :boolean,
description: "Enables ssh" description: "Enables SSH"
}, },
%{ %{
key: :priv_dir, key: :priv_dir,
type: :string, type: :string,
description: "Dir with ssh keys", description: "Dir with SSH keys",
suggestions: ["/some/path/ssh_keys"] suggestions: ["/some/path/ssh_keys"]
}, },
%{ %{
@ -2711,43 +2748,6 @@
} }
] ]
}, },
%{
group: :pleroma,
key: :suggestions,
type: :group,
children: [
%{
key: :enabled,
type: :boolean,
description: "Enables suggestions"
},
%{
key: :third_party_engine,
type: :string,
description: "URL for third party engine",
suggestions: [
"http://vinayaka.distsn.org/cgi-bin/vinayaka-user-match-suggestions-api.cgi?{{host}}+{{user}}"
]
},
%{
key: :timeout,
type: :integer,
description: "Request timeout to third party engine",
suggestions: [300_000]
},
%{
key: :limit,
type: :integer,
description: "Limit for suggestions",
suggestions: [40]
},
%{
key: :web,
type: :string,
suggestions: ["https://vinayaka.distsn.org"]
}
]
},
%{ %{
group: :prometheus, group: :prometheus,
key: Pleroma.Web.Endpoint.MetricsExporter, key: Pleroma.Web.Endpoint.MetricsExporter,
@ -2795,7 +2795,7 @@
key: :user_agent, key: :user_agent,
type: [:string, :atom], type: [:string, :atom],
description: description:
"What user agent to use. Must be a string or an atom `:default`. Default value is `:default`", "What user agent to use. Must be a string or an atom `:default`. Default value is `:default`.",
suggestions: ["Pleroma", :default] suggestions: ["Pleroma", :default]
}, },
%{ %{
@ -2967,19 +2967,19 @@
%{ %{
key: :enabled, key: :enabled,
type: :boolean, type: :boolean,
description: "Enable/disable the plug. Defaults to `false`." description: "Enable/disable the plug. Default: `false`."
}, },
%{ %{
key: :headers, key: :headers,
type: {:list, :string}, type: {:list, :string},
description: description:
"A list of strings naming the `req_headers` to use when deriving the `remote_ip`. Order does not matter. Defaults to `~w[forwarded x-forwarded-for x-client-ip x-real-ip]`." "A list of strings naming the `req_headers` to use when deriving the `remote_ip`. Order does not matter. Default: `~w[forwarded x-forwarded-for x-client-ip x-real-ip]`."
}, },
%{ %{
key: :proxies, key: :proxies,
type: {:list, :string}, type: {:list, :string},
description: description:
"A list of strings in [CIDR](https://en.wikipedia.org/wiki/CIDR) notation specifying the IPs of known proxies. Defaults to `[]`." "A list of strings in [CIDR](https://en.wikipedia.org/wiki/CIDR) notation specifying the IPs of known proxies. Default: `[]`."
}, },
%{ %{
key: :reserved, key: :reserved,
@ -3000,14 +3000,13 @@
key: :activity_pub, key: :activity_pub,
type: :integer, type: :integer,
description: description:
"activity pub routes (except question activities). Defaults to `nil` (no expiration).", "Activity pub routes (except question activities). Default: `nil` (no expiration).",
suggestions: [30_000] suggestions: [30_000, nil]
}, },
%{ %{
key: :activity_pub_question, key: :activity_pub_question,
type: :integer, type: :integer,
description: description: "Activity pub routes (question activities). Default: `30_000` (30 seconds).",
"activity pub routes (question activities). Defaults to `30_000` (30 seconds).",
suggestions: [30_000] suggestions: [30_000]
} }
] ]

View file

@ -29,7 +29,7 @@ Has these additional fields under the `pleroma` object:
- `spoiler_text`: a map consisting of alternate representations of the `spoiler_text` property with the key being it's mimetype. Currently the only alternate representation supported is `text/plain` - `spoiler_text`: a map consisting of alternate representations of the `spoiler_text` property with the key being it's mimetype. Currently the only alternate representation supported is `text/plain`
- `expires_at`: a datetime (iso8601) that states when the post will expire (be deleted automatically), or empty if the post won't expire - `expires_at`: a datetime (iso8601) that states when the post will expire (be deleted automatically), or empty if the post won't expire
- `thread_muted`: true if the thread the post belongs to is muted - `thread_muted`: true if the thread the post belongs to is muted
- `emoji_reactions`: A list with emoji / reaction count tuples. Contains no information about the reacting users, for that use the `emoji_reactions_by` endpoint. - `emoji_reactions`: A list with emoji / reaction maps. The format is {emoji: "☕", count: 1}. Contains no information about the reacting users, for that use the `emoji_reactions_by` endpoint.
## Attachments ## Attachments

View file

@ -455,7 +455,7 @@ Emoji reactions work a lot like favourites do. They make it possible to react to
* Example Response: * Example Response:
```json ```json
[ [
["😀", [{"id" => "xyz.."...}, {"id" => "zyx..."}]], {"emoji": "😀", "count": 2, "accounts": [{"id" => "xyz.."...}, {"id" => "zyx..."}]},
["☕", [{"id" => "abc..."}]] {"emoji": "☕", "count": 1, "accounts": [{"id" => "abc..."}]}
] ]
``` ```

View file

@ -0,0 +1,24 @@
# Managing emails
{! backend/administration/CLI_tasks/general_cli_task_info.include !}
## Send test email (instance email by default)
```sh tab="OTP"
./bin/pleroma_ctl email test [--to <destination email address>]
```
```sh tab="From Source"
mix pleroma.email test [--to <destination email address>]
```
Example:
```sh tab="OTP"
./bin/pleroma_ctl email test --to root@example.org
```
```sh tab="From Source"
mix pleroma.email test --to root@example.org
```

View file

@ -1,31 +0,0 @@
# How to activate user recommendation (Who to follow panel)
![who-to-follow-panel-small](/uploads/9de1b1300436c32461d272945f1bc23e/who-to-follow-panel-small.png)
To show the *who to follow* panel, edit `config/prod.secret.exs` in the Pleroma backend. Following code activates the *who to follow* panel:
```elixir
config :pleroma, :suggestions,
enabled: true,
third_party_engine:
"http://vinayaka.distsn.org/cgi-bin/vinayaka-user-match-suggestions-api.cgi?{{host}}+{{user}}",
timeout: 300_000,
limit: 40,
web: "https://vinayaka.distsn.org"
```
`config/config.exs` already includes this code, but `enabled:` is `false`.
`/api/v1/suggestions` is also provided when *who to follow* panel is enabled.
For advanced customization, following code shows the newcomers of the fediverse at the *who to follow* panel:
```elixir
config :pleroma, :suggestions,
enabled: true,
third_party_engine:
"http://vinayaka.distsn.org/cgi-bin/vinayaka-user-new-suggestions-api.cgi?{{host}}+{{user}}",
timeout: 60_000,
limit: 40,
web: "https://vinayaka.distsn.org/user-new.html"
```

View file

@ -3,53 +3,63 @@
Pleroma is a federated social networking platform, compatible with GNU social, Mastodon and other OStatus and ActivityPub implementations. It is free software licensed under the AGPLv3. Pleroma is a federated social networking platform, compatible with GNU social, Mastodon and other OStatus and ActivityPub implementations. It is free software licensed under the AGPLv3.
It actually consists of two components: a backend, named simply Pleroma, and a user-facing frontend, named Pleroma-FE. It also includes the Mastodon frontend, if that's your thing. It actually consists of two components: a backend, named simply Pleroma, and a user-facing frontend, named Pleroma-FE. It also includes the Mastodon frontend, if that's your thing.
It's part of what we call the fediverse, a federated network of instances which speak common protocols and can communicate with each other. It's part of what we call the fediverse, a federated network of instances which speak common protocols and can communicate with each other.
One account on a instance is enough to talk to the entire fediverse! One account on an instance is enough to talk to the entire fediverse!
## How can I use it? ## How can I use it?
Pleroma instances are already widely deployed, a list can be found here: Pleroma instances are already widely deployed, a list can be found at <http://distsn.org/pleroma-instances.html>. Information on all existing fediverse instances can be found at <https://fediverse.network/>.
http://distsn.org/pleroma-instances.html
If you don't feel like joining an existing instance, but instead prefer to deploy your own instance, that's easy too! If you don't feel like joining an existing instance, but instead prefer to deploy your own instance, that's easy too!
Installation instructions can be found here: Installation instructions can be found in the installation section of these docs.
[main Pleroma wiki](/)
## I got an account, now what? ## I got an account, now what?
Great! Now you can explore the fediverse! Great! Now you can explore the fediverse! Open the login page for your Pleroma instance (e.g. <https://pleroma.soykaf.com>) and login with your username and password. (If you don't have an account yet, click on Register)
- Open the login page for your Pleroma instance (for ex. https://pleroma.soykaf.com) and login with your username and password.
(If you don't have one yet, click on Register) :slightly_smiling_face:
At this point you will have two columns in front of you. At this point you will have two columns in front of you.
### Left column ### Left column
- first block: here you can see your avatar, your nickname a bio, and statistics (Statuses, Following, Followers).
Under that you have a text form which allows you to post new statuses. The icon on the left is for uploading media files and attach them to your post. The number under the text form is a character counter, every instance can have a different character limit (the default is 5000). - first block: here you can see your avatar, your nickname and statistics (Statuses, Following, Followers). Clicking your profile pic will open your profile.
If you want to mention someone, type @ + name of the person. A drop-down menu will help you in finding the right person. :slight_smile: Under that you have a text form which allows you to post new statuses. The number on the bottom of the text form is a character counter, every instance can have a different character limit (the default is 5000).
If you want to mention someone, type @ + name of the person. A drop-down menu will help you in finding the right person.
Under the text form there are also several visibility options and there is the option to use rich text.
Under that the icon on the left is for uploading media files and attach them to your post. There is also an emoji-picker and an option to post a poll.
To post your status, simply press Submit. To post your status, simply press Submit.
On the top right you will also see a wrench icon. This opens your personal settings.
- second block: Here you can switch between the different timelines: - second block: Here you can switch between the different timelines:
- Timeline: all the people that you follow - Timeline: all the people that you follow
- Mentions: all the statutes where you are mentioned - Interactions: here you can switch between different timelines where there was interaction with your account. There is Mentions, Repeats and Favorites, and New follows
- Public Timeline: all the statutes from the local instance - Direct Messages: these are the Direct Messages sent to you
- The Whole Known Network: everything, local and remote! - Public Timeline: all the statutes from the local instance
- The Whole Known Network: all public posts the instance knows about, both local and remote!
- third block: this is the Chat block, where you communicate with people on the same instance in realtime. It is local-only, for now, but we're planning to make it extendable to the entire fediverse! :sweat_smile: - About: This isn't a Timeline but shows relevant info about the instance. You can find a list of the moderators and admins, Terms of Service, MRF policies and enabled features.
- Optional third block: This is the Instance panel that can be activated, but is deactivated by default. It's fully customisable and by default has links to the pleroma-fe and Mastodon-fe.
- fourth block: This is the Notifications block, here you will get notified whenever somebody mentions you, follows you, repeats or favorites one of your statuses. - fourth block: This is the Notifications block, here you will get notified whenever somebody mentions you, follows you, repeats or favorites one of your statuses.
### Right column ### Right column
This is where the interesting stuff happens! :slight_smile: This is where the interesting stuff happens!
Depending on the timeline you will see different statuses, but each status has a standard structure: Depending on the timeline you will see different statuses, but each status has a standard structure:
- Icon + name + link to profile. An optional left-arrow if it's a reply to another status (hovering will reveal the replied-to status).
- A + button on the right allows you to Expand/Collapse an entire discussion thread. It also updates in realtime!
- A binocular icon allows you to open the status on the instance where it's originating from.
- The text of the status, including mentions. If you click on a mention, it will automatically open the profile page of that person.
- Four buttons (left to right): Reply, Repeat, Favorite, Delete.
## Mastodon interface - Profile pic, name and link to profile. An optional left-arrow if it's a reply to another status (hovering will reveal the replied-to status). Clicking on the profile pic will uncollapse the user's profile.
If the Pleroma interface isn't your thing, or you're just trying something new but you want to keep using the familiar Mastodon interface, we got that too! :smile: - A `+` button on the right allows you to Expand/Collapse an entire discussion thread. It also updates in realtime!
Just add a "/web" after your instance url (for ex. https://pleroma.soycaf.com/web) and you'll end on the Mastodon web interface, but with a Pleroma backend! MAGIC! :fireworks: - An arrow icon allows you to open the status on the instance where it's originating from.
For more information on the Mastodon interface, please look here: - The text of the status, including mentions and attachements. If you click on a mention, it will automatically open the profile page of that person.
https://github.com/tootsuite/documentation/blob/master/Using-Mastodon/User-guide.md - Three buttons (left to right): Reply, Repeat, Favorite. There is also a forth button, this is a dropdown menu for simple moderation like muting the conversation or, if you have moderation rights, delete the status from the server.
### Top right
- The magnifier icon opens the search screen where you can search for statuses, people and hashtags. It's also possible to import statusses from remote servers by pasting the url to the post in the search field.
- The gear icon gives you general settings
- If you have admin rights, you'll see an icon that opens the admin interface
- The last icon is to log out
### Bottom right
On the bottom right you have a chatbox. Here you can communicate with people on the same instance in realtime. It is local-only, for now, but there are plans to make it extendable to the entire fediverse!
### Mastodon interface
If the Pleroma interface isn't your thing, or you're just trying something new but you want to keep using the familiar Mastodon interface, we got that too!
Just add a "/web" after your instance url (e.g. <https://pleroma.soycaf.com/web>) and you'll end on the Mastodon web interface, but with a Pleroma backend! MAGIC!
The Mastodon interface is from the Glitch-soc fork. For more information on the Mastodon interface you can check the [Mastodon](https://docs.joinmastodon.org/) and [Glitch-soc](https://glitch-soc.github.io/docs/) documentation.
Remember, what you see is only the frontend part of Mastodon, the backend is still Pleroma. Remember, what you see is only the frontend part of Mastodon, the backend is still Pleroma.

View file

@ -52,6 +52,9 @@ def migrate_to_db(file_path \\ nil) do
defp do_migrate_to_db(config_file) do defp do_migrate_to_db(config_file) do
if File.exists?(config_file) do if File.exists?(config_file) do
Ecto.Adapters.SQL.query!(Repo, "TRUNCATE config;")
Ecto.Adapters.SQL.query!(Repo, "ALTER SEQUENCE config_id_seq RESTART;")
custom_config = custom_config =
config_file config_file
|> read_file() |> read_file()

View file

@ -0,0 +1,25 @@
defmodule Mix.Tasks.Pleroma.Email do
use Mix.Task
@shortdoc "Simple Email test"
@moduledoc File.read!("docs/administration/CLI_tasks/email.md")
def run(["test" | args]) do
Mix.Pleroma.start_pleroma()
{options, [], []} =
OptionParser.parse(
args,
strict: [
to: :string
]
)
email = Pleroma.Emails.AdminEmail.test_email(options[:to])
{:ok, _} = Pleroma.Emails.Mailer.deliver(email)
Mix.shell().info(
"Test email has been sent to #{inspect(email.to)} from #{inspect(email.from)}"
)
end
end

View file

@ -9,6 +9,7 @@ defmodule Mix.Tasks.Pleroma.Emoji do
@moduledoc File.read!("docs/administration/CLI_tasks/emoji.md") @moduledoc File.read!("docs/administration/CLI_tasks/emoji.md")
def run(["ls-packs" | args]) do def run(["ls-packs" | args]) do
Mix.Pleroma.start_pleroma()
Application.ensure_all_started(:hackney) Application.ensure_all_started(:hackney)
{options, [], []} = parse_global_opts(args) {options, [], []} = parse_global_opts(args)
@ -35,6 +36,7 @@ def run(["ls-packs" | args]) do
end end
def run(["get-packs" | args]) do def run(["get-packs" | args]) do
Mix.Pleroma.start_pleroma()
Application.ensure_all_started(:hackney) Application.ensure_all_started(:hackney)
{options, pack_names, []} = parse_global_opts(args) {options, pack_names, []} = parse_global_opts(args)

View file

@ -18,6 +18,7 @@ defmodule Mix.Tasks.Pleroma.RobotsTxt do
""" """
def run(["disallow_all"]) do def run(["disallow_all"]) do
Mix.Pleroma.start_pleroma()
static_dir = Pleroma.Config.get([:instance, :static_dir], "instance/static/") static_dir = Pleroma.Config.get([:instance, :static_dir], "instance/static/")
if !File.exists?(static_dir) do if !File.exists?(static_dir) do

View file

@ -236,15 +236,7 @@ def from_binary_with_convert(binary) do
end end
@spec from_string(String.t()) :: atom() | no_return() @spec from_string(String.t()) :: atom() | no_return()
def from_string(":" <> entity), do: String.to_existing_atom(entity) def from_string(string), do: do_transform_string(string)
def from_string(entity) when is_binary(entity) do
if is_module_name?(entity) do
String.to_existing_atom("Elixir.#{entity}")
else
entity
end
end
@spec convert(any()) :: any() @spec convert(any()) :: any()
def convert(entity), do: do_convert(entity) def convert(entity), do: do_convert(entity)
@ -416,7 +408,7 @@ defp do_transform_string(value) do
@spec is_module_name?(String.t()) :: boolean() @spec is_module_name?(String.t()) :: boolean()
def is_module_name?(string) do def is_module_name?(string) do
Regex.match?(~r/^(Pleroma|Phoenix|Tesla|Quack|Ueberauth)\./, string) or Regex.match?(~r/^(Pleroma|Phoenix|Tesla|Quack|Ueberauth|Swoosh)\./, string) or
string in ["Oban", "Ueberauth", "ExSyslogger"] string in ["Oban", "Ueberauth", "ExSyslogger"]
end end
end end

View file

@ -7,6 +7,7 @@ defmodule Pleroma.Emails.AdminEmail do
import Swoosh.Email import Swoosh.Email
alias Pleroma.Config
alias Pleroma.Web.Router.Helpers alias Pleroma.Web.Router.Helpers
defp instance_config, do: Pleroma.Config.get(:instance) defp instance_config, do: Pleroma.Config.get(:instance)
@ -17,7 +18,20 @@ defp instance_notify_email do
end end
defp user_url(user) do defp user_url(user) do
Helpers.feed_url(Pleroma.Web.Endpoint, :feed_redirect, user.id) Helpers.user_feed_url(Pleroma.Web.Endpoint, :feed_redirect, user.id)
end
def test_email(mail_to \\ nil) do
html_body = """
<h3>Instance Test Email</h3>
<p>A test email was requested. Hello. :)</p>
"""
new()
|> to(mail_to || Config.get([:instance, :email]))
|> from({instance_name(), instance_notify_email()})
|> subject("Instance Test Email")
|> html_body(html_body)
end end
def report(to, reporter, account, statuses, comment) do def report(to, reporter, account, statuses, comment) do

View file

@ -11,11 +11,9 @@ def init(options) do
end end
def call(%{assigns: %{user: %User{} = user}} = conn, _) do def call(%{assigns: %{user: %User{} = user}} = conn, _) do
if User.auth_active?(user) do case User.account_status(user) do
conn :active -> conn
else _ -> assign(conn, :user, nil)
conn
|> assign(:user, nil)
end end
end end

View file

@ -12,6 +12,7 @@ defmodule Pleroma.User do
alias Comeonin.Pbkdf2 alias Comeonin.Pbkdf2
alias Ecto.Multi alias Ecto.Multi
alias Pleroma.Activity alias Pleroma.Activity
alias Pleroma.Config
alias Pleroma.Conversation.Participation alias Pleroma.Conversation.Participation
alias Pleroma.Delivery alias Pleroma.Delivery
alias Pleroma.FollowingRelationship alias Pleroma.FollowingRelationship
@ -35,7 +36,7 @@ defmodule Pleroma.User do
require Logger require Logger
@type t :: %__MODULE__{} @type t :: %__MODULE__{}
@type account_status :: :active | :deactivated | :password_reset_pending | :confirmation_pending
@primary_key {:id, FlakeId.Ecto.CompatType, autogenerate: true} @primary_key {:id, FlakeId.Ecto.CompatType, autogenerate: true}
# credo:disable-for-next-line Credo.Check.Readability.MaxLineLength # credo:disable-for-next-line Credo.Check.Readability.MaxLineLength
@ -216,14 +217,21 @@ def unquote(:"#{outgoing_relation_target}_ap_ids")(user, restrict_deactivated? \
end end
end end
@doc "Returns if the user should be allowed to authenticate" @doc "Returns status account"
def auth_active?(%User{deactivated: true}), do: false @spec account_status(User.t()) :: account_status()
def account_status(%User{deactivated: true}), do: :deactivated
def account_status(%User{password_reset_pending: true}), do: :password_reset_pending
def auth_active?(%User{confirmation_pending: true}), def account_status(%User{confirmation_pending: true}) do
do: !Pleroma.Config.get([:instance, :account_activation_required]) case Config.get([:instance, :account_activation_required]) do
true -> :confirmation_pending
_ -> :active
end
end
def auth_active?(%User{}), do: true def account_status(%User{}), do: :active
@spec visible_for?(User.t(), User.t() | nil) :: boolean()
def visible_for?(user, for_user \\ nil) def visible_for?(user, for_user \\ nil)
def visible_for?(%User{invisible: true}, _), do: false def visible_for?(%User{invisible: true}, _), do: false
@ -231,15 +239,17 @@ def visible_for?(%User{invisible: true}, _), do: false
def visible_for?(%User{id: user_id}, %User{id: for_id}) when user_id == for_id, do: true def visible_for?(%User{id: user_id}, %User{id: for_id}) when user_id == for_id, do: true
def visible_for?(%User{} = user, for_user) do def visible_for?(%User{} = user, for_user) do
auth_active?(user) || superuser?(for_user) account_status(user) == :active || superuser?(for_user)
end end
def visible_for?(_, _), do: false def visible_for?(_, _), do: false
@spec superuser?(User.t()) :: boolean()
def superuser?(%User{local: true, is_admin: true}), do: true def superuser?(%User{local: true, is_admin: true}), do: true
def superuser?(%User{local: true, is_moderator: true}), do: true def superuser?(%User{local: true, is_moderator: true}), do: true
def superuser?(_), do: false def superuser?(_), do: false
@spec invisible?(User.t()) :: boolean()
def invisible?(%User{invisible: true}), do: true def invisible?(%User{invisible: true}), do: true
def invisible?(_), do: false def invisible?(_), do: false
@ -1502,7 +1512,7 @@ def insert_or_update_user(data) do
data data
|> Map.put(:name, blank?(data[:name]) || data[:nickname]) |> Map.put(:name, blank?(data[:name]) || data[:nickname])
|> remote_user_creation() |> remote_user_creation()
|> Repo.insert(on_conflict: :replace_all_except_primary_key, conflict_target: :nickname) |> Repo.insert(on_conflict: {:replace_all_except, [:id]}, conflict_target: :nickname)
|> set_cache() |> set_cache()
end end

View file

@ -58,7 +58,7 @@ def create(relationship_type, %User{} = source, %User{} = target) do
target_id: target.id target_id: target.id
}) })
|> Repo.insert( |> Repo.insert(
on_conflict: :replace_all_except_primary_key, on_conflict: {:replace_all_except, [:id]},
conflict_target: [:source_id, :relationship_type, :target_id] conflict_target: [:source_id, :relationship_type, :target_id]
) )
end end

View file

@ -728,7 +728,6 @@ def fetch_user_abstract_activities(user, reading_user, params \\ %{}) do
params params
|> Map.put("user", reading_user) |> Map.put("user", reading_user)
|> Map.put("actor_id", user.ap_id) |> Map.put("actor_id", user.ap_id)
|> Map.put("whole_db", true)
recipients = recipients =
user_activities_recipients(%{ user_activities_recipients(%{
@ -746,7 +745,6 @@ def fetch_user_activities(user, reading_user, params \\ %{}) do
|> Map.put("type", ["Create", "Announce"]) |> Map.put("type", ["Create", "Announce"])
|> Map.put("user", reading_user) |> Map.put("user", reading_user)
|> Map.put("actor_id", user.ap_id) |> Map.put("actor_id", user.ap_id)
|> Map.put("whole_db", true)
|> Map.put("pinned_activity_ids", user.pinned_activities) |> Map.put("pinned_activity_ids", user.pinned_activities)
params = params =
@ -773,7 +771,6 @@ def fetch_instance_activities(params) do
params params
|> Map.put("type", ["Create", "Announce"]) |> Map.put("type", ["Create", "Announce"])
|> Map.put("instance", params["instance"]) |> Map.put("instance", params["instance"])
|> Map.put("whole_db", true)
fetch_activities([Pleroma.Constants.as_public()], params, :offset) fetch_activities([Pleroma.Constants.as_public()], params, :offset)
|> Enum.reverse() |> Enum.reverse()

View file

@ -337,7 +337,7 @@ def add_emoji_reaction_to_object(
%Activity{data: %{"content" => emoji, "actor" => actor}}, %Activity{data: %{"content" => emoji, "actor" => actor}},
object object
) do ) do
reactions = object.data["reactions"] || [] reactions = get_cached_emoji_reactions(object)
new_reactions = new_reactions =
case Enum.find_index(reactions, fn [candidate, _] -> emoji == candidate end) do case Enum.find_index(reactions, fn [candidate, _] -> emoji == candidate end) do
@ -365,7 +365,7 @@ def remove_emoji_reaction_from_object(
%Activity{data: %{"content" => emoji, "actor" => actor}}, %Activity{data: %{"content" => emoji, "actor" => actor}},
object object
) do ) do
reactions = object.data["reactions"] || [] reactions = get_cached_emoji_reactions(object)
new_reactions = new_reactions =
case Enum.find_index(reactions, fn [candidate, _] -> emoji == candidate end) do case Enum.find_index(reactions, fn [candidate, _] -> emoji == candidate end) do
@ -385,6 +385,14 @@ def remove_emoji_reaction_from_object(
update_element_in_object("reaction", new_reactions, object, count) update_element_in_object("reaction", new_reactions, object, count)
end end
def get_cached_emoji_reactions(object) do
if is_list(object.data["reactions"]) do
object.data["reactions"]
else
[]
end
end
@spec add_like_to_object(Activity.t(), Object.t()) :: @spec add_like_to_object(Activity.t(), Object.t()) ::
{:ok, Object.t()} | {:error, Ecto.Changeset.t()} {:ok, Object.t()} | {:error, Ecto.Changeset.t()}
def add_like_to_object(%Activity{data: %{"actor" => actor}}, object) do def add_like_to_object(%Activity{data: %{"actor" => actor}}, object) do

View file

@ -76,8 +76,7 @@ def assign_account_by_id(%{params: %{"id" => id}} = conn, _) do
end end
end end
def try_render(conn, target, params) def try_render(conn, target, params) when is_binary(target) do
when is_binary(target) do
case render(conn, target, params) do case render(conn, target, params) do
nil -> render_error(conn, :not_implemented, "Can't display this activity") nil -> render_error(conn, :not_implemented, "Can't display this activity")
res -> res res -> res
@ -87,4 +86,8 @@ def try_render(conn, target, params)
def try_render(conn, _, _) do def try_render(conn, _, _) do
render_error(conn, :not_implemented, "Can't display this activity") render_error(conn, :not_implemented, "Can't display this activity")
end end
@spec put_in_if_exist(map(), atom() | String.t(), any) :: map()
def put_in_if_exist(map, _key, nil), do: map
def put_in_if_exist(map, key, value), do: put_in(map, key, value)
end end

View file

@ -13,21 +13,53 @@ defmodule Pleroma.Web.Feed.FeedView do
require Pleroma.Constants require Pleroma.Constants
def prepare_activity(activity) do @spec pub_date(String.t() | DateTime.t()) :: String.t()
def pub_date(date) when is_binary(date) do
date
|> Timex.parse!("{ISO:Extended}")
|> pub_date
end
def pub_date(%DateTime{} = date), do: Timex.format!(date, "{RFC822}")
def prepare_activity(activity, opts \\ []) do
object = activity_object(activity) object = activity_object(activity)
actor =
if opts[:actor] do
Pleroma.User.get_cached_by_ap_id(activity.actor)
end
%{ %{
activity: activity, activity: activity,
data: Map.get(object, :data), data: Map.get(object, :data),
object: object object: object,
actor: actor
} }
end end
def most_recent_update(activities) do
with %{updated_at: updated_at} <- List.first(activities) do
NaiveDateTime.to_iso8601(updated_at)
end
end
def most_recent_update(activities, user) do def most_recent_update(activities, user) do
(List.first(activities) || user).updated_at (List.first(activities) || user).updated_at
|> NaiveDateTime.to_iso8601() |> NaiveDateTime.to_iso8601()
end end
def feed_logo do
case Pleroma.Config.get([:feed, :logo]) do
nil ->
"#{Pleroma.Web.base_url()}/static/logo.png"
logo ->
"#{Pleroma.Web.base_url()}#{logo}"
end
|> MediaProxy.url()
end
def logo(user) do def logo(user) do
user user
|> User.avatar_url() |> User.avatar_url()
@ -40,6 +72,8 @@ def activity_object(activity), do: Object.normalize(activity)
def activity_title(%{data: %{"content" => content}}, opts \\ %{}) do def activity_title(%{data: %{"content" => content}}, opts \\ %{}) do
content content
|> Pleroma.Web.Metadata.Utils.scrub_html()
|> Pleroma.Emoji.Formatter.demojify()
|> Formatter.truncate(opts[:max_length], opts[:omission]) |> Formatter.truncate(opts[:max_length], opts[:omission])
|> escape() |> escape()
end end
@ -50,6 +84,8 @@ def activity_content(%{data: %{"content" => content}}) do
|> escape() |> escape()
end end
def activity_content(_), do: ""
def activity_context(activity), do: activity.data["context"] def activity_context(activity), do: activity.data["context"]
def attachment_href(attachment) do def attachment_href(attachment) do

View file

@ -0,0 +1,41 @@
# Pleroma: A lightweight social networking server
# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.Feed.TagController do
use Pleroma.Web, :controller
alias Pleroma.Config
alias Pleroma.Web.ActivityPub.ActivityPub
alias Pleroma.Web.Feed.FeedView
import Pleroma.Web.ControllerHelper, only: [put_in_if_exist: 3]
def feed(conn, %{"tag" => raw_tag} = params) do
{format, tag} = parse_tag(raw_tag)
activities =
%{"type" => ["Create"], "tag" => tag}
|> put_in_if_exist("max_id", params["max_id"])
|> ActivityPub.fetch_public_activities()
conn
|> put_resp_content_type("application/atom+xml")
|> put_view(FeedView)
|> render("tag.#{format}",
activities: activities,
tag: tag,
feed_config: Config.get([:feed])
)
end
@spec parse_tag(binary() | any()) :: {format :: String.t(), tag :: String.t()}
defp parse_tag(raw_tag) when is_binary(raw_tag) do
case Enum.reverse(String.split(raw_tag, ".")) do
[format | tag] when format in ["atom", "rss"] -> {format, Enum.join(tag, ".")}
_ -> {"rss", raw_tag}
end
end
defp parse_tag(raw_tag), do: {"rss", raw_tag}
end

View file

@ -2,13 +2,16 @@
# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/> # Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only # SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.Feed.FeedController do defmodule Pleroma.Web.Feed.UserController do
use Pleroma.Web, :controller use Pleroma.Web, :controller
alias Fallback.RedirectController alias Fallback.RedirectController
alias Pleroma.User alias Pleroma.User
alias Pleroma.Web.ActivityPub.ActivityPub alias Pleroma.Web.ActivityPub.ActivityPub
alias Pleroma.Web.ActivityPub.ActivityPubController alias Pleroma.Web.ActivityPub.ActivityPubController
alias Pleroma.Web.Feed.FeedView
import Pleroma.Web.ControllerHelper, only: [put_in_if_exist: 3]
plug(Pleroma.Plugs.SetFormatPlug when action in [:feed_redirect]) plug(Pleroma.Plugs.SetFormatPlug when action in [:feed_redirect])
@ -27,7 +30,7 @@ def feed_redirect(%{assigns: %{format: format}} = conn, _params)
def feed_redirect(conn, %{"nickname" => nickname}) do def feed_redirect(conn, %{"nickname" => nickname}) do
with {_, %User{} = user} <- {:fetch_user, User.get_cached_by_nickname(nickname)} do with {_, %User{} = user} <- {:fetch_user, User.get_cached_by_nickname(nickname)} do
redirect(conn, external: "#{feed_url(conn, :feed, user.nickname)}.atom") redirect(conn, external: "#{user_feed_url(conn, :feed, user.nickname)}.atom")
end end
end end
@ -36,15 +39,15 @@ def feed(conn, %{"nickname" => nickname} = params) do
activities = activities =
%{ %{
"type" => ["Create"], "type" => ["Create"],
"whole_db" => true,
"actor_id" => user.ap_id "actor_id" => user.ap_id
} }
|> Map.merge(Map.take(params, ["max_id"])) |> put_in_if_exist("max_id", params["max_id"])
|> ActivityPub.fetch_public_activities() |> ActivityPub.fetch_public_activities()
conn conn
|> put_resp_content_type("application/atom+xml") |> put_resp_content_type("application/atom+xml")
|> render("feed.xml", |> put_view(FeedView)
|> render("user.xml",
user: user, user: user,
activities: activities, activities: activities,
feed_config: Pleroma.Config.get([:feed]) feed_config: Pleroma.Config.get([:feed])

View file

@ -7,62 +7,8 @@ defmodule Pleroma.Web.MastodonAPI.SuggestionController do
require Logger require Logger
alias Pleroma.Config
alias Pleroma.Plugs.OAuthScopesPlug
alias Pleroma.User
alias Pleroma.Web.MediaProxy
action_fallback(Pleroma.Web.MastodonAPI.FallbackController)
plug(OAuthScopesPlug, %{scopes: ["read"]} when action == :index)
plug(Pleroma.Plugs.EnsurePublicOrAuthenticatedPlug)
@doc "GET /api/v1/suggestions" @doc "GET /api/v1/suggestions"
def index(%{assigns: %{user: user}} = conn, _) do def index(conn, _) do
if Config.get([:suggestions, :enabled], false) do json(conn, [])
with {:ok, data} <- fetch_suggestions(user) do
limit = Config.get([:suggestions, :limit], 23)
data =
data
|> Enum.slice(0, limit)
|> Enum.map(fn x ->
x
|> Map.put("id", fetch_suggestion_id(x))
|> Map.put("avatar", MediaProxy.url(x["avatar"]))
|> Map.put("avatar_static", MediaProxy.url(x["avatar_static"]))
end)
json(conn, data)
end
else
json(conn, [])
end
end
defp fetch_suggestions(user) do
api = Config.get([:suggestions, :third_party_engine], "")
timeout = Config.get([:suggestions, :timeout], 5000)
host = Config.get([Pleroma.Web.Endpoint, :url, :host])
url =
api
|> String.replace("{{host}}", host)
|> String.replace("{{user}}", user.nickname)
with {:ok, %{status: 200, body: body}} <-
Pleroma.HTTP.get(url, [], adapter: [recv_timeout: timeout, pool: :default]) do
Jason.decode(body)
else
e -> Logger.error("Could not retrieve suggestions at fetch #{url}, #{inspect(e)}")
end
end
defp fetch_suggestion_id(attrs) do
case User.get_or_fetch(attrs["acct"]) do
{:ok, %User{id: id}} -> id
_ -> 0
end
end end
end end

View file

@ -7,10 +7,6 @@ defmodule Pleroma.Web.MastodonAPI.AppView do
alias Pleroma.Web.OAuth.App alias Pleroma.Web.OAuth.App
@vapid_key :web_push_encryption
|> Application.get_env(:vapid_details, [])
|> Keyword.get(:public_key)
def render("show.json", %{app: %App{} = app}) do def render("show.json", %{app: %App{} = app}) do
%{ %{
id: app.id |> to_string, id: app.id |> to_string,
@ -32,8 +28,10 @@ def render("short.json", %{app: %App{website: webiste, client_name: name}}) do
end end
defp with_vapid_key(data) do defp with_vapid_key(data) do
if @vapid_key do vapid_key = Application.get_env(:web_push_encryption, :vapid_details, [])[:public_key]
Map.put(data, "vapid_key", @vapid_key)
if vapid_key do
Map.put(data, "vapid_key", vapid_key)
else else
data data
end end

View file

@ -256,7 +256,7 @@ def render("show.json", %{activity: %{data: %{"object" => _object}} = activity}
emoji_reactions = emoji_reactions =
with %{data: %{"reactions" => emoji_reactions}} <- object do with %{data: %{"reactions" => emoji_reactions}} <- object do
Enum.map(emoji_reactions, fn [emoji, users] -> Enum.map(emoji_reactions, fn [emoji, users] ->
[emoji, length(users)] %{emoji: emoji, count: length(users)}
end) end)
else else
_ -> [] _ -> []

View file

@ -16,7 +16,7 @@ def build_tags(%{user: user}) do
[ [
rel: "alternate", rel: "alternate",
type: "application/atom+xml", type: "application/atom+xml",
href: Helpers.feed_path(Endpoint, :feed, user.nickname) <> ".atom" href: Helpers.user_feed_path(Endpoint, :feed, user.nickname) <> ".atom"
], []} ], []}
] ]
end end

View file

@ -21,15 +21,22 @@ def scrub_html_and_truncate(%{data: %{"content" => content}} = object) do
def scrub_html_and_truncate(content, max_length \\ 200) when is_binary(content) do def scrub_html_and_truncate(content, max_length \\ 200) when is_binary(content) do
content content
# html content comes from DB already encoded, decode first and scrub after |> scrub_html
|> HtmlEntities.decode()
|> String.replace(~r/<br\s?\/?>/, " ")
|> HTML.strip_tags()
|> Emoji.Formatter.demojify() |> Emoji.Formatter.demojify()
|> HtmlEntities.decode() |> HtmlEntities.decode()
|> Formatter.truncate(max_length) |> Formatter.truncate(max_length)
end end
def scrub_html(content) when is_binary(content) do
content
# html content comes from DB already encoded, decode first and scrub after
|> HtmlEntities.decode()
|> String.replace(~r/<br\s?\/?>/, " ")
|> HTML.strip_tags()
end
def scrub_html(content), do: content
def attachment_url(url) do def attachment_url(url) do
MediaProxy.url(url) MediaProxy.url(url)
end end

View file

@ -73,9 +73,6 @@ def raw_nodeinfo do
if Config.get([:chat, :enabled]) do if Config.get([:chat, :enabled]) do
"chat" "chat"
end, end,
if Config.get([:suggestions, :enabled]) do
"suggestions"
end,
if Config.get([:instance, :allow_relay]) do if Config.get([:instance, :allow_relay]) do
"relay" "relay"
end, end,
@ -108,11 +105,7 @@ def raw_nodeinfo do
nodeDescription: Config.get([:instance, :description]), nodeDescription: Config.get([:instance, :description]),
private: !Config.get([:instance, :public], true), private: !Config.get([:instance, :public], true),
suggestions: %{ suggestions: %{
enabled: Config.get([:suggestions, :enabled], false), enabled: false
thirdPartyEngine: Config.get([:suggestions, :third_party_engine], ""),
timeout: Config.get([:suggestions, :timeout], 5000),
limit: Config.get([:suggestions, :limit], 23),
web: Config.get([:suggestions, :web], "")
}, },
staffAccounts: staff_accounts, staffAccounts: staff_accounts,
federation: federation_response, federation: federation_response,

View file

@ -167,17 +167,37 @@ defp handle_create_authorization_error(
defp handle_create_authorization_error( defp handle_create_authorization_error(
%Plug.Conn{} = conn, %Plug.Conn{} = conn,
{:auth_active, false}, {:account_status, :confirmation_pending},
%{"authorization" => _} = params %{"authorization" => _} = params
) do ) do
# Per https://github.com/tootsuite/mastodon/blob/
# 51e154f5e87968d6bb115e053689767ab33e80cd/app/controllers/api/base_controller.rb#L76
conn conn
|> put_flash(:error, dgettext("errors", "Your login is missing a confirmed e-mail address")) |> put_flash(:error, dgettext("errors", "Your login is missing a confirmed e-mail address"))
|> put_status(:forbidden) |> put_status(:forbidden)
|> authorize(params) |> authorize(params)
end end
defp handle_create_authorization_error(
%Plug.Conn{} = conn,
{:account_status, :password_reset_pending},
%{"authorization" => _} = params
) do
conn
|> put_flash(:error, dgettext("errors", "Password reset is required"))
|> put_status(:forbidden)
|> authorize(params)
end
defp handle_create_authorization_error(
%Plug.Conn{} = conn,
{:account_status, :deactivated},
%{"authorization" => _} = params
) do
conn
|> put_flash(:error, dgettext("errors", "Your account is currently disabled"))
|> put_status(:forbidden)
|> authorize(params)
end
defp handle_create_authorization_error(%Plug.Conn{} = conn, error, %{"authorization" => _}) do defp handle_create_authorization_error(%Plug.Conn{} = conn, error, %{"authorization" => _}) do
Authenticator.handle_error(conn, error) Authenticator.handle_error(conn, error)
end end
@ -218,46 +238,14 @@ def token_exchange(
) do ) do
with {:ok, %User{} = user} <- Authenticator.get_user(conn), with {:ok, %User{} = user} <- Authenticator.get_user(conn),
{:ok, app} <- Token.Utils.fetch_app(conn), {:ok, app} <- Token.Utils.fetch_app(conn),
{:auth_active, true} <- {:auth_active, User.auth_active?(user)}, {:account_status, :active} <- {:account_status, User.account_status(user)},
{:user_active, true} <- {:user_active, !user.deactivated},
{:password_reset_pending, false} <-
{:password_reset_pending, user.password_reset_pending},
{:ok, scopes} <- validate_scopes(app, params), {:ok, scopes} <- validate_scopes(app, params),
{:ok, auth} <- Authorization.create_authorization(app, user, scopes), {:ok, auth} <- Authorization.create_authorization(app, user, scopes),
{:ok, token} <- Token.exchange_token(app, auth) do {:ok, token} <- Token.exchange_token(app, auth) do
json(conn, Token.Response.build(user, token)) json(conn, Token.Response.build(user, token))
else else
{:auth_active, false} -> error ->
# Per https://github.com/tootsuite/mastodon/blob/ handle_token_exchange_error(conn, error)
# 51e154f5e87968d6bb115e053689767ab33e80cd/app/controllers/api/base_controller.rb#L76
render_error(
conn,
:forbidden,
"Your login is missing a confirmed e-mail address",
%{},
"missing_confirmed_email"
)
{:user_active, false} ->
render_error(
conn,
:forbidden,
"Your account is currently disabled",
%{},
"account_is_disabled"
)
{:password_reset_pending, true} ->
render_error(
conn,
:forbidden,
"Password reset is required",
%{},
"password_reset_required"
)
_error ->
render_invalid_credentials_error(conn)
end end
end end
@ -286,6 +274,43 @@ def token_exchange(%Plug.Conn{} = conn, %{"grant_type" => "client_credentials"}
# Bad request # Bad request
def token_exchange(%Plug.Conn{} = conn, params), do: bad_request(conn, params) def token_exchange(%Plug.Conn{} = conn, params), do: bad_request(conn, params)
defp handle_token_exchange_error(%Plug.Conn{} = conn, {:account_status, :deactivated}) do
render_error(
conn,
:forbidden,
"Your account is currently disabled",
%{},
"account_is_disabled"
)
end
defp handle_token_exchange_error(
%Plug.Conn{} = conn,
{:account_status, :password_reset_pending}
) do
render_error(
conn,
:forbidden,
"Password reset is required",
%{},
"password_reset_required"
)
end
defp handle_token_exchange_error(%Plug.Conn{} = conn, {:account_status, :confirmation_pending}) do
render_error(
conn,
:forbidden,
"Your login is missing a confirmed e-mail address",
%{},
"missing_confirmed_email"
)
end
defp handle_token_exchange_error(%Plug.Conn{} = conn, _error) do
render_invalid_credentials_error(conn)
end
def token_revoke(%Plug.Conn{} = conn, %{"token" => _token} = params) do def token_revoke(%Plug.Conn{} = conn, %{"token" => _token} = params) do
with {:ok, app} <- Token.Utils.fetch_app(conn), with {:ok, app} <- Token.Utils.fetch_app(conn),
{:ok, _token} <- RevokeToken.revoke(app, params) do {:ok, _token} <- RevokeToken.revoke(app, params) do
@ -472,7 +497,7 @@ defp do_create_authorization(
%App{} = app <- Repo.get_by(App, client_id: client_id), %App{} = app <- Repo.get_by(App, client_id: client_id),
true <- redirect_uri in String.split(app.redirect_uris), true <- redirect_uri in String.split(app.redirect_uris),
{:ok, scopes} <- validate_scopes(app, auth_attrs), {:ok, scopes} <- validate_scopes(app, auth_attrs),
{:auth_active, true} <- {:auth_active, User.auth_active?(user)} do {:account_status, :active} <- {:account_status, User.account_status(user)} do
Authorization.create_authorization(app, user, scopes) Authorization.create_authorization(app, user, scopes)
end end
end end

View file

@ -49,7 +49,12 @@ def emoji_reactions_by(%{assigns: %{user: user}} = conn, %{"id" => activity_id})
emoji_reactions emoji_reactions
|> Enum.map(fn [emoji, users] -> |> Enum.map(fn [emoji, users] ->
users = Enum.map(users, &User.get_cached_by_ap_id/1) users = Enum.map(users, &User.get_cached_by_ap_id/1)
{emoji, AccountView.render("index.json", %{users: users, for: user, as: :user})}
%{
emoji: emoji,
count: length(users),
accounts: AccountView.render("index.json", %{users: users, for: user, as: :user})
}
end) end)
conn conn

View file

@ -48,6 +48,6 @@ defp maybe_put_title(meta, html) when meta != %{} do
defp maybe_put_title(meta, _), do: meta defp maybe_put_title(meta, _), do: meta
defp get_page_title(html) do defp get_page_title(html) do
Floki.find(html, "title") |> Floki.text() Floki.find(html, "html head title") |> List.first() |> Floki.text()
end end
end end

View file

@ -527,8 +527,10 @@ defmodule Pleroma.Web.Router do
get("/notice/:id", OStatus.OStatusController, :notice) get("/notice/:id", OStatus.OStatusController, :notice)
get("/notice/:id/embed_player", OStatus.OStatusController, :notice_player) get("/notice/:id/embed_player", OStatus.OStatusController, :notice_player)
get("/users/:nickname/feed", Feed.FeedController, :feed) get("/users/:nickname/feed", Feed.UserController, :feed, as: :user_feed)
get("/users/:nickname", Feed.FeedController, :feed_redirect) get("/users/:nickname", Feed.UserController, :feed_redirect, as: :user_feed)
get("/tags/:tag", Feed.TagController, :feed, as: :tag_feed)
end end
scope "/", Pleroma.Web do scope "/", Pleroma.Web do

View file

@ -9,7 +9,7 @@
<ostatus:conversation ref="<%= activity_context(@activity) %>"> <ostatus:conversation ref="<%= activity_context(@activity) %>">
<%= activity_context(@activity) %> <%= activity_context(@activity) %>
</ostatus:conversation> </ostatus:conversation>
<link ref="<%= activity_context(@activity) %>" rel="ostatus:conversation"/> <link href="<%= activity_context(@activity) %>" rel="ostatus:conversation"/>
<%= if @data["summary"] do %> <%= if @data["summary"] do %>
<summary><%= @data["summary"] %></summary> <summary><%= @data["summary"] %></summary>

View file

@ -0,0 +1,51 @@
<entry>
<activity:object-type>http://activitystrea.ms/schema/1.0/note</activity:object-type>
<activity:verb>http://activitystrea.ms/schema/1.0/post</activity:verb>
<%= render @view_module, "_tag_author.atom", assigns %>
<id><%= @data["id"] %></id>
<title><%= activity_title(@object, Keyword.get(@feed_config, :post_title, %{})) %></title>
<content type="html"><%= activity_content(@object) %></content>
<%= if @activity.local do %>
<link type="application/atom+xml" href='<%= @data["id"] %>' rel="self"/>
<link type="text/html" href='<%= @data["id"] %>' rel="alternate"/>
<% else %>
<link type="text/html" href='<%= @data["external_url"] %>' rel="alternate"/>
<% end %>
<published><%= @data["published"] %></published>
<updated><%= @data["published"] %></updated>
<ostatus:conversation ref="<%= activity_context(@activity) %>">
<%= activity_context(@activity) %>
</ostatus:conversation>
<link href="<%= activity_context(@activity) %>" rel="ostatus:conversation"/>
<%= if @data["summary"] do %>
<summary><%= @data["summary"] %></summary>
<% end %>
<%= for id <- @activity.recipients do %>
<%= if id == Pleroma.Constants.as_public() do %>
<link rel="mentioned"
ostatus:object-type="http://activitystrea.ms/schema/1.0/collection"
href="http://activityschema.org/collection/public"/>
<% else %>
<%= unless Regex.match?(~r/^#{Pleroma.Web.base_url()}.+followers$/, id) do %>
<link rel="mentioned"
ostatus:object-type="http://activitystrea.ms/schema/1.0/person"
href="<%= id %>" />
<% end %>
<% end %>
<% end %>
<%= for tag <- @data["tag"] || [] do %>
<category term="<%= tag %>"></category>
<% end %>
<%= for {emoji, file} <- @data["emoji"] || %{} do %>
<link name="<%= emoji %>" rel="emoji" href="<%= file %>"/>
<% end %>
</entry>

View file

@ -0,0 +1,15 @@
<item>
<title><%= activity_title(@object, Keyword.get(@feed_config, :post_title, %{})) %></title>
<guid isPermalink="true"><%= activity_context(@activity) %></guid>
<link><%= activity_context(@activity) %></link>
<pubDate><%= pub_date(@data["published"]) %></pubDate>
<description><%= activity_content(@object) %></description>
<%= for attachment <- @data["attachment"] || [] do %>
<enclosure url="<%= attachment_href(attachment) %>" type="<%= attachment_type(attachment) %>"/>
<% end %>
</item>

View file

@ -0,0 +1,18 @@
<author>
<activity:object-type>http://activitystrea.ms/schema/1.0/person</activity:object-type>
<id><%= @actor.ap_id %></id>
<uri><%= @actor.ap_id %></uri>
<name><%= @actor.nickname %></name>
<summary><%= escape(@actor.bio) %></summary>
<link rel="avatar" href="<%= User.avatar_url(@actor) %>"/>
<%= if User.banner_url(@actor) do %>
<link rel="header" href="<%= User.banner_url(@actor) %>"/>
<% end %>
<%= if @actor.local do %>
<ap_enabled>true</ap_enabled>
<% end %>
<poco:preferredUsername><%= @actor.nickname %></poco:preferredUsername>
<poco:displayName><%= @actor.name %></poco:displayName>
<poco:note><%= escape(@actor.bio) %></poco:note>
</author>

View file

@ -0,0 +1,22 @@
<?xml version="1.0" encoding="UTF-8"?>
<feed xml:lang="en-US" xmlns="http://www.w3.org/2005/Atom"
xmlns:thr="http://purl.org/syndication/thread/1.0"
xmlns:georss="http://www.georss.org/georss"
xmlns:activity="http://activitystrea.ms/spec/1.0/"
xmlns:media="http://purl.org/syndication/atommedia"
xmlns:poco="http://portablecontacts.net/spec/1.0"
xmlns:ostatus="http://ostatus.org/schema/1.0"
xmlns:statusnet="http://status.net/schema/api/1/">
<id><%= '#{tag_feed_url(@conn, :feed, @tag)}.rss' %></id>
<title>#<%= @tag %></title>
<subtitle>These are public toots tagged with #<%= @tag %>. You can interact with them if you have an account anywhere in the fediverse.</subtitle>
<logo><%= feed_logo() %></logo>
<updated><%= most_recent_update(@activities) %></updated>
<link rel="self" href="<%= '#{tag_feed_url(@conn, :feed, @tag)}.atom' %>" type="application/atom+xml"/>
<%= for activity <- @activities do %>
<%= render @view_module, "_tag_activity.atom", Map.merge(assigns, prepare_activity(activity, actor: true)) %>
<% end %>
</feed>

View file

@ -0,0 +1,15 @@
<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:webfeeds="http://webfeeds.org/rss/1.0">
<channel>
<title>#<%= @tag %></title>
<description>These are public toots tagged with #<%= @tag %>. You can interact with them if you have an account anywhere in the fediverse.</description>
<link><%= '#{tag_feed_url(@conn, :feed, @tag)}.rss' %></link>
<webfeeds:logo><%= feed_logo() %></webfeeds:logo>
<webfeeds:accentColor>2b90d9</webfeeds:accentColor>
<%= for activity <- @activities do %>
<%= render @view_module, "_tag_activity.xml", Map.merge(assigns, prepare_activity(activity)) %>
<% end %>
</channel>
</rss>

View file

@ -6,16 +6,16 @@
xmlns:poco="http://portablecontacts.net/spec/1.0" xmlns:poco="http://portablecontacts.net/spec/1.0"
xmlns:ostatus="http://ostatus.org/schema/1.0"> xmlns:ostatus="http://ostatus.org/schema/1.0">
<id><%= feed_url(@conn, :feed, @user.nickname) <> ".atom" %></id> <id><%= user_feed_url(@conn, :feed, @user.nickname) <> ".atom" %></id>
<title><%= @user.nickname <> "'s timeline" %></title> <title><%= @user.nickname <> "'s timeline" %></title>
<updated><%= most_recent_update(@activities, @user) %></updated> <updated><%= most_recent_update(@activities, @user) %></updated>
<logo><%= logo(@user) %></logo> <logo><%= logo(@user) %></logo>
<link rel="self" href="<%= '#{feed_url(@conn, :feed, @user.nickname)}.atom' %>" type="application/atom+xml"/> <link rel="self" href="<%= '#{user_feed_url(@conn, :feed, @user.nickname)}.atom' %>" type="application/atom+xml"/>
<%= render @view_module, "_author.xml", assigns %> <%= render @view_module, "_author.xml", assigns %>
<%= if last_activity(@activities) do %> <%= if last_activity(@activities) do %>
<link rel="next" href="<%= '#{feed_url(@conn, :feed, @user.nickname)}.atom?max_id=#{last_activity(@activities).id}' %>" type="application/atom+xml"/> <link rel="next" href="<%= '#{user_feed_url(@conn, :feed, @user.nickname)}.atom?max_id=#{last_activity(@activities).id}' %>" type="application/atom+xml"/>
<% end %> <% end %>
<%= for activity <- @activities do %> <%= for activity <- @activities do %>

View file

@ -12,7 +12,10 @@ defmodule Pleroma.Workers.AttachmentsCleanupWorker do
@impl Oban.Worker @impl Oban.Worker
def perform( def perform(
%{"object" => %{"data" => %{"attachment" => [_ | _] = attachments, "actor" => actor}}}, %{
"op" => "cleanup_attachments",
"object" => %{"data" => %{"attachment" => [_ | _] = attachments, "actor" => actor}}
},
_job _job
) do ) do
hrefs = hrefs =
@ -37,7 +40,7 @@ def perform(
) )
# The query above can be time consumptive on large instances until we # The query above can be time consumptive on large instances until we
# refactor how uploads are stored # refactor how uploads are stored
|> Repo.all(timout: :infinity) |> Repo.all(timeout: :infinity)
# we should delete 1 object for any given attachment, but don't delete # we should delete 1 object for any given attachment, but don't delete
# files if there are more than 1 object for it # files if there are more than 1 object for it
|> Enum.reduce(%{}, fn %{ |> Enum.reduce(%{}, fn %{
@ -70,7 +73,11 @@ def perform(
_ -> "" _ -> ""
end end
base_url = Pleroma.Config.get([__MODULE__, :base_url], Pleroma.Web.base_url()) base_url =
String.trim_trailing(
Pleroma.Config.get([Pleroma.Upload, :base_url], Pleroma.Web.base_url()),
"/"
)
file_path = String.trim_leading(href, "#{base_url}/#{prefix}") file_path = String.trim_leading(href, "#{base_url}/#{prefix}")
@ -84,5 +91,5 @@ def perform(
|> Repo.delete_all() |> Repo.delete_all()
end end
def perform(%{"object" => _object}, _job), do: :ok def perform(%{"op" => "cleanup_attachments", "object" => _object}, _job), do: :ok
end end

View file

@ -101,7 +101,7 @@ defp deps do
{:phoenix_pubsub, "~> 1.1"}, {:phoenix_pubsub, "~> 1.1"},
{:phoenix_ecto, "~> 4.0"}, {:phoenix_ecto, "~> 4.0"},
{:ecto_enum, "~> 1.4"}, {:ecto_enum, "~> 1.4"},
{:ecto_sql, "~> 3.2"}, {:ecto_sql, "~> 3.3.2"},
{:postgrex, ">= 0.13.5"}, {:postgrex, ">= 0.13.5"},
{:oban, "~> 0.12.0"}, {:oban, "~> 0.12.0"},
{:quantum, "~> 2.3"}, {:quantum, "~> 2.3"},

View file

@ -20,13 +20,13 @@
"crontab": {:hex, :crontab, "1.1.8", "2ce0e74777dfcadb28a1debbea707e58b879e6aa0ffbf9c9bb540887bce43617", [:mix], [{:ecto, "~> 1.0 or ~> 2.0 or ~> 3.0", [hex: :ecto, repo: "hexpm", optional: true]}], "hexpm"}, "crontab": {:hex, :crontab, "1.1.8", "2ce0e74777dfcadb28a1debbea707e58b879e6aa0ffbf9c9bb540887bce43617", [:mix], [{:ecto, "~> 1.0 or ~> 2.0 or ~> 3.0", [hex: :ecto, repo: "hexpm", optional: true]}], "hexpm"},
"crypt": {:git, "https://github.com/msantos/crypt", "1f2b58927ab57e72910191a7ebaeff984382a1d3", [ref: "1f2b58927ab57e72910191a7ebaeff984382a1d3"]}, "crypt": {:git, "https://github.com/msantos/crypt", "1f2b58927ab57e72910191a7ebaeff984382a1d3", [ref: "1f2b58927ab57e72910191a7ebaeff984382a1d3"]},
"custom_base": {:hex, :custom_base, "0.2.1", "4a832a42ea0552299d81652aa0b1f775d462175293e99dfbe4d7dbaab785a706", [:mix], [], "hexpm"}, "custom_base": {:hex, :custom_base, "0.2.1", "4a832a42ea0552299d81652aa0b1f775d462175293e99dfbe4d7dbaab785a706", [:mix], [], "hexpm"},
"db_connection": {:hex, :db_connection, "2.1.1", "a51e8a2ee54ef2ae6ec41a668c85787ed40cb8944928c191280fe34c15b76ae5", [:mix], [{:connection, "~> 1.0.2", [hex: :connection, repo: "hexpm", optional: false]}], "hexpm"}, "db_connection": {:hex, :db_connection, "2.2.0", "e923e88887cd60f9891fd324ac5e0290954511d090553c415fbf54be4c57ee63", [:mix], [{:connection, "~> 1.0.2", [hex: :connection, repo: "hexpm", optional: false]}], "hexpm"},
"decimal": {:hex, :decimal, "1.8.0", "ca462e0d885f09a1c5a342dbd7c1dcf27ea63548c65a65e67334f4b61803822e", [:mix], [], "hexpm"}, "decimal": {:hex, :decimal, "1.8.1", "a4ef3f5f3428bdbc0d35374029ffcf4ede8533536fa79896dd450168d9acdf3c", [:mix], [], "hexpm"},
"deep_merge": {:hex, :deep_merge, "1.0.0", "b4aa1a0d1acac393bdf38b2291af38cb1d4a52806cf7a4906f718e1feb5ee961", [:mix], [], "hexpm"}, "deep_merge": {:hex, :deep_merge, "1.0.0", "b4aa1a0d1acac393bdf38b2291af38cb1d4a52806cf7a4906f718e1feb5ee961", [:mix], [], "hexpm"},
"earmark": {:hex, :earmark, "1.4.2", "3aa0bd23bc4c61cf2f1e5d752d1bb470560a6f8539974f767a38923bb20e1d7f", [:mix], [], "hexpm"}, "earmark": {:hex, :earmark, "1.4.2", "3aa0bd23bc4c61cf2f1e5d752d1bb470560a6f8539974f767a38923bb20e1d7f", [:mix], [], "hexpm"},
"ecto": {:hex, :ecto, "3.2.5", "76c864b77948a479e18e69cc1d0f0f4ee7cced1148ffe6a093ff91eba644f0b5", [:mix], [{:decimal, "~> 1.6", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}], "hexpm"}, "ecto": {:hex, :ecto, "3.3.1", "82ab74298065bf0c64ca299f6c6785e68ea5d6b980883ee80b044499df35aba1", [:mix], [{:decimal, "~> 1.6", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}], "hexpm"},
"ecto_enum": {:hex, :ecto_enum, "1.4.0", "d14b00e04b974afc69c251632d1e49594d899067ee2b376277efd8233027aec8", [:mix], [{:ecto, ">= 3.0.0", [hex: :ecto, repo: "hexpm", optional: false]}, {:ecto_sql, "> 3.0.0", [hex: :ecto_sql, repo: "hexpm", optional: false]}, {:mariaex, ">= 0.0.0", [hex: :mariaex, repo: "hexpm", optional: true]}, {:postgrex, ">= 0.0.0", [hex: :postgrex, repo: "hexpm", optional: true]}], "hexpm"}, "ecto_enum": {:hex, :ecto_enum, "1.4.0", "d14b00e04b974afc69c251632d1e49594d899067ee2b376277efd8233027aec8", [:mix], [{:ecto, ">= 3.0.0", [hex: :ecto, repo: "hexpm", optional: false]}, {:ecto_sql, "> 3.0.0", [hex: :ecto_sql, repo: "hexpm", optional: false]}, {:mariaex, ">= 0.0.0", [hex: :mariaex, repo: "hexpm", optional: true]}, {:postgrex, ">= 0.0.0", [hex: :postgrex, repo: "hexpm", optional: true]}], "hexpm"},
"ecto_sql": {:hex, :ecto_sql, "3.2.2", "d10845bc147b9f61ef485cbf0973c0a337237199bd9bd30dd9542db00aadc26b", [:mix], [{:db_connection, "~> 2.1", [hex: :db_connection, repo: "hexpm", optional: false]}, {:ecto, "~> 3.2.0", [hex: :ecto, repo: "hexpm", optional: false]}, {:myxql, "~> 0.2.0 or ~> 0.3.0", [hex: :myxql, repo: "hexpm", optional: true]}, {:postgrex, "~> 0.15.0", [hex: :postgrex, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm"}, "ecto_sql": {:hex, :ecto_sql, "3.3.2", "92804e0de69bb63e621273c3492252cb08a29475c05d40eeb6f41ad2d483cfd3", [:mix], [{:db_connection, "~> 2.2", [hex: :db_connection, repo: "hexpm", optional: false]}, {:ecto, "~> 3.3", [hex: :ecto, repo: "hexpm", optional: false]}, {:myxql, "~> 0.3.0", [hex: :myxql, repo: "hexpm", optional: true]}, {:postgrex, "~> 0.15.0", [hex: :postgrex, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm"},
"esshd": {:hex, :esshd, "0.1.0", "6f93a2062adb43637edad0ea7357db2702a4b80dd9683482fe00f5134e97f4c1", [:mix], [], "hexpm"}, "esshd": {:hex, :esshd, "0.1.0", "6f93a2062adb43637edad0ea7357db2702a4b80dd9683482fe00f5134e97f4c1", [:mix], [], "hexpm"},
"eternal": {:hex, :eternal, "1.2.1", "d5b6b2499ba876c57be2581b5b999ee9bdf861c647401066d3eeed111d096bc4", [:mix], [], "hexpm"}, "eternal": {:hex, :eternal, "1.2.1", "d5b6b2499ba876c57be2581b5b999ee9bdf861c647401066d3eeed111d096bc4", [:mix], [], "hexpm"},
"ex2ms": {:hex, :ex2ms, "1.5.0", "19e27f9212be9a96093fed8cdfbef0a2b56c21237196d26760f11dfcfae58e97", [:mix], [], "hexpm"}, "ex2ms": {:hex, :ex2ms, "1.5.0", "19e27f9212be9a96093fed8cdfbef0a2b56c21237196d26760f11dfcfae58e97", [:mix], [], "hexpm"},
@ -70,7 +70,7 @@
"myhtmlex": {:git, "https://git.pleroma.social/pleroma/myhtmlex.git", "ad0097e2f61d4953bfef20fb6abddf23b87111e6", [ref: "ad0097e2f61d4953bfef20fb6abddf23b87111e6", submodules: true]}, "myhtmlex": {:git, "https://git.pleroma.social/pleroma/myhtmlex.git", "ad0097e2f61d4953bfef20fb6abddf23b87111e6", [ref: "ad0097e2f61d4953bfef20fb6abddf23b87111e6", submodules: true]},
"nimble_parsec": {:hex, :nimble_parsec, "0.5.1", "c90796ecee0289dbb5ad16d3ad06f957b0cd1199769641c961cfe0b97db190e0", [:mix], [], "hexpm"}, "nimble_parsec": {:hex, :nimble_parsec, "0.5.1", "c90796ecee0289dbb5ad16d3ad06f957b0cd1199769641c961cfe0b97db190e0", [:mix], [], "hexpm"},
"nodex": {:git, "https://git.pleroma.social/pleroma/nodex", "cb6730f943cfc6aad674c92161be23a8411f15d1", [ref: "cb6730f943cfc6aad674c92161be23a8411f15d1"]}, "nodex": {:git, "https://git.pleroma.social/pleroma/nodex", "cb6730f943cfc6aad674c92161be23a8411f15d1", [ref: "cb6730f943cfc6aad674c92161be23a8411f15d1"]},
"oban": {:hex, :oban, "0.12.0", "5477d5ab4a5a201c0b6c89764040ebfc5d2c71c488a36f378016ce5990838f0f", [:mix], [{:ecto_sql, "~> 3.1", [hex: :ecto_sql, repo: "hexpm", optional: false]}, {:jason, "~> 1.1", [hex: :jason, repo: "hexpm", optional: false]}, {:postgrex, "~> 0.14", [hex: :postgrex, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm"}, "oban": {:hex, :oban, "0.12.1", "695e9490c6e0edfca616d80639528e448bd29b3bff7b7dd10a56c79b00a5d7fb", [:mix], [{:ecto_sql, "~> 3.1", [hex: :ecto_sql, repo: "hexpm", optional: false]}, {:jason, "~> 1.1", [hex: :jason, repo: "hexpm", optional: false]}, {:postgrex, "~> 0.14", [hex: :postgrex, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm"},
"parse_trans": {:hex, :parse_trans, "3.3.0", "09765507a3c7590a784615cfd421d101aec25098d50b89d7aa1d66646bc571c1", [:rebar3], [], "hexpm"}, "parse_trans": {:hex, :parse_trans, "3.3.0", "09765507a3c7590a784615cfd421d101aec25098d50b89d7aa1d66646bc571c1", [:rebar3], [], "hexpm"},
"pbkdf2_elixir": {:hex, :pbkdf2_elixir, "0.12.4", "8dd29ed783f2e12195d7e0a4640effc0a7c37e6537da491f1db01839eee6d053", [:mix], [], "hexpm"}, "pbkdf2_elixir": {:hex, :pbkdf2_elixir, "0.12.4", "8dd29ed783f2e12195d7e0a4640effc0a7c37e6537da491f1db01839eee6d053", [:mix], [], "hexpm"},
"phoenix": {:hex, :phoenix, "1.4.10", "619e4a545505f562cd294df52294372d012823f4fd9d34a6657a8b242898c255", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:phoenix_pubsub, "~> 1.1", [hex: :phoenix_pubsub, repo: "hexpm", optional: false]}, {:plug, "~> 1.8.1 or ~> 1.9", [hex: :plug, repo: "hexpm", optional: false]}, {:plug_cowboy, "~> 1.0 or ~> 2.0", [hex: :plug_cowboy, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm"}, "phoenix": {:hex, :phoenix, "1.4.10", "619e4a545505f562cd294df52294372d012823f4fd9d34a6657a8b242898c255", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:phoenix_pubsub, "~> 1.1", [hex: :phoenix_pubsub, repo: "hexpm", optional: false]}, {:plug, "~> 1.8.1 or ~> 1.9", [hex: :plug, repo: "hexpm", optional: false]}, {:plug_cowboy, "~> 1.0 or ~> 2.0", [hex: :plug_cowboy, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm"},
@ -84,7 +84,7 @@
"plug_static_index_html": {:hex, :plug_static_index_html, "1.0.0", "840123d4d3975585133485ea86af73cb2600afd7f2a976f9f5fd8b3808e636a0", [:mix], [{:plug, "~> 1.0", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm"}, "plug_static_index_html": {:hex, :plug_static_index_html, "1.0.0", "840123d4d3975585133485ea86af73cb2600afd7f2a976f9f5fd8b3808e636a0", [:mix], [{:plug, "~> 1.0", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm"},
"poison": {:hex, :poison, "3.1.0", "d9eb636610e096f86f25d9a46f35a9facac35609a7591b3be3326e99a0484665", [:mix], [], "hexpm"}, "poison": {:hex, :poison, "3.1.0", "d9eb636610e096f86f25d9a46f35a9facac35609a7591b3be3326e99a0484665", [:mix], [], "hexpm"},
"poolboy": {:hex, :poolboy, "1.5.2", "392b007a1693a64540cead79830443abf5762f5d30cf50bc95cb2c1aaafa006b", [:rebar3], [], "hexpm"}, "poolboy": {:hex, :poolboy, "1.5.2", "392b007a1693a64540cead79830443abf5762f5d30cf50bc95cb2c1aaafa006b", [:rebar3], [], "hexpm"},
"postgrex": {:hex, :postgrex, "0.15.1", "23ce3417de70f4c0e9e7419ad85bdabcc6860a6925fe2c6f3b1b5b1e8e47bf2f", [:mix], [{:connection, "~> 1.0", [hex: :connection, repo: "hexpm", optional: false]}, {:db_connection, "~> 2.1", [hex: :db_connection, repo: "hexpm", optional: false]}, {:decimal, "~> 1.5", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}], "hexpm"}, "postgrex": {:hex, :postgrex, "0.15.3", "5806baa8a19a68c4d07c7a624ccdb9b57e89cbc573f1b98099e3741214746ae4", [:mix], [{:connection, "~> 1.0", [hex: :connection, repo: "hexpm", optional: false]}, {:db_connection, "~> 2.1", [hex: :db_connection, repo: "hexpm", optional: false]}, {:decimal, "~> 1.5", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}], "hexpm"},
"prometheus": {:hex, :prometheus, "4.4.1", "1e96073b3ed7788053768fea779cbc896ddc3bdd9ba60687f2ad50b252ac87d6", [:mix, :rebar3], [], "hexpm"}, "prometheus": {:hex, :prometheus, "4.4.1", "1e96073b3ed7788053768fea779cbc896ddc3bdd9ba60687f2ad50b252ac87d6", [:mix, :rebar3], [], "hexpm"},
"prometheus_ecto": {:hex, :prometheus_ecto, "1.4.3", "3dd4da1812b8e0dbee81ea58bb3b62ed7588f2eae0c9e97e434c46807ff82311", [:mix], [{:ecto, "~> 2.0 or ~> 3.0", [hex: :ecto, repo: "hexpm", optional: false]}, {:prometheus_ex, "~> 1.1 or ~> 2.0 or ~> 3.0", [hex: :prometheus_ex, repo: "hexpm", optional: false]}], "hexpm"}, "prometheus_ecto": {:hex, :prometheus_ecto, "1.4.3", "3dd4da1812b8e0dbee81ea58bb3b62ed7588f2eae0c9e97e434c46807ff82311", [:mix], [{:ecto, "~> 2.0 or ~> 3.0", [hex: :ecto, repo: "hexpm", optional: false]}, {:prometheus_ex, "~> 1.1 or ~> 2.0 or ~> 3.0", [hex: :prometheus_ex, repo: "hexpm", optional: false]}], "hexpm"},
"prometheus_ex": {:hex, :prometheus_ex, "3.0.5", "fa58cfd983487fc5ead331e9a3e0aa622c67232b3ec71710ced122c4c453a02f", [:mix], [{:prometheus, "~> 4.0", [hex: :prometheus, repo: "hexpm", optional: false]}], "hexpm"}, "prometheus_ex": {:hex, :prometheus_ex, "3.0.5", "fa58cfd983487fc5ead331e9a3e0aa622c67232b3ec71710ced122c4c453a02f", [:mix], [{:prometheus, "~> 4.0", [hex: :prometheus, repo: "hexpm", optional: false]}], "hexpm"},

View file

@ -0,0 +1,459 @@
## `msgid`s in this file come from POT (.pot) files.
##
## Do not add, change, or remove `msgid`s manually here as
## they're tied to the ones in the corresponding POT file
## (with the same domain).
##
## Use `mix gettext.extract --merge` or `mix gettext.merge`
## to merge POT files into PO files.
msgid ""
msgstr ""
"Language: fr\n"
"Plural-Forms: nplurals=2; plural=(n > 1);\n"
msgid "can't be blank"
msgstr "ne peut être vide"
## grammatical gender…
msgid "has already been taken"
msgstr "a déjà été pris"
msgid "is invalid"
msgstr "est invalide"
msgid "has invalid format"
msgstr "a un format invalide"
msgid "has an invalid entry"
msgstr "a une entrée invalide"
## grammatical gender…
msgid "is reserved"
msgstr "est réservé"
msgid "does not match confirmation"
msgstr "ne correspondent pas"
msgid "is still associated with this entry"
msgstr ""
msgid "are still associated with this entry"
msgstr ""
msgid "should be %{count} character(s)"
msgid_plural "should be %{count} character(s)"
msgstr[0] "devrait avoir %{count} charactère"
msgstr[1] "devrait avoir %{count} charactères"
msgid "should have %{count} item(s)"
msgid_plural "should have %{count} item(s)"
msgstr[0] "devrait avoir %{count} objet"
msgstr[1] "devrait avoir %{count} objets"
msgid "should be at least %{count} character(s)"
msgid_plural "should be at least %{count} character(s)"
msgstr[0] "devrait avoir au moins %{count} charactère"
msgstr[1] "devrait avoir au moins %{count} charactères"
msgid "should have at least %{count} item(s)"
msgid_plural "should have at least %{count} item(s)"
msgstr[0] "devrait avoir au moins %{count} objet"
msgstr[1] "devrait avoir au moins %{count} objets"
msgid "should be at most %{count} character(s)"
msgid_plural "should be at most %{count} character(s)"
msgstr[0] "devrait avoir au plus %{count} charactère"
msgstr[1] "devrait avoir au plus %{count} charactères"
msgid "should have at most %{count} item(s)"
msgid_plural "should have at most %{count} item(s)"
msgstr[0] "devrait avoir au plus %{count} objet"
msgstr[1] "devrait avoir au plus %{count} objets"
msgid "must be less than %{number}"
msgstr "doit être inférieur à %{number}"
msgid "must be greater than %{number}"
msgstr "doit être supérieur à %{number}"
msgid "must be less than or equal to %{number}"
msgstr "doit être inférieur ou égal à %{number}"
msgid "must be greater than or equal to %{number}"
msgstr "doit être supérieur ou égal à %{number}"
msgid "must be equal to %{number}"
msgstr "doit égal à %{number}"
#, elixir-format
#: lib/pleroma/web/common_api/common_api.ex:381
msgid "Account not found"
msgstr "Compte non trouvé"
#, elixir-format
#: lib/pleroma/web/common_api/common_api.ex:153
msgid "Already voted"
msgstr "A déjà voté"
#, elixir-format
#: lib/pleroma/web/oauth/oauth_controller.ex:263
msgid "Bad request"
msgstr "Requête Invalide"
#, elixir-format
#: lib/pleroma/web/activity_pub/activity_pub_controller.ex:254
msgid "Can't delete object"
msgstr "Ne peut supprimer cet objet"
#, elixir-format
#: lib/pleroma/web/mastodon_api/mastodon_api_controller.ex:569
msgid "Can't delete this post"
msgstr "Ne peut supprimer ce message"
#, elixir-format
#: lib/pleroma/web/mastodon_api/mastodon_api_controller.ex:1731
#: lib/pleroma/web/mastodon_api/mastodon_api_controller.ex:1737
msgid "Can't display this activity"
msgstr "Ne peut afficher cette activitée"
#, elixir-format
#: lib/pleroma/web/mastodon_api/mastodon_api_controller.ex:195
msgid "Can't find user"
msgstr "Compte non trouvé"
#, elixir-format
#: lib/pleroma/web/mastodon_api/mastodon_api_controller.ex:1148
msgid "Can't get favorites"
msgstr "Favoris non trouvables"
#, elixir-format
#: lib/pleroma/web/activity_pub/activity_pub_controller.ex:263
msgid "Can't like object"
msgstr "Ne peut aimer cet objet"
#, elixir-format
#: lib/pleroma/web/common_api/utils.ex:518
msgid "Cannot post an empty status without attachments"
msgstr "Ne peut envoyer un status vide sans attachements"
#, elixir-format
#: lib/pleroma/web/common_api/utils.ex:461
msgid "Comment must be up to %{max_size} characters"
msgstr "Le commentaire ne doit faire plus de %{max_size} charactères"
#, elixir-format
#: lib/pleroma/web/admin_api/config.ex:63
msgid "Config with params %{params} not found"
msgstr "Configuration avec les paramètres %{params} non trouvée"
#, elixir-format
#: lib/pleroma/web/common_api/common_api.ex:78
msgid "Could not delete"
msgstr "Échec de la suppression"
#, elixir-format
#: lib/pleroma/web/common_api/common_api.ex:110
msgid "Could not favorite"
msgstr "Échec de mise en favoris"
#, elixir-format
#: lib/pleroma/web/common_api/common_api.ex:310
msgid "Could not pin"
msgstr "Échec de l'épinglage"
#, elixir-format
#: lib/pleroma/web/common_api/common_api.ex:89
msgid "Could not repeat"
msgstr "Échec de création la répétition"
#, elixir-format
#: lib/pleroma/web/common_api/common_api.ex:120
msgid "Could not unfavorite"
msgstr "Échec de suppression des favoris"
#, elixir-format
#: lib/pleroma/web/common_api/common_api.ex:327
msgid "Could not unpin"
msgstr "Échec du dépinglage"
#, elixir-format
#: lib/pleroma/web/common_api/common_api.ex:99
msgid "Could not unrepeat"
msgstr "Échec de suppression de la répétition"
#, elixir-format
#: lib/pleroma/web/common_api/common_api.ex:392
msgid "Could not update state"
msgstr "Échec de la mise à jour du status"
#, elixir-format
#: lib/pleroma/web/mastodon_api/mastodon_api_controller.ex:1271
msgid "Error."
msgstr "Erreur."
#, elixir-format
#: lib/pleroma/captcha/kocaptcha.ex:36
msgid "Invalid CAPTCHA"
msgstr "CAPTCHA invalide"
#, elixir-format
#: lib/pleroma/web/mastodon_api/mastodon_api_controller.ex:1700
#: lib/pleroma/web/oauth/oauth_controller.ex:465
msgid "Invalid credentials"
msgstr "Paramètres d'authentification invalides"
#, elixir-format
#: lib/pleroma/plugs/ensure_authenticated_plug.ex:20
msgid "Invalid credentials."
msgstr "Paramètres d'authentification invalides."
#, elixir-format
#: lib/pleroma/web/common_api/common_api.ex:154
msgid "Invalid indices"
msgstr "Indices invalides"
#, elixir-format
#: lib/pleroma/web/admin_api/admin_api_controller.ex:411
msgid "Invalid parameters"
msgstr "Paramètres invalides"
#, elixir-format
#: lib/pleroma/web/common_api/utils.ex:377
msgid "Invalid password."
msgstr "Mot de passe invalide."
#, elixir-format
#: lib/pleroma/web/mastodon_api/mastodon_api_controller.ex:163
msgid "Invalid request"
msgstr "Requête invalide"
#, elixir-format
#: lib/pleroma/captcha/kocaptcha.ex:16
msgid "Kocaptcha service unavailable"
msgstr "Service Kocaptcha non disponible"
#, elixir-format
#: lib/pleroma/web/mastodon_api/mastodon_api_controller.ex:1696
msgid "Missing parameters"
msgstr "Paramètres manquants"
#, elixir-format
#: lib/pleroma/web/common_api/utils.ex:496
msgid "No such conversation"
msgstr "Conversation inconnue"
#, elixir-format
#: lib/pleroma/web/admin_api/admin_api_controller.ex:163
#: lib/pleroma/web/admin_api/admin_api_controller.ex:206
msgid "No such permission_group"
msgstr "Groupe de permission inconnu"
#, elixir-format
#: lib/pleroma/plugs/uploaded_media.ex:69
#: lib/pleroma/web/activity_pub/activity_pub_controller.ex:311
#: lib/pleroma/web/admin_api/admin_api_controller.ex:399
#: lib/pleroma/web/mastodon_api/subscription_controller.ex:63
#: lib/pleroma/web/ostatus/ostatus_controller.ex:248
msgid "Not found"
msgstr "Non Trouvé"
#, elixir-format
#: lib/pleroma/web/common_api/common_api.ex:152
msgid "Poll's author can't vote"
msgstr "L'auteur·rice d'un sondage ne peut voter"
#, elixir-format
#: lib/pleroma/web/mastodon_api/mastodon_api_controller.ex:443
#: lib/pleroma/web/mastodon_api/mastodon_api_controller.ex:444
#: lib/pleroma/web/mastodon_api/mastodon_api_controller.ex:473
#: lib/pleroma/web/mastodon_api/mastodon_api_controller.ex:476
#: lib/pleroma/web/mastodon_api/mastodon_api_controller.ex:1180
#: lib/pleroma/web/mastodon_api/mastodon_api_controller.ex:1564
msgid "Record not found"
msgstr "Enregistrement non trouvé"
#, elixir-format
#: lib/pleroma/web/admin_api/admin_api_controller.ex:417
#: lib/pleroma/web/mastodon_api/mastodon_api_controller.ex:1570
#: lib/pleroma/web/mastodon_api/subscription_controller.ex:69
#: lib/pleroma/web/ostatus/ostatus_controller.ex:252
msgid "Something went wrong"
msgstr "Erreur inconnue"
#, elixir-format
#: lib/pleroma/web/common_api/common_api.ex:253
msgid "The message visibility must be direct"
msgstr "La visibilitée du message doit être « direct »"
#, elixir-format
#: lib/pleroma/web/common_api/utils.ex:521
msgid "The status is over the character limit"
msgstr "Le status est au-delà de la limite de charactères"
#, elixir-format
#: lib/pleroma/plugs/ensure_public_or_authenticated_plug.ex:27
msgid "This resource requires authentication."
msgstr "Cette resource nécessite une authentification."
#, elixir-format
#: lib/pleroma/plugs/rate_limiter.ex:89
msgid "Throttled"
msgstr "Limité"
#, elixir-format
#: lib/pleroma/web/common_api/common_api.ex:155
msgid "Too many choices"
msgstr "Trop de choix"
#, elixir-format
#: lib/pleroma/web/activity_pub/activity_pub_controller.ex:268
msgid "Unhandled activity type"
msgstr "Type d'activitée non-gérée"
#, elixir-format
#: lib/pleroma/plugs/user_is_admin_plug.ex:20
msgid "User is not admin."
msgstr "Le compte n'est pas admin."
#, elixir-format
#: lib/pleroma/web/common_api/common_api.ex:380
msgid "Valid `account_id` required"
msgstr "Un `account_id` valide est requis"
#, elixir-format
#: lib/pleroma/web/admin_api/admin_api_controller.ex:185
msgid "You can't revoke your own admin status."
msgstr "Vous ne pouvez révoquer votre propre status d'admin."
#, elixir-format
#: lib/pleroma/web/oauth/oauth_controller.ex:216
msgid "Your account is currently disabled"
msgstr "Votre compte est actuellement désactivé"
#, elixir-format
#: lib/pleroma/web/oauth/oauth_controller.ex:158
#: lib/pleroma/web/oauth/oauth_controller.ex:213
msgid "Your login is missing a confirmed e-mail address"
msgstr "Une confirmation de l'addresse de couriel est requise pour l'authentification"
#, elixir-format
#: lib/pleroma/web/activity_pub/activity_pub_controller.ex:221
msgid "can't read inbox of %{nickname} as %{as_nickname}"
msgstr "Ne peut lire la boite de réception de %{nickname} en tant que %{as_nickname}"
#, elixir-format
#: lib/pleroma/web/activity_pub/activity_pub_controller.ex:297
msgid "can't update outbox of %{nickname} as %{as_nickname}"
msgstr "Ne peut poster dans la boite d'émission de %{nickname} en tant que %{as_nickname}"
#, elixir-format
#: lib/pleroma/web/common_api/common_api.ex:335
msgid "conversation is already muted"
msgstr "la conversation est déjà baillonée"
#, elixir-format
#: lib/pleroma/web/activity_pub/activity_pub_controller.ex:192
#: lib/pleroma/web/activity_pub/activity_pub_controller.ex:317
#: lib/pleroma/web/mastodon_api/mastodon_api_controller.ex:1196
#: lib/pleroma/web/mastodon_api/mastodon_api_controller.ex:1247
msgid "error"
msgstr "erreur"
#, elixir-format
#: lib/pleroma/web/mastodon_api/mastodon_api_controller.ex:789
msgid "mascots can only be images"
msgstr "les mascottes ne peuvent être que des images"
#, elixir-format
#: lib/pleroma/web/activity_pub/activity_pub_controller.ex:34
msgid "not found"
msgstr "non trouvé"
#, elixir-format
#: lib/pleroma/web/oauth/oauth_controller.ex:298
msgid "Bad OAuth request."
msgstr "Requête OAuth invalide."
#, elixir-format
#: lib/pleroma/captcha/captcha.ex:92
msgid "CAPTCHA already used"
msgstr "CAPTCHA déjà utilisé"
#, elixir-format
#: lib/pleroma/captcha/captcha.ex:89
msgid "CAPTCHA expired"
msgstr "CAPTCHA expiré"
#, elixir-format
#: lib/pleroma/plugs/uploaded_media.ex:50
msgid "Failed"
msgstr "Échec"
#, elixir-format
#: lib/pleroma/web/oauth/oauth_controller.ex:314
msgid "Failed to authenticate: %{message}."
msgstr "Échec de l'authentification: %{message}"
#, elixir-format
#: lib/pleroma/web/oauth/oauth_controller.ex:345
msgid "Failed to set up user account."
msgstr "Échec de création de votre compte."
#, elixir-format
#: lib/pleroma/plugs/oauth_scopes_plug.ex:37
msgid "Insufficient permissions: %{permissions}."
msgstr "Permissions insuffisantes: %{permissions}."
#, elixir-format
#: lib/pleroma/plugs/uploaded_media.ex:89
msgid "Internal Error"
msgstr "Erreur interne"
#, elixir-format
#: lib/pleroma/web/oauth/fallback_controller.ex:22
#: lib/pleroma/web/oauth/fallback_controller.ex:29
msgid "Invalid Username/Password"
msgstr "Nom d'utilisateur/mot de passe invalide"
#, elixir-format
#: lib/pleroma/captcha/captcha.ex:107
msgid "Invalid answer data"
msgstr "Réponse invalide"
#, elixir-format
#: lib/pleroma/web/nodeinfo/nodeinfo_controller.ex:204
msgid "Nodeinfo schema version not handled"
msgstr "Version du schéma nodeinfo non géré"
#, elixir-format
#: lib/pleroma/web/oauth/oauth_controller.ex:145
msgid "This action is outside the authorized scopes"
msgstr "Cette action est en dehors des authorisations" # "scopes" ?
#, elixir-format
#: lib/pleroma/web/oauth/fallback_controller.ex:14
msgid "Unknown error, please check the details and try again."
msgstr "Erreur inconnue, veuillez vérifier les détails et réessayer."
#, elixir-format
#: lib/pleroma/web/oauth/oauth_controller.ex:93
#: lib/pleroma/web/oauth/oauth_controller.ex:131
msgid "Unlisted redirect_uri."
msgstr "redirect_uri non listé."
#, elixir-format
#: lib/pleroma/web/oauth/oauth_controller.ex:294
msgid "Unsupported OAuth provider: %{provider}."
msgstr "Fournisseur OAuth non supporté : %{provider}."
#, elixir-format
#: lib/pleroma/uploaders/uploader.ex:71
msgid "Uploader callback timeout"
msgstr ""
## msgstr "Attente écoulée"
#, elixir-format
#: lib/pleroma/web/uploader_controller.ex:11
#: lib/pleroma/web/uploader_controller.ex:23
msgid "bad request"
msgstr "requête invalide"

View file

@ -1 +1 @@
<!DOCTYPE html><html lang=en><head><meta charset=utf-8><meta name=viewport content="width=device-width,initial-scale=1,user-scalable=no"><title>Pleroma</title><!--server-generated-meta--><link rel=icon type=image/png href=/favicon.png><link rel=stylesheet href=/static/font/css/lato.css><link href=/static/css/vendors~app.b2603a50868c68a1c192.css rel=stylesheet><link href=/static/css/app.ae04505b31bb0ee2765e.css rel=stylesheet><link href=/static/fontello.1579191725174.css rel=stylesheet></head><body class=hidden><noscript>To use Pleroma, please enable JavaScript.</noscript><div id=app></div><script type=text/javascript src=/static/js/vendors~app.ef669266eac4d086d74e.js></script><script type=text/javascript src=/static/js/app.6fc3537f2985f0a2e06a.js></script></body></html> <!DOCTYPE html><html lang=en><head><meta charset=utf-8><meta name=viewport content="width=device-width,initial-scale=1,user-scalable=no"><link rel=stylesheet href=/static/font/css/fontello.css><link rel=stylesheet href=/static/font/css/animation.css><link rel=stylesheet href=/static/font/css/lato.css><title>Pleroma</title><!--server-generated-meta--><link rel=icon type=image/png href=/favicon.png><link rel=stylesheet href=/static/font/css/lato.css><link href=/static/css/vendors~app.b2603a50868c68a1c192.css rel=stylesheet><link href=/static/css/app.ae04505b31bb0ee2765e.css rel=stylesheet><link href=/static/fontello.1580295842879.css rel=stylesheet></head><body class=hidden><noscript>To use Pleroma, please enable JavaScript.</noscript><div id=app></div><script type=text/javascript src=/static/js/vendors~app.9ab182239f3a2abee89f.js></script><script type=text/javascript src=/static/js/app.8457067449d0acdd01fc.js></script></body></html>

View file

@ -1,5 +1,5 @@
{ {
"theme": "pleroma-dark", "theme": "ihatebeingalive",
"background": "/static/aurora_borealis.jpg", "background": "/static/aurora_borealis.jpg",
"logo": "/static/logo.png", "logo": "/static/logo.png",
"logoMask": true, "logoMask": true,

View file

@ -74,6 +74,8 @@
<glyph glyph-name="home-2" unicode="&#xe821;" d="M521 826q322-279 500-429 20-16 20-40 0-21-15-37t-36-15l-105 0 0-364q0-21-15-37t-36-16l-156 0q-22 0-37 16t-16 37l0 208-209 0 0-208q0-21-15-37t-36-16l-156 0q-21 0-37 16t-16 37l0 364-103 0q-22 0-37 15t-16 37 19 40z" horiz-adv-x="1041" /> <glyph glyph-name="home-2" unicode="&#xe821;" d="M521 826q322-279 500-429 20-16 20-40 0-21-15-37t-36-15l-105 0 0-364q0-21-15-37t-36-16l-156 0q-22 0-37 16t-16 37l0 208-209 0 0-208q0-21-15-37t-36-16l-156 0q-21 0-37 16t-16 37l0 364-103 0q-22 0-37 15t-16 37 19 40z" horiz-adv-x="1041" />
<glyph glyph-name="arrow-curved" unicode="&#xe822;" d="M799 302l0-56 112 0-223-223-224 223 112 0 0 56q0 116-81 197t-197 82-198-82-82-197q0 162 115 276t276 114 276-114 114-276z" horiz-adv-x="928" />
<glyph glyph-name="spin3" unicode="&#xe832;" d="M494 857c-266 0-483-210-494-472-1-19 13-20 13-20l84 0c16 0 19 10 19 18 10 199 176 358 378 358 107 0 205-45 273-118l-58-57c-11-12-11-27 5-31l247-50c21-5 46 11 37 44l-58 227c-2 9-16 22-29 13l-65-60c-89 91-214 148-352 148z m409-508c-16 0-19-10-19-18-10-199-176-358-377-358-108 0-205 45-274 118l59 57c10 12 10 27-5 31l-248 50c-21 5-46-11-37-44l58-227c2-9 16-22 30-13l64 60c89-91 214-148 353-148 265 0 482 210 493 473 1 18-13 19-13 19l-84 0z" horiz-adv-x="1000" /> <glyph glyph-name="spin3" unicode="&#xe832;" d="M494 857c-266 0-483-210-494-472-1-19 13-20 13-20l84 0c16 0 19 10 19 18 10 199 176 358 378 358 107 0 205-45 273-118l-58-57c-11-12-11-27 5-31l247-50c21-5 46 11 37 44l-58 227c-2 9-16 22-29 13l-65-60c-89 91-214 148-352 148z m409-508c-16 0-19-10-19-18-10-199-176-358-377-358-108 0-205 45-274 118l59 57c10 12 10 27-5 31l-248 50c-21 5-46-11-37-44l58-227c2-9 16-22 30-13l64 60c89-91 214-148 353-148 265 0 482 210 493 473 1 18-13 19-13 19l-84 0z" horiz-adv-x="1000" />
<glyph glyph-name="spin4" unicode="&#xe834;" d="M498 857c-114 0-228-39-320-116l0 0c173 140 428 130 588-31 134-134 164-332 89-495-10-29-5-50 12-68 21-20 61-23 84 0 3 3 12 15 15 24 71 180 33 393-112 539-99 98-228 147-356 147z m-409-274c-14 0-29-5-39-16-3-3-13-15-15-24-71-180-34-393 112-539 185-185 479-195 676-31l0 0c-173-140-428-130-589 31-134 134-163 333-89 495 11 29 6 50-12 68-11 11-27 17-44 16z" horiz-adv-x="1001" /> <glyph glyph-name="spin4" unicode="&#xe834;" d="M498 857c-114 0-228-39-320-116l0 0c173 140 428 130 588-31 134-134 164-332 89-495-10-29-5-50 12-68 21-20 61-23 84 0 3 3 12 15 15 24 71 180 33 393-112 539-99 98-228 147-356 147z m-409-274c-14 0-29-5-39-16-3-3-13-15-15-24-71-180-34-393 112-539 185-185 479-195 676-31l0 0c-173-140-428-130-589 31-134 134-163 333-89 495 11 29 6 50-12 68-11 11-27 17-44 16z" horiz-adv-x="1001" />

Before

Width:  |  Height:  |  Size: 24 KiB

After

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

Binary file not shown.

Binary file not shown.

View file

@ -0,0 +1,435 @@
<?xml version="1.0" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg xmlns="http://www.w3.org/2000/svg">
<defs >
<font id="Lato" horiz-adv-x="1042" ><font-face
font-family="Lato"
units-per-em="2000"
panose-1="2 15 5 2 2 2 4 3 2 3"
ascent="1974"
descent="-426"
alphabetic="0" />
<glyph unicode=" " glyph-name="space" horiz-adv-x="386" />
<glyph unicode="!" glyph-name="exclam" horiz-adv-x="686" d="M430 1433V861Q430 816 429 773T424 687T417 598T406 500H285Q279 552 275 597T267 686T263 773T261 861V1433H430ZM218 110Q218 136 227 159T253 199T293 226T342 236Q368 236 391 226T431 199T457
159T467 110Q467 83 458 61T431 21T391 -5T342 -15Q316 -15 293 -6T254 21T228 60T218 110Z" />
<glyph unicode="&quot;" glyph-name="quotedbl" horiz-adv-x="794" d="M307 1433V1143L291 988Q288 956 274 939T229 921Q203 921 189 938T168 988L152 1143V1433H307ZM640 1433V1143L624 988Q621 956 607 939T562 921Q536 921 522 938T501 988L485 1143V1433H640Z" />
<glyph unicode="#" glyph-name="numbersign" horiz-adv-x="1160" d="M790 423L706 0H625Q602 0 586 17T570 61Q570 65 570 68T572 77L643 423H396L325 68Q317 31 295 16T243 0H164L249 423H103Q80 423 67 434T54 474Q54 479 54 485T56 497L64 554H268L333 880H101L114
954Q119 983 137 997T194 1011H352L424 1369Q430 1399 451 1416T503 1433H583L499 1011H746L830 1433H909Q934 1433 950 1418T967 1379Q967 1371 966 1366L893 1011H1105L1092 936Q1087 907 1069 894T1012 880H874L809 554H988Q1012 554 1025 543T1038 502Q1038
497 1038 492T1036 480L1027 423H790ZM415 554H662L727 880H480L415 554Z" />
<glyph unicode="$" glyph-name="dollar" horiz-adv-x="1160" d="M498 -12Q377 -1 278 47T106 171L159 253Q166 264 179 271T206 278Q225 278 249 259T308 216T391 168T508 137L545 668Q475 689 408 715T287 784T201 891T168 1053Q168 1126 196 1195T279 1319T414
1407T598 1446L608 1590Q610 1609 623 1624T658 1639H724L710 1441Q815 1428 891 1387T1027 1288L984 1222Q964 1192 938 1192Q924 1192 904 1204T854 1233T787 1266T700 1290L667 806Q739 784 809 758T935 691T1025 589T1060 435Q1060 345 1030 266T943 127T802
30T611 -14L599 -190Q597 -209 584 -223T549 -238H483L498 -12ZM891 407Q891 457 873 493T822 555T747 600T655 635L621 137Q686 143 736 165T821 223T873 304T891 407ZM336 1071Q336 1023 353 988T400 925T470 878T557 842L587 1293Q522 1287 475 1267T397 1217T351
1150T336 1071Z" />
<glyph unicode="%" glyph-name="percent" horiz-adv-x="1572" d="M707 1087Q707 1003 681 937T612 825T510 755T389 731Q321 731 264 755T163 824T96 936T72 1087Q72 1173 96 1240T163 1353T263 1423T389 1447Q456 1447 514 1423T615 1353T682 1240T707 1087ZM568
1087Q568 1153 554 1199T516 1274T459 1317T389 1330Q352 1330 320 1317T263 1275T226 1199T212 1087Q212 1022 225 977T263 903T319 862T389 849Q426 849 458 861T515 902T554 976T568 1087ZM1208 1397Q1221 1414 1235 1423T1274 1433H1402L355 29Q345 16 331
8T298 0H166L1208 1397ZM1499 338Q1499 254 1473 188T1404 77T1303 7T1182 -17Q1114 -17 1057 7T956 76T889 188T865 338Q865 424 889 491T956 605T1056 675T1182 699Q1249 699 1307 675T1408 605T1475 492T1499 338ZM1361 338Q1361 404 1347 450T1309 526T1252
568T1182 581Q1145 581 1113 568T1056 526T1019 451T1005 338Q1005 273 1018 228T1056 155T1112 114T1182 101Q1219 101 1251 113T1308 154T1347 228T1361 338Z" />
<glyph unicode="&amp;" glyph-name="ampersand" horiz-adv-x="1406" d="M660 1449Q739 1449 804 1424T916 1356T991 1259T1023 1143L912 1121Q907 1120 903 1120Q890 1120 879 1127T862 1152Q855 1178 840 1206T800 1257T741 1295T660 1310Q610 1310 570 1294T502
1250T458 1183T442 1099Q442 1064 450 1032T476 968T520 901T585 828L997 409Q1035 476 1057 549T1088 697Q1090 716 1100 727T1128 738H1238Q1236 623 1201 512T1100 304L1400 0H1228Q1199 0 1181 7T1141 36L997 181Q903 90 781 37T511 -16Q431 -16 354 11T217
89T119 215T82 382Q82 452 105 514T170 630T269 725T394 797Q333 874 304 947T275 1098Q275 1171 301 1235T378 1346T499 1421T660 1449ZM263 396Q263 331 287 282T350 198T439 147T541 129Q653 129 742 170T899 279L476 706Q370 649 317 570T263 396Z" />
<glyph unicode="&apos;" glyph-name="quotesingle" horiz-adv-x="460" d="M307 1433V1143L291 988Q288 956 274 939T229 921Q203 921 189 938T168 988L152 1143V1433H307Z" />
<glyph unicode="(" glyph-name="parenleft" horiz-adv-x="600" d="M289 629Q289 415 344 214T503 -171Q509 -182 511 -190T513 -206Q513 -220 506 -229T488 -245L409 -293Q334 -178 282 -65T197 162T149 391T134 629Q134 750 149 866T196 1095T281 1322T409 1551L488
1502Q499 1495 506 1486T513 1463Q513 1448 503 1429Q398 1247 344 1045T289 629Z" />
<glyph unicode=")" glyph-name="parenright" horiz-adv-x="600" d="M298 629Q298 843 244 1045T84 1429Q74 1448 74 1463Q74 1477 81 1486T99 1502L178 1551Q253 1435 305 1322T390 1096T438 866T453 629Q453 507 438 392T391 162T306 -65T178 -293L99 -245Q88
-238 81 -229T74 -206Q74 -198 76 -190T84 -171Q188 12 243 213T298 629Z" />
<glyph unicode="*" glyph-name="asterisk" horiz-adv-x="800" d="M354 863V1060Q354 1079 356 1096T366 1129Q346 1104 313 1084L141 985L97 1060L269 1160Q305 1181 342 1184Q322 1186 305 1191T269 1209L96 1310L140 1385L313 1285Q348 1265 370 1233Q361 1251
358 1269T354 1308V1506H442V1309Q442 1268 428 1237Q439 1252 452 1263T483 1285L655 1384L699 1309L527 1209Q510 1198 494 1192T459 1184Q477 1182 493 1177T527 1160L700 1059L656 984L483 1084Q465 1095 451 1106T426 1133Q442 1100 442 1061V863H354Z" />
<glyph unicode="+" glyph-name="plus" horiz-adv-x="1160" d="M651 1166V739H1058V604H651V174H505V604H100V739H505V1166H651Z" />
<glyph unicode="," glyph-name="comma" horiz-adv-x="424" d="M94 123Q94 146 102 166T126 202T164 227T212 236Q242 236 265 225T304 195T328 150T336 94Q336 49 323 1T286 -95T227 -188T146 -271L116 -242Q103 -230 103 -214Q103 -201 117 -187Q127 -176 142
-158T174 -115T203 -62T223 0H210Q184 0 163 9T127 34T103 73T94 123Z" />
<glyph unicode="-" glyph-name="hyphen" horiz-adv-x="694" d="M100 675H594V524H100V675Z" />
<glyph unicode="." glyph-name="period" horiz-adv-x="424" d="M88 110Q88 136 97 159T123 199T163 226T212 236Q238 236 261 226T301 199T327 159T337 110Q337 83 328 61T301 21T261 -5T212 -15Q186 -15 163 -6T124 21T98 60T88 110Z" />
<glyph unicode="/" glyph-name="slash" horiz-adv-x="746" d="M161 -21Q147 -56 120 -73T63 -90H-12L589 1407Q602 1439 626 1456T683 1473H758L161 -21Z" />
<glyph unicode="0" glyph-name="zero" horiz-adv-x="1160" d="M1100 716Q1100 528 1060 391T949 163T784 29T579 -15Q469 -15 375 29T210 163T100 390T60 716Q60 904 100 1042T210 1270T374 1405T579 1449Q688 1449 783 1405T949 1271T1059 1042T1100 716ZM915
716Q915 880 888 991T814 1171T706 1268T579 1298Q513 1298 452 1269T345 1171T272 992T244 716Q244 552 271 441T345 262T452 165T579 135Q645 135 706 164T813 262T887 441T915 716Z" />
<glyph unicode="1" glyph-name="one" horiz-adv-x="1160" d="M287 136H595V1113Q595 1157 598 1202L342 983Q332 975 322 972T303 968Q288 968 276 974T258 990L202 1067L628 1436H773V136H1055V0H287V136Z" />
<glyph unicode="2" glyph-name="two" horiz-adv-x="1160" d="M601 1449Q692 1449 771 1422T907 1344T998 1218T1031 1050Q1031 970 1007 902T942 772T848 651T734 531L357 145Q397 156 438 162T517 169H997Q1026 169 1043 152T1060 108V0H104V61Q104 80 111 100T136
137L595 598Q652 656 699 709T779 817T831 926T849 1045Q849 1108 829 1155T774 1234T692 1280T591 1296Q537 1296 491 1280T410 1236T350 1168T315 1082Q307 1053 291 1040T249 1027Q244 1027 239 1027T226 1029L133 1045Q147 1143 187 1218T288 1345T429 1422T601
1449Z" />
<glyph unicode="3" glyph-name="three" horiz-adv-x="1160" d="M620 1449Q711 1449 788 1423T920 1349T1007 1233T1038 1082Q1038 1014 1021 961T971 867T892 798T789 753Q929 716 999 629T1070 411Q1070 312 1033 233T930 99T779 14T593 -16Q479 -16 398 12T261
91T169 209T108 358L184 390Q205 399 226 399Q246 399 261 391T285 364Q287 360 289 356T293 346Q307 317 327 281T381 212T465 158T591 136Q666 136 722 160T816 224T873 311T892 406Q892 464 877 512T821 595T709 650T525 670V799Q616 800 680 819T786 871T845
950T864 1052Q864 1114 845 1160T791 1236T711 1281T610 1296Q556 1296 510 1280T429 1236T369 1168T333 1082Q325 1053 309 1040T268 1027Q263 1027 258 1027T245 1029L152 1045Q166 1143 206 1218T307 1345T448 1422T620 1449Z" />
<glyph unicode="4" glyph-name="four" horiz-adv-x="1160" d="M903 517H1120V415Q1120 399 1111 388T1081 377H903V0H746V377H111Q91 377 77 388T58 417L40 508L737 1433H903V517ZM746 1108Q746 1134 747 1164T754 1226L233 517H746V1108Z" />
<glyph unicode="5" glyph-name="five" horiz-adv-x="1160" d="M978 1355Q978 1317 954 1293T873 1268H423L357 892Q469 916 564 916Q676 916 761 883T905 792T992 655T1022 483Q1022 369 982 277T873 120T709 19T506 -16Q443 -16 385 -4T277 30T184 78T108 135L162
211Q180 237 210 237Q229 237 254 222T316 187T400 153T516 137Q591 137 651 161T753 229T819 336T842 475Q842 542 823 596T764 688T665 747T527 768Q473 768 415 759T295 730L183 763L299 1433H978V1355Z" />
<glyph unicode="6" glyph-name="six" horiz-adv-x="1160" d="M650 878Q736 878 813 850T948 767T1040 633T1074 451Q1074 352 1038 267T938 119T782 20T582 -16Q474 -16 387 18T237 116T142 270T108 473Q108 567 150 673T283 901L646 1390Q660 1408 685 1420T742
1433H900L403 804Q454 839 516 858T650 878ZM280 442Q280 373 300 316T358 218T452 155T579 132Q651 132 709 155T808 219T872 316T895 438Q895 508 873 564T811 660T715 721T592 742Q520 742 462 718T364 652T302 556T280 442Z" />
<glyph unicode="7" glyph-name="seven" horiz-adv-x="1160" d="M1084 1433V1353Q1084 1319 1077 1297T1061 1260L468 63Q455 37 432 19T370 0H243L845 1182Q858 1207 871 1228T902 1268H154Q137 1268 124 1281T110 1312V1433H1084Z" />
<glyph unicode="8" glyph-name="eight" horiz-adv-x="1160" d="M579 -16Q472 -16 383 12T230 94T131 222T96 392Q96 530 168 619T374 747Q261 789 204 872T146 1072Q146 1151 177 1220T265 1340T401 1420T579 1449Q676 1449 756 1420T893 1340T981 1220T1012 1072Q1012
956 954 873T784 747Q918 709 990 620T1062 392Q1062 298 1027 223T928 94T775 13T579 -16ZM579 126Q649 126 704 145T798 200T857 285T878 395Q878 469 854 521T788 607T692 655T579 671Q520 671 466 656T371 607T305 522T280 395Q280 335 300 286T359 201T453
146T579 126ZM579 814Q649 814 698 835T779 892T824 974T838 1069Q838 1119 822 1163T773 1240T692 1292T579 1311Q515 1311 467 1292T386 1240T337 1163T320 1069Q320 1020 334 974T379 893T459 836T579 814Z" />
<glyph unicode="9" glyph-name="nine" horiz-adv-x="1160" d="M549 588Q468 588 396 615T268 694T181 823T148 999Q148 1093 183 1175T281 1318T431 1414T622 1449Q725 1449 809 1415T953 1320T1045 1174T1078 986Q1078 924 1067 869T1033 760T981 655T911 546L562
42Q549 23 525 12T470 0H306L742 571Q764 600 783 626T819 678Q764 634 695 611T549 588ZM907 1007Q907 1074 886 1128T826 1221T736 1279T620 1300Q554 1300 500 1279T407 1219T347 1128T326 1012Q326 944 345 891T401 800T490 744T608 725Q680 725 735 748T829
811T887 901T907 1007Z" />
<glyph unicode=":" glyph-name="colon" horiz-adv-x="504" d="M128 110Q128 136 137 159T163 199T203 226T252 236Q278 236 301 226T341 199T367 159T377 110Q377 83 368 61T341 21T301 -5T252 -15Q226 -15 203 -6T164 21T138 60T128 110ZM128 860Q128 886 137
909T163 949T203 976T252 986Q278 986 301 976T341 949T367 909T377 860Q377 833 368 811T341 771T301 745T252 735Q226 735 203 744T164 771T138 810T128 860Z" />
<glyph unicode=";" glyph-name="semicolon" horiz-adv-x="504" d="M134 123Q134 146 142 166T166 202T204 227T252 236Q282 236 305 225T344 195T368 150T376 94Q376 49 363 1T326 -95T267 -188T186 -271L156 -242Q143 -230 143 -214Q143 -201 157 -187Q167 -176
182 -158T214 -115T243 -62T263 0H250Q224 0 203 9T167 34T143 73T134 123ZM128 860Q128 886 137 909T163 949T203 976T252 986Q278 986 301 976T341 949T367 909T377 860Q377 833 368 811T341 771T301 745T252 735Q226 735 203 744T164 771T138 810T128 860Z"
/>
<glyph unicode="&lt;" glyph-name="less" horiz-adv-x="1160" d="M148 710L922 1111V984Q922 967 914 955T886 932L437 704Q417 693 395 686T347 672Q372 667 394 659T437 641L886 414Q906 404 914 391T922 362V234L148 636V710Z" />
<glyph unicode="=" glyph-name="equal" horiz-adv-x="1160" d="M150 574H1009V439H150V574ZM150 909H1009V774H150V909Z" />
<glyph unicode="&gt;" glyph-name="greater" horiz-adv-x="1160" d="M238 234V362Q238 378 246 391T274 414L723 641Q743 651 764 659T811 672Q786 678 765 685T723 704L274 932Q254 942 246 954T238 984V1111L1011 710V636L238 234Z" />
<glyph unicode="?" glyph-name="question" horiz-adv-x="796" d="M34 1305Q65 1334 102 1360T184 1406T280 1437T392 1449Q471 1449 538 1426T655 1361T732 1259T760 1124Q760 1048 738 993T681 897T605 825T529 767T468 713T438 653L420 500H298L286 666V677Q286
719 308 751T365 812T439 869T513 932T570 1011T593 1115Q593 1158 576 1193T530 1252T462 1290T377 1303Q316 1303 273 1288T199 1255T151 1222T120 1207Q95 1207 81 1230L34 1305ZM230 110Q230 136 239 159T265 199T305 226T354 236Q380 236 403 226T443 199T469
159T479 110Q479 83 470 61T443 21T403 -5T354 -15Q328 -15 305 -6T266 21T240 60T230 110Z" />
<glyph unicode="@" glyph-name="at" horiz-adv-x="1644" d="M1167 186Q1089 186 1040 223T978 339Q920 258 852 223T706 188Q646 188 602 208T529 265T485 352T470 460Q470 545 502 631T599 787T758 901T979 945Q1046 945 1096 935T1192 904L1099 543Q1080 468
1080 419Q1080 383 1089 360T1113 323T1149 304T1193 299Q1242 299 1286 327T1363 406T1416 528T1436 687Q1436 825 1392 929T1270 1102T1087 1206T859 1241Q725 1241 608 1190T404 1049T267 836T217 568Q217 398 270 270T416 54T633 -77T901 -121Q1053 -121 1169
-88T1371 -4Q1386 5 1398 5Q1419 5 1429 -19L1454 -85Q1347 -157 1211 -198T901 -239Q728 -239 579 -184T321 -26T149 228T86 568Q86 677 113 778T191 966T311 1126T467 1250T652 1330T859 1359Q951 1359 1039 1339T1205 1280T1350 1184T1463 1052T1537 886T1564
687Q1564 579 1534 488T1450 329T1324 224T1167 186ZM741 306Q772 306 803 316T864 351T918 419T960 527L1036 822Q997 831 951 831Q876 831 814 800T706 717T636 600T610 465Q610 393 643 350T741 306Z" />
<glyph unicode="A" glyph-name="A" horiz-adv-x="1360" d="M1353 0H1203Q1177 0 1161 13T1137 46L1003 392H360L226 46Q219 28 202 14T160 0H10L583 1433H780L1353 0ZM414 532H949L724 1115Q702 1169 681 1250Q670 1209 660 1175T639 1114L414 532Z" />
<glyph unicode="B" glyph-name="B" horiz-adv-x="1294" d="M174 0V1433H631Q763 1433 858 1407T1015 1333T1106 1216T1136 1059Q1136 1006 1120 957T1070 866T986 791T867 737Q1024 706 1104 624T1184 408Q1184 317 1151 242T1053 113T895 30T681 0H174ZM368 653V154H678Q761
154 820 173T918 226T975 308T993 413Q993 524 915 588T677 653H368ZM368 791H624Q706 791 766 809T865 859T924 936T943 1036Q943 1162 867 1221T631 1280H368V791Z" />
<glyph unicode="C" glyph-name="C" horiz-adv-x="1370" d="M1184 296Q1200 296 1213 283L1289 200Q1201 98 1076 41T773 -16Q618 -16 492 37T277 187T139 419T90 716Q90 878 142 1013T290 1245T517 1395T810 1449Q968 1449 1082 1400T1286 1267L1223 1178Q1216
1168 1207 1162T1181 1155Q1168 1155 1154 1164T1119 1188T1072 1218T1008 1248T922 1271T809 1281Q694 1281 599 1242T434 1129T327 951T288 716Q288 582 326 477T431 300T589 189T785 151Q849 151 900 158T994 182T1075 222T1151 281Q1168 296 1184 296Z" />
<glyph unicode="D" glyph-name="D" horiz-adv-x="1506" d="M1416 716Q1416 555 1365 423T1221 197T998 52T710 0H174V1433H710Q868 1433 998 1382T1221 1236T1365 1009T1416 716ZM1217 716Q1217 848 1181 952T1079 1128T919 1238T710 1276H369V157H710Q825 157
919 195T1079 304T1181 480T1217 716Z" />
<glyph unicode="E" glyph-name="E" horiz-adv-x="1162" d="M1057 1433V1275H369V799H926V647H369V158H1057V0H174V1433H1057Z" />
<glyph unicode="F" glyph-name="F" horiz-adv-x="1132" d="M1057 1433V1275H369V774H957V616H369V0H174V1433H1057Z" />
<glyph unicode="G" glyph-name="G" horiz-adv-x="1468" d="M813 141Q871 141 919 146T1011 163T1092 189T1168 225V541H946Q927 541 916 552T904 579V689H1344V139Q1290 100 1232 71T1107 23T964 -6T799 -16Q643 -16 513 37T289 187T143 419T90 716Q90 880 141
1015T288 1246T519 1396T823 1449Q908 1449 981 1437T1116 1401T1232 1344T1331 1268L1276 1180Q1259 1153 1232 1153Q1216 1153 1197 1164Q1172 1178 1141 1198T1066 1236T961 1268T817 1281Q696 1281 598 1242T431 1129T325 951T288 716Q288 580 326 474T435
293T601 180T813 141Z" />
<glyph unicode="H" glyph-name="H" horiz-adv-x="1512" d="M1336 0H1141V652H369V0H174V1433H369V794H1141V1433H1336V0Z" />
<glyph unicode="I" glyph-name="I" horiz-adv-x="614" d="M404 0H210V1433H404V0Z" />
<glyph unicode="J" glyph-name="J" horiz-adv-x="888" d="M713 495Q713 375 684 280T597 120T455 19T262 -16Q165 -16 60 12Q62 41 65 69T71 126Q73 143 83 153T115 164Q133 164 163 155T243 146Q309 146 360 166T447 228T501 335T520 491V1433H713V495Z" />
<glyph unicode="K" glyph-name="K" horiz-adv-x="1362" d="M387 805H460Q498 805 520 814T563 847L1040 1387Q1062 1412 1082 1422T1135 1433H1300L754 816Q733 793 715 777T675 751Q703 742 724 724T768 679L1338 0H1170Q1151 0 1138 3T1116 11T1098 24T1082
41L587 610Q576 622 567 630T545 645T516 653T475 656H387V0H194V1433H387V805Z" />
<glyph unicode="L" glyph-name="L" horiz-adv-x="1028" d="M368 163H988V0H174V1433H368V163Z" />
<glyph unicode="M" glyph-name="M" horiz-adv-x="1840" d="M879 518Q893 494 903 468T924 414Q934 442 945 467T970 519L1455 1400Q1468 1423 1482 1428T1522 1433H1665V0H1495V1053Q1495 1074 1496 1098T1499 1147L1008 251Q983 206 938 206H910Q865 206 840
251L338 1150Q341 1124 342 1099T344 1053V0H174V1433H317Q343 1433 357 1428T384 1400L879 518V518Z" />
<glyph unicode="N" glyph-name="N" horiz-adv-x="1512" d="M274 1433Q300 1433 312 1427T341 1400L1171 320Q1168 346 1167 370T1166 418V1433H1336V0H1238Q1215 0 1200 8T1169 35L340 1114Q342 1089 343 1065T344 1021V0H174V1433H274V1433Z" />
<glyph unicode="O" glyph-name="O" horiz-adv-x="1596" d="M1505 716Q1505 555 1454 421T1310 189T1087 39T798 -15Q640 -15 510 38T287 189T143 420T92 716Q92 877 143 1011T287 1243T510 1395T798 1449Q956 1449 1086 1395T1310 1244T1454 1012T1505 716ZM1306
716Q1306 848 1270 953T1168 1130T1008 1242T798 1281Q683 1281 589 1242T429 1131T326 953T290 716Q290 584 326 480T428 303T589 192T798 153Q914 153 1008 191T1168 302T1270 479T1306 716Z" />
<glyph unicode="P" glyph-name="P" horiz-adv-x="1222" d="M387 536V0H194V1433H617Q753 1433 853 1402T1020 1312T1118 1172T1151 989Q1151 889 1116 806T1014 663T846 570T617 536H387ZM387 690H617Q700 690 763 712T870 773T935 868T957 989Q957 1126 873 1203T617
1280H387V690Z" />
<glyph unicode="Q" glyph-name="Q" horiz-adv-x="1596" d="M1505 716Q1505 615 1485 524T1425 355T1330 212T1204 101L1572 -296H1412Q1376 -296 1348 -286T1297 -251L1045 23Q988 5 927 -5T798 -15Q640 -15 510 38T287 189T143 420T92 716Q92 877 143 1011T287
1243T510 1395T798 1449Q956 1449 1086 1395T1310 1244T1454 1012T1505 716ZM1306 716Q1306 848 1270 953T1168 1130T1008 1242T798 1281Q683 1281 589 1242T429 1131T326 953T290 716Q290 584 326 480T428 303T589 192T798 153Q914 153 1008 191T1168 302T1270
479T1306 716Z" />
<glyph unicode="R" glyph-name="R" horiz-adv-x="1288" d="M387 598V0H194V1433H599Q735 1433 834 1406T997 1326T1093 1201T1124 1036Q1124 960 1100 894T1031 776T920 686T771 630Q807 609 835 569L1253 0H1081Q1028 0 1003 41L631 553Q614 577 594 587T534
598H387ZM387 739H590Q675 739 739 759T847 817T913 907T935 1022Q935 1150 851 1215T599 1280H387V739Z" />
<glyph unicode="S" glyph-name="S" horiz-adv-x="1060" d="M908 1209Q899 1194 889 1187T863 1179Q846 1179 824 1196T767 1233T684 1271T566 1288Q501 1288 451 1271T368 1223T317 1153T300 1065Q300 1005 329 966T407 898T517 850T643 807T769 758T879 688T957
585T987 435Q987 341 955 259T862 115T711 19T507 -16Q368 -16 254 34T58 171L114 263Q122 274 133 281T159 289Q180 289 207 267T274 217T372 168T513 145Q582 145 636 164T727 217T785 300T805 407Q805 472 776 513T698 583T589 630T463 670T337 718T227 788T150
895T120 1055Q120 1131 149 1202T235 1328T373 1416T563 1449Q683 1449 782 1411T955 1301L908 1209Z" />
<glyph unicode="T" glyph-name="T" horiz-adv-x="1180" d="M1150 1433V1270H687V0H493V1270H28V1433H1150Z" />
<glyph unicode="U" glyph-name="U" horiz-adv-x="1460" d="M731 154Q820 154 890 184T1008 268T1082 397T1108 562V1433H1301V562Q1301 438 1262 332T1149 149T969 27T731 -17Q599 -17 493 27T313 148T200 332T160 562V1433H353V563Q353 473 378 398T452 269T571
185T731 154Z" />
<glyph unicode="V" glyph-name="V" horiz-adv-x="1360" d="M8 1433H163Q189 1433 205 1420T229 1387L634 376Q648 342 659 302T682 219Q691 262 701 302T726 376L1129 1387Q1136 1404 1153 1418T1195 1433H1351L767 0H592L8 1433Z" />
<glyph unicode="W" glyph-name="W" horiz-adv-x="2038" d="M14 1433H175Q201 1433 218 1420T241 1387L537 391Q545 364 551 333T564 268Q571 302 578 333T594 391L931 1387Q937 1404 954 1418T997 1433H1053Q1079 1433 1095 1420T1119 1387L1454 391Q1472 339
1486 272Q1492 305 1497 335T1510 391L1807 1387Q1812 1405 1829 1419T1872 1433H2023L1576 0H1402L1039 1093Q1028 1124 1019 1165Q1014 1145 1010 1127T1000 1093L635 0H461L14 1433Z" />
<glyph unicode="X" glyph-name="X" horiz-adv-x="1286" d="M507 736L34 1433H227Q248 1433 258 1426T276 1406L650 832Q657 853 671 878L1024 1402Q1033 1416 1043 1424T1069 1433H1254L779 745L1270 0H1078Q1056 0 1044 11T1023 37L639 638Q632 617 621 598L247
37Q238 23 227 12T194 0H14L507 736Z" />
<glyph unicode="Y" glyph-name="Y" horiz-adv-x="1258" d="M726 570V0H533V570L8 1433H178Q204 1433 219 1420T245 1388L573 831Q593 796 606 765T631 704Q642 735 655 766T688 831L1015 1388Q1024 1404 1039 1418T1080 1433H1252L726 570Z" />
<glyph unicode="Z" glyph-name="Z" horiz-adv-x="1248" d="M1172 1433V1361Q1172 1327 1151 1297L340 158H1158V0H86V76Q86 106 105 133L917 1275H124V1433H1172Z" />
<glyph unicode="[" glyph-name="bracketleft" horiz-adv-x="600" d="M142 -289V1533H510V1463Q510 1441 497 1429T461 1416H292V-171H461Q483 -171 496 -183T510 -219V-289H142Z" />
<glyph unicode="\" glyph-name="backslash" horiz-adv-x="750" d="M-20 1473H56Q89 1473 113 1456T150 1407L751 -90H676Q647 -90 619 -73T578 -21L-20 1473Z" />
<glyph unicode="]" glyph-name="bracketright" horiz-adv-x="600" d="M90 -219Q90 -199 103 -185T139 -171H308V1416H139Q117 1416 104 1429T90 1463V1533H458V-289H90V-219Z" />
<glyph unicode="^" glyph-name="asciicircum" horiz-adv-x="1160" d="M516 1433H631L989 787H860Q843 787 831 797T811 821L615 1173Q602 1196 593 1217T576 1260Q562 1216 539 1173L345 821Q337 807 326 797T294 787H158L516 1433Z" />
<glyph unicode="_" glyph-name="underscore" horiz-adv-x="788" d="M788 -165V-285H0V-165H788Z" />
<glyph unicode="`" glyph-name="grave" horiz-adv-x="614" d="M207 1449Q240 1449 256 1439T286 1405L435 1163H333Q312 1163 299 1169T272 1191L38 1449H207Z" />
<glyph unicode="a" glyph-name="a" horiz-adv-x="1014" d="M890 0H811Q785 0 769 8T748 42L728 136Q688 100 650 72T570 24T481 -6T375 -16Q316 -16 265 0T175 50T115 133T92 253Q92 313 125 368T231 467T424 537T715 569V648Q715 766 665 826T515 887Q450 887
406 871T329 834T273 797T226 780Q208 780 195 789T173 813L141 870Q225 951 322 991T537 1031Q622 1031 688 1003T799 925T867 804T890 648V0ZM428 109Q475 109 514 118T587 145T653 188T715 245V456Q592 452 506 437T366 396T288 337T263 261Q263 221 276 192T311
145T363 118T428 109Z" />
<glyph unicode="b" glyph-name="b" horiz-adv-x="1118" d="M152 0V1473H331V867Q394 940 475 984T662 1029Q750 1029 821 996T942 898T1019 736T1046 513Q1046 401 1016 305T930 138T792 27T608 -14Q510 -14 442 24T322 130L313 38Q305 0 267 0H152ZM604 887Q517
887 452 847T331 734V244Q379 178 437 151T568 124Q710 124 786 225T862 513Q862 612 845 683T794 799T713 866T604 887Z" />
<glyph unicode="c" glyph-name="c" horiz-adv-x="934" d="M837 833Q829 822 821 816T798 810Q783 810 766 822T721 850T656 877T561 890Q487 890 430 864T335 787T277 666T257 507Q257 415 278 344T337 224T429 150T552 124Q617 124 659 139T729 174T775 208T811
224Q834 224 845 207L895 142Q829 61 730 24T521 -14Q426 -14 345 21T203 122T109 286T74 507Q74 620 105 716T197 881T347 990T551 1029Q657 1029 739 995T884 897L837 833Z" />
<glyph unicode="d" glyph-name="d" horiz-adv-x="1118" d="M859 0Q821 0 811 37L795 160Q730 81 647 34T455 -14Q368 -14 297 19T176 118T99 280T72 503Q72 615 102 711T188 879T326 990T510 1031Q603 1031 669 1000T787 911V1473H965V0H859ZM514 130Q601 130
666 170T787 283V773Q738 839 680 865T550 892Q408 892 332 791T256 503Q256 404 273 334T323 218T404 151T514 130Z" />
<glyph unicode="e" glyph-name="e" horiz-adv-x="1048" d="M547 1029Q638 1029 715 999T848 911T935 769T967 576Q967 534 958 520T924 506H250Q252 410 276 339T342 221T442 150T572 126Q639 126 687 141T771 175T829 208T870 224Q892 224 904 207L954 142Q921
102 875 73T777 24T668 -4T557 -14Q452 -14 364 21T211 125T110 295T74 527Q74 633 106 725T200 884T349 990T547 1029ZM551 898Q422 898 348 824T256 617H807Q807 679 790 730T740 819T660 877T551 898Z" />
<glyph unicode="f" glyph-name="f" horiz-adv-x="674" d="M186 0V861L74 874Q53 879 40 889T26 920V993H186V1091Q186 1178 210 1245T280 1359T390 1430T534 1454Q602 1454 660 1434L656 1345Q655 1325 639 1321T594 1317H563Q517 1317 480 1305T415 1266T374
1195T359 1086V993H652V864H365V0H186Z" />
<glyph unicode="g" glyph-name="g" horiz-adv-x="1022" d="M487 1030Q553 1030 610 1016T715 973H990V907Q990 874 948 865L833 849Q867 784 867 704Q867 630 839 570T760 466T640 400T487 377Q416 377 353 394Q321 374 305 351T288 306Q288 270 317 252T394 225T503
215T627 209T752 193T861 157T938 88T967 -26Q967 -91 935 -152T841 -260T692 -336T493 -365Q382 -365 299 -343T160 -284T78 -199T50 -97Q50 -22 97 30T228 114Q185 134 160 167T134 257Q134 279 142 302T166 349T207 393T263 430Q188 472 146 541T103 704Q103
778 131 838T211 941T332 1007T487 1030ZM803 -55Q803 -17 782 6T725 41T642 60T543 68T437 74T334 85Q277 58 242 19T206 -74Q206 -108 223 -137T277 -188T367 -222T496 -235Q568 -235 625 -222T721 -185T782 -128T803 -55ZM487 495Q541 495 582 510T652 552T694
616T708 699Q708 792 652 847T487 902Q380 902 324 847T267 699Q267 654 281 617T324 552T393 510T487 495Z" />
<glyph unicode="h" glyph-name="h" horiz-adv-x="1112" d="M146 0V1473H324V877Q389 946 468 987T650 1029Q733 1029 796 1002T902 924T967 802T989 645V0H811V645Q811 760 759 823T598 887Q519 887 451 849T324 746V0H146Z" />
<glyph unicode="i" glyph-name="i" horiz-adv-x="512" d="M344 1013V0H166V1013H344ZM384 1331Q384 1305 374 1283T346 1243T305 1215T256 1205Q230 1205 208 1215T168 1242T140 1282T130 1331Q130 1357 140 1380T167 1421T207 1449T256 1459Q282 1459 305 1449T345
1422T373 1381T384 1331Z" />
<glyph unicode="j" glyph-name="j" horiz-adv-x="508" d="M344 1013V-75Q344 -136 328 -188T278 -280T189 -341T58 -364Q25 -364 -2 -359T-56 -344L-48 -248Q-46 -235 -39 -232T-17 -228Q-9 -228 0 -228T22 -229Q100 -229 133 -193T166 -75V1013H344ZM384 1331Q384
1305 374 1283T346 1243T305 1215T256 1205Q230 1205 208 1215T168 1242T140 1282T130 1331Q130 1357 140 1380T167 1421T207 1449T256 1459Q282 1459 305 1449T345 1422T373 1381T384 1331Z" />
<glyph unicode="k" glyph-name="k" horiz-adv-x="1048" d="M331 1473V606H377Q397 606 410 611T439 634L759 977Q774 993 789 1003T829 1013H991L618 616Q604 599 591 586T560 563Q578 551 592 536T620 500L1016 0H856Q834 0 819 8T789 35L456 450Q441 471 426
477T381 484H331V0H152V1473H331Z" />
<glyph unicode="l" glyph-name="l" horiz-adv-x="512" d="M344 1473V0H166V1473H344Z" />
<glyph unicode="m" glyph-name="m" horiz-adv-x="1642" d="M146 0V1013H252Q290 1013 300 976L313 872Q369 941 438 985T600 1029Q703 1029 766 972T858 818Q879 873 913 913T991 979T1082 1017T1181 1029Q1261 1029 1323 1004T1429 929T1496 809T1519 645V0H1341V645Q1341
764 1289 825T1138 887Q1094 887 1055 872T985 826T938 751T920 645V0H742V645Q742 767 693 827T550 887Q484 887 428 852T324 755V0H146Z" />
<glyph unicode="n" glyph-name="n" horiz-adv-x="1112" d="M146 0V1013H252Q290 1013 300 976L314 866Q380 939 461 984T650 1029Q733 1029 796 1002T902 924T967 802T989 645V0H811V645Q811 760 759 823T598 887Q519 887 451 849T324 746V0H146Z" />
<glyph unicode="o" glyph-name="o" horiz-adv-x="1112" d="M556 1029Q667 1029 756 992T908 887T1004 723T1038 507Q1038 387 1005 291T909 127T757 23T556 -14Q445 -14 356 22T203 127T106 291T72 507Q72 626 106 722T203 887T355 992T556 1029ZM556 125Q706
125 780 225T854 506Q854 687 780 788T556 889Q480 889 424 863T331 788T275 668T256 506Q256 416 274 345T330 226T424 151T556 125Z" />
<glyph unicode="p" glyph-name="p" horiz-adv-x="1104" d="M146 -343V1013H252Q290 1013 300 976L315 856Q380 935 463 983T656 1031Q743 1031 814 998T935 899T1012 736T1039 513Q1039 401 1009 305T923 138T786 27T602 -14Q508 -14 442 17T324 105V-343H146ZM597
887Q510 887 445 847T324 734V244Q373 178 431 151T562 124Q703 124 779 225T855 513Q855 612 838 683T787 799T706 866T597 887Z" />
<glyph unicode="q" glyph-name="q" horiz-adv-x="1118" d="M965 1013V-343H787V150Q723 76 642 31T455 -14Q368 -14 297 19T176 118T99 280T72 503Q72 615 102 711T188 879T326 990T510 1031Q608 1031 676 996T799 897L811 976Q821 1013 859 1013H965ZM514 130Q601
130 666 170T787 283V773Q739 837 680 864T550 892Q408 892 332 791T256 503Q256 404 273 334T323 218T404 151T514 130Z" />
<glyph unicode="r" glyph-name="r" horiz-adv-x="806" d="M146 0V1013H248Q277 1013 288 1002T303 964L315 806Q367 912 443 971T623 1031Q665 1031 699 1022T762 995L739 862Q732 837 708 837Q694 837 665 846T584 856Q491 856 429 802T324 645V0H146Z" />
<glyph unicode="s" glyph-name="s" horiz-adv-x="868" d="M726 846Q714 824 689 824Q674 824 655 835T609 859T543 884T453 896Q408 896 372 885T311 853T272 807T258 749Q258 710 280 684T340 639T424 606T520 575T617 538T701 488T760 415T783 310Q783 240 758
181T684 78T564 9T400 -16Q294 -16 208 18T62 107L104 175Q112 188 123 195T152 202Q170 202 190 188T238 157T307 126T409 112Q461 112 500 125T565 162T603 215T616 279Q616 321 594 348T534 395T450 429T353 460T256 496T171 548T112 624T89 735Q89 793 113
846T183 940T296 1005T449 1029Q549 1029 628 998T766 911L726 846Z" />
<glyph unicode="t" glyph-name="t" horiz-adv-x="746" d="M453 -16Q333 -16 269 51T204 244V864H82Q66 864 55 873T44 903V974L210 995L251 1308Q253 1323 264 1332T292 1342H382V993H672V864H382V256Q382 192 413 161T493 130Q521 130 541 137T577 154T602 170T621
178Q635 178 646 161L698 76Q652 33 587 9T453 -16Z" />
<glyph unicode="u" glyph-name="u" horiz-adv-x="1112" d="M300 1013V367Q300 252 353 189T513 126Q591 126 660 163T787 266V1013H965V0H859Q821 0 811 37L797 146Q731 73 649 29T461 -16Q378 -16 315 11T208 89T144 210T122 367V1013H300Z" />
<glyph unicode="v" glyph-name="v" horiz-adv-x="1024" d="M18 1013H164Q185 1013 199 1002T219 976L476 324Q490 288 498 252T513 181Q521 216 530 252T553 324L813 976Q819 992 832 1002T866 1013H1005L592 0H431L18 1013Z" />
<glyph unicode="w" glyph-name="w" horiz-adv-x="1532" d="M14 1013H154Q176 1013 190 1002T209 976L403 324Q411 288 418 255T430 187Q438 221 448 254T469 324L683 980Q688 995 700 1005T732 1015H809Q829 1015 842 1005T860 980L1069 324Q1080 289 1088 255T1105
188Q1110 221 1118 257T1135 324L1333 976Q1338 992 1352 1002T1385 1013H1519L1191 0H1050Q1024 0 1014 34L790 721Q782 744 777 767T767 814Q762 791 757 767T744 720L517 34Q506 0 476 0H342L14 1013Z" />
<glyph unicode="x" glyph-name="x" horiz-adv-x="1008" d="M383 519L42 1013H213Q235 1013 245 1006T263 986L511 606Q520 634 537 662L755 982Q765 996 775 1004T800 1013H964L623 529L978 0H807Q785 0 773 11T752 37L497 434Q490 405 476 382L240 37Q230 23
219 12T187 0H28L383 519Z" />
<glyph unicode="y" glyph-name="y" horiz-adv-x="1024" d="M443 -299Q434 -319 421 -331T379 -343H247L432 59L14 1013H168Q191 1013 204 1002T223 976L494 338Q503 316 509 294T521 249Q528 272 535 294T551 339L814 976Q820 992 834 1002T866 1013H1008L443 -299Z" />
<glyph unicode="z" glyph-name="z" horiz-adv-x="924" d="M853 937Q853 918 846 901T828 869L280 139H833V0H70V74Q70 87 76 104T95 138L646 873H101V1013H853V937Z" />
<glyph unicode="{" glyph-name="braceleft" horiz-adv-x="600" d="M181 425Q181 488 146 528T44 569V676Q111 676 146 716T181 820Q181 870 173 919T156 1017T138 1116T130 1218Q130 1287 150 1345T212 1445T315 1510T459 1533H512V1454Q512 1434 498 1425T472
1416H452Q375 1416 331 1366T286 1229Q286 1173 293 1121T309 1020T325 921T332 822Q332 784 321 752T290 694T242 650T181 622Q214 613 241 595T289 550T321 492T332 423Q332 373 325 324T309 226T293 124T286 16Q286 -71 330 -121T452 -171H472Q484 -171 498
-180T512 -209V-289H459Q377 -289 316 -266T213 -200T151 -100T130 27Q130 79 138 128T155 227T173 326T181 425Z" />
<glyph unicode="|" glyph-name="bar" horiz-adv-x="600" d="M230 1533H368V-343H230V1533Z" />
<glyph unicode="}" glyph-name="braceright" horiz-adv-x="600" d="M419 425Q419 375 427 326T444 228T462 129T470 27Q470 -42 449 -100T387 -200T285 -265T141 -289H88V-209Q88 -189 102 -180T128 -171H148Q225 -171 269 -121T314 16Q314 72 307 124T291 225T275
324T268 423Q268 460 279 492T310 550T358 594T419 622Q386 631 359 649T311 693T279 751T268 822Q268 872 275 921T291 1019T307 1121T314 1229Q314 1315 270 1365T148 1416H128Q116 1416 102 1425T88 1454V1533H141Q223 1533 284 1510T387 1445T449 1345T470
1218Q470 1166 462 1116T445 1017T427 919T419 820Q419 756 454 716T556 676V569Q489 569 454 529T419 425Z" />
<glyph unicode="~" glyph-name="asciitilde" horiz-adv-x="1160" d="M759 613Q824 613 860 655T898 768H1042Q1042 701 1024 645T971 549T885 487T770 465Q718 465 667 481T569 516T478 552T399 569Q334 569 298 527T260 414H116Q116 481 134 537T187 633T272
695T388 718Q440 718 491 702T589 666T680 630T759 613Z" />
<glyph unicode="&#xa0;" glyph-name="uni00A0" horiz-adv-x="386" />
<glyph unicode="&#xa1;" glyph-name="exclamdown" horiz-adv-x="686" d="M262 -343V198Q262 243 263 285T268 371T275 461T286 559H407Q413 507 417 461T425 372T429 286T431 198V-343H262ZM218 904Q218 931 227 953T254 993T293 1019T343 1029Q369 1029 391 1020T431
993T458 954T468 904Q468 878 458 855T431 815T392 788T343 778Q316 778 294 788T254 815T228 855T218 904Z" />
<glyph unicode="&#xa2;" glyph-name="cent" horiz-adv-x="1160" d="M561 -11Q469 -1 392 38T258 144T170 301T138 506Q138 617 171 710T267 873T422 982T633 1026L645 1205Q647 1225 660 1239T695 1254H761L745 1021Q827 1009 893 978T1013 897L967 835Q959 824
952 819T930 813Q918 813 902 821T863 842T809 866T735 885L683 123Q746 127 788 142T860 175T909 206T944 220Q955 220 964 216T978 204L1026 141Q966 69 874 32T674 -12L662 -187Q660 -206 647 -220T612 -235H546L561 -11ZM315 506Q315 344 382 248T571 129L623
889Q547 883 490 855T393 777T335 659T315 506Z" />
<glyph unicode="&#xa3;" glyph-name="sterling" horiz-adv-x="1160" d="M52 672Q52 698 68 716T113 734H247V995Q247 1089 274 1171T356 1315T493 1412T685 1448Q763 1448 823 1429T931 1375T1012 1296T1071 1199L999 1153Q989 1147 979 1145T958 1142Q944 1142
932 1147T908 1167Q888 1192 868 1215T822 1256T763 1284T685 1295Q622 1295 574 1274T494 1214T446 1120T430 997V734H871V662Q871 644 856 629T819 614H430V371Q430 296 402 242T323 142Q352 147 380 150T439 154H1115V78Q1115 64 1110 51T1094 26T1070 7T1038
0H74V115Q108 125 139 142T194 184T232 244T247 325V614H52V672Z" />
<glyph unicode="&#xa4;" glyph-name="currency" horiz-adv-x="1160" d="M223 672Q223 729 239 779T285 874L132 1027L223 1117L374 965Q418 996 470 1013T580 1030Q637 1030 688 1014T782 967L935 1120L1024 1029L873 877Q904 833 921 782T938 672Q938 615 922
564T876 470L1028 319L937 227L785 379Q741 349 689 332T580 315Q523 315 473 331T378 377L225 224L136 315L287 467Q257 511 240 562T223 672ZM355 672Q355 626 372 586T421 515T492 466T580 448Q627 448 668 466T740 514T789 585T807 672Q807 719 789 760T741
832T669 880T580 898Q533 898 493 881T421 832T373 760T355 672Z" />
<glyph unicode="&#xa5;" glyph-name="yen" horiz-adv-x="1160" d="M146 625H452L44 1433H193Q219 1433 234 1421T260 1388L536 822Q550 787 560 758T577 700Q584 729 593 758T616 822L891 1388Q899 1405 915 1419T957 1433H1107L698 625H1005V523H665V418H1005V315H665V0H486V315H146V418H486V523H146V625Z"
/>
<glyph unicode="&#xa6;" glyph-name="brokenbar" horiz-adv-x="600" d="M230 1533H368V739H230V1533ZM230 452H368V-343H230V452Z" />
<glyph unicode="&#xa7;" glyph-name="section" horiz-adv-x="1006" d="M817 1265Q805 1243 780 1243Q765 1243 746 1254T700 1278T634 1303T544 1315Q496 1315 458 1303T393 1269T352 1220T338 1161Q338 1123 362 1095T426 1043T517 998T620 953T724 903T814 840T878
758T903 651Q903 570 864 507T741 405Q790 368 821 319T852 201Q852 131 827 72T754 -31T634 -100T470 -125Q364 -125 278 -91T132 -2L173 66Q181 79 192 86T221 93Q239 93 259 79T308 48T379 16T485 2Q535 2 574 14T641 49T682 103T696 172Q696 217 671 250T605
308T512 355T405 398T298 445T205 506T139 587T114 698Q114 776 157 838T293 936Q243 974 212 1026T180 1154Q180 1212 204 1265T274 1359T387 1423T540 1447Q640 1447 719 1416T857 1330L817 1265ZM272 726Q272 675 307 640T396 576T517 521T645 463Q699 489 723
526T747 611Q747 647 732 675T691 726T630 768T556 804T474 839T392 877Q326 847 299 811T272 726Z" />
<glyph unicode="&#xa8;" glyph-name="dieresis" horiz-adv-x="614" d="M239 1289Q239 1266 230 1246T205 1211T169 1187T125 1178Q103 1178 83 1187T48 1211T23 1246T14 1289Q14 1312 23 1333T47 1369T83 1394T125 1403Q148 1403 168 1394T205 1370T230 1333T239
1289ZM598 1289Q598 1266 589 1246T565 1211T529 1187T485 1178Q462 1178 442 1187T406 1211T382 1246T373 1289Q373 1312 382 1333T406 1369T441 1394T485 1403Q508 1403 528 1394T564 1370T589 1333T598 1289Z" />
<glyph unicode="&#xa9;" glyph-name="copyright" horiz-adv-x="1596" d="M1030 463Q1038 468 1043 471T1055 475Q1066 475 1070 472T1080 463L1141 399Q1084 333 1001 297T802 260Q704 260 624 294T486 389T397 534T365 718Q365 819 399 903T495 1048T639 1142T821
1176Q929 1176 1005 1142T1138 1053L1092 988Q1087 982 1079 976T1059 970Q1045 970 1030 981T989 1005T926 1030T829 1042Q759 1042 703 1020T607 955T546 853T524 718Q524 641 545 581T605 479T696 417T811 395Q859 395 892 401T949 417T991 439T1030 463ZM68
716Q68 817 94 910T167 1085T282 1233T430 1348T604 1422T798 1448Q899 1448 992 1422T1167 1348T1315 1234T1429 1086T1503 911T1529 716Q1529 616 1503 523T1430 348T1315 201T1167 86T993 12T798 -14Q697 -14 604 12T430 86T282 200T168 348T94 522T68 716ZM168
716Q168 627 190 545T253 392T351 262T478 163T629 99T798 76Q930 76 1045 126T1246 262T1381 465T1431 716Q1431 805 1409 888T1345 1042T1247 1173T1119 1273T968 1338T798 1361Q666 1361 551 1311T351 1173T217 968T168 716Z" />
<glyph unicode="&#xaa;" glyph-name="ordfeminine" horiz-adv-x="684" d="M596 840H536Q518 840 509 845T492 869L480 918Q456 897 433 881T385 854T332 837T270 831Q232 831 200 841T143 871T106 922T92 993Q92 1027 111 1060T174 1120T290 1164T470 1184V1221Q470
1284 441 1314T355 1344Q317 1344 292 1335T249 1316T216 1297T185 1288Q171 1288 161 1295T146 1313L124 1355Q176 1404 236 1427T370 1450Q424 1450 466 1433T537 1386T581 1314T596 1221V840ZM309 923Q360 923 397 942T470 996V1101Q400 1099 352 1091T273 1069T230
1038T217 999Q217 957 243 940T309 923Z" />
<glyph unicode="&#xab;" glyph-name="guillemotleft" horiz-adv-x="926" d="M138 518V541L387 930L445 902Q459 895 466 884T473 860Q473 843 463 827L304 566Q290 542 276 529Q291 515 304 493L463 232Q468 224 470 215T473 198Q473 170 445 157L387 129L138
518ZM434 518V541L683 930L741 902Q755 895 762 884T769 860Q769 843 759 827L600 566Q586 542 572 529Q587 515 600 493L759 232Q764 224 766 215T769 198Q769 170 741 157L683 129L434 518Z" />
<glyph unicode="&#xac;" glyph-name="logicalnot" horiz-adv-x="1160" d="M148 739H1008V315H857V604H148V739Z" />
<glyph unicode="&#xad;" glyph-name="uni00AD" horiz-adv-x="694" d="M100 675H594V524H100V675Z" />
<glyph unicode="&#xae;" glyph-name="registered" horiz-adv-x="1596" d="M68 716Q68 817 94 910T167 1085T282 1233T430 1348T604 1422T798 1448Q899 1448 992 1422T1167 1348T1315 1234T1429 1086T1503 911T1529 716Q1529 616 1503 523T1430 348T1315 201T1167
86T993 12T798 -14Q697 -14 604 12T430 86T282 200T168 348T94 522T68 716ZM168 716Q168 627 190 545T253 392T351 262T478 163T629 99T798 76Q930 76 1045 126T1246 262T1381 465T1431 716Q1431 805 1409 888T1345 1042T1247 1173T1119 1273T968 1338T798 1361Q666
1361 551 1311T351 1173T217 968T168 716ZM654 626V272H498V1164H786Q958 1164 1041 1102T1124 917Q1124 823 1071 757T911 666Q928 656 940 641T964 606L1192 272H1044Q1011 272 995 297L794 599Q785 612 773 619T734 626H654ZM654 740H770Q825 740 863 750T925
781T959 830T970 897Q970 934 961 962T930 1008T873 1035T786 1044H654V740Z" />
<glyph unicode="&#xaf;" glyph-name="overscore" horiz-adv-x="614" d="M20 1348H594V1231H20V1348Z" />
<glyph unicode="&#xb0;" glyph-name="degree" horiz-adv-x="794" d="M70 1128Q70 1195 95 1254T164 1356T267 1425T396 1450Q465 1450 524 1425T628 1357T697 1254T722 1128Q722 1062 697 1004T628 902T525 833T396 807Q327 807 268 832T164 901T95 1003T70 1128ZM197
1127Q197 1085 212 1049T254 985T317 943T396 927Q438 927 474 942T537 985T579 1048T594 1127Q594 1169 579 1206T537 1270T474 1313T396 1329Q354 1329 318 1314T254 1271T212 1206T197 1127Z" />
<glyph unicode="&#xb1;" glyph-name="plusminus" horiz-adv-x="1160" d="M651 1202V826H1058V690H651V322H505V690H100V826H505V1202H651ZM100 215H1058V80H100V215Z" />
<glyph unicode="&#xb2;" glyph-name="twosuperior" horiz-adv-x="664" d="M346 1637Q398 1637 440 1622T513 1580T559 1515T576 1429Q576 1389 564 1355T530 1289T482 1229T425 1170L263 1005Q286 1011 309 1015T354 1019H549Q570 1019 581 1008T593 977V900H82V943Q82
956 87 970T104 996L325 1215Q350 1240 372 1266T410 1318T435 1371T445 1425Q445 1476 415 1503T340 1531Q294 1531 266 1507T223 1441Q215 1427 206 1419T179 1411Q175 1411 171 1411T161 1413L90 1425Q105 1531 174 1584T346 1637Z" />
<glyph unicode="&#xb3;" glyph-name="threesuperior" horiz-adv-x="664" d="M354 1637Q405 1637 446 1623T516 1583T562 1523T578 1449Q578 1321 459 1276Q525 1257 559 1218T594 1117Q594 1062 573 1020T518 950T438 907T344 892Q287 892 245 904T173 941T121
1002T84 1085L139 1109Q154 1115 168 1115Q197 1115 208 1092Q214 1079 223 1063T247 1033T283 1010T337 1000Q368 1000 391 1010T431 1035T455 1071T463 1112Q463 1142 455 1163T427 1199T375 1220T295 1227V1314Q382 1315 417 1345T453 1427Q453 1477 424 1503T347
1529Q299 1529 271 1506T230 1442Q222 1426 214 1419T190 1411Q186 1411 182 1411T172 1413L105 1425Q112 1478 134 1517T188 1583T263 1623T354 1637Z" />
<glyph unicode="&#xb4;" glyph-name="acute" horiz-adv-x="614" d="M597 1449L364 1191Q350 1176 337 1170T302 1163H196L344 1405Q358 1428 374 1438T423 1449H597Z" />
<glyph unicode="&#xb5;" glyph-name="mu" horiz-adv-x="1112" d="M300 1013V355Q300 246 354 186T513 126Q591 126 660 163T787 266V1013H965V0H859Q821 0 811 37L797 146Q730 74 660 40T502 6Q428 6 372 31T277 103Q284 61 287 18T290 -64V-343H201Q163 -343
143 -323T122 -267V1013H300Z" />
<glyph unicode="&#xb6;" glyph-name="paragraph" horiz-adv-x="1338" d="M1302 1433V1280H1083V-201H926V1280H649V-201H492V660Q388 660 305 690T164 773T74 897T42 1049Q42 1135 73 1205T163 1326T305 1405T492 1433H1302Z" />
<glyph unicode="&#xb7;" glyph-name="middot" horiz-adv-x="546" d="M124 593Q124 624 135 652T167 700T215 732T272 744Q303 744 331 732T379 700T411 652T423 593Q423 563 411 536T379 489T331 457T272 445Q242 445 215 456T168 488T136 536T124 593Z" />
<glyph unicode="&#xb8;" glyph-name="cedilla" horiz-adv-x="614" d="M172 -247Q178 -247 186 -250T205 -258T232 -266T269 -270Q311 -270 332 -254T354 -211Q354 -192 343 -179T312 -157T261 -142T193 -131L236 10H348L324 -70Q414 -90 454 -124T495 -213Q495
-245 479 -270T435 -314T366 -341T278 -351Q237 -351 200 -343T132 -320L149 -265Q155 -247 172 -247Z" />
<glyph unicode="&#xb9;" glyph-name="onesuperior" horiz-adv-x="664" d="M173 985H320V1425L324 1468L217 1380Q205 1371 191 1371Q168 1371 159 1385L120 1441L342 1631H450V985H580V900H173V985Z" />
<glyph unicode="&#xba;" glyph-name="ordmasculine" horiz-adv-x="762" d="M382 1449Q452 1449 508 1428T605 1366T667 1269T689 1140Q689 1068 667 1010T605 912T509 850T382 828Q311 828 254 849T157 911T94 1010T72 1140Q72 1211 94 1268T156 1366T254 1427T382
1449ZM382 934Q466 934 507 986T549 1139Q549 1239 508 1291T382 1343Q295 1343 254 1291T212 1139Q212 1039 253 987T382 934Z" />
<glyph unicode="&#xbb;" glyph-name="guillemotright" horiz-adv-x="926" d="M236 129L178 157Q150 170 150 198Q150 215 160 232L319 493Q332 517 346 529Q334 540 319 566L160 827Q150 844 150 861Q150 889 178 902L236 930L485 541V518L236 129ZM781 541V518L532
129L474 157Q446 170 446 198Q446 215 456 232L615 493Q628 517 642 529Q630 540 615 566L456 827Q446 844 446 861Q446 889 474 902L532 930L781 541Z" />
<glyph unicode="&#xbc;" glyph-name="onequarter" horiz-adv-x="1424" d="M1295 267H1404V202Q1404 191 1397 184T1377 176H1295V0H1186V176H880Q862 176 852 184T839 204L829 261L1171 729H1295V267ZM155 788H302V1228L306 1271L199 1183Q187 1174 173 1174Q150
1174 141 1188L102 1244L324 1434H432V788H562V703H155V788ZM1186 508Q1186 527 1187 549T1191 594L950 267H1186V508ZM434 53Q415 22 393 11T342 0H266L1084 1372Q1102 1401 1125 1417T1180 1433H1257L434 53Z" />
<glyph unicode="&#xbd;" glyph-name="onehalf" horiz-adv-x="1424" d="M1126 737Q1178 737 1220 722T1293 680T1339 615T1356 529Q1356 489 1344 455T1310 389T1262 329T1205 270L1043 105Q1066 111 1089 115T1134 119H1329Q1350 119 1361 108T1373 77V0H862V43Q862
56 867 70T884 96L1105 315Q1130 340 1152 366T1190 418T1215 471T1225 525Q1225 576 1195 603T1120 631Q1074 631 1046 607T1003 541Q995 527 986 519T959 511Q955 511 951 511T941 513L870 525Q885 631 954 684T1126 737ZM155 788H302V1228L306 1271L199 1183Q187
1174 173 1174Q150 1174 141 1188L102 1244L324 1434H432V788H562V703H155V788ZM390 53Q371 22 349 11T298 0H222L1040 1372Q1058 1401 1081 1417T1136 1433H1213L390 53Z" />
<glyph unicode="&#xbe;" glyph-name="threequarters" horiz-adv-x="1426" d="M1296 267H1405V202Q1405 191 1398 184T1378 176H1296V0H1187V176H881Q863 176 853 184T840 204L830 261L1172 729H1296V267ZM338 1440Q389 1440 430 1426T500 1386T546 1326T562 1252Q562
1124 443 1079Q509 1060 543 1021T578 920Q578 865 557 823T502 753T422 710T328 695Q271 695 229 707T157 744T105 805T68 888L123 912Q138 918 152 918Q181 918 192 895Q198 882 207 866T231 836T267 813T321 803Q352 803 375 813T415 838T439 874T447 915Q447
945 439 966T411 1002T359 1023T279 1030V1117Q366 1118 401 1148T437 1230Q437 1280 408 1306T331 1332Q283 1332 255 1309T214 1245Q206 1229 198 1222T174 1214Q170 1214 166 1214T156 1216L89 1228Q96 1281 118 1320T172 1386T247 1426T338 1440ZM1187 508Q1187
527 1188 549T1192 594L951 267H1187V508ZM439 53Q420 22 398 11T347 0H271L1089 1372Q1107 1401 1130 1417T1185 1433H1262L439 53Z" />
<glyph unicode="&#xbf;" glyph-name="questiondown" horiz-adv-x="796" d="M770 -212Q739 -241 702 -267T620 -313T524 -344T412 -356Q333 -356 266 -334T149 -271T72 -171T44 -37Q44 39 66 92T123 183T199 248T275 299T336 347T366 405L384 559H506L518 392V380Q518
336 496 305T439 249T365 201T291 147T234 77T211 -22Q211 -66 228 -100T274 -159T342 -197T427 -210Q488 -210 531 -195T605 -162T654 -129T685 -114Q699 -114 707 -120T723 -137L770 -212ZM324 903Q324 929 333 952T359 992T399 1019T448 1029Q474 1029 497 1019T537
992T563 952T573 903Q573 876 564 854T537 814T497 788T448 778Q422 778 399 787T360 814T334 853T324 903Z" />
<glyph unicode="&#xc0;" glyph-name="Agrave" horiz-adv-x="1360" d="M1353 0H1203Q1177 0 1161 13T1137 46L1003 392H360L226 46Q219 28 202 14T160 0H10L583 1433H780L1353 0ZM414 532H949L724 1115Q702 1169 681 1250Q670 1209 660 1175T639 1114L414 532ZM520
1782Q552 1782 568 1776T604 1749L815 1546H676Q655 1546 643 1549T614 1565L319 1782H520Z" />
<glyph unicode="&#xc1;" glyph-name="Aacute" horiz-adv-x="1360" d="M1353 0H1203Q1177 0 1161 13T1137 46L1003 392H360L226 46Q219 28 202 14T160 0H10L583 1433H780L1353 0ZM414 532H949L724 1115Q702 1169 681 1250Q670 1209 660 1175T639 1114L414 532ZM1021
1782L727 1566Q710 1554 697 1550T663 1546H525L736 1749Q746 1759 754 1765T772 1775T792 1780T820 1782H1021Z" />
<glyph unicode="&#xc2;" glyph-name="Acircumflex" horiz-adv-x="1360" d="M1353 0H1203Q1177 0 1161 13T1137 46L1003 392H360L226 46Q219 28 202 14T160 0H10L583 1433H780L1353 0ZM414 532H949L724 1115Q702 1169 681 1250Q670 1209 660 1175T639 1114L414
532ZM1006 1546H871Q859 1546 845 1549T822 1559L692 1654Q684 1658 680 1662Q672 1656 668 1654L538 1559Q529 1553 515 1550T489 1546H354L592 1756H768L1006 1546Z" />
<glyph unicode="&#xc3;" glyph-name="Atilde" horiz-adv-x="1360" d="M1353 0H1203Q1177 0 1161 13T1137 46L1003 392H360L226 46Q219 28 202 14T160 0H10L583 1433H780L1353 0ZM414 532H949L724 1115Q702 1169 681 1250Q670 1209 660 1175T639 1114L414 532ZM801
1663Q836 1663 854 1683T874 1741H972Q972 1698 961 1662T929 1600T877 1559T806 1544Q771 1544 739 1557T678 1585T622 1614T572 1627Q538 1627 520 1606T500 1548H400Q400 1591 411 1627T444 1690T497 1731T568 1746Q603 1746 635 1733T696 1705T751 1676T801
1663Z" />
<glyph unicode="&#xc4;" glyph-name="Adieresis" horiz-adv-x="1360" d="M1353 0H1203Q1177 0 1161 13T1137 46L1003 392H360L226 46Q219 28 202 14T160 0H10L583 1433H780L1353 0ZM414 532H949L724 1115Q702 1169 681 1250Q670 1209 660 1175T639 1114L414 532ZM578
1666Q578 1644 569 1625T545 1590T510 1567T466 1558Q445 1558 426 1566T391 1590T367 1624T358 1666Q358 1689 367 1709T391 1745T425 1769T466 1778Q489 1778 509 1769T545 1745T569 1710T578 1666ZM1004 1666Q1004 1644 995 1625T971 1590T936 1567T894 1558Q871
1558 851 1566T816 1590T793 1624T784 1666Q784 1689 792 1709T816 1745T851 1769T894 1778Q916 1778 936 1769T971 1745T995 1710T1004 1666Z" />
<glyph unicode="&#xc5;" glyph-name="Aring" horiz-adv-x="1360" d="M1353 0H1203Q1177 0 1161 13T1137 46L1003 392H360L226 46Q219 28 202 14T160 0H10L583 1433H780L1353 0ZM414 532H949L724 1115Q702 1169 681 1250Q670 1209 660 1175T639 1114L414 532ZM489
1659Q489 1698 504 1731T545 1787T605 1824T677 1837Q716 1837 750 1824T811 1788T853 1731T868 1659Q868 1621 853 1589T812 1534T751 1498T677 1485Q639 1485 605 1498T546 1534T505 1589T489 1659ZM578 1659Q578 1616 605 1588T679 1559Q724 1559 751 1587T779
1659Q779 1704 752 1732T679 1760Q632 1760 605 1732T578 1659Z" />
<glyph unicode="&#xc6;" glyph-name="AE" horiz-adv-x="1858" d="M733 1433H1754V1275H1005L1065 799H1624V647H1084L1145 158H1754V0H982L933 392H377L198 45Q187 25 169 13T124 0H-24L733 1433ZM450 532H915L821 1285Q809 1244 795 1210T766 1144L450 532Z" />
<glyph unicode="&#xc7;" glyph-name="Ccedilla" horiz-adv-x="1370" d="M643 -247Q649 -247 657 -250T676 -258T703 -266T740 -270Q782 -270 803 -254T825 -211Q825 -192 814 -179T783 -157T732 -142T664 -131L700 -13Q561 -1 448 57T256 211T133 435T90 716Q90
878 142 1013T290 1245T517 1395T810 1449Q968 1449 1082 1400T1286 1267L1223 1178Q1216 1168 1207 1162T1181 1155Q1168 1155 1154 1164T1119 1188T1072 1218T1008 1248T922 1271T809 1281Q694 1281 599 1242T434 1129T327 951T288 716Q288 582 326 477T431 300T589
189T785 151Q849 151 900 158T994 182T1075 222T1151 281Q1168 296 1184 296Q1200 296 1213 283L1289 200Q1206 103 1090 47T811 -15L795 -70Q885 -90 925 -124T966 -213Q966 -245 950 -270T906 -314T837 -341T749 -351Q708 -351 671 -343T603 -320L620 -265Q626
-247 643 -247Z" />
<glyph unicode="&#xc8;" glyph-name="Egrave" horiz-adv-x="1162" d="M1057 1433V1275H369V799H926V647H369V158H1057V0H174V1433H1057ZM468 1782Q500 1782 516 1776T552 1749L763 1546H624Q603 1546 591 1549T562 1565L267 1782H468Z" />
<glyph unicode="&#xc9;" glyph-name="Eacute" horiz-adv-x="1162" d="M1057 1433V1275H369V799H926V647H369V158H1057V0H174V1433H1057ZM969 1782L675 1566Q658 1554 645 1550T611 1546H473L684 1749Q694 1759 702 1765T720 1775T740 1780T768 1782H969Z" />
<glyph unicode="&#xca;" glyph-name="Ecircumflex" horiz-adv-x="1162" d="M1057 1433V1275H369V799H926V647H369V158H1057V0H174V1433H1057ZM954 1546H819Q807 1546 793 1549T770 1559L640 1654Q632 1658 628 1662Q620 1656 616 1654L486 1559Q477 1553 463 1550T437
1546H302L540 1756H716L954 1546Z" />
<glyph unicode="&#xcb;" glyph-name="Edieresis" horiz-adv-x="1162" d="M1057 1433V1275H369V799H926V647H369V158H1057V0H174V1433H1057ZM526 1666Q526 1644 517 1625T493 1590T458 1567T414 1558Q393 1558 374 1566T339 1590T315 1624T306 1666Q306 1689 315
1709T339 1745T373 1769T414 1778Q437 1778 457 1769T493 1745T517 1710T526 1666ZM952 1666Q952 1644 943 1625T919 1590T884 1567T842 1558Q819 1558 799 1566T764 1590T741 1624T732 1666Q732 1689 740 1709T764 1745T799 1769T842 1778Q864 1778 884 1769T919
1745T943 1710T952 1666Z" />
<glyph unicode="&#xcc;" glyph-name="Igrave" horiz-adv-x="614" d="M404 0H210V1433H404V0ZM405 1782Q437 1782 453 1776T489 1749L700 1546H561Q540 1546 528 1549T499 1565L204 1782H405Z" />
<glyph unicode="&#xcd;" glyph-name="Iacute" horiz-adv-x="614" d="M404 0H210V1433H404V0ZM906 1782L612 1566Q595 1554 582 1550T548 1546H410L621 1749Q631 1759 639 1765T657 1775T677 1780T705 1782H906Z" />
<glyph unicode="&#xce;" glyph-name="Icircumflex" horiz-adv-x="614" d="M404 0H210V1433H404V0ZM635 1546H500Q488 1546 474 1549T451 1559L321 1654Q313 1658 309 1662Q301 1656 297 1654L167 1559Q158 1553 144 1550T118 1546H-17L221 1756H397L635 1546Z" />
<glyph unicode="&#xcf;" glyph-name="Idieresis" horiz-adv-x="614" d="M404 0H210V1433H404V0ZM206 1666Q206 1644 197 1625T173 1590T138 1567T94 1558Q73 1558 54 1566T19 1590T-5 1624T-14 1666Q-14 1689 -5 1709T19 1745T53 1769T94 1778Q117 1778 137 1769T173
1745T197 1710T206 1666ZM632 1666Q632 1644 623 1625T599 1590T564 1567T522 1558Q499 1558 479 1566T444 1590T421 1624T412 1666Q412 1689 420 1709T444 1745T479 1769T522 1778Q544 1778 564 1769T599 1745T623 1710T632 1666Z" />
<glyph unicode="&#xd0;" glyph-name="Eth" horiz-adv-x="1578" d="M50 780H247V1433H782Q940 1433 1070 1382T1294 1236T1438 1009T1489 716Q1489 555 1438 423T1294 197T1071 52T782 0H247V666H50V780ZM1290 716Q1290 848 1254 952T1152 1128T992 1238T782 1276H441V780H822V666H441V157H782Q898
157 992 195T1152 304T1254 480T1290 716Z" />
<glyph unicode="&#xd1;" glyph-name="Ntilde" horiz-adv-x="1512" d="M274 1433Q300 1433 312 1427T341 1400L1171 320Q1168 346 1167 370T1166 418V1433H1336V0H1238Q1215 0 1200 8T1169 35L340 1114Q342 1089 343 1065T344 1021V0H174V1433H274V1433ZM901 1663Q936
1663 954 1683T974 1741H1072Q1072 1698 1061 1662T1029 1600T977 1559T906 1544Q871 1544 839 1557T778 1585T722 1614T672 1627Q638 1627 620 1606T600 1548H500Q500 1591 511 1627T544 1690T597 1731T668 1746Q703 1746 735 1733T796 1705T851 1676T901 1663Z"
/>
<glyph unicode="&#xd2;" glyph-name="Ograve" horiz-adv-x="1596" d="M1505 716Q1505 555 1454 421T1310 189T1087 39T798 -15Q640 -15 510 38T287 189T143 420T92 716Q92 877 143 1011T287 1243T510 1395T798 1449Q956 1449 1086 1395T1310 1244T1454 1012T1505
716ZM1306 716Q1306 848 1270 953T1168 1130T1008 1242T798 1281Q683 1281 589 1242T429 1131T326 953T290 716Q290 584 326 480T428 303T589 192T798 153Q914 153 1008 191T1168 302T1270 479T1306 716ZM640 1782Q672 1782 688 1776T724 1749L935 1546H796Q775
1546 763 1549T734 1565L439 1782H640Z" />
<glyph unicode="&#xd3;" glyph-name="Oacute" horiz-adv-x="1596" d="M1505 716Q1505 555 1454 421T1310 189T1087 39T798 -15Q640 -15 510 38T287 189T143 420T92 716Q92 877 143 1011T287 1243T510 1395T798 1449Q956 1449 1086 1395T1310 1244T1454 1012T1505
716ZM1306 716Q1306 848 1270 953T1168 1130T1008 1242T798 1281Q683 1281 589 1242T429 1131T326 953T290 716Q290 584 326 480T428 303T589 192T798 153Q914 153 1008 191T1168 302T1270 479T1306 716ZM1141 1782L847 1566Q830 1554 817 1550T783 1546H645L856
1749Q866 1759 874 1765T892 1775T912 1780T940 1782H1141Z" />
<glyph unicode="&#xd4;" glyph-name="Ocircumflex" horiz-adv-x="1596" d="M1505 716Q1505 555 1454 421T1310 189T1087 39T798 -15Q640 -15 510 38T287 189T143 420T92 716Q92 877 143 1011T287 1243T510 1395T798 1449Q956 1449 1086 1395T1310 1244T1454 1012T1505
716ZM1306 716Q1306 848 1270 953T1168 1130T1008 1242T798 1281Q683 1281 589 1242T429 1131T326 953T290 716Q290 584 326 480T428 303T589 192T798 153Q914 153 1008 191T1168 302T1270 479T1306 716ZM1126 1546H991Q979 1546 965 1549T942 1559L812 1654Q804
1658 800 1662Q792 1656 788 1654L658 1559Q649 1553 635 1550T609 1546H474L712 1756H888L1126 1546Z" />
<glyph unicode="&#xd5;" glyph-name="Otilde" horiz-adv-x="1596" d="M1505 716Q1505 555 1454 421T1310 189T1087 39T798 -15Q640 -15 510 38T287 189T143 420T92 716Q92 877 143 1011T287 1243T510 1395T798 1449Q956 1449 1086 1395T1310 1244T1454 1012T1505
716ZM1306 716Q1306 848 1270 953T1168 1130T1008 1242T798 1281Q683 1281 589 1242T429 1131T326 953T290 716Q290 584 326 480T428 303T589 192T798 153Q914 153 1008 191T1168 302T1270 479T1306 716ZM921 1663Q956 1663 974 1683T994 1741H1092Q1092 1698 1081
1662T1049 1600T997 1559T926 1544Q891 1544 859 1557T798 1585T742 1614T692 1627Q658 1627 640 1606T620 1548H520Q520 1591 531 1627T564 1690T617 1731T688 1746Q723 1746 755 1733T816 1705T871 1676T921 1663Z" />
<glyph unicode="&#xd6;" glyph-name="Odieresis" horiz-adv-x="1596" d="M1505 716Q1505 555 1454 421T1310 189T1087 39T798 -15Q640 -15 510 38T287 189T143 420T92 716Q92 877 143 1011T287 1243T510 1395T798 1449Q956 1449 1086 1395T1310 1244T1454 1012T1505
716ZM1306 716Q1306 848 1270 953T1168 1130T1008 1242T798 1281Q683 1281 589 1242T429 1131T326 953T290 716Q290 584 326 480T428 303T589 192T798 153Q914 153 1008 191T1168 302T1270 479T1306 716ZM698 1666Q698 1644 689 1625T665 1590T630 1567T586 1558Q565
1558 546 1566T511 1590T487 1624T478 1666Q478 1689 487 1709T511 1745T545 1769T586 1778Q609 1778 629 1769T665 1745T689 1710T698 1666ZM1124 1666Q1124 1644 1115 1625T1091 1590T1056 1567T1014 1558Q991 1558 971 1566T936 1590T913 1624T904 1666Q904
1689 912 1709T936 1745T971 1769T1014 1778Q1036 1778 1056 1769T1091 1745T1115 1710T1124 1666Z" />
<glyph unicode="&#xd7;" glyph-name="multiply" horiz-adv-x="1160" d="M1017 1014L673 670L1027 317L932 221L578 575L221 219L126 315L482 671L137 1016L232 1112L577 766L921 1110L1017 1014Z" />
<glyph unicode="&#xd8;" glyph-name="Oslash" horiz-adv-x="1596" d="M1505 716Q1505 555 1454 421T1310 189T1087 39T798 -15Q690 -15 596 9T423 82L323 -54Q301 -83 272 -96T214 -109H136L327 151Q215 249 154 393T92 716Q92 877 143 1011T287 1243T510 1395T798
1449Q913 1449 1013 1420T1196 1337L1278 1448Q1298 1475 1314 1486T1362 1498H1462L1290 1263Q1393 1165 1449 1026T1505 716ZM290 716Q290 581 327 476T434 297L1093 1196Q1033 1238 959 1259T798 1281Q683 1281 589 1242T429 1131T326 953T290 716ZM1306 716Q1306
842 1274 942T1181 1115L526 223Q642 153 798 153Q914 153 1008 191T1168 302T1270 479T1306 716Z" />
<glyph unicode="&#xd9;" glyph-name="Ugrave" horiz-adv-x="1460" d="M731 154Q820 154 890 184T1008 268T1082 397T1108 562V1433H1301V562Q1301 438 1262 332T1149 149T969 27T731 -17Q599 -17 493 27T313 148T200 332T160 562V1433H353V563Q353 473 378 398T452
269T571 185T731 154ZM570 1782Q602 1782 618 1776T654 1749L865 1546H726Q705 1546 693 1549T664 1565L369 1782H570Z" />
<glyph unicode="&#xda;" glyph-name="Uacute" horiz-adv-x="1460" d="M731 154Q820 154 890 184T1008 268T1082 397T1108 562V1433H1301V562Q1301 438 1262 332T1149 149T969 27T731 -17Q599 -17 493 27T313 148T200 332T160 562V1433H353V563Q353 473 378 398T452
269T571 185T731 154ZM1071 1782L777 1566Q760 1554 747 1550T713 1546H575L786 1749Q796 1759 804 1765T822 1775T842 1780T870 1782H1071Z" />
<glyph unicode="&#xdb;" glyph-name="Ucircumflex" horiz-adv-x="1460" d="M731 154Q820 154 890 184T1008 268T1082 397T1108 562V1433H1301V562Q1301 438 1262 332T1149 149T969 27T731 -17Q599 -17 493 27T313 148T200 332T160 562V1433H353V563Q353 473 378
398T452 269T571 185T731 154ZM1056 1546H921Q909 1546 895 1549T872 1559L742 1654Q734 1658 730 1662Q722 1656 718 1654L588 1559Q579 1553 565 1550T539 1546H404L642 1756H818L1056 1546Z" />
<glyph unicode="&#xdc;" glyph-name="Udieresis" horiz-adv-x="1460" d="M731 154Q820 154 890 184T1008 268T1082 397T1108 562V1433H1301V562Q1301 438 1262 332T1149 149T969 27T731 -17Q599 -17 493 27T313 148T200 332T160 562V1433H353V563Q353 473 378
398T452 269T571 185T731 154ZM628 1666Q628 1644 619 1625T595 1590T560 1567T516 1558Q495 1558 476 1566T441 1590T417 1624T408 1666Q408 1689 417 1709T441 1745T475 1769T516 1778Q539 1778 559 1769T595 1745T619 1710T628 1666ZM1054 1666Q1054 1644 1045
1625T1021 1590T986 1567T944 1558Q921 1558 901 1566T866 1590T843 1624T834 1666Q834 1689 842 1709T866 1745T901 1769T944 1778Q966 1778 986 1769T1021 1745T1045 1710T1054 1666Z" />
<glyph unicode="&#xdd;" glyph-name="Yacute" horiz-adv-x="1258" d="M726 570V0H533V570L8 1433H178Q204 1433 219 1420T245 1388L573 831Q593 796 606 765T631 704Q642 735 655 766T688 831L1015 1388Q1024 1404 1039 1418T1080 1433H1252L726 570ZM971 1782L677
1566Q660 1554 647 1550T613 1546H475L686 1749Q696 1759 704 1765T722 1775T742 1780T770 1782H971Z" />
<glyph unicode="&#xde;" glyph-name="Thorn" horiz-adv-x="1222" d="M387 272V0H194V1433H387V1169H617Q753 1169 853 1138T1020 1048T1118 908T1151 725Q1151 625 1116 542T1014 399T846 306T617 272H387ZM387 426H617Q700 426 763 448T870 509T935 604T957 725Q957
862 873 939T617 1016H387V426Z" />
<glyph unicode="&#xdf;" glyph-name="germandbls" horiz-adv-x="1218" d="M673 1454Q776 1454 851 1424T976 1348T1048 1246T1072 1140Q1072 1080 1051 1037T997 961T928 903T858 854T805 805T783 747Q783 708 809 682T876 634T962 588T1049 529T1115 443T1142
314Q1142 236 1114 175T1035 72T918 7T771 -16Q674 -16 595 18T456 107L497 175Q505 188 516 195T545 202Q563 202 583 188T630 157T695 126T786 112Q830 112 865 125T924 162T962 217T975 286Q975 342 947 377T877 437T786 484T695 534T625 604T597 712Q597 765
619 804T675 875T748 934T820 991T876 1056T899 1140Q899 1172 887 1204T846 1263T774 1305T666 1322Q598 1322 543 1301T448 1238T387 1133T365 986V0H186V992Q186 1096 220 1181T319 1327T473 1421T673 1454Z" />
<glyph unicode="&#xe0;" glyph-name="agrave" horiz-adv-x="1014" d="M890 0H811Q785 0 769 8T748 42L728 136Q688 100 650 72T570 24T481 -6T375 -16Q316 -16 265 0T175 50T115 133T92 253Q92 313 125 368T231 467T424 537T715 569V648Q715 766 665 826T515 887Q450
887 406 871T329 834T273 797T226 780Q208 780 195 789T173 813L141 870Q225 951 322 991T537 1031Q622 1031 688 1003T799 925T867 804T890 648V0ZM428 109Q475 109 514 118T587 145T653 188T715 245V456Q592 452 506 437T366 396T288 337T263 261Q263 221 276
192T311 145T363 118T428 109ZM428 1449Q461 1449 477 1439T507 1405L656 1163H554Q533 1163 520 1169T493 1191L259 1449H428Z" />
<glyph unicode="&#xe1;" glyph-name="aacute" horiz-adv-x="1014" d="M890 0H811Q785 0 769 8T748 42L728 136Q688 100 650 72T570 24T481 -6T375 -16Q316 -16 265 0T175 50T115 133T92 253Q92 313 125 368T231 467T424 537T715 569V648Q715 766 665 826T515 887Q450
887 406 871T329 834T273 797T226 780Q208 780 195 789T173 813L141 870Q225 951 322 991T537 1031Q622 1031 688 1003T799 925T867 804T890 648V0ZM428 109Q475 109 514 118T587 145T653 188T715 245V456Q592 452 506 437T366 396T288 337T263 261Q263 221 276
192T311 145T363 118T428 109ZM818 1449L585 1191Q571 1176 558 1170T523 1163H417L565 1405Q579 1428 595 1438T644 1449H818Z" />
<glyph unicode="&#xe2;" glyph-name="acircumflex" horiz-adv-x="1014" d="M890 0H811Q785 0 769 8T748 42L728 136Q688 100 650 72T570 24T481 -6T375 -16Q316 -16 265 0T175 50T115 133T92 253Q92 313 125 368T231 467T424 537T715 569V648Q715 766 665 826T515
887Q450 887 406 871T329 834T273 797T226 780Q208 780 195 789T173 813L141 870Q225 951 322 991T537 1031Q622 1031 688 1003T799 925T867 804T890 648V0ZM428 109Q475 109 514 118T587 145T653 188T715 245V456Q592 452 506 437T366 396T288 337T263 261Q263
221 276 192T311 145T363 118T428 109ZM833 1169H714Q693 1169 674 1183L546 1309L529 1326L513 1309L384 1183Q378 1178 367 1174T344 1169H221L444 1433H610L833 1169Z" />
<glyph unicode="&#xe3;" glyph-name="atilde" horiz-adv-x="1014" d="M890 0H811Q785 0 769 8T748 42L728 136Q688 100 650 72T570 24T481 -6T375 -16Q316 -16 265 0T175 50T115 133T92 253Q92 313 125 368T231 467T424 537T715 569V648Q715 766 665 826T515 887Q450
887 406 871T329 834T273 797T226 780Q208 780 195 789T173 813L141 870Q225 951 322 991T537 1031Q622 1031 688 1003T799 925T867 804T890 648V0ZM428 109Q475 109 514 118T587 145T653 188T715 245V456Q592 452 506 437T366 396T288 337T263 261Q263 221 276
192T311 145T363 118T428 109ZM638 1325Q674 1325 693 1346T714 1411H822Q822 1364 810 1325T774 1257T718 1214T645 1198Q610 1198 580 1212T522 1244T471 1275T424 1290Q352 1290 350 1202H239Q239 1250 252 1289T289 1357T346 1401T418 1417Q453 1417 483 1403T541
1371T591 1340T638 1325Z" />
<glyph unicode="&#xe4;" glyph-name="adieresis" horiz-adv-x="1014" d="M890 0H811Q785 0 769 8T748 42L728 136Q688 100 650 72T570 24T481 -6T375 -16Q316 -16 265 0T175 50T115 133T92 253Q92 313 125 368T231 467T424 537T715 569V648Q715 766 665 826T515
887Q450 887 406 871T329 834T273 797T226 780Q208 780 195 789T173 813L141 870Q225 951 322 991T537 1031Q622 1031 688 1003T799 925T867 804T890 648V0ZM428 109Q475 109 514 118T587 145T653 188T715 245V456Q592 452 506 437T366 396T288 337T263 261Q263
221 276 192T311 145T363 118T428 109ZM460 1289Q460 1266 451 1246T426 1211T390 1187T346 1178Q324 1178 304 1187T269 1211T244 1246T235 1289Q235 1312 244 1333T268 1369T304 1394T346 1403Q369 1403 389 1394T426 1370T451 1333T460 1289ZM819 1289Q819 1266
810 1246T786 1211T750 1187T706 1178Q683 1178 663 1187T627 1211T603 1246T594 1289Q594 1312 603 1333T627 1369T662 1394T706 1403Q729 1403 749 1394T785 1370T810 1333T819 1289Z" />
<glyph unicode="&#xe5;" glyph-name="aring" horiz-adv-x="1014" d="M890 0H811Q785 0 769 8T748 42L728 136Q688 100 650 72T570 24T481 -6T375 -16Q316 -16 265 0T175 50T115 133T92 253Q92 313 125 368T231 467T424 537T715 569V648Q715 766 665 826T515 887Q450
887 406 871T329 834T273 797T226 780Q208 780 195 789T173 813L141 870Q225 951 322 991T537 1031Q622 1031 688 1003T799 925T867 804T890 648V0ZM428 109Q475 109 514 118T587 145T653 188T715 245V456Q592 452 506 437T366 396T288 337T263 261Q263 221 276
192T311 145T363 118T428 109ZM328 1315Q328 1357 344 1391T387 1450T451 1488T527 1502Q568 1502 604 1489T669 1450T713 1391T729 1315Q729 1274 713 1240T669 1182T605 1145T527 1131Q487 1131 451 1144T388 1182T344 1240T328 1315ZM428 1315Q428 1271 455
1243T529 1215Q574 1215 601 1243T629 1315Q629 1360 602 1388T529 1416Q482 1416 455 1388T428 1315Z" />
<glyph unicode="&#xe6;" glyph-name="ae" horiz-adv-x="1632" d="M1166 1029Q1248 1029 1318 997T1440 904T1521 756T1551 556Q1551 515 1543 501T1510 486H883Q887 395 910 328T971 215T1062 148T1180 126Q1249 126 1295 140T1371 173T1421 205T1456 220Q1470
220 1479 216T1494 203L1541 142Q1508 102 1465 73T1372 24T1269 -4T1164 -14Q1047 -14 952 42T801 215Q774 153 731 109T634 37T522 -3T403 -16Q334 -16 277 1T179 54T115 142T92 267Q92 327 125 385T231 489T424 564T715 598V648Q715 766 665 829T515 892Q450
892 406 875T329 836T273 798T226 780Q208 780 195 789T173 813L141 870Q225 951 315 991T519 1031Q639 1031 712 980T818 838Q872 926 958 977T1166 1029ZM715 486Q592 481 506 464T366 418T288 354T263 276Q263 189 313 149T444 109Q501 109 550 127T636 182T694
274T715 403V486ZM1159 898Q1098 898 1050 878T968 818T913 723T885 597H1393Q1393 662 1378 717T1332 813T1259 875T1159 898Z" />
<glyph unicode="&#xe7;" glyph-name="ccedilla" horiz-adv-x="934" d="M400 -247Q406 -247 414 -250T433 -258T460 -266T497 -270Q539 -270 560 -254T582 -211Q582 -192 571 -179T540 -157T489 -142T421 -131L458 -10Q375 1 305 40T183 146T103 303T74 507Q74
620 105 716T197 881T347 990T551 1029Q657 1029 739 995T884 897L837 833Q829 822 821 816T798 810Q783 810 766 822T721 850T656 877T561 890Q487 890 430 864T335 787T277 666T257 507Q257 415 278 344T337 224T429 150T552 124Q617 124 659 139T729 174T775
208T811 224Q822 224 830 220T845 207L895 142Q836 70 751 33T569 -12L552 -70Q642 -90 682 -124T723 -213Q723 -245 707 -270T663 -314T594 -341T506 -351Q465 -351 428 -343T360 -320L377 -265Q383 -247 400 -247Z" />
<glyph unicode="&#xe8;" glyph-name="egrave" horiz-adv-x="1048" d="M547 1029Q638 1029 715 999T848 911T935 769T967 576Q967 534 958 520T924 506H250Q252 410 276 339T342 221T442 150T572 126Q639 126 687 141T771 175T829 208T870 224Q892 224 904 207L954
142Q921 102 875 73T777 24T668 -4T557 -14Q452 -14 364 21T211 125T110 295T74 527Q74 633 106 725T200 884T349 990T547 1029ZM551 898Q422 898 348 824T256 617H807Q807 679 790 730T740 819T660 877T551 898ZM451 1449Q484 1449 500 1439T530 1405L679 1163H577Q556
1163 543 1169T516 1191L282 1449H451Z" />
<glyph unicode="&#xe9;" glyph-name="eacute" horiz-adv-x="1048" d="M547 1029Q638 1029 715 999T848 911T935 769T967 576Q967 534 958 520T924 506H250Q252 410 276 339T342 221T442 150T572 126Q639 126 687 141T771 175T829 208T870 224Q892 224 904 207L954
142Q921 102 875 73T777 24T668 -4T557 -14Q452 -14 364 21T211 125T110 295T74 527Q74 633 106 725T200 884T349 990T547 1029ZM551 898Q422 898 348 824T256 617H807Q807 679 790 730T740 819T660 877T551 898ZM841 1449L608 1191Q594 1176 581 1170T546 1163H440L588
1405Q602 1428 618 1438T667 1449H841Z" />
<glyph unicode="&#xea;" glyph-name="ecircumflex" horiz-adv-x="1048" d="M547 1029Q638 1029 715 999T848 911T935 769T967 576Q967 534 958 520T924 506H250Q252 410 276 339T342 221T442 150T572 126Q639 126 687 141T771 175T829 208T870 224Q892 224 904
207L954 142Q921 102 875 73T777 24T668 -4T557 -14Q452 -14 364 21T211 125T110 295T74 527Q74 633 106 725T200 884T349 990T547 1029ZM551 898Q422 898 348 824T256 617H807Q807 679 790 730T740 819T660 877T551 898ZM856 1169H737Q716 1169 697 1183L569 1309L552
1326L536 1309L407 1183Q401 1178 390 1174T367 1169H244L467 1433H633L856 1169Z" />
<glyph unicode="&#xeb;" glyph-name="edieresis" horiz-adv-x="1048" d="M547 1029Q638 1029 715 999T848 911T935 769T967 576Q967 534 958 520T924 506H250Q252 410 276 339T342 221T442 150T572 126Q639 126 687 141T771 175T829 208T870 224Q892 224 904 207L954
142Q921 102 875 73T777 24T668 -4T557 -14Q452 -14 364 21T211 125T110 295T74 527Q74 633 106 725T200 884T349 990T547 1029ZM551 898Q422 898 348 824T256 617H807Q807 679 790 730T740 819T660 877T551 898ZM483 1289Q483 1266 474 1246T449 1211T413 1187T369
1178Q347 1178 327 1187T292 1211T267 1246T258 1289Q258 1312 267 1333T291 1369T327 1394T369 1403Q392 1403 412 1394T449 1370T474 1333T483 1289ZM842 1289Q842 1266 833 1246T809 1211T773 1187T729 1178Q706 1178 686 1187T650 1211T626 1246T617 1289Q617
1312 626 1333T650 1369T685 1394T729 1403Q752 1403 772 1394T808 1370T833 1333T842 1289Z" />
<glyph unicode="&#xec;" glyph-name="igrave" horiz-adv-x="512" d="M344 1013V0H166V1013H344ZM418 1449Q451 1449 467 1439T497 1405L646 1163H544Q523 1163 510 1169T483 1191L249 1449H418Z" />
<glyph unicode="&#xed;" glyph-name="iacute" horiz-adv-x="512" d="M344 1013V0H166V1013H344ZM808 1449L575 1191Q561 1176 548 1170T513 1163H407L555 1405Q569 1428 585 1438T634 1449H808Z" />
<glyph unicode="&#xee;" glyph-name="icircumflex" horiz-adv-x="512" d="M344 1013V0H166V1013H344ZM822 1169H703Q682 1169 663 1183L535 1309L518 1326L502 1309L373 1183Q367 1178 356 1174T333 1169H210L433 1433H599L822 1169Z" />
<glyph unicode="&#xef;" glyph-name="idieresis" horiz-adv-x="512" d="M344 1013V0H166V1013H344ZM450 1289Q450 1266 441 1246T416 1211T380 1187T336 1178Q314 1178 294 1187T259 1211T234 1246T225 1289Q225 1312 234 1333T258 1369T294 1394T336 1403Q359
1403 379 1394T416 1370T441 1333T450 1289ZM809 1289Q809 1266 800 1246T776 1211T740 1187T696 1178Q673 1178 653 1187T617 1211T593 1246T584 1289Q584 1312 593 1333T617 1369T652 1394T696 1403Q719 1403 739 1394T775 1370T800 1333T809 1289Z" />
<glyph unicode="&#xf0;" glyph-name="eth" horiz-adv-x="1106" d="M417 1065Q413 1072 411 1078T408 1091Q408 1113 431 1128L534 1200Q489 1220 439 1237T331 1268Q313 1273 301 1286T288 1323Q288 1338 293 1352L313 1414Q409 1398 499 1368T670 1290L837 1412L872
1355Q880 1342 880 1331Q880 1310 858 1294L761 1227Q821 1178 870 1116T955 977T1009 807T1029 604Q1029 461 998 347T906 153T753 30T541 -13Q443 -13 358 20T211 114T112 265T76 466Q76 560 107 643T196 790T336 890T523 927Q623 927 711 884T865 754Q845 890
787 985T634 1144L450 1009L417 1065ZM545 125Q616 125 673 152T771 233T835 370T861 564Q845 607 819 647T756 719T669 769T557 788Q482 788 426 763T331 695T274 592T254 464Q254 383 277 320T340 213T432 148T545 125Z" />
<glyph unicode="&#xf1;" glyph-name="ntilde" horiz-adv-x="1112" d="M146 0V1013H252Q290 1013 300 976L314 866Q380 939 461 984T650 1029Q733 1029 796 1002T902 924T967 802T989 645V0H811V645Q811 760 759 823T598 887Q519 887 451 849T324 746V0H146ZM675
1325Q711 1325 730 1346T751 1411H859Q859 1364 847 1325T811 1257T755 1214T682 1198Q647 1198 617 1212T559 1244T508 1275T461 1290Q389 1290 387 1202H276Q276 1250 289 1289T326 1357T383 1401T455 1417Q490 1417 520 1403T578 1371T628 1340T675 1325Z" />
<glyph unicode="&#xf2;" glyph-name="ograve" horiz-adv-x="1112" d="M556 1029Q667 1029 756 992T908 887T1004 723T1038 507Q1038 387 1005 291T909 127T757 23T556 -14Q445 -14 356 22T203 127T106 291T72 507Q72 626 106 722T203 887T355 992T556 1029ZM556
125Q706 125 780 225T854 506Q854 687 780 788T556 889Q480 889 424 863T331 788T275 668T256 506Q256 416 274 345T330 226T424 151T556 125ZM458 1449Q491 1449 507 1439T537 1405L686 1163H584Q563 1163 550 1169T523 1191L289 1449H458Z" />
<glyph unicode="&#xf3;" glyph-name="oacute" horiz-adv-x="1112" d="M556 1029Q667 1029 756 992T908 887T1004 723T1038 507Q1038 387 1005 291T909 127T757 23T556 -14Q445 -14 356 22T203 127T106 291T72 507Q72 626 106 722T203 887T355 992T556 1029ZM556
125Q706 125 780 225T854 506Q854 687 780 788T556 889Q480 889 424 863T331 788T275 668T256 506Q256 416 274 345T330 226T424 151T556 125ZM848 1449L615 1191Q601 1176 588 1170T553 1163H447L595 1405Q609 1428 625 1438T674 1449H848Z" />
<glyph unicode="&#xf4;" glyph-name="ocircumflex" horiz-adv-x="1112" d="M556 1029Q667 1029 756 992T908 887T1004 723T1038 507Q1038 387 1005 291T909 127T757 23T556 -14Q445 -14 356 22T203 127T106 291T72 507Q72 626 106 722T203 887T355 992T556 1029ZM556
125Q706 125 780 225T854 506Q854 687 780 788T556 889Q480 889 424 863T331 788T275 668T256 506Q256 416 274 345T330 226T424 151T556 125ZM863 1169H744Q723 1169 704 1183L576 1309L559 1326L543 1309L414 1183Q408 1178 397 1174T374 1169H251L474 1433H640L863
1169Z" />
<glyph unicode="&#xf5;" glyph-name="otilde" horiz-adv-x="1112" d="M556 1029Q667 1029 756 992T908 887T1004 723T1038 507Q1038 387 1005 291T909 127T757 23T556 -14Q445 -14 356 22T203 127T106 291T72 507Q72 626 106 722T203 887T355 992T556 1029ZM556
125Q706 125 780 225T854 506Q854 687 780 788T556 889Q480 889 424 863T331 788T275 668T256 506Q256 416 274 345T330 226T424 151T556 125ZM668 1325Q704 1325 723 1346T744 1411H852Q852 1364 840 1325T804 1257T748 1214T675 1198Q640 1198 610 1212T552 1244T501
1275T454 1290Q382 1290 380 1202H269Q269 1250 282 1289T319 1357T376 1401T448 1417Q483 1417 513 1403T571 1371T621 1340T668 1325Z" />
<glyph unicode="&#xf6;" glyph-name="odieresis" horiz-adv-x="1112" d="M556 1029Q667 1029 756 992T908 887T1004 723T1038 507Q1038 387 1005 291T909 127T757 23T556 -14Q445 -14 356 22T203 127T106 291T72 507Q72 626 106 722T203 887T355 992T556 1029ZM556
125Q706 125 780 225T854 506Q854 687 780 788T556 889Q480 889 424 863T331 788T275 668T256 506Q256 416 274 345T330 226T424 151T556 125ZM490 1289Q490 1266 481 1246T456 1211T420 1187T376 1178Q354 1178 334 1187T299 1211T274 1246T265 1289Q265 1312
274 1333T298 1369T334 1394T376 1403Q399 1403 419 1394T456 1370T481 1333T490 1289ZM849 1289Q849 1266 840 1246T816 1211T780 1187T736 1178Q713 1178 693 1187T657 1211T633 1246T624 1289Q624 1312 633 1333T657 1369T692 1394T736 1403Q759 1403 779 1394T815
1370T840 1333T849 1289Z" />
<glyph unicode="&#xf7;" glyph-name="divide" horiz-adv-x="1160" d="M100 739H1058V604H100V739ZM454 1026Q454 1052 463 1075T489 1115T528 1142T578 1152Q604 1152 626 1142T666 1115T693 1075T703 1026Q703 999 693 977T666 937T627 911T578 901Q551 901 529
910T490 937T464 976T454 1026ZM454 314Q454 340 463 363T489 403T528 430T578 440Q604 440 626 430T666 403T693 363T703 314Q703 287 693 265T666 225T627 199T578 189Q551 189 529 198T490 225T464 264T454 314Z" />
<glyph unicode="&#xf8;" glyph-name="oslash" horiz-adv-x="1112" d="M912 884Q973 816 1006 721T1039 507Q1039 387 1006 291T910 127T758 23T557 -14Q481 -14 416 3T296 52L241 -22Q219 -51 190 -63T131 -76H64L209 120Q143 189 108 286T73 507Q73 626 107 722T204
887T356 992T557 1029Q636 1029 703 1010T827 956L895 1047Q915 1074 931 1085T979 1097H1069L912 884ZM246 506Q246 346 305 249L741 839Q668 895 557 895Q481 895 423 868T326 791T266 669T246 506ZM557 120Q632 120 689 146T786 223T846 344T866 506Q866 657
814 753L381 168Q451 120 557 120Z" />
<glyph unicode="&#xf9;" glyph-name="ugrave" horiz-adv-x="1112" d="M300 1013V367Q300 252 353 189T513 126Q591 126 660 163T787 266V1013H965V0H859Q821 0 811 37L797 146Q731 73 649 29T461 -16Q378 -16 315 11T208 89T144 210T122 367V1013H300ZM452 1449Q485
1449 501 1439T531 1405L680 1163H578Q557 1163 544 1169T517 1191L283 1449H452Z" />
<glyph unicode="&#xfa;" glyph-name="uacute" horiz-adv-x="1112" d="M300 1013V367Q300 252 353 189T513 126Q591 126 660 163T787 266V1013H965V0H859Q821 0 811 37L797 146Q731 73 649 29T461 -16Q378 -16 315 11T208 89T144 210T122 367V1013H300ZM842 1449L609
1191Q595 1176 582 1170T547 1163H441L589 1405Q603 1428 619 1438T668 1449H842Z" />
<glyph unicode="&#xfb;" glyph-name="ucircumflex" horiz-adv-x="1112" d="M300 1013V367Q300 252 353 189T513 126Q591 126 660 163T787 266V1013H965V0H859Q821 0 811 37L797 146Q731 73 649 29T461 -16Q378 -16 315 11T208 89T144 210T122 367V1013H300ZM857
1169H738Q717 1169 698 1183L570 1309L553 1326L537 1309L408 1183Q402 1178 391 1174T368 1169H245L468 1433H634L857 1169Z" />
<glyph unicode="&#xfc;" glyph-name="udieresis" horiz-adv-x="1112" d="M300 1013V367Q300 252 353 189T513 126Q591 126 660 163T787 266V1013H965V0H859Q821 0 811 37L797 146Q731 73 649 29T461 -16Q378 -16 315 11T208 89T144 210T122 367V1013H300ZM484
1289Q484 1266 475 1246T450 1211T414 1187T370 1178Q348 1178 328 1187T293 1211T268 1246T259 1289Q259 1312 268 1333T292 1369T328 1394T370 1403Q393 1403 413 1394T450 1370T475 1333T484 1289ZM843 1289Q843 1266 834 1246T810 1211T774 1187T730 1178Q707
1178 687 1187T651 1211T627 1246T618 1289Q618 1312 627 1333T651 1369T686 1394T730 1403Q753 1403 773 1394T809 1370T834 1333T843 1289Z" />
<glyph unicode="&#xfd;" glyph-name="yacute" horiz-adv-x="1024" d="M443 -299Q434 -319 421 -331T379 -343H247L432 59L14 1013H168Q191 1013 204 1002T223 976L494 338Q503 316 509 294T521 249Q528 272 535 294T551 339L814 976Q820 992 834 1002T866 1013H1008L443
-299ZM825 1449L592 1191Q578 1176 565 1170T530 1163H424L572 1405Q586 1428 602 1438T651 1449H825Z" />
<glyph unicode="&#xfe;" glyph-name="thorn" horiz-adv-x="1104" d="M146 -343V1473H324V866Q387 940 469 984T656 1029Q743 1029 814 996T935 898T1012 736T1039 513Q1039 401 1009 305T923 138T786 27T602 -14Q507 -14 441 20T324 118V-343H146ZM597 887Q510
887 445 847T324 734V244Q373 178 431 151T562 124Q703 124 779 225T855 513Q855 612 838 683T787 799T706 866T597 887Z" />
<glyph unicode="&#xff;" glyph-name="ydieresis" horiz-adv-x="1024" d="M443 -299Q434 -319 421 -331T379 -343H247L432 59L14 1013H168Q191 1013 204 1002T223 976L494 338Q503 316 509 294T521 249Q528 272 535 294T551 339L814 976Q820 992 834 1002T866 1013H1008L443
-299ZM467 1289Q467 1266 458 1246T433 1211T397 1187T353 1178Q331 1178 311 1187T276 1211T251 1246T242 1289Q242 1312 251 1333T275 1369T311 1394T353 1403Q376 1403 396 1394T433 1370T458 1333T467 1289ZM826 1289Q826 1266 817 1246T793 1211T757 1187T713
1178Q690 1178 670 1187T634 1211T610 1246T601 1289Q601 1312 610 1333T634 1369T669 1394T713 1403Q736 1403 756 1394T792 1370T817 1333T826 1289Z" />
<glyph unicode="&#x2013;" glyph-name="endash" horiz-adv-x="1112" d="M156 655H956V525H156V655Z" />
<glyph unicode="&#x2014;" glyph-name="emdash" horiz-adv-x="1642" d="M156 655H1486V525H156V655Z" />
<glyph unicode="&#x2018;" glyph-name="quoteleft" horiz-adv-x="424" d="M114 1012Q85 1060 72 1108T58 1205Q58 1295 102 1377T227 1529L282 1495Q290 1490 292 1484T295 1472Q295 1458 285 1448Q269 1428 254 1406T227 1360T208 1308T201 1251Q201 1219 210
1185T243 1113Q250 1102 250 1090Q250 1066 223 1056L114 1012Z" />
<glyph unicode="&#x2019;" glyph-name="quoteright" horiz-adv-x="424" d="M271 1508Q300 1460 313 1412T326 1316Q326 1225 282 1143T158 991L103 1025Q95 1030 93 1036T90 1048Q90 1062 100 1072Q116 1091 131 1113T158 1160T177 1212T184 1269Q184 1301 175
1335T142 1407Q135 1418 135 1429Q135 1453 162 1464L271 1508Z" />
<glyph unicode="&#x201a;" glyph-name="quotesinglbase" horiz-adv-x="424" d="M271 241Q300 193 313 145T326 49Q326 -42 282 -124T158 -276L103 -242Q95 -237 93 -231T90 -219Q90 -205 100 -195Q116 -176 131 -154T158 -107T177 -55T184 2Q184 34 175 68T142
140Q135 151 135 162Q135 186 162 197L271 241Z" />
<glyph unicode="&#x201c;" glyph-name="quotedblleft" horiz-adv-x="728" d="M114 1012Q85 1060 72 1108T58 1205Q58 1295 102 1377T227 1529L282 1495Q290 1490 292 1484T295 1472Q295 1458 285 1448Q269 1428 254 1406T227 1360T208 1308T201 1251Q201 1219
210 1185T243 1113Q250 1102 250 1090Q250 1066 223 1056L114 1012ZM418 1012Q389 1060 376 1108T362 1205Q362 1295 406 1377T531 1529L586 1495Q594 1490 596 1484T599 1472Q599 1458 589 1448Q573 1428 558 1406T531 1360T512 1308T505 1251Q505 1219 514 1185T547
1113Q554 1102 554 1090Q554 1066 527 1056L418 1012Z" />
<glyph unicode="&#x201d;" glyph-name="quotedblright" horiz-adv-x="728" d="M271 1508Q300 1460 313 1412T326 1316Q326 1225 282 1143T158 991L103 1025Q95 1030 93 1036T90 1048Q90 1062 100 1072Q116 1091 131 1113T158 1160T177 1212T184 1269Q184 1301
175 1335T142 1407Q135 1418 135 1429Q135 1453 162 1464L271 1508ZM575 1508Q604 1460 617 1412T630 1316Q630 1225 586 1143T462 991L407 1025Q399 1030 397 1036T394 1048Q394 1062 404 1072Q420 1091 435 1113T462 1160T481 1212T488 1269Q488 1301 479 1335T446
1407Q439 1418 439 1429Q439 1453 466 1464L575 1508Z" />
<glyph unicode="&#x201e;" glyph-name="quotedblbase" horiz-adv-x="728" d="M271 241Q300 193 313 145T326 49Q326 -42 282 -124T158 -276L103 -242Q95 -237 93 -231T90 -219Q90 -205 100 -195Q116 -176 131 -154T158 -107T177 -55T184 2Q184 34 175 68T142 140Q135
151 135 162Q135 186 162 197L271 241ZM575 241Q604 193 617 145T630 49Q630 -42 586 -124T462 -276L407 -242Q399 -237 397 -231T394 -219Q394 -205 404 -195Q420 -176 435 -154T462 -107T481 -55T488 2Q488 34 479 68T446 140Q439 151 439 162Q439 186 466 197L575
241Z" />
<glyph unicode="&#x2022;" glyph-name="bullet" horiz-adv-x="1160" d="M208 595Q208 672 237 740T317 858T434 938T578 968Q655 968 723 939T841 859T921 740T951 595Q951 518 922 451T842 333T723 254T578 224Q502 224 435 253T317 333T238 450T208 595Z" />
<glyph unicode="&#x2039;" glyph-name="guilsinglleft" horiz-adv-x="632" d="M138 518V541L387 930L445 902Q459 895 466 884T473 860Q473 843 463 827L304 566Q290 542 276 529Q291 515 304 493L463 232Q468 224 470 215T473 198Q473 170 445 157L387 129L138 518Z" />
<glyph unicode="&#x203a;" glyph-name="guilsinglright" horiz-adv-x="632" d="M485 541V518L236 129L178 157Q150 170 150 198Q150 215 160 232L319 493Q332 517 346 529Q334 540 319 566L160 827Q150 844 150 861Q150 889 178 902L236 930L485 541Z" />
</font>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 78 KiB

Binary file not shown.

Binary file not shown.

Binary file not shown.

View file

@ -1,11 +1,11 @@
@font-face { @font-face {
font-family: "Icons"; font-family: "Icons";
src: url("./font/fontello.1579191725174.eot"); src: url("./font/fontello.1580295842879.eot");
src: url("./font/fontello.1579191725174.eot") format("embedded-opentype"), src: url("./font/fontello.1580295842879.eot") format("embedded-opentype"),
url("./font/fontello.1579191725174.woff2") format("woff2"), url("./font/fontello.1580295842879.woff2") format("woff2"),
url("./font/fontello.1579191725174.woff") format("woff"), url("./font/fontello.1580295842879.woff") format("woff"),
url("./font/fontello.1579191725174.ttf") format("truetype"), url("./font/fontello.1580295842879.ttf") format("truetype"),
url("./font/fontello.1579191725174.svg") format("svg"); url("./font/fontello.1580295842879.svg") format("svg");
font-weight: normal; font-weight: normal;
font-style: normal; font-style: normal;
} }
@ -132,3 +132,5 @@ .icon-home-2::before { content: "\e821"; }
.icon-chat::before { content: "\e81e"; } .icon-chat::before { content: "\e81e"; }
.icon-login::before { content: "\e820"; } .icon-login::before { content: "\e820"; }
.icon-arrow-curved::before { content: "\e822"; }

View file

@ -333,6 +333,12 @@
"css": "login", "css": "login",
"code": 59424, "code": 59424,
"src": "fontawesome" "src": "fontawesome"
},
{
"uid": "f3ebd6751c15a280af5cc5f4a764187d",
"css": "arrow-curved",
"code": 59426,
"src": "iconic"
} }
] ]
} }

View file

@ -1,2 +1,2 @@
(window.webpackJsonp=window.webpackJsonp||[]).push([[2],{567:function(t,e,i){var c=i(568);"string"==typeof c&&(c=[[t.i,c,""]]),c.locals&&(t.exports=c.locals);(0,i(4).default)("cc6cdea4",c,!0,{})},568:function(t,e,i){(t.exports=i(3)(!1)).push([t.i,".sticker-picker{width:100%}.sticker-picker .contents{min-height:250px}.sticker-picker .contents .sticker-picker-content{display:-ms-flexbox;display:flex;-ms-flex-wrap:wrap;flex-wrap:wrap;padding:0 4px}.sticker-picker .contents .sticker-picker-content .sticker{display:-ms-flexbox;display:flex;-ms-flex:1 1 auto;flex:1 1 auto;margin:4px;width:56px;height:56px}.sticker-picker .contents .sticker-picker-content .sticker img{height:100%}.sticker-picker .contents .sticker-picker-content .sticker img:hover{filter:drop-shadow(0 0 5px var(--link,#d8a070))}",""])},569:function(t,e,i){"use strict";i.r(e);var c=i(88),n={components:{TabSwitcher:i(49).a},data:function(){return{meta:{stickers:[]},path:""}},computed:{pack:function(){return this.$store.state.instance.stickers||[]}},methods:{clear:function(){this.meta={stickers:[]}},pick:function(t,e){var i=this,n=this.$store;fetch(t).then(function(t){t.blob().then(function(t){var a=new File([t],e,{mimetype:"image/png"}),s=new FormData;s.append("file",a),c.a.uploadMedia({store:n,formData:s}).then(function(t){i.$emit("uploaded",t),i.clear()},function(t){console.warn("Can't attach sticker"),console.warn(t),i.$emit("upload-failed","default")})})})}}},a=i(0);var s=function(t){i(567)},r=Object(a.a)(n,function(){var t=this,e=t.$createElement,i=t._self._c||e;return i("div",{staticClass:"sticker-picker"},[i("tab-switcher",{staticClass:"tab-switcher",attrs:{"render-only-focused":!0,"scrollable-tabs":""}},t._l(t.pack,function(e){return i("div",{key:e.path,staticClass:"sticker-picker-content",attrs:{"image-tooltip":e.meta.title,image:e.path+e.meta.tabIcon}},t._l(e.meta.stickers,function(c){return i("div",{key:c,staticClass:"sticker",on:{click:function(i){i.stopPropagation(),i.preventDefault(),t.pick(e.path+c,e.meta.title)}}},[i("img",{attrs:{src:e.path+c}})])}),0)}),0)],1)},[],!1,s,null,null);e.default=r.exports}}]); (window.webpackJsonp=window.webpackJsonp||[]).push([[2],{575:function(t,e,i){var c=i(576);"string"==typeof c&&(c=[[t.i,c,""]]),c.locals&&(t.exports=c.locals);(0,i(3).default)("cc6cdea4",c,!0,{})},576:function(t,e,i){(t.exports=i(2)(!1)).push([t.i,".sticker-picker{width:100%}.sticker-picker .contents{min-height:250px}.sticker-picker .contents .sticker-picker-content{display:-ms-flexbox;display:flex;-ms-flex-wrap:wrap;flex-wrap:wrap;padding:0 4px}.sticker-picker .contents .sticker-picker-content .sticker{display:-ms-flexbox;display:flex;-ms-flex:1 1 auto;flex:1 1 auto;margin:4px;width:56px;height:56px}.sticker-picker .contents .sticker-picker-content .sticker img{height:100%}.sticker-picker .contents .sticker-picker-content .sticker img:hover{filter:drop-shadow(0 0 5px var(--link,#d8a070))}",""])},577:function(t,e,i){"use strict";i.r(e);var c=i(88),n={components:{TabSwitcher:i(51).a},data:function(){return{meta:{stickers:[]},path:""}},computed:{pack:function(){return this.$store.state.instance.stickers||[]}},methods:{clear:function(){this.meta={stickers:[]}},pick:function(t,e){var i=this,n=this.$store;fetch(t).then(function(t){t.blob().then(function(t){var a=new File([t],e,{mimetype:"image/png"}),s=new FormData;s.append("file",a),c.a.uploadMedia({store:n,formData:s}).then(function(t){i.$emit("uploaded",t),i.clear()},function(t){console.warn("Can't attach sticker"),console.warn(t),i.$emit("upload-failed","default")})})})}}},a=i(0);var s=function(t){i(575)},r=Object(a.a)(n,function(){var t=this,e=t.$createElement,i=t._self._c||e;return i("div",{staticClass:"sticker-picker"},[i("tab-switcher",{staticClass:"tab-switcher",attrs:{"render-only-focused":!0,"scrollable-tabs":""}},t._l(t.pack,function(e){return i("div",{key:e.path,staticClass:"sticker-picker-content",attrs:{"image-tooltip":e.meta.title,image:e.path+e.meta.tabIcon}},t._l(e.meta.stickers,function(c){return i("div",{key:c,staticClass:"sticker",on:{click:function(i){i.stopPropagation(),i.preventDefault(),t.pick(e.path+c,e.meta.title)}}},[i("img",{attrs:{src:e.path+c}})])}),0)}),0)],1)},[],!1,s,null,null);e.default=r.exports}}]);
//# sourceMappingURL=2.7eaed4ad0d253fad6f55.js.map //# sourceMappingURL=2.59b096781ddca107175d.js.map

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View file

@ -1,6 +1,7 @@
{ {
"pleroma-dark": [ "Pleroma Dark", "#121a24", "#182230", "#b9b9ba", "#d8a070", "#d31014", "#0fa00f", "#0095ff", "#ffa500" ], "pleroma-dark": [ "Pleroma Dark", "#121a24", "#182230", "#b9b9ba", "#d8a070", "#d31014", "#0fa00f", "#0095ff", "#ffa500" ],
"pleroma-light": [ "Pleroma Light", "#f2f4f6", "#dbe0e8", "#304055", "#f86f0f", "#d31014", "#0fa00f", "#0095ff", "#ffa500" ], "pleroma-light": [ "Pleroma Light", "#f2f4f6", "#dbe0e8", "#304055", "#f86f0f", "#d31014", "#0fa00f", "#0095ff", "#ffa500" ],
"pleroma-amoled": [ "Pleroma Dark AMOLED", "#000000", "#111111", "#b0b0b1", "#d8a070", "#aa0000", "#0fa00f", "#0095ff", "#d59500"],
"classic-dark": [ "Classic Dark", "#161c20", "#282e32", "#b9b9b9", "#baaa9c", "#d31014", "#0fa00f", "#0095ff", "#ffa500" ], "classic-dark": [ "Classic Dark", "#161c20", "#282e32", "#b9b9b9", "#baaa9c", "#d31014", "#0fa00f", "#0095ff", "#ffa500" ],
"bird": [ "Bird", "#f8fafd", "#e6ecf0", "#14171a", "#0084b8", "#e0245e", "#17bf63", "#1b95e0", "#fab81e"], "bird": [ "Bird", "#f8fafd", "#e6ecf0", "#14171a", "#0084b8", "#e0245e", "#17bf63", "#1b95e0", "#fab81e"],
"ir-black": [ "Ir Black", "#000000", "#242422", "#b5b3aa", "#ff6c60", "#FF6C60", "#A8FF60", "#96CBFE", "#FFFFB6" ], "ir-black": [ "Ir Black", "#000000", "#242422", "#b5b3aa", "#ff6c60", "#FF6C60", "#A8FF60", "#96CBFE", "#FFFFB6" ],

View file

@ -1,6 +1,6 @@
<h4>Terms of Service</h4> <h4>Terms of Service</h4>
<p>It's mainly "be nice".</p> <p>It's mainly "be nice"</p>
<ol> <ol>
<li> <li>

View file

@ -1,4 +1,4 @@
var serviceWorkerOption = {"assets":["/static/fontello.1579191725174.css","/static/font/fontello.1579191725174.eot","/static/font/fontello.1579191725174.svg","/static/font/fontello.1579191725174.ttf","/static/font/fontello.1579191725174.woff","/static/font/fontello.1579191725174.woff2","/static/img/nsfw.ca95de9.png","/static/css/app.ae04505b31bb0ee2765e.css","/static/js/app.6fc3537f2985f0a2e06a.js","/static/css/vendors~app.b2603a50868c68a1c192.css","/static/js/vendors~app.ef669266eac4d086d74e.js","/static/js/2.7eaed4ad0d253fad6f55.js"]}; var serviceWorkerOption = {"assets":["/static/fontello.1580295842879.css","/static/font/fontello.1580295842879.eot","/static/font/fontello.1580295842879.svg","/static/font/fontello.1580295842879.ttf","/static/font/fontello.1580295842879.woff","/static/font/fontello.1580295842879.woff2","/static/img/nsfw.ca95de9.png","/static/css/app.ae04505b31bb0ee2765e.css","/static/js/app.8457067449d0acdd01fc.js","/static/css/vendors~app.b2603a50868c68a1c192.css","/static/js/vendors~app.9ab182239f3a2abee89f.js","/static/js/2.59b096781ddca107175d.js"]};
!function(e){var n={};function t(r){if(n[r])return n[r].exports;var o=n[r]={i:r,l:!1,exports:{}};return e[r].call(o.exports,o,o.exports,t),o.l=!0,o.exports}t.m=e,t.c=n,t.d=function(e,n,r){t.o(e,n)||Object.defineProperty(e,n,{enumerable:!0,get:r})},t.r=function(e){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},t.t=function(e,n){if(1&n&&(e=t(e)),8&n)return e;if(4&n&&"object"==typeof e&&e&&e.__esModule)return e;var r=Object.create(null);if(t.r(r),Object.defineProperty(r,"default",{enumerable:!0,value:e}),2&n&&"string"!=typeof e)for(var o in e)t.d(r,o,function(n){return e[n]}.bind(null,o));return r},t.n=function(e){var n=e&&e.__esModule?function(){return e.default}:function(){return e};return t.d(n,"a",n),n},t.o=function(e,n){return Object.prototype.hasOwnProperty.call(e,n)},t.p="/",t(t.s=1)}([function(e,n){ !function(e){var n={};function t(r){if(n[r])return n[r].exports;var o=n[r]={i:r,l:!1,exports:{}};return e[r].call(o.exports,o,o.exports,t),o.l=!0,o.exports}t.m=e,t.c=n,t.d=function(e,n,r){t.o(e,n)||Object.defineProperty(e,n,{enumerable:!0,get:r})},t.r=function(e){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},t.t=function(e,n){if(1&n&&(e=t(e)),8&n)return e;if(4&n&&"object"==typeof e&&e&&e.__esModule)return e;var r=Object.create(null);if(t.r(r),Object.defineProperty(r,"default",{enumerable:!0,value:e}),2&n&&"string"!=typeof e)for(var o in e)t.d(r,o,function(n){return e[n]}.bind(null,o));return r},t.n=function(e){var n=e&&e.__esModule?function(){return e.default}:function(){return e};return t.d(n,"a",n),n},t.o=function(e,n){return Object.prototype.hasOwnProperty.call(e,n)},t.p="/",t(t.s=1)}([function(e,n){
/*! /*!

File diff suppressed because one or more lines are too long

View file

@ -307,6 +307,15 @@ test "Quack.Logger module" do
assert ConfigDB.from_binary(binary) == Quack.Logger assert ConfigDB.from_binary(binary) == Quack.Logger
end end
test "Swoosh.Adapters modules" do
binary = ConfigDB.transform("Swoosh.Adapters.SMTP")
assert binary == :erlang.term_to_binary(Swoosh.Adapters.SMTP)
assert ConfigDB.from_binary(binary) == Swoosh.Adapters.SMTP
binary = ConfigDB.transform("Swoosh.Adapters.AmazonSES")
assert binary == :erlang.term_to_binary(Swoosh.Adapters.AmazonSES)
assert ConfigDB.from_binary(binary) == Swoosh.Adapters.AmazonSES
end
test "sigil" do test "sigil" do
binary = ConfigDB.transform("~r[comp[lL][aA][iI][nN]er]") binary = ConfigDB.transform("~r[comp[lL][aA][iI][nN]er]")
assert binary == :erlang.term_to_binary(~r/comp[lL][aA][iI][nN]er/) assert binary == :erlang.term_to_binary(~r/comp[lL][aA][iI][nN]er/)

View file

@ -105,17 +105,4 @@ test "transfer config values with full subkey update" do
Application.put_env(:pleroma, :assets, assets) Application.put_env(:pleroma, :assets, assets)
end) end)
end end
test "non existing atom" do
ConfigDB.create(%{
group: ":pleroma",
key: ":undefined_atom_key",
value: [live: 2, com: 3]
})
assert ExUnit.CaptureLog.capture_log(fn ->
TransferTask.start_link([])
end) =~
"updating env causes error, group: \":pleroma\" key: \":undefined_atom_key\" value: [live: 2, com: 3] error: %ArgumentError{message: \"argument error\"}"
end
end end

View file

@ -19,8 +19,8 @@ test "build report email" do
AdminEmail.report(to_user, reporter, account, [%{name: "Test", id: "12"}], "Test comment") AdminEmail.report(to_user, reporter, account, [%{name: "Test", id: "12"}], "Test comment")
status_url = Helpers.o_status_url(Pleroma.Web.Endpoint, :notice, "12") status_url = Helpers.o_status_url(Pleroma.Web.Endpoint, :notice, "12")
reporter_url = Helpers.feed_url(Pleroma.Web.Endpoint, :feed_redirect, reporter.id) reporter_url = Helpers.user_feed_url(Pleroma.Web.Endpoint, :feed_redirect, reporter.id)
account_url = Helpers.feed_url(Pleroma.Web.Endpoint, :feed_redirect, account.id) account_url = Helpers.user_feed_url(Pleroma.Web.Endpoint, :feed_redirect, account.id)
assert res.to == [{to_user.name, to_user.email}] assert res.to == [{to_user.name, to_user.email}]
assert res.from == {config[:name], config[:notify_email]} assert res.from == {config[:name], config[:notify_email]}

File diff suppressed because it is too large Load diff

File diff suppressed because one or more lines are too long

View file

@ -119,7 +119,20 @@ test "turning urls into links" do
end end
end end
describe "add_user_links" do describe "Formatter.linkify" do
test "correctly finds mentions that contain the domain name" do
_user = insert(:user, %{nickname: "lain"})
_remote_user = insert(:user, %{nickname: "lain@lain.com", local: false})
text = "hey @lain@lain.com what's up"
{_text, mentions, []} = Formatter.linkify(text)
[{username, user}] = mentions
assert username == "@lain@lain.com"
assert user.nickname == "lain@lain.com"
end
test "gives a replacement for user links, using local nicknames in user links text" do test "gives a replacement for user links, using local nicknames in user links text" do
text = "@gsimg According to @archa_eme_, that is @daggsy. Also hello @archaeme@archae.me" text = "@gsimg According to @archa_eme_, that is @daggsy. Also hello @archaeme@archae.me"
gsimg = insert(:user, %{nickname: "gsimg"}) gsimg = insert(:user, %{nickname: "gsimg"})

View file

@ -177,6 +177,39 @@ test "with objects that have legacy data.url attribute" do
assert {:ok, []} == File.ls("#{uploads_dir}/#{path}") assert {:ok, []} == File.ls("#{uploads_dir}/#{path}")
end end
test "With custom base_url" do
Pleroma.Config.put([Pleroma.Upload, :uploader], Pleroma.Uploaders.Local)
Pleroma.Config.put([Pleroma.Upload, :base_url], "https://sub.domain.tld/dir/")
file = %Plug.Upload{
content_type: "image/jpg",
path: Path.absname("test/fixtures/image.jpg"),
filename: "an_image.jpg"
}
user = insert(:user)
{:ok, %Object{} = attachment} =
Pleroma.Web.ActivityPub.ActivityPub.upload(file, actor: user.ap_id)
%{data: %{"attachment" => [%{"url" => [%{"href" => href}]}]}} =
note = insert(:note, %{user: user, data: %{"attachment" => [attachment.data]}})
uploads_dir = Pleroma.Config.get!([Pleroma.Uploaders.Local, :uploads])
path = href |> Path.dirname() |> Path.basename()
assert {:ok, ["an_image.jpg"]} == File.ls("#{uploads_dir}/#{path}")
Object.delete(note)
ObanHelpers.perform(all_enqueued(worker: Pleroma.Workers.AttachmentsCleanupWorker))
assert Object.get_by_id(attachment.id) == nil
assert {:ok, []} == File.ls("#{uploads_dir}/#{path}")
end
end end
describe "normalizer" do describe "normalizer" do

View file

@ -6,6 +6,6 @@ defmodule Pleroma.RuntimeTest do
use ExUnit.Case, async: true use ExUnit.Case, async: true
test "it loads custom runtime modules" do test "it loads custom runtime modules" do
assert Code.ensure_compiled?(RuntimeModule) assert {:module, RuntimeModule} == Code.ensure_compiled(RuntimeModule)
end end
end end

View file

@ -19,7 +19,7 @@ def request(
else else
error -> error ->
with {:error, message} <- error do with {:error, message} <- error do
Logger.warn(message) Logger.warn(to_string(message))
end end
{_, _r} = error {_, _r} = error

View file

@ -25,30 +25,50 @@ defmodule Mix.Tasks.Pleroma.ConfigTest do
end end
test "error if file with custom settings doesn't exist" do test "error if file with custom settings doesn't exist" do
Mix.Tasks.Pleroma.Config.run(["migrate_to_db"]) Mix.Tasks.Pleroma.Config.migrate_to_db("config/not_existance_config_file.exs")
assert_receive {:mix_shell, :info, assert_receive {:mix_shell, :info,
[ [
"To migrate settings, you must define custom settings in config/test.secret.exs." "To migrate settings, you must define custom settings in config/not_existance_config_file.exs."
]}, ]},
15 15
end end
test "settings are migrated to db" do describe "migrate_to_db/1" do
initial = Application.get_env(:quack, :level) setup do
on_exit(fn -> Application.put_env(:quack, :level, initial) end) initial = Application.get_env(:quack, :level)
assert Repo.all(ConfigDB) == [] on_exit(fn -> Application.put_env(:quack, :level, initial) end)
end
Mix.Tasks.Pleroma.Config.migrate_to_db("test/fixtures/config/temp.secret.exs") test "settings are migrated to db" do
assert Repo.all(ConfigDB) == []
config1 = ConfigDB.get_by_params(%{group: ":pleroma", key: ":first_setting"}) Mix.Tasks.Pleroma.Config.migrate_to_db("test/fixtures/config/temp.secret.exs")
config2 = ConfigDB.get_by_params(%{group: ":pleroma", key: ":second_setting"})
config3 = ConfigDB.get_by_params(%{group: ":quack", key: ":level"})
refute ConfigDB.get_by_params(%{group: ":pleroma", key: "Pleroma.Repo"})
assert ConfigDB.from_binary(config1.value) == [key: "value", key2: [Repo]] config1 = ConfigDB.get_by_params(%{group: ":pleroma", key: ":first_setting"})
assert ConfigDB.from_binary(config2.value) == [key: "value2", key2: ["Activity"]] config2 = ConfigDB.get_by_params(%{group: ":pleroma", key: ":second_setting"})
assert ConfigDB.from_binary(config3.value) == :info config3 = ConfigDB.get_by_params(%{group: ":quack", key: ":level"})
refute ConfigDB.get_by_params(%{group: ":pleroma", key: "Pleroma.Repo"})
assert ConfigDB.from_binary(config1.value) == [key: "value", key2: [Repo]]
assert ConfigDB.from_binary(config2.value) == [key: "value2", key2: ["Activity"]]
assert ConfigDB.from_binary(config3.value) == :info
end
test "config table is truncated before migration" do
ConfigDB.create(%{
group: ":pleroma",
key: ":first_setting",
value: [key: "value", key2: ["Activity"]]
})
assert Repo.aggregate(ConfigDB, :count, :id) == 1
Mix.Tasks.Pleroma.Config.migrate_to_db("test/fixtures/config/temp.secret.exs")
config = ConfigDB.get_by_params(%{group: ":pleroma", key: ":first_setting"})
assert ConfigDB.from_binary(config.value) == [key: "value", key2: [Repo]]
end
end end
describe "with deletion temp file" do describe "with deletion temp file" do

52
test/tasks/email_test.exs Normal file
View file

@ -0,0 +1,52 @@
defmodule Mix.Tasks.Pleroma.EmailTest do
use Pleroma.DataCase
import Swoosh.TestAssertions
alias Pleroma.Config
alias Pleroma.Tests.ObanHelpers
setup_all do
Mix.shell(Mix.Shell.Process)
on_exit(fn ->
Mix.shell(Mix.Shell.IO)
end)
:ok
end
describe "pleroma.email test" do
test "Sends test email with no given address" do
mail_to = Config.get([:instance, :email])
:ok = Mix.Tasks.Pleroma.Email.run(["test"])
ObanHelpers.perform_all()
assert_receive {:mix_shell, :info, [message]}
assert message =~ "Test email has been sent"
assert_email_sent(
to: mail_to,
html_body: ~r/a test email was requested./i
)
end
test "Sends test email with given address" do
mail_to = "hewwo@example.com"
:ok = Mix.Tasks.Pleroma.Email.run(["test", "--to", mail_to])
ObanHelpers.perform_all()
assert_receive {:mix_shell, :info, [message]}
assert message =~ "Test email has been sent"
assert_email_sent(
to: mail_to,
html_body: ~r/a test email was requested./i
)
end
end
end

View file

@ -585,7 +585,7 @@ test "returns an ap_id for a user" do
user = insert(:user) user = insert(:user)
assert User.ap_id(user) == assert User.ap_id(user) ==
Pleroma.Web.Router.Helpers.feed_url( Pleroma.Web.Router.Helpers.user_feed_url(
Pleroma.Web.Endpoint, Pleroma.Web.Endpoint,
:feed_redirect, :feed_redirect,
user.nickname user.nickname
@ -596,7 +596,7 @@ test "returns an ap_followers link for a user" do
user = insert(:user) user = insert(:user)
assert User.ap_followers(user) == assert User.ap_followers(user) ==
Pleroma.Web.Router.Helpers.feed_url( Pleroma.Web.Router.Helpers.user_feed_url(
Pleroma.Web.Endpoint, Pleroma.Web.Endpoint,
:feed_redirect, :feed_redirect,
user.nickname user.nickname
@ -1286,23 +1286,35 @@ test "User.delete() plugs any possible zombie objects" do
end end
end end
test "auth_active?/1 works correctly" do describe "account_status/1" do
Pleroma.Config.put([:instance, :account_activation_required], true) clear_config([:instance, :account_activation_required])
local_user = insert(:user, local: true, confirmation_pending: true) test "return confirmation_pending for unconfirm user" do
confirmed_user = insert(:user, local: true, confirmation_pending: false) Pleroma.Config.put([:instance, :account_activation_required], true)
remote_user = insert(:user, local: false) user = insert(:user, confirmation_pending: true)
assert User.account_status(user) == :confirmation_pending
end
refute User.auth_active?(local_user) test "return active for confirmed user" do
assert User.auth_active?(confirmed_user) Pleroma.Config.put([:instance, :account_activation_required], true)
assert User.auth_active?(remote_user) user = insert(:user, confirmation_pending: false)
assert User.account_status(user) == :active
end
# also shows unactive for deactivated users test "return active for remote user" do
user = insert(:user, local: false)
assert User.account_status(user) == :active
end
deactivated_but_confirmed = test "returns :password_reset_pending for user with reset password" do
insert(:user, local: true, confirmation_pending: false, deactivated: true) user = insert(:user, password_reset_pending: true)
assert User.account_status(user) == :password_reset_pending
end
refute User.auth_active?(deactivated_but_confirmed) test "returns :deactivated for deactivated user" do
user = insert(:user, local: true, confirmation_pending: false, deactivated: true)
assert User.account_status(user) == :deactivated
end
end end
describe "superuser?/1" do describe "superuser?/1" do

View file

@ -636,4 +636,17 @@ test "removes actor from announcements" do
assert updated_object.data["announcement_count"] == 1 assert updated_object.data["announcement_count"] == 1
end end
end end
describe "get_cached_emoji_reactions/1" do
test "returns the data or an emtpy list" do
object = insert(:note)
assert Utils.get_cached_emoji_reactions(object) == []
object = insert(:note, data: %{"reactions" => [["x", ["lain"]]]})
assert Utils.get_cached_emoji_reactions(object) == [["x", ["lain"]]]
object = insert(:note, data: %{"reactions" => %{}})
assert Utils.get_cached_emoji_reactions(object) == []
end
end
end end

View file

@ -0,0 +1,154 @@
# Pleroma: A lightweight social networking server
# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.Feed.TagControllerTest do
use Pleroma.Web.ConnCase
import Pleroma.Factory
import SweetXml
alias Pleroma.Web.Feed.FeedView
clear_config([:feed])
test "gets a feed (ATOM)", %{conn: conn} do
Pleroma.Config.put(
[:feed, :post_title],
%{max_length: 25, omission: "..."}
)
user = insert(:user)
{:ok, activity1} = Pleroma.Web.CommonAPI.post(user, %{"status" => "yeah #PleromaArt"})
object = Pleroma.Object.normalize(activity1)
object_data =
Map.put(object.data, "attachment", [
%{
"url" => [
%{
"href" =>
"https://peertube.moe/static/webseed/df5f464b-be8d-46fb-ad81-2d4c2d1630e3-480.mp4",
"mediaType" => "video/mp4",
"type" => "Link"
}
]
}
])
object
|> Ecto.Changeset.change(data: object_data)
|> Pleroma.Repo.update()
{:ok, _activity2} =
Pleroma.Web.CommonAPI.post(user, %{"status" => "42 This is :moominmamma #PleromaArt"})
{:ok, _activity3} = Pleroma.Web.CommonAPI.post(user, %{"status" => "This is :moominmamma"})
response =
conn
|> put_req_header("content-type", "application/atom+xml")
|> get(tag_feed_path(conn, :feed, "pleromaart.atom"))
|> response(200)
xml = parse(response)
assert xpath(xml, ~x"//feed/title/text()") == '#pleromaart'
assert xpath(xml, ~x"//feed/entry/title/text()"l) == [
'42 This is :moominmamm...',
'yeah #PleromaArt'
]
assert xpath(xml, ~x"//feed/entry/author/name/text()"ls) == [user.nickname, user.nickname]
assert xpath(xml, ~x"//feed/entry/author/id/text()"ls) == [user.ap_id, user.ap_id]
end
test "gets a feed (RSS)", %{conn: conn} do
Pleroma.Config.put(
[:feed, :post_title],
%{max_length: 25, omission: "..."}
)
user = insert(:user)
{:ok, activity1} = Pleroma.Web.CommonAPI.post(user, %{"status" => "yeah #PleromaArt"})
object = Pleroma.Object.normalize(activity1)
object_data =
Map.put(object.data, "attachment", [
%{
"url" => [
%{
"href" =>
"https://peertube.moe/static/webseed/df5f464b-be8d-46fb-ad81-2d4c2d1630e3-480.mp4",
"mediaType" => "video/mp4",
"type" => "Link"
}
]
}
])
object
|> Ecto.Changeset.change(data: object_data)
|> Pleroma.Repo.update()
{:ok, activity2} =
Pleroma.Web.CommonAPI.post(user, %{"status" => "42 This is :moominmamma #PleromaArt"})
{:ok, _activity3} = Pleroma.Web.CommonAPI.post(user, %{"status" => "This is :moominmamma"})
response =
conn
|> put_req_header("content-type", "application/rss+xml")
|> get(tag_feed_path(conn, :feed, "pleromaart.rss"))
|> response(200)
xml = parse(response)
assert xpath(xml, ~x"//channel/title/text()") == '#pleromaart'
assert xpath(xml, ~x"//channel/description/text()"s) ==
"These are public toots tagged with #pleromaart. You can interact with them if you have an account anywhere in the fediverse."
assert xpath(xml, ~x"//channel/link/text()") ==
'#{Pleroma.Web.base_url()}/tags/pleromaart.rss'
assert xpath(xml, ~x"//channel/webfeeds:logo/text()") ==
'#{Pleroma.Web.base_url()}/static/logo.png'
assert xpath(xml, ~x"//channel/item/title/text()"l) == [
'42 This is :moominmamm...',
'yeah #PleromaArt'
]
assert xpath(xml, ~x"//channel/item/pubDate/text()"sl) == [
FeedView.pub_date(activity1.data["published"]),
FeedView.pub_date(activity2.data["published"])
]
assert xpath(xml, ~x"//channel/item/enclosure/@url"sl) == [
"https://peertube.moe/static/webseed/df5f464b-be8d-46fb-ad81-2d4c2d1630e3-480.mp4"
]
obj1 = Pleroma.Object.normalize(activity1)
obj2 = Pleroma.Object.normalize(activity2)
assert xpath(xml, ~x"//channel/item/description/text()"sl) == [
HtmlEntities.decode(FeedView.activity_content(obj2)),
HtmlEntities.decode(FeedView.activity_content(obj1))
]
response =
conn
|> put_req_header("content-type", "application/atom+xml")
|> get(tag_feed_path(conn, :feed, "pleromaart"))
|> response(200)
xml = parse(response)
assert xpath(xml, ~x"//channel/title/text()") == '#pleromaart'
assert xpath(xml, ~x"//channel/description/text()"s) ==
"These are public toots tagged with #pleromaart. You can interact with them if you have an account anywhere in the fediverse."
end
end

View file

@ -2,7 +2,7 @@
# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/> # Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only # SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.Feed.FeedControllerTest do defmodule Pleroma.Web.Feed.UserControllerTest do
use Pleroma.Web.ConnCase use Pleroma.Web.ConnCase
import Pleroma.Factory import Pleroma.Factory
@ -49,7 +49,7 @@ test "gets a feed", %{conn: conn} do
resp = resp =
conn conn
|> put_req_header("content-type", "application/atom+xml") |> put_req_header("content-type", "application/atom+xml")
|> get("/users/#{user.nickname}/feed.atom") |> get(user_feed_path(conn, :feed, user.nickname))
|> response(200) |> response(200)
activity_titles = activity_titles =
@ -65,7 +65,7 @@ test "returns 404 for a missing feed", %{conn: conn} do
conn = conn =
conn conn
|> put_req_header("content-type", "application/atom+xml") |> put_req_header("content-type", "application/atom+xml")
|> get("/users/nonexisting/feed.atom") |> get(user_feed_path(conn, :feed, "nonexisting"))
assert response(conn, 404) assert response(conn, 404)
end end
@ -91,7 +91,7 @@ test "undefined format. it returns error when user not found", %{conn: conn} do
response = response =
conn conn
|> put_req_header("accept", "application/xml") |> put_req_header("accept", "application/xml")
|> get("/users/jimm") |> get(user_feed_path(conn, :feed, "jimm"))
|> response(404) |> response(404)
assert response == ~S({"error":"Not found"}) assert response == ~S({"error":"Not found"})

View file

@ -7,7 +7,6 @@ defmodule Pleroma.Web.MastodonAPI.SuggestionControllerTest do
alias Pleroma.Config alias Pleroma.Config
import ExUnit.CaptureLog
import Pleroma.Factory import Pleroma.Factory
import Tesla.Mock import Tesla.Mock
@ -36,11 +35,7 @@ defmodule Pleroma.Web.MastodonAPI.SuggestionControllerTest do
[other_user: other_user] [other_user: other_user]
end end
clear_config(:suggestions) test "returns empty result", %{conn: conn} do
test "returns empty result when suggestions disabled", %{conn: conn} do
Config.put([:suggestions, :enabled], false)
res = res =
conn conn
|> get("/api/v1/suggestions") |> get("/api/v1/suggestions")
@ -48,43 +43,4 @@ test "returns empty result when suggestions disabled", %{conn: conn} do
assert res == [] assert res == []
end end
test "returns error", %{conn: conn} do
Config.put([:suggestions, :enabled], true)
Config.put([:suggestions, :third_party_engine], "http://test500?{{host}}&{{user}}")
assert capture_log(fn ->
res =
conn
|> get("/api/v1/suggestions")
|> json_response(500)
assert res == "Something went wrong"
end) =~ "Could not retrieve suggestions"
end
test "returns suggestions", %{conn: conn, other_user: other_user} do
Config.put([:suggestions, :enabled], true)
Config.put([:suggestions, :third_party_engine], "http://test200?{{host}}&{{user}}")
res =
conn
|> get("/api/v1/suggestions")
|> json_response(200)
assert res == [
%{
"acct" => "yj455",
"avatar" => "https://social.heldscal.la/avatar/201.jpeg",
"avatar_static" => "https://social.heldscal.la/avatar/s/201.jpeg",
"id" => 0
},
%{
"acct" => other_user.ap_id,
"avatar" => "https://social.heldscal.la/avatar/202.jpeg",
"avatar_static" => "https://social.heldscal.la/avatar/s/202.jpeg",
"id" => other_user.id
}
]
end
end end

View file

@ -36,7 +36,10 @@ test "has an emoji reaction list" do
activity = Repo.get(Activity, activity.id) activity = Repo.get(Activity, activity.id)
status = StatusView.render("show.json", activity: activity) status = StatusView.render("show.json", activity: activity)
assert status[:pleroma][:emoji_reactions] == [["", 2], ["🍵", 1]] assert status[:pleroma][:emoji_reactions] == [
%{emoji: "", count: 2},
%{emoji: "🍵", count: 1}
]
end end
test "loads and returns the direct conversation id when given the `with_direct_conversation_id` option" do test "loads and returns the direct conversation id when given the `with_direct_conversation_id` option" do

View file

@ -819,7 +819,7 @@ test "rejects token exchange for valid credentials belonging to unconfirmed user
|> User.confirmation_changeset(need_confirmation: true) |> User.confirmation_changeset(need_confirmation: true)
|> User.update_and_set_cache() |> User.update_and_set_cache()
refute Pleroma.User.auth_active?(user) refute Pleroma.User.account_status(user) == :active
app = insert(:oauth_app) app = insert(:oauth_app)
@ -849,7 +849,7 @@ test "rejects token exchange for valid credentials belonging to deactivated user
app = insert(:oauth_app) app = insert(:oauth_app)
conn = resp =
build_conn() build_conn()
|> post("/oauth/token", %{ |> post("/oauth/token", %{
"grant_type" => "password", "grant_type" => "password",
@ -858,10 +858,12 @@ test "rejects token exchange for valid credentials belonging to deactivated user
"client_id" => app.client_id, "client_id" => app.client_id,
"client_secret" => app.client_secret "client_secret" => app.client_secret
}) })
|> json_response(403)
assert resp = json_response(conn, 403) assert resp == %{
assert %{"error" => _} = resp "error" => "Your account is currently disabled",
refute Map.has_key?(resp, "access_token") "identifier" => "account_is_disabled"
}
end end
test "rejects token exchange for user with password_reset_pending set to true" do test "rejects token exchange for user with password_reset_pending set to true" do
@ -875,7 +877,7 @@ test "rejects token exchange for user with password_reset_pending set to true" d
app = insert(:oauth_app, scopes: ["read", "write"]) app = insert(:oauth_app, scopes: ["read", "write"])
conn = resp =
build_conn() build_conn()
|> post("/oauth/token", %{ |> post("/oauth/token", %{
"grant_type" => "password", "grant_type" => "password",
@ -884,12 +886,41 @@ test "rejects token exchange for user with password_reset_pending set to true" d
"client_id" => app.client_id, "client_id" => app.client_id,
"client_secret" => app.client_secret "client_secret" => app.client_secret
}) })
|> json_response(403)
assert resp = json_response(conn, 403) assert resp == %{
"error" => "Password reset is required",
"identifier" => "password_reset_required"
}
end
assert resp["error"] == "Password reset is required" test "rejects token exchange for user with confirmation_pending set to true" do
assert resp["identifier"] == "password_reset_required" Pleroma.Config.put([:instance, :account_activation_required], true)
refute Map.has_key?(resp, "access_token") password = "testpassword"
user =
insert(:user,
password_hash: Comeonin.Pbkdf2.hashpwsalt(password),
confirmation_pending: true
)
app = insert(:oauth_app, scopes: ["read", "write"])
resp =
build_conn()
|> post("/oauth/token", %{
"grant_type" => "password",
"username" => user.nickname,
"password" => password,
"client_id" => app.client_id,
"client_secret" => app.client_secret
})
|> json_response(403)
assert resp == %{
"error" => "Your login is missing a confirmed e-mail address",
"identifier" => "missing_confirmed_email"
}
end end
test "rejects an invalid authorization code" do test "rejects an invalid authorization code" do

View file

@ -71,7 +71,7 @@ test "GET /api/v1/pleroma/statuses/:id/emoji_reactions_by", %{conn: conn} do
|> get("/api/v1/pleroma/statuses/#{activity.id}/emoji_reactions_by") |> get("/api/v1/pleroma/statuses/#{activity.id}/emoji_reactions_by")
|> json_response(200) |> json_response(200)
[["🎅", [represented_user]]] = result [%{"emoji" => "🎅", "count" => 1, "accounts" => [represented_user]}] = result
assert represented_user["id"] == other_user.id assert represented_user["id"] == other_user.id
end end

View file

@ -66,4 +66,38 @@ test "parses twitter card with name & property attributes" do
"https://www.nytimes.com/2019/08/01/nyregion/nypd-facial-recognition-children-teenagers.html" "https://www.nytimes.com/2019/08/01/nyregion/nypd-facial-recognition-children-teenagers.html"
}} }}
end end
test "respect only first title tag on the page" do
image_path =
"https://assets.atlasobscura.com/media/W1siZiIsInVwbG9hZHMvYXNzZXRzLzkwYzgyMzI4LThlMDUtNGRiNS05MDg3LTUzMGUxZTM5N2RmMmVkOTM5ZDM4MGM4OTIx" <>
"YTQ5MF9EQVIgZXhodW1hdGlvbiBvZiBNYXJnYXJldCBDb3JiaW4gZ3JhdmUgMTkyNi5qcGciXSxbInAiLCJjb252ZXJ0IiwiIl0sWyJwIiwiY29udmVydCIsIi1xdWFsaXR5IDgxIC1hdXRvLW9" <>
"yaWVudCJdLFsicCIsInRodW1iIiwiNjAweD4iXV0/DAR%20exhumation%20of%20Margaret%20Corbin%20grave%201926.jpg"
html = File.read!("test/fixtures/margaret-corbin-grave-west-point.html")
assert TwitterCard.parse(html, %{}) ==
{:ok,
%{
site: "@atlasobscura",
title:
"The Missing Grave of Margaret Corbin, Revolutionary War Veteran - Atlas Obscura",
card: "summary_large_image",
image: image_path
}}
end
test "takes first founded title in html head if there is html markup error" do
html = File.read!("test/fixtures/nypd-facial-recognition-children-teenagers4.html")
assert TwitterCard.parse(html, %{}) ==
{:ok,
%{
site: nil,
title:
"She Was Arrested at 14. Then Her Photo Went to a Facial Recognition Database. - The New York Times",
"app:id:googleplay": "com.nytimes.android",
"app:name:googleplay": "NYTimes",
"app:url:googleplay": "nytimes://reader/id/100000006583622"
}}
end
end end