Merge remote-tracking branch 'upstream/develop' into emoji-selector-update

* upstream/develop: (116 commits)
  Password reset page
  add a comment
  force img updating immediately
  Fixed "sequimiento" to "seguimiento".
  Replace `/api/externalprofile/show.json` with a MastoAPI equialent
  Use mastodon api in follow requests
  "Optional" in lowercase.
  Update es.json
  fix pin/unpin status logic
  rename a mutation
  update
  fix user avatar fallback logic
  remove dead code
  Corrected "Media Proxy" translation.
  Update es.json
  make bio textarea resizable vertically only
  remove dead code
  Make image orientation consistent on FF, fix videos w/ modal
  remove dead code
  fix crazy watch logic in conversation
  ...
This commit is contained in:
Henry Jameson 2019-09-08 13:44:29 +03:00
commit db086fe1fd
80 changed files with 2652 additions and 340 deletions

View file

@ -27,16 +27,17 @@ exports.cssLoaders = function (options) {
return [
{
test: /\.(post)?css$/,
use: generateLoaders(['css-loader']),
use: generateLoaders(['css-loader', 'postcss-loader']),
},
{
test: /\.less$/,
use: generateLoaders(['css-loader', 'less-loader']),
use: generateLoaders(['css-loader', 'postcss-loader', 'less-loader']),
},
{
test: /\.sass$/,
use: generateLoaders([
'css-loader',
'postcss-loader',
{
loader: 'sass-loader',
options: {
@ -47,11 +48,11 @@ exports.cssLoaders = function (options) {
},
{
test: /\.scss$/,
use: generateLoaders(['css-loader', 'sass-loader'])
use: generateLoaders(['css-loader', 'postcss-loader', 'sass-loader'])
},
{
test: /\.styl(us)?$/,
use: generateLoaders(['css-loader', 'stylus-loader']),
use: generateLoaders(['css-loader', 'postcss-loader', 'stylus-loader']),
},
]
}

98
docs/CONFIGURATION.md Normal file
View file

@ -0,0 +1,98 @@
# Pleroma-FE configuration and customization for instance administrators
* *For user configuration, see USER_GUIDE.md*
* *For local development server configuration, see HACKING.md*
## Where configuration is stored
PleromaFE gets its configuration from several sources, in order of preference (the one above overrides ones below it)
1. `/api/statusnet/config.json` - this is generated on Backend and contains multiple things including instance name, char limit etc. It also contains FE/Client-specific data, PleromaFE uses `pleromafe` field of it. For more info on changing config on BE, look [here](https://docs-develop.pleroma.social/config.html#frontend_configurations)
2. `/static/config.json` - this is a static FE-provided file, containing only FE specific configuration. This file is completely optional and could be removed but is useful as a fallback if some configuration JSON property isn't present in BE-provided config. It's also a reference point to check what default configuration are and what JSON properties even exist. In local dev mode it could be used to override BE configuration, more about that in HACKING.md. File is located [here](https://git.pleroma.social/pleroma/pleroma-fe/blob/develop/static/config.json).
3. Built-in defaults. Those are hard-coded defaults that are used when `/static/config.json` is not available and BE-provided configuration JSON is missing some JSON properties. ( [Code](https://git.pleroma.social/pleroma/pleroma-fe/blob/develop/src/modules/instance.js) )
## Instance-defaults
Important note that some configurations are treated as "instance default" - it means user is able to change this configuration for themselves. Currently, defaults are only applied for new visitors and people who haven't changed the option in question. If you change some instance default option, there is a chance it won't affect some users.
There's currently no mechanism for user-settings synchronization across several browsers, *user* essentially means *visitor*, most user settings are stored in local storage/IndexedDB and not tied to an account in any way.
## Options
### `theme`
Default theme used for new users. De-facto instance-default, user can change theme.
### `background`
Default image background. Be aware of using too big images as they may take longer to load. Currently image is fitted with `background-size: cover` which means "scaled and cropped", currently left-aligned. De-facto instance default, user can choose their own background, if they remove their own background, instance default will be used instead.
### `logo`, `logoMask`, `logoMargin`
Instance `logo`, could be any image, including svg. 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](https://www.html5rocks.com/en/tutorials/masking/adobe/). 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`.
`logoMargin` 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.
### `redirectRootNoLogin`, `redirectRootLogin`
These two settings should point to where FE should redirect visitor when they login/open up website root
### `chatDisabled`
hides the chat (TODO: even if it's enabled on backend)
### `showInstanceSpecificPanel`
This allows you to include arbitrary HTML content in a panel below navigation menu. PleromaFE looks for an html page `instance/panel.html`, by default it's not provided in FE, but BE bundles some [default one](https://git.pleroma.social/pleroma/pleroma/blob/develop/priv/static/instance/panel.html). De-facto instance-defaults, since user can hide instance-specific panel.
### `collapseMessageWithSubject`
Collapse post content when post has a subject line (content warning). Instance-default.
### `scopeCopy`
Copy post scope (visibility) when replying to a post. Instance-default.
### `subjectLineBehavior`
How to handle subject line (CW) when replying to a post.
* `"email"` - like EMail - prepend `re: ` to subject line if it doesn't already start with it.
* `"masto"` - like Mastodon - copy it as is.
* `"noop"` - do not copy
Instance-default.
### `postContentType`
Default post formatting option (markdown/bbcode/plaintext/etc...)
### `alwaysShowSubjectInput`
`true` - will always show subject line input, `false` - only show when it's not empty (i.e. replying). To hide subject line input completely, set it to `false` and `subjectLineBehavior` to `"noop"`
### `hidePostStats` and `hideUserStats`
Hide counters for posts and users respectively, i.e. hiding repeats/favorites counts for posts, hiding followers/friends counts for users. This is just cosmetic and aimed to ease pressure and bias imposed by stat numbers of people and/or posts. (as an example: so that people care less about how many followers someone has since they can't see that info)
### `loginMethod`
`"password"` - show simple password field
`"token"` - show button to log in with external method (will redirect to login form, more details in BE documentation)
### `webPushNotifications`
Enables [PushAPI](https://developer.mozilla.org/en-US/docs/Web/API/Push_API) - based notifications for users. Instance-default.
### `noAttachmentLinks`
**TODO Currently doesn't seem to be doing anything code-wise**, but implication is to disable adding links for attachments, which looks nicer but breaks compatibility with old GNU/Social servers.
### `nsfwCensorImage`
Use custom image for NSFW'd images
### `showFeaturesPanel`
Show panel showcasing instance features/settings to logged-out visitors
## Indirect configuration
Some features are configured depending on how backend is configured. In general the approach is "if backend allows it there's no need to hide it, if backend doesn't allow it there's no need to show it.
### Chat
**TODO somewhat broken, see: chatDisabled** chat can be disabled by disabling it in backend
### Rich text formatting in post formatting
Rich text formatting options are displayed depending on how many formatting options are enabled on backend, if you don't want your users to use rich text at all you can only allow "text/plain" one, frontend then will only display post text format as a label instead of dropdown (just so that users know for example if you only allow Markdown, only BBCode or only Plain text)
### Who to follow
This is a panel intended for users to find people to follow based on randomness or on post contents. Being potentially privacy unfriendly feature it needs to be enabled and configured in backend to be enabled.
### Safe DM message display
Setting this will change the warning text that is displayed for direct messages.
ATTENTION: If you actually want the behavior to change. You will need to set the appropriate option at the backend. See the backend documentation for information about that.
DO NOT activate this without checking the backend configuration first!

100
docs/HACKING.md Normal file
View file

@ -0,0 +1,100 @@
# Hacking, tweaking, contributing
## What PleromaFE even is, how it works
PleromaFE is an SPA (Single-Page Application) backed by [Vue](https://vuejs.org/) framework. It means that it's just a nearly-empty HTML page with bunch of JavaScript that actually generates and controls DOM (i.e. html elements) in Runtime. Currently, there's no way around it - you have to have Javascript enabled in the browser to make it work, there is a theoretical possibility to generate some HTML server-side but it's not implemented yet.
You can serve static html page and everything from any HTTP(S) server but currently it will try to access /api/ path at same domain it's running on, meaning that as of right now you cannot put it on one domain and access the other without proxying requests.
Development server does exactly that - it serves static html page with javascript and all other assets, adds some code to automatically reload when changes to code are made and proxies requests to some other server.
## Setting up develop server
Setting up development server is fairly straight-forward.
1. On your system you must have **[Node.js](https://nodejs.org/) version 8** and newer installed. For older systems or systems that do not package node you can try [NodeSource](https://github.com/nodesource/distributions) repositories. *Windows support theoretically possible but isn't tested.*
2. For fetching dependencies and running basic tasks you will *[Yarn](https://yarnpkg.com/)* installed.
3. Clone the repository, `cd` into it and run `yarn` to fetch dependencies.
4. If you want to point development server at some instance you will need to copy `config/local.example.json` to `config/local.json` and change the `target` to point at instance you want, otherwise it will point to `localhost:4000` which is default address for locally-run Pleroma Backend
5. Run `yarn dev` - it will start the server.
6. Open `localhost:8080` in your browser, it might take a while initially until everything is built first time.
## Setting up production build
This could be a bit trickier, you basically need steps 1-4 from *develop build* instructions, and run `yarn build` which will compile and copy eveything needed for production into `dist` folder. As said before, this technically could be used anywhere with some details.
### Replacing your instance's frontend with custom FE build
This is the most easiest way to use and test FE build: you just need to copy or symlink contents of `dist` folder into backend's [static directory](https://docs.pleroma.social/static_dir.html), by default it is located in `instance/static`, or in `/var/lib/pleroma/static` for OTP release installations, create it if it doesn't exist already. Be aware that running `yarn build` wipes the contents of `dist` folder.
### Running production build locally or on a separate server
This is **highly experimental** and only tried once, with no actual simple solution available yet
You will need an HTTP server that can proxy requests for `/api`, `/instance`, `/nodeinfo` and show index.html for every 404 page.
For nginx you'll probably need something like this:
```nginx
server {
listen 80 default_server;
index index.html index.htm index.nginx-debian.html;
root /var/www/html
location /api {
proxy_pass https://example.tld;
}
location /instance {
proxy_pass https://example.tld;
}
location /nodeinfo {
proxy_pass https://example.tld;
}
location / {
try_files $uri $uri/ /index.html;
}
}
```
(ed. note: this is close to what i used last time i had to do it, it may not work and need additions, i basically adjusted default nginx server in debian)
## Basic architecture
### API, Data, Operations
In 99% cases PleromaFE uses [MastoAPI](https://docs.joinmastodon.org/api/) with [Pleroma Extensions](https://docs-develop.pleroma.social/differences_in_mastoapi_responses.html) to fetch the data. The rest is either QvitterAPI leftovers or pleroma-exclusive APIs. QvitterAPI doesn't exactly have documentation and uses different JSON structure and sometimes different parameters and workflows, [this](https://twitter-api.readthedocs.io/en/latest/index.html) could be a good reference though. Some pleroma-exclusive API may still be using QvitterAPI JSON structure.
PleromaFE supports both formats by transforming them into internal format which is basically QvitterAPI one with some additions and renaming. All data is passed trough [Entity Normalizer](/src/services/entity_normalizer/entity_normalizer.service.js) which can serve as a reference of API and what's actually used, it's also a host for all the hacks and data transformation.
For most part, PleromaFE tries to store all the info it can get in global vuex store - every user and post are passed trough updating mechanism where data is either added or merged with existing data, reactively updating the information throughout UI, so if in newest request user's post counter increased, it will be instantly updated in open user profile cards. This is also used to find users, posts and sometimes to build timelines and/or request parameters.
PleromaFE also tries to persist this store, however only stable data is stored, such as user authentication and preferences, user highlights. Persistence is performed by saving and loading chunk of vuex store in browser's LocalStorage/IndexedDB.
TODO: Refactor API code and document it here
### Themes
PleromaFE uses custom theme "framework" which is pretty much just a style tag rendered by vue which only contains CSS3 variables. Every color used in UI should be derived from theme. Theme is stored in a JSON object containing color, opacity, shadow and font information, with most of it being optional.
The most basic theme can consist of 4 to 8 "basic colors", which is also what previous version of themes allowed, with all other colors being derived from those basic colors, i.e. "light background" will be "background" color lightened/darkened, "panel header" will be same as "foreground". The idea is that you can specify just basic color palette and everything else will be generated automatically, but if you really need to tweak some specific color - you can.
As said before - older version only allowed 4 to 8 colors, it also used arrays instead of objects, we still support that. The basic colors are: background, foreground, text, links, red, orange, blue, green. First 4 are mandatory, last 4 have default fallbacks since ever more ancient theme formats only had 4 colors.
Note that with older version themes used different internal naming when persisting state.
Themes are meant to be backwards and somewhat forwards compatible - new colors should properly inherit from some existing one, making it compatible with older versions. When loading newer version of theme all unrecognized colors will be ignored, which for most part should be fine, however adding new features (gradients, masks, whatever it might be) might be breaky.
Lastly, pleroma provides some contrast information and generates readable text color automatically, which is done by tracking background/text color pairs and their contrast - if contrast too low it will try to use background color with inverted lightness, if it's still unacceptable it will fall back to pure black/white.
### Still Image
Most images are wrapped in a component called StillImage, which does one simple thing - tries to detect if image is a GIF and if it is (and user has enabled relevant setting) it will show `<canvas>` with that image instead of actual image. It uses standard method to render an image into canvas which renders first frame of a GIF if it's animated (obviously because canvas by itself isn't animated and you'd need to animate it yourself in JS), it will show actual image on hover. Statuses also allow playing animated avatars when you hover over a post, not just image itself.
## Contributing
Feel free to contribute, most preferred way is by starting a Merge Request in GitLab. Please try to use descriptive names for your branches and merge requests, avoid naming them "fix-issue-777" "777" and so on.

200
docs/USER_GUIDE.md Normal file
View file

@ -0,0 +1,200 @@
> Be prepared for breaking changes, unexpected behavior and this user guide becoming obsolete and wrong.
> If there was no insanity
>
> it would be necessary to create it.
>
> --Catbag
# Pleroma-FE user guide
Pleroma-FE user interface is modeled after Qvitter which is modeled after older Twitter design. It provides a simple 2-column interface for microblogging. While being simple by default it also provides many powerful customization options.
## Posting, reading, basic functions.
After registering and logging in you're presented with your timeline in right column and new post form with timeline list and notifications in the left column.
Posts will contain the text you are posting, but some content will be modified:
1. Mentions: Mentions have the form of @user or @user<span></span>@instance.tld. These will become links to the user's profile. In addition, the mentioned user will always get a notification about the post they have been mentioned in, so only mention users that you want to receive this message.
2. URLs: URLs like `http://example.com` will be automatically be turned into a clickable links.
3. Hashtags: Hashtags like #cofe will also be turned into links.
**Depending on your instance some of the options might not be available or have different defaults**
Let's clear up some basic stuff. When you post something it's called a **post** or it could be called a **status** or even a **toot** or a **prööt** depending on whom you ask. Post has body/content but it also has some other stuff in it - from attachments, visibility scope, subject line.
* **Attachments** are fairly simple - you can attach any file to a post as long as the file is within maximum size limits. If you're uploading explicit material you can mark all of your attachments as sensitive (or add `#nsfw` tag) - it will hide the images and videos behind a warning so that it won't be displayed instantly.
* **Subject line** also known as **CW** (Content Warning) could be used as a header to the post and/or to warn others about contents of the post having something that might upset somebody or something among those lines. Several applications allow to hide post content leaving only subject line visible. As a side-effect using subject line will also mark your images as sensitive (see above).
* **Visiblity scope** controls who will be able to see your posts. There are four scopes available:
1. `Public`: This is the default, and some fediverse software like GNU Social only supports this. This means that your post is accessible by anyone and will be shown in the public timelines.
2. `Unlisted`: This is the same as public, but your post won't appear in the public timelines. The post will still be accessible by anyone who comes across it (for example, by looking at your profile) or by direct linking. They will also appear in public searches.
3. `Followers only`: This will show your post only to your followers. Only they will be able to interact with it. Be careful: When somebody follows you, they will be able to see all your previous `followers only` posts as well! If you want to restrict who can follow you, consider [locking your account down to only approved followers](#profle).
4. `Direct`: This will only send the message to the people explicitly mentioned in the post.
A few things to consider about the security and usage of these scopes:
- None of these options will change the fact that the messages are all saved in the database unencrypted. They will be visible to your server admin and to any other admin of a server who receives this post. Do not share information that you would consider secret or dangerous. Use encrypted messaging systems for these things.
- Follower-only posts can lead to fragmented conversations. If you post a follower-only post and somebody else replies to it with a follower-only post, only people following both of you will see the whole conversation thread. Everybody else will only see half of it. Keep this in mind and keep conversations public if possible.
- Changing scopes during a thread or adding people to a direct message will not retroactively make them see the whole conversation. If you add someone to a direct message conversation, they will not see the post that happened before they were mentioned.
* **Reply-to** if you are replying to someone, your post will also contain a note that your post is referring to the post you're replying to. Person you're replying to will receive a notification *even* if you remove them from mentioned people. You won't receive notifications when replying to your own posts, but it's useful to reply to your own posts to provide people some context if it's a follow-up to a previous post. There's a small "Reply to ..." label under post author's name which you can hover on to see what post it's referring to.
Sometimes you may encounter posts that seem different than what they are supposed to. For example, you might see a direct message without any mentions in the text. This can happen because internally, the Fediverse has a different addressing mechanism similar to email, with `to` and `cc` fields. While these are not directly accessible in PleromaFE, other software in the Fediverse might generate those posts. Do not worry in these cases, these are normal and not a bug.
#### Rich text
By default new posts you make are plaintext, meaning you can't make text **bold** or add custom links or make lists or anything like that. However if your instance allows it you can use Markdown or BBCode or HTML to spice up your text, however there are certain limitations to what HTML tags and what features of Markdown you can use.
this section will be expanded later
### Other actions
In addition to posting you can also *favorite* post also known as *liking* them and *repeat* posts (also known as *retweeting*, *boosting* and even *reprööting*). Favoriting a post increments a counter on it, notifies post author of your affection towards that post and also adds that post to your "favorited" posts list (in your own profile, "Favorites" tab). Reprööting a post does all that and also repeats this post to your followers and your profile page with a note "*user* repeated post".
Your own posts can be deleted, but this will only reliably delete the post from your own instance. Other instances will receive a deletion notice, but there's no way to force them to actually delete a post. In addition, not all instances that contain the message might even receive the deletion notice, because they might be offline or not known to have the post because they received it through a repeat. Lastly, deletion notice might not reach certain frontends and clients - post will be visible for them until page refresh or cache clear, they probably won't be able to interact with it apart from replying to it (which will have reply-to mark missing).
If you are a moderator, you can also delete posts by other people. If those people are on your instance, it will delete the post and send out the deletion notice to other servers. If they are not on your instance, it will just remove the post from your local instance.
There's also an option to report a user via a post (if the feature is available on your instance) which could be used to notify your (and probably other instance's) admin that someone is being naughty.
## Users
When you see someone, you can click on their user picture to view their profile, and click on the userpic in that to see *full* profile. You can *follow* them, *mute* and *block* them. Following is self-explanatory, it adds them t your Home Timeline, lists you as a follower and gives you access to follower-only posts if they have any. Muting makes posts and notifications made by them very tiny, giving you an option to see the post if you're curious. However on clients other than PleromaFE their posts will be completely removed. *Blocking* a user removes them from your timeline and notifications and prevents them from following you (automatically unfollows them from you).
Please note that some users can be "locked", meaning instead of following them you send a follow request they need to approve for you to become their follower.
## Timelines
Currently you have several timelines to browse trough:
* **Timeline** aka Home Timeline - this timeline contains all posts by people you follow and your own posts, as well as posts mentioning you directly.
* **Interactions** all interactions you've had with people on the network, basically same as notifications except grouped in convenient way - mentions separate from favorites with repeats separate from follows
* **Direct Messages** all posts with `direct` scope addressed to you or mentioning you.
* **Public Timelines** all posts made by users on instance you're on
* **The Whole Known Network** also known as **TWKN** or **Federated Timeline** - all posts on the network by everyone, almost. Due to nature of the network your instance may not know *all** the instances on the network, so only posts originating from known instances are shown there.
## Your profile
By clicking wrench icon above the post form you can access the profile edit or "user settings" screen.
### Profle
Here you can set up how you appear to other users among with some other settings:
- Name: this is text that displays next to your avatar in posts. Please note that you **cannot** change your *@<span></span>handle*
- Bio: this will be displayed under your profile - you can put anything you want there you want for everyone to see.
- Restrict your account to approved followers only: this makes your account "locked", when people follow you - you have to approve or deny their follow requests, this gives more control over who sees your followers only posts.
- Default visibility scope: this chooses your default post scope for new posts
- Strip rich text from all posts: this strips rich text formatting (bold/italics/lists etc) from all incoming posts. Will only affect newly fetched posts.
If you're admin or moderator on your instance you also get "Show [role] badge in my profile" - this controls whether to show "Admin" or "Moderator** label on your profile page.
**For all options mentioned above you have to click "Submit" button for changes to take place**
- Avatar: this changes picture next to your posts. Your avatar shouldn't exceed 2 MiB (2097152 bytes) or it could cause problems with certain instances.
- Banner: this changes background on your profile card. Same as avatar it shouldn't exceed 2 MiB limit.
- Profile Background: this changes background picture for UI. It isn't shown to anyone else **yet**, but some time later it will be shown when viewing your profile.
### Security
Here you can change your password, revoke access tokens, configure 2-factor authentication (if available).
### Notifications
This screen allows more fine-grained control over what notifications to show to you based on whom it comes from
### Data Import/Export
This allows you to export and import a list of people you follow, in case instance's database gets reverted or if you want to move to another server. Note that you **CANNOT export/import list of people who *follow you***, they'll just need to follow you back after you move.
### Blocks and Mutes
These screens give access to full list of people you block/mute, useful for *un*blocking/*un*muting people because blocking/muting them most likely removes them out of your sight completely.
## Other stuff
By default you can see **ALL** posts made by other users on your Home Timeline, this contrast behavior of Twitter and Mastodon, which shows you only non-reply posts and replies to people you follow. You can set it up to replicate the said behavior, however the option is currently broken.
You can view other people's profiles and search for users (top-right corner, person with a plus icon). Tag search is possible but not implemented properly yet, right now you can click on tag link in a post to see posts tagged with that post.
You can also view posts you've favorited on your own profile, but you cannot see favorites by other people.
Due to nature of how Pleroma (backend) operates you might see old posts appear as if they are new, this is because instance just learned about that post (i.e. your instance is younger that some other ones) and someone interacted with old post. Posts are sorted by date of when they are received, not date they have been posted because it's very easy to spoof the date, so a post claiming it "was" made in year 2077 could hand at top of your TL forever.
# Customization and configuration
Clicking on the cog icon in the upper right will go to the settings screen.
## General
### Interface
- Language: Here you can set the interface language. The default language is the one that you set in your browser settings.
- Hide instance-specific panel: This hides the panel in the lower left that usually contains general information about the server.
### Timeline
- Hide posts of muted users: If this is set, 'muting' a user will completely hide their posts instead of collapsing them.
- Collapse posts with subjects: This will collapse posts that contain a subject, hiding their content. Subjects are also sometimes called content warnings.
- Enable automatic streaming of new posts when scrolled to the top: With this enabled, new posts will automatically stream in when you are scrolled to the top. Otherwise, you will see a button on the timeline that will let you display the new posts.
- Pause streaming when tab is not focused: This pauses the automatic streaming that the previous option enables when the tab is out of focus. This is useful if you don't want to miss any new posts.
- Enable automatic loading when scrolled to the bottom: When this is disabled, a button will be shown on the bottom of the timeline that will let you load older posts.
- Enable reply-link preview on hover: Status posts in the timeline and notifications contain links to replies and to the post they are a reply to. If this setting is enabled, hovering over that link will display that linked post in a small hovering overlay.
### Composing
- Copy scope when replying: When this is activated, the scope of a reply will be the same as the scope of the post it is replying to. This is useful to prevent accidentally moving private discussions to public, or vice versa.
- Always show subject field: Whether or not to display the 'subject' input field in the post form. If you do not want to use subjects, you can deactivate this.
- Copy subject when replying: This controls if the subject of a post will be copied from the post it is replying to.
- Post status content type: Selects the default content type of your post. The options are: Plain text, HTML, BBCode and Markdown.
- Minimize scope selection options: Selecting this will reduce the visibility scopes to 'direct', your default post scope and post scope of post you're replying to.
- Automatically hide New Post button: Mobile interface only: hide floating "New post" button when scrolling
### Attachments
- Hide attachments in timeline: Do not display attachments in timelines. They will still display in expanded conversations. This is useful to save bandwidth and for browsing in public.
- Hide attachments in conversations: Also hide attachments in expanded conversations.
- Maximum amount of thumbnails per post: Exactly that :)
- Enable clickthrough NSFW attachment hiding: Hide attachments that are marked as NSFW/sensitive behind a click-through image.`
- Preload images: This will preload the hidden images so that they display faster when clicking through.
- Open NSFW attachments with just one click: Directly open NSFW attachments in a maximised state instead of revealing the image thumbnail.
- Play-on-hover GIFs: With this activated, GIFs images and avatars will only be animated on mouse hover. Otherwise, they will be always animated. This is very useful if your timeline looks too flashy from people's animated avatars and eases the CPU load.
- Loop videos: Whether to loop videos indefinitely.
- Loop only videos without sound: Some instances will use videos without sounds instead of GIFs. This will make only those videos autoplay.
- Play videos directly in the media viewer: Play videos right in the timeline instead of opening it in a modal
- Don't crop the attachment in thumbnails: if enabled, images in attachments will be fit entirely inside the container instead of being zoomed in and cropped.
### Notifications
- Enable web push notifications: this enables Web Push notifications, to allow receiving notifications even when the page isn't opened, doesn't affect regular notifications.
## Theme
You can change the look and feel of Pleroma Frontend here. You can choose from several instance-provided presets and you can load one from file and save current theme to file. Before you apply new theme you can see what it will look like approximately in preview section.
Themes engine was made to be easy to use while giving an option for powerful in-depth customization - you can just tweak colors on "Common" tab and leave everything else as is.
If there's a little check box next to a color picker it means that color is optional and unless checked will be automatically picked based on some other color or defaults.
For some features you can also adjust transparency of it by changing its opacity, you just need to tick checkbox next to it, otherwise it will be using default opacity.
Contrast information is also provided - you can see how readable text is based on contrast between text color and background, icons under color pickers represent contrast rating based on [WCAG](https://www.w3.org/TR/2008/REC-WCAG20-20081211/#visual-audio-contrast-contrast) - thumbs up means AAA rating (good), half-filled circle means AA rating (acceptable) and warning icon means it doesn't pass the minimal contrast requirement and probably will be less readable, especially for vision-challenged people, you can hover over icon to see more detailed information. *Please note* that if background is not opaque (opacity != 1) contrast will be measured based on "worst case scenario", i.e. behind semi-transparent background lies some solid color that makes text harder to read, this however is still inaccurate because it doesn't account that background can be noisy/busy, making text even harder to read.
Apart from colors you can also tweak shadow and lighting, which is used mostly to give buttons proper relief based on their state, give panes their shade, make things glow etc. It's quite powerful, and basically provides somewhat convenient interface for [CSS Shadows](https://developer.mozilla.org/en-US/docs/Web/CSS/box-shadow).
Another thing you can tweak is theme's roundness - some people like sharp edges, some want things more rounded. This is also used if you want circled or square avatars.
Lastly, you can redefine fonts used in UI without changing fonts in your browser or system, this however requires you to enter font's full name and having that font installed on your system.
## Filtering
- Types of notifications to show: This controls what kind of notifications will appear in notification column and which notifications to get in your system outside the web page
- Replies in timeline: You may know that other social networks like Twitter will often not display replies to other people in your timeline, even if you are following the poster. Pleroma usually will show these posts to you to encourage conversation. If you do not like this behavior, you can change it here.
- Hide post statistics: This hides the number of favorites, number of replies, etc.
- Hide user statistics: This hides the number of followers, friends, etc.
- Muted words: A list of words that will be muted (i.e. displayed in a collapsed state) on the timeline and in notifications. An easy way to tune down noise in your timeline. Posts can always be expanded when you actually want to see them.
- Hide filtered statuses: Selecting this will hide the filtered / muted posts completely instead of collapsing them.
## Version
Just displays the backend and frontend version. Useful to mention in bug reports.

View file

@ -9,7 +9,8 @@
<link rel="stylesheet" href="/static/font/css/fontello.css">
<link rel="stylesheet" href="/static/font/css/animation.css">
</head>
<body style="display: none">
<body>
<noscript>To use Pleroma, please enable JavaScript.</noscript>
<div id="app"></div>
<!-- built files will be auto injected -->
</body>

View file

@ -93,6 +93,7 @@
"nightwatch": "^0.9.8",
"opn": "^4.0.2",
"ora": "^0.3.0",
"postcss-loader": "^3.0.0",
"raw-loader": "^0.5.1",
"sass": "^1.17.3",
"sass-loader": "git://github.com/webpack-contrib/sass-loader",

5
postcss.config.js Normal file
View file

@ -0,0 +1,5 @@
module.exports = {
plugins: [
require('autoprefixer')
]
}

View file

@ -89,7 +89,11 @@ export default {
sitename () { return this.$store.state.instance.name },
chat () { return this.$store.state.chat.channel.state === 'joined' },
suggestionsEnabled () { return this.$store.state.instance.suggestionsEnabled },
showInstanceSpecificPanel () { return this.$store.state.instance.showInstanceSpecificPanel },
showInstanceSpecificPanel () {
return this.$store.state.instance.showInstanceSpecificPanel &&
!this.$store.state.config.hideISP &&
this.$store.state.instance.instanceSpecificPanelContent
},
showFeaturesPanel () { return this.$store.state.instance.showFeaturesPanel },
isMobileLayout () { return this.$store.state.interface.mobileLayout }
},

View file

@ -549,23 +549,6 @@ nav {
color: var(--faint, $fallback--faint);
box-shadow: 0px 0px 4px rgba(0,0,0,.6);
box-shadow: var(--topBarShadow);
.back-button {
display: block;
max-width: 99px;
transition-property: opacity, max-width;
transition-duration: 300ms;
transition-timing-function: ease-out;
i {
margin: 0 1em;
}
&.hidden {
opacity: 0;
max-width: 5px;
}
}
}
.fade-enter-active, .fade-leave-active {
@ -601,12 +584,6 @@ nav {
overflow-y: scroll;
}
nav {
.back-button {
display: none;
}
}
.sidebar-bounds {
overflow: hidden;
max-height: 100vh;

View file

@ -41,6 +41,7 @@
<search-bar
class="nav-icon mobile-hidden"
@toggled="onSearchBarToggled"
@click.stop.native
/>
<router-link
class="mobile-hidden"
@ -106,6 +107,7 @@
:floating="true"
class="floating-chat mobile-hidden"
/>
<MobilePostStatusModal />
<UserReportingModal />
<portal-target name="modal" />
</div>

View file

@ -109,12 +109,6 @@ const setSettings = async ({ apiConfig, staticConfig, store }) => {
copyInstanceOption('noAttachmentLinks')
copyInstanceOption('showFeaturesPanel')
if ((config.chatDisabled)) {
store.dispatch('disableChat')
} else {
store.dispatch('initializeSocket')
}
return store.dispatch('setTheme', config['theme'])
}
@ -254,6 +248,7 @@ const getNodeInfo = async ({ store }) => {
store.dispatch('setInstanceOption', { name: 'gopherAvailable', value: features.includes('gopher') })
store.dispatch('setInstanceOption', { name: 'pollsAvailable', value: features.includes('polls') })
store.dispatch('setInstanceOption', { name: 'pollLimits', value: metadata.pollLimits })
store.dispatch('setInstanceOption', { name: 'mailerEnabled', value: metadata.mailerEnabled })
store.dispatch('setInstanceOption', { name: 'restrictedNicknames', value: metadata.restrictedNicknames })
store.dispatch('setInstanceOption', { name: 'postFormats', value: metadata.postFormats })

View file

@ -9,6 +9,7 @@ import UserProfile from 'components/user_profile/user_profile.vue'
import Search from 'components/search/search.vue'
import Settings from 'components/settings/settings.vue'
import Registration from 'components/registration/registration.vue'
import PasswordReset from 'components/password_reset/password_reset.vue'
import UserSettings from 'components/user_settings/user_settings.vue'
import FollowRequests from 'components/follow_requests/follow_requests.vue'
import OAuthCallback from 'components/oauth_callback/oauth_callback.vue'
@ -46,6 +47,7 @@ export default (store) => {
{ name: 'dms', path: '/users/:username/dms', component: DMs, beforeEnter: validateAuthenticatedRoute },
{ name: 'settings', path: '/settings', component: Settings },
{ name: 'registration', path: '/registration', component: Registration },
{ name: 'password-reset', path: '/password-reset', component: PasswordReset },
{ name: 'registration-token', path: '/registration/:token', component: Registration },
{ name: 'friend-requests', path: '/friend-requests', component: FollowRequests, beforeEnter: validateAuthenticatedRoute },
{ name: 'user-settings', path: '/user-settings', component: UserSettings, beforeEnter: validateAuthenticatedRoute },

View file

@ -190,6 +190,7 @@
.video {
width: 100%;
height: 100%;
}
.play-icon {
@ -286,7 +287,7 @@
}
img {
image-orientation: from-image;
image-orientation: from-image; // NOTE: only FF supports this
}
}
}

View file

@ -87,6 +87,7 @@
&-expanded-content {
flex: 1;
margin-left: 0.7em;
min-width: 0;
}
}
</style>

View file

@ -42,7 +42,7 @@ const conversation = {
'statusoid',
'collapsable',
'isPage',
'showPinned'
'pinnedStatusIdsObject'
],
created () {
if (this.isPage) {
@ -110,7 +110,7 @@ const conversation = {
Status
},
watch: {
'$route': 'fetchConversation',
status: 'fetchConversation',
expanded (value) {
if (value) {
this.fetchConversation()
@ -149,9 +149,6 @@ const conversation = {
},
toggleExpanded () {
this.expanded = !this.expanded
if (!this.expanded) {
this.setHighlight(null)
}
}
}
}

View file

@ -21,7 +21,7 @@
:inline-expanded="collapsable && isExpanded"
:statusoid="status"
:expandable="!isExpanded"
:show-pinned="showPinned"
:show-pinned="pinnedStatusIdsObject && pinnedStatusIdsObject[status.id]"
:focused="focused(status.id)"
:in-conversation="isExpanded"
:highlight="getHighlight()"

View file

@ -16,6 +16,16 @@ const ExtraButtons = {
this.$store.dispatch('unpinStatus', this.status.id)
.then(() => this.$emit('onSuccess'))
.catch(err => this.$emit('onError', err.error.error))
},
muteConversation () {
this.$store.dispatch('muteConversation', this.status.id)
.then(() => this.$emit('onSuccess'))
.catch(err => this.$emit('onError', err.error.error))
},
unmuteConversation () {
this.$store.dispatch('unmuteConversation', this.status.id)
.then(() => this.$emit('onSuccess'))
.catch(err => this.$emit('onError', err.error.error))
}
},
computed: {
@ -31,8 +41,8 @@ const ExtraButtons = {
canPin () {
return this.ownStatus && (this.status.visibility === 'public' || this.status.visibility === 'unlisted')
},
enabled () {
return this.canPin || this.canDelete
canMute () {
return !!this.currentUser
}
}
}

View file

@ -1,6 +1,6 @@
<template>
<v-popover
v-if="enabled"
v-if="canDelete || canMute || canPin"
trigger="click"
placement="top"
class="extra-button-popover"
@ -9,6 +9,20 @@
>
<div slot="popover">
<div class="dropdown-menu">
<button
v-if="canMute && !status.muted"
class="dropdown-item dropdown-item-icon"
@click.prevent="muteConversation"
>
<i class="icon-eye-off" /><span>{{ $t("status.mute_conversation") }}</span>
</button>
<button
v-if="canMute && status.muted"
class="dropdown-item dropdown-item-icon"
@click.prevent="unmuteConversation"
>
<i class="icon-eye-off" /><span>{{ $t("status.unmute_conversation") }}</span>
</button>
<button
v-if="!status.pinned && canPin"
v-close-popover

View file

@ -1,8 +1,6 @@
const FeaturesPanel = {
computed: {
chat: function () {
return this.$store.state.instance.chatAvailable && (!this.$store.state.chatDisabled)
},
chat: function () { return this.$store.state.instance.chatAvailable },
gopher: function () { return this.$store.state.instance.gopherAvailable },
whoToFollow: function () { return this.$store.state.instance.suggestionsEnabled },
mediaProxy: function () { return this.$store.state.instance.mediaProxyAvailable },

View file

@ -61,13 +61,17 @@
}
&.contain-fit {
img, video {
img,
video,
canvas {
object-fit: contain;
}
}
&.cover-fit {
img, video {
img,
video,
canvas {
object-fit: cover;
}
}

View file

@ -2,9 +2,6 @@ const InstanceSpecificPanel = {
computed: {
instanceSpecificPanelContent () {
return this.$store.state.instance.instanceSpecificPanelContent
},
show () {
return !this.$store.state.config.hideISP
}
}
}

View file

@ -1,8 +1,5 @@
<template>
<div
v-if="show"
class="instance-specific-panel"
>
<div class="instance-specific-panel">
<div class="panel panel-default">
<div class="panel-body">
<!-- eslint-disable vue/no-v-html -->
@ -14,6 +11,3 @@
</template>
<script src="./instance_specific_panel.js" ></script>
<style lang="scss">
</style>

View file

@ -13,8 +13,8 @@ const Interactions = {
}
},
methods: {
onModeSwitch (index, dataset) {
this.filterMode = tabModeDict[dataset.filter]
onModeSwitch (key) {
this.filterMode = tabModeDict[key]
}
},
components: {

View file

@ -10,18 +10,15 @@
:on-switch="onModeSwitch"
>
<span
data-tab-dummy
data-filter="mentions"
key="mentions"
:label="$t('nav.mentions')"
/>
<span
data-tab-dummy
data-filter="likes+repeats"
key="likes+repeats"
:label="$t('interactions.favs_repeats')"
/>
<span
data-tab-dummy
data-filter="follows"
key="follows"
:label="$t('interactions.follows')"
/>
</tab-switcher>

View file

@ -5,6 +5,11 @@ const LinkPreview = {
'size',
'nsfw'
],
data () {
return {
imageLoaded: false
}
},
computed: {
useImage () {
// Currently BE shoudn't give cards if tagged NSFW, this is a bit paranoid
@ -15,6 +20,15 @@ const LinkPreview = {
useDescription () {
return this.card.description && /\S/.test(this.card.description)
}
},
created () {
if (this.useImage) {
const newImg = new Image()
newImg.onload = () => {
this.imageLoaded = true
}
newImg.src = this.card.image
}
}
}

View file

@ -7,7 +7,7 @@
rel="noopener"
>
<div
v-if="useImage"
v-if="useImage && imageLoaded"
class="card-image"
:class="{ 'small-image': size === 'small' }"
>

View file

@ -33,6 +33,11 @@
type="password"
>
</div>
<div class="form-group">
<router-link :to="{name: 'password-reset'}">
{{ $t('password_reset.forgot_password') }}
</router-link>
</div>
</template>
<div

View file

@ -63,6 +63,7 @@
max-width: 90%;
max-height: 90%;
box-shadow: 0px 5px 15px 0 rgba(0, 0, 0, 0.5);
image-orientation: from-image; // NOTE: only FF supports this
}
.modal-view-button-arrow {

View file

@ -1,14 +1,12 @@
import SideDrawer from '../side_drawer/side_drawer.vue'
import Notifications from '../notifications/notifications.vue'
import MobilePostStatusModal from '../mobile_post_status_modal/mobile_post_status_modal.vue'
import { unseenNotificationsFromStore } from '../../services/notification_utils/notification_utils'
import GestureService from '../../services/gesture_service/gesture_service'
const MobileNav = {
components: {
SideDrawer,
Notifications,
MobilePostStatusModal
Notifications
},
data: () => ({
notificationsCloseGesture: undefined,

View file

@ -70,7 +70,6 @@
ref="sideDrawer"
:logout="logout"
/>
<MobilePostStatusModal />
</div>
</template>

View file

@ -34,14 +34,19 @@
@import '../../_variables.scss';
.post-form-modal-view {
max-height: 100%;
display: block;
align-items: flex-start;
}
.post-form-modal-panel {
flex-shrink: 0;
margin: 25% 0 4em 0;
margin-top: 25%;
margin-bottom: 2em;
width: 100%;
max-width: 700px;
@media (orientation: landscape) {
margin-top: 8%;
}
}
.new-status-button {

View file

@ -0,0 +1,62 @@
import { mapState } from 'vuex'
import passwordResetApi from '../../services/new_api/password_reset.js'
const passwordReset = {
data: () => ({
user: {
email: ''
},
isPending: false,
success: false,
throttled: false,
error: null
}),
computed: {
...mapState({
signedIn: (state) => !!state.users.currentUser,
instance: state => state.instance
}),
mailerEnabled () {
return this.instance.mailerEnabled
}
},
created () {
if (this.signedIn) {
this.$router.push({ name: 'root' })
}
},
methods: {
dismissError () {
this.error = null
},
submit () {
this.isPending = true
const email = this.user.email
const instance = this.instance.server
passwordResetApi({ instance, email }).then(({ status }) => {
this.isPending = false
this.user.email = ''
if (status === 204) {
this.success = true
this.error = null
} else if (status === 404 || status === 400) {
this.error = this.$t('password_reset.not_found')
this.$nextTick(() => {
this.$refs.email.focus()
})
} else if (status === 429) {
this.throttled = true
this.error = this.$t('password_reset.too_many_requests')
}
}).catch(() => {
this.isPending = false
this.user.email = ''
this.error = this.$t('general.generic_error')
})
}
}
}
export default passwordReset

View file

@ -0,0 +1,116 @@
<template>
<div class="settings panel panel-default">
<div class="panel-heading">
{{ $t('password_reset.password_reset') }}
</div>
<div class="panel-body">
<form
class="password-reset-form"
@submit.prevent="submit"
>
<div class="container">
<div v-if="!mailerEnabled">
<p>
{{ $t('password_reset.password_reset_disabled') }}
</p>
</div>
<div v-else-if="success || throttled">
<p v-if="success">
{{ $t('password_reset.check_email') }}
</p>
<div class="form-group text-center">
<router-link :to="{name: 'root'}">
{{ $t('password_reset.return_home') }}
</router-link>
</div>
</div>
<div v-else>
<p>
{{ $t('password_reset.instruction') }}
</p>
<div class="form-group">
<input
ref="email"
v-model="user.email"
:disabled="isPending"
:placeholder="$t('password_reset.placeholder')"
class="form-control"
type="input"
>
</div>
<div class="form-group">
<button
:disabled="isPending"
type="submit"
class="btn btn-default btn-block"
>
{{ $t('general.submit') }}
</button>
</div>
</div>
<p
v-if="error"
class="alert error notice-dismissible"
>
<span>{{ error }}</span>
<a
class="button-icon dismiss"
@click.prevent="dismissError()"
>
<i class="icon-cancel" />
</a>
</p>
</div>
</form>
</div>
</div>
</template>
<script src="./password_reset.js"></script>
<style lang="scss">
@import '../../_variables.scss';
.password-reset-form {
display: flex;
flex-direction: column;
align-items: center;
margin: 0.6em;
.container {
display: flex;
flex: 1 0;
flex-direction: column;
margin-top: 0.6em;
max-width: 18rem;
}
.form-group {
display: flex;
flex-direction: column;
margin-bottom: 1em;
padding: 0.3em 0.0em 0.3em;
line-height: 24px;
}
.error {
text-align: center;
animation-name: shakeError;
animation-duration: 0.4s;
animation-timing-function: ease-in-out;
}
.alert {
padding: 0.5em;
margin: 0.3em 0.0em 1em;
}
.notice-dismissible {
padding-right: 2rem;
}
.icon-cancel {
cursor: pointer;
}
}
</style>

View file

@ -268,6 +268,7 @@ $validations-cRed: #f04124;
textarea {
min-height: 100px;
resize: vertical;
}
.form-group {

View file

@ -75,8 +75,8 @@ const Search = {
const length = this[tabName].length
return length === 0 ? '' : ` (${length})`
},
onResultTabSwitch (_index, dataset) {
this.currenResultTab = dataset.filter
onResultTabSwitch (key) {
this.currenResultTab = key
},
getActiveTab () {
if (this.visibleStatuses.length > 0) {

View file

@ -31,21 +31,18 @@
<tab-switcher
ref="tabSwitcher"
:on-switch="onResultTabSwitch"
:custom-active="currenResultTab"
:active-tab="currenResultTab"
>
<span
data-tab-dummy
data-filter="statuses"
key="statuses"
:label="$t('user_card.statuses') + resultCount('visibleStatuses')"
/>
<span
data-tab-dummy
data-filter="people"
key="people"
:label="$t('search.people') + resultCount('users')"
/>
<span
data-tab-dummy
data-filter="hashtags"
key="hashtags"
:label="$t('search.hashtags') + resultCount('hashtags')"
/>
</tab-switcher>

View file

@ -20,6 +20,11 @@ const SearchBar = {
toggleHidden () {
this.hidden = !this.hidden
this.$emit('toggled', this.hidden)
this.$nextTick(() => {
if (!this.hidden) {
this.$refs.searchInput.focus()
}
})
}
}
}

View file

@ -335,7 +335,7 @@ const Status = {
return
}
}
if (target.className.match(/hashtag/)) {
if (target.rel.match(/(?:^|\s)tag(?:$|\s)/) || target.className.match(/hashtag/)) {
// Extract tag name from link url
const tag = extractTagFromUrl(target.href)
if (tag) {

View file

@ -32,7 +32,7 @@
</template>
<template v-else>
<div
v-if="showPinned && statusoid.pinned"
v-if="showPinned"
class="status-pin"
>
<i class="fa icon-pin faint" />

View file

@ -7,8 +7,10 @@
v-if="animated"
ref="canvas"
/>
<!-- NOTE: key is required to force to re-render img tag when src is changed -->
<img
ref="src"
:key="src"
:src="src"
:referrerpolicy="referrerpolicy"
@load="onLoad"

View file

@ -14,7 +14,7 @@ export default Vue.component('tab-switcher', {
required: false,
type: Function
},
customActive: {
activeTab: {
required: false,
type: String
},
@ -29,6 +29,16 @@ export default Vue.component('tab-switcher', {
active: this.$slots.default.findIndex(_ => _.tag)
}
},
computed: {
activeIndex () {
// In case of controlled component
if (this.activeTab) {
return this.$slots.default.findIndex(slot => this.activeTab === slot.key)
} else {
return this.active
}
}
},
beforeUpdate () {
const currentSlot = this.$slots.default[this.active]
if (!currentSlot.tag) {
@ -36,22 +46,14 @@ export default Vue.component('tab-switcher', {
}
},
methods: {
activateTab (index, dataset) {
activateTab (index) {
return (e) => {
e.preventDefault()
if (typeof this.onSwitch === 'function') {
this.onSwitch.call(null, index, this.$slots.default[index].elm.dataset)
this.onSwitch.call(null, this.$slots.default[index].key)
}
this.active = index
}
},
isActiveTab (index) {
const customActiveIndex = this.$slots.default.findIndex(slot => {
const dataFilter = slot.data && slot.data.attrs && slot.data.attrs['data-filter']
return this.customActive && this.customActive === dataFilter
})
return customActiveIndex > -1 ? customActiveIndex === index : index === this.active
}
},
render (h) {
@ -61,13 +63,13 @@ export default Vue.component('tab-switcher', {
const classesTab = ['tab']
const classesWrapper = ['tab-wrapper']
if (this.isActiveTab(index)) {
if (this.activeIndex === index) {
classesTab.push('active')
classesWrapper.push('active')
}
if (slot.data.attrs.image) {
return (
<div class={ classesWrapper.join(' ')}>
<div class={classesWrapper.join(' ')}>
<button
disabled={slot.data.attrs.disabled}
onClick={this.activateTab(index)}
@ -79,7 +81,7 @@ export default Vue.component('tab-switcher', {
)
}
return (
<div class={ classesWrapper.join(' ')}>
<div class={classesWrapper.join(' ')}>
<button
disabled={slot.data.attrs.disabled}
onClick={this.activateTab(index)}
@ -91,7 +93,7 @@ export default Vue.component('tab-switcher', {
const contents = this.$slots.default.map((slot, index) => {
if (!slot.tag) return
const active = index === this.active
const active = this.activeIndex === index
if (this.renderOnlyFocused) {
return active
? <div class="active">{slot}</div>

View file

@ -1,7 +1,20 @@
import Status from '../status/status.vue'
import timelineFetcher from '../../services/timeline_fetcher/timeline_fetcher.service.js'
import Conversation from '../conversation/conversation.vue'
import { throttle } from 'lodash'
import { throttle, keyBy } from 'lodash'
export const getExcludedStatusIdsByPinning = (statuses, pinnedStatusIds) => {
const ids = []
if (pinnedStatusIds && pinnedStatusIds.length > 0) {
for (let status of statuses) {
if (!pinnedStatusIds.includes(status.id)) {
break
}
ids.push(status.id)
}
}
return ids
}
const Timeline = {
props: [
@ -11,7 +24,8 @@ const Timeline = {
'userId',
'tag',
'embedded',
'count'
'count',
'pinnedStatusIds'
],
data () {
return {
@ -39,6 +53,15 @@ const Timeline = {
body: ['timeline-body'].concat(!this.embedded ? ['panel-body'] : []),
footer: ['timeline-footer'].concat(!this.embedded ? ['panel-footer'] : [])
}
},
// id map of statuses which need to be hidden in the main list due to pinning logic
excludedStatusIdsObject () {
const ids = getExcludedStatusIdsByPinning(this.timeline.visibleStatuses, this.pinnedStatusIds)
// Convert id array to object
return keyBy(ids)
},
pinnedStatusIdsObject () {
return keyBy(this.pinnedStatusIds)
}
},
components: {

View file

@ -28,13 +28,25 @@
</div>
<div :class="classes.body">
<div class="timeline">
<template v-for="statusId in pinnedStatusIds">
<conversation
v-for="status in timeline.visibleStatuses"
v-if="timeline.statusesObject[statusId]"
:key="statusId + '-pinned'"
class="status-fadein"
:statusoid="timeline.statusesObject[statusId]"
:collapsable="true"
:pinned-status-ids-object="pinnedStatusIdsObject"
/>
</template>
<template v-for="status in timeline.visibleStatuses">
<conversation
v-if="!excludedStatusIdsObject[status.id]"
:key="status.id"
class="status-fadein"
:statusoid="status"
:collapsable="true"
/>
</template>
</div>
</div>
<div :class="classes.footer">

View file

@ -16,7 +16,7 @@ const UserAvatar = {
},
computed: {
imgSrc () {
return this.showPlaceholder ? '/images/avi.png' : this.src
return this.showPlaceholder ? '/images/avi.png' : this.user.profile_image_url_original
}
},
methods: {

View file

@ -3,7 +3,7 @@
class="avatar"
:alt="user.screen_name"
:title="user.screen_name"
:src="user.profile_image_url_original"
:src="imgSrc"
:class="{ 'avatar-compact': compact, 'better-shadow': betterShadow }"
:image-load-error="imageLoadError"
/>

View file

@ -7,7 +7,7 @@ import { requestFollow, requestUnfollow } from '../../services/follow_manipulate
import generateProfileLink from 'src/services/user_profile_link_generator/user_profile_link_generator'
export default {
props: [ 'user', 'switcher', 'selected', 'hideBio', 'rounded', 'bordered' ],
props: [ 'user', 'switcher', 'selected', 'hideBio', 'rounded', 'bordered', 'allowZoomingAvatar' ],
data () {
return {
followRequestInProgress: false,
@ -162,6 +162,14 @@ export default {
},
reportUser () {
this.$store.dispatch('openUserReportingModal', this.user.id)
},
zoomAvatar () {
const attachment = {
url: this.user.profile_image_url_original,
mimetype: 'image'
}
this.$store.dispatch('setMedia', [attachment])
this.$store.dispatch('setCurrent', attachment)
}
}
}

View file

@ -7,7 +7,23 @@
<div class="panel-heading">
<div class="user-info">
<div class="container">
<router-link :to="userProfileLink(user)">
<a
v-if="allowZoomingAvatar"
class="user-info-avatar-link"
@click="zoomAvatar"
>
<UserAvatar
:better-shadow="betterShadow"
:user="user"
/>
<div class="user-info-avatar-link-overlay">
<i class="button-icon icon-zoom-in" />
</div>
</a>
<router-link
v-else
:to="userProfileLink(user)"
>
<UserAvatar
:better-shadow="betterShadow"
:user="user"
@ -351,6 +367,7 @@
.container {
padding: 16px 0 6px;
display: flex;
align-items: flex-start;
max-height: 56px;
.avatar {
@ -372,6 +389,35 @@
}
}
&-avatar-link {
position: relative;
cursor: pointer;
&-overlay {
position: absolute;
left: 0;
top: 0;
right: 0;
bottom: 0;
background-color: rgba(0, 0, 0, 0.3);
display: flex;
justify-content: center;
align-items: center;
border-radius: $fallback--avatarRadius;
border-radius: var(--avatarRadius, $fallback--avatarRadius);
opacity: 0;
transition: opacity .2s ease;
i {
color: #FFF;
}
}
&:hover &-overlay {
opacity: 1;
}
}
.usersettings {
color: $fallback--lightText;
color: var(--lightText, $fallback--lightText);

View file

@ -22,21 +22,23 @@ const FriendList = withLoadMore({
additionalPropNames: ['userId']
})(List)
const defaultTabKey = 'statuses'
const UserProfile = {
data () {
return {
error: false,
userId: null
userId: null,
tab: defaultTabKey
}
},
created () {
// Make sure that timelines used in this page are empty
this.cleanUp()
const routeParams = this.$route.params
this.load(routeParams.name || routeParams.id)
this.tab = get(this.$route, 'query.tab', defaultTabKey)
},
destroyed () {
this.cleanUp()
this.stopFetching()
},
computed: {
timeline () {
@ -67,17 +69,36 @@ const UserProfile = {
},
methods: {
load (userNameOrId) {
const startFetchingTimeline = (timeline, userId) => {
// Clear timeline only if load another user's profile
if (userId !== this.$store.state.statuses.timelines[timeline].userId) {
this.$store.commit('clearTimeline', { timeline })
}
this.$store.dispatch('startFetchingTimeline', { timeline, userId })
}
const loadById = (userId) => {
this.userId = userId
startFetchingTimeline('user', userId)
startFetchingTimeline('media', userId)
if (this.isUs) {
startFetchingTimeline('favorites', userId)
}
// Fetch all pinned statuses immediately
this.$store.dispatch('fetchPinnedStatuses', userId)
}
// Reset view
this.userId = null
this.error = false
// Check if user data is already loaded in store
const user = this.$store.getters.findUser(userNameOrId)
if (user) {
this.userId = user.id
this.fetchTimelines()
loadById(user.id)
} else {
this.$store.dispatch('fetchUser', userNameOrId)
.then(({ id }) => {
this.userId = id
this.fetchTimelines()
})
.then(({ id }) => loadById(id))
.catch((reason) => {
const errorMessage = get(reason, 'error.error')
if (errorMessage === 'No user with such user_id') { // Known error
@ -90,40 +111,33 @@ const UserProfile = {
})
}
},
fetchTimelines () {
const userId = this.userId
this.$store.dispatch('startFetchingTimeline', { timeline: 'user', userId })
this.$store.dispatch('startFetchingTimeline', { timeline: 'media', userId })
if (this.isUs) {
this.$store.dispatch('startFetchingTimeline', { timeline: 'favorites', userId })
}
// Fetch all pinned statuses immediately
this.$store.dispatch('fetchPinnedStatuses', userId)
},
cleanUp () {
stopFetching () {
this.$store.dispatch('stopFetching', 'user')
this.$store.dispatch('stopFetching', 'favorites')
this.$store.dispatch('stopFetching', 'media')
this.$store.commit('clearTimeline', { timeline: 'user' })
this.$store.commit('clearTimeline', { timeline: 'favorites' })
this.$store.commit('clearTimeline', { timeline: 'media' })
},
switchUser (userNameOrId) {
this.stopFetching()
this.load(userNameOrId)
},
onTabSwitch (tab) {
this.tab = tab
this.$router.replace({ query: { tab } })
}
},
watch: {
'$route.params.id': function (newVal) {
if (newVal) {
this.cleanUp()
this.load(newVal)
this.switchUser(newVal)
}
},
'$route.params.name': function (newVal) {
if (newVal) {
this.cleanUp()
this.load(newVal)
this.switchUser(newVal)
}
},
$route () {
this.$refs.tabSwitcher.activateTab(0)()
'$route.query': function (newVal) {
this.tab = newVal.tab || defaultTabKey
}
},
components: {

View file

@ -8,36 +8,28 @@
:user="user"
:switcher="true"
:selected="timeline.viewing"
:allow-zooming-avatar="true"
rounded="top"
/>
<tab-switcher
ref="tabSwitcher"
:active-tab="tab"
:render-only-focused="true"
:on-switch="onTabSwitch"
>
<div :label="$t('user_card.statuses')">
<div class="timeline">
<template v-for="statusId in user.pinnedStatuseIds">
<Conversation
v-if="timeline.statusesObject[statusId]"
:key="statusId"
class="status-fadein"
:statusoid="timeline.statusesObject[statusId]"
:collapsable="true"
:show-pinned="true"
/>
</template>
</div>
<Timeline
key="statuses"
:label="$t('user_card.statuses')"
:count="user.statuses_count"
:embedded="true"
:title="$t('user_profile.timeline_title')"
:timeline="timeline"
:timeline-name="'user'"
timeline-name="user"
:user-id="userId"
:pinned-status-ids="user.pinnedStatusIds"
/>
</div>
<div
v-if="followsTabVisible"
key="followees"
:label="$t('user_card.followees')"
:disabled="!user.friends_count"
>
@ -52,6 +44,7 @@
</div>
<div
v-if="followersTabVisible"
key="followers"
:label="$t('user_card.followers')"
:disabled="!user.followers_count"
>
@ -68,6 +61,7 @@
</FollowerList>
</div>
<Timeline
key="media"
:label="$t('user_card.media')"
:disabled="!media.visibleStatuses.length"
:embedded="true"
@ -78,6 +72,7 @@
/>
<Timeline
v-if="isUs"
key="favorites"
:label="$t('user_card.favorites')"
:disabled="!favorites.visibleStatuses.length"
:embedded="true"

View file

@ -21,11 +21,12 @@ const WhoToFollow = {
name: i.display_name,
screen_name: i.acct,
profile_image_url: i.avatar || '/images/avi.png',
profile_image_url_original: i.avatar || '/images/avi.png'
profile_image_url_original: i.avatar || '/images/avi.png',
statusnet_profile_url: i.url
}
this.users.push(user)
this.$store.state.api.backendInteractor.externalProfile(user.screen_name)
this.$store.state.api.backendInteractor.fetchUser({ id: user.screen_name })
.then((externalUser) => {
if (!externalUser.error) {
this.$store.commit('addNewUsers', [externalUser])

View file

@ -13,7 +13,7 @@ function showWhoToFollow (panel, reply) {
toFollow.img = img
toFollow.name = name
panel.$store.state.api.backendInteractor.externalProfile(name)
panel.$store.state.api.backendInteractor.fetchUser({ id: name })
.then((externalUser) => {
if (!externalUser.error) {
panel.$store.commit('addNewUsers', [externalUser])

View file

@ -267,7 +267,7 @@
"loop_video": "Loop videos",
"loop_video_silent_only": "Loop only videos without sound (i.e. Mastodon's \"gifs\")",
"mutes_tab": "Mutes",
"play_videos_in_modal": "Play videos directly in the media viewer",
"play_videos_in_modal": "Play videos in a popup frame",
"use_contain_fit": "Don't crop the attachment in thumbnails",
"name": "Name",
"name_bio": "Name & Bio",
@ -513,7 +513,9 @@
"pinned": "Pinned",
"delete_confirm": "Do you really want to delete this status?",
"reply_to": "Reply to",
"replies_list": "Replies:"
"replies_list": "Replies:",
"mute_conversation": "Mute conversation",
"unmute_conversation": "Unmute conversation"
},
"user_card": {
"approve": "Approve",
@ -611,5 +613,16 @@
"person_talking": "{count} person talking",
"people_talking": "{count} people talking",
"no_results": "No results"
},
"password_reset": {
"forgot_password": "Forgot password?",
"password_reset": "Password reset",
"instruction": "Enter your email address or username. We will send you a link to reset your password.",
"placeholder": "Your email or username",
"check_email": "Check your email for a link to reset your password.",
"return_home": "Return to the home page",
"not_found": "We couldn't find that email or username.",
"too_many_requests": "You have reached the limit of attempts, try again later.",
"password_reset_disabled": "Password reset is disabled. Please contact your instance administrator."
}
}

View file

@ -9,9 +9,9 @@
"features_panel": {
"chat": "Chat",
"gopher": "Gopher",
"media_proxy": "Media proxy",
"media_proxy": "Proxy de medios",
"scope_options": "Opciones del alcance de la visibilidad",
"text_limit": "Límite de carácteres",
"text_limit": "Límite de caracteres",
"title": "Características",
"who_to_follow": "A quién seguir"
},
@ -47,19 +47,19 @@
"login": {
"login": "Identificación",
"description": "Identificación con OAuth",
"logout": "Salir",
"logout": "Cerrar sesión",
"password": "Contraseña",
"placeholder": "p.ej. lain",
"register": "Registrar",
"register": "Registrarse",
"username": "Usuario",
"hint": "Inicia sesión para unirte a la discusión",
"authentication_code": "Código de autentificación",
"authentication_code": "Código de autenticación",
"enter_recovery_code": "Inserta el código de recuperación",
"enter_two_factor_code": "Inserta el código de doble factor",
"enter_two_factor_code": "Inserta el código de dos factores",
"recovery_code": "Código de recuperación",
"heading" : {
"totp" : "Autentificación de doble factor",
"recovery" : "Recuperación de doble factor"
"totp" : "Autenticación de dos factores",
"recovery" : "Recuperación de dos factores"
}
},
"media_modal": {
@ -67,13 +67,13 @@
"next": "Siguiente"
},
"nav": {
"about": "Sobre",
"about": "Acerca de",
"back": "Volver",
"chat": "Chat Local",
"friend_requests": "Solicitudes de amistad",
"friend_requests": "Solicitudes de seguimiento",
"mentions": "Menciones",
"interactions": "Interacciones",
"dms": "Mensajes Directo",
"dms": "Mensajes Directos",
"public_tl": "Línea Temporal Pública",
"timeline": "Línea Temporal",
"twkn": "Toda La Red Conocida",
@ -89,7 +89,7 @@
"load_older": "Cargar notificaciones antiguas",
"notifications": "Notificaciones",
"read": "¡Leído!",
"repeated_you": "repite tu estado",
"repeated_you": "repit tu estado",
"no_more_notifications": "No hay más notificaciones"
},
"polls": {
@ -100,16 +100,19 @@
"vote": "Votar",
"type": "Tipo de encuesta",
"single_choice": "Elección única",
"multiple_choices": "Múltiples elecciones",
"multiple_choices": "Elección múltiple",
"expiry": "Tiempo de vida de la encuesta",
"expires_in": "La encuensta termina en {0}",
"expired": "La encuesta terminó hace {0}",
"not_enough_options": "Muy pocas opciones únicas en la encuesta"
},
"stickers": {
"add_sticker": "Añadir Pegatina"
},
"interactions": {
"favs_repeats": "Favoritos y Repetidos",
"follows": "Nuevos seguidores",
"load_older": "Cargar interacciones antiguas"
"load_older": "Cargar interacciones más antiguas"
},
"post_status": {
"new_status": "Publicar un nuevo estado",
@ -134,20 +137,20 @@
},
"scope": {
"direct": "Directo - Solo para los usuarios mencionados.",
"private": "Solo-Seguidores - Solo tus seguidores leeran la publicación",
"private": "Solo-seguidores - Solo tus seguidores leerán la publicación",
"public": "Público - Entradas visibles en las Líneas Temporales Públicas",
"unlisted": "Sin Listar - Entradas no visibles en las Líneas Temporales Públicas"
"unlisted": "Sin listar - Entradas no visibles en las Líneas Temporales Públicas"
}
},
"registration": {
"bio": "Biografía",
"email": "Correo electrónico",
"fullname": "Nombre a mostrar",
"password_confirm": "Confirmación de contraseña",
"password_confirm": "Confirmar contraseña",
"registration": "Registro",
"token": "Token de invitación",
"captcha": "CAPTCHA",
"new_captcha": "Click en la imagen para obtener un nuevo captca",
"new_captcha": "Haz click en la imagen para obtener un nuevo captcha",
"username_placeholder": "p.ej. lain",
"fullname_placeholder": "p.ej. Lain Iwakura",
"bio_placeholder": "e.g.\nHola, soy un ejemplo.\nAquí puedes poner algo representativo tuyo... o no.",
@ -161,7 +164,7 @@
}
},
"selectable_list": {
"select_all": "Seleccionarlo todo"
"select_all": "Seleccionar todo"
},
"settings": {
"app_name": "Nombre de la aplicación",
@ -172,8 +175,8 @@
"setup_otp" : "Configurar OTP",
"wait_pre_setup_otp" : "preconfiguración OTP",
"confirm_and_enable" : "Confirmar y habilitar OTP",
"title": "Autentificación de Doble Factor",
"generate_new_recovery_codes" : "Generar nuevos códigos de recuperación",
"title": "Autentificación de dos factores",
"generate_new_recovery_codes" : "Generar códigos de recuperación nuevos",
"warning_of_generate_new_codes" : "Cuando generas nuevos códigos de recuperación, los antiguos dejarán de funcionar.",
"recovery_codes" : "Códigos de recuperación.",
"waiting_a_recovery_codes": "Recibiendo códigos de respaldo",
@ -181,16 +184,16 @@
"authentication_methods" : "Métodos de autentificación",
"scan": {
"title": "Escanear",
"desc": "Usando su aplicación de doble factor, escanee este código QR o ingrese la clave de texto:",
"desc": "Usando su aplicación de dos factores, escanee este código QR o ingrese la clave de texto:",
"secret_code": "Clave"
},
"verify": {
"desc": "Para habilitar la autenticación de doble factor, ingrese el código de su aplicación 2FA:"
"desc": "Para habilitar la autenticación de dos factores, ingrese el código de su aplicación 2FA:"
}
},
"attachmentRadius": "Adjuntos",
"attachments": "Adjuntos",
"autoload": "Activar carga automática al llegar al final de la página",
"autoload": "Habilitar carga automática al llegar al final de la página",
"avatar": "Avatar",
"avatarAltRadius": "Avatares (Notificaciones)",
"avatarRadius": "Avatares",
@ -224,12 +227,12 @@
"delete_account_instructions": "Escribe tu contraseña para confirmar la eliminación de tu cuenta.",
"avatar_size_instruction": "El tamaño mínimo recomendado para el avatar es de 150X150 píxeles.",
"export_theme": "Exportar tema",
"filtering": "Filtros",
"filtering": "Filtrado",
"filtering_explanation": "Todos los estados que contengan estas palabras serán silenciados, una por línea",
"follow_export": "Exportar personas que tú sigues",
"follow_export_button": "Exporta tus seguidores a un archivo csv",
"follow_export_button": "Exporta tus seguidores a un fichero csv",
"follow_import": "Importar personas que tú sigues",
"follow_import_error": "Error al importal el archivo",
"follow_import_error": "Error al importar el fichero",
"follows_imported": "¡Importado! Procesarlos llevará tiempo.",
"foreground": "Primer plano",
"general": "General",
@ -259,7 +262,7 @@
"loop_video": "Vídeos en bucle",
"loop_video_silent_only": "Bucle solo en vídeos sin sonido (p.ej. \"gifs\" de Mastodon)",
"mutes_tab": "Silenciados",
"play_videos_in_modal": "Reproducir los vídeos directamente en el visor de medios",
"play_videos_in_modal": "Reproducir los vídeos en un marco emergente",
"use_contain_fit": "No recortar los adjuntos en miniaturas",
"name": "Nombre",
"name_bio": "Nombre y Biografía",
@ -274,8 +277,8 @@
"no_mutes": "No hay usuarios sinlenciados",
"hide_follows_description": "No mostrar a quién sigo",
"hide_followers_description": "No mostrar quién me sigue",
"show_admin_badge": "Mostrar la placa de administrador en mi perfil",
"show_moderator_badge": "Mostrar la placa de moderador en mi perfil",
"show_admin_badge": "Mostrar la insignia de Administrador en mi perfil",
"show_moderator_badge": "Mostrar la insignia de Moderador en mi perfil",
"nsfw_clickthrough": "Activar el clic para ocultar los adjuntos NSFW",
"oauth_tokens": "Tokens de OAuth",
"token": "Token",
@ -288,13 +291,13 @@
"profile_background": "Fondo del Perfil",
"profile_banner": "Cabecera del Perfil",
"profile_tab": "Perfil",
"radii_help": "Estable el redondeo de las esquinas del interfaz (en píxeles)",
"radii_help": "Estable el redondeo de las esquinas de la interfaz (en píxeles)",
"replies_in_timeline": "Réplicas en la línea temporal",
"reply_link_preview": "Activar la previsualización del enlace de responder al pasar el ratón por encim",
"reply_link_preview": "Activar la previsualización del enlace de responder al pasar el ratón por encima",
"reply_visibility_all": "Mostrar todas las réplicas",
"reply_visibility_following": "Solo mostrar réplicas para mí o usuarios a los que sigo",
"reply_visibility_self": "Solo mostrar réplicas para mí",
"autohide_floating_post_button": "Ocultar automáticamente el botón 'Nueva Publicación' (móvil)",
"autohide_floating_post_button": "Ocultar automáticamente el botón 'Nueva Publicación' (para móviles)",
"saving_err": "Error al guardar los ajustes",
"saving_ok": "Ajustes guardados",
"search_user_to_block": "Buscar usuarios a bloquear",
@ -303,25 +306,25 @@
"scope_copy": "Copiar la visibilidad de la publicación cuando contestamos (En los mensajes directos (MDs) siempre se copia)",
"minimal_scopes_mode": "Minimizar las opciones de publicación",
"set_new_avatar": "Cambiar avatar",
"set_new_profile_background": "Cambiar fondo del perfil",
"set_new_profile_banner": "Cambiar cabecera del perfil",
"set_new_profile_background": "Cambiar el fondo del perfil",
"set_new_profile_banner": "Cambiar la cabecera del perfil",
"settings": "Ajustes",
"subject_input_always_show": "Mostrar siempre el campo del tema",
"subject_line_behavior": "Copiar el tema en las contestaciones",
"subject_line_email": "Tipo email: \"re: tema\"",
"subject_line_mastodon": "Tipo mastodon: copiar como es",
"subject_line_behavior": "Copiar el tema en las respuestas",
"subject_line_email": "Como email: \"re: tema\"",
"subject_line_mastodon": "Como mastodon: copiar como es",
"subject_line_noop": "No copiar",
"post_status_content_type": "Formato de publicación",
"stop_gifs": "Iniciar GIFs al pasar el ratón",
"streaming": "Habilite la transmisión automática de nuevas publicaciones cuando se desplaza hacia la parte superior",
"streaming": "Habilitar la transmisión automática de nuevas publicaciones cuando se desplaza hacia la parte superior",
"text": "Texto",
"theme": "Tema",
"theme_help": "Use códigos de color hexadecimales (#rrggbb) para personalizar su tema de colores.",
"theme_help_v2_1": "También puede invalidar los colores y la opacidad de ciertos componentes si activa la casilla de verificación, use el botón \"Borrar todo\" para deshacer los cambios.",
"theme_help_v2_2": "Los iconos debajo de algunas entradas son indicadores de contraste de fondo/texto, desplace el ratón para obtener información detallada. Tenga en cuenta que cuando se utilizan indicadores de contraste de transparencia se muestra el peor caso posible.",
"theme_help_v2_1": "También puede invalidar los colores y la opacidad de ciertos componentes si activa la casilla de verificación. Use el botón \"Borrar todo\" para deshacer los cambios.",
"theme_help_v2_2": "Los iconos debajo de algunas entradas son indicadores de contraste de fondo/texto, desplace el ratón por encima para obtener información más detallada. Tenga en cuenta que cuando se utilizan indicadores de contraste de transparencia se muestra el peor caso posible.",
"tooltipRadius": "Información/alertas",
"upload_a_photo": "Subir una foto",
"user_settings": "Ajustes de Usuario",
"user_settings": "Ajustes del Usuario",
"values": {
"false": "no",
"true": "sí"
@ -392,14 +395,14 @@
"shadow_id": "Sombra #{value}",
"blur": "Difuminar",
"spread": "Cantidad",
"inset": "Insertada",
"inset": "Sombra interior",
"hint": "Para las sombras, también puede usar --variable como un valor de color para usar las variables CSS3. Tenga en cuenta que establecer la opacidad no funcionará en este caso.",
"filter_hint": {
"always_drop_shadow": "Advertencia, esta sombra siempre usa {0} cuando el navegador lo soporta.",
"drop_shadow_syntax": "{0} no soporta el parámetro {1} y la palabra clave {2}.",
"avatar_inset": "Tenga en cuenta que la combinación de sombras insertadas como no-insertadas en los avatares, puede dar resultados inesperados con los avatares transparentes.",
"avatar_inset": "Tenga en cuenta que la combinación de sombras interiores como no-interiores en los avatares, puede dar resultados inesperados con los avatares transparentes.",
"spread_zero": "Sombras con una cantidad > 0 aparecerá como si estuviera puesto a cero",
"inset_classic": "Las sombras insertadas estarán usando {0}"
"inset_classic": "Las sombras interiores estarán usando {0}"
},
"components": {
"panel": "Panel",
@ -417,7 +420,7 @@
},
"fonts": {
"_tab_label": "Fuentes",
"help": "Seleccione la fuente para utilizar para los elementos de la interfaz de usuario. Para \"personalizado\", debe ingresar el nombre exacto de la fuente tal como aparece en el sistema.",
"help": "Seleccione la fuente a utilizar para los elementos de la interfaz de usuario. Para \"personalizar\", debe ingresar el nombre exacto de la fuente tal como aparece en el sistema.",
"components": {
"interface": "Interfaz",
"input": "Campos de entrada",
@ -476,7 +479,7 @@
"second_short": "{0}s",
"seconds_short": "{0}s",
"week": "{0} semana",
"weeks": "{0} semana",
"weeks": "{0} semanas",
"week_short": "{0}sem",
"weeks_short": "{0}sem",
"year": "{0} año",
@ -504,11 +507,11 @@
"unpin": "Desclavar de tu perfil",
"pinned": "Fijado",
"delete_confirm": "¿Realmente quieres borrar la publicación?",
"reply_to": "Responder a",
"reply_to": "Respondiendo a",
"replies_list": "Respuestas:"
},
"user_card": {
"approve": "Aprovar",
"approve": "Aprobar",
"block": "Bloquear",
"blocked": "¡Bloqueado!",
"deny": "Denegar",
@ -535,8 +538,8 @@
"unblock": "Desbloquear",
"unblock_progress": "Desbloqueando...",
"block_progress": "Bloqueando...",
"unmute": "Desenmudecer",
"unmute_progress": "Sesenmudeciendo...",
"unmute": "Quitar silencio",
"unmute_progress": "Quitando silencio...",
"mute_progress": "Silenciando...",
"admin_menu": {
"moderation": "Moderación",
@ -546,7 +549,7 @@
"revoke_moderator": "Revocar permisos de Moderador",
"activate_account": "Activar cuenta",
"deactivate_account": "Desactivar cuenta",
"delete_account": "Borrar cuenta",
"delete_account": "Eliminar cuenta",
"force_nsfw": "Marcar todas las publicaciones como NSFW (no es seguro/apropiado para el trabajo)",
"strip_media": "Eliminar archivos multimedia de las publicaciones",
"force_unlisted": "Forzar que se publique en el modo -Sin Listar-",
@ -554,12 +557,12 @@
"disable_remote_subscription": "No permitir que usuarios de instancias remotas te siga.",
"disable_any_subscription": "No permitir que ningún usuario te siga",
"quarantine": "No permitir publicaciones de usuarios de instancias remotas",
"delete_user": "Borrar usuario",
"delete_user": "Eliminar usuario",
"delete_user_confirmation": "¿Estás completamente seguro? Esta acción no se puede deshacer."
}
},
"user_profile": {
"timeline_title": "Linea temporal del usuario",
"timeline_title": "Linea Temporal del Usuario",
"profile_does_not_exist": "Lo sentimos, este perfil no existe.",
"profile_loading_error": "Lo sentimos, hubo un error al cargar este perfil."
},
@ -599,7 +602,7 @@
},
"search": {
"people": "Personas",
"hashtags": "Hashtags",
"hashtags": "Etiquetas",
"person_talking": "{count} personas hablando",
"people_talking": "{count} gente hablando",
"no_results": "Sin resultados"

610
src/i18n/eu.json Normal file
View file

@ -0,0 +1,610 @@
{
"chat": {
"title": "Txata"
},
"exporter": {
"export": "Esportatu",
"processing": "Prozesatzen, zure fitxategia deskargatzeko eskatuko zaizu laster"
},
"features_panel": {
"chat": "Txata",
"gopher": "Ghoper",
"media_proxy": "Media proxy",
"scope_options": "Ikusgaitasun aukerak",
"text_limit": "Testu limitea",
"title": "Ezaugarriak",
"who_to_follow": "Nori jarraitu"
},
"finder": {
"error_fetching_user": "Errorea erabiltzailea eskuratzen",
"find_user": "Bilatu erabiltzailea"
},
"general": {
"apply": "Aplikatu",
"submit": "Bidali",
"more": "Gehiago",
"generic_error": "Errore bat gertatu da",
"optional": "Hautazkoa",
"show_more": "Gehiago erakutsi",
"show_less": "Gutxiago erakutsi",
"cancel": "Ezeztatu",
"disable": "Ezgaitu",
"enable": "Gaitu",
"confirm": "Baieztatu",
"verify": "Egiaztatu"
},
"image_cropper": {
"crop_picture": "Moztu argazkia",
"save": "Gorde",
"save_without_cropping": "Gorde moztu gabe",
"cancel": "Ezeztatu"
},
"importer": {
"submit": "Bidali",
"success": "Ondo inportatu da.",
"error": "Errore bat gertatu da fitxategi hau inportatzerakoan."
},
"login": {
"login": "Saioa hasi",
"description": "OAuth-ekin saioa hasi",
"logout": "Saioa itxi",
"password": "Pasahitza",
"placeholder": "adibidez Lain",
"register": "Erregistratu",
"username": "Erabiltzaile-izena",
"hint": "Hasi saioa eztabaidan parte-hartzeko",
"authentication_code": "Autentifikazio kodea",
"enter_recovery_code": "Sartu berreskuratze kodea",
"enter_two_factor_code": "Sartu bi-faktore kodea",
"recovery_code": "Berreskuratze kodea",
"heading": {
"totp": "Bi-faktore autentifikazioa",
"recovery": "Bi-faktore berreskuratzea"
}
},
"media_modal": {
"previous": "Aurrekoa",
"next": "Hurrengoa"
},
"nav": {
"about": "Honi buruz",
"back": "Atzera",
"chat": "Txat lokala",
"friend_requests": "Jarraitzeko eskaerak",
"mentions": "Aipamenak",
"interactions": "Interakzioak",
"dms": "Zuzeneko Mezuak",
"public_tl": "Denbora-lerro Publikoa",
"timeline": "Denbora-lerroa",
"twkn": "Ezagutzen den Sarea",
"user_search": "Erabiltzailea Bilatu",
"search": "Bilatu",
"who_to_follow": "Nori jarraitu",
"preferences": "Hobespenak"
},
"notifications": {
"broken_favorite": "Egoera ezezaguna, bilatzen...",
"favorited_you": "zure mezua gogoko du",
"followed_you": "Zu jarraitzen zaitu",
"load_older": "Kargatu jakinarazpen zaharragoak",
"notifications": "Jakinarazpenak",
"read": "Irakurri!",
"repeated_you": "zure mezua errepikatu du",
"no_more_notifications": "Ez dago jakinarazpen gehiago"
},
"polls": {
"add_poll": "Inkesta gehitu",
"add_option": "Gehitu aukera",
"option": "Aukera",
"votes": "Bozkak",
"vote": "Bozka",
"type": "Inkesta mota",
"single_choice": "Aukera bakarra",
"multiple_choices": "Aukera anizkoitza",
"expiry": "Inkestaren iraupena",
"expires_in": "Inkesta {0} bukatzen da",
"expired": "Inkesta {0} bukatu zen",
"not_enough_options": "Aukera gutxiegi inkestan"
},
"stickers": {
"add_sticker": "Pegatina gehitu"
},
"interactions": {
"favs_repeats": "Errepikapen eta gogokoak",
"follows": "Jarraitzaile berriak",
"load_older": "Kargatu elkarrekintza zaharragoak"
},
"post_status": {
"new_status": "Mezu berri bat idatzi",
"account_not_locked_warning": "Zure kontua ez dago {0}. Edozeinek jarraitzen hastearekin, zure mezuak irakur dezake.",
"account_not_locked_warning_link": "Blokeatuta",
"attachments_sensitive": "Nabarmendu eranskinak hunkigarri gisa ",
"content_type": {
"text/plain": "Testu arrunta",
"text/html": "HTML",
"text/markdown": "Markdown",
"text/bbcode": "BBCode"
},
"content_warning": "Gaia (hautazkoa)",
"default": "Iadanik Los Angeles-en",
"direct_warning_to_all": "Mezu hau aipatutako erabiltzaile guztientzat ikusgai egongo da.",
"direct_warning_to_first_only": "Mezu hau ikusgai egongo da bakarrik hasieran aipatzen diren erabiltzaileei.",
"posting": "Argitaratzen",
"scope_notice": {
"public": "Mezu hau guztiontzat ikusgai izango da",
"private": "Mezu hau zure jarraitzaileek soilik ikusiko dute",
"unlisted": "Mezu hau ez da argitaratuko Denbora-lerro Publikoan ezta Ezagutzen den Sarean"
},
"scope": {
"direct": "Zuzena - Bidali aipatutako erabiltzaileei besterik ez",
"private": "Jarraitzaileentzako bakarrik- Bidali jarraitzaileentzat bakarrik",
"public": "Publickoa - Bistaratu denbora-lerro publikoetan",
"unlisted": "Zerrendatu gabea - ez bidali denbora-lerro publikoetan"
}
},
"registration": {
"bio": "Biografia",
"email": "E-posta",
"fullname": "Erakutsi izena",
"password_confirm": "Pasahitza berretsi",
"registration": "Izena ematea",
"token": "Gonbidapen txartela",
"captcha": "CAPTCHA",
"new_captcha": "Klikatu irudia captcha berri bat lortzeko",
"username_placeholder": "Adibidez lain",
"fullname_placeholder": "Adibidez Lain Iwakura",
"bio_placeholder": "Adidibez.\nKaixo, Lain naiz.\nFedibertsoa gustokoa dut eta euskeraz hitzegiten dut.",
"validations": {
"username_required": "Ezin da hutsik utzi",
"fullname_required": "Ezin da hutsik utzi",
"email_required": "Ezin da hutsik utzi",
"password_required": "Ezin da hutsik utzi",
"password_confirmation_required": "Ezin da hutsik utzi",
"password_confirmation_match": "Pasahitzaren berdina izan behar du"
}
},
"selectable_list": {
"select_all": "Hautatu denak"
},
"settings": {
"app_name": "App izena",
"security": "Segurtasuna",
"enter_current_password_to_confirm": "Sar ezazu zure egungo pasahitza zure identitatea baieztatzeko",
"mfa": {
"otp": "OTP",
"setup_otp": "OTP konfiguratu",
"wait_pre_setup_otp": "OTP aurredoitzen",
"confirm_and_enable": "Baieztatu eta gaitu OTP",
"title": "Bi-faktore autentifikazioa",
"generate_new_recovery_codes": "Sortu berreskuratze kode berriak",
"warning_of_generate_new_codes": "Berreskuratze kode berriak sortzean, zure berreskuratze kode zaharrak ez dute balioko",
"recovery_codes": "Berreskuratze kodea",
"waiting_a_recovery_codes": "Babes-kopia kodeak jasotzen...",
"recovery_codes_warning": "Idatzi edo gorde kodeak leku seguruan - bestela ez dituzu berriro ikusiko. Zure 2FA aplikaziorako sarbidea eta berreskuratze kodeak galduz gero, zure kontutik blokeatuta egongo zara.",
"authentication_methods": "Autentifikazio metodoa",
"scan": {
"title": "Eskaneatu",
"desc": "Zure bi-faktore aplikazioa erabiliz, eskaneatu QR kode hau edo idatzi testu-gakoa:",
"secret_code": "Giltza"
},
"verify": {
"desc": "Bi-faktore autentifikazioa gaitzeko, sar ezazu bi-faktore kodea zure app-tik"
}
},
"attachmentRadius": "Eranskinak",
"attachments": "Eranskinak",
"autoload": "Gaitu karga automatikoa beheraino mugitzean",
"avatar": "Avatarra",
"avatarAltRadius": "Avatarra (Aipamenak)",
"avatarRadius": "Avatarrak",
"background": "Atzeko planoa",
"bio": "Biografia",
"block_export": "Bloke esportatzea",
"block_export_button": "Esportatu zure blokeak csv fitxategi batera",
"block_import": "Bloke inportazioa",
"block_import_error": "Errorea blokeak inportatzen",
"blocks_imported": "Blokeak inportaturik! Hauek prozesatzeak denbora hartuko du.",
"blocks_tab": "Blokeak",
"btnRadius": "Botoiak",
"cBlue": "Urdina (erantzun, jarraitu)",
"cGreen": "Berdea (Bertxiotu)",
"cOrange": "Laranja (Gogokoa)",
"cRed": "Gorria (ezeztatu)",
"change_password": "Pasahitza aldatu",
"change_password_error": "Arazao bat egon da zure pasahitza aldatzean",
"changed_password": "Pasahitza ondo aldatu da!",
"collapse_subject": "Bildu gaia daukaten mezuak",
"composing": "Idazten",
"confirm_new_password": "Baieztatu pasahitz berria",
"current_avatar": "Zure uneko avatarra",
"current_password": "Indarrean den pasahitza",
"current_profile_banner": "Zure profilaren banner-a",
"data_import_export_tab": "Datuak Inportatu / Esportatu",
"default_vis": "Lehenetsitako ikusgaitasunak",
"delete_account": "Ezabatu kontua",
"delete_account_description": "Betirako ezabatu zure kontua eta zure mezu guztiak",
"delete_account_error": "Arazo bat gertatu da zure kontua ezabatzerakoan. Arazoa jarraitu eskero, administratzailearekin harremanetan jarri.",
"delete_account_instructions": "Idatzi zure pasahitza kontua ezabatzeko.",
"avatar_size_instruction": "Avatar irudien gomendatutako gutxieneko tamaina 150x150 pixel dira.",
"export_theme": "Gorde aurre-ezarpena",
"filtering": "Iragazten",
"filtering_explanation": "Hitz hauek dituzten muzu guztiak isilduak izango dira. Lerro bakoitzeko bat",
"follow_export": "Jarraitzen dituzunak esportatu",
"follow_export_button": "Esportatu zure jarraitzaileak csv fitxategi batean",
"follow_import": "Jarraitzen dituzunak inportatu",
"follow_import_error": "Errorea jarraitzaileak inportatzerakoan",
"follows_imported": "Jarraitzaileak inportatuta! Prozesatzeak denbora pixka bat iraungo du.",
"foreground": "Aurreko planoa",
"general": "Orokorra",
"hide_attachments_in_convo": "Ezkutatu eranskinak elkarrizketatan ",
"hide_attachments_in_tl": "Ezkutatu eranskinak donbora-lerroan",
"hide_muted_posts": "Ezkutatu mutututako erabiltzaileen mezuak",
"max_thumbnails": "Mezu bakoitzeko argazki-miniatura kopuru maximoa",
"hide_isp": "Instantziari buruzko panela ezkutatu",
"preload_images": "Argazkiak aurrekargatu",
"use_one_click_nsfw": "Ireki eduki hunkigarria duten eranskinak klik batekin",
"hide_post_stats": "Ezkutatu mezuaren estatistikak (adibidez faborito kopurua)",
"hide_user_stats": "Ezkutatu erabiltzaile estatistikak (adibidez jarraitzaile kopurua)",
"hide_filtered_statuses": "Ezkutatu iragazitako mezuak",
"import_blocks_from_a_csv_file": "Blokeatutakoak inportatu CSV fitxategi batetik",
"import_followers_from_a_csv_file": "Inportatu jarraitzaileak csv fitxategi batetik",
"import_theme": "Kargatu aurre-ezarpena",
"inputRadius": "Sarrera eremuak",
"checkboxRadius": "Kuadrotxoak",
"instance_default": "(lehenetsia: {value})",
"instance_default_simple": "(lehenetsia)",
"interface": "Interfazea",
"interfaceLanguage": "Interfaze hizkuntza",
"invalid_theme_imported": "Hautatutako fitxategia ez da onartutako Pleroma gaia. Ez da zure gaian aldaketarik burutu.",
"limited_availability": "Ez dago erabilgarri zure nabigatzailean",
"links": "Estekak",
"lock_account_description": "Mugatu zure kontua soilik onartutako jarraitzaileei",
"loop_video": "Begizta bideoak",
"loop_video_silent_only": "Soinu gabeko bideoak begiztatu bakarrik (adibidez Mastodon-eko gif-ak)",
"mutes_tab": "Mututuak",
"play_videos_in_modal": "Erreproduzitu bideoak zuzenean multimedia erreproduzigailuan",
"use_contain_fit": "Eranskinak ez moztu miniaturetan",
"name": "Izena",
"name_bio": "Izena eta biografia",
"new_password": "Pasahitz berria",
"notification_visibility": "Erakusteko jakinarazpen motak",
"notification_visibility_follows": "Jarraitzaileak",
"notification_visibility_likes": "Gogokoak",
"notification_visibility_mentions": "Aipamenak",
"notification_visibility_repeats": "Errepikapenak",
"no_rich_text_description": "Kendu testu-formatu aberastuak mezu guztietatik",
"no_blocks": "Ez daude erabiltzaile blokeatutak",
"no_mutes": "Ez daude erabiltzaile mututuak",
"hide_follows_description": "Ez erakutsi nor jarraitzen ari naizen",
"hide_followers_description": "Ez erakutsi nor ari de ni jarraitzen",
"show_admin_badge": "Erakutsi Administratzaile etiketa nire profilan",
"show_moderator_badge": "Erakutsi Moderatzaile etiketa nire profilan",
"nsfw_clickthrough": "Gaitu klika hunkigarri eranskinak ezkutatzeko",
"oauth_tokens": "OAuth tokenak",
"token": "Tokena",
"refresh_token": "Berrgin Tokena",
"valid_until": "Baliozkoa Arte",
"revoke_token": "Ezeztatu",
"panelRadius": "Panelak",
"pause_on_unfocused": "Eguneraketa automatikoa gelditu fitxatik kanpo",
"presets": "Aurrezarpenak",
"profile_background": "Profilaren atzeko planoa",
"profile_banner": "Profilaren Banner-a",
"profile_tab": "Profila",
"radii_help": "Konfiguratu interfazearen ertzen biribiltzea (pixeletan)",
"replies_in_timeline": "Denbora-lerroko erantzunak",
"reply_link_preview": "Gaitu erantzun-estekaren aurrebista arratoiarekin",
"reply_visibility_all": "Erakutsi erantzun guztiak",
"reply_visibility_following": "Erakutsi bakarrik niri zuzendutako edo nik jarraitutako erabiltzaileen erantzunak",
"reply_visibility_self": "Erakutsi bakarrik niri zuzendutako erantzunak",
"autohide_floating_post_button": "Automatikoki ezkutatu Mezu Berriaren botoia (sakelako)",
"saving_err": "Errorea ezarpenak gordetzean",
"saving_ok": "Ezarpenak gordeta",
"search_user_to_block": "Bilatu zein blokeatu nahi duzun",
"search_user_to_mute": "Bilatu zein isilarazi nahi duzun",
"security_tab": "Segurtasuna",
"scope_copy": "Ikusgaitasun aukerak kopiatu mezua erantzuterakoan (Zuzeneko Mezuak beti kopiatzen dute)",
"minimal_scopes_mode": "Bildu ikusgaitasun aukerak",
"set_new_avatar": "Ezarri avatar berria",
"set_new_profile_background": "Ezarri atzeko plano berria",
"set_new_profile_banner": "Ezarri profil banner berria",
"settings": "Ezarpenak",
"subject_input_always_show": "Erakutsi beti gaiaren eremua",
"subject_line_behavior": "Gaia kopiatu erantzuterakoan",
"subject_line_email": "E-maila bezala: \"re: gaia\"",
"subject_line_mastodon": "Mastodon bezala: kopiatu den bezala",
"subject_line_noop": "Ez kopiatu",
"post_status_content_type": "Argitarapen formatua",
"stop_gifs": "GIF-a iniziatu arratoia gainean jarrita",
"streaming": "Gaitu mezu berrien karga goraino mugitzean",
"text": "Testua",
"theme": "Gaia",
"theme_help": "Erabili hex-kolore kodeak (#rrggbb) gaiaren koloreak pertsonalizatzeko.",
"theme_help_v2_1": "Zenbait osagaien koloreak eta opakutasuna ezeztatu ditzakezu kontrol-laukia aktibatuz, \"Garbitu dena\" botoia erabili aldaketak deusezteko.",
"theme_help_v2_2": "Sarreren batzuen azpian dauden ikonoak atzeko planoaren eta testuaren arteko kontrastearen adierazleak dira, kokatu arratoia gainean informazio zehatza eskuratzeko. Kontuan izan gardentasun kontrasteen adierazleek erabiltzen direnean, kasurik okerrena erakusten dutela.",
"tooltipRadius": "Argibideak/alertak",
"upload_a_photo": "Argazkia kargatu",
"user_settings": "Erabiltzaile Ezarpenak",
"values": {
"false": "ez",
"true": "bai"
},
"notifications": "Jakinarazpenak",
"notification_setting": "Jaso pertsona honen jakinarazpenak:",
"notification_setting_follows": "Jarraitutako erabiltzaileak",
"notification_setting_non_follows": "Jarraitzen ez dituzun erabiltzaileak",
"notification_setting_followers": "Zu jarraitzen zaituzten erabiltzaileak",
"notification_setting_non_followers": "Zu jarraitzen ez zaituzten erabiltzaileak",
"notification_mutes": "Erabiltzaile jakin baten jakinarazpenak jasotzeari uzteko, isilarazi ezazu.",
"notification_blocks": "Erabiltzaile bat blokeatzeak jakinarazpen guztiak gelditzen ditu eta harpidetza ezeztatu.",
"enable_web_push_notifications": "Gaitu web jakinarazpenak",
"style": {
"switcher": {
"keep_color": "Mantendu koloreak",
"keep_shadows": "Mantendu itzalak",
"keep_opacity": "Mantendu opakotasuna",
"keep_roundness": "Mantendu biribiltasuna",
"keep_fonts": "Mantendu iturriak",
"save_load_hint": "\"Mantendu\" aukerak uneko konfiguratutako aukerak gordetzen ditu gaiak hautatzerakoan edo kargatzean, gai hauek esportatze garaian ere gordetzen ditu. Kontrol-lauki guztiak garbitzen direnean, esportazio-gaiak dena gordeko du.",
"reset": "Berrezarri",
"clear_all": "Garbitu dena",
"clear_opacity": "Garbitu opakotasuna"
},
"common": {
"color": "Kolorea",
"opacity": "Opakotasuna",
"contrast": {
"hint": "Kontrastearen erlazioa {ratio} da, {level} {context}",
"level": {
"aa": "AA Mailako gidaliburua betetzen du (gutxienezkoa)",
"aaa": "AAA Mailako gidaliburua betetzen du (gomendatua)",
"bad": "ez ditu irisgarritasun arauak betetzen"
},
"context": {
"18pt": "testu handientzat (+18pt)",
"text": "testuentzat"
}
}
},
"common_colors": {
"_tab_label": "Ohikoa",
"main": "Ohiko koloreak",
"foreground_hint": "Ikusi \"Aurreratua\" fitxa kontrol zehatzagoa lortzeko",
"rgbo": "Ikono, azentu eta etiketak"
},
"advanced_colors": {
"_tab_label": "Aurreratua",
"alert": "Alerten atzeko planoa",
"alert_error": "Errorea",
"badge": "Etiketen atzeko planoa",
"badge_notification": "Jakinarazpenak",
"panel_header": "Panelaren goiburua",
"top_bar": "Goiko barra",
"borders": "Ertzak",
"buttons": "Botoiak",
"inputs": "Sarrera eremuak",
"faint_text": "Testu itzalita"
},
"radii": {
"_tab_label": "Biribiltasuna"
},
"shadows": {
"_tab_label": "Itzal eta argiak",
"component": "Atala",
"override": "Berridatzi",
"shadow_id": "Itzala #{value}",
"blur": "Lausotu",
"spread": "Hedapena",
"inset": "Barrutik",
"hint": "Itzaletarako ere erabil dezakezu --aldagarri kolore balio gisa CSS3 aldagaiak erabiltzeko. Kontuan izan opakutasuna ezartzeak ez duela kasu honetan funtzionatuko.",
"filter_hint": {
"always_drop_shadow": "Kontuz, itzal honek beti erabiltzen du {0} nabigatzaileak onartzen duenean.",
"drop_shadow_syntax": "{0} ez du onartzen {1} parametroa eta {2} gako-hitza.",
"avatar_inset": "Kontuan izan behar da barruko eta kanpoko itzal konbinazioak, ez esparotako emaitzak ager daitezkeela atzeko plano gardena duten Avatarretan.",
"spread_zero": "Hedapena > 0 duten itzalak zero izango balitz bezala agertuko dira",
"inset_classic": "Barruko itzalak {0} erabiliko dute"
},
"components": {
"panel": "Panela",
"panelHeader": "Panel goiburua",
"topBar": "Goiko barra",
"avatar": "Erabiltzailearen avatarra (profilan)",
"avatarStatus": "Erabiltzailearen avatarra (mezuetan)",
"popup": "Popup-ak eta argibideak",
"button": "Botoia",
"buttonHover": "Botoia (gainean)",
"buttonPressed": "Botoai (sakatuta)",
"buttonPressedHover": "Botoia (sakatuta+gainean)",
"input": "Sarrera eremuak"
}
},
"fonts": {
"_tab_label": "Letra-tipoak",
"help": "Aukeratu letra-tipoak erabiltzailearen interfazean erabiltzeko. \"Pertsonalizatua\" letra-tipoan, sisteman agertzen den izen berdinarekin idatzi behar duzu.",
"components": {
"interface": "Interfazea",
"input": "Sarrera eremuak",
"post": "Mezuen testua",
"postCode": "Tarte-bakarreko testua mezuetan (testu-formatu aberastuak)"
},
"family": "Letra-tipoaren izena",
"size": "Tamaina (px)",
"weight": "Pisua (lodiera)",
"custom": "Pertsonalizatua"
},
"preview": {
"header": "Aurrebista",
"content": "Edukia",
"error": "Adibide errorea",
"button": "Botoia",
"text": "Hamaika {0} eta {1}",
"mono": "edukia",
"input": "Jadanik Los Angeles-en",
"faint_link": "laguntza",
"fine_print": "Irakurri gure {0} ezer erabilgarria ikasteko!",
"header_faint": "Ondo dago",
"checkbox": "Baldintzak berrikusi ditut",
"link": "esteka polita"
}
},
"version": {
"title": "Bertsioa",
"backend_version": "Backend Bertsio",
"frontend_version": "Frontend Bertsioa"
}
},
"time": {
"day": "{0} egun",
"days": "{0} egun",
"day_short": "{0}d",
"days_short": "{0}d",
"hour": "{0} ordu",
"hours": "{0} ordu",
"hour_short": "{0}o",
"hours_short": "{0}o",
"in_future": "{0} barru",
"in_past": "duela {0}",
"minute": "{0} minutu",
"minutes": "{0} minutu",
"minute_short": "{0}min",
"minutes_short": "{0}min",
"month": "{0} hilabete",
"months": "{0} hilabete",
"month_short": "{0}h",
"months_short": "{0}h",
"now": "oraintxe bertan",
"now_short": "orain",
"second": "{0} segundu",
"seconds": "{0} segundu",
"second_short": "{0}s",
"seconds_short": "{0}s",
"week": "{0} aste",
"weeks": "{0} aste",
"week_short": "{0}a",
"weeks_short": "{0}a",
"year": "{0} urte",
"years": "{0} urte",
"year_short": "{0}u",
"years_short": "{0}u"
},
"timeline": {
"collapse": "Bildu",
"conversation": "Elkarrizketa",
"error_fetching": "Errorea eguneraketak eskuratzen",
"load_older": "Kargatu mezu zaharragoak",
"no_retweet_hint": "Mezu hau jarraitzailentzko bakarrik markatuta dago eta ezin da errepikatu",
"repeated": "Errepikatuta",
"show_new": "Berriena erakutsi",
"up_to_date": "Eguneratuta",
"no_more_statuses": "Ez daude mezu gehiago",
"no_statuses": "Mezurik gabe"
},
"status": {
"favorites": "Gogokoak",
"repeats": "Errepikapenak",
"delete": "Mezua ezabatu",
"pin": "Profilan ainguratu",
"unpin": "Aingura ezeztatu profilatik",
"pinned": "Ainguratuta",
"delete_confirm": "Mezu hau benetan ezabatu nahi duzu?",
"reply_to": "Erantzun",
"replies_list": "Erantzunak:"
},
"user_card": {
"approve": "Onartu",
"block": "Blokeatu",
"blocked": "Blokeatuta!",
"deny": "Ukatu",
"favorites": "Gogokoak",
"follow": "Jarraitu",
"follow_sent": "Eskaera bidalita!",
"follow_progress": "Eskatzen...",
"follow_again": "Eskaera berriro bidali?",
"follow_unfollow": "Jarraitzeari utzi",
"followees": "Jarraitzen",
"followers": "Jarraitzaileak",
"following": "Jarraitzen!",
"follows_you": "Jarraitzen dizu!",
"its_you": "Zu zara!",
"media": "Multimedia",
"mute": "Isilarazi",
"muted": "Isilduta",
"per_day": "eguneko",
"remote_follow": "Jarraitu",
"report": "Berri eman",
"statuses": "Mezuak",
"subscribe": "Harpidetu",
"unsubscribe": "Harpidetza ezeztatu",
"unblock": "Blokeoa kendu",
"unblock_progress": "Blokeoa ezeztatzen...",
"block_progress": "Blokeatzen...",
"unmute": "Isiltasuna kendu",
"unmute_progress": "Isiltasuna kentzen...",
"mute_progress": "Isiltzen...",
"admin_menu": {
"moderation": "Moderazioa",
"grant_admin": "Administratzaile baimena",
"revoke_admin": "Ezeztatu administratzaile baimena",
"grant_moderator": "Moderatzaile baimena",
"revoke_moderator": "Ezeztatu moderatzaile baimena",
"activate_account": "Aktibatu kontua",
"deactivate_account": "Desaktibatu kontua",
"delete_account": "Ezabatu kontua",
"force_nsfw": "Markatu mezu guztiak hunkigarri gisa",
"strip_media": "Kendu multimedia mezuetatik",
"force_unlisted": "Behartu mezuak listatu gabekoak izatea",
"sandbox": "Behartu zure jarraitzaileentzako bakarrik argitaratzera",
"disable_remote_subscription": "Ez utzi istantzia kanpoko erabiltzaileak zuri jarraitzea",
"disable_any_subscription": "Ez utzi beste erabiltzaileak zuri jarraitzea",
"quarantine": "Ez onartu mezuak beste instantzietatik",
"delete_user": "Erabiltzailea ezabatu",
"delete_user_confirmation": "Erabat ziur zaude? Ekintza hau ezin da desegin."
}
},
"user_profile": {
"timeline_title": "Erabiltzailearen denbora-lerroa",
"profile_does_not_exist": "Barkatu, profil hau ez da existitzen.",
"profile_loading_error": "Barkatu, errore bat gertatu da profila kargatzean."
},
"user_reporting": {
"title": "{0}-ri buruz berri ematen",
"add_comment_description": "Zure kexa moderatzaileei bidaliko da. Nahi baduzu zure kexaren zergatia idatz dezakezu:",
"additional_comments": "Iruzkin gehiago",
"forward_description": "Kontu hau beste instantzia batekoa da. Nahi duzu txostenaren kopia bat bidali ere?",
"forward_to": "{0}-ri birbidali",
"submit": "Bidali",
"generic_error": "Errore bat gertatu da zure eskaera prozesatzerakoan."
},
"who_to_follow": {
"more": "Gehiago",
"who_to_follow": "Nori jarraitu"
},
"tool_tip": {
"media_upload": "Multimedia igo",
"repeat": "Erreplikatu",
"reply": "Erantzun",
"favorite": "Gogokoa",
"user_settings": "Erabiltzaile ezarpenak"
},
"upload": {
"error": {
"base": "Igoerak huts egin du.",
"file_too_big": "Artxiboa haundiegia [{filesize}{filesizeunit} / {allowedsize}{allowedsizeunit}]",
"default": "Saiatu berriro geroago"
},
"file_size_units": {
"B": "B",
"KiB": "KiB",
"MiB": "MiB",
"GiB": "GiB",
"TiB": "TiB"
}
},
"search": {
"people": "Gendea",
"hashtags": "Traolak",
"person_talking": "{count} pertsona hitzegiten",
"people_talking": "{count} gende hitzegiten",
"no_results": "Emaitzarik ez"
}
}

View file

@ -278,8 +278,15 @@
"status": {
"favorites": "Tykkäykset",
"repeats": "Toistot",
"delete": "Poista",
"pin": "Kiinnitä profiiliisi",
"unpin": "Poista kiinnitys",
"pinned": "Kiinnitetty",
"delete_confirm": "Haluatko varmasti postaa viestin?",
"reply_to": "Vastaus",
"replies_list": "Vastaukset:"
"replies_list": "Vastaukset:",
"mute_conversation": "Hiljennä keskustelu",
"unmute_conversation": "Poista hiljennys"
},
"user_card": {
"approve": "Hyväksy",

View file

@ -78,6 +78,7 @@
"timeline": "タイムライン",
"twkn": "つながっているすべてのネットワーク",
"user_search": "ユーザーをさがす",
"search": "さがす",
"who_to_follow": "おすすめユーザー",
"preferences": "せってい"
},
@ -105,6 +106,9 @@
"expired": "いれふだは {0} まえに、おわりました",
"not_enough_options": "ユニークなオプションが、たりません"
},
"stickers": {
"add_sticker": "ステッカーをふやす"
},
"interactions": {
"favs_repeats": "リピートとおきにいり",
"follows": "あたらしいフォロー",
@ -506,7 +510,9 @@
"pinned": "ピンどめ",
"delete_confirm": "ほんとうに、このステータスを、けしてもいいですか?",
"reply_to": "へんしん:",
"replies_list": "へんしん:"
"replies_list": "へんしん:",
"mute_conversation": "スレッドをミュートする",
"unmute_conversation": "スレッドをミュートするのをやめる"
},
"user_card": {
"approve": "うけいれ",
@ -531,6 +537,8 @@
"remote_follow": "リモートフォロー",
"report": "つうほう",
"statuses": "ステータス",
"subscribe": "サブスクライブ",
"unsubscribe": "サブスクライブをやめる",
"unblock": "ブロックをやめる",
"unblock_progress": "ブロックをとりけしています...",
"block_progress": "ブロックしています...",
@ -595,5 +603,12 @@
"GiB": "GiB",
"TiB": "TiB"
}
},
"search": {
"people": "ひとびと",
"hashtags": "ハッシュタグ",
"person_talking": "{count} にんが、はなしています",
"people_talking": "{count} にんが、はなしています",
"no_results": "みつかりませんでした"
}
}

View file

@ -78,6 +78,7 @@
"timeline": "タイムライン",
"twkn": "接続しているすべてのネットワーク",
"user_search": "ユーザーを探す",
"search": "検索",
"who_to_follow": "おすすめユーザー",
"preferences": "設定"
},
@ -105,6 +106,9 @@
"expired": "投票は {0} 前に終了しました",
"not_enough_options": "相異なる選択肢が不足しています"
},
"stickers": {
"add_sticker": "ステッカーを追加"
},
"interactions": {
"favs_repeats": "リピートとお気に入り",
"follows": "新しいフォロワー",
@ -506,7 +510,9 @@
"pinned": "ピン留め",
"delete_confirm": "本当にこのステータスを削除してもよろしいですか?",
"reply_to": "返信",
"replies_list": "返信:"
"replies_list": "返信:",
"mute_conversation": "スレッドをミュート",
"unmute_conversation": "スレッドのミュートを解除"
},
"user_card": {
"approve": "受け入れ",
@ -531,6 +537,8 @@
"remote_follow": "リモートフォロー",
"report": "通報",
"statuses": "ステータス",
"subscribe": "購読",
"unsubscribe": "購読を解除",
"unblock": "ブロック解除",
"unblock_progress": "ブロックを解除しています...",
"block_progress": "ブロックしています...",
@ -595,5 +603,12 @@
"GiB": "GiB",
"TiB": "TiB"
}
},
"search": {
"people": "人々",
"hashtags": "ハッシュタグ",
"person_talking": "{count} 人が話しています",
"people_talking": "{count} 人が話しています",
"no_results": "見つかりませんでした"
}
}

View file

@ -16,6 +16,7 @@ const messages = {
eo: require('./eo.json'),
es: require('./es.json'),
et: require('./et.json'),
eu: require('./eu.json'),
fi: require('./fi.json'),
fr: require('./fr.json'),
ga: require('./ga.json'),
@ -32,6 +33,7 @@ const messages = {
pt: require('./pt.json'),
ro: require('./ro.json'),
ru: require('./ru.json'),
te: require('./te.json'),
zh: require('./zh.json')
}

View file

@ -2,14 +2,18 @@
"chat": {
"title": "Nettprat"
},
"exporter": {
"export": "Eksporter",
"processing": "Arbeider, du vil snart bli spurt om å laste ned filen din"
},
"features_panel": {
"chat": "Nettprat",
"gopher": "Gopher",
"media_proxy": "Media proxy",
"scope_options": "Velg mottakere",
"text_limit": "Tekst-grense",
"text_limit": "Tekstgrense",
"title": "Egenskaper",
"who_to_follow": "Hvem å følge"
"who_to_follow": "Kontoer å følge"
},
"finder": {
"error_fetching_user": "Feil ved henting av bruker",
@ -17,23 +21,66 @@
},
"general": {
"apply": "Bruk",
"submit": "Send"
"submit": "Send",
"more": "Mer",
"generic_error": "Det oppsto en feil",
"optional": "valgfritt",
"show_more": "Vis mer",
"show_less": "Vis mindre",
"cancel": "Avbryt",
"disable": "Slå av",
"enable": "Slå på",
"confirm": "Godta",
"verify": "Godkjenn"
},
"image_cropper": {
"crop_picture": "Minsk bilde",
"save": "Lagre",
"save_without_cropping": "Lagre uten å minske bildet",
"cancel": "Avbryt"
},
"importer": {
"submit": "Send",
"success": "Importering fullført",
"error": "Det oppsto en feil under importering av denne filen"
},
"login": {
"login": "Logg inn",
"description": "Log inn med OAuth",
"logout": "Logg ut",
"password": "Passord",
"placeholder": "f. eks lain",
"register": "Registrer",
"username": "Brukernavn"
"username": "Brukernavn",
"hint": "Logg inn for å delta i diskusjonen",
"authentication_code": "Verifikasjonskode",
"enter_recovery_code": "Skriv inn en gjenopprettingskode",
"enter_two_factor_code": "Skriv inn en to-faktors kode",
"recovery_code": "Gjenopprettingskode",
"heading" : {
"totp" : "To-faktors autentisering",
"recovery" : "To-faktors gjenoppretting"
}
},
"media_modal": {
"previous": "Forrige",
"next": "Neste"
},
"nav": {
"about": "Om",
"back": "Tilbake",
"chat": "Lokal nettprat",
"friend_requests": "Følgeforespørsler",
"mentions": "Nevnt",
"interactions": "Interaksjooner",
"dms": "Direktemeldinger",
"public_tl": "Offentlig Tidslinje",
"timeline": "Tidslinje",
"twkn": "Det hele kjente nettverket"
"twkn": "Det hele kjente nettverket",
"user_search": "Søk etter brukere",
"search": "Søk",
"who_to_follow": "Kontoer å følge",
"preferences": "Innstillinger"
},
"notifications": {
"broken_favorite": "Ukjent status, leter etter den...",
@ -42,19 +89,52 @@
"load_older": "Last eldre varsler",
"notifications": "Varslinger",
"read": "Les!",
"repeated_you": "Gjentok din status"
"repeated_you": "Gjentok din status",
"no_more_notifications": "Ingen gjenstående varsler"
},
"polls": {
"add_poll": "Legg til undersøkelse",
"add_option": "Legg til svaralternativ",
"option": "Svaralternativ",
"votes": "stemmer",
"vote": "Stem",
"type": "Undersøkelsestype",
"single_choice": "Enkeltvalg",
"multiple_choices": "Flervalg",
"expiry": "Undersøkelsestid",
"expires_in": "Undersøkelsen er over om {0}",
"expired": "Undersøkelsen ble ferdig {0} siden",
"not_enough_options": "For få unike svaralternativer i undersøkelsen"
},
"stickers": {
"add_sticker": "Legg til klistremerke"
},
"interactions": {
"favs_repeats": "Gjentakelser og favoritter",
"follows": "Nye følgere",
"load_older": "Last eldre interaksjoner"
},
"post_status": {
"new_status": "Legg ut ny status",
"account_not_locked_warning": "Kontoen din er ikke {0}. Hvem som helst kan følge deg for å se dine statuser til følgere",
"account_not_locked_warning_link": "låst",
"attachments_sensitive": "Merk vedlegg som sensitive",
"content_type": {
"text/plain": "Klar tekst"
"text/plain": "Klar tekst",
"text/html": "HTML",
"text/markdown": "Markdown",
"text/bbcode": "BBCode"
},
"content_warning": "Tema (valgfritt)",
"default": "Landet akkurat i L.A.",
"direct_warning": "Denne statusen vil kun bli sett av nevnte brukere",
"direct_warning_to_all": "Denne statusen vil være synlig av nevnte brukere",
"direct_warning_to_first_only": "Denne statusen vil være synlig for de brukerene som blir nevnt først i statusen.",
"posting": "Publiserer",
"scope_notice": {
"public": "Denne statusen vil være synlig for alle",
"private": "Denne statusen vil være synlig for dine følgere",
"unlisted": "Denne statusen vil ikke være synlig i Offentlig Tidslinje eller Det Hele Kjente Nettverket"
},
"scope": {
"direct": "Direkte, publiser bare til nevnte brukere",
"private": "Bare følgere, publiser bare til brukere som følger deg",
@ -68,9 +148,49 @@
"fullname": "Visningsnavn",
"password_confirm": "Bekreft passord",
"registration": "Registrering",
"token": "Invitasjons-bevis"
"token": "Invitasjons-bevis",
"captcha": "CAPTCHA",
"new_captcha": "Trykk på bildet for å få en ny captcha",
"username_placeholder": "f.eks. Lain Iwakura",
"fullname_placeholder": "f.eks. Lain Iwakura",
"bio_placeholder": "e.g.\nHei, jeg er Lain.\nJeg er en animert jente som bor i forstaden i Japan. Du kjenner meg kanskje fra the Wired.",
"validations": {
"username_required": "kan ikke stå tomt",
"fullname_required": "kan ikke stå tomt",
"email_required": "kan ikke stå tomt",
"password_required": "kan ikke stå tomt",
"password_confirmation_required": "kan ikke stå tomt",
"password_confirmation_match": "skal være det samme som passord"
}
},
"selectable_list": {
"select_all": "Velg alle"
},
"settings": {
"app_name": "Applikasjonsnavn",
"security": "Sikkerhet",
"enter_current_password_to_confirm": "Skriv inn ditt nåverende passord for å bekrefte din identitet",
"mfa": {
"otp" : "OTP",
"setup_otp" : "Set opp OTP",
"wait_pre_setup_otp" : "forhåndsstiller OTP",
"confirm_and_enable" : "Bekreft og slå på OTP",
"title": "To-faktors autentisering",
"generate_new_recovery_codes" : "Generer nye gjenopprettingskoder",
"warning_of_generate_new_codes" : "Når du genererer nye gjenopprettingskoder, vil de gamle slutte å fungere.",
"recovery_codes" : "Gjenopprettingskoder.",
"waiting_a_recovery_codes": "Mottar gjenopprettingskoder...",
"recovery_codes_warning" : "Skriv disse kodene ned eller plasser dem ett sikkert sted - ellers så vil du ikke se dem igjen. Dersom du mister tilgang til din to-faktors app og dine gjenopprettingskoder, vil du bli stengt ute av kontoen din.",
"authentication_methods" : "Autentiseringsmetoder",
"scan": {
"title": "Skann",
"desc": "Ved hjelp av din to-faktors applikasjon, skann denne QR-koden eller skriv inn tekstnøkkelen",
"secret_code": "Nøkkel"
},
"verify": {
"desc": "For å skru på to-faktors autentisering, skriv inn koden i fra din to-faktors app:"
}
},
"attachmentRadius": "Vedlegg",
"attachments": "Vedlegg",
"autoload": "Automatisk lasting når du blar ned til bunnen",
@ -79,6 +199,12 @@
"avatarRadius": "Profilbilde",
"background": "Bakgrunn",
"bio": "Biografi",
"block_export": "Eksporter blokkeringer",
"block_export_button": "Eksporter blokkeringer til en csv fil",
"block_import": "Import blokkeringer",
"block_import_error": "Det oppsto en feil under importering av blokkeringer",
"blocks_imported": "Blokkeringer importert, det vil ta litt å prossesere dem",
"blocks_tab": "Blokkeringer",
"btnRadius": "Knapper",
"cBlue": "Blå (Svar, følg)",
"cGreen": "Grønn (Gjenta)",
@ -88,6 +214,7 @@
"change_password_error": "Feil ved endring av passord",
"changed_password": "Passord endret",
"collapse_subject": "Sammenfold statuser med tema",
"composing": "komponering",
"confirm_new_password": "Bekreft nytt passord",
"current_avatar": "Ditt nåværende profilbilde",
"current_password": "Nåværende passord",
@ -95,15 +222,15 @@
"data_import_export_tab": "Data import / eksport",
"default_vis": "Standard visnings-omfang",
"delete_account": "Slett konto",
"delete_account_description": "Slett din konto og alle dine statuser",
"delete_account_description": "Fjern din konto og alle dine meldinger for alltid.",
"delete_account_error": "Det oppsto et problem ved sletting av kontoen din, hvis dette problemet forblir kontakt din administrator",
"delete_account_instructions": "Skriv inn ditt passord i feltet nedenfor for å bekrefte sletting av konto",
"avatar_size_instruction": "Den anbefalte minste-størrelsen for profilbilder er 150x150 piksler",
"export_theme": "Lagre tema",
"filtering": "Filtrering",
"filtering_explanation": "Alle statuser som inneholder disse ordene vil bli dempet, en kombinasjon av tegn per linje",
"follow_export": "Eksporter følginger",
"follow_export_button": "Eksporter følgingene dine til en .csv fil",
"follow_export_processing": "Jobber, du vil snart bli spurt om å laste ned filen din.",
"follow_import": "Importer følginger",
"follow_import_error": "Feil ved importering av følginger.",
"follows_imported": "Følginger importert! Behandling vil ta litt tid.",
@ -111,10 +238,22 @@
"general": "Generell",
"hide_attachments_in_convo": "Gjem vedlegg i samtaler",
"hide_attachments_in_tl": "Gjem vedlegg på tidslinje",
"hide_muted_posts": "Gjem statuser i fra gjemte brukere",
"max_thumbnails": "Maks antall forhåndsbilder per status",
"hide_isp": "Gjem instans-spesifikt panel",
"preload_images": "Forhåndslast bilder",
"use_one_click_nsfw": "Åpne sensitive vedlegg med ett klikk",
"hide_post_stats": "Gjem status statistikk (f.eks. antall likes",
"hide_user_stats": "Gjem bruker statistikk (f.eks. antall følgere)",
"hide_filtered_statuses": "Gjem filtrerte statuser",
"import_blocks_from_a_csv_file": "Importer blokkeringer fra en csv fil",
"import_followers_from_a_csv_file": "Importer følginger fra en csv fil",
"import_theme": "Last tema",
"inputRadius": "Input felt",
"inputRadius": "Tekst felt",
"checkboxRadius": "Sjekkbokser",
"instance_default": "(standard: {value})",
"instance_default_simple": "(standard)",
"interface": "Grensesnitt",
"interfaceLanguage": "Grensesnitt-språk",
"invalid_theme_imported": "Den valgte filen er ikke ett støttet Pleroma-tema, ingen endringer til ditt tema ble gjort",
"limited_availability": "Ikke tilgjengelig i din nettleser",
@ -122,6 +261,9 @@
"lock_account_description": "Begrens din konto til bare godkjente følgere",
"loop_video": "Gjenta videoer",
"loop_video_silent_only": "Gjenta bare videoer uten lyd, (for eksempel Mastodon sine \"gifs\")",
"mutes_tab": "Dempinger",
"play_videos_in_modal": "Spill videoer direkte i media-avspilleren",
"use_contain_fit": "Ikke minsk vedlegget i forhåndsvisninger",
"name": "Navn",
"name_bio": "Navn & Biografi",
"new_password": "Nytt passord",
@ -131,10 +273,16 @@
"notification_visibility_mentions": "Nevnt",
"notification_visibility_repeats": "Gjentakelser",
"no_rich_text_description": "Fjern all formatering fra statuser",
"no_blocks": "Ingen blokkeringer",
"no_mutes": "Ingen dempinger",
"hide_follows_description": "Ikke hvis hvem jeg følger",
"hide_followers_description": "Ikke hvis hvem som følger meg",
"show_admin_badge": "Hvis ett administratormerke på min profil",
"show_moderator_badge": "Hvis ett moderatormerke på min profil",
"nsfw_clickthrough": "Krev trykk for å vise statuser som kan være upassende",
"oauth_tokens": "OAuth Tokens",
"token": "Pollett",
"refresh_token": "Refresh Token",
"refresh_token": "Fornyingspolett",
"valid_until": "Gyldig til",
"revoke_token": "Tilbakekall",
"panelRadius": "Panel",
@ -149,24 +297,195 @@
"reply_visibility_all": "Vis alle svar",
"reply_visibility_following": "Vis bare svar som er til meg eller folk jeg følger",
"reply_visibility_self": "Vis bare svar som er til meg",
"autohide_floating_post_button": "Skjul Ny Status knapp automatisk (mobil)",
"saving_err": "Feil ved lagring av innstillinger",
"saving_ok": "Innstillinger lagret",
"search_user_to_block": "Søk etter hvem du vil blokkere",
"search_user_to_mute": "Søk etter hvem du vil dempe",
"security_tab": "Sikkerhet",
"scope_copy": "Kopier mottakere når du svarer noen (Direktemeldinger blir alltid kopiert",
"minimal_scopes_mode": "Minimaliser mottakervalg",
"set_new_avatar": "Rediger profilbilde",
"set_new_profile_background": "Rediger profil-bakgrunn",
"set_new_profile_banner": "Sett ny profil-banner",
"settings": "Innstillinger",
"subject_input_always_show": "Alltid hvis tema-felt",
"subject_line_behavior": "Kopier tema når du svarer",
"subject_line_email": "Som email: \"re: tema\"",
"subject_line_mastodon": "Som mastodon: kopier som den er",
"subject_line_noop": "Ikke koper",
"post_status_content_type": "Status innholdstype",
"stop_gifs": "Spill av GIFs når du holder over dem",
"streaming": "Automatisk strømming av nye statuser når du har bladd til toppen",
"text": "Tekst",
"theme": "Tema",
"theme_help": "Bruk heksadesimale fargekoder (#rrggbb) til å endre farge-temaet ditt.",
"theme_help_v2_1": "Du kan også overskrive noen komponenter sine farger og opasitet ved å sjekke av sjekkboksen, bruk \"Nullstill alt\" knappen for å fjerne alle overskrivelser.",
"theme_help_v2_2": "Ikoner under noen av innstillingene er bakgrunn/tekst kontrast indikatorer, hold over dem for detaljert informasjon. Vennligst husk at disse indikatorene viser det verste utfallet.",
"tooltipRadius": "Verktøytips/advarsler",
"upload_a_photo": "Last opp ett bilde",
"user_settings": "Brukerinstillinger",
"values": {
"false": "nei",
"true": "ja"
},
"notifications": "Varsler",
"notification_setting": "Motta varsler i fra:",
"notification_setting_follows": "Brukere du følger",
"notification_setting_non_follows": "Brukere du ikke følger",
"notification_setting_followers": "Brukere som følger deg",
"notification_setting_non_followers": "Brukere som ikke følger deg",
"notification_mutes": "For å stoppe å motta varsler i fra en spesifikk bruker, kan du dempe dem.",
"notification_blocks": "Hvis du blokkerer en bruker vil det stoppe alle varsler og i tilleg få dem til å slutte å følge deg",
"enable_web_push_notifications": "Skru på pushnotifikasjoner i nettlesere",
"style": {
"switcher": {
"keep_color": "Behold farger",
"keep_shadows": "Behold skygger",
"keep_opacity": "Behold opasitet",
"keep_roundness": "Behold rundhet",
"keep_fonts": "Behold fonter",
"save_load_hint": "\"Behold\" alternativer beholder de instillingene som er satt når du velger eller laster inn temaer, det lagrer også disse alternativene når du eksporterer ett tema, Når alle sjekkboksene er tomme, vil alt bli lagret når du eksporterer ett tema.",
"reset": "Still in på nytt",
"clear_all": "Nullstill alt",
"clear_opacity": "Nullstill opasitet"
},
"common": {
"color": "Farge",
"opacity": "Opasitet",
"contrast": {
"hint": "Kontrast forholdet er {ratio}, it {level} {context}",
"level": {
"aa": "møter Nivå AA retningslinje (minimal)",
"aaa": "møter Nivå AAA retningslinje (recommended)",
"bad": "møter ingen tilgjengeligshetsretningslinjer"
},
"context": {
"18pt": "for stor (18pt+) tekst",
"text": "for tekst"
}
}
},
"common_colors": {
"_tab_label": "Vanlig",
"main": "Vanlige farger",
"foreground_hint": "Se \"Avansert\" fanen for mer detaljert kontroll",
"rgbo": "Ikoner, aksenter, merker"
},
"advanced_colors": {
"_tab_label": "Avansert",
"alert": "Varslingsbakgrunn",
"alert_error": "Feil",
"badge": "Merkebakgrunn",
"badge_notification": "Varsling",
"panel_header": "Panelhode",
"top_bar": "Topplinje",
"borders": "Kanter",
"buttons": "Knapper",
"inputs": "Tekstfelt",
"faint_text": "Svak tekst"
},
"radii": {
"_tab_label": "Rundhet"
},
"shadows": {
"_tab_label": "Skygger og belysning",
"component": "Komponent",
"override": "Overskriv",
"shadow_id": "Skygge #{value}",
"blur": "Uklarhet",
"spread": "Spredning",
"inset": "Insett",
"hint": "For skygger kan du sette --variable som en fargeveerdi for å bruke CSS3 variabler. Vær oppmerksom på at å sette opasitet da ikke vil fungere her.",
"filter_hint": {
"always_drop_shadow": "Advarsel, denne skyggen bruker alltid {0} når nettleseren støtter det.",
"drop_shadow_syntax": "{0} støtter ikke {1} parameter og {2} nøkkelord.",
"avatar_inset": "Vær oppmerksom på at å kombinere både insatte og uinsatte skygger på profilbilder kan gi uforventede resultater med gjennomsiktige profilbilder.",
"spread_zero": "Skygger med spredning > 0 vil fremstå som de var satt til 0",
"inset_classic": "Insette skygger vil bruke {0}"
},
"components": {
"panel": "Panel",
"panelHeader": "Panelhode",
"topBar": "Topplinje",
"avatar": "Profilbilde (i profilvisning)",
"avatarStatus": "Profilbilde (i statusvisning)",
"popup": "Popups og tooltips",
"button": "Knapp",
"buttonHover": "Knapp (holdt)",
"buttonPressed": "Knapp (nedtrykt)",
"buttonPressedHover": "Knapp (nedtrykt+holdt)",
"input": "Tekstfelt"
}
},
"fonts": {
"_tab_label": "Fonter",
"help": "Velg font til elementene i brukergrensesnittet. For \"egendefinert\" må du skrive inn det nøyaktige font-navnet som det fremstår på systemet",
"components": {
"interface": "Grensesnitt",
"input": "Tekstfelt",
"post": "Statustekst",
"postCode": "Monospaced tekst i en status (rik tekst)"
},
"family": "Font naavn",
"size": "Størrelse (i piksler)",
"weight": "Vekt (dristighet)",
"custom": "Egendefinert"
},
"preview": {
"header": "Forhåndsvisning",
"content": "Innhold",
"error": "Eksempel feil",
"button": "Knapp",
"text": "Mye mer {0} og {1}",
"mono": "innhold",
"input": "Landet akkurat i L.A.",
"faint_link": "hjelpfull brukerveiledning",
"fine_print": "Les vår {0} for å lære ingenting nyttig!",
"header_faint": "Dette er OK",
"checkbox": "Jeg har skumlest vilkår og betingelser",
"link": "en flott liten link"
}
},
"version": {
"title": "Versjon",
"backend_version": "Backend Versjon",
"frontend_version": "Frontend Versjon"
}
},
"time": {
"day": "{0} dag",
"days": "{0} dager",
"day_short": "{0}d",
"days_short": "{0}d",
"hour": "{0} time",
"hours": "{0} timer",
"hour_short": "{0}t",
"hours_short": "{0}t",
"in_future": "om {0}",
"in_past": "{0} siden",
"minute": "{0} minutt",
"minutes": "{0} minutter",
"minute_short": "{0}min",
"minutes_short": "{0}min",
"month": "{0} måned",
"months": "{0} måneder",
"month_short": "{0}md.",
"months_short": "{0}md.",
"now": "akkurat nå",
"now_short": "nå",
"second": "{0} sekund",
"seconds": "{0} sekunder",
"second_short": "{0}s",
"seconds_short": "{0}s",
"week": "{0} uke",
"weeks": "{0} uker",
"week_short": "{0}u",
"weeks_short": "{0}u",
"year": "{0} år",
"years": "{0} år",
"year_short": "{0}år",
"years_short": "{0}år"
},
"timeline": {
"collapse": "Sammenfold",
@ -176,29 +495,116 @@
"no_retweet_hint": "Status er markert som bare til følgere eller direkte og kan ikke gjentas",
"repeated": "gjentok",
"show_new": "Vis nye",
"up_to_date": "Oppdatert"
"up_to_date": "Oppdatert",
"no_more_statuses": "Ingen flere statuser",
"no_statuses": "Ingen statuser"
},
"status": {
"favorites": "Favoritter",
"repeats": "Gjentakelser",
"delete": "Slett status",
"pin": "Fremhev på profil",
"unpin": "Fjern fremhevelse",
"pinned": "Fremhevet",
"delete_confirm": "Har du virkelig lyst til å slette denne statusen?",
"reply_to": "Svar til",
"replies_list": "Svar:"
},
"user_card": {
"approve": "Godkjenn",
"block": "Blokker",
"blocked": "Blokkert!",
"deny": "Avslå",
"favorites": "Favoritter",
"follow": "Følg",
"follow_sent": "Forespørsel sendt!",
"follow_progress": "Forespør…",
"follow_again": "Gjenta forespørsel?",
"follow_unfollow": "Avfølg",
"followees": "Følger",
"followers": "Følgere",
"following": "Følger!",
"follows_you": "Følger deg!",
"its_you": "Det er deg!",
"media": "Media",
"mute": "Demp",
"muted": "Dempet",
"per_day": "per dag",
"remote_follow": "Følg eksternt",
"statuses": "Statuser"
"report": "Rapport",
"statuses": "Statuser",
"subscribe": "Abonner",
"unsubscribe": "Avabonner",
"unblock": "Fjern blokkering",
"unblock_progress": "Fjerner blokkering...",
"block_progress": "Blokkerer...",
"unmute": "Fjern demping",
"unmute_progress": "Fjerner demping...",
"mute_progress": "Demper...",
"admin_menu": {
"moderation": "Moderering",
"grant_admin": "Gi Administrator",
"revoke_admin": "Fjern Administrator",
"grant_moderator": "Gi Moderator",
"revoke_moderator": "Fjern Moderator",
"activate_account": "Aktiver konto",
"deactivate_account": "Deaktiver kontro",
"delete_account": "Slett konto",
"force_nsfw": "Merk alle statuser som sensitive",
"strip_media": "Fjern media i fra statuser",
"force_unlisted": "Tving statuser til å være uopplistet",
"sandbox": "Tving statuser til å bare vises til følgere",
"disable_remote_subscription": "Fjern mulighet til å følge brukeren fra andre instanser",
"disable_any_subscription": "Fjern mulighet til å følge brukeren",
"quarantine": "Gjør at statuser fra brukeren ikke kan sendes til andre instanser",
"delete_user": "Slett bruker",
"delete_user_confirmation": "Er du helt sikker? Denne handlingen kan ikke omgjøres."
}
},
"user_profile": {
"timeline_title": "Bruker-tidslinje"
"timeline_title": "Bruker-tidslinje",
"profile_does_not_exist": "Beklager, denne profilen eksisterer ikke.",
"profile_loading_error": "Beklager, det oppsto en feil under lasting av denne profilen."
},
"user_reporting": {
"title": "Rapporterer {0}",
"add_comment_description": "Rapporten blir sent til moderatorene av din instans. Du kan gi en forklaring på hvorfor du rapporterer denne kontoen under:",
"additional_comments": "Videre kommentarer",
"forward_description": "Denne kontoen er fra en annen server, vil du sende en kopi av rapporten til dem også?",
"forward_to": "Videresend til {0}",
"submit": "Send",
"generic_error": "Det oppsto en feil under behandling av din forespørsel."
},
"who_to_follow": {
"more": "Mer",
"who_to_follow": "Hvem å følge"
"who_to_follow": "Kontoer å følge"
},
"tool_tip": {
"media_upload": "Last opp media",
"repeat": "Gjenta",
"reply": "Svar",
"favorite": "Lik",
"user_settings": "Brukerinnstillinger"
},
"upload":{
"error": {
"base": "Det oppsto en feil under opplastning.",
"file_too_big": "Fil for stor [{filesize}{filesizeunit} / {allowedsize}{allowedsizeunit}]",
"default": "Prøv igjen senere"
},
"file_size_units": {
"B": "B",
"KiB": "KiB",
"MiB": "MiB",
"GiB": "GiB",
"TiB": "TiB"
}
},
"search": {
"people": "Folk",
"hashtags": "Emneknagger",
"person_talking": "{count} person snakker om dette",
"people_talking": "{count} personer snakker om dette",
"no_results": "Ingen resultater"
}
}

View file

@ -389,5 +389,16 @@
"person_talking": "Популярно у {count} человека",
"people_talking": "Популярно у {count} человек",
"no_results": "Ничего не найдено"
},
"password_reset": {
"forgot_password": "Забыли пароль?",
"password_reset": "Сброс пароля",
"instruction": "Введите ваш email или имя пользователя, и мы отправим вам ссылку для сброса пароля.",
"placeholder": "Ваш email или имя пользователя",
"check_email": "Проверьте ваш email и перейдите по ссылке для сброса пароля.",
"return_home": "Вернуться на главную страницу",
"not_found": "Мы не смогли найти аккаунт с таким email-ом или именем пользователя.",
"too_many_requests": "Вы исчерпали допустимое количество попыток, попробуйте позже.",
"password_reset_disabled": "Сброс пароля отключен. Cвяжитесь с администратором вашего сервера."
}
}

352
src/i18n/te.json Normal file
View file

@ -0,0 +1,352 @@
{
"chat.title": "చాట్",
"features_panel.chat": "చాట్",
"features_panel.gopher": "గోఫర్",
"features_panel.media_proxy": "మీడియా ప్రాక్సీ",
"features_panel.scope_options": "స్కోప్ ఎంపికలు",
"features_panel.text_limit": "వచన పరిమితి",
"features_panel.title": "లక్షణాలు",
"features_panel.who_to_follow": "ఎవరిని అనుసరించాలి",
"finder.error_fetching_user": "వినియోగదారుని పొందడంలో లోపం",
"finder.find_user": "వినియోగదారుని కనుగొనండి",
"general.apply": "వర్తించు",
"general.submit": "సమర్పించు",
"general.more": "మరిన్ని",
"general.generic_error": "ఒక తప్పిదం సంభవించినది",
"general.optional": "ఐచ్చికం",
"image_cropper.crop_picture": "చిత్రాన్ని కత్తిరించండి",
"image_cropper.save": "దాచు",
"image_cropper.save_without_cropping": "కత్తిరించకుండా సేవ్ చేయి",
"image_cropper.cancel": "రద్దుచేయి",
"login.login": "లాగిన్",
"login.description": "OAuth తో లాగిన్ అవ్వండి",
"login.logout": "లాగౌట్",
"login.password": "సంకేతపదము",
"login.placeholder": "ఉదా. lain",
"login.register": "నమోదు చేసుకోండి",
"login.username": "వాడుకరి పేరు",
"login.hint": "చర్చలో చేరడానికి లాగిన్ అవ్వండి",
"media_modal.previous": "ముందరి పుట",
"media_modal.next": "తరువాత",
"nav.about": "గురించి",
"nav.back": "వెనక్కి",
"nav.chat": "స్థానిక చాట్",
"nav.friend_requests": "అనుసరించడానికి అభ్యర్థనలు",
"nav.mentions": "ప్రస్తావనలు",
"nav.dms": "నేరుగా పంపిన సందేశాలు",
"nav.public_tl": "ప్రజా కాలక్రమం",
"nav.timeline": "కాలక్రమం",
"nav.twkn": "మొత్తం తెలిసిన నెట్వర్క్",
"nav.user_search": "వాడుకరి శోధన",
"nav.who_to_follow": "ఎవరిని అనుసరించాలి",
"nav.preferences": "ప్రాధాన్యతలు",
"notifications.broken_favorite": "తెలియని స్థితి, దాని కోసం శోధిస్తోంది...",
"notifications.favorited_you": "మీ స్థితిని ఇష్టపడ్డారు",
"notifications.followed_you": "మిమ్మల్ని అనుసరించారు",
"notifications.load_older": "పాత నోటిఫికేషన్లను లోడ్ చేయండి",
"notifications.notifications": "ప్రకటనలు",
"notifications.read": "చదివాను!",
"notifications.repeated_you": "మీ స్థితిని పునరావృతం చేసారు",
"notifications.no_more_notifications": "ఇక నోటిఫికేషన్లు లేవు",
"post_status.new_status": "క్రొత్త స్థితిని పోస్ట్ చేయండి",
"post_status.account_not_locked_warning": "మీ ఖాతా {} కాదు. ఎవరైనా మిమ్మల్ని అనుసరించి అనుచరులకు మాత్రమే ఉద్దేశించిన పోస్టులను చూడవచ్చు.",
"post_status.account_not_locked_warning_link": "తాళం వేయబడినది",
"post_status.attachments_sensitive": "జోడింపులను సున్నితమైనవిగా గుర్తించండి",
"post_status.content_type.text/plain": "సాధారణ అక్షరాలు",
"post_status.content_type.text/html": "హెచ్‌టిఎమ్ఎల్",
"post_status.content_type.text/markdown": "మార్క్డౌన్",
"post_status.content_warning": "విషయం (ఐచ్ఛికం)",
"post_status.default": "ఇప్పుడే విజయవాడలో దిగాను.",
"post_status.direct_warning": "ఈ పోస్ట్ మాత్రమే పేర్కొన్న వినియోగదారులకు మాత్రమే కనిపిస్తుంది.",
"post_status.posting": "పోస్ట్ చేస్తున్నా",
"post_status.scope.direct": "ప్రత్యక్ష - పేర్కొన్న వినియోగదారులకు మాత్రమే పోస్ట్ చేయబడుతుంది",
"post_status.scope.private": "అనుచరులకు మాత్రమే - అనుచరులకు మాత్రమే పోస్ట్ చేయబడుతుంది",
"post_status.scope.public": "పబ్లిక్ - ప్రజా కాలక్రమాలకు పోస్ట్ చేయబడుతుంది",
"post_status.scope.unlisted": "జాబితా చేయబడనిది - ప్రజా కాలక్రమాలకు పోస్ట్ చేయవద్దు",
"registration.bio": "బయో",
"registration.email": "ఈ మెయిల్",
"registration.fullname": "ప్రదర్శన పేరు",
"registration.password_confirm": "పాస్వర్డ్ నిర్ధారణ",
"registration.registration": "నమోదు",
"registration.token": "ఆహ్వాన టోకెన్",
"registration.captcha": "కాప్చా",
"registration.new_captcha": "కొత్త కాప్చా పొందుటకు చిత్రం మీద క్లిక్ చేయండి",
"registration.username_placeholder": "ఉదా. lain",
"registration.fullname_placeholder": "ఉదా. Lain Iwakura",
"registration.bio_placeholder": "e.g.\nHi, I'm Lain.\nIm an anime girl living in suburban Japan. You may know me from the Wired.",
"registration.validations.username_required": "ఖాళీగా విడిచిపెట్టరాదు",
"registration.validations.fullname_required": "ఖాళీగా విడిచిపెట్టరాదు",
"registration.validations.email_required": "ఖాళీగా విడిచిపెట్టరాదు",
"registration.validations.password_required": "ఖాళీగా విడిచిపెట్టరాదు",
"registration.validations.password_confirmation_required": "ఖాళీగా విడిచిపెట్టరాదు",
"registration.validations.password_confirmation_match": "సంకేతపదం వలె ఉండాలి",
"settings.app_name": "అనువర్తన పేరు",
"settings.attachmentRadius": "జోడింపులు",
"settings.attachments": "జోడింపులు",
"settings.autoload": "క్రిందికి స్క్రోల్ చేయబడినప్పుడు స్వయంచాలక లోడింగ్ని ప్రారంభించు",
"settings.avatar": "అవతారం",
"settings.avatarAltRadius": "అవతారాలు (ప్రకటనలు)",
"settings.avatarRadius": "అవతారాలు",
"settings.background": "బ్యాక్‌గ్రౌండు",
"settings.bio": "బయో",
"settings.blocks_tab": "బ్లాక్‌లు",
"settings.btnRadius": "బటన్లు",
"settings.cBlue": "నీలం (ప్రత్యుత్తరం, అనుసరించండి)",
"settings.cGreen": "Green (Retweet)",
"settings.cOrange": "ఆరెంజ్ (ఇష్టపడు)",
"settings.cRed": "Red (Cancel)",
"settings.change_password": "పాస్‌వర్డ్ మార్చండి",
"settings.change_password_error": "మీ పాస్వర్డ్ను మార్చడంలో సమస్య ఉంది.",
"settings.changed_password": "పాస్వర్డ్ విజయవంతంగా మార్చబడింది!",
"settings.collapse_subject": "Collapse posts with subjects",
"settings.composing": "Composing",
"settings.confirm_new_password": "కొత్త పాస్వర్డ్ను నిర్ధారించండి",
"settings.current_avatar": "మీ ప్రస్తుత అవతారం",
"settings.current_password": "ప్రస్తుత పాస్వర్డ్",
"settings.current_profile_banner": "మీ ప్రస్తుత ప్రొఫైల్ బ్యానర్",
"settings.data_import_export_tab": "Data Import / Export",
"settings.default_vis": "Default visibility scope",
"settings.delete_account": "Delete Account",
"settings.delete_account_description": "మీ ఖాతా మరియు మీ అన్ని సందేశాలను శాశ్వతంగా తొలగించండి.",
"settings.delete_account_error": "There was an issue deleting your account. If this persists please contact your instance administrator.",
"settings.delete_account_instructions": "ఖాతా తొలగింపును నిర్ధారించడానికి దిగువ ఇన్పుట్లో మీ పాస్వర్డ్ను టైప్ చేయండి.",
"settings.avatar_size_instruction": "అవతార్ చిత్రాలకు సిఫార్సు చేసిన కనీస పరిమాణం 150x150 పిక్సెల్స్.",
"settings.export_theme": "Save preset",
"settings.filtering": "వడపోత",
"settings.filtering_explanation": "All statuses containing these words will be muted, one per line",
"settings.follow_export": "Follow export",
"settings.follow_export_button": "Export your follows to a csv file",
"settings.follow_export_processing": "Processing, you'll soon be asked to download your file",
"settings.follow_import": "Follow import",
"settings.follow_import_error": "అనుచరులను దిగుమతి చేయడంలో లోపం",
"settings.follows_imported": "Follows imported! Processing them will take a while.",
"settings.foreground": "Foreground",
"settings.general": "General",
"settings.hide_attachments_in_convo": "సంభాషణలలో జోడింపులను దాచు",
"settings.hide_attachments_in_tl": "కాలక్రమంలో జోడింపులను దాచు",
"settings.hide_muted_posts": "మ్యూట్ చేసిన వినియోగదారుల యొక్క పోస్ట్లను దాచిపెట్టు",
"settings.max_thumbnails": "Maximum amount of thumbnails per post",
"settings.hide_isp": "Hide instance-specific panel",
"settings.preload_images": "Preload images",
"settings.use_one_click_nsfw": "కేవలం ఒక క్లిక్ తో NSFW జోడింపులను తెరవండి",
"settings.hide_post_stats": "Hide post statistics (e.g. the number of favorites)",
"settings.hide_user_stats": "Hide user statistics (e.g. the number of followers)",
"settings.hide_filtered_statuses": "Hide filtered statuses",
"settings.import_followers_from_a_csv_file": "Import follows from a csv file",
"settings.import_theme": "Load preset",
"settings.inputRadius": "Input fields",
"settings.checkboxRadius": "Checkboxes",
"settings.instance_default": "(default: {value})",
"settings.instance_default_simple": "(default)",
"settings.interface": "Interface",
"settings.interfaceLanguage": "Interface language",
"settings.invalid_theme_imported": "The selected file is not a supported Pleroma theme. No changes to your theme were made.",
"settings.limited_availability": "మీ బ్రౌజర్లో అందుబాటులో లేదు",
"settings.links": "Links",
"settings.lock_account_description": "మీ ఖాతాను ఆమోదించిన అనుచరులకు మాత్రమే పరిమితం చేయండి",
"settings.loop_video": "Loop videos",
"settings.loop_video_silent_only": "Loop only videos without sound (i.e. Mastodon's \"gifs\")",
"settings.mutes_tab": "మ్యూట్ చేయబడినవి",
"settings.play_videos_in_modal": "మీడియా వీక్షికలో నేరుగా వీడియోలను ప్లే చేయి",
"settings.use_contain_fit": "అటాచ్మెంట్ సూక్ష్మచిత్రాలను కత్తిరించవద్దు",
"settings.name": "Name",
"settings.name_bio": "పేరు & బయో",
"settings.new_password": "కొత్త సంకేతపదం",
"settings.notification_visibility": "చూపించవలసిన నోటిఫికేషన్ రకాలు",
"settings.notification_visibility_follows": "Follows",
"settings.notification_visibility_likes": "ఇష్టాలు",
"settings.notification_visibility_mentions": "ప్రస్తావనలు",
"settings.notification_visibility_repeats": "పునఃప్రసారాలు",
"settings.no_rich_text_description": "అన్ని పోస్ట్ల నుండి రిచ్ టెక్స్ట్ ఫార్మాటింగ్ను స్ట్రిప్ చేయండి",
"settings.no_blocks": "బ్లాక్స్ లేవు",
"settings.no_mutes": "మ్యూట్లు లేవు",
"settings.hide_follows_description": "నేను ఎవరిని అనుసరిస్తున్నానో చూపించవద్దు",
"settings.hide_followers_description": "నన్ను ఎవరు అనుసరిస్తున్నారో చూపవద్దు",
"settings.show_admin_badge": "నా ప్రొఫైల్ లో అడ్మిన్ బ్యాడ్జ్ చూపించు",
"settings.show_moderator_badge": "నా ప్రొఫైల్లో మోడరేటర్ బ్యాడ్జ్ని చూపించు",
"settings.nsfw_clickthrough": "Enable clickthrough NSFW attachment hiding",
"settings.oauth_tokens": "OAuth tokens",
"settings.token": "Token",
"settings.refresh_token": "Refresh Token",
"settings.valid_until": "Valid Until",
"settings.revoke_token": "Revoke",
"settings.panelRadius": "Panels",
"settings.pause_on_unfocused": "Pause streaming when tab is not focused",
"settings.presets": "Presets",
"settings.profile_background": "Profile Background",
"settings.profile_banner": "Profile Banner",
"settings.profile_tab": "Profile",
"settings.radii_help": "Set up interface edge rounding (in pixels)",
"settings.replies_in_timeline": "Replies in timeline",
"settings.reply_link_preview": "Enable reply-link preview on mouse hover",
"settings.reply_visibility_all": "Show all replies",
"settings.reply_visibility_following": "Only show replies directed at me or users I'm following",
"settings.reply_visibility_self": "Only show replies directed at me",
"settings.saving_err": "Error saving settings",
"settings.saving_ok": "Settings saved",
"settings.security_tab": "Security",
"settings.scope_copy": "Copy scope when replying (DMs are always copied)",
"settings.set_new_avatar": "Set new avatar",
"settings.set_new_profile_background": "Set new profile background",
"settings.set_new_profile_banner": "Set new profile banner",
"settings.settings": "Settings",
"settings.subject_input_always_show": "Always show subject field",
"settings.subject_line_behavior": "Copy subject when replying",
"settings.subject_line_email": "Like email: \"re: subject\"",
"settings.subject_line_mastodon": "Like mastodon: copy as is",
"settings.subject_line_noop": "Do not copy",
"settings.post_status_content_type": "Post status content type",
"settings.stop_gifs": "Play-on-hover GIFs",
"settings.streaming": "Enable automatic streaming of new posts when scrolled to the top",
"settings.text": "Text",
"settings.theme": "Theme",
"settings.theme_help": "Use hex color codes (#rrggbb) to customize your color theme.",
"settings.theme_help_v2_1": "You can also override certain component's colors and opacity by toggling the checkbox, use \"Clear all\" button to clear all overrides.",
"settings.theme_help_v2_2": "Icons underneath some entries are background/text contrast indicators, hover over for detailed info. Please keep in mind that when using transparency contrast indicators show the worst possible case.",
"settings.tooltipRadius": "Tooltips/alerts",
"settings.upload_a_photo": "Upload a photo",
"settings.user_settings": "User Settings",
"settings.values.false": "no",
"settings.values.true": "yes",
"settings.notifications": "Notifications",
"settings.enable_web_push_notifications": "Enable web push notifications",
"settings.style.switcher.keep_color": "Keep colors",
"settings.style.switcher.keep_shadows": "Keep shadows",
"settings.style.switcher.keep_opacity": "Keep opacity",
"settings.style.switcher.keep_roundness": "Keep roundness",
"settings.style.switcher.keep_fonts": "Keep fonts",
"settings.style.switcher.save_load_hint": "\"Keep\" options preserve currently set options when selecting or loading themes, it also stores said options when exporting a theme. When all checkboxes unset, exporting theme will save everything.",
"settings.style.switcher.reset": "Reset",
"settings.style.switcher.clear_all": "Clear all",
"settings.style.switcher.clear_opacity": "Clear opacity",
"settings.style.common.color": "Color",
"settings.style.common.opacity": "Opacity",
"settings.style.common.contrast.hint": "Contrast ratio is {ratio}, it {level} {context}",
"settings.style.common.contrast.level.aa": "meets Level AA guideline (minimal)",
"settings.style.common.contrast.level.aaa": "meets Level AAA guideline (recommended)",
"settings.style.common.contrast.level.bad": "doesn't meet any accessibility guidelines",
"settings.style.common.contrast.context.18pt": "for large (18pt+) text",
"settings.style.common.contrast.context.text": "for text",
"settings.style.common_colors._tab_label": "Common",
"settings.style.common_colors.main": "Common colors",
"settings.style.common_colors.foreground_hint": "See \"Advanced\" tab for more detailed control",
"settings.style.common_colors.rgbo": "Icons, accents, badges",
"settings.style.advanced_colors._tab_label": "Advanced",
"settings.style.advanced_colors.alert": "Alert background",
"settings.style.advanced_colors.alert_error": "Error",
"settings.style.advanced_colors.badge": "Badge background",
"settings.style.advanced_colors.badge_notification": "Notification",
"settings.style.advanced_colors.panel_header": "Panel header",
"settings.style.advanced_colors.top_bar": "Top bar",
"settings.style.advanced_colors.borders": "Borders",
"settings.style.advanced_colors.buttons": "Buttons",
"settings.style.advanced_colors.inputs": "Input fields",
"settings.style.advanced_colors.faint_text": "Faded text",
"settings.style.radii._tab_label": "Roundness",
"settings.style.shadows._tab_label": "Shadow and lighting",
"settings.style.shadows.component": "Component",
"settings.style.shadows.override": "Override",
"settings.style.shadows.shadow_id": "Shadow #{value}",
"settings.style.shadows.blur": "Blur",
"settings.style.shadows.spread": "Spread",
"settings.style.shadows.inset": "Inset",
"settings.style.shadows.hint": "For shadows you can also use --variable as a color value to use CSS3 variables. Please note that setting opacity won't work in this case.",
"settings.style.shadows.filter_hint.always_drop_shadow": "Warning, this shadow always uses {0} when browser supports it.",
"settings.style.shadows.filter_hint.drop_shadow_syntax": "{0} does not support {1} parameter and {2} keyword.",
"settings.style.shadows.filter_hint.avatar_inset": "Please note that combining both inset and non-inset shadows on avatars might give unexpected results with transparent avatars.",
"settings.style.shadows.filter_hint.spread_zero": "Shadows with spread > 0 will appear as if it was set to zero",
"settings.style.shadows.filter_hint.inset_classic": "Inset shadows will be using {0}",
"settings.style.shadows.components.panel": "Panel",
"settings.style.shadows.components.panelHeader": "Panel header",
"settings.style.shadows.components.topBar": "Top bar",
"settings.style.shadows.components.avatar": "User avatar (in profile view)",
"settings.style.shadows.components.avatarStatus": "User avatar (in post display)",
"settings.style.shadows.components.popup": "Popups and tooltips",
"settings.style.shadows.components.button": "Button",
"settings.style.shadows.components.buttonHover": "Button (hover)",
"settings.style.shadows.components.buttonPressed": "Button (pressed)",
"settings.style.shadows.components.buttonPressedHover": "Button (pressed+hover)",
"settings.style.shadows.components.input": "Input field",
"settings.style.fonts._tab_label": "Fonts",
"settings.style.fonts.help": "Select font to use for elements of UI. For \"custom\" you have to enter exact font name as it appears in system.",
"settings.style.fonts.components.interface": "Interface",
"settings.style.fonts.components.input": "Input fields",
"settings.style.fonts.components.post": "Post text",
"settings.style.fonts.components.postCode": "Monospaced text in a post (rich text)",
"settings.style.fonts.family": "Font name",
"settings.style.fonts.size": "Size (in px)",
"settings.style.fonts.weight": "Weight (boldness)",
"settings.style.fonts.custom": "Custom",
"settings.style.preview.header": "Preview",
"settings.style.preview.content": "Content",
"settings.style.preview.error": "Example error",
"settings.style.preview.button": "Button",
"settings.style.preview.text": "A bunch of more {0} and {1}",
"settings.style.preview.mono": "content",
"settings.style.preview.input": "Just landed in L.A.",
"settings.style.preview.faint_link": "helpful manual",
"settings.style.preview.fine_print": "Read our {0} to learn nothing useful!",
"settings.style.preview.header_faint": "This is fine",
"settings.style.preview.checkbox": "I have skimmed over terms and conditions",
"settings.style.preview.link": "a nice lil' link",
"settings.version.title": "Version",
"settings.version.backend_version": "Backend Version",
"settings.version.frontend_version": "Frontend Version",
"timeline.collapse": "Collapse",
"timeline.conversation": "Conversation",
"timeline.error_fetching": "Error fetching updates",
"timeline.load_older": "Load older statuses",
"timeline.no_retweet_hint": "Post is marked as followers-only or direct and cannot be repeated",
"timeline.repeated": "repeated",
"timeline.show_new": "Show new",
"timeline.up_to_date": "Up-to-date",
"timeline.no_more_statuses": "No more statuses",
"timeline.no_statuses": "No statuses",
"status.reply_to": "Reply to",
"status.replies_list": "Replies:",
"user_card.approve": "Approve",
"user_card.block": "Block",
"user_card.blocked": "Blocked!",
"user_card.deny": "Deny",
"user_card.favorites": "Favorites",
"user_card.follow": "Follow",
"user_card.follow_sent": "Request sent!",
"user_card.follow_progress": "Requesting…",
"user_card.follow_again": "Send request again?",
"user_card.follow_unfollow": "Unfollow",
"user_card.followees": "Following",
"user_card.followers": "Followers",
"user_card.following": "Following!",
"user_card.follows_you": "Follows you!",
"user_card.its_you": "It's you!",
"user_card.media": "Media",
"user_card.mute": "Mute",
"user_card.muted": "Muted",
"user_card.per_day": "per day",
"user_card.remote_follow": "Remote follow",
"user_card.statuses": "Statuses",
"user_card.unblock": "Unblock",
"user_card.unblock_progress": "Unblocking...",
"user_card.block_progress": "Blocking...",
"user_card.unmute": "Unmute",
"user_card.unmute_progress": "Unmuting...",
"user_card.mute_progress": "Muting...",
"user_profile.timeline_title": "User Timeline",
"user_profile.profile_does_not_exist": "Sorry, this profile does not exist.",
"user_profile.profile_loading_error": "Sorry, there was an error loading this profile.",
"who_to_follow.more": "More",
"who_to_follow.who_to_follow": "Who to follow",
"tool_tip.media_upload": "Upload Media",
"tool_tip.repeat": "Repeat",
"tool_tip.reply": "Reply",
"tool_tip.favorite": "Favorite",
"tool_tip.user_settings": "User Settings",
"upload.error.base": "Upload failed.",
"upload.error.file_too_big": "File too big [{filesize}{filesizeunit} / {allowedsize}{allowedsizeunit}]",
"upload.error.default": "Try again later",
"upload.file_size_units.B": "B",
"upload.file_size_units.KiB": "KiB",
"upload.file_size_units.MiB": "MiB",
"upload.file_size_units.GiB": "GiB",
"upload.file_size_units.TiB": "TiB"
}

View file

@ -6,7 +6,6 @@ const api = {
backendInteractor: backendInteractorService(),
fetchers: {},
socket: null,
chatDisabled: false,
followRequests: []
},
mutations: {
@ -25,9 +24,6 @@ const api = {
setSocket (state, socket) {
state.socket = socket
},
setChatDisabled (state, value) {
state.chatDisabled = value
},
setFollowRequests (state, value) {
state.followRequests = value
}
@ -55,17 +51,20 @@ const api = {
setWsToken (store, token) {
store.commit('setWsToken', token)
},
initializeSocket (store) {
initializeSocket ({ dispatch, commit, state, rootState }) {
// Set up websocket connection
if (!store.state.chatDisabled) {
const token = store.state.wsToken
const token = state.wsToken
if (rootState.instance.chatAvailable && typeof token !== 'undefined' && state.socket === null) {
const socket = new Socket('/socket', { params: { token } })
socket.connect()
store.dispatch('initializeChat', socket)
commit('setSocket', socket)
dispatch('initializeChat', socket)
}
},
disableChat (store) {
store.commit('setChatDisabled', true)
disconnectFromSocket ({ commit, state }) {
state.socket && state.socket.disconnect()
commit('setSocket', null)
},
removeFollowRequest (store, request) {
let requests = store.state.followRequests.filter((it) => it !== request)

View file

@ -1,16 +1,12 @@
const chat = {
state: {
messages: [],
channel: { state: '' },
socket: null
channel: { state: '' }
},
mutations: {
setChannel (state, channel) {
state.channel = channel
},
setSocket (state, socket) {
state.socket = socket
},
addMessage (state, message) {
state.messages.push(message)
state.messages = state.messages.slice(-19, 20)
@ -20,12 +16,8 @@ const chat = {
}
},
actions: {
disconnectFromChat (store) {
store.state.socket && store.state.socket.disconnect()
},
initializeChat (store, socket) {
const channel = socket.channel('chat:public')
store.commit('setSocket', socket)
channel.on('new_msg', (msg) => {
store.commit('addMessage', msg)
})

View file

@ -79,6 +79,11 @@ const instance = {
case 'name':
dispatch('setPageTitle')
break
case 'chatAvailable':
if (value) {
dispatch('initializeSocket')
}
break
}
},
setTheme ({ commit }, themeName) {

View file

@ -426,9 +426,9 @@ export const mutations = {
newStatus.favoritedBy.push(user)
}
},
setPinned (state, status) {
setMuted (state, status) {
const newStatus = state.allStatusesObject[status.id]
newStatus.pinned = status.pinned
newStatus.muted = status.muted
},
setRetweeted (state, { status, value }) {
const newStatus = state.allStatusesObject[status.id]
@ -556,13 +556,21 @@ const statuses = {
rootState.api.backendInteractor.fetchPinnedStatuses(userId)
.then(statuses => dispatch('addNewStatuses', { statuses, timeline: 'user', userId, showImmediately: true, noIdUpdate: true }))
},
pinStatus ({ rootState, commit }, statusId) {
pinStatus ({ rootState, dispatch }, statusId) {
return rootState.api.backendInteractor.pinOwnStatus(statusId)
.then((status) => commit('setPinned', status))
.then((status) => dispatch('addNewStatuses', { statuses: [status] }))
},
unpinStatus ({ rootState, commit }, statusId) {
unpinStatus ({ rootState, dispatch }, statusId) {
rootState.api.backendInteractor.unpinOwnStatus(statusId)
.then((status) => commit('setPinned', status))
.then((status) => dispatch('addNewStatuses', { statuses: [status] }))
},
muteConversation ({ rootState, commit }, statusId) {
return rootState.api.backendInteractor.muteConversation(statusId)
.then((status) => commit('setMuted', status))
},
unmuteConversation ({ rootState, commit }, statusId) {
return rootState.api.backendInteractor.unmuteConversation(statusId)
.then((status) => commit('setMuted', status))
},
retweet ({ rootState, commit }, status) {
// Optimistic retweeting...

View file

@ -3,7 +3,6 @@ import oauthApi from '../services/new_api/oauth.js'
import { compact, map, each, merge, last, concat, uniq } from 'lodash'
import { set } from 'vue'
import { registerPushNotifications, unregisterPushNotifications } from '../services/push/push.js'
import { humanizeErrors } from './errors'
// TODO: Unify with mergeOrAdd in statuses.js
export const mergeOrAdd = (arr, obj, item) => {
@ -165,13 +164,13 @@ export const mutations = {
state.currentUser.muteIds.push(muteId)
}
},
setPinned (state, status) {
setPinnedToUser (state, status) {
const user = state.usersObject[status.user.id]
const index = user.pinnedStatuseIds.indexOf(status.id)
const index = user.pinnedStatusIds.indexOf(status.id)
if (status.pinned && index === -1) {
user.pinnedStatuseIds.push(status.id)
user.pinnedStatusIds.push(status.id)
} else if (!status.pinned && index !== -1) {
user.pinnedStatuseIds.splice(index, 1)
user.pinnedStatusIds.splice(index, 1)
}
},
setUserForStatus (state, status) {
@ -339,13 +338,13 @@ const users = {
// Reconnect users to statuses
store.commit('setUserForStatus', status)
// Set pinned statuses to user
store.commit('setPinned', status)
store.commit('setPinnedToUser', status)
})
each(compact(map(statuses, 'retweeted_status')), (status) => {
// Reconnect users to retweets
store.commit('setUserForStatus', status)
// Set pinned retweets to user
store.commit('setPinned', status)
store.commit('setPinnedToUser', status)
})
},
addNewNotifications (store, { notifications }) {
@ -382,16 +381,8 @@ const users = {
store.dispatch('loginUser', data.access_token)
} catch (e) {
let errors = e.message
// replace ap_id with username
if (typeof errors === 'object') {
if (errors.ap_id) {
errors.username = errors.ap_id
delete errors.ap_id
}
errors = humanizeErrors(errors)
}
store.commit('signUpFailure', errors)
throw Error(errors)
throw e
}
},
async getCaptcha (store) {
@ -419,7 +410,7 @@ const users = {
})
.then(() => {
store.commit('clearCurrentUser')
store.dispatch('disconnectFromChat')
store.dispatch('disconnectFromSocket')
store.commit('clearToken')
store.dispatch('stopFetching', 'friends')
store.commit('setBackendInteractor', backendInteractorService(store.getters.getToken()))

View file

@ -1,18 +1,14 @@
import { each, map, concat, last } from 'lodash'
import { parseStatus, parseUser, parseNotification, parseAttachment } from '../entity_normalizer/entity_normalizer.service.js'
import 'whatwg-fetch'
import { StatusCodeError } from '../errors/errors'
import { RegistrationError, StatusCodeError } from '../errors/errors'
/* eslint-env browser */
const EXTERNAL_PROFILE_URL = '/api/externalprofile/show.json'
const QVITTER_USER_NOTIFICATIONS_READ_URL = '/api/qvitter/statuses/notifications/read.json'
const BLOCKS_IMPORT_URL = '/api/pleroma/blocks_import'
const FOLLOW_IMPORT_URL = '/api/pleroma/follow_import'
const DELETE_ACCOUNT_URL = '/api/pleroma/delete_account'
const CHANGE_PASSWORD_URL = '/api/pleroma/change_password'
const FOLLOW_REQUESTS_URL = '/api/pleroma/friend_requests'
const APPROVE_USER_URL = '/api/pleroma/friendships/approve'
const DENY_USER_URL = '/api/pleroma/friendships/deny'
const TAG_USER_URL = '/api/pleroma/admin/users/tag'
const PERMISSION_GROUP_URL = (screenName, right) => `/api/pleroma/admin/users/${screenName}/permission_group/${right}`
const ACTIVATION_STATUS_URL = screenName => `/api/pleroma/admin/users/${screenName}/activation_status`
@ -40,6 +36,9 @@ const MASTODON_FOLLOW_URL = id => `/api/v1/accounts/${id}/follow`
const MASTODON_UNFOLLOW_URL = id => `/api/v1/accounts/${id}/unfollow`
const MASTODON_FOLLOWING_URL = id => `/api/v1/accounts/${id}/following`
const MASTODON_FOLLOWERS_URL = id => `/api/v1/accounts/${id}/followers`
const MASTODON_FOLLOW_REQUESTS_URL = '/api/v1/follow_requests'
const MASTODON_APPROVE_USER_URL = id => `/api/v1/follow_requests/${id}/authorize`
const MASTODON_DENY_USER_URL = id => `/api/v1/follow_requests/${id}/reject`
const MASTODON_DIRECT_MESSAGES_TIMELINE_URL = '/api/v1/timelines/direct'
const MASTODON_PUBLIC_TIMELINE = '/api/v1/timelines/public'
const MASTODON_USER_HOME_TIMELINE_URL = '/api/v1/timelines/home'
@ -67,6 +66,8 @@ const MASTODON_PROFILE_UPDATE_URL = '/api/v1/accounts/update_credentials'
const MASTODON_REPORT_USER_URL = '/api/v1/reports'
const MASTODON_PIN_OWN_STATUS = id => `/api/v1/statuses/${id}/pin`
const MASTODON_UNPIN_OWN_STATUS = id => `/api/v1/statuses/${id}/unpin`
const MASTODON_MUTE_CONVERSATION = id => `/api/v1/statuses/${id}/mute`
const MASTODON_UNMUTE_CONVERSATION = id => `/api/v1/statuses/${id}/unmute`
const MASTODON_SEARCH_2 = `/api/v2/search`
const MASTODON_USER_SEARCH_URL = '/api/v1/accounts/search'
@ -199,12 +200,11 @@ const register = ({ params, credentials }) => {
...rest
})
})
.then((response) => [response.ok, response])
.then(([ok, response]) => {
if (ok) {
.then((response) => {
if (response.ok) {
return response.json()
} else {
return response.json().then((error) => { throw new Error(error) })
return response.json().then((error) => { throw new RegistrationError(error) })
}
})
}
@ -219,14 +219,6 @@ const authHeaders = (accessToken) => {
}
}
const externalProfile = ({ profileUrl, credentials }) => {
let url = `${EXTERNAL_PROFILE_URL}?profileurl=${profileUrl}`
return fetch(url, {
headers: authHeaders(credentials),
method: 'GET'
}).then((data) => data.json())
}
const followUser = ({ id, credentials }) => {
let url = MASTODON_FOLLOW_URL(id)
return fetch(url, {
@ -253,6 +245,16 @@ const unpinOwnStatus = ({ id, credentials }) => {
.then((data) => parseStatus(data))
}
const muteConversation = ({ id, credentials }) => {
return promisedRequest({ url: MASTODON_MUTE_CONVERSATION(id), credentials, method: 'POST' })
.then((data) => parseStatus(data))
}
const unmuteConversation = ({ id, credentials }) => {
return promisedRequest({ url: MASTODON_UNMUTE_CONVERSATION(id), credentials, method: 'POST' })
.then((data) => parseStatus(data))
}
const blockUser = ({ id, credentials }) => {
return fetch(MASTODON_BLOCK_USER_URL(id), {
headers: authHeaders(credentials),
@ -268,7 +270,7 @@ const unblockUser = ({ id, credentials }) => {
}
const approveUser = ({ id, credentials }) => {
let url = `${APPROVE_USER_URL}?user_id=${id}`
let url = MASTODON_APPROVE_USER_URL(id)
return fetch(url, {
headers: authHeaders(credentials),
method: 'POST'
@ -276,7 +278,7 @@ const approveUser = ({ id, credentials }) => {
}
const denyUser = ({ id, credentials }) => {
let url = `${DENY_USER_URL}?user_id=${id}`
let url = MASTODON_DENY_USER_URL(id)
return fetch(url, {
headers: authHeaders(credentials),
method: 'POST'
@ -352,9 +354,10 @@ const fetchFollowers = ({ id, maxId, sinceId, limit = 20, credentials }) => {
}
const fetchFollowRequests = ({ credentials }) => {
const url = FOLLOW_REQUESTS_URL
const url = MASTODON_FOLLOW_REQUESTS_URL
return fetch(url, { headers: authHeaders(credentials) })
.then((data) => data.json())
.then((data) => data.map(parseUser))
}
const fetchConversation = ({ id, credentials }) => {
@ -921,6 +924,8 @@ const apiService = {
unfollowUser,
pinOwnStatus,
unpinOwnStatus,
muteConversation,
unmuteConversation,
blockUser,
unblockUser,
fetchUser,
@ -952,7 +957,6 @@ const apiService = {
updateBg,
updateProfile,
updateBanner,
externalProfile,
importBlocks,
importFollows,
deleteAccount,

View file

@ -117,6 +117,8 @@ const backendInteractorService = credentials => {
const fetchPinnedStatuses = (id) => apiService.fetchPinnedStatuses({ credentials, id })
const pinOwnStatus = (id) => apiService.pinOwnStatus({ credentials, id })
const unpinOwnStatus = (id) => apiService.unpinOwnStatus({ credentials, id })
const muteConversation = (id) => apiService.muteConversation({ credentials, id })
const unmuteConversation = (id) => apiService.unmuteConversation({ credentials, id })
const getCaptcha = () => apiService.getCaptcha()
const register = (params) => apiService.register({ credentials, params })
@ -125,8 +127,6 @@ const backendInteractorService = credentials => {
const updateBanner = ({ banner }) => apiService.updateBanner({ credentials, banner })
const updateProfile = ({ params }) => apiService.updateProfile({ credentials, params })
const externalProfile = (profileUrl) => apiService.externalProfile({ profileUrl, credentials })
const importBlocks = (file) => apiService.importBlocks({ file, credentials })
const importFollows = (file) => apiService.importFollows({ file, credentials })
@ -178,6 +178,8 @@ const backendInteractorService = credentials => {
fetchPinnedStatuses,
pinOwnStatus,
unpinOwnStatus,
muteConversation,
unmuteConversation,
tagUser,
untagUser,
addRight,
@ -190,7 +192,6 @@ const backendInteractorService = credentials => {
updateBg,
updateBanner,
updateProfile,
externalProfile,
importBlocks,
importFollows,
deleteAccount,

View file

@ -65,6 +65,7 @@ export const parseUser = (data) => {
if (relationship) {
output.follows_you = relationship.followed_by
output.requested = relationship.requested
output.following = relationship.following
output.statusnet_blocking = relationship.blocking
output.muted = relationship.muting
@ -152,7 +153,7 @@ export const parseUser = (data) => {
output.statuses_count = data.statuses_count
output.friendIds = []
output.followerIds = []
output.pinnedStatuseIds = []
output.pinnedStatusIds = []
if (data.pleroma) {
output.follow_request_count = data.pleroma.follow_request_count
@ -240,6 +241,7 @@ export const parseStatus = (data) => {
output.external_url = data.url
output.poll = data.poll
output.pinned = data.pinned
output.muted = data.muted
} else {
output.favorited = data.favorited
output.fave_num = data.fave_num

View file

@ -1,3 +1,5 @@
import { humanizeErrors } from '../../modules/errors'
export function StatusCodeError (statusCode, body, options, response) {
this.name = 'StatusCodeError'
this.statusCode = statusCode
@ -12,3 +14,36 @@ export function StatusCodeError (statusCode, body, options, response) {
}
StatusCodeError.prototype = Object.create(Error.prototype)
StatusCodeError.prototype.constructor = StatusCodeError
export class RegistrationError extends Error {
constructor (error) {
super()
if (Error.captureStackTrace) {
Error.captureStackTrace(this)
}
try {
// the error is probably a JSON object with a single key, "errors", whose value is another JSON object containing the real errors
if (typeof error === 'string') {
error = JSON.parse(error)
if (error.hasOwnProperty('error')) {
error = JSON.parse(error.error)
}
}
if (typeof error === 'object') {
// replace ap_id with username
if (error.ap_id) {
error.username = error.ap_id
delete error.ap_id
}
this.message = humanizeErrors(error)
} else {
this.message = error
}
} catch (e) {
// can't parse it, so just treat it like a string
this.message = error
}
}
}

View file

@ -2,17 +2,17 @@ const fetchUser = (attempt, user, store) => new Promise((resolve, reject) => {
setTimeout(() => {
store.state.api.backendInteractor.fetchUser({ id: user.id })
.then((user) => store.commit('addNewUsers', [user]))
.then(() => resolve([user.following, attempt]))
.then(() => resolve([user.following, user.requested, user.locked, attempt]))
.catch((e) => reject(e))
}, 500)
}).then(([following, attempt]) => {
if (!following && attempt <= 3) {
}).then(([following, sent, locked, attempt]) => {
if (!following && !(locked && sent) && attempt <= 3) {
// If we BE reports that we still not following that user - retry,
// increment attempts by one
return fetchUser(++attempt, user, store)
} else {
// If we run out of attempts, just return whatever status is.
return following
return sent
}
})
@ -21,14 +21,10 @@ export const requestFollow = (user, store) => new Promise((resolve, reject) => {
.then((updated) => {
store.commit('updateUserRelationship', [updated])
// For locked users we just mark it that we sent the follow request
if (updated.locked) {
resolve({ sent: true })
}
if (updated.following) {
// If we get result immediately, just stop.
resolve({ sent: false })
if (updated.following || (user.locked && user.requested)) {
// If we get result immediately or the account is locked, just stop.
resolve({ sent: updated.requested })
return
}
// But usually we don't get result immediately, so we ask server
@ -39,14 +35,8 @@ export const requestFollow = (user, store) => new Promise((resolve, reject) => {
// Recursive Promise, it will call itself up to 3 times.
return fetchUser(1, user, store)
.then((following) => {
if (following) {
// We confirmed and everything's good.
resolve({ sent: false })
} else {
// If after all the tries, just treat it as if user is locked
resolve({ sent: false })
}
.then((sent) => {
resolve({ sent })
})
})
})

View file

@ -0,0 +1,18 @@
import { reduce } from 'lodash'
const MASTODON_PASSWORD_RESET_URL = `/auth/password`
const resetPassword = ({ instance, email }) => {
const params = { email }
const query = reduce(params, (acc, v, k) => {
const encoded = `${k}=${encodeURIComponent(v)}`
return `${acc}&${encoded}`
}, '')
const url = `${instance}${MASTODON_PASSWORD_RESET_URL}?${query}`
return window.fetch(url, {
method: 'POST'
})
}
export default resetPassword

View file

@ -6,7 +6,6 @@
"logoMargin": ".1em",
"redirectRootNoLogin": "/main/all",
"redirectRootLogin": "/main/friends",
"chatDisabled": false,
"showInstanceSpecificPanel": false,
"collapseMessageWithSubject": false,
"scopeCopy": true,

View file

@ -291,6 +291,12 @@
"search": [
"bell-ringing-o"
]
},
{
"uid": "0b2b66e526028a6972d51a6f10281b4b",
"css": "zoom-in",
"code": 59420,
"src": "fontawesome"
}
]
}

View file

@ -27,6 +27,7 @@
.icon-pin:before { content: '\e819'; } /* '' */
.icon-wrench:before { content: '\e81a'; } /* '' */
.icon-chart-bar:before { content: '\e81b'; } /* '' */
.icon-zoom-in:before { content: '\e81c'; } /* '' */
.icon-spin3:before { content: '\e832'; } /* '' */
.icon-spin4:before { content: '\e834'; } /* '' */
.icon-link-ext:before { content: '\f08e'; } /* '' */

View file

@ -27,6 +27,7 @@
.icon-pin { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe819;&nbsp;'); }
.icon-wrench { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe81a;&nbsp;'); }
.icon-chart-bar { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe81b;&nbsp;'); }
.icon-zoom-in { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe81c;&nbsp;'); }
.icon-spin3 { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe832;&nbsp;'); }
.icon-spin4 { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe834;&nbsp;'); }
.icon-link-ext { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xf08e;&nbsp;'); }

View file

@ -38,6 +38,7 @@
.icon-pin { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe819;&nbsp;'); }
.icon-wrench { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe81a;&nbsp;'); }
.icon-chart-bar { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe81b;&nbsp;'); }
.icon-zoom-in { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe81c;&nbsp;'); }
.icon-spin3 { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe832;&nbsp;'); }
.icon-spin4 { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe834;&nbsp;'); }
.icon-link-ext { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xf08e;&nbsp;'); }

View file

@ -62,6 +62,8 @@
<glyph glyph-name="chart-bar" unicode="&#xe81b;" d="M357 357v-286h-143v286h143z m214 286v-572h-142v572h142z m572-643v-72h-1143v858h71v-786h1072z m-357 500v-429h-143v429h143z m214 214v-643h-143v643h143z" horiz-adv-x="1142.9" />
<glyph glyph-name="zoom-in" unicode="&#xe81c;" d="M571 411v-36q0-7-5-13t-12-5h-125v-125q0-7-6-13t-12-5h-36q-7 0-13 5t-5 13v125h-125q-7 0-12 5t-6 13v36q0 7 6 12t12 5h125v125q0 8 5 13t13 5h36q7 0 12-5t6-13v-125h125q7 0 12-5t5-12z m72-18q0 103-73 176t-177 74-177-74-73-176 73-177 177-73 177 73 73 177z m286-465q0-29-21-50t-51-21q-30 0-50 21l-191 191q-100-69-223-69-80 0-153 31t-125 84-84 125-31 153 31 152 84 126 125 84 153 31 153-31 125-84 84-126 31-152q0-123-69-223l191-191q21-21 21-51z" horiz-adv-x="928.6" />
<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" />

Before

Width:  |  Height:  |  Size: 20 KiB

After

Width:  |  Height:  |  Size: 21 KiB

View file

@ -0,0 +1,27 @@
import { getExcludedStatusIdsByPinning } from 'src/components/timeline/timeline.js'
describe('Timeline', () => {
describe('getExcludedStatusIdsByPinning', () => {
const mockStatuses = (ids) => ids.map(id => ({ id }))
it('should return only members of both pinnedStatusIds and ids of the given statuses', () => {
const statusIds = [1, 2, 3, 4]
const statuses = mockStatuses(statusIds)
const pinnedStatusIds = [1, 3, 5]
const result = getExcludedStatusIdsByPinning(statuses, pinnedStatusIds)
result.forEach(item => {
expect(item).to.be.oneOf(statusIds)
expect(item).to.be.oneOf(pinnedStatusIds)
})
})
it('should return ids of pinned statuses not posted before any unpinned status', () => {
const pinnedStatusIdSet1 = ['PINNED1', 'PINNED2']
const pinnedStatusIdSet2 = ['PINNED3', 'PINNED4']
const pinnedStatusIds = [...pinnedStatusIdSet1, ...pinnedStatusIdSet2]
const statusIds = [...pinnedStatusIdSet1, 'UNPINNED1', ...pinnedStatusIdSet2]
const statuses = mockStatuses(statusIds)
expect(getExcludedStatusIdsByPinning(statuses, pinnedStatusIds)).to.eql(pinnedStatusIdSet1)
})
})
})

View file

@ -1387,10 +1387,29 @@ cache-base@^1.0.1:
union-value "^1.0.0"
unset-value "^1.0.0"
caller-callsite@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/caller-callsite/-/caller-callsite-2.0.0.tgz#847e0fce0a223750a9a027c54b33731ad3154134"
integrity sha1-hH4PzgoiN1CpoCfFSzNzGtMVQTQ=
dependencies:
callsites "^2.0.0"
caller-path@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/caller-path/-/caller-path-2.0.0.tgz#468f83044e369ab2010fac5f06ceee15bb2cb1f4"
integrity sha1-Ro+DBE42mrIBD6xfBs7uFbsssfQ=
dependencies:
caller-callsite "^2.0.0"
callsite@1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/callsite/-/callsite-1.0.0.tgz#280398e5d664bd74038b6f0905153e6e8af1bc20"
callsites@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/callsites/-/callsites-2.0.0.tgz#06eb84f00eea413da86affefacbffb36093b3c50"
integrity sha1-BuuE8A7qQT2oav/vrL/7Ngk7PFA=
callsites@^3.0.0:
version "3.1.0"
resolved "https://registry.yarnpkg.com/callsites/-/callsites-3.1.0.tgz#b3630abd8943432f54b3f0519238e33cd7df2f73"
@ -1817,6 +1836,16 @@ cosmiconfig@^2.1.0, cosmiconfig@^2.1.1:
parse-json "^2.2.0"
require-from-string "^1.1.0"
cosmiconfig@^5.0.0:
version "5.2.1"
resolved "https://registry.yarnpkg.com/cosmiconfig/-/cosmiconfig-5.2.1.tgz#040f726809c591e77a17c0a3626ca45b4f168b1a"
integrity sha512-H65gsXo1SKjf8zmrJ67eJk8aIRKV5ff2D4uKZIBZShbhGSpEmsQOPW/SKMKYhSTrqR7ufy6RP69rPogdaPh/kA==
dependencies:
import-fresh "^2.0.0"
is-directory "^0.3.1"
js-yaml "^3.13.1"
parse-json "^4.0.0"
create-ecdh@^4.0.0:
version "4.0.3"
resolved "https://registry.yarnpkg.com/create-ecdh/-/create-ecdh-4.0.3.tgz#c9111b6f33045c4697f144787f9254cdc77c45ff"
@ -2390,7 +2419,7 @@ errno@^0.1.3, errno@~0.1.7:
dependencies:
prr "~1.0.1"
error-ex@^1.2.0:
error-ex@^1.2.0, error-ex@^1.3.1:
version "1.3.2"
resolved "https://registry.yarnpkg.com/error-ex/-/error-ex-1.3.2.tgz#b4ac40648107fdcdcfae242f428bea8a14d4f1bf"
dependencies:
@ -3503,6 +3532,21 @@ immediate@~3.0.5:
version "3.0.6"
resolved "https://registry.yarnpkg.com/immediate/-/immediate-3.0.6.tgz#9db1dbd0faf8de6fbe0f5dd5e56bb606280de69b"
import-cwd@^2.0.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/import-cwd/-/import-cwd-2.1.0.tgz#aa6cf36e722761285cb371ec6519f53e2435b0a9"
integrity sha1-qmzzbnInYShcs3HsZRn1PiQ1sKk=
dependencies:
import-from "^2.1.0"
import-fresh@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/import-fresh/-/import-fresh-2.0.0.tgz#d81355c15612d386c61f9ddd3922d4304822a546"
integrity sha1-2BNVwVYS04bGH53dOSLUMEgipUY=
dependencies:
caller-path "^2.0.0"
resolve-from "^3.0.0"
import-fresh@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/import-fresh/-/import-fresh-3.0.0.tgz#a3d897f420cab0e671236897f75bc14b4885c390"
@ -3510,6 +3554,13 @@ import-fresh@^3.0.0:
parent-module "^1.0.0"
resolve-from "^4.0.0"
import-from@^2.1.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/import-from/-/import-from-2.1.0.tgz#335db7f2a7affd53aaa471d4b8021dee36b7f3b1"
integrity sha1-M1238qev/VOqpHHUuAId7ja387E=
dependencies:
resolve-from "^3.0.0"
imurmurhash@^0.1.4:
version "0.1.4"
resolved "https://registry.yarnpkg.com/imurmurhash/-/imurmurhash-0.1.4.tgz#9218b9b2b928a238b13dc4fb6b6d576f231453ea"
@ -3949,7 +4000,7 @@ js-yaml@3.x, js-yaml@^3.4.3:
argparse "^1.0.7"
esprima "^4.0.0"
js-yaml@^3.13.0:
js-yaml@^3.13.0, js-yaml@^3.13.1:
version "3.13.1"
resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.13.1.tgz#aff151b30bfdfa8e49e05da22e7415e9dfa37847"
dependencies:
@ -3979,7 +4030,7 @@ json-loader@^0.5.4:
version "0.5.7"
resolved "https://registry.yarnpkg.com/json-loader/-/json-loader-0.5.7.tgz#dca14a70235ff82f0ac9a3abeb60d337a365185d"
json-parse-better-errors@^1.0.2:
json-parse-better-errors@^1.0.1, json-parse-better-errors@^1.0.2:
version "1.0.2"
resolved "https://registry.yarnpkg.com/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz#bb867cfb3450e69107c131d1c514bab3dc8bcaa9"
@ -5315,6 +5366,14 @@ parse-json@^2.2.0:
dependencies:
error-ex "^1.2.0"
parse-json@^4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/parse-json/-/parse-json-4.0.0.tgz#be35f5425be1f7f6c747184f98a788cb99477ee0"
integrity sha1-vjX1Qlvh9/bHRxhPmKeIy5lHfuA=
dependencies:
error-ex "^1.3.1"
json-parse-better-errors "^1.0.1"
parseqs@0.0.5:
version "0.0.5"
resolved "https://registry.yarnpkg.com/parseqs/-/parseqs-0.0.5.tgz#d5208a3738e46766e291ba2ea173684921a8b89d"
@ -5541,6 +5600,14 @@ postcss-load-config@^1.1.0:
postcss-load-options "^1.2.0"
postcss-load-plugins "^2.3.0"
postcss-load-config@^2.0.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/postcss-load-config/-/postcss-load-config-2.1.0.tgz#c84d692b7bb7b41ddced94ee62e8ab31b417b003"
integrity sha512-4pV3JJVPLd5+RueiVVB+gFOAa7GWc25XQcMp86Zexzke69mKf6Nx9LRcQywdz7yZI9n1udOxmLuAwTBypypF8Q==
dependencies:
cosmiconfig "^5.0.0"
import-cwd "^2.0.0"
postcss-load-options@^1.2.0:
version "1.2.0"
resolved "https://registry.yarnpkg.com/postcss-load-options/-/postcss-load-options-1.2.0.tgz#b098b1559ddac2df04bc0bb375f99a5cfe2b6d8c"
@ -5555,6 +5622,16 @@ postcss-load-plugins@^2.3.0:
cosmiconfig "^2.1.1"
object-assign "^4.1.0"
postcss-loader@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/postcss-loader/-/postcss-loader-3.0.0.tgz#6b97943e47c72d845fa9e03f273773d4e8dd6c2d"
integrity sha512-cLWoDEY5OwHcAjDnkyRQzAXfs2jrKjXpO/HQFcc5b5u/r7aa471wdmChmwfnv7x2u840iat/wi0lQ5nbRgSkUA==
dependencies:
loader-utils "^1.1.0"
postcss "^7.0.0"
postcss-load-config "^2.0.0"
schema-utils "^1.0.0"
postcss-merge-idents@^2.1.5:
version "2.1.7"
resolved "https://registry.yarnpkg.com/postcss-merge-idents/-/postcss-merge-idents-2.1.7.tgz#4c5530313c08e1d5b3bbf3d2bbc747e278eea270"
@ -5740,6 +5817,15 @@ postcss@^6.0.1, postcss@^6.0.8:
source-map "^0.6.1"
supports-color "^5.4.0"
postcss@^7.0.0:
version "7.0.17"
resolved "https://registry.yarnpkg.com/postcss/-/postcss-7.0.17.tgz#4da1bdff5322d4a0acaab4d87f3e782436bad31f"
integrity sha512-546ZowA+KZ3OasvQZHsbuEpysvwTZNGJv9EfyCQdsIDltPSWHAeTQ5fQy/Npi2ZDtLI3zs7Ps/p6wThErhm9fQ==
dependencies:
chalk "^2.4.2"
source-map "^0.6.1"
supports-color "^6.1.0"
postcss@^7.0.5:
version "7.0.8"
resolved "https://registry.yarnpkg.com/postcss/-/postcss-7.0.8.tgz#2a3c5f2bdd00240cd0d0901fd998347c93d36696"
@ -6203,6 +6289,11 @@ requires-port@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/requires-port/-/requires-port-1.0.0.tgz#925d2601d39ac485e091cf0da5c6e694dc3dcaff"
resolve-from@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-3.0.0.tgz#b22c7af7d9d6881bc8b6e653335eebcb0a188748"
integrity sha1-six699nWiBvItuZTM17rywoYh0g=
resolve-from@^4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-4.0.0.tgz#4abcd852ad32dd7baabfe9b40e00a36db5f392e6"
@ -6844,7 +6935,7 @@ supports-color@^5.3.0, supports-color@^5.4.0:
dependencies:
has-flag "^3.0.0"
supports-color@^6.0.0:
supports-color@^6.0.0, supports-color@^6.1.0:
version "6.1.0"
resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-6.1.0.tgz#0764abc69c63d5ac842dd4867e8d025e880df8f3"
dependencies: