Compare commits

...

179 Commits

Author SHA1 Message Date
Shpuld Shpludson 7e9c8c3d21 Merge branch 'develop' into 'master'
Update master

See merge request pleroma/pleroma-fe!646
2019-03-07 15:11:11 +00:00
Shpuld Shpludson c44f0a9bde Merge branch 'fix/fetch-activities-by-last-id' into 'develop'
#406 Fetch activities by last id

See merge request pleroma/pleroma-fe!621
2019-03-07 05:06:43 +00:00
Shpuld Shpludson 423e1b3b12 Merge branch 'i18n-cs' into 'develop'
I18n: Add Czech translation

See merge request pleroma/pleroma-fe!627
2019-03-06 20:23:46 +00:00
Shpuld Shpludson de46ede8fd Merge branch 'issue-346-auto-post' into 'develop'
#346: Hyperlink is not included

Closes #346

See merge request pleroma/pleroma-fe!626
2019-03-06 20:22:12 +00:00
Shpuld Shpludson 830f3762ef Merge branch 'emoji-limit' into 'develop'
compatibility with upcoming changes

See merge request pleroma/pleroma-fe!643
2019-03-06 05:11:07 +00:00
Henry Jameson 68b2d9ef56 compatibility with upcoming changes 2019-03-05 20:15:18 +02:00
Shpuld Shpludson 216073eb58 Merge branch 'fix/gallery-and-attachment-spacing' into 'develop'
Unify spacing between gallery/link preview/attachment components

See merge request pleroma/pleroma-fe!638
2019-03-05 14:40:20 +00:00
Shpuld Shpludson 8cda230fba Merge branch '419-avatar-cropper-png' into 'develop'
Generate cropped avatar image in the original file type

Closes #419

See merge request pleroma/pleroma-fe!640
2019-03-05 14:40:08 +00:00
taehoon ff9e55ae42 Generate cropped avatar image in the original file type 2019-03-04 21:22:32 -05:00
Shpuld Shpludson 3e4d465eba Merge branch 'patch-2' into 'develop'
Update of the Occitan file.

See merge request pleroma/pleroma-fe!635
2019-03-04 19:27:38 +00:00
Exilat 5a273d528f Update oc.json 2019-03-04 18:53:16 +00:00
Shpuld Shpludson 4ab3e29877 Merge branch 'develop' into 'develop'
Portuguese translation update

See merge request pleroma/pleroma-fe!637
2019-03-04 18:21:14 +00:00
shpuld bd0485ad2f unify spacing between gallery/link preview/attachment components 2019-03-04 18:56:47 +02:00
Shpuld Shpludson d6e9ad3b61 Merge branch 'develop' into 'develop'
Update and complete the Esperanto translation

See merge request pleroma/pleroma-fe!630
2019-03-04 16:11:17 +00:00
Shpuld Shpludson 893767b780 Merge branch 'issue-417-profile-tab' into 'develop'
Issue 417 profile tab

Closes #417

See merge request pleroma/pleroma-fe!634
2019-03-04 16:09:23 +00:00
aaabulafiaaa a2a3bda1f6 more pt strings 2019-03-04 08:36:35 -03:00
aaabulafiaaa 116a51e949 more pt strings 2019-03-03 19:42:02 -03:00
aaabulafiaaa ae1a9a8626 More strings 2019-03-03 19:03:11 -03:00
rondnelly assis 793abed7ed Translating more strings 2019-03-03 21:55:16 +00:00
Shpuld Shpludson 42d36fc98b Merge branch 'issue-418-notification-timeago' into 'develop'
Issue 418 notification timeago

Closes #418

See merge request pleroma/pleroma-fe!636
2019-03-03 20:16:51 +00:00
dave 0ea9e4ca14 #418: update timeago margin to align with icons 2019-03-03 14:15:53 -05:00
dave f392668b73 #418: update notification timeago format 2019-03-03 14:11:38 -05:00
Exilat 3e16b5a2e0 Update of the Occitan file.
Not at 100% but more complete then what it was.
2019-03-03 19:06:32 +00:00
dave 5a0bb29f02 #417: reset tab from the outside 2019-03-03 13:38:48 -05:00
dave 3d30ad1dda #417: refresh tab on user profile only 2019-03-03 12:53:01 -05:00
dave 10711f9045 #417: reset tab status when active user changes 2019-03-03 12:15:55 -05:00
Shpuld Shpludson 3ed1eb5723 Merge branch 'hotfix-replies' into 'develop'
fix broken statuses

See merge request pleroma/pleroma-fe!632
2019-03-03 14:58:05 +00:00
shpuld c1ee7c028b Fix bug in replies 2019-03-03 16:43:41 +02:00
Henry Jameson 6841f516fc fix broken statuses 2019-03-02 20:39:04 +02:00
Shpuld Shpludson cef0306428 Merge branch '416-name-font-size' into 'develop'
Update font-size of username in UserCardContent component

Closes #416

See merge request pleroma/pleroma-fe!631
2019-03-02 18:05:29 +00:00
taehoon 94b0321c71 Update font-size of username in UserCardContent component 2019-03-02 12:58:17 -05:00
Aditoo17 93f5f94698 I18n: Add Czech translation 2019-03-02 18:25:49 +01:00
Tirifto a884bbb6a6 Update and complete the Esperanto translation 2019-03-02 17:50:59 +01:00
Shpuld Shpludson 11113dd7c4 Merge branch 'feature/improve-status-header-and-spacing' into 'develop'
Re-do status header a bit, add more consistent spacing to status

Closes #264

See merge request pleroma/pleroma-fe!617
2019-03-02 16:35:38 +00:00
Shpuld Shpludson c4f8426349 Re-do status header a bit, add more consistent spacing to status 2019-03-02 16:35:38 +00:00
Shpuld Shpludson 6f903016a4 Merge branch 'fix/fetching-error-by-tag' into 'develop'
#402 Fix fetching error by tag

See merge request pleroma/pleroma-fe!622
2019-03-02 15:48:54 +00:00
shpuld 068da3cf9f Fix JS error when no statuses returned 2019-03-02 14:57:41 +02:00
jasper 81e89fed3d Fetch activites by last id 2019-03-01 12:53:24 -08:00
jasper c26f32ed92 Fix fetching error by tag 2019-03-01 10:20:25 -08:00
Shpuld Shpludson 53e104dc32 Merge branch 'issue-388-request-count-broken' into 'develop'
#388: get follow request on a real-time basis

Closes #388

See merge request pleroma/pleroma-fe!619
2019-03-01 18:03:30 +00:00
dave bbab1b1dc6 #346: Hyperlink is not included 2019-02-28 21:03:35 -05:00
Shpuld Shpludson bbe4f3e3af Merge branch 'issue-410-greyout-checkbox' into 'develop'
#410: disable checkbox when parent is disabled

Closes #410

See merge request pleroma/pleroma-fe!624
2019-02-28 18:02:15 +00:00
Shpuld Shpludson 88c7c8b114 Merge branch '398-rewrite-follow-list' into 'develop'
Split UserCard into FollowCard/FollowRequestCard and Rewrite FollowList using HOCs

Closes #398

See merge request pleroma/pleroma-fe!616
2019-02-28 17:53:40 +00:00
dave cccf33d6dd #388: update naming properly 2019-02-28 12:53:37 -05:00
taehoon 36460fd384 Fix layout overflow issue 2019-02-28 12:48:14 -05:00
taehoon a8d11b22e7 Add a class to screen name 2019-02-28 12:41:20 -05:00
taehoon 24e1adf6df Add back accidently removed logic 2019-02-28 12:41:20 -05:00
taehoon 0e86681aba Merge all slots of BasicUserCard into one 2019-02-28 12:41:20 -05:00
taehoon 6e41b4b546 Revert "Minor mobile layout improvement for BasicUserCard"
This reverts commit 1cd964bffff3ae4d324d0ab99e5c218eae7cd0ca.
2019-02-28 12:41:20 -05:00
taehoon d5f8e2b1ee Minor mobile layout improvement for BasicUserCard 2019-02-28 12:41:20 -05:00
taehoon 651c97153b Use native filter function 2019-02-28 12:41:20 -05:00
taehoon a6f2af4ad9 Shorten a classname 2019-02-28 12:41:20 -05:00
taehoon c5519fa587 Improve mobile layout of user card 2019-02-28 12:41:20 -05:00
taehoon 1fecac9ba6 Update naming 2019-02-28 12:41:20 -05:00
taehoon 8dd42cfc65 Add back some css 2019-02-28 12:41:20 -05:00
taehoon 30f5bf1463 Remove legacy class names in BasicUserCard 2019-02-28 12:41:20 -05:00
taehoon 0d283c3f17 Remove UserCard 2019-02-28 12:41:20 -05:00
taehoon 96e7e8235d Migrate UserCard to FollowCard and FollowRequestCard 2019-02-28 12:41:20 -05:00
taehoon 390b2bcfee Add FollowRequestCard component 2019-02-28 12:41:20 -05:00
taehoon 4b0a11acef Improve action button size and position in BasicUserCard 2019-02-28 12:41:20 -05:00
taehoon 8923492e68 Remove needless div wrapper 2019-02-28 12:41:20 -05:00
taehoon 9ca805a991 Add new FollowCard component 2019-02-28 12:41:20 -05:00
taehoon 1337e42b2d Add third slot area to BasicUserCard 2019-02-28 12:41:20 -05:00
taehoon 784523b8ec Use big avatar in BasicUserCard 2019-02-28 12:41:20 -05:00
taehoon 3ab42efbc3 Remove unused component local registration 2019-02-28 12:41:20 -05:00
taehoon 3f9fd07f93 Remove non-existing prop binding 2019-02-28 12:41:20 -05:00
taehoon bb1fac4bc2 Remove needless component local registration 2019-02-28 12:41:20 -05:00
taehoon 080786c945 Rewrite FollowList using hocs 2019-02-28 12:41:20 -05:00
taehoon cb383df517 Fix bug to get wrapped component prop name list 2019-02-28 12:40:22 -05:00
taehoon 5c43374588 Clean up addFollowers action 2019-02-28 12:40:22 -05:00
taehoon 3a689ef8ee Allow HOCs to accept additional props 2019-02-28 12:40:22 -05:00
dave 1c57a1c9b4 #410: disable checkbox when parent is disabled 2019-02-28 12:28:45 -05:00
Shpuld Shpludson dc01f90dde Merge branch '386-display-invalid-user-profile' into 'develop'
Show error message when visit profile page of invalid user

Closes #386

See merge request pleroma/pleroma-fe!606
2019-02-28 17:19:14 +00:00
Shpuld Shpludson 24f3770fb5 Merge branch 'keyboard-binding' into 'develop'
Keyboard binding

Closes #369

See merge request pleroma/pleroma-fe!593
2019-02-28 17:11:31 +00:00
Shpuld Shpludson efc4fa1099 Merge branch 'feature/oauth-tokens-in-settings' into 'develop'
Add OAuth Tokens management to settings

See merge request pleroma/pleroma-fe!572
2019-02-28 17:03:11 +00:00
jasper 4d026baf17 Fix fetching error by tag 2019-02-27 18:14:42 -08:00
jasper 9aec49bacb Fetch activites by last id 2019-02-27 17:45:08 -08:00
Maxim Filippov 267952b4e7 "Are you sure?" -> i18n 2019-02-28 04:05:54 +03:00
Maxim Filippov c71f411ad6 Show only "app_name" and "valid_until" (OAuth tokens table) 2019-02-28 04:05:54 +03:00
Maxim Filippov afbe524a2e use translations 2019-02-28 04:04:26 +03:00
Maxim Filippov b9082fb13f Remove outdated test 2019-02-28 04:04:26 +03:00
Maxim Filippov c10a15386a Code style 2019-02-28 04:04:26 +03:00
Maxim Filippov 2c7406d9a8 Add OAuth Tokens management to settings 2019-02-28 04:04:26 +03:00
dave 9f1214555e #388: remove empty line 2019-02-27 14:40:21 -05:00
dave 7c6446a9de #388: get follow request on a real-time basis 2019-02-27 14:38:10 -05:00
Shpuld Shpludson 058238c3c6 Merge branch 'feat/max-attachments-configurable' into 'develop'
Fix #399 Make max attachments configurable

Closes #399

See merge request pleroma/pleroma-fe!618
2019-02-27 14:38:58 +00:00
Shpuld Shpludson 95fb768b5a Fix #399 Make max attachments configurable 2019-02-27 14:38:58 +00:00
taehoon b78227456e Better error handling 2019-02-26 12:26:04 -05:00
taehoon e687b58091 Show error message when visit profile page of invalid user 2019-02-26 11:08:13 -05:00
HJ d5348c13ff Merge branch 'issue-380-noification-unread' into 'develop'
#380: fix false alarm for unread notifications

Closes #380

See merge request pleroma/pleroma-fe!614
2019-02-25 20:08:45 +00:00
HJ fef0981bc4 Merge branch 'issue-396-registration-bio-field' into 'develop'
#396: update registration form

Closes #396

See merge request pleroma/pleroma-fe!612
2019-02-25 19:58:17 +00:00
dave 7dbbd8e270 #380: simply and remove redundancy 2019-02-25 14:57:56 -05:00
dave 4e8b696797 #380: fix false alarm for unread notifications 2019-02-25 12:12:49 -05:00
Shpuld Shpludson dbf46942d0 Merge branch 'fix/active-user-search-text-field' into 'develop'
#387 Activate user search text field after click button.

See merge request pleroma/pleroma-fe!608
2019-02-25 16:09:18 +00:00
Shpuld Shpludson baa09002b5 Merge branch 'issue-390-load-other-user-media-data' into 'develop'
#390: fixed the problem loading other user's media data

Closes #390

See merge request pleroma/pleroma-fe!613
2019-02-25 16:08:53 +00:00
dave 662e6bda14 #390: fixed the problem loading other user's media data 2019-02-25 10:21:17 -05:00
jasper bbd3e6b10f Update search input name 2019-02-25 06:08:52 -08:00
dave 20c68d33eb #396: update en.json 2019-02-24 15:44:06 -05:00
dave 8275f2aa9a #396: update registration form 2019-02-24 14:30:29 -05:00
Shpuld Shpludson 1dd7120e16 Merge branch 'issue-389-search-loading-icon' into 'develop'
#389: add loading icon in search panel

Closes #389

See merge request pleroma/pleroma-fe!610
2019-02-24 16:24:53 +00:00
dave 9bedf96b98 #389: add loading icon in search panel 2019-02-22 13:37:02 -05:00
Shpuld Shpludson 99051f4a55 Merge branch 'fix/fetch-error-when-login' into 'develop'
#370 fix/fetch-error-when-login

See merge request pleroma/pleroma-fe!596
2019-02-22 15:17:17 +00:00
Shpuld Shpludson e34e1ccdae Merge branch '227-manage-blocks-mutes' into 'develop'
Add Blocks / Mutes management tabs under user settings page

See merge request pleroma/pleroma-fe!578
2019-02-22 14:54:12 +00:00
taehoon 22851a3a96 Remove needless code 2019-02-21 20:15:51 -05:00
jasper 34d723215b Activate user search text field 2019-02-21 11:20:46 -08:00
HJ 3768a4623f Merge branch 'fix-mutes' into 'develop'
bad defaults

Closes #384

See merge request pleroma/pleroma-fe!605
2019-02-21 17:56:41 +00:00
Henry Jameson 4136d9cdd1 bad defaults 2019-02-21 19:52:58 +02:00
Shpuld Shpludson c3fa2c90e2 Merge branch 'issue-376-no-statuses' into 'develop'
#376: update status timeline when it's empty

Closes #376

See merge request pleroma/pleroma-fe!601
2019-02-21 17:24:39 +00:00
Shpuld Shpludson 254b0afab7 Merge branch 'issue-383-content-type' into 'develop'
#383: content type error

Closes #383

See merge request pleroma/pleroma-fe!603
2019-02-21 17:06:10 +00:00
dave 09822cc15b #383: content type error 2019-02-21 11:16:11 -05:00
taehoon eb22e7f462 Use Object.entries instead of Object.keys 2019-02-20 21:50:10 -05:00
taehoon 37eec09b9b Comment out the mutes tab 2019-02-20 13:30:31 -05:00
taehoon 5bd36c6476 Remove needless css 2019-02-20 13:30:31 -05:00
taehoon 46e1f30347 Fix indent 2019-02-20 13:30:31 -05:00
taehoon 85d43d17f5 Add missing translation strings 2019-02-20 13:30:31 -05:00
taehoon 32c112bc96 Remove pagination logic in fetchBlocks api 2019-02-20 13:30:31 -05:00
taehoon 395d212904 Add new strings to i18n 2019-02-20 13:30:31 -05:00
taehoon a5162bd636 Add note for empty state to the lists using slot 2019-02-20 13:30:31 -05:00
taehoon b4a5b5203f Minor css improvements of hocs 2019-02-20 13:30:31 -05:00
taehoon 8680046c4e Pass down slots into wrapped components 2019-02-20 13:30:31 -05:00
taehoon 6d4d705c51 Rename some options and add comments to HOCs 2019-02-20 13:30:31 -05:00
taehoon 339373b495 Improve chaining hocs using vue-compose 2019-02-20 13:30:30 -05:00
taehoon f81b82b471 Use hoc definitions to be factor of factory 2019-02-20 13:30:30 -05:00
taehoon 8f608e060c Just save blocks/mutes instead of adding 2019-02-20 13:30:30 -05:00
taehoon 1fd9a1c7c0 Set blockIds and muteIds to the currentUser state only 2019-02-20 13:30:30 -05:00
taehoon e91a94ff9c Add mutes tab 2019-02-20 13:30:30 -05:00
taehoon 09315b2780 Add a prop to force-refresh data to withSubscription hoc 2019-02-20 13:30:30 -05:00
taehoon 8c8a6edc78 Remove pagination support from block-list 2019-02-20 13:30:30 -05:00
taehoon 159e84532e Add withSubscription hoc 2019-02-20 13:30:30 -05:00
taehoon 8270274865 Update hocs to pass parent-scope bindings to the wrapped component 2019-02-20 13:30:30 -05:00
taehoon 52913d8f87 Complete functionality of BlockCard 2019-02-20 13:30:30 -05:00
taehoon 0220d3d304 Finally, added BlockCard 2019-02-20 13:30:30 -05:00
taehoon 5d6e1864a5 Update BasicUserCard template and add a slot for customization 2019-02-20 13:30:30 -05:00
taehoon 94e6de11b7 Add withList hoc and remove UserList component 2019-02-20 13:30:30 -05:00
taehoon a817cc7cb4 Wire up ui to real blocks api data 2019-02-20 13:30:30 -05:00
taehoon a56d2dfeb1 Add blocks tab with test data to user settings page 2019-02-20 13:30:30 -05:00
taehoon 4a737cbe45 Add reusable BasicUserCard and UserList components 2019-02-20 13:30:30 -05:00
taehoon 12df967cb6 Add withLoadMore hoc 2019-02-20 13:30:30 -05:00
dave e20a7be3aa #376: update status timeline when it's empty 2019-02-20 10:13:28 -05:00
Shpuld Shpludson cfdd885e04 Merge branch 'issue-371-user-setting-notification' into 'develop'
#371: show notification when user setting's saved

Closes #371

See merge request pleroma/pleroma-fe!598
2019-02-20 14:42:27 +00:00
Shpuld Shpludson a49f6dbf65 Merge branch 'fix/follow-list' into 'develop'
Watch user change in follow-list

Closes #360

See merge request pleroma/pleroma-fe!584
2019-02-20 14:32:45 +00:00
Shpuld Shpludson 22a0858ef4 Merge branch 'issue-377-local-user-no-link' into 'develop'
#377: no exteral profile link for local users

Closes #377

See merge request pleroma/pleroma-fe!599
2019-02-19 19:35:44 +00:00
dave 0caf59a911 #377: no exteral profile link for local users 2019-02-19 13:49:04 -05:00
Shpuld Shpludson 40159324b4 Merge branch 'issue-364-register-freeze' into 'develop'
#364: update ap_id error with username

Closes #364

See merge request pleroma/pleroma-fe!597
2019-02-19 18:00:39 +00:00
jasper 1e43a47c3c Update: Fix fetch error when login 2019-02-19 09:42:53 -08:00
dave 63cfe051c7 #371: show notification when user setting's saved 2019-02-19 12:38:49 -05:00
Shpuld Shpludson 53484ae4bf Merge branch 'issue-344-css-refactor' into 'develop'
#344 - replace float: with flexbox, clean up

See merge request pleroma/pleroma-fe!566
2019-02-19 16:49:44 +00:00
Shpuld Shpludson 0e1295edea Merge branch 'follow-requests-for-locked-accounts' into 'develop'
Follow Request notification count for restricted accounts

See merge request pleroma/pleroma-fe!561
2019-02-19 16:38:05 +00:00
shpuld 32df77c16a Merge branch 'tae-hoon/pleroma-fe-323-improve-image-lightbox' into develop 2019-02-19 18:34:09 +02:00
shpuld 4e0f934301 Clean up CSS a bit 2019-02-19 18:33:40 +02:00
Shpuld Shpludson 7b9a6f8d43 Merge branch '256-avatar-crop-popup' into 'develop'
Add avatar cropper

Closes #256

See merge request pleroma/pleroma-fe!547
2019-02-19 15:37:01 +00:00
Shpuld Shpludson 8f96de2ba5 Merge branch 'patch-pleroma-615' into 'develop'
#336: Deleted static/bg2.jpg, static/bgalt.jpg files

See merge request pleroma/pleroma-fe!555
2019-02-19 15:27:54 +00:00
dave 5251ccd708 #364: update ap_id error with username 2019-02-19 09:22:42 -05:00
Edijs d032ce2758 Validate if there are any new status to show 2019-02-18 18:32:20 -07:00
jasper b4709515f2 Fix error when login 2019-02-18 17:15:16 -08:00
jasper d4a2376e12 fix/fetch-error-when-login 2019-02-18 12:32:08 -08:00
Edijs 0d2922afb3 Remove event listener when component destroy 2019-02-17 14:37:24 -07:00
Edijs 642b0be0b2 Bind a keyboard to show new status 2019-02-17 14:31:13 -07:00
taehoon 3094c50ff9 Fix lint errors 2019-02-15 13:34:33 -05:00
taehoon 2de756aa0c Better error handling 2019-02-15 13:34:33 -05:00
taehoon 2132d58075 Remove cropped image size restriction 2019-02-15 13:34:33 -05:00
taehoon b6d197ce1d Remove modal component 2019-02-15 13:34:33 -05:00
taehoon b24db12e1c Make embedded image cropper 2019-02-15 13:34:33 -05:00
taehoon 546ba9eba9 Revert eslintrc changes 2019-02-15 13:34:33 -05:00
taehoon b458b2ae5f Check if variable exists before using 2019-02-15 13:34:33 -05:00
taehoon ddfdaf3284 Remove event listeners when destory ImageCropper 2019-02-15 13:34:33 -05:00
taehoon 228e6681e3 Localization of ImageCropper component 2019-02-15 13:34:33 -05:00
taehoon 205e38ffa9 Remove event listener when modal is destroyed 2019-02-15 13:34:33 -05:00
taehoon 09949fc7ee Crop avatar image using minWidth/minHeight 2019-02-15 13:34:33 -05:00
taehoon a001ffecf0 Add back the existing translation string 2019-02-15 13:34:33 -05:00
taehoon 13725f040b Add avatar crop popup 2019-02-15 13:34:33 -05:00
eugenijm d831b81912 Added follow requests counter to nav bar to make it visible behorehand 2019-02-15 12:49:00 +03:00
Edijs 3398817c25 watch only user 2019-02-14 13:25:22 -07:00
Edijs 0ca145ac95 Watch user change in follow-list 2019-02-14 13:19:00 -07:00
dave f2ce2e9176 #344 - replace float: with flexbox, clean up 2019-02-11 22:35:24 -05:00
taehoon a20974faac Use correct syntax for pseudoelement 2019-02-11 06:56:46 -05:00
taehoon 971e75aa64 Add translation strings 2019-02-10 13:26:08 -05:00
taehoon 750c308909 Support lightbox gallery navigation via arrow buttons and keyboard 2019-02-10 13:23:01 -05:00
Lidar ce81a5f72e Deleted static/bg2.jpg, static/bgalt.jpg files 2019-02-09 16:26:54 +00:00
98 changed files with 3089 additions and 1863 deletions
+1 -1
View File
@@ -11,7 +11,7 @@ module.exports = {
'html' 'html'
], ],
// add your custom rules here // add your custom rules here
'rules': { rules: {
// allow paren-less arrow functions // allow paren-less arrow functions
'arrow-parens': 0, 'arrow-parens': 0,
// allow async-await // allow async-await
+2
View File
@@ -17,6 +17,7 @@
"babel-plugin-add-module-exports": "^0.2.1", "babel-plugin-add-module-exports": "^0.2.1",
"babel-plugin-lodash": "^3.2.11", "babel-plugin-lodash": "^3.2.11",
"chromatism": "^3.0.0", "chromatism": "^3.0.0",
"cropperjs": "^1.4.3",
"diff": "^3.0.1", "diff": "^3.0.1",
"karma-mocha-reporter": "^2.2.1", "karma-mocha-reporter": "^2.2.1",
"localforage": "^1.5.0", "localforage": "^1.5.0",
@@ -27,6 +28,7 @@
"sass-loader": "^4.0.2", "sass-loader": "^4.0.2",
"vue": "^2.5.13", "vue": "^2.5.13",
"vue-chat-scroll": "^1.2.1", "vue-chat-scroll": "^1.2.1",
"vue-compose": "^0.7.1",
"vue-i18n": "^7.3.2", "vue-i18n": "^7.3.2",
"vue-router": "^3.0.1", "vue-router": "^3.0.1",
"vue-template-compiler": "^2.3.4", "vue-template-compiler": "^2.3.4",
+16 -17
View File
@@ -181,8 +181,7 @@ input, textarea, .select {
color: $fallback--text; color: $fallback--text;
color: var(--text, $fallback--text); color: var(--text, $fallback--text);
} }
&:disabled, &:disabled {
{
&, &,
& + label, & + label,
& + label::before { & + label::before {
@@ -629,6 +628,16 @@ nav {
color: $fallback--faint; color: $fallback--faint;
color: var(--faint, $fallback--faint); color: var(--faint, $fallback--faint);
} }
.faint-link {
color: $fallback--faint;
color: var(--faint, $fallback--faint);
&:hover {
text-decoration: underline;
}
}
@media all and (min-width: 800px) { @media all and (min-width: 800px) {
.logo { .logo {
opacity: 1 !important; opacity: 1 !important;
@@ -649,10 +658,6 @@ nav {
color: var(--lightText, $fallback--lightText); color: var(--lightText, $fallback--lightText);
} }
.text-format {
float: right;
}
div { div {
padding-top: 5px; padding-top: 5px;
} }
@@ -666,6 +671,10 @@ nav {
border-radius: var(--inputRadius, $fallback--inputRadius); border-radius: var(--inputRadius, $fallback--inputRadius);
} }
.button-icon {
font-size: 1.2em;
}
@keyframes shakeError { @keyframes shakeError {
0% { 0% {
transform: translateX(0); transform: translateX(0);
@@ -710,16 +719,6 @@ nav {
margin: 0.5em 0 0.5em 0; margin: 0.5em 0 0.5em 0;
} }
.button-icon {
font-size: 1.2em;
}
.status .status-actions {
div {
max-width: 4em;
}
}
.menu-button { .menu-button {
display: block; display: block;
margin-right: 0.8em; margin-right: 0.8em;
@@ -728,7 +727,7 @@ nav {
.login-hint { .login-hint {
text-align: center; text-align: center;
@media all and (min-width: 801px) { @media all and (min-width: 801px) {
display: none; display: none;
} }
+1 -1
View File
@@ -88,7 +88,7 @@
.attachment { .attachment {
position: relative; position: relative;
margin: 0.5em 0.5em 0em 0em; margin-top: 0.5em;
align-self: flex-start; align-self: flex-start;
line-height: 0; line-height: 0;
@@ -0,0 +1,28 @@
import UserCardContent from '../user_card_content/user_card_content.vue'
import UserAvatar from '../user_avatar/user_avatar.vue'
import generateProfileLink from 'src/services/user_profile_link_generator/user_profile_link_generator'
const BasicUserCard = {
props: [
'user'
],
data () {
return {
userExpanded: false
}
},
components: {
UserCardContent,
UserAvatar
},
methods: {
toggleUserExpanded () {
this.userExpanded = !this.userExpanded
},
userProfileLink (user) {
return generateProfileLink(user.id, user.screen_name, this.$store.state.instance.restrictedNicknames)
}
}
}
export default BasicUserCard
@@ -0,0 +1,79 @@
<template>
<div class="user-card">
<router-link :to="userProfileLink(user)">
<UserAvatar class="avatar" @click.prevent.native="toggleUserExpanded" :src="user.profile_image_url"/>
</router-link>
<div class="user-card-expanded-content" v-if="userExpanded">
<user-card-content :user="user" :switcher="false"></user-card-content>
</div>
<div class="user-card-collapsed-content" v-else>
<div :title="user.name" class="user-card-user-name">
<span v-if="user.name_html" v-html="user.name_html"></span>
<span v-else>{{ user.name }}</span>
</div>
<div>
<router-link class="user-card-screen-name" :to="userProfileLink(user)">
@{{user.screen_name}}
</router-link>
</div>
<slot></slot>
</div>
</div>
</template>
<script src="./basic_user_card.js"></script>
<style lang="scss">
@import '../../_variables.scss';
.user-card {
display: flex;
flex: 1 0;
padding-top: 0.6em;
padding-right: 1em;
padding-bottom: 0.6em;
padding-left: 1em;
border-bottom: 1px solid;
margin: 0;
border-bottom-color: $fallback--border;
border-bottom-color: var(--border, $fallback--border);
&-collapsed-content {
margin-left: 0.7em;
text-align: left;
flex: 1;
min-width: 0;
}
&-user-name {
img {
object-fit: contain;
height: 16px;
width: 16px;
vertical-align: middle;
}
}
&-expanded-content {
flex: 1;
margin-left: 0.7em;
border-radius: $fallback--panelRadius;
border-radius: var(--panelRadius, $fallback--panelRadius);
border-style: solid;
border-color: $fallback--border;
border-color: var(--border, $fallback--border);
border-width: 1px;
overflow: hidden;
.panel-heading {
background: transparent;
flex-direction: column;
align-items: stretch;
}
p {
margin-bottom: 0;
}
}
}
</style>
+37
View File
@@ -0,0 +1,37 @@
import BasicUserCard from '../basic_user_card/basic_user_card.vue'
const BlockCard = {
props: ['userId'],
data () {
return {
progress: false
}
},
computed: {
user () {
return this.$store.getters.userById(this.userId)
},
blocked () {
return this.user.statusnet_blocking
}
},
components: {
BasicUserCard
},
methods: {
unblockUser () {
this.progress = true
this.$store.dispatch('unblockUser', this.user.id).then(() => {
this.progress = false
})
},
blockUser () {
this.progress = true
this.$store.dispatch('blockUser', this.user.id).then(() => {
this.progress = false
})
}
}
}
export default BlockCard
+34
View File
@@ -0,0 +1,34 @@
<template>
<basic-user-card :user="user">
<div class="block-card-content-container">
<button class="btn btn-default" @click="unblockUser" :disabled="progress" v-if="blocked">
<template v-if="progress">
{{ $t('user_card.unblock_progress') }}
</template>
<template v-else>
{{ $t('user_card.unblock') }}
</template>
</button>
<button class="btn btn-default" @click="blockUser" :disabled="progress" v-else>
<template v-if="progress">
{{ $t('user_card.block_progress') }}
</template>
<template v-else>
{{ $t('user_card.block') }}
</template>
</button>
</div>
</basic-user-card>
</template>
<script src="./block_card.js"></script>
<style lang="scss">
.block-card-content-container {
margin-top: 0.5em;
text-align: right;
button {
width: 10em;
}
}
</style>
+9 -2
View File
@@ -3,8 +3,8 @@
<div class="panel panel-default"> <div class="panel panel-default">
<div class="panel-heading timeline-heading" :class="{ 'chat-heading': floating }" @click.stop.prevent="togglePanel"> <div class="panel-heading timeline-heading" :class="{ 'chat-heading': floating }" @click.stop.prevent="togglePanel">
<div class="title"> <div class="title">
{{$t('chat.title')}} <span>{{$t('chat.title')}}</span>
<i class="icon-cancel" style="float: right;" v-if="floating"></i> <i class="icon-cancel" v-if="floating"></i>
</div> </div>
</div> </div>
<div class="chat-window" v-chat-scroll> <div class="chat-window" v-chat-scroll>
@@ -98,4 +98,11 @@
resize: none; resize: none;
} }
} }
.chat-panel {
.title {
display: flex;
justify-content: space-between;
}
}
</style> </style>
+45
View File
@@ -0,0 +1,45 @@
import BasicUserCard from '../basic_user_card/basic_user_card.vue'
import { requestFollow, requestUnfollow } from '../../services/follow_manipulate/follow_manipulate'
const FollowCard = {
props: [
'user',
'noFollowsYou'
],
data () {
return {
inProgress: false,
requestSent: false,
updated: false
}
},
components: {
BasicUserCard
},
computed: {
isMe () { return this.$store.state.users.currentUser.id === this.user.id },
following () { return this.updated ? this.updated.following : this.user.following },
showFollow () {
return !this.following || this.updated && !this.updated.following
}
},
methods: {
followUser () {
this.inProgress = true
requestFollow(this.user, this.$store).then(({ sent, updated }) => {
this.inProgress = false
this.requestSent = sent
this.updated = updated
})
},
unfollowUser () {
this.inProgress = true
requestUnfollow(this.user, this.$store).then(({ updated }) => {
this.inProgress = false
this.updated = updated
})
}
}
}
export default FollowCard
@@ -0,0 +1,53 @@
<template>
<basic-user-card :user="user">
<div class="follow-card-content-container">
<span class="faint" v-if="!noFollowsYou && user.follows_you">
{{ isMe ? $t('user_card.its_you') : $t('user_card.follows_you') }}
</span>
<button
v-if="showFollow"
class="btn btn-default"
@click="followUser"
:disabled="inProgress"
:title="requestSent ? $t('user_card.follow_again') : ''"
>
<template v-if="inProgress">
{{ $t('user_card.follow_progress') }}
</template>
<template v-else-if="requestSent">
{{ $t('user_card.follow_sent') }}
</template>
<template v-else>
{{ $t('user_card.follow') }}
</template>
</button>
<button v-if="following" class="btn btn-default pressed" @click="unfollowUser" :disabled="inProgress">
<template v-if="inProgress">
{{ $t('user_card.follow_progress') }}
</template>
<template v-else>
{{ $t('user_card.follow_unfollow') }}
</template>
</button>
</div>
</basic-user-card>
</template>
<script src="./follow_card.js"></script>
<style lang="scss">
.follow-card-content-container {
flex-shrink: 0;
display: flex;
flex-direction: row;
justify-content: space-between;
flex-wrap: wrap;
line-height: 1.5em;
.btn {
margin-top: 0.5em;
margin-left: auto;
width: 10em;
}
}
</style>
-65
View File
@@ -1,65 +0,0 @@
import UserCard from '../user_card/user_card.vue'
const FollowList = {
data () {
return {
loading: false,
bottomedOut: false,
error: false
}
},
props: ['userId', 'showFollowers'],
created () {
window.addEventListener('scroll', this.scrollLoad)
if (this.entries.length === 0) {
this.fetchEntries()
}
},
destroyed () {
window.removeEventListener('scroll', this.scrollLoad)
this.$store.dispatch('clearFriendsAndFollowers', this.userId)
},
computed: {
user () {
return this.$store.getters.userById(this.userId)
},
entries () {
return this.showFollowers ? this.user.followers : this.user.friends
},
showFollowsYou () {
return !this.showFollowers || (this.showFollowers && this.userId !== this.$store.state.users.currentUser.id)
}
},
methods: {
fetchEntries () {
if (!this.loading) {
const command = this.showFollowers ? 'addFollowers' : 'addFriends'
this.loading = true
this.$store.dispatch(command, this.userId).then(entries => {
this.error = false
this.loading = false
this.bottomedOut = entries.length === 0
}).catch(() => {
this.error = true
this.loading = false
})
}
},
scrollLoad (e) {
const bodyBRect = document.body.getBoundingClientRect()
const height = Math.max(bodyBRect.height, -(bodyBRect.y))
if (this.loading === false &&
this.bottomedOut === false &&
this.$el.offsetHeight > 0 &&
(window.innerHeight + window.pageYOffset) >= (height - 750)
) {
this.fetchEntries()
}
}
},
components: {
UserCard
}
}
export default FollowList
@@ -1,33 +0,0 @@
<template>
<div class="follow-list">
<user-card
v-for="entry in entries"
:key="entry.id" :user="entry"
:noFollowsYou="!showFollowsYou"
/>
<div class="text-center panel-footer">
<a v-if="error" @click="fetchEntries" class="alert error">
{{$t('general.generic_error')}}
</a>
<i v-else-if="loading" class="icon-spin3 animate-spin"/>
<span v-else-if="bottomedOut"></span>
<a v-else @click="fetchEntries">{{$t('general.more')}}</a>
</div>
</div>
</template>
<script src="./follow_list.js"></script>
<style lang="scss">
.follow-list {
.panel-footer {
padding: 10px;
}
.error {
font-size: 14px;
}
}
</style>
@@ -0,0 +1,20 @@
import BasicUserCard from '../basic_user_card/basic_user_card.vue'
const FollowRequestCard = {
props: ['user'],
components: {
BasicUserCard
},
methods: {
approveUser () {
this.$store.state.api.backendInteractor.approveUser(this.user.id)
this.$store.dispatch('removeFollowRequest', this.user)
},
denyUser () {
this.$store.state.api.backendInteractor.denyUser(this.user.id)
this.$store.dispatch('removeFollowRequest', this.user)
}
}
}
export default FollowRequestCard
@@ -0,0 +1,29 @@
<template>
<basic-user-card :user="user">
<div class="follow-request-card-content-container">
<button class="btn btn-default" @click="approveUser">{{ $t('user_card.approve') }}</button>
<button class="btn btn-default" @click="denyUser">{{ $t('user_card.deny') }}</button>
</div>
</basic-user-card>
</template>
<script src="./follow_request_card.js"></script>
<style lang="scss">
.follow-request-card-content-container {
display: flex;
flex-direction: row;
flex-wrap: wrap;
button {
margin-top: 0.5em;
margin-right: 0.5em;
flex: 1 1;
max-width: 12em;
min-width: 8em;
&:last-child {
margin-right: 0;
}
}
}
</style>
@@ -1,22 +1,13 @@
import UserCard from '../user_card/user_card.vue' import FollowRequestCard from '../follow_request_card/follow_request_card.vue'
const FollowRequests = { const FollowRequests = {
components: { components: {
UserCard FollowRequestCard
},
created () {
this.updateRequests()
}, },
computed: { computed: {
requests () { requests () {
return this.$store.state.api.followRequests return this.$store.state.api.followRequests
} }
},
methods: {
updateRequests () {
this.$store.state.api.backendInteractor.fetchFollowRequests()
.then((requests) => { this.$store.commit('setFollowRequests', requests) })
}
} }
} }
@@ -4,7 +4,7 @@
{{$t('nav.friend_requests')}} {{$t('nav.friend_requests')}}
</div> </div>
<div class="panel-body"> <div class="panel-body">
<user-card v-for="request in requests" :key="request.id" :user="request" :showFollows="false" :showApproval="true"></user-card> <FollowRequestCard v-for="request in requests" :key="request.id" :user="request"/>
</div> </div>
</div> </div>
</template> </template>
+3 -1
View File
@@ -27,7 +27,6 @@
align-content: stretch; align-content: stretch;
flex-grow: 1; flex-grow: 1;
margin-top: 0.5em; margin-top: 0.5em;
margin-bottom: 0.25em;
.attachments, .attachment { .attachments, .attachment {
margin: 0 0.5em 0 0; margin: 0 0.5em 0 0;
@@ -36,6 +35,9 @@
box-sizing: border-box; box-sizing: border-box;
// to make failed images a bit more noticeable on chromium // to make failed images a bit more noticeable on chromium
min-width: 2em; min-width: 2em;
&:last-child {
margin: 0;
}
} }
.image-attachment { .image-attachment {
@@ -0,0 +1,128 @@
import Cropper from 'cropperjs'
import 'cropperjs/dist/cropper.css'
const ImageCropper = {
props: {
trigger: {
type: [String, window.Element],
required: true
},
submitHandler: {
type: Function,
required: true
},
cropperOptions: {
type: Object,
default () {
return {
aspectRatio: 1,
autoCropArea: 1,
viewMode: 1,
movable: false,
zoomable: false,
guides: false
}
}
},
mimes: {
type: String,
default: 'image/png, image/gif, image/jpeg, image/bmp, image/x-icon'
},
saveButtonLabel: {
type: String
},
cancelButtonLabel: {
type: String
}
},
data () {
return {
cropper: undefined,
dataUrl: undefined,
filename: undefined,
submitting: false,
submitError: null
}
},
computed: {
saveText () {
return this.saveButtonLabel || this.$t('image_cropper.save')
},
cancelText () {
return this.cancelButtonLabel || this.$t('image_cropper.cancel')
},
submitErrorMsg () {
return this.submitError && this.submitError instanceof Error ? this.submitError.toString() : this.submitError
}
},
methods: {
destroy () {
if (this.cropper) {
this.cropper.destroy()
}
this.$refs.input.value = ''
this.dataUrl = undefined
this.$emit('close')
},
submit () {
this.submitting = true
this.avatarUploadError = null
this.submitHandler(this.cropper, this.file)
.then(() => this.destroy())
.catch((err) => {
this.submitError = err
})
.finally(() => {
this.submitting = false
})
},
pickImage () {
this.$refs.input.click()
},
createCropper () {
this.cropper = new Cropper(this.$refs.img, this.cropperOptions)
},
getTriggerDOM () {
return typeof this.trigger === 'object' ? this.trigger : document.querySelector(this.trigger)
},
readFile () {
const fileInput = this.$refs.input
if (fileInput.files != null && fileInput.files[0] != null) {
this.file = fileInput.files[0]
let reader = new window.FileReader()
reader.onload = (e) => {
this.dataUrl = e.target.result
this.$emit('open')
}
reader.readAsDataURL(this.file)
this.$emit('changed', this.file, reader)
}
},
clearError () {
this.submitError = null
}
},
mounted () {
// listen for click event on trigger
const trigger = this.getTriggerDOM()
if (!trigger) {
this.$emit('error', 'No image make trigger found.', 'user')
} else {
trigger.addEventListener('click', this.pickImage)
}
// listen for input file changes
const fileInput = this.$refs.input
fileInput.addEventListener('change', this.readFile)
},
beforeDestroy: function () {
// remove the event listeners
const trigger = this.getTriggerDOM()
if (trigger) {
trigger.removeEventListener('click', this.pickImage)
}
const fileInput = this.$refs.input
fileInput.removeEventListener('change', this.readFile)
}
}
export default ImageCropper
@@ -0,0 +1,42 @@
<template>
<div class="image-cropper">
<div v-if="dataUrl">
<div class="image-cropper-image-container">
<img ref="img" :src="dataUrl" alt="" @load.stop="createCropper" />
</div>
<div class="image-cropper-buttons-wrapper">
<button class="btn" type="button" :disabled="submitting" @click="submit" v-text="saveText"></button>
<button class="btn" type="button" :disabled="submitting" @click="destroy" v-text="cancelText"></button>
<i class="icon-spin4 animate-spin" v-if="submitting"></i>
</div>
<div class="alert error" v-if="submitError">
{{submitErrorMsg}}
<i class="button-icon icon-cancel" @click="clearError"></i>
</div>
</div>
<input ref="input" type="file" class="image-cropper-img-input" :accept="mimes">
</div>
</template>
<script src="./image_cropper.js"></script>
<style lang="scss">
.image-cropper {
&-img-input {
display: none;
}
&-image-container {
position: relative;
img {
display: block;
max-width: 100%;
}
}
&-buttons-wrapper {
margin-top: 15px;
}
}
</style>
+1 -4
View File
@@ -23,10 +23,7 @@
flex-direction: row; flex-direction: row;
cursor: pointer; cursor: pointer;
overflow: hidden; overflow: hidden;
margin-top: 0.5em;
// TODO: clean up the random margins in attachments, this makes preview line
// up with attachments...
margin-right: 0.5em;
.card-image { .card-image {
flex-shrink: 0; flex-shrink: 0;
+43 -8
View File
@@ -11,27 +11,62 @@ const MediaModal = {
showing () { showing () {
return this.$store.state.mediaViewer.activated return this.$store.state.mediaViewer.activated
}, },
media () {
return this.$store.state.mediaViewer.media
},
currentIndex () { currentIndex () {
return this.$store.state.mediaViewer.currentIndex return this.$store.state.mediaViewer.currentIndex
}, },
currentMedia () { currentMedia () {
return this.$store.state.mediaViewer.media[this.currentIndex] return this.media[this.currentIndex]
},
canNavigate () {
return this.media.length > 1
}, },
type () { type () {
return this.currentMedia ? fileTypeService.fileType(this.currentMedia.mimetype) : null return this.currentMedia ? fileTypeService.fileType(this.currentMedia.mimetype) : null
} }
}, },
created () {
document.addEventListener('keyup', e => {
if (e.keyCode === 27 && this.showing) { // escape
this.hide()
}
})
},
methods: { methods: {
hide () { hide () {
this.$store.dispatch('closeMediaViewer') this.$store.dispatch('closeMediaViewer')
},
goPrev () {
if (this.canNavigate) {
const prevIndex = this.currentIndex === 0 ? this.media.length - 1 : (this.currentIndex - 1)
this.$store.dispatch('setCurrent', this.media[prevIndex])
}
},
goNext () {
if (this.canNavigate) {
const nextIndex = this.currentIndex === this.media.length - 1 ? 0 : (this.currentIndex + 1)
this.$store.dispatch('setCurrent', this.media[nextIndex])
}
},
handleKeyupEvent (e) {
if (this.showing && e.keyCode === 27) { // escape
this.hide()
}
},
handleKeydownEvent (e) {
if (!this.showing) {
return
}
if (e.keyCode === 39) { // arrow right
this.goNext()
} else if (e.keyCode === 37) { // arrow left
this.goPrev()
}
} }
},
mounted () {
document.addEventListener('keyup', this.handleKeyupEvent)
document.addEventListener('keydown', this.handleKeydownEvent)
},
destroyed () {
document.removeEventListener('keyup', this.handleKeyupEvent)
document.removeEventListener('keydown', this.handleKeydownEvent)
} }
} }
+78 -3
View File
@@ -8,6 +8,22 @@
:controls="true" :controls="true"
@click.stop.native=""> @click.stop.native="">
</VideoAttachment> </VideoAttachment>
<button
:title="$t('media_modal.previous')"
class="modal-view-button-arrow modal-view-button-arrow--prev"
v-if="canNavigate"
@click.stop.prevent="goPrev"
>
<i class="icon-left-open arrow-icon" />
</button>
<button
:title="$t('media_modal.next')"
class="modal-view-button-arrow modal-view-button-arrow--next"
v-if="canNavigate"
@click.stop.prevent="goNext"
>
<i class="icon-right-open arrow-icon" />
</button>
</div> </div>
</template> </template>
@@ -19,15 +35,29 @@
.modal-view { .modal-view {
z-index: 1000; z-index: 1000;
position: fixed; position: fixed;
width: 100vw;
height: 100vh;
top: 0; top: 0;
left: 0; left: 0;
right: 0;
bottom: 0;
display: flex; display: flex;
justify-content: center; justify-content: center;
align-items: center; align-items: center;
background-color: rgba(0, 0, 0, 0.5); background-color: rgba(0, 0, 0, 0.5);
cursor: pointer;
&:hover {
.modal-view-button-arrow {
opacity: 0.75;
&:focus,
&:hover {
outline: none;
box-shadow: none;
}
&:hover {
opacity: 1;
}
}
}
} }
.modal-image { .modal-image {
@@ -35,4 +65,49 @@
max-height: 90%; max-height: 90%;
box-shadow: 0px 5px 15px 0 rgba(0, 0, 0, 0.5); box-shadow: 0px 5px 15px 0 rgba(0, 0, 0, 0.5);
} }
.modal-view-button-arrow {
position: absolute;
display: block;
top: 50%;
margin-top: -50px;
width: 70px;
height: 100px;
border: 0;
padding: 0;
opacity: 0;
box-shadow: none;
background: none;
appearance: none;
overflow: visible;
cursor: pointer;
transition: opacity 333ms cubic-bezier(.4,0,.22,1);
.arrow-icon {
position: absolute;
top: 35px;
height: 30px;
width: 32px;
font-size: 14px;
line-height: 30px;
color: #FFF;
text-align: center;
background-color: rgba(0,0,0,.3);
}
&--prev {
left: 0;
.arrow-icon {
left: 6px;
}
}
&--next {
right: 0;
.arrow-icon {
right: 6px;
}
}
}
</style> </style>
+37
View File
@@ -0,0 +1,37 @@
import BasicUserCard from '../basic_user_card/basic_user_card.vue'
const MuteCard = {
props: ['userId'],
data () {
return {
progress: false
}
},
computed: {
user () {
return this.$store.getters.userById(this.userId)
},
muted () {
return this.user.muted
}
},
components: {
BasicUserCard
},
methods: {
unmuteUser () {
this.progress = true
this.$store.dispatch('unmuteUser', this.user.id).then(() => {
this.progress = false
})
},
muteUser () {
this.progress = true
this.$store.dispatch('muteUser', this.user.id).then(() => {
this.progress = false
})
}
}
}
export default MuteCard
+24
View File
@@ -0,0 +1,24 @@
<template>
<basic-user-card :user="user">
<template slot="secondary-area">
<button class="btn btn-default" @click="unmuteUser" :disabled="progress" v-if="muted">
<template v-if="progress">
{{ $t('user_card.unmute_progress') }}
</template>
<template v-else>
{{ $t('user_card.unmute') }}
</template>
</button>
<button class="btn btn-default" @click="muteUser" :disabled="progress" v-else>
<template v-if="progress">
{{ $t('user_card.mute_progress') }}
</template>
<template v-else>
{{ $t('user_card.mute') }}
</template>
</button>
</template>
</basic-user-card>
</template>
<script src="./mute_card.js"></script>
+13
View File
@@ -1,10 +1,23 @@
import followRequestFetcher from '../../services/follow_request_fetcher/follow_request_fetcher.service'
const NavPanel = { const NavPanel = {
created () {
if (this.currentUser && this.currentUser.locked) {
const store = this.$store
const credentials = store.state.users.currentUser.credentials
followRequestFetcher.startFetching({ store, credentials })
}
},
computed: { computed: {
currentUser () { currentUser () {
return this.$store.state.users.currentUser return this.$store.state.users.currentUser
}, },
chat () { chat () {
return this.$store.state.chat.channel return this.$store.state.chat.channel
},
followRequestCount () {
return this.$store.state.api.followRequests.length
} }
} }
} }
+10 -1
View File
@@ -19,7 +19,10 @@
</li> </li>
<li v-if='currentUser && currentUser.locked'> <li v-if='currentUser && currentUser.locked'>
<router-link :to="{ name: 'friend-requests' }"> <router-link :to="{ name: 'friend-requests' }">
{{ $t("nav.friend_requests") }} {{ $t("nav.friend_requests")}}
<span v-if='followRequestCount > 0' class="badge follow-request-count">
{{followRequestCount}}
</span>
</router-link> </router-link>
</li> </li>
<li> <li>
@@ -52,6 +55,12 @@
padding: 0; padding: 0;
} }
.follow-request-count {
margin: -6px 10px;
background-color: $fallback--bg;
background-color: var(--input, $fallback--faint);
}
.nav-panel li { .nav-panel li {
border-bottom: 1px solid; border-bottom: 1px solid;
border-color: $fallback--border; border-color: $fallback--border;
+5 -1
View File
@@ -25,7 +25,11 @@
<small>{{$t('notifications.followed_you')}}</small> <small>{{$t('notifications.followed_you')}}</small>
</span> </span>
</div> </div>
<small class="timeago"><router-link v-if="notification.status" :to="{ name: 'conversation', params: { id: notification.status.id } }"><timeago :since="notification.action.created_at" :auto-update="240"></timeago></router-link></small> <div class="timeago">
<router-link v-if="notification.status" :to="{ name: 'conversation', params: { id: notification.status.id } }" class="faint-link">
<timeago :since="notification.action.created_at" :auto-update="240"></timeago>
</router-link>
</div>
</span> </span>
<div class="follow-text" v-if="notification.type === 'follow'"> <div class="follow-text" v-if="notification.type === 'follow'">
<router-link :to="userProfileLink(notification.action.user)"> <router-link :to="userProfileLink(notification.action.user)">
@@ -103,6 +103,7 @@
flex: 1 1 0; flex: 1 1 0;
display: flex; display: flex;
flex-wrap: nowrap; flex-wrap: nowrap;
justify-content: space-between;
.name-and-action { .name-and-action {
flex: 1; flex: 1;
@@ -123,9 +124,9 @@
object-fit: contain object-fit: contain
} }
} }
.timeago { .timeago {
float: right; margin-right: .2em;
font-size: 12px;
} }
.icon-retweet.lit { .icon-retweet.lit {
@@ -30,7 +30,9 @@
@drop="fileDrop" @drop="fileDrop"
@dragover.prevent="fileDrag" @dragover.prevent="fileDrag"
@input="resize" @input="resize"
@paste="paste"> @paste="paste"
:disabled="posting"
>
</textarea> </textarea>
<div class="visibility-tray"> <div class="visibility-tray">
<span class="text-format" v-if="formattingOptionsEnabled"> <span class="text-format" v-if="formattingOptionsEnabled">
@@ -118,6 +120,14 @@
} }
} }
.post-status-form {
.visibility-tray {
display: flex;
justify-content: space-between;
flex-direction: row-reverse;
}
}
.post-status-form, .login { .post-status-form, .login {
.form-bottom { .form-bottom {
display: flex; display: flex;
+8 -4
View File
@@ -9,7 +9,7 @@
<div class='text-fields'> <div class='text-fields'>
<div class='form-group' :class="{ 'form-group--error': $v.user.username.$error }"> <div class='form-group' :class="{ 'form-group--error': $v.user.username.$error }">
<label class='form--label' for='sign-up-username'>{{$t('login.username')}}</label> <label class='form--label' for='sign-up-username'>{{$t('login.username')}}</label>
<input :disabled="isPending" v-model.trim='$v.user.username.$model' class='form-control' id='sign-up-username' placeholder='e.g. lain'> <input :disabled="isPending" v-model.trim='$v.user.username.$model' class='form-control' id='sign-up-username' :placeholder="$t('registration.username_placeholder')">
</div> </div>
<div class="form-error" v-if="$v.user.username.$dirty"> <div class="form-error" v-if="$v.user.username.$dirty">
<ul> <ul>
@@ -21,7 +21,7 @@
<div class='form-group' :class="{ 'form-group--error': $v.user.fullname.$error }"> <div class='form-group' :class="{ 'form-group--error': $v.user.fullname.$error }">
<label class='form--label' for='sign-up-fullname'>{{$t('registration.fullname')}}</label> <label class='form--label' for='sign-up-fullname'>{{$t('registration.fullname')}}</label>
<input :disabled="isPending" v-model.trim='$v.user.fullname.$model' class='form-control' id='sign-up-fullname' placeholder='e.g. Lain Iwakura'> <input :disabled="isPending" v-model.trim='$v.user.fullname.$model' class='form-control' id='sign-up-fullname' :placeholder="$t('registration.fullname_placeholder')">
</div> </div>
<div class="form-error" v-if="$v.user.fullname.$dirty"> <div class="form-error" v-if="$v.user.fullname.$dirty">
<ul> <ul>
@@ -44,8 +44,8 @@
</div> </div>
<div class='form-group'> <div class='form-group'>
<label class='form--label' for='bio'>{{$t('registration.bio')}}</label> <label class='form--label' for='bio'>{{$t('registration.bio')}} ({{$t('general.optional')}})</label>
<input :disabled="isPending" v-model='user.bio' class='form-control' id='bio'> <textarea :disabled="isPending" v-model='user.bio' class='form-control' id='bio' :placeholder="$t('registration.bio_placeholder')"></textarea>
</div> </div>
<div class='form-group' :class="{ 'form-group--error': $v.user.password.$error }"> <div class='form-group' :class="{ 'form-group--error': $v.user.password.$error }">
@@ -139,6 +139,10 @@ $validations-cRed: #f04124;
flex-direction: column; flex-direction: column;
} }
textarea {
min-height: 100px;
}
.form-group { .form-group {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
+5
View File
@@ -12,6 +12,7 @@ const settings = {
return { return {
hideAttachmentsLocal: user.hideAttachments, hideAttachmentsLocal: user.hideAttachments,
hideAttachmentsInConvLocal: user.hideAttachmentsInConv, hideAttachmentsInConvLocal: user.hideAttachmentsInConv,
maxThumbnails: user.maxThumbnails,
hideNsfwLocal: user.hideNsfw, hideNsfwLocal: user.hideNsfw,
useOneClickNsfw: user.useOneClickNsfw, useOneClickNsfw: user.useOneClickNsfw,
hideISPLocal: user.hideISP, hideISPLocal: user.hideISP,
@@ -186,6 +187,10 @@ const settings = {
}, },
useContainFit (value) { useContainFit (value) {
this.$store.dispatch('setOption', { name: 'useContainFit', value }) this.$store.dispatch('setOption', { name: 'useContainFit', value })
},
maxThumbnails (value) {
value = this.maxThumbnails = Math.floor(Math.max(value, 0))
this.$store.dispatch('setOption', { name: 'maxThumbnails', value })
} }
} }
} }
+9 -15
View File
@@ -136,6 +136,10 @@
<input type="checkbox" id="hideAttachmentsInConv" v-model="hideAttachmentsInConvLocal"> <input type="checkbox" id="hideAttachmentsInConv" v-model="hideAttachmentsInConvLocal">
<label for="hideAttachmentsInConv">{{$t('settings.hide_attachments_in_convo')}}</label> <label for="hideAttachmentsInConv">{{$t('settings.hide_attachments_in_convo')}}</label>
</li> </li>
<li>
<label for="maxThumbnails">{{$t('settings.max_thumbnails')}}</label>
<input class="number-input" type="number" id="maxThumbnails" v-model.number="maxThumbnails" min="0" step="1">
</li>
<li> <li>
<input type="checkbox" id="hideNsfw" v-model="hideNsfwLocal"> <input type="checkbox" id="hideNsfw" v-model="hideNsfwLocal">
<label for="hideNsfw">{{$t('settings.nsfw_clickthrough')}}</label> <label for="hideNsfw">{{$t('settings.nsfw_clickthrough')}}</label>
@@ -146,7 +150,7 @@
<label for="preloadImage">{{$t('settings.preload_images')}}</label> <label for="preloadImage">{{$t('settings.preload_images')}}</label>
</li> </li>
<li> <li>
<input type="checkbox" id="useOneClickNsfw" v-model="useOneClickNsfw"> <input :disabled="!hideNsfwLocal" type="checkbox" id="useOneClickNsfw" v-model="useOneClickNsfw">
<label for="useOneClickNsfw">{{$t('settings.use_one_click_nsfw')}}</label> <label for="useOneClickNsfw">{{$t('settings.use_one_click_nsfw')}}</label>
</li> </li>
</ul> </ul>
@@ -311,25 +315,15 @@
color: $fallback--cRed; color: $fallback--cRed;
} }
.old-avatar {
width: 128px;
border-radius: $fallback--avatarRadius;
border-radius: var(--avatarRadius, $fallback--avatarRadius);
}
.new-avatar {
object-fit: cover;
width: 128px;
height: 128px;
border-radius: $fallback--avatarRadius;
border-radius: var(--avatarRadius, $fallback--avatarRadius);
}
.btn { .btn {
min-height: 28px; min-height: 28px;
min-width: 10em; min-width: 10em;
padding: 0 2em; padding: 0 2em;
} }
.number-input {
max-width: 6em;
}
} }
.select-multiple { .select-multiple {
display: flex; display: flex;
@@ -32,6 +32,9 @@ const SideDrawer = {
}, },
sitename () { sitename () {
return this.$store.state.instance.name return this.$store.state.instance.name
},
followRequestCount () {
return this.$store.state.api.followRequests.length
} }
}, },
methods: { methods: {
@@ -45,6 +45,10 @@
<li v-if="currentUser && currentUser.locked" @click="toggleDrawer"> <li v-if="currentUser && currentUser.locked" @click="toggleDrawer">
<router-link to='/friend-requests'> <router-link to='/friend-requests'>
{{ $t("nav.friend_requests") }} {{ $t("nav.friend_requests") }}
<span v-if='followRequestCount > 0' class="badge follow-request-count">
{{followRequestCount}}
</span>
</router-link> </router-link>
</li> </li>
<li @click="toggleDrawer"> <li @click="toggleDrawer">
+6 -4
View File
@@ -23,7 +23,7 @@ const Status = {
'highlight', 'highlight',
'compact', 'compact',
'replies', 'replies',
'noReplyLinks', 'isPreview',
'noHeading', 'noHeading',
'inlineExpanded' 'inlineExpanded'
], ],
@@ -40,8 +40,7 @@ const Status = {
expandingSubject: typeof this.$store.state.config.collapseMessageWithSubject === 'undefined' expandingSubject: typeof this.$store.state.config.collapseMessageWithSubject === 'undefined'
? !this.$store.state.instance.collapseMessageWithSubject ? !this.$store.state.instance.collapseMessageWithSubject
: !this.$store.state.config.collapseMessageWithSubject, : !this.$store.state.config.collapseMessageWithSubject,
betterShadow: this.$store.state.interface.browserSupport.cssFilter, betterShadow: this.$store.state.interface.browserSupport.cssFilter
maxAttachments: 9
} }
}, },
computed: { computed: {
@@ -225,7 +224,7 @@ const Status = {
attachmentSize () { attachmentSize () {
if ((this.$store.state.config.hideAttachments && !this.inConversation) || if ((this.$store.state.config.hideAttachments && !this.inConversation) ||
(this.$store.state.config.hideAttachmentsInConv && this.inConversation) || (this.$store.state.config.hideAttachmentsInConv && this.inConversation) ||
(this.status.attachments.length > this.maxAttachments)) { (this.status.attachments.length > this.maxThumbnails)) {
return 'hide' return 'hide'
} else if (this.compact) { } else if (this.compact) {
return 'small' return 'small'
@@ -249,6 +248,9 @@ const Status = {
return this.status.attachments.filter( return this.status.attachments.filter(
file => !fileType.fileMatchesSomeType(this.galleryTypes, file) file => !fileType.fileMatchesSomeType(this.galleryTypes, file)
) )
},
maxThumbnails () {
return this.$store.state.config.maxThumbnails
} }
}, },
components: { components: {
+165 -110
View File
@@ -1,6 +1,6 @@
<template> <template>
<div class="status-el" v-if="!hideStatus" :class="[{ 'status-el_focused': isFocused }, { 'status-conversation': inlineExpanded }]"> <div class="status-el" v-if="!hideStatus" :class="[{ 'status-el_focused': isFocused }, { 'status-conversation': inlineExpanded }]">
<template v-if="muted && !noReplyLinks"> <template v-if="muted && !isPreview">
<div class="media status container muted"> <div class="media status container muted">
<small> <small>
<router-link :to="userProfileLink"> <router-link :to="userProfileLink">
@@ -13,7 +13,7 @@
</template> </template>
<template v-else> <template v-else>
<div v-if="retweet && !noHeading" :class="[repeaterClass, { highlighted: repeaterStyle }]" :style="[repeaterStyle]" class="media container retweet-info"> <div v-if="retweet && !noHeading" :class="[repeaterClass, { highlighted: repeaterStyle }]" :style="[repeaterStyle]" class="media container retweet-info">
<UserAvatar v-if="retweet" :betterShadow="betterShadow" :src="statusoid.user.profile_image_url_original"/> <UserAvatar class="media-left" v-if="retweet" :betterShadow="betterShadow" :src="statusoid.user.profile_image_url_original"/>
<div class="media-body faint"> <div class="media-body faint">
<span class="user-name"> <span class="user-name">
<router-link v-if="retweeterHtml" :to="retweeterProfileLink" v-html="retweeterHtml"/> <router-link v-if="retweeterHtml" :to="retweeterProfileLink" v-html="retweeterHtml"/>
@@ -31,57 +31,69 @@
</router-link> </router-link>
</div> </div>
<div class="status-body"> <div class="status-body">
<div class="usercard media-body" v-if="userExpanded"> <div class="usercard" v-if="userExpanded">
<user-card-content :user="status.user" :switcher="false"></user-card-content> <user-card-content :user="status.user" :switcher="false"></user-card-content>
</div> </div>
<div v-if="!noHeading" class="media-body container media-heading"> <div v-if="!noHeading" class="media-heading">
<div class="media-heading-left"> <div class="heading-name-row">
<div class="name-and-links"> <div class="name-and-account-name">
<h4 class="user-name" v-if="status.user.name_html" v-html="status.user.name_html"></h4> <h4 class="user-name" v-if="status.user.name_html" v-html="status.user.name_html"></h4>
<h4 class="user-name" v-else>{{status.user.name}}</h4> <h4 class="user-name" v-else>{{status.user.name}}</h4>
<span class="links"> <router-link class="account-name" :to="userProfileLink">
<router-link :to="userProfileLink"> {{status.user.screen_name}}
{{status.user.screen_name}} </router-link>
</router-link> </div>
<span v-if="isReply" class="faint reply-info">
<i class="icon-right-open"></i> <span class="heading-right">
<router-link :to="replyProfileLink"> <router-link class="timeago faint-link" :to="{ name: 'conversation', params: { id: status.id } }">
{{replyToName}} <timeago :since="status.created_at" :auto-update="60"></timeago>
</router-link> </router-link>
</span> <div class="button-icon visibility-icon" v-if="status.visibility">
<a v-if="isReply && !noReplyLinks" href="#" @click.prevent="gotoOriginal(status.in_reply_to_status_id)" :aria-label="$t('tool_tip.reply')"> <i :class="visibilityIcon(status.visibility)" :title="status.visibility | capitalize"></i>
<i class="button-icon icon-reply" @mouseenter="replyEnter(status.in_reply_to_status_id, $event)" @mouseout="replyLeave()"></i> </div>
<a :href="status.external_url" target="_blank" v-if="!status.is_local && !isPreview" class="source_url" title="Source">
<i class="button-icon icon-link-ext-alt"></i>
</a>
<template v-if="expandable && !isPreview">
<a href="#" @click.prevent="toggleExpanded" title="Expand">
<i class="button-icon icon-plus-squared"></i>
</a> </a>
</template>
<a href="#" @click.prevent="toggleMute" v-if="unmuted"><i class="button-icon icon-eye-off"></i></a>
</span>
</div>
<div class="heading-reply-row">
<div v-if="isReply" class="reply-to-and-accountname">
<a class="reply-to"
href="#" @click.prevent="gotoOriginal(status.in_reply_to_status_id)"
:aria-label="$t('tool_tip.reply')"
@mouseenter.prevent.stop="replyEnter(status.in_reply_to_status_id, $event)"
@mouseleave.prevent.stop="replyLeave()"
>
<i class="button-icon icon-reply" v-if="!isPreview"></i>
<span class="faint-link reply-to-text">{{$t('status.reply_to')}}</span>
</a>
<router-link :to="replyProfileLink">
{{replyToName}}
</router-link>
<span class="faint replies-separator" v-if="replies && replies.length">
-
</span> </span>
</div> </div>
<h4 class="replies" v-if="inConversation && !noReplyLinks"> <div class="replies" v-if="inConversation && !isPreview">
<small v-if="replies.length">Replies:</small> <span class="faint" v-if="replies && replies.length">{{$t('status.replies_list')}}</span>
<small class="reply-link" v-bind:key="reply.id" v-for="reply in replies"> <span class="reply-link faint" v-if="replies" v-for="reply in replies">
<a href="#" @click.prevent="gotoOriginal(reply.id)" @mouseenter="replyEnter(reply.id, $event)" @mouseout="replyLeave()">{{reply.name}}&nbsp;</a> <a href="#" @click.prevent="gotoOriginal(reply.id)" @mouseenter="replyEnter(reply.id, $event)" @mouseout="replyLeave()">{{reply.name}}</a>
</small> </span>
</h4>
</div>
<div class="media-heading-right">
<router-link class="timeago" :to="{ name: 'conversation', params: { id: status.id } }">
<timeago :since="status.created_at" :auto-update="60"></timeago>
</router-link>
<div class="button-icon visibility-icon" v-if="status.visibility">
<i :class="visibilityIcon(status.visibility)" :title="status.visibility | capitalize"></i>
</div> </div>
<a :href="status.external_url" target="_blank" v-if="!status.is_local" class="source_url" title="Source">
<i class="button-icon icon-link-ext-alt"></i>
</a>
<template v-if="expandable">
<a href="#" @click.prevent="toggleExpanded" title="Expand">
<i class="button-icon icon-plus-squared"></i>
</a>
</template>
<a href="#" @click.prevent="toggleMute" v-if="unmuted"><i class="button-icon icon-eye-off"></i></a>
</div> </div>
</div> </div>
<div v-if="showPreview" class="status-preview-container"> <div v-if="showPreview" class="status-preview-container">
<status class="status-preview" v-if="preview" :noReplyLinks="true" :statusoid="preview" :compact=true></status> <status class="status-preview" v-if="preview" :isPreview="true" :statusoid="preview" :compact=true></status>
<div class="status-preview status-preview-loading" v-else> <div class="status-preview status-preview-loading" v-else>
<i class="icon-spin4 animate-spin"></i> <i class="icon-spin4 animate-spin"></i>
</div> </div>
@@ -123,7 +135,7 @@
<link-preview :card="status.card" :size="attachmentSize" :nsfw="nsfwClickthrough" /> <link-preview :card="status.card" :size="attachmentSize" :nsfw="nsfwClickthrough" />
</div> </div>
<div v-if="!noHeading && !noReplyLinks" class='status-actions media-body'> <div v-if="!noHeading && !isPreview" class='status-actions media-body'>
<div v-if="loggedIn"> <div v-if="loggedIn">
<a href="#" v-on:click.prevent="toggleReplying" :title="$t('tool_tip.reply')"> <a href="#" v-on:click.prevent="toggleReplying" :title="$t('tool_tip.reply')">
<i class="button-icon icon-reply" :class="{'icon-reply-active': replying}"></i> <i class="button-icon icon-reply" :class="{'icon-reply-active': replying}"></i>
@@ -147,6 +159,8 @@
<style lang="scss"> <style lang="scss">
@import '../../_variables.scss'; @import '../../_variables.scss';
$status-margin: 0.75em;
.status-body { .status-body {
flex: 1; flex: 1;
min-width: 0; min-width: 0;
@@ -202,13 +216,16 @@
} }
} }
.media-left {
margin-right: $status-margin;
}
.status-el { .status-el {
hyphens: auto; hyphens: auto;
overflow-wrap: break-word; overflow-wrap: break-word;
word-wrap: break-word; word-wrap: break-word;
word-break: break-word; word-break: break-word;
border-left-width: 0px; border-left-width: 0px;
line-height: 18px;
min-width: 0; min-width: 0;
border-color: $fallback--border; border-color: $fallback--border;
border-color: var(--border, $fallback--border); border-color: var(--border, $fallback--border);
@@ -229,22 +246,34 @@
.media-body { .media-body {
flex: 1; flex: 1;
padding: 0; padding: 0;
margin: 0 0 0.25em 0.8em;
} }
.usercard { .usercard {
margin-bottom: .7em margin: 0;
margin-bottom: $status-margin;
}
.user-name {
white-space: nowrap;
font-size: 14px;
overflow: hidden;
flex-shrink: 0;
max-width: 85%;
font-weight: bold;
img {
width: 14px;
height: 14px;
vertical-align: middle;
object-fit: contain
}
} }
.media-heading { .media-heading {
flex-wrap: nowrap;
line-height: 18px;
}
.media-heading-left {
padding: 0; padding: 0;
vertical-align: bottom; vertical-align: bottom;
flex-basis: 100%; flex-basis: 100%;
margin-bottom: 0.5em;
a { a {
display: inline-block; display: inline-block;
@@ -254,83 +283,102 @@
small { small {
font-weight: lighter; font-weight: lighter;
} }
h4 {
white-space: nowrap; .heading-name-row {
font-size: 14px;
margin-right: 0.25em;
overflow: hidden;
text-overflow: ellipsis;
}
.name-and-links {
padding: 0; padding: 0;
flex: 1 0;
display: flex; display: flex;
flex-wrap: wrap; justify-content: space-between;
align-items: baseline; line-height: 18px;
.name-and-account-name {
display: flex;
min-width: 0;
}
.user-name { .user-name {
margin-right: .45em; flex-shrink: 1;
margin-right: 0.4em;
overflow: hidden;
text-overflow: ellipsis;
}
img { .account-name {
width: 14px; min-width: 1.6em;
height: 14px; margin-right: 0.4em;
vertical-align: middle; white-space: nowrap;
object-fit: contain overflow: hidden;
} text-overflow: ellipsis;
flex: 1 1 0;
} }
} }
.links { .heading-right {
display: flex; display: flex;
flex-shrink: 0;
}
.timeago {
margin-right: 0.2em;
}
.heading-reply-row {
align-content: baseline;
font-size: 12px; font-size: 12px;
color: $fallback--link; line-height: 18px;
color: var(--link, $fallback--link);
max-width: 100%; max-width: 100%;
display: flex;
flex-wrap: wrap;
align-items: stretch;
a { a {
max-width: 100%; max-width: 100%;
text-overflow: ellipsis; text-overflow: ellipsis;
overflow: hidden; overflow: hidden;
white-space: nowrap; white-space: nowrap;
} }
& > span { }
text-overflow: ellipsis;
overflow: hidden; .reply-to-and-accountname {
white-space: nowrap; display: flex;
} height: 18px;
& > a:last-child { margin-right: 0.5em;
flex-shrink: 0; overflow: hidden;
max-width: 100%;
.icon-reply {
transform: scaleX(-1);
} }
} }
.reply-info { .reply-info {
display: flex; display: flex;
} }
.reply-to {
display: flex;
}
.reply-to-text {
overflow: hidden;
text-overflow: ellipsis;
margin: 0 0.4em 0 0.2em;
}
.replies-separator {
margin-left: 0.4em;
}
.replies { .replies {
line-height: 16px; line-height: 18px;
}
.reply-link {
margin-right: 0.2em;
}
}
.media-heading-right {
display: inline-flex;
flex-shrink: 0;
flex-wrap: nowrap;
margin-left: .25em;
align-self: baseline;
.timeago {
margin-right: 0.2em;
font-size: 12px; font-size: 12px;
align-self: last baseline; display: flex;
flex-wrap: wrap;
& > * {
margin-right: 0.4em;
}
} }
> * { .reply-link {
margin-left: 0.2em; height: 17px;
}
a:hover i {
color: $fallback--text;
color: var(--text, $fallback--text);
} }
} }
@@ -366,14 +414,19 @@
} }
.status-content { .status-content {
margin-right: 0.5em;
font-family: var(--postFont, sans-serif); font-family: var(--postFont, sans-serif);
line-height: 1.4em;
img, video { img, video {
max-width: 100%; max-width: 100%;
max-height: 400px; max-height: 400px;
vertical-align: middle; vertical-align: middle;
object-fit: contain; object-fit: contain;
&.emoji {
width: 32px;
height: 32px;
}
} }
blockquote { blockquote {
@@ -390,9 +443,11 @@
} }
p { p {
margin: 0; margin: 0 0 1em 0;
margin-top: 0.2em; }
margin-bottom: 0.5em;
p:last-child {
margin: 0 0 0 0;
} }
h1 { h1 {
@@ -417,7 +472,7 @@
} }
.retweet-info { .retweet-info {
padding: 0.4em 0.6em 0 0.6em; padding: 0.4em $status-margin;
margin: 0; margin: 0;
.avatar.still-image { .avatar.still-image {
@@ -488,10 +543,10 @@
.status-actions { .status-actions {
width: 100%; width: 100%;
display: flex; display: flex;
margin-top: $status-margin;
div, favorite-button { div, favorite-button {
padding-top: 0.25em; max-width: 4em;
max-width: 6em;
flex: 1; flex: 1;
} }
} }
@@ -517,9 +572,9 @@
.status { .status {
display: flex; display: flex;
padding: 0.6em; padding: $status-margin;
&.is-retweet { &.is-retweet {
padding-top: 0.1em; padding-top: 0;
} }
} }
+13 -5
View File
@@ -1,7 +1,6 @@
import Status from '../status/status.vue' import Status from '../status/status.vue'
import timelineFetcher from '../../services/timeline_fetcher/timeline_fetcher.service.js' import timelineFetcher from '../../services/timeline_fetcher/timeline_fetcher.service.js'
import StatusOrConversation from '../status_or_conversation/status_or_conversation.vue' import StatusOrConversation from '../status_or_conversation/status_or_conversation.vue'
import UserCard from '../user_card/user_card.vue'
import { throttle } from 'lodash' import { throttle } from 'lodash'
const Timeline = { const Timeline = {
@@ -11,7 +10,8 @@ const Timeline = {
'title', 'title',
'userId', 'userId',
'tag', 'tag',
'embedded' 'embedded',
'count'
], ],
data () { data () {
return { return {
@@ -43,8 +43,7 @@ const Timeline = {
}, },
components: { components: {
Status, Status,
StatusOrConversation, StatusOrConversation
UserCard
}, },
created () { created () {
const store = this.$store const store = this.$store
@@ -53,6 +52,8 @@ const Timeline = {
window.addEventListener('scroll', this.scrollLoad) window.addEventListener('scroll', this.scrollLoad)
if (this.timelineName === 'friends' && !credentials) { return false }
timelineFetcher.fetchAndUpdate({ timelineFetcher.fetchAndUpdate({
store, store,
credentials, credentials,
@@ -67,14 +68,21 @@ const Timeline = {
document.addEventListener('visibilitychange', this.handleVisibilityChange, false) document.addEventListener('visibilitychange', this.handleVisibilityChange, false)
this.unfocused = document.hidden this.unfocused = document.hidden
} }
window.addEventListener('keydown', this.handleShortKey)
}, },
destroyed () { destroyed () {
window.removeEventListener('scroll', this.scrollLoad) window.removeEventListener('scroll', this.scrollLoad)
window.removeEventListener('keydown', this.handleShortKey)
if (typeof document.hidden !== 'undefined') document.removeEventListener('visibilitychange', this.handleVisibilityChange, false) if (typeof document.hidden !== 'undefined') document.removeEventListener('visibilitychange', this.handleVisibilityChange, false)
this.$store.commit('setLoading', { timeline: this.timelineName, value: false }) this.$store.commit('setLoading', { timeline: this.timelineName, value: false })
}, },
methods: { methods: {
handleShortKey (e) {
if (e.key === '.') this.showNewStatuses()
},
showNewStatuses () { showNewStatuses () {
if (this.newStatusCount === 0) return
if (this.timeline.flushMarker !== 0) { if (this.timeline.flushMarker !== 0) {
this.$store.commit('clearTimeline', { timeline: this.timelineName }) this.$store.commit('clearTimeline', { timeline: this.timelineName })
this.$store.commit('queueFlush', { timeline: this.timelineName, id: 0 }) this.$store.commit('queueFlush', { timeline: this.timelineName, id: 0 })
@@ -98,7 +106,7 @@ const Timeline = {
tag: this.tag tag: this.tag
}).then(statuses => { }).then(statuses => {
store.commit('setLoading', { timeline: this.timelineName, value: false }) store.commit('setLoading', { timeline: this.timelineName, value: false })
if (statuses.length === 0) { if (statuses && statuses.length === 0) {
this.bottomedOut = true this.bottomedOut = true
} }
}) })
+4 -1
View File
@@ -20,7 +20,10 @@
</div> </div>
</div> </div>
<div :class="classes.footer"> <div :class="classes.footer">
<div v-if="bottomedOut" class="new-status-notification text-center panel-footer faint"> <div v-if="count===0" class="new-status-notification text-center panel-footer faint">
{{$t('timeline.no_statuses')}}
</div>
<div v-else-if="bottomedOut" class="new-status-notification text-center panel-footer faint">
{{$t('timeline.no_more_statuses')}} {{$t('timeline.no_more_statuses')}}
</div> </div>
<a v-else-if="!timeline.loading" href="#" v-on:click.prevent='fetchOlderStatuses()'> <a v-else-if="!timeline.loading" href="#" v-on:click.prevent='fetchOlderStatuses()'>
-64
View File
@@ -1,64 +0,0 @@
import UserCardContent from '../user_card_content/user_card_content.vue'
import UserAvatar from '../user_avatar/user_avatar.vue'
import generateProfileLink from 'src/services/user_profile_link_generator/user_profile_link_generator'
import { requestFollow, requestUnfollow } from '../../services/follow_manipulate/follow_manipulate'
const UserCard = {
props: [
'user',
'noFollowsYou',
'showApproval'
],
data () {
return {
userExpanded: false,
followRequestInProgress: false,
followRequestSent: false,
updated: false
}
},
components: {
UserCardContent,
UserAvatar
},
computed: {
currentUser () { return this.$store.state.users.currentUser },
following () { return this.updated ? this.updated.following : this.user.following },
showFollow () {
return !this.showApproval && (!this.following || this.updated && !this.updated.following)
}
},
methods: {
toggleUserExpanded () {
this.userExpanded = !this.userExpanded
},
approveUser () {
this.$store.state.api.backendInteractor.approveUser(this.user.id)
this.$store.dispatch('removeFollowRequest', this.user)
},
denyUser () {
this.$store.state.api.backendInteractor.denyUser(this.user.id)
this.$store.dispatch('removeFollowRequest', this.user)
},
userProfileLink (user) {
return generateProfileLink(user.id, user.screen_name, this.$store.state.instance.restrictedNicknames)
},
followUser () {
this.followRequestInProgress = true
requestFollow(this.user, this.$store).then(({ sent, updated }) => {
this.followRequestInProgress = false
this.followRequestSent = sent
this.updated = updated
})
},
unfollowUser () {
this.followRequestInProgress = true
requestUnfollow(this.user, this.$store).then(({ updated }) => {
this.followRequestInProgress = false
this.updated = updated
})
}
}
}
export default UserCard
-159
View File
@@ -1,159 +0,0 @@
<template>
<div class="card">
<router-link :to="userProfileLink(user)">
<UserAvatar class="avatar" @click.prevent.native="toggleUserExpanded" :src="user.profile_image_url"/>
</router-link>
<div class="user-card-main-content">
<div class="usercard" v-if="userExpanded">
<user-card-content :user="user" :switcher="false"></user-card-content>
</div>
<div class="name-and-screen-name" v-if="!userExpanded">
<div :title="user.name" class="user-name">
<span v-if="user.name_html" v-html="user.name_html"></span>
<span v-else>{{ user.name }}</span>
</div>
<div class="user-link-action">
<router-link class='user-screen-name' :to="userProfileLink(user)">
@{{user.screen_name}}
</router-link>
</div>
</div>
<div class="follow-box" v-if="!userExpanded">
<span class="faint" v-if="!noFollowsYou && user.follows_you">
{{ currentUser.id == user.id ? $t('user_card.its_you') : $t('user_card.follows_you') }}
</span>
<button
v-if="showFollow"
class="btn btn-default"
@click="followUser"
:disabled="followRequestInProgress"
:title="followRequestSent ? $t('user_card.follow_again') : ''"
>
<template v-if="followRequestInProgress">
{{ $t('user_card.follow_progress') }}
</template>
<template v-else-if="followRequestSent">
{{ $t('user_card.follow_sent') }}
</template>
<template v-else>
{{ $t('user_card.follow') }}
</template>
</button>
<button v-if="following" class="btn btn-default pressed" @click="unfollowUser" :disabled="followRequestInProgress">
<template v-if="followRequestInProgress">
{{ $t('user_card.follow_progress') }}
</template>
<template v-else>
{{ $t('user_card.follow_unfollow') }}
</template>
</button>
</div>
<div class="approval" v-if="showApproval">
<button class="btn btn-default" @click="approveUser">{{ $t('user_card.approve') }}</button>
<button class="btn btn-default" @click="denyUser">{{ $t('user_card.deny') }}</button>
</div>
</div>
</div>
</template>
<script src="./user_card.js"></script>
<style lang="scss">
@import '../../_variables.scss';
.user-card-main-content {
display: flex;
flex-direction: column;
flex: 1 1 100%;
margin-left: 0.7em;
min-width: 0;
}
.name-and-screen-name {
text-align: left;
width: 100%;
.user-name {
img {
object-fit: contain;
height: 16px;
width: 16px;
vertical-align: middle;
}
}
.user-link-action {
display: flex;
align-items: flex-start;
justify-content: space-between;
}
}
.card {
display: flex;
flex: 1 0;
padding-top: 0.6em;
padding-right: 1em;
padding-bottom: 0.6em;
padding-left: 1em;
border-bottom: 1px solid;
margin: 0;
border-bottom-color: $fallback--border;
border-bottom-color: var(--border, $fallback--border);
.avatar {
padding: 0;
}
.follow-box {
text-align: center;
flex-shrink: 0;
display: flex;
flex-direction: row;
justify-content: space-between;
flex-wrap: wrap;
line-height: 1.5em;
.btn {
margin-top: 0.5em;
margin-left: auto;
width: 10em;
}
}
}
.usercard {
width: fill-available;
border-radius: $fallback--panelRadius;
border-radius: var(--panelRadius, $fallback--panelRadius);
border-style: solid;
border-color: $fallback--border;
border-color: var(--border, $fallback--border);
border-width: 1px;
overflow: hidden;
.panel-heading {
background: transparent;
flex-direction: column;
align-items: stretch;
}
p {
margin-bottom: 0;
}
}
.approval {
display: flex;
flex-direction: row;
flex-wrap: wrap;
button {
margin-top: 0.5em;
margin-right: 0.5em;
flex: 1 1;
max-width: 12em;
min-width: 8em;
}
}
</style>
@@ -13,7 +13,7 @@
<router-link :to="{ name: 'user-settings' }" v-if="!isOtherUser"> <router-link :to="{ name: 'user-settings' }" v-if="!isOtherUser">
<i class="button-icon icon-cog usersettings" :title="$t('tool_tip.user_settings')"></i> <i class="button-icon icon-cog usersettings" :title="$t('tool_tip.user_settings')"></i>
</router-link> </router-link>
<a :href="user.statusnet_profile_url" target="_blank" v-if="isOtherUser"> <a :href="user.statusnet_profile_url" target="_blank" v-if="isOtherUser && !user.is_local">
<i class="icon-link-ext usersettings"></i> <i class="icon-link-ext usersettings"></i>
</a> </a>
</div> </div>
@@ -222,6 +222,14 @@
overflow: hidden; overflow: hidden;
flex: 1 1 auto; flex: 1 1 auto;
margin-right: 1em; margin-right: 1em;
font-size: 15px;
img {
object-fit: contain;
height: 16px;
width: 16px;
vertical-align: middle;
}
} }
.user-screen-name { .user-screen-name {
@@ -386,4 +394,24 @@
} }
} }
.usercard {
width: fill-available;
border-radius: $fallback--panelRadius;
border-radius: var(--panelRadius, $fallback--panelRadius);
border-style: solid;
border-color: $fallback--border;
border-color: var(--border, $fallback--border);
border-width: 1px;
overflow: hidden;
.panel-heading {
background: transparent;
flex-direction: column;
align-items: stretch;
}
p {
margin-bottom: 0;
}
}
</style> </style>
@@ -8,6 +8,7 @@ const UserFinder = {
methods: { methods: {
findUser (username) { findUser (username) {
this.$router.push({ name: 'user-search', query: { query: username } }) this.$router.push({ name: 'user-search', query: { query: username } })
this.$refs.userSearchInput.focus()
}, },
toggleHidden () { toggleHidden () {
this.hidden = !this.hidden this.hidden = !this.hidden
+1 -1
View File
@@ -4,7 +4,7 @@
<i class="icon-spin4 user-finder-icon animate-spin-slow" v-if="loading" /> <i class="icon-spin4 user-finder-icon animate-spin-slow" v-if="loading" />
<a href="#" v-if="hidden" :title="$t('finder.find_user')"><i class="icon-user-plus user-finder-icon" @click.prevent.stop="toggleHidden" /></a> <a href="#" v-if="hidden" :title="$t('finder.find_user')"><i class="icon-user-plus user-finder-icon" @click.prevent.stop="toggleHidden" /></a>
<template v-else> <template v-else>
<input class="user-finder-input" @keyup.enter="findUser(username)" v-model="username" :placeholder="$t('finder.find_user')" id="user-finder-input" type="text"/> <input class="user-finder-input" ref="userSearchInput" @keyup.enter="findUser(username)" v-model="username" :placeholder="$t('finder.find_user')" id="user-finder-input" type="text"/>
<button class="btn search-button" @click="findUser(username)"> <button class="btn search-button" @click="findUser(username)">
<i class="icon-search"/> <i class="icon-search"/>
</button> </button>
+47 -4
View File
@@ -1,9 +1,39 @@
import { compose } from 'vue-compose'
import get from 'lodash/get'
import UserCardContent from '../user_card_content/user_card_content.vue' import UserCardContent from '../user_card_content/user_card_content.vue'
import UserCard from '../user_card/user_card.vue' import FollowCard from '../follow_card/follow_card.vue'
import Timeline from '../timeline/timeline.vue' import Timeline from '../timeline/timeline.vue'
import FollowList from '../follow_list/follow_list.vue' import withLoadMore from '../../hocs/with_load_more/with_load_more'
import withList from '../../hocs/with_list/with_list'
const FollowerList = compose(
withLoadMore({
fetch: (props, $store) => $store.dispatch('addFollowers', props.userId),
select: (props, $store) => get($store.getters.userById(props.userId), 'followers', []),
destory: (props, $store) => $store.dispatch('clearFollowers', props.userId),
childPropName: 'entries',
additionalPropNames: ['userId']
}),
withList({ getEntryProps: user => ({ user }) })
)(FollowCard)
const FriendList = compose(
withLoadMore({
fetch: (props, $store) => $store.dispatch('addFriends', props.userId),
select: (props, $store) => get($store.getters.userById(props.userId), 'friends', []),
destory: (props, $store) => $store.dispatch('clearFriends', props.userId),
childPropName: 'entries',
additionalPropNames: ['userId']
}),
withList({ getEntryProps: user => ({ user }) })
)(FollowCard)
const UserProfile = { const UserProfile = {
data () {
return {
error: false
}
},
created () { created () {
this.$store.commit('clearTimeline', { timeline: 'user' }) this.$store.commit('clearTimeline', { timeline: 'user' })
this.$store.commit('clearTimeline', { timeline: 'favorites' }) this.$store.commit('clearTimeline', { timeline: 'favorites' })
@@ -13,6 +43,16 @@ const UserProfile = {
this.startFetchFavorites() this.startFetchFavorites()
if (!this.user.id) { if (!this.user.id) {
this.$store.dispatch('fetchUser', this.fetchBy) this.$store.dispatch('fetchUser', this.fetchBy)
.catch((reason) => {
const errorMessage = get(reason, 'error.error')
if (errorMessage === 'No user with such user_id') { // Known error
this.error = this.$t('user_profile.profile_does_not_exist')
} else if (errorMessage) {
this.error = errorMessage
} else {
this.error = this.$t('user_profile.profile_loading_error')
}
})
} }
}, },
destroyed () { destroyed () {
@@ -101,13 +141,16 @@ const UserProfile = {
} }
this.cleanUp() this.cleanUp()
this.startUp() this.startUp()
},
$route () {
this.$refs.tabSwitcher.activateTab(0)()
} }
}, },
components: { components: {
UserCardContent, UserCardContent,
UserCard,
Timeline, Timeline,
FollowList FollowerList,
FriendList
} }
} }
+6 -10
View File
@@ -6,10 +6,11 @@
:switcher="true" :switcher="true"
:selected="timeline.viewing" :selected="timeline.viewing"
/> />
<tab-switcher :renderOnlyFocused="true"> <tab-switcher :renderOnlyFocused="true" ref="tabSwitcher">
<Timeline <Timeline
:label="$t('user_card.statuses')" :label="$t('user_card.statuses')"
:disabled="!user.statuses_count" :disabled="!user.statuses_count"
:count="user.statuses_count"
:embedded="true" :embedded="true"
:title="$t('user_profile.timeline_title')" :title="$t('user_profile.timeline_title')"
:timeline="timeline" :timeline="timeline"
@@ -17,16 +18,10 @@
:user-id="fetchBy" :user-id="fetchBy"
/> />
<div :label="$t('user_card.followees')" v-if="followsTabVisible" :disabled="!user.friends_count"> <div :label="$t('user_card.followees')" v-if="followsTabVisible" :disabled="!user.friends_count">
<FollowList v-if="user.friends_count > 0" :userId="userId" :showFollowers="false" /> <FriendList :userId="userId" />
<div class="userlist-placeholder" v-else>
<i class="icon-spin3 animate-spin"></i>
</div>
</div> </div>
<div :label="$t('user_card.followers')" v-if="followersTabVisible" :disabled="!user.followers_count"> <div :label="$t('user_card.followers')" v-if="followersTabVisible" :disabled="!user.followers_count">
<FollowList v-if="user.followers_count > 0" :userId="userId" :showFollowers="true" /> <FollowerList :userId="userId" :entryProps="{noFollowsYou: isUs}" />
<div class="userlist-placeholder" v-else>
<i class="icon-spin3 animate-spin"></i>
</div>
</div> </div>
<Timeline <Timeline
:label="$t('user_card.media')" :label="$t('user_card.media')"
@@ -54,7 +49,8 @@
</div> </div>
</div> </div>
<div class="panel-body"> <div class="panel-body">
<i class="icon-spin3 animate-spin"></i> <span v-if="error">{{ error }}</span>
<i class="icon-spin3 animate-spin" v-else></i>
</div> </div>
</div> </div>
</div> </div>
+7 -3
View File
@@ -1,8 +1,8 @@
import UserCard from '../user_card/user_card.vue' import FollowCard from '../follow_card/follow_card.vue'
import userSearchApi from '../../services/new_api/user_search.js' import userSearchApi from '../../services/new_api/user_search.js'
const userSearch = { const userSearch = {
components: { components: {
UserCard FollowCard
}, },
props: [ props: [
'query' 'query'
@@ -10,7 +10,8 @@ const userSearch = {
data () { data () {
return { return {
username: '', username: '',
users: [] users: [],
loading: false
} }
}, },
mounted () { mounted () {
@@ -24,14 +25,17 @@ const userSearch = {
methods: { methods: {
newQuery (query) { newQuery (query) {
this.$router.push({ name: 'user-search', query: { query } }) this.$router.push({ name: 'user-search', query: { query } })
this.$refs.userSearchInput.focus()
}, },
search (query) { search (query) {
if (!query) { if (!query) {
this.users = [] this.users = []
return return
} }
this.loading = true
userSearchApi.search({query, store: this.$store}) userSearchApi.search({query, store: this.$store})
.then((res) => { .then((res) => {
this.loading = false
this.users = res this.users = res
}) })
} }
+10 -3
View File
@@ -4,13 +4,16 @@
{{$t('nav.user_search')}} {{$t('nav.user_search')}}
</div> </div>
<div class="user-search-input-container"> <div class="user-search-input-container">
<input class="user-finder-input" @keyup.enter="newQuery(username)" v-model="username" :placeholder="$t('finder.find_user')"/> <input class="user-finder-input" ref="userSearchInput" @keyup.enter="newQuery(username)" v-model="username" :placeholder="$t('finder.find_user')"/>
<button class="btn search-button" @click="newQuery(username)"> <button class="btn search-button" @click="newQuery(username)">
<i class="icon-search"/> <i class="icon-search"/>
</button> </button>
</div> </div>
<div class="panel-body"> <div v-if="loading" class="text-center loading-icon">
<user-card v-for="user in users" :key="user.id" :user="user" :showFollows="true"></user-card> <i class="icon-spin3 animate-spin"/>
</div>
<div v-else class="panel-body">
<FollowCard v-for="user in users" :key="user.id" :user="user"/>
</div> </div>
</div> </div>
</template> </template>
@@ -27,4 +30,8 @@
margin-left: 0.5em; margin-left: 0.5em;
} }
} }
.loading-icon {
padding: 1em;
}
</style> </style>
+55 -30
View File
@@ -1,8 +1,32 @@
import { unescape } from 'lodash' import { compose } from 'vue-compose'
import unescape from 'lodash/unescape'
import get from 'lodash/get'
import TabSwitcher from '../tab_switcher/tab_switcher.js' import TabSwitcher from '../tab_switcher/tab_switcher.js'
import ImageCropper from '../image_cropper/image_cropper.vue'
import StyleSwitcher from '../style_switcher/style_switcher.vue' import StyleSwitcher from '../style_switcher/style_switcher.vue'
import fileSizeFormatService from '../../services/file_size_format/file_size_format.js' import fileSizeFormatService from '../../services/file_size_format/file_size_format.js'
import BlockCard from '../block_card/block_card.vue'
import MuteCard from '../mute_card/mute_card.vue'
import withSubscription from '../../hocs/with_subscription/with_subscription'
import withList from '../../hocs/with_list/with_list'
const BlockList = compose(
withSubscription({
fetch: (props, $store) => $store.dispatch('fetchBlocks'),
select: (props, $store) => get($store.state.users.currentUser, 'blockIds', []),
childPropName: 'entries'
}),
withList({ getEntryProps: userId => ({ userId }) })
)(BlockCard)
const MuteList = compose(
withSubscription({
fetch: (props, $store) => $store.dispatch('fetchMutes'),
select: (props, $store) => get($store.state.users.currentUser, 'muteIds', []),
childPropName: 'entries'
}),
withList({ getEntryProps: userId => ({ userId }) })
)(MuteCard)
const UserSettings = { const UserSettings = {
data () { data () {
@@ -20,14 +44,12 @@ const UserSettings = {
followImportError: false, followImportError: false,
followsImported: false, followsImported: false,
enableFollowsExport: true, enableFollowsExport: true,
avatarUploading: false, pickAvatarBtnVisible: true,
bannerUploading: false, bannerUploading: false,
backgroundUploading: false, backgroundUploading: false,
followListUploading: false, followListUploading: false,
avatarPreview: null,
bannerPreview: null, bannerPreview: null,
backgroundPreview: null, backgroundPreview: null,
avatarUploadError: null,
bannerUploadError: null, bannerUploadError: null,
backgroundUploadError: null, backgroundUploadError: null,
deletingAccount: false, deletingAccount: false,
@@ -39,9 +61,15 @@ const UserSettings = {
activeTab: 'profile' activeTab: 'profile'
} }
}, },
created () {
this.$store.dispatch('fetchTokens')
},
components: { components: {
StyleSwitcher, StyleSwitcher,
TabSwitcher TabSwitcher,
ImageCropper,
BlockList,
MuteList
}, },
computed: { computed: {
user () { user () {
@@ -60,6 +88,18 @@ const UserSettings = {
private: { selected: this.newDefaultScope === 'private' }, private: { selected: this.newDefaultScope === 'private' },
direct: { selected: this.newDefaultScope === 'direct' } direct: { selected: this.newDefaultScope === 'direct' }
} }
},
currentSaveStateNotice () {
return this.$store.state.interface.settings.currentSaveStateNotice
},
oauthTokens () {
return this.$store.state.oauthTokens.tokens.map(oauthToken => {
return {
id: oauthToken.id,
appName: oauthToken.app_name,
validUntil: new Date(oauthToken.valid_until).toLocaleDateString()
}
})
} }
}, },
methods: { methods: {
@@ -117,35 +157,15 @@ const UserSettings = {
} }
reader.readAsDataURL(file) reader.readAsDataURL(file)
}, },
submitAvatar () { submitAvatar (cropper, file) {
if (!this.avatarPreview) { return } const img = cropper.getCroppedCanvas().toDataURL(file.type)
return this.$store.state.api.backendInteractor.updateAvatar({ params: { img } }).then((user) => {
let img = this.avatarPreview
// eslint-disable-next-line no-undef
let imginfo = new Image()
let cropX, cropY, cropW, cropH
imginfo.src = img
if (imginfo.height > imginfo.width) {
cropX = 0
cropW = imginfo.width
cropY = Math.floor((imginfo.height - imginfo.width) / 2)
cropH = imginfo.width
} else {
cropY = 0
cropH = imginfo.height
cropX = Math.floor((imginfo.width - imginfo.height) / 2)
cropW = imginfo.height
}
this.avatarUploading = true
this.$store.state.api.backendInteractor.updateAvatar({params: {img, cropX, cropY, cropW, cropH}}).then((user) => {
if (!user.error) { if (!user.error) {
this.$store.commit('addNewUsers', [user]) this.$store.commit('addNewUsers', [user])
this.$store.commit('setCurrentUser', user) this.$store.commit('setCurrentUser', user)
this.avatarPreview = null
} else { } else {
this.avatarUploadError = this.$t('upload.error.base') + user.error throw new Error(this.$t('upload.error.base') + user.error)
} }
this.avatarUploading = false
}) })
}, },
clearUploadError (slot) { clearUploadError (slot) {
@@ -299,6 +319,11 @@ const UserSettings = {
logout () { logout () {
this.$store.dispatch('logout') this.$store.dispatch('logout')
this.$router.replace('/') this.$router.replace('/')
},
revokeToken (id) {
if (window.confirm(`${this.$i18n.t('settings.revoke_token')}?`)) {
this.$store.dispatch('revokeToken', id)
}
} }
} }
} }
+69 -13
View File
@@ -1,7 +1,20 @@
<template> <template>
<div class="settings panel panel-default"> <div class="settings panel panel-default">
<div class="panel-heading"> <div class="panel-heading">
{{$t('settings.user_settings')}} <div class="title">
{{$t('settings.user_settings')}}
</div>
<transition name="fade">
<template v-if="currentSaveStateNotice">
<div @click.prevent class="alert error" v-if="currentSaveStateNotice.error">
{{ $t('settings.saving_err') }}
</div>
<div @click.prevent class="alert transparent" v-if="!currentSaveStateNotice.error">
{{ $t('settings.saving_ok') }}
</div>
</template>
</transition>
</div> </div>
<div class="panel-body profile-edit"> <div class="panel-body profile-edit">
<tab-switcher> <tab-switcher>
@@ -48,19 +61,10 @@
<h2>{{$t('settings.avatar')}}</h2> <h2>{{$t('settings.avatar')}}</h2>
<p class="visibility-notice">{{$t('settings.avatar_size_instruction')}}</p> <p class="visibility-notice">{{$t('settings.avatar_size_instruction')}}</p>
<p>{{$t('settings.current_avatar')}}</p> <p>{{$t('settings.current_avatar')}}</p>
<img :src="user.profile_image_url_original" class="old-avatar"></img> <img :src="user.profile_image_url_original" class="current-avatar"></img>
<p>{{$t('settings.set_new_avatar')}}</p> <p>{{$t('settings.set_new_avatar')}}</p>
<img class="new-avatar" v-bind:src="avatarPreview" v-if="avatarPreview"> <button class="btn" type="button" id="pick-avatar" v-show="pickAvatarBtnVisible">{{$t('settings.upload_a_photo')}}</button>
</img> <image-cropper trigger="#pick-avatar" :submitHandler="submitAvatar" @open="pickAvatarBtnVisible=false" @close="pickAvatarBtnVisible=true" />
<div>
<input type="file" @change="uploadFile('avatar', $event)" ></input>
</div>
<i class="icon-spin4 animate-spin" v-if="avatarUploading"></i>
<button class="btn btn-default" v-else-if="avatarPreview" @click="submitAvatar">{{$t('general.submit')}}</button>
<div class='alert error' v-if="avatarUploadError">
Error: {{ avatarUploadError }}
<i class="button-icon icon-cancel" @click="clearUploadError('avatar')"></i>
</div>
</div> </div>
<div class="setting-item"> <div class="setting-item">
<h2>{{$t('settings.profile_banner')}}</h2> <h2>{{$t('settings.profile_banner')}}</h2>
@@ -117,6 +121,30 @@
<p v-if="changePasswordError">{{changePasswordError}}</p> <p v-if="changePasswordError">{{changePasswordError}}</p>
</div> </div>
<div class="setting-item">
<h2>{{$t('settings.oauth_tokens')}}</h2>
<table class="oauth-tokens">
<thead>
<tr>
<th>{{$t('settings.app_name')}}</th>
<th>{{$t('settings.valid_until')}}</th>
<th></th>
</tr>
</thead>
<tbody>
<tr v-for="oauthToken in oauthTokens" :key="oauthToken.id">
<td>{{oauthToken.appName}}</td>
<td>{{oauthToken.validUntil}}</td>
<td class="actions">
<button class="btn btn-default" @click="revokeToken(oauthToken.id)">
{{$t('settings.revoke_token')}}
</button>
</td>
</tr>
</tbody>
</table>
</div>
<div class="setting-item"> <div class="setting-item">
<h2>{{$t('settings.delete_account')}}</h2> <h2>{{$t('settings.delete_account')}}</h2>
<p v-if="!deletingAccount">{{$t('settings.delete_account_description')}}</p> <p v-if="!deletingAccount">{{$t('settings.delete_account_description')}}</p>
@@ -158,6 +186,12 @@
<h2>{{$t('settings.follow_export_processing')}}</h2> <h2>{{$t('settings.follow_export_processing')}}</h2>
</div> </div>
</div> </div>
<div :label="$t('settings.blocks_tab')">
<block-list :refresh="true">
<template slot="empty">{{$t('settings.no_blocks')}}</template>
</block-list>
</div>
</tab-switcher> </tab-switcher>
</div> </div>
</div> </div>
@@ -167,6 +201,8 @@
</script> </script>
<style lang="scss"> <style lang="scss">
@import '../../_variables.scss';
.profile-edit { .profile-edit {
.bio { .bio {
margin: 0; margin: 0;
@@ -193,5 +229,25 @@
.bg { .bg {
max-width: 100%; max-width: 100%;
} }
.current-avatar {
display: block;
width: 150px;
height: 150px;
border-radius: $fallback--avatarRadius;
border-radius: var(--avatarRadius, $fallback--avatarRadius);
}
.oauth-tokens {
width: 100%;
th {
text-align: left;
}
.actions {
text-align: right;
}
}
} }
</style> </style>
@@ -1,9 +1,9 @@
import apiService from '../../services/api/api.service.js' import apiService from '../../services/api/api.service.js'
import UserCard from '../user_card/user_card.vue' import FollowCard from '../follow_card/follow_card.vue'
const WhoToFollow = { const WhoToFollow = {
components: { components: {
UserCard FollowCard
}, },
data () { data () {
return { return {
@@ -4,7 +4,7 @@
{{$t('who_to_follow.who_to_follow')}} {{$t('who_to_follow.who_to_follow')}}
</div> </div>
<div class="panel-body"> <div class="panel-body">
<user-card v-for="user in users" :key="user.id" :user="user" :showFollows="true"></user-card> <FollowCard v-for="user in users" :key="user.id" :user="user"/>
</div> </div>
</div> </div>
</template> </template>
+40
View File
@@ -0,0 +1,40 @@
import Vue from 'vue'
import map from 'lodash/map'
import isEmpty from 'lodash/isEmpty'
import './with_list.scss'
const defaultEntryPropsGetter = entry => ({ entry })
const defaultKeyGetter = entry => entry.id
const withList = ({
getEntryProps = defaultEntryPropsGetter, // function to accept entry and index values and return props to be passed into the item component
getKey = defaultKeyGetter // funciton to accept entry and index values and return key prop value
}) => (ItemComponent) => (
Vue.component('withList', {
props: [
'entries', // array of entry
'entryProps', // additional props to be passed into each entry
'entryListeners' // additional event listeners to be passed into each entry
],
render (createElement) {
return (
<div class="with-list">
{map(this.entries, (entry, index) => {
const props = {
key: getKey(entry, index),
props: {
...this.$props.entryProps,
...getEntryProps(entry, index)
},
on: this.$props.entryListeners
}
return <ItemComponent {...props} />
})}
{isEmpty(this.entries) && this.$slots.empty && <div class="with-list-empty-content faint">{this.$slots.empty}</div>}
</div>
)
}
})
)
export default withList
+6
View File
@@ -0,0 +1,6 @@
.with-list {
&-empty-content {
text-align: center;
padding: 10px;
}
}
+94
View File
@@ -0,0 +1,94 @@
import Vue from 'vue'
import isEmpty from 'lodash/isEmpty'
import { getComponentProps } from '../../services/component_utils/component_utils'
import './with_load_more.scss'
const withLoadMore = ({
fetch, // function to fetch entries and return a promise
select, // function to select data from store
destroy, // function called at "destroyed" lifecycle
childPropName = 'entries', // name of the prop to be passed into the wrapped component
additionalPropNames = [] // additional prop name list of the wrapper component
}) => (WrappedComponent) => {
const originalProps = Object.keys(getComponentProps(WrappedComponent))
const props = originalProps.filter(v => v !== childPropName).concat(additionalPropNames)
return Vue.component('withLoadMore', {
render (createElement) {
const props = {
props: {
...this.$props,
[childPropName]: this.entries
},
on: this.$listeners,
scopedSlots: this.$scopedSlots
}
const children = Object.entries(this.$slots).map(([key, value]) => createElement('template', { slot: key }, value))
return (
<div class="with-load-more">
<WrappedComponent {...props}>
{children}
</WrappedComponent>
<div class="with-load-more-footer">
{this.error && <a onClick={this.fetchEntries} class="alert error">{this.$t('general.generic_error')}</a>}
{!this.error && this.loading && <i class="icon-spin3 animate-spin"/>}
{!this.error && !this.loading && !this.bottomedOut && <a onClick={this.fetchEntries}>{this.$t('general.more')}</a>}
</div>
</div>
)
},
props,
data () {
return {
loading: false,
bottomedOut: false,
error: false
}
},
computed: {
entries () {
return select(this.$props, this.$store) || []
}
},
created () {
window.addEventListener('scroll', this.scrollLoad)
if (this.entries.length === 0) {
this.fetchEntries()
}
},
destroyed () {
window.removeEventListener('scroll', this.scrollLoad)
destroy && destroy(this.$props, this.$store)
},
methods: {
fetchEntries () {
if (!this.loading) {
this.loading = true
this.error = false
fetch(this.$props, this.$store)
.then((newEntries) => {
this.loading = false
this.bottomedOut = isEmpty(newEntries)
})
.catch(() => {
this.loading = false
this.error = true
})
}
},
scrollLoad (e) {
const bodyBRect = document.body.getBoundingClientRect()
const height = Math.max(bodyBRect.height, -(bodyBRect.y))
if (this.loading === false &&
this.bottomedOut === false &&
this.$el.offsetHeight > 0 &&
(window.innerHeight + window.pageYOffset) >= (height - 750)
) {
this.fetchEntries()
}
}
}
})
}
export default withLoadMore
@@ -0,0 +1,10 @@
.with-load-more {
&-footer {
padding: 10px;
text-align: center;
.error {
font-size: 14px;
}
}
}
@@ -0,0 +1,84 @@
import Vue from 'vue'
import isEmpty from 'lodash/isEmpty'
import { getComponentProps } from '../../services/component_utils/component_utils'
import './with_subscription.scss'
const withSubscription = ({
fetch, // function to fetch entries and return a promise
select, // function to select data from store
childPropName = 'content', // name of the prop to be passed into the wrapped component
additionalPropNames = [] // additional prop name list of the wrapper component
}) => (WrappedComponent) => {
const originalProps = Object.keys(getComponentProps(WrappedComponent))
const props = originalProps.filter(v => v !== childPropName).concat(additionalPropNames)
return Vue.component('withSubscription', {
props: [
...props,
'refresh' // boolean saying to force-fetch data whenever created
],
render (createElement) {
if (!this.error && !this.loading) {
const props = {
props: {
...this.$props,
[childPropName]: this.fetchedData
},
on: this.$listeners,
scopedSlots: this.$scopedSlots
}
const children = Object.entries(this.$slots).map(([key, value]) => createElement('template', { slot: key }, value))
return (
<div class="with-subscription">
<WrappedComponent {...props}>
{children}
</WrappedComponent>
</div>
)
} else {
return (
<div class="with-subscription-loading">
{this.error
? <a onClick={this.fetchData} class="alert error">{this.$t('general.generic_error')}</a>
: <i class="icon-spin3 animate-spin"/>
}
</div>
)
}
},
data () {
return {
loading: false,
error: false
}
},
computed: {
fetchedData () {
return select(this.$props, this.$store)
}
},
created () {
if (this.refresh || isEmpty(this.fetchedData)) {
this.fetchData()
}
},
methods: {
fetchData () {
if (!this.loading) {
this.loading = true
this.error = false
fetch(this.$props, this.$store)
.then(() => {
this.loading = false
})
.catch(() => {
this.error = true
this.loading = false
})
}
}
}
})
}
export default withSubscription
@@ -0,0 +1,10 @@
.with-subscription {
&-loading {
padding: 10px;
text-align: center;
.error {
font-size: 14px;
}
}
}
+5
View File
@@ -134,6 +134,11 @@
"notification_visibility_mentions": "الإشارات", "notification_visibility_mentions": "الإشارات",
"notification_visibility_repeats": "", "notification_visibility_repeats": "",
"nsfw_clickthrough": "", "nsfw_clickthrough": "",
"oauth_tokens": "رموز OAuth",
"token": "رمز",
"refresh_token": "رمز التحديث",
"valid_until": "صالح حتى",
"revoke_token": "سحب",
"panelRadius": "", "panelRadius": "",
"pause_on_unfocused": "", "pause_on_unfocused": "",
"presets": "النماذج", "presets": "النماذج",
+5
View File
@@ -132,6 +132,11 @@
"notification_visibility_repeats": "Republica una entrada meva", "notification_visibility_repeats": "Republica una entrada meva",
"no_rich_text_description": "Neteja el formatat de text de totes les entrades", "no_rich_text_description": "Neteja el formatat de text de totes les entrades",
"nsfw_clickthrough": "Amaga el contingut NSFW darrer d'una imatge clicable", "nsfw_clickthrough": "Amaga el contingut NSFW darrer d'una imatge clicable",
"oauth_tokens": "Llistats OAuth",
"token": "Token",
"refresh_token": "Actualitza el token",
"valid_until": "Vàlid fins",
"revoke_token": "Revocar",
"panelRadius": "Panells", "panelRadius": "Panells",
"pause_on_unfocused": "Pausa la reproducció en continu quan la pestanya perdi el focus", "pause_on_unfocused": "Pausa la reproducció en continu quan la pestanya perdi el focus",
"presets": "Temes", "presets": "Temes",
+427
View File
@@ -0,0 +1,427 @@
{
"chat": {
"title": "Chat"
},
"features_panel": {
"chat": "Chat",
"gopher": "Gopher",
"media_proxy": "Mediální proxy",
"scope_options": "Možnosti rozsahů",
"text_limit": "Textový limit",
"title": "Vlastnosti",
"who_to_follow": "Koho sledovat"
},
"finder": {
"error_fetching_user": "Chyba při načítání uživatele",
"find_user": "Najít uživatele"
},
"general": {
"apply": "Použít",
"submit": "Odeslat",
"more": "Více",
"generic_error": "Vyskytla se chyba",
"optional": "volitelné"
},
"image_cropper": {
"crop_picture": "Oříznout obrázek",
"save": "Uložit",
"cancel": "Zrušit"
},
"login": {
"login": "Přihlásit",
"description": "Přihlásit pomocí OAuth",
"logout": "Odhlásit",
"password": "Heslo",
"placeholder": "např. lain",
"register": "Registrovat",
"username": "Uživatelské jméno",
"hint": "Chcete-li se přidat do diskuze, přihlaste se"
},
"media_modal": {
"previous": "Předchozí",
"next": "Další"
},
"nav": {
"about": "O instanci",
"back": "Zpět",
"chat": "Místní chat",
"friend_requests": "Požadavky o sledování",
"mentions": "Zmínky",
"dms": "Přímé zprávy",
"public_tl": "Veřejná časová osa",
"timeline": "Časová osa",
"twkn": "Celá známá síť",
"user_search": "Hledání uživatelů",
"who_to_follow": "Koho sledovat",
"preferences": "Předvolby"
},
"notifications": {
"broken_favorite": "Neznámý příspěvek, hledám jej…",
"favorited_you": "si oblíbil/a váš příspěvek",
"followed_you": "vás nyní sleduje",
"load_older": "Načíst starší oznámení",
"notifications": "Oznámení",
"read": "Číst!",
"repeated_you": "zopakoval/a váš příspěvek",
"no_more_notifications": "Žádná další oznámení"
},
"post_status": {
"new_status": "Napsat nový příspěvek",
"account_not_locked_warning": "Váš účet není {0}. Kdokoliv vás může sledovat a vidět vaše příspěvky pouze pro sledující.",
"account_not_locked_warning_link": "uzamčen",
"attachments_sensitive": "Označovat přílohy jako citlivé",
"content_type": {
"plain_text": "Prostý text"
},
"content_warning": "Předmět (volitelný)",
"default": "Právě jsem přistál v L.A.",
"direct_warning": "Tento příspěvek uvidí pouze všichni zmínění uživatelé.",
"posting": "Přispívání",
"scope": {
"direct": "Přímý - Poslat pouze zmíněným uživatelům",
"private": "Pouze pro sledující - Poslat pouze sledujícím",
"public": "Veřejný - Poslat na veřejné časové osy",
"unlisted": "Neuvedený - Neposlat na veřejné časové osy"
}
},
"registration": {
"bio": "O vás",
"email": "E-mail",
"fullname": "Zobrazované jméno",
"password_confirm": "Potvrzení hesla",
"registration": "Registrace",
"token": "Token pozvánky",
"captcha": "CAPTCHA",
"new_captcha": "Kliknutím na obrázek získáte novou CAPTCHA",
"username_placeholder": "např. lain",
"fullname_placeholder": "např. Lain Iwakura",
"bio_placeholder": "např.\nNazdar, jsem Lain\nJsem anime dívka a žiji v příměstském Japonsku. Možná mě znáte z Wired.",
"validations": {
"username_required": "nemůže být prázdné",
"fullname_required": "nemůže být prázdné",
"email_required": "nemůže být prázdný",
"password_required": "nemůže být prázdné",
"password_confirmation_required": "nemůže být prázdné",
"password_confirmation_match": "musí být stejné jako heslo"
}
},
"settings": {
"app_name": "Název aplikace",
"attachmentRadius": "Přílohy",
"attachments": "Přílohy",
"autoload": "Povolit automatické načítání při rolování dolů",
"avatar": "Avatar",
"avatarAltRadius": "Avatary (oznámení)",
"avatarRadius": "Avatary",
"background": "Pozadí",
"bio": "O vás",
"blocks_tab": "Blokování",
"btnRadius": "Tlačítka",
"cBlue": "Modrá (Odpovědět, sledovat)",
"cGreen": "Zelená (Zopakovat)",
"cOrange": "Oranžová (Oblíbit)",
"cRed": "Červená (Zrušit)",
"change_password": "Změnit heslo",
"change_password_error": "Při změně vašeho hesla se vyskytla chyba.",
"changed_password": "Heslo bylo úspěšně změněno!",
"collapse_subject": "Zabalit příspěvky s předměty",
"composing": "Komponování",
"confirm_new_password": "Potvrďte nové heslo",
"current_avatar": "Váš současný avatar",
"current_password": "Současné heslo",
"current_profile_banner": "Váš současný profilový banner",
"data_import_export_tab": "Import/export dat",
"default_vis": "Výchozí rozsah viditelnosti",
"delete_account": "Smazat účet",
"delete_account_description": "Trvale smaže váš účet a všechny vaše příspěvky.",
"delete_account_error": "Při mazání vašeho účtu nastala chyba. Pokud tato chyba bude trvat, kontaktujte prosím admministrátora vaší instance.",
"delete_account_instructions": "Pro potvrzení smazání účtu napište své heslo do pole níže.",
"avatar_size_instruction": "Doporučená minimální velikost pro avatarové obrázky je 150x150 pixelů.",
"export_theme": "Uložit přednastavení",
"filtering": "Filtrování",
"filtering_explanation": "Všechny příspěvky obsahující tato slova budou skryty. Napište jedno slovo na každý řádek",
"follow_export": "Export sledovaných",
"follow_export_button": "Exportovat vaše sledované do souboru CSV",
"follow_export_processing": "Zpracovávám, brzy si budete moci stáhnout váš soubor",
"follow_import": "Import sledovaných",
"follow_import_error": "Chyba při importování sledovaných",
"follows_imported": "Sledovaní importováni! Jejich zpracování bude chvilku trvat.",
"foreground": "Popředí",
"general": "Obecné",
"hide_attachments_in_convo": "Skrývat přílohy v konverzacích",
"hide_attachments_in_tl": "Skrývat přílohy v časové ose",
"max_thumbnails": "Maximální počet miniatur na příspěvek",
"hide_isp": "Skrýt panel specifický pro instanci",
"preload_images": "Přednačítat obrázky",
"use_one_click_nsfw": "Otevírat citlivé přílohy pouze jedním kliknutím",
"hide_post_stats": "Skrývat statistiky příspěvků (např. počet oblíbení)",
"hide_user_stats": "Skrývat statistiky uživatelů (např. počet sledujících)",
"hide_filtered_statuses": "Skrývat filtrované příspěvky",
"import_followers_from_a_csv_file": "Importovat sledované ze souboru CSV",
"import_theme": "Načíst přednastavení",
"inputRadius": "Vstupní pole",
"checkboxRadius": "Zaškrtávací pole",
"instance_default": "(výchozí: {value})",
"instance_default_simple": "(výchozí)",
"interface": "Rozhraní",
"interfaceLanguage": "Jazyk rozhraní",
"invalid_theme_imported": "Zvolený soubor není podporovaný motiv Pleroma. Nebyly provedeny žádné změny s vaším motivem.",
"limited_availability": "Nedostupné ve vašem prohlížeči",
"links": "Odkazy",
"lock_account_description": "Omezit váš účet pouze na schválené sledující",
"loop_video": "Opakovat videa",
"loop_video_silent_only": "Opakovat pouze videa beze zvuku (t.j. „GIFy“ na Mastodonu)",
"mutes_tab": "Ignorování",
"play_videos_in_modal": "Přehrávat videa přímo v prohlížeči médií",
"use_contain_fit": "Neořezávat přílohu v miniaturách",
"name": "Jméno",
"name_bio": "Jméno a popis",
"new_password": "Nové heslo",
"notification_visibility": "Typy oznámení k zobrazení",
"notification_visibility_follows": "Sledující",
"notification_visibility_likes": "Oblíbení",
"notification_visibility_mentions": "Zmínky",
"notification_visibility_repeats": "Zopakování",
"no_rich_text_description": "Odstranit ze všech příspěvků formátování textu",
"no_blocks": "Žádná blokování",
"no_mutes": "Žádná ignorování",
"hide_follows_description": "Nezobrazovat, koho sleduji",
"hide_followers_description": "Nezobrazovat, kdo mě sleduje",
"show_admin_badge": "Zobrazovat v mém profilu odznak administrátora",
"show_moderator_badge": "Zobrazovat v mém profilu odznak moderátora",
"nsfw_clickthrough": "Povolit prokliknutelné skrývání citlivých příloh",
"oauth_tokens": "Tokeny OAuth",
"token": "Token",
"refresh_token": "Obnovit token",
"valid_until": "Platný do",
"revoke_token": "Odvolat",
"panelRadius": "Panely",
"pause_on_unfocused": "Pozastavit streamování, pokud není záložka prohlížeče v soustředění",
"presets": "Přednastavení",
"profile_background": "Profilové pozadí",
"profile_banner": "Profilový banner",
"profile_tab": "Profil",
"radii_help": "Nastavit zakulacení rohů rozhraní (v pixelech)",
"replies_in_timeline": "Odpovědi v časové ose",
"reply_link_preview": "Povolit náhledy odkazu pro odpověď při přejetí myši",
"reply_visibility_all": "Zobrazit všechny odpovědiShow all replies",
"reply_visibility_following": "Zobrazit pouze odpovědi směřované na mě nebo uživatele, které sleduji",
"reply_visibility_self": "Zobrazit pouze odpovědi směřované na mě",
"saving_err": "Chyba při ukládání nastavení",
"saving_ok": "Nastavení uložena",
"security_tab": "Bezpečnost",
"scope_copy": "Kopírovat rozsah při odpovídání (přímé zprávy jsou vždy kopírovány)",
"set_new_avatar": "Nastavit nový avatar",
"set_new_profile_background": "Nastavit nové profilové pozadí",
"set_new_profile_banner": "Nastavit nový profilový banner",
"settings": "Nastavení",
"subject_input_always_show": "Vždy zobrazit pole pro předmět",
"subject_line_behavior": "Kopírovat předmět při odpovídání",
"subject_line_email": "Jako u e-mailu: „re: předmět“",
"subject_line_mastodon": "Jako u Mastodonu: zkopírovat tak, jak je",
"subject_line_noop": "Nekopírovat",
"post_status_content_type": "Publikovat typ obsahu příspěvku",
"status_content_type_plain": "Prostý text",
"stop_gifs": "Přehrávat GIFy při přejetí myši",
"streaming": "Povolit automatické streamování nových příspěvků při rolování nahoru",
"text": "Text",
"theme": "Motiv",
"theme_help": "Použijte hexadecimální barevné kódy (#rrggbb) pro přizpůsobení vašeho barevného motivu.",
"theme_help_v2_1": "Zaškrtnutím pole můžete také přepsat barvy a průhlednost některých komponentů, pro smazání všech přednastavení použijte tlačítko „Smazat vše“.",
"theme_help_v2_2": "Ikony pod některými položkami jsou indikátory kontrastu pozadí/textu, pro detailní informace nad nimi přejeďte myší. Prosím berte na vědomí, že při používání kontrastu průhlednosti ukazují indikátory nejhorší možný případ.",
"tooltipRadius": "Popisky/upozornění",
"upload_a_photo": "Nahrát fotku",
"user_settings": "Uživatelská nastavení",
"values": {
"false": "ne",
"true": "ano"
},
"notifications": "Oznámení",
"enable_web_push_notifications": "Povolit webová push oznámení",
"style": {
"switcher": {
"keep_color": "Ponechat barvy",
"keep_shadows": "Ponechat stíny",
"keep_opacity": "Ponechat průhlednost",
"keep_roundness": "Ponechat kulatost",
"keep_fonts": "Keep fonts",
"save_load_hint": "Možnosti „Ponechat“ dočasně ponechávají aktuálně nastavené možností při volení či nahrávání motivů, také tyto možnosti ukládají při exportování motivu. Pokud není žádné pole zaškrtnuto, uloží export motivu všechno.",
"reset": "Resetovat",
"clear_all": "Vymazat vše",
"clear_opacity": "Vymazat průhlednost"
},
"common": {
"color": "Barva",
"opacity": "Průhlednost",
"contrast": {
"hint": "Poměr kontrastu je {ratio}, {level} {context}",
"level": {
"aa": "splňuje směrnici úrovně AA (minimální)",
"aaa": "splňuje směrnici úrovně AAA (doporučováno)",
"bad": "nesplňuje žádné směrnice přístupnosti"
},
"context": {
"18pt": "pro velký (18+ bodů) text",
"text": "pro text"
}
}
},
"common_colors": {
"_tab_label": "Obvyklé",
"main": "Obvyklé barvy",
"foreground_hint": "Pro detailnější kontrolu viz záložka „Pokročilé“",
"rgbo": "Ikony, odstíny, odznaky"
},
"advanced_colors": {
"_tab_label": "Pokročilé",
"alert": "Pozadí upozornění",
"alert_error": "Chyba",
"badge": "Pozadí odznaků",
"badge_notification": "Oznámení",
"panel_header": "Záhlaví panelu",
"top_bar": "Vrchní pruh",
"borders": "Okraje",
"buttons": "Tlačítka",
"inputs": "Vstupní pole",
"faint_text": "Vybledlý text"
},
"radii": {
"_tab_label": "Kulatost"
},
"shadows": {
"_tab_label": "Stín a osvětlení",
"component": "Komponent",
"override": "Přepsat",
"shadow_id": "Stín #{value}",
"blur": "Rozmazání",
"spread": "Rozsah",
"inset": "Vsazení",
"hint": "Pro stíny můžete také použít --variable jako hodnotu barvy pro použití proměnných CSS3. Prosím berte na vědomí, že nastavení průhlednosti v tomto případě nebude fungovat.",
"filter_hint": {
"always_drop_shadow": "Varování, tento stín vždy používá {0}, když to prohlížeč podporuje.",
"drop_shadow_syntax": "{0} nepodporuje parametr {1} a klíčové slovo {2}.",
"avatar_inset": "Prosím berte na vědomí, že kombinování vsazených i nevsazených stínů u avatarů může u průhledných avatarů dát neočekávané výsledky.",
"spread_zero": "Stíny s rozsahem > 0 se zobrazí, jako kdyby byl rozsah nastaven na nulu",
"inset_classic": "Vsazené stíny budou používat {0}"
},
"components": {
"panel": "Panel",
"panelHeader": "Záhlaví panelu",
"topBar": "Vrchní pruh",
"avatar": "Avatar uživatele (v zobrazení profilu)",
"avatarStatus": "Avatar uživatele (v zobrazení příspěvku)",
"popup": "Vyskakovací okna a popisky",
"button": "Tlačítko",
"buttonHover": "Tlačítko (přejetí myši)",
"buttonPressed": "Tlačítko (stisknuto)",
"buttonPressedHover": "Button (stisknuto+přejetí myši)",
"input": "Vstupní pole"
}
},
"fonts": {
"_tab_label": "Písma",
"help": "Zvolte písmo, které bude použito pro prvky rozhraní. U možnosti „vlastní“ musíte zadat přesný název písma tak, jak se zobrazuje v systému.",
"components": {
"interface": "Rozhraní",
"input": "Vstupní pole",
"post": "Text příspěvků",
"postCode": "Neproporcionální text v příspěvku (formátovaný text)"
},
"family": "Název písma",
"size": "Velikost (v pixelech)",
"weight": "Tloušťka",
"custom": "Vlastní"
},
"preview": {
"header": "Náhled",
"content": "Obsah",
"error": "Příklad chyby",
"button": "Tlačítko",
"text": "Spousta dalšího {0} a {1}",
"mono": "obsahu",
"input": "Just landed in L.A.",
"faint_link": "pomocný manuál",
"fine_print": "Přečtěte si náš {0} a nenaučte se nic užitečného!",
"header_faint": "Tohle je v pohodě",
"checkbox": "Pročetl/a jsem podmínky používání",
"link": "hezký malý odkaz"
}
}
},
"timeline": {
"collapse": "Zabalit",
"conversation": "Konverzace",
"error_fetching": "Chyba při načítání aktualizací",
"load_older": "Načíst starší příspěvky",
"no_retweet_hint": "Příspěvek je označen jako pouze pro sledující či přímý a nemůže být zopakován",
"repeated": "zopakoval/a",
"show_new": "Zobrazit nové",
"up_to_date": "Aktuální",
"no_more_statuses": "Žádné další příspěvky",
"no_statuses": "Žádné příspěvky"
},
"status": {
"reply_to": "Odpovědět uživateli",
"replies_list": "Odpovědi:"
},
"user_card": {
"approve": "Schválit",
"block": "Blokovat",
"blocked": "Blokován/a!",
"deny": "Zamítnout",
"favorites": "Oblíbené",
"follow": "Sledovat",
"follow_sent": "Požadavek odeslán!",
"follow_progress": "Odeslílám požadavek…",
"follow_again": "Odeslat požadavek znovu?",
"follow_unfollow": "Přestat sledovat",
"followees": "Sledovaní",
"followers": "Sledující",
"following": "Sledujete!",
"follows_you": "Sleduje vás!",
"its_you": "Jste to vy!",
"media": "Média",
"mute": "Ignorovat",
"muted": "Ignorován/a",
"per_day": "za den",
"remote_follow": "Vzdálené sledování",
"statuses": "Příspěvky",
"unblock": "Odblokovat",
"unblock_progress": "Odblokuji…",
"block_progress": "Blokuji…",
"unmute": "Přestat ignorovat",
"unmute_progress": "Ruším ignorování…",
"mute_progress": "Ignoruji…"
},
"user_profile": {
"timeline_title": "Uživatelská časová osa",
"profile_does_not_exist": "Omlouváme se, tento profil neexistuje.",
"profile_loading_error": "Omlouváme se, při načítání tohoto profilu se vyskytla chyba."
},
"who_to_follow": {
"more": "Více",
"who_to_follow": "Koho sledovat"
},
"tool_tip": {
"media_upload": "Nahrát média",
"repeat": "Zopakovat",
"reply": "Odpovědět",
"favorite": "Oblíbit",
"user_settings": "Uživatelské nastavení"
},
"upload":{
"error": {
"base": "Nahrávání selhalo.",
"file_too_big": "Soubor je úříliš velký [{filesize}{filesizeunit} / {allowedsize}{allowedsizeunit}]",
"default": "Zkuste to znovu později"
},
"file_size_units": {
"B": "B",
"KiB": "KiB",
"MiB": "MiB",
"GiB": "GiB",
"TiB": "TiB"
}
}
}
+5
View File
@@ -159,6 +159,11 @@
"hide_follows_description": "Zeige nicht, wem ich folge", "hide_follows_description": "Zeige nicht, wem ich folge",
"hide_followers_description": "Zeige nicht, wer mir folgt", "hide_followers_description": "Zeige nicht, wer mir folgt",
"nsfw_clickthrough": "Aktiviere ausblendbares Overlay für Anhänge, die als NSFW markiert sind", "nsfw_clickthrough": "Aktiviere ausblendbares Overlay für Anhänge, die als NSFW markiert sind",
"oauth_tokens": "OAuth-Token",
"token": "Zeichen",
"refresh_token": "Token aktualisieren",
"valid_until": "Gültig bis",
"revoke_token": "Widerrufen",
"panelRadius": "Panel", "panelRadius": "Panel",
"pause_on_unfocused": "Streaming pausieren, wenn das Tab nicht fokussiert ist", "pause_on_unfocused": "Streaming pausieren, wenn das Tab nicht fokussiert ist",
"presets": "Voreinstellungen", "presets": "Voreinstellungen",
+42 -4
View File
@@ -19,7 +19,13 @@
"apply": "Apply", "apply": "Apply",
"submit": "Submit", "submit": "Submit",
"more": "More", "more": "More",
"generic_error": "An error occured" "generic_error": "An error occured",
"optional": "optional"
},
"image_cropper": {
"crop_picture": "Crop picture",
"save": "Save",
"cancel": "Cancel"
}, },
"login": { "login": {
"login": "Log in", "login": "Log in",
@@ -31,6 +37,10 @@
"username": "Username", "username": "Username",
"hint": "Log in to join the discussion" "hint": "Log in to join the discussion"
}, },
"media_modal": {
"previous": "Previous",
"next": "Next"
},
"nav": { "nav": {
"about": "About", "about": "About",
"back": "Back", "back": "Back",
@@ -83,6 +93,9 @@
"token": "Invite token", "token": "Invite token",
"captcha": "CAPTCHA", "captcha": "CAPTCHA",
"new_captcha": "Click the image to get a new captcha", "new_captcha": "Click the image to get a new captcha",
"username_placeholder": "e.g. lain",
"fullname_placeholder": "e.g. Lain Iwakura",
"bio_placeholder": "e.g.\nHi, I'm Lain\nIm an anime girl living in suburban Japan. You may know me from the Wired.",
"validations": { "validations": {
"username_required": "cannot be left blank", "username_required": "cannot be left blank",
"fullname_required": "cannot be left blank", "fullname_required": "cannot be left blank",
@@ -93,6 +106,7 @@
} }
}, },
"settings": { "settings": {
"app_name": "App name",
"attachmentRadius": "Attachments", "attachmentRadius": "Attachments",
"attachments": "Attachments", "attachments": "Attachments",
"autoload": "Enable automatic loading when scrolled to the bottom", "autoload": "Enable automatic loading when scrolled to the bottom",
@@ -101,6 +115,7 @@
"avatarRadius": "Avatars", "avatarRadius": "Avatars",
"background": "Background", "background": "Background",
"bio": "Bio", "bio": "Bio",
"blocks_tab": "Blocks",
"btnRadius": "Buttons", "btnRadius": "Buttons",
"cBlue": "Blue (Reply, follow)", "cBlue": "Blue (Reply, follow)",
"cGreen": "Green (Retweet)", "cGreen": "Green (Retweet)",
@@ -135,6 +150,7 @@
"general": "General", "general": "General",
"hide_attachments_in_convo": "Hide attachments in conversations", "hide_attachments_in_convo": "Hide attachments in conversations",
"hide_attachments_in_tl": "Hide attachments in timeline", "hide_attachments_in_tl": "Hide attachments in timeline",
"max_thumbnails": "Maximum amount of thumbnails per post",
"hide_isp": "Hide instance-specific panel", "hide_isp": "Hide instance-specific panel",
"preload_images": "Preload images", "preload_images": "Preload images",
"use_one_click_nsfw": "Open NSFW attachments with just one click", "use_one_click_nsfw": "Open NSFW attachments with just one click",
@@ -155,6 +171,7 @@
"lock_account_description": "Restrict your account to approved followers only", "lock_account_description": "Restrict your account to approved followers only",
"loop_video": "Loop videos", "loop_video": "Loop videos",
"loop_video_silent_only": "Loop only videos without sound (i.e. Mastodon's \"gifs\")", "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 directly in the media viewer",
"use_contain_fit": "Don't crop the attachment in thumbnails", "use_contain_fit": "Don't crop the attachment in thumbnails",
"name": "Name", "name": "Name",
@@ -166,11 +183,18 @@
"notification_visibility_mentions": "Mentions", "notification_visibility_mentions": "Mentions",
"notification_visibility_repeats": "Repeats", "notification_visibility_repeats": "Repeats",
"no_rich_text_description": "Strip rich text formatting from all posts", "no_rich_text_description": "Strip rich text formatting from all posts",
"no_blocks": "No blocks",
"no_mutes": "No mutes",
"hide_follows_description": "Don't show who I'm following", "hide_follows_description": "Don't show who I'm following",
"hide_followers_description": "Don't show who's following me", "hide_followers_description": "Don't show who's following me",
"show_admin_badge": "Show Admin badge in my profile", "show_admin_badge": "Show Admin badge in my profile",
"show_moderator_badge": "Show Moderator badge in my profile", "show_moderator_badge": "Show Moderator badge in my profile",
"nsfw_clickthrough": "Enable clickthrough NSFW attachment hiding", "nsfw_clickthrough": "Enable clickthrough NSFW attachment hiding",
"oauth_tokens": "OAuth tokens",
"token": "Token",
"refresh_token": "Refresh Token",
"valid_until": "Valid Until",
"revoke_token": "Revoke",
"panelRadius": "Panels", "panelRadius": "Panels",
"pause_on_unfocused": "Pause streaming when tab is not focused", "pause_on_unfocused": "Pause streaming when tab is not focused",
"presets": "Presets", "presets": "Presets",
@@ -206,6 +230,7 @@
"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.", "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.",
"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.", "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.",
"tooltipRadius": "Tooltips/alerts", "tooltipRadius": "Tooltips/alerts",
"upload_a_photo": "Upload a photo",
"user_settings": "User Settings", "user_settings": "User Settings",
"values": { "values": {
"false": "no", "false": "no",
@@ -332,7 +357,12 @@
"repeated": "repeated", "repeated": "repeated",
"show_new": "Show new", "show_new": "Show new",
"up_to_date": "Up-to-date", "up_to_date": "Up-to-date",
"no_more_statuses": "No more statuses" "no_more_statuses": "No more statuses",
"no_statuses": "No statuses"
},
"status": {
"reply_to": "Reply to",
"replies_list": "Replies:"
}, },
"user_card": { "user_card": {
"approve": "Approve", "approve": "Approve",
@@ -355,10 +385,18 @@
"muted": "Muted", "muted": "Muted",
"per_day": "per day", "per_day": "per day",
"remote_follow": "Remote follow", "remote_follow": "Remote follow",
"statuses": "Statuses" "statuses": "Statuses",
"unblock": "Unblock",
"unblock_progress": "Unblocking...",
"block_progress": "Blocking...",
"unmute": "Unmute",
"unmute_progress": "Unmuting...",
"mute_progress": "Muting..."
}, },
"user_profile": { "user_profile": {
"timeline_title": "User Timeline" "timeline_title": "User Timeline",
"profile_does_not_exist": "Sorry, this profile does not exist.",
"profile_loading_error": "Sorry, there was an error loading this profile."
}, },
"who_to_follow": { "who_to_follow": {
"more": "More", "more": "More",
+324 -21
View File
@@ -2,118 +2,421 @@
"chat": { "chat": {
"title": "Babilejo" "title": "Babilejo"
}, },
"features_panel": {
"chat": "Babilejo",
"gopher": "Gopher",
"media_proxy": "Aŭdvidaĵa prokurilo",
"scope_options": "Agordoj de amplekso",
"text_limit": "Teksta limo",
"title": "Funkcioj",
"who_to_follow": "Kiun aboni"
},
"finder": { "finder": {
"error_fetching_user": "Eraro alportante uzanton", "error_fetching_user": "Eraro alportante uzanton",
"find_user": "Trovi uzanton" "find_user": "Trovi uzanton"
}, },
"general": { "general": {
"apply": "Apliki", "apply": "Apliki",
"submit": "Sendi" "submit": "Sendi",
"more": "Pli",
"generic_error": "Eraro okazis",
"optional": "Malnepra"
},
"image_cropper": {
"crop_picture": "Tondi bildon",
"save": "Konservi",
"cancel": "Nuligi"
}, },
"login": { "login": {
"login": "Ensaluti", "login": "Saluti",
"logout": "Elsaluti", "description": "Saluti per OAuth",
"logout": "Adiaŭi",
"password": "Pasvorto", "password": "Pasvorto",
"placeholder": "ekz. lain", "placeholder": "ekz. lain",
"register": "Registriĝi", "register": "Registriĝi",
"username": "Salutnomo" "username": "Salutnomo",
"hint": "Salutu por partopreni la diskutadon"
},
"media_modal": {
"previous": "Antaŭa",
"next": "Sekva"
}, },
"nav": { "nav": {
"about": "Pri",
"back": "Reen",
"chat": "Loka babilejo", "chat": "Loka babilejo",
"friend_requests": "Abonaj petoj",
"mentions": "Mencioj", "mentions": "Mencioj",
"dms": "Rektaj mesaĝoj",
"public_tl": "Publika tempolinio", "public_tl": "Publika tempolinio",
"timeline": "Tempolinio", "timeline": "Tempolinio",
"twkn": "La tuta konata reto" "twkn": "La tuta konata reto",
"user_search": "Serĉi uzantojn",
"who_to_follow": "Kiun aboni",
"preferences": "Agordoj"
}, },
"notifications": { "notifications": {
"broken_favorite": "Nekonata stato, serĉante ĝin…",
"favorited_you": "ŝatis vian staton", "favorited_you": "ŝatis vian staton",
"followed_you": "ekabonis vin", "followed_you": "ekabonis vin",
"load_older": "Enlegi pli malnovajn sciigojn",
"notifications": "Sciigoj", "notifications": "Sciigoj",
"read": "Legite!", "read": "Legite!",
"repeated_you": "ripetis vian staton" "repeated_you": "ripetis vian staton",
"no_more_notifications": "Neniuj pliaj sciigoj"
}, },
"post_status": { "post_status": {
"new_status": "Afiŝi novan staton",
"account_not_locked_warning": "Via konto ne estas {0}. Iu ajn povas vin aboni por vidi viajn afiŝoj nur por abonantoj.",
"account_not_locked_warning_link": "ŝlosita",
"attachments_sensitive": "Marki kunsendaĵojn kiel konsternajn",
"content_type": {
"plain_text": "Plata teksto"
},
"content_warning": "Temo (malnepra)",
"default": "Ĵus alvenis al la Universala Kongreso!", "default": "Ĵus alvenis al la Universala Kongreso!",
"posting": "Afiŝante" "direct_warning": "Ĉi tiu afiŝo estos videbla nur por ĉiuj menciitaj uzantoj.",
"posting": "Afiŝante",
"scope": {
"direct": "Rekta Afiŝi nur al menciitaj uzantoj",
"private": "Nur abonantoj Afiŝi nur al abonantoj",
"public": "Publika Afiŝi al publikaj tempolinioj",
"unlisted": "Nelistigita Ne afiŝi al publikaj tempolinioj"
}
}, },
"registration": { "registration": {
"bio": "Priskribo", "bio": "Priskribo",
"email": "Retpoŝtadreso", "email": "Retpoŝtadreso",
"fullname": "Vidiga nomo", "fullname": "Vidiga nomo",
"password_confirm": "Konfirmo de pasvorto", "password_confirm": "Konfirmo de pasvorto",
"registration": "Registriĝo" "registration": "Registriĝo",
"token": "Invita ĵetono",
"captcha": "TESTO DE HOMECO",
"new_captcha": "Alklaku la bildon por akiri novan teston",
"username_placeholder": "ekz. lain",
"fullname_placeholder": "ekz. Lain Iwakura",
"bio_placeholder": "ekz.\nSaluton, mi estas Lain\nMi estas animea knabino vivante en Japanujo. Eble vi konas min de la retejo « Wired ».",
"validations": {
"username_required": "ne povas resti malplena",
"fullname_required": "ne povas resti malplena",
"email_required": "ne povas resti malplena",
"password_required": "ne povas resti malplena",
"password_confirmation_required": "ne povas resti malplena",
"password_confirmation_match": "samu la pasvorton"
}
}, },
"settings": { "settings": {
"app_name": "Nomo de aplikaĵo",
"attachmentRadius": "Kunsendaĵoj", "attachmentRadius": "Kunsendaĵoj",
"attachments": "Kunsendaĵoj", "attachments": "Kunsendaĵoj",
"autoload": "Ŝalti memfaran ŝarĝadon ĉe subo de paĝo", "autoload": "Ŝalti memfaran enlegadon ĉe subo de paĝo",
"avatar": "Profilbildo", "avatar": "Profilbildo",
"avatarAltRadius": "Profilbildoj (sciigoj)", "avatarAltRadius": "Profilbildoj (sciigoj)",
"avatarRadius": "Profilbildoj", "avatarRadius": "Profilbildoj",
"background": "Fono", "background": "Fono",
"bio": "Priskribo", "bio": "Priskribo",
"blocks_tab": "Baroj",
"btnRadius": "Butonoj", "btnRadius": "Butonoj",
"cBlue": "Blua (Respondo, abono)", "cBlue": "Blua (Respondo, abono)",
"cGreen": "Verda (Kunhavigo)", "cGreen": "Verda (Kunhavigo)",
"cOrange": "Oranĝa (Ŝato)", "cOrange": "Oranĝa (Ŝato)",
"cRed": "Ruĝa (Nuligo)", "cRed": "Ruĝa (Nuligo)",
"change_password": "Ŝanĝi pasvorton",
"change_password_error": "Okazis eraro dum ŝanĝo de via pasvorto.",
"changed_password": "Pasvorto sukcese ŝanĝiĝis!",
"collapse_subject": "Maletendi afiŝojn kun temoj",
"composing": "Verkante",
"confirm_new_password": "Konfirmu novan pasvorton",
"current_avatar": "Via nuna profilbildo", "current_avatar": "Via nuna profilbildo",
"current_password": "Nuna pasvorto",
"current_profile_banner": "Via nuna profila rubando", "current_profile_banner": "Via nuna profila rubando",
"data_import_export_tab": "Enporto / Elporto de datenoj",
"default_vis": "Implicita videbleca amplekso",
"delete_account": "Forigi konton",
"delete_account_description": "Por ĉiam forigi vian konton kaj ĉiujn viajn mesaĝojn",
"delete_account_error": "Okazis eraro dum forigo de via kanto. Se tio daŭre okazados, bonvolu kontakti la administranton de via nodo.",
"delete_account_instructions": "Entajpu sube vian pasvorton por konfirmi forigon de konto.",
"avatar_size_instruction": "La rekomendata malpleja grando de profilbildoj estas 150×150 bilderoj.",
"export_theme": "Konservi antaŭagordon",
"filtering": "Filtrado", "filtering": "Filtrado",
"filtering_explanation": "Ĉiuj statoj kun tiuj ĉi vortoj silentiĝos, po unu linie", "filtering_explanation": "Ĉiuj statoj kun tiuj ĉi vortoj silentiĝos, po unu linio",
"follow_export": "Abona elporto",
"follow_export_button": "Elporti viajn abonojn al CSV-dosiero",
"follow_export_processing": "Traktante; baldaŭ vi ricevos peton elŝuti la dosieron",
"follow_import": "Abona enporto", "follow_import": "Abona enporto",
"follow_import_error": "Eraro enportante abonojn", "follow_import_error": "Eraro enportante abonojn",
"follows_imported": "Abonoj enportiĝis! Traktado daŭros iom.", "follows_imported": "Abonoj enportiĝis! Traktado daŭros iom.",
"foreground": "Malfono", "foreground": "Malfono",
"general": "Ĝenerala",
"hide_attachments_in_convo": "Kaŝi kunsendaĵojn en interparoloj", "hide_attachments_in_convo": "Kaŝi kunsendaĵojn en interparoloj",
"hide_attachments_in_tl": "Kaŝi kunsendaĵojn en tempolinio", "hide_attachments_in_tl": "Kaŝi kunsendaĵojn en tempolinio",
"max_thumbnails": "Plej multa nombro da bildetoj po afiŝo",
"hide_isp": "Kaŝi nodo-propran breton",
"preload_images": "Antaŭ-enlegi bildojn",
"use_one_click_nsfw": "Malfermi konsternajn kunsendaĵojn per nur unu klako",
"hide_post_stats": "Kaŝi statistikon de afiŝoj (ekz. nombron da ŝatoj)",
"hide_user_stats": "Kaŝi statistikon de uzantoj (ekz. nombron da abonantoj)",
"hide_filtered_statuses": "Kaŝi filtritajn statojn",
"import_followers_from_a_csv_file": "Enporti abonojn el CSV-dosiero", "import_followers_from_a_csv_file": "Enporti abonojn el CSV-dosiero",
"import_theme": "Enlegi antaŭagordojn",
"inputRadius": "Enigaj kampoj",
"checkboxRadius": "Markbutonoj",
"instance_default": "(implicita: {value})",
"instance_default_simple": "(implicita)",
"interface": "Fasado",
"interfaceLanguage": "Lingvo de fasado",
"invalid_theme_imported": "La elektita dosiero ne estas subtenata haŭto de Pleromo. Neniuj ŝanĝoj al via haŭto okazis.",
"limited_availability": "Nehavebla en via foliumilo",
"links": "Ligiloj", "links": "Ligiloj",
"lock_account_description": "Limigi vian konton al nur abonantoj aprobitaj",
"loop_video": "Ripetadi filmojn",
"loop_video_silent_only": "Ripetadi nur filmojn sen sono (ekz. la \"GIF-ojn\" de Mastodon)",
"mutes_tab": "Silentigoj",
"play_videos_in_modal": "Ludi filmojn rekte en la aŭdvidaĵa spektilo",
"use_contain_fit": "Ne tondi la kunsendaĵon en bildetoj",
"name": "Nomo", "name": "Nomo",
"name_bio": "Nomo kaj priskribo", "name_bio": "Nomo kaj priskribo",
"new_password": "Nova pasvorto",
"notification_visibility": "Montrotaj specoj de sciigoj",
"notification_visibility_follows": "Abonoj",
"notification_visibility_likes": "Ŝatoj",
"notification_visibility_mentions": "Mencioj",
"notification_visibility_repeats": "Ripetoj",
"no_rich_text_description": "Forigi riĉtekstajn formojn de ĉiuj afiŝoj",
"no_blocks": "Neniuj baroj",
"no_mutes": "Neniuj silentigoj",
"hide_follows_description": "Ne montri kiun mi sekvas",
"hide_followers_description": "Ne montri kiu min sekvas",
"show_admin_badge": "Montri la insignon de administranto en mia profilo",
"show_moderator_badge": "Montri la insignon de kontrolanto en mia profilo",
"nsfw_clickthrough": "Ŝalti traklakan kaŝon de konsternaj kunsendaĵoj", "nsfw_clickthrough": "Ŝalti traklakan kaŝon de konsternaj kunsendaĵoj",
"panelRadius": "Paneloj", "oauth_tokens": "Ĵetonoj de OAuth",
"token": "Ĵetono",
"refresh_token": "Ĵetono de novigo",
"valid_until": "Valida ĝis",
"revoke_token": "Senvalidigi",
"panelRadius": "Bretoj",
"pause_on_unfocused": "Paŭzigi elsendfluon kiam langeto ne estas fokusata",
"presets": "Antaŭagordoj", "presets": "Antaŭagordoj",
"profile_background": "Profila fono", "profile_background": "Profila fono",
"profile_banner": "Profila rubando", "profile_banner": "Profila rubando",
"radii_help": "Agordi fasadan rondigon de randoj (rastrumere)", "profile_tab": "Profilo",
"reply_link_preview": "Ŝalti respond-ligilan antaŭvidon dum ŝvebo", "radii_help": "Agordi fasadan rondigon de randoj (bildere)",
"replies_in_timeline": "Respondoj en tempolinio",
"reply_link_preview": "Ŝalti respond-ligilan antaŭvidon dum musa ŝvebo",
"reply_visibility_all": "Montri ĉiujn respondojn",
"reply_visibility_following": "Montri nur respondojn por mi aŭ miaj abonatoj",
"reply_visibility_self": "Montri nur respondojn por mi",
"saving_err": "Eraro dum konservo de agordoj",
"saving_ok": "Agordoj konserviĝis",
"security_tab": "Sekureco",
"scope_copy": "Kopii amplekson por respondo (rektaj mesaĝoj ĉiam kopiiĝas)",
"set_new_avatar": "Agordi novan profilbildon", "set_new_avatar": "Agordi novan profilbildon",
"set_new_profile_background": "Agordi novan profilan fonon", "set_new_profile_background": "Agordi novan profilan fonon",
"set_new_profile_banner": "Agordi novan profilan rubandon", "set_new_profile_banner": "Agordi novan profilan rubandon",
"settings": "Agordoj", "settings": "Agordoj",
"stop_gifs": "Movi GIF-bildojn dum ŝvebo", "subject_input_always_show": "Ĉiam montri teman kampon",
"subject_line_behavior": "Kopii temon por respondo",
"subject_line_email": "Kiel retpoŝto: \"re: temo\"",
"subject_line_mastodon": "Kiel Mastodon: kopii senŝanĝe",
"subject_line_noop": "Ne kopii",
"post_status_content_type": "Afiŝi specon de la enhavo de la stato",
"status_content_type_plain": "Plata teksto",
"stop_gifs": "Movi GIF-bildojn dum musa ŝvebo",
"streaming": "Ŝalti memfaran fluigon de novaj afiŝoj ĉe la supro de la paĝo", "streaming": "Ŝalti memfaran fluigon de novaj afiŝoj ĉe la supro de la paĝo",
"text": "Teksto", "text": "Teksto",
"theme": "Etoso", "theme": "Haŭto",
"theme_help": "Uzu deksesumajn kolorkodojn (#rrvvbb) por adapti vian koloran etoson.", "theme_help": "Uzu deksesumajn kolorkodojn (#rrvvbb) por adapti vian koloran haŭton.",
"theme_help_v2_1": "Vi ankaŭ povas superagordi la kolorojn kaj travideblecon de kelkaj eroj per marko de la markbutono; uzu la butonon \"Vakigi ĉion\" por forigi ĉîujn superagordojn.",
"theme_help_v2_2": "Bildsimboloj sub kelkaj eroj estas indikiloj de kontrasto inter fono kaj teksto; muse ŝvebu por detalaj informoj. Bonvolu memori, ke la indikilo montras la plej malbonan okazeblon dum sia uzo.",
"tooltipRadius": "Ŝpruchelpiloj/avertoj", "tooltipRadius": "Ŝpruchelpiloj/avertoj",
"user_settings": "Uzantaj agordoj" "upload_a_photo": "Alŝuti foton",
"user_settings": "Agordoj de uzanto",
"values": {
"false": "ne",
"true": "jes"
},
"notifications": "Sciigoj",
"enable_web_push_notifications": "Ŝalti retajn puŝajn sciigojn",
"style": {
"switcher": {
"keep_color": "Konservi kolorojn",
"keep_shadows": "Konservi ombrojn",
"keep_opacity": "Konservi maltravideblecon",
"keep_roundness": "Konservi rondecon",
"keep_fonts": "Konservi tiparojn",
"save_load_hint": "Elektebloj de \"konservi\" konservas la nuntempajn agordojn dum elektado aŭ enlegado de haŭtoj. Ĝi ankaŭ konservas tiujn agordojn dum elportado de haŭto. Kun ĉiuj markbutonoj nemarkitaj, elporto de la haŭto ĉion konservos.",
"reset": "Restarigi",
"clear_all": "Vakigi ĉion",
"clear_opacity": "Vakigi maltravideblecon"
},
"common": {
"color": "Koloro",
"opacity": "Maltravidebleco",
"contrast": {
"hint": "Proporcio de kontrasto estas {ratio}, ĝi {level} {context}",
"level": {
"aa": "plenumas la gvidilon je nivelo AA (malpleja)",
"aaa": "plenumas la gvidilon je nivela AAA (rekomendita)",
"bad": "plenumas neniujn faciluzajn gvidilojn"
},
"context": {
"18pt": "por granda (18pt+) teksto",
"text": "por teksto"
}
}
},
"common_colors": {
"_tab_label": "Komunaj",
"main": "Komunaj koloroj",
"foreground_hint": "Vidu langeton \"Specialaj\" por pli detalaj agordoj",
"rgbo": "Bildsimboloj, emfazoj, insignoj"
},
"advanced_colors": {
"_tab_label": "Specialaj",
"alert": "Averta fono",
"alert_error": "Eraro",
"badge": "Insigna fono",
"badge_notification": "Sciigo",
"panel_header": "Kapo de breto",
"top_bar": "Supra breto",
"borders": "Limoj",
"buttons": "Butonoj",
"inputs": "Enigaj kampoj",
"faint_text": "Malvigla teksto"
},
"radii": {
"_tab_label": "Rondeco"
},
"shadows": {
"_tab_label": "Ombro kaj lumo",
"component": "Ero",
"override": "Transpasi",
"shadow_id": "Ombro #{value}",
"blur": "Malklarigo",
"spread": "Vastigo",
"inset": "Internigo",
"hint": "Por ombroj vi ankaŭ povas uzi --variable kiel koloran valoron, por uzi variantojn de CSS3. Bonvolu rimarki, ke tiuokaze agordoj de maltravidebleco ne funkcios.",
"filter_hint": {
"always_drop_shadow": "Averto: ĉi tiu ombro ĉiam uzas {0} kiam la foliumilo ĝin subtenas.",
"drop_shadow_syntax": "{0} ne subtenas parametron {1} kaj ŝlosilvorton {2}.",
"avatar_inset": "Bonvolu rimarki, ke agordi ambaŭ internajn kaj eksterajn ombrojn por profilbildoj povas redoni neatenditajn rezultojn ĉe profilbildoj travideblaj.",
"spread_zero": "Ombroj kun vastigo > 0 aperos kvazaŭ ĝi estus fakte nulo",
"inset_classic": "Internaj ombroj uzos {0}"
},
"components": {
"panel": "Breto",
"panelHeader": "Kapo de breto",
"topBar": "Supra breto",
"avatar": "Profilbildo de uzanto (en profila vido)",
"avatarStatus": "Profilbildo de uzanto (en afiŝa vido)",
"popup": "Ŝprucaĵoj",
"button": "Butono",
"buttonHover": "Butono (je ŝvebo)",
"buttonPressed": "Butono (premita)",
"buttonPressedHover": "Butono (premita je ŝvebo)",
"input": "Eniga kampo"
}
},
"fonts": {
"_tab_label": "Tiparoj",
"help": "Elektu tiparon uzotan por eroj de la fasado. Por \"propra\" vi devas enigi la precizan nomon de tiparo tiel, kiel ĝi aperas en la sistemo",
"components": {
"interface": "Fasado",
"input": "Enigaj kampoj",
"post": "Teksto de afiŝo",
"postCode": "Egallarĝa teksto en afiŝo (riĉteksto)"
},
"family": "Nomo de tiparo",
"size": "Grando (en bilderoj)",
"weight": "Pezo (graseco)",
"custom": "Propra"
},
"preview": {
"header": "Antaŭrigardo",
"content": "Enhavo",
"error": "Ekzempla eraro",
"button": "Butono",
"text": "Kelko da pliaj {0} kaj {1}",
"mono": "enhavo",
"input": "Ĵus alvenis al la Universala Kongreso!",
"faint_link": "helpan manlibron",
"fine_print": "Legu nian {0} por nenion utilan ekscii!",
"header_faint": "Tio estas en ordo",
"checkbox": "Mi legetis la kondiĉojn de uzado",
"link": "bela eta ligil"
}
}
}, },
"timeline": { "timeline": {
"collapse": "Maletendi", "collapse": "Maletendi",
"conversation": "Interparolo", "conversation": "Interparolo",
"error_fetching": "Eraro dum ĝisdatigo", "error_fetching": "Eraro dum ĝisdatigo",
"load_older": "Montri pli malnovajn statojn", "load_older": "Montri pli malnovajn statojn",
"repeated": "ripetata", "no_retweet_hint": "Afiŝo estas markita kiel rekta aŭ nur por abonantoj, kaj ne eblas ĝin ripeti",
"repeated": "ripetita",
"show_new": "Montri novajn", "show_new": "Montri novajn",
"up_to_date": "Ĝisdata" "up_to_date": "Ĝisdata",
"no_more_statuses": "Neniuj pliaj statoj",
"no_statuses": "Neniuj statoj"
}, },
"user_card": { "user_card": {
"approve": "Aprobi",
"block": "Bari", "block": "Bari",
"blocked": "Barita!", "blocked": "Barita!",
"deny": "Rifuzi",
"favorites": "Ŝatataj",
"follow": "Aboni", "follow": "Aboni",
"follow_sent": "Peto sendiĝis!",
"follow_progress": "Petanta…",
"follow_again": "Ĉu sendi peton denove?",
"follow_unfollow": "Malaboni",
"followees": "Abonatoj", "followees": "Abonatoj",
"followers": "Abonantoj", "followers": "Abonantoj",
"following": "Abonanta!", "following": "Abonanta!",
"follows_you": "Abonas vin!", "follows_you": "Abonas vin!",
"its_you": "Tio estas vi!",
"media": "Aŭdvidaĵoj",
"mute": "Silentigi", "mute": "Silentigi",
"muted": "Silentigitaj", "muted": "Silentigitaj",
"per_day": "tage", "per_day": "tage",
"remote_follow": "Fore aboni", "remote_follow": "Fore aboni",
"statuses": "Statoj" "statuses": "Statoj",
"unblock": "Malbari",
"unblock_progress": "Malbaranta…",
"block_progress": "Baranta…",
"unmute": "Malsilentigi",
"unmute_progress": "Malsilentiganta…",
"mute_progress": "Silentiganta…"
}, },
"user_profile": { "user_profile": {
"timeline_title": "Uzanta tempolinio" "timeline_title": "Uzanta tempolinio",
"profile_does_not_exist": "Pardonu, ĉi tiu profilo ne ekzistas.",
"profile_loading_error": "Pardonu, eraro okazis dum enlegado de ĉi tiu profilo."
},
"who_to_follow": {
"more": "Pli",
"who_to_follow": "Kiun aboni"
},
"tool_tip": {
"media_upload": "Alŝuti aŭdvidaĵon",
"repeat": "Ripeti",
"reply": "Respondi",
"favorite": "Ŝati",
"user_settings": "Agordoj de uzanto"
},
"upload":{
"error": {
"base": "Alŝuto malsukcesis.",
"file_too_big": "Dosiero estas tro granda [{filesize}{filesizeunit} / {allowedsize}{allowedsizeunit}]",
"default": "Reprovu pli poste"
},
"file_size_units": {
"B": "B",
"KiB": "KiB",
"MiB": "MiB",
"GiB": "GiB",
"TiB": "TiB"
}
} }
} }
+5
View File
@@ -171,6 +171,11 @@
"show_admin_badge": "Mostrar la placa de administrador en mi perfil", "show_admin_badge": "Mostrar la placa de administrador en mi perfil",
"show_moderator_badge": "Mostrar la placa de moderador en mi perfil", "show_moderator_badge": "Mostrar la placa de moderador en mi perfil",
"nsfw_clickthrough": "Activar el clic para ocultar los adjuntos NSFW", "nsfw_clickthrough": "Activar el clic para ocultar los adjuntos NSFW",
"oauth_tokens": "Tokens de OAuth",
"token": "Token",
"refresh_token": "Actualizar el token",
"valid_until": "Válido hasta",
"revoke_token": "Revocar",
"panelRadius": "Paneles", "panelRadius": "Paneles",
"pause_on_unfocused": "Parar la transmisión cuando no estés en foco.", "pause_on_unfocused": "Parar la transmisión cuando no estés en foco.",
"presets": "Por defecto", "presets": "Por defecto",
+10
View File
@@ -133,6 +133,7 @@
"general": "Yleinen", "general": "Yleinen",
"hide_attachments_in_convo": "Piilota liitteet keskusteluissa", "hide_attachments_in_convo": "Piilota liitteet keskusteluissa",
"hide_attachments_in_tl": "Piilota liitteet aikajanalla", "hide_attachments_in_tl": "Piilota liitteet aikajanalla",
"max_thumbnails": "Suurin sallittu määrä liitteitä esikatselussa",
"hide_isp": "Piilota palvelimenkohtainen ruutu", "hide_isp": "Piilota palvelimenkohtainen ruutu",
"preload_images": "Esilataa kuvat", "preload_images": "Esilataa kuvat",
"use_one_click_nsfw": "Avaa NSFW-liitteet yhdellä painalluksella", "use_one_click_nsfw": "Avaa NSFW-liitteet yhdellä painalluksella",
@@ -165,6 +166,11 @@
"no_rich_text_description": "Älä näytä tekstin muotoilua.", "no_rich_text_description": "Älä näytä tekstin muotoilua.",
"hide_network_description": "Älä näytä seurauksiani tai seuraajiani", "hide_network_description": "Älä näytä seurauksiani tai seuraajiani",
"nsfw_clickthrough": "Piilota NSFW liitteet klikkauksen taakse", "nsfw_clickthrough": "Piilota NSFW liitteet klikkauksen taakse",
"oauth_tokens": "OAuth-merkit",
"token": "Token",
"refresh_token": "Päivitä token",
"valid_until": "Voimassa asti",
"revoke_token": "Peruuttaa",
"panelRadius": "Ruudut", "panelRadius": "Ruudut",
"pause_on_unfocused": "Pysäytä automaattinen viestien näyttö välilehden ollessa pois fokuksesta", "pause_on_unfocused": "Pysäytä automaattinen viestien näyttö välilehden ollessa pois fokuksesta",
"presets": "Valmiit teemat", "presets": "Valmiit teemat",
@@ -215,6 +221,10 @@
"up_to_date": "Ajantasalla", "up_to_date": "Ajantasalla",
"no_more_statuses": "Ei enempää viestejä" "no_more_statuses": "Ei enempää viestejä"
}, },
"status": {
"reply_to": "Vastaus",
"replies_list": "Vastaukset:"
},
"user_card": { "user_card": {
"approve": "Hyväksy", "approve": "Hyväksy",
"block": "Estä", "block": "Estä",
+5
View File
@@ -137,6 +137,11 @@
"notification_visibility_mentions": "Mentionnés", "notification_visibility_mentions": "Mentionnés",
"notification_visibility_repeats": "Partages", "notification_visibility_repeats": "Partages",
"nsfw_clickthrough": "Masquer les images marquées comme contenu adulte ou sensible", "nsfw_clickthrough": "Masquer les images marquées comme contenu adulte ou sensible",
"oauth_tokens": "Jetons OAuth",
"token": "Jeton",
"refresh_token": "Refresh Token",
"valid_until": "Valable jusque",
"revoke_token": "Révoquer",
"panelRadius": "Fenêtres", "panelRadius": "Fenêtres",
"pause_on_unfocused": "Suspendre le streaming lorsque l'onglet n'est pas centré", "pause_on_unfocused": "Suspendre le streaming lorsque l'onglet n'est pas centré",
"presets": "Thèmes prédéfinis", "presets": "Thèmes prédéfinis",
+5
View File
@@ -134,6 +134,11 @@
"notification_visibility_repeats": "Atphostáil", "notification_visibility_repeats": "Atphostáil",
"no_rich_text_description": "Bain formáidiú téacs saibhir ó gach post", "no_rich_text_description": "Bain formáidiú téacs saibhir ó gach post",
"nsfw_clickthrough": "Cumasaigh an ceangaltán NSFW cliceáil ar an gcnaipe", "nsfw_clickthrough": "Cumasaigh an ceangaltán NSFW cliceáil ar an gcnaipe",
"oauth_tokens": "Tocanna OAuth",
"token": "Token",
"refresh_token": "Athnuachan Comórtas",
"valid_until": "Bailí Go dtí",
"revoke_token": "Athghairm",
"panelRadius": "Painéil", "panelRadius": "Painéil",
"pause_on_unfocused": "Sruthú ar sos nuair a bhíonn an fócas caillte", "pause_on_unfocused": "Sruthú ar sos nuair a bhíonn an fócas caillte",
"presets": "Réamhshocruithe", "presets": "Réamhshocruithe",
+5
View File
@@ -129,6 +129,11 @@
"notification_visibility_mentions": "אזכורים", "notification_visibility_mentions": "אזכורים",
"notification_visibility_repeats": "חזרות", "notification_visibility_repeats": "חזרות",
"nsfw_clickthrough": "החל החבאת צירופים לא בטוחים לצפיה בעת עבודה בעזרת לחיצת עכבר", "nsfw_clickthrough": "החל החבאת צירופים לא בטוחים לצפיה בעת עבודה בעזרת לחיצת עכבר",
"oauth_tokens": "אסימוני OAuth",
"token": "אסימון",
"refresh_token": "רענון האסימון",
"valid_until": "בתוקף עד",
"revoke_token": "בטל",
"panelRadius": "פאנלים", "panelRadius": "פאנלים",
"pause_on_unfocused": "השהה זרימת הודעות כשהחלון לא בפוקוס", "pause_on_unfocused": "השהה זרימת הודעות כשהחלון לא בפוקוס",
"presets": "ערכים קבועים מראש", "presets": "ערכים קבועים מראש",
+5
View File
@@ -93,6 +93,11 @@
"notification_visibility_mentions": "Menzioni", "notification_visibility_mentions": "Menzioni",
"notification_visibility_repeats": "Condivisioni", "notification_visibility_repeats": "Condivisioni",
"no_rich_text_description": "Togli la formattazione del testo da tutti i post", "no_rich_text_description": "Togli la formattazione del testo da tutti i post",
"oauth_tokens": "Token OAuth",
"token": "Token",
"refresh_token": "Aggiorna token",
"valid_until": "Valido fino a",
"revoke_token": "Revocare",
"panelRadius": "Pannelli", "panelRadius": "Pannelli",
"pause_on_unfocused": "Metti in pausa l'aggiornamento continuo quando la scheda non è in primo piano", "pause_on_unfocused": "Metti in pausa l'aggiornamento continuo quando la scheda non è in primo piano",
"presets": "Valori predefiniti", "presets": "Valori predefiniti",
+5
View File
@@ -171,6 +171,11 @@
"show_admin_badge": "アドミンのしるしをみる", "show_admin_badge": "アドミンのしるしをみる",
"show_moderator_badge": "モデレーターのしるしをみる", "show_moderator_badge": "モデレーターのしるしをみる",
"nsfw_clickthrough": "NSFWなファイルをかくす", "nsfw_clickthrough": "NSFWなファイルをかくす",
"oauth_tokens": "OAuthトークン",
"token": "トークン",
"refresh_token": "トークンを更新",
"valid_until": "まで有効",
"revoke_token": "取り消す",
"panelRadius": "パネル", "panelRadius": "パネル",
"pause_on_unfocused": "タブにフォーカスがないときストリーミングをとめる", "pause_on_unfocused": "タブにフォーカスがないときストリーミングをとめる",
"presets": "プリセット", "presets": "プリセット",
+5
View File
@@ -159,6 +159,11 @@
"hide_follows_description": "내가 팔로우하는 사람을 표시하지 않음", "hide_follows_description": "내가 팔로우하는 사람을 표시하지 않음",
"hide_followers_description": "나를 따르는 사람을 보여주지 마라.", "hide_followers_description": "나를 따르는 사람을 보여주지 마라.",
"nsfw_clickthrough": "NSFW 이미지 \"클릭해서 보이기\"를 활성화", "nsfw_clickthrough": "NSFW 이미지 \"클릭해서 보이기\"를 활성화",
"oauth_tokens": "OAuth 토큰",
"token": "토큰",
"refresh_token": "토큰 새로 고침",
"valid_until": "까지 유효하다",
"revoke_token": "취소",
"panelRadius": "패널", "panelRadius": "패널",
"pause_on_unfocused": "탭이 활성 상태가 아닐 때 스트리밍 멈추기", "pause_on_unfocused": "탭이 활성 상태가 아닐 때 스트리밍 멈추기",
"presets": "프리셋", "presets": "프리셋",
+1
View File
@@ -10,6 +10,7 @@
const messages = { const messages = {
ar: require('./ar.json'), ar: require('./ar.json'),
ca: require('./ca.json'), ca: require('./ca.json'),
cs: require('./cs.json'),
de: require('./de.json'), de: require('./de.json'),
en: require('./en.json'), en: require('./en.json'),
eo: require('./eo.json'), eo: require('./eo.json'),
+5
View File
@@ -132,6 +132,11 @@
"notification_visibility_repeats": "Gjentakelser", "notification_visibility_repeats": "Gjentakelser",
"no_rich_text_description": "Fjern all formatering fra statuser", "no_rich_text_description": "Fjern all formatering fra statuser",
"nsfw_clickthrough": "Krev trykk for å vise statuser som kan være upassende", "nsfw_clickthrough": "Krev trykk for å vise statuser som kan være upassende",
"oauth_tokens": "OAuth Tokens",
"token": "Pollett",
"refresh_token": "Refresh Token",
"valid_until": "Gyldig til",
"revoke_token": "Tilbakekall",
"panelRadius": "Panel", "panelRadius": "Panel",
"pause_on_unfocused": "Stopp henting av poster når vinduet ikke er i fokus", "pause_on_unfocused": "Stopp henting av poster når vinduet ikke er i fokus",
"presets": "Forhåndsdefinerte tema", "presets": "Forhåndsdefinerte tema",
+5
View File
@@ -159,6 +159,11 @@
"no_rich_text_description": "Strip rich text formattering van alle posts", "no_rich_text_description": "Strip rich text formattering van alle posts",
"hide_network_description": "Toon niet wie mij volgt en wie ik volg.", "hide_network_description": "Toon niet wie mij volgt en wie ik volg.",
"nsfw_clickthrough": "Schakel doorklikbaar verbergen van NSFW bijlages in", "nsfw_clickthrough": "Schakel doorklikbaar verbergen van NSFW bijlages in",
"oauth_tokens": "OAuth-tokens",
"token": "Token",
"refresh_token": "Token vernieuwen",
"valid_until": "Geldig tot",
"revoke_token": "Intrekken",
"panelRadius": "Panelen", "panelRadius": "Panelen",
"pause_on_unfocused": "Pauzeer streamen wanneer de tab niet gefocused is", "pause_on_unfocused": "Pauzeer streamen wanneer de tab niet gefocused is",
"presets": "Presets", "presets": "Presets",
+172 -60
View File
@@ -1,51 +1,82 @@
{ {
"chat": { "chat": {
"title": "Messatjariá" "title": "Messatjariá"
},
"features_panel": {
"chat": "Chat",
"gopher": "Gopher",
"media_proxy": "Servidor mandatari mèdia",
"scope_options": "Nivèls de confidencialitat",
"text_limit": "Limita de tèxte",
"title": "Foncionalitats",
"who_to_follow": "Qual seguir"
}, },
"finder": { "finder": {
"error_fetching_user": "Error pendent la recèrca dun utilizaire", "error_fetching_user": "Error pendent la cèrca dun utilizaire",
"find_user": "Cercar un utilizaire" "find_user": "Cercar un utilizaire"
}, },
"general": { "general": {
"apply": "Aplicar", "apply": "Aplicar",
"submit": "Mandar" "submit": "Mandar",
"more": "Mai",
"generic_error": "Una error ses producha",
"optional": "opcional"
},
"image_cropper": {
"crop_picture": "Talhar limatge",
"save": "Salvar",
"cancel": "Anullar"
}, },
"login": { "login": {
"login": "Connexion", "login": "Connexion",
"description": "Connexion via OAuth",
"logout": "Desconnexion", "logout": "Desconnexion",
"password": "Senhal", "password": "Senhal",
"placeholder": "e.g. lain", "placeholder": "e.g. lain",
"register": "Se marcar", "register": "Se marcar",
"username": "Nom dutilizaire" "username": "Nom dutilizaire",
"hint": "Connectatz-vos per participar a la discutida"
},
"media_modal": {
"previous": "Precedent",
"next": "Seguent"
}, },
"nav": { "nav": {
"about": "A prepaus",
"back": "Tornar",
"chat": "Chat local", "chat": "Chat local",
"friend_requests": "Demandas de seguiment",
"mentions": "Notificacions", "mentions": "Notificacions",
"dms": "Messatges privats",
"public_tl": "Estatuts locals", "public_tl": "Estatuts locals",
"timeline": "Flux dactualitat", "timeline": "Flux dactualitat",
"twkn": "Lo malhum conegut", "twkn": "Lo malhum conegut",
"friend_requests": "Demandas d'abonament" "user_search": "Cèrca dutilizaires",
"who_to_follow": "Qual seguir",
"preferences": "Preferéncias"
}, },
"notifications": { "notifications": {
"broken_favorite": "Estatut desconegut, sèm a lo cercar...",
"favorited_you": "a aimat vòstre estatut", "favorited_you": "a aimat vòstre estatut",
"followed_you": "vos a seguit", "followed_you": "vos a seguit",
"load_older": "Cargar las notificaciones mai ancianas",
"notifications": "Notficacions", "notifications": "Notficacions",
"read": "Legit !", "read": "Legit !",
"repeated_you": "a repetit vòstre estatut", "repeated_you": "a repetit vòstre estatut",
"broken_favorite": "Estatut desconegut, sèm a lo cercar...", "no_more_notifications": "Pas mai de notificacions"
"load_older": "Cargar las notificaciones mai ancianas"
}, },
"post_status": { "post_status": {
"content_warning": "Avís de contengut (opcional)", "new_status": "Publicar destatuts novèls",
"default": "Escrivètz aquí vòstre estatut.",
"posting": "Mandadís",
"account_not_locked_warning": "Vòstre compte es pas {0}. Qual que siá pòt vos seguir per veire vòstras publicacions destinadas pas qu'a vòstres seguidors.", "account_not_locked_warning": "Vòstre compte es pas {0}. Qual que siá pòt vos seguir per veire vòstras publicacions destinadas pas qu'a vòstres seguidors.",
"account_not_locked_warning_link": "clavat", "account_not_locked_warning_link": "clavat",
"attachments_sensitive": "Marcar las pèças juntas coma sensiblas", "attachments_sensitive": "Marcar las pèças juntas coma sensiblas",
"content_type": { "content_type": {
"plain_text": "Tèxte brut" "plain_text": "Tèxte brut"
}, },
"content_warning": "Avís de contengut (opcional)",
"default": "Escrivètz aquí vòstre estatut.",
"direct_warning": "Aquesta publicacion serà pas que visibla pels utilizaires mencionats.", "direct_warning": "Aquesta publicacion serà pas que visibla pels utilizaires mencionats.",
"posting": "Mandadís",
"scope": { "scope": {
"direct": "Dirècte - Publicar pels utilizaires mencionats solament", "direct": "Dirècte - Publicar pels utilizaires mencionats solament",
"private": "Seguidors solament - Publicar pels sols seguidors", "private": "Seguidors solament - Publicar pels sols seguidors",
@@ -59,9 +90,23 @@
"fullname": "Nom complèt", "fullname": "Nom complèt",
"password_confirm": "Confirmar lo senhal", "password_confirm": "Confirmar lo senhal",
"registration": "Inscripcion", "registration": "Inscripcion",
"token": "Geton de convidat" "token": "Geton de convidat",
"captcha": "CAPTCHA",
"new_captcha": "Clicatz limatge per obténer una nòva captcha",
"username_placeholder": "e.g. lain",
"fullname_placeholder": "e.g. Lain Iwakura",
"bio_placeholder": "e.g.\nHi, Soi lo Lain\nSoi afocada danimes e vivi al Japan. Benlèu que me coneissètz de the Wired.",
"validations": {
"username_required": "pòt pas èsser void",
"fullname_required": "pòt pas èsser void",
"email_required": "pòt pas èsser void",
"password_required": "pòt pas èsser void",
"password_confirmation_required": "pòt pas èsser void",
"password_confirmation_match": "deu èsser lo meteis senhal"
}
}, },
"settings": { "settings": {
"app_name": "Nom de laplicacion",
"attachmentRadius": "Pèças juntas", "attachmentRadius": "Pèças juntas",
"attachments": "Pèças juntas", "attachments": "Pèças juntas",
"autoload": "Activar lo cargament automatic un còp arribat al cap de la pagina", "autoload": "Activar lo cargament automatic un còp arribat al cap de la pagina",
@@ -70,6 +115,7 @@
"avatarRadius": "Avatars", "avatarRadius": "Avatars",
"background": "Rèire plan", "background": "Rèire plan",
"bio": "Biografia", "bio": "Biografia",
"blocks_tab": "Blocatges",
"btnRadius": "Botons", "btnRadius": "Botons",
"cBlue": "Blau (Respondre, seguir)", "cBlue": "Blau (Respondre, seguir)",
"cGreen": "Verd (Repartajar)", "cGreen": "Verd (Repartajar)",
@@ -78,15 +124,21 @@
"change_password": "Cambiar lo senhal", "change_password": "Cambiar lo senhal",
"change_password_error": "Una error ses producha en cambiant lo senhal.", "change_password_error": "Una error ses producha en cambiant lo senhal.",
"changed_password": "Senhal corrèctament cambiat !", "changed_password": "Senhal corrèctament cambiat !",
"collapse_subject": "Replegar las publicacions amb de subjèctes",
"composing": "Escritura",
"confirm_new_password": "Confirmatz lo nòu senhal", "confirm_new_password": "Confirmatz lo nòu senhal",
"current_avatar": "Vòstre avatar actual", "current_avatar": "Vòstre avatar actual",
"current_password": "Senhal actual", "current_password": "Senhal actual",
"current_profile_banner": "Bandièra actuala del perfil", "current_profile_banner": "Bandièra actuala del perfil",
"data_import_export_tab": "Importar / Exportar las donadas",
"default_vis": "Nivèl de visibilitat per defaut",
"delete_account": "Suprimir lo compte", "delete_account": "Suprimir lo compte",
"delete_account_description": "Suprimir vòstre compte e los messatges per sempre.", "delete_account_description": "Suprimir vòstre compte e los messatges per sempre.",
"delete_account_error": "Una error ses producha en suprimir lo compte. Saquò ten darribar mercés de contactar vòstre administrador dinstància.", "delete_account_error": "Una error ses producha en suprimir lo compte. Saquò ten darribar mercés de contactar vòstre administrador dinstància.",
"delete_account_instructions": "Picatz vòstre senhal dins lo camp tèxte çai-jos per confirmar la supression del compte.", "delete_account_instructions": "Picatz vòstre senhal dins lo camp tèxte çai-jos per confirmar la supression del compte.",
"filtering": "Filtre", "avatar_size_instruction": "La talha minimum recomandada pels imatges davatar es 150x150 pixèls.",
"export_theme": "Enregistrar la preconfiguracion",
"filtering": "Filtratge",
"filtering_explanation": "Totes los estatuts amb aqueles mots seràn en silenci, un mot per linha", "filtering_explanation": "Totes los estatuts amb aqueles mots seràn en silenci, un mot per linha",
"follow_export": "Exportar los abonaments", "follow_export": "Exportar los abonaments",
"follow_export_button": "Exportar vòstres abonaments dins un fichièr csv", "follow_export_button": "Exportar vòstres abonaments dins un fichièr csv",
@@ -95,62 +147,91 @@
"follow_import_error": "Error en important los seguidors", "follow_import_error": "Error en important los seguidors",
"follows_imported": "Seguidors importats. Lo tractament pòt trigar una estona.", "follows_imported": "Seguidors importats. Lo tractament pòt trigar una estona.",
"foreground": "Endavant", "foreground": "Endavant",
"general": "General",
"hide_attachments_in_convo": "Rescondre las pèças juntas dins las conversacions", "hide_attachments_in_convo": "Rescondre las pèças juntas dins las conversacions",
"hide_attachments_in_tl": "Rescondre las pèças juntas", "hide_attachments_in_tl": "Rescondre las pèças juntas",
"import_followers_from_a_csv_file": "Importar los seguidors dun fichièr csv", "max_thumbnails": "Nombre maximum de vinhetas per publicacion",
"inputRadius": "Camps tèxte", "hide_isp": "Amagar lo panèl especial instància",
"links": "Ligams", "preload_images": "Precargar los imatges",
"name": "Nom", "use_one_click_nsfw": "Dobrir las pèças juntas NSFW amb un clic",
"name_bio": "Nom & Bio",
"new_password": "Nòu senhal",
"nsfw_clickthrough": "Activar lo clic per mostrar los imatges marcats coma pels adults o sensibles",
"panelRadius": "Panèls",
"presets": "Pre-enregistrats",
"profile_background": "Imatge de fons",
"profile_banner": "Bandièra del perfil",
"radii_help": "Configurar los caires arredondits de linterfàcia (en pixèls)",
"reply_link_preview": "Activar lapercebut en passar la mirga",
"set_new_avatar": "Cambiar lavatar",
"set_new_profile_background": "Cambiar limatge de fons",
"set_new_profile_banner": "Cambiar de bandièra",
"settings": "Paramètres",
"stop_gifs": "Lançar los GIFs al subrevòl",
"streaming": "Activar lo cargament automatic dels novèls estatus en anar amont",
"text": "Tèxte",
"theme": "Tèma",
"theme_help": "Emplegatz los còdis de color hex (#rrggbb) per personalizar vòstre tèma de color.",
"tooltipRadius": "Astúcias/Alèrta",
"user_settings": "Paramètres utilizaire",
"collapse_subject": "Replegar las publicacions amb de subjèctes",
"data_import_export_tab": "Importar / Exportar las donadas",
"default_vis": "Nivèl de visibilitat per defaut",
"export_theme": "Enregistrar la preconfiguracion",
"general": "General",
"hide_post_stats": "Amagar los estatistics de publicacion (ex. lo ombre de favorits)", "hide_post_stats": "Amagar los estatistics de publicacion (ex. lo ombre de favorits)",
"hide_user_stats": "Amagar las estatisticas de lutilizaire (ex. lo nombre de seguidors)", "hide_user_stats": "Amagar las estatisticas de lutilizaire (ex. lo nombre de seguidors)",
"hide_filtered_statuses": "Amagar los estatuts filtrats",
"import_followers_from_a_csv_file": "Importar los seguidors dun fichièr csv",
"import_theme": "Cargar un tèma", "import_theme": "Cargar un tèma",
"instance_default": "(defaut : {value})", "inputRadius": "Camps tèxte",
"checkboxRadius": "Casas de marcar",
"instance_default": "(defaut : {value})",
"instance_default_simple": "(defaut)",
"interface": "Interfàcia",
"interfaceLanguage": "Lenga de linterfàcia", "interfaceLanguage": "Lenga de linterfàcia",
"invalid_theme_imported": "Lo fichièr seleccionat es pas un tèma Pleroma valid. Cap de cambiament es estat fach a vòstre tèma.", "invalid_theme_imported": "Lo fichièr seleccionat es pas un tèma Pleroma valid. Cap de cambiament es estat fach a vòstre tèma.",
"limited_availability": "Pas disponible per vòstre navigador", "limited_availability": "Pas disponible per vòstre navigador",
"links": "Ligams",
"lock_account_description": "Limitar vòstre compte als seguidors acceptats solament", "lock_account_description": "Limitar vòstre compte als seguidors acceptats solament",
"loop_video": "Bocla vidèo", "loop_video": "Bocla vidèo",
"loop_video_silent_only": "Legir en bocla solament las vidèos sens son (coma los « Gifs » de Mastodon)", "loop_video_silent_only": "Legir en bocla solament las vidèos sens son (coma los « Gifs » de Mastodon)",
"notification_visibility": "Tipes de notificacion de mostrar", "mutes_tab": "Agamats",
"play_videos_in_modal": "Legir las vidèoas dirèctament dins la visualizaira mèdia",
"use_contain_fit": "Talhar pas las pèças juntas per las vinhetas",
"name": "Nom",
"name_bio": "Nom & Bio",
"new_password": "Nòu senhal",
"notification_visibility_follows": "Abonaments", "notification_visibility_follows": "Abonaments",
"notification_visibility_likes": "Aiman", "notification_visibility_likes": "Aimar",
"notification_visibility_mentions": "Mencions", "notification_visibility_mentions": "Mencions",
"notification_visibility_repeats": "Repeticions", "notification_visibility_repeats": "Repeticions",
"notification_visibility": "Tipes de notificacion de mostrar",
"no_rich_text_description": "Netejar lo format tèxte de totas las publicacions", "no_rich_text_description": "Netejar lo format tèxte de totas las publicacions",
"no_blocks": "Cap de blocatge",
"no_mutes": "Cap damagat",
"hide_follows_description": "Mostrar pas qual seguissi",
"hide_followers_description": "Mostrar pas qual me seguisson",
"show_admin_badge": "Mostrar lo badge Admin badge al perfil meu",
"show_moderator_badge": "Mostrar lo badge Moderator al perfil meu",
"nsfw_clickthrough": "Activar lo clic per mostrar los imatges marcats coma pels adults o sensibles",
"oauth_tokens": "Listats OAuth",
"token": "Geton",
"refresh_token": "Actualizar lo geton",
"valid_until": "Valid fins a",
"revoke_token": "Revocar",
"panelRadius": "Panèls",
"pause_on_unfocused": "Pausar la difusion quand longlet es pas seleccionat", "pause_on_unfocused": "Pausar la difusion quand longlet es pas seleccionat",
"presets": "Pre-enregistrats",
"profile_background": "Imatge de fons",
"profile_banner": "Bandièra del perfil",
"profile_tab": "Perfil", "profile_tab": "Perfil",
"radii_help": "Configurar los caires arredondits de linterfàcia (en pixèls)",
"replies_in_timeline": "Responsas del flux", "replies_in_timeline": "Responsas del flux",
"reply_link_preview": "Activar lapercebut en passar la mirga",
"reply_visibility_all": "Mostrar totas las responsas", "reply_visibility_all": "Mostrar totas las responsas",
"reply_visibility_following": "Mostrar pas que las responsas que me son destinada a ieu o un utilizaire que seguissi", "reply_visibility_following": "Mostrar pas que las responsas que me son destinada a ieu o un utilizaire que seguissi",
"reply_visibility_self": "Mostrar pas que las responsas que me son destinadas", "reply_visibility_self": "Mostrar pas que las responsas que me son destinadas",
"saving_err": "Error en enregistrant los paramètres", "saving_err": "Error en enregistrant los paramètres",
"saving_ok": "Paramètres enregistrats", "saving_ok": "Paramètres enregistrats",
"scope_copy": "Copiar lo nivèl de confidencialitat per las responsas (Totjorn aissí pels Messatges Dirèctes)",
"security_tab": "Seguretat", "security_tab": "Seguretat",
"set_new_avatar": "Definir un nòu avatar",
"set_new_profile_background": "Definir un nòu fons de perfil",
"set_new_profile_banner": "Definir una nòva bandièra de perfil",
"settings": "Paramètres",
"subject_input_always_show": "Totjorn mostrar lo camp de subjècte",
"subject_line_behavior": "Copiar lo subjècte per las responsas",
"subject_line_email": "Coma los corrièls: \"re: subjècte\"",
"subject_line_mastodon": "Coma mastodon: copiar tal coma es",
"subject_line_noop": "Copiar pas",
"post_status_content_type": "Publicar lo tipe de contengut dels estatuts",
"status_content_type_plain": "Tèxte brut",
"stop_gifs": "Lançar los GIFs al subrevòl",
"streaming": "Activar lo cargament automatic dels novèls estatus en anar amont",
"text": "Tèxt",
"theme": "Tèma",
"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.",
"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.",
"theme_help": "Emplegatz los còdis de color hex (#rrggbb) per personalizar vòstre tèma de color.",
"tooltipRadius": "Astúcias/alèrtas",
"upload_a_photo": "Enviar una fotografia",
"user_settings": "Paramètres utilizaire",
"values": { "values": {
"false": "non", "false": "non",
"true": "òc" "true": "òc"
@@ -166,36 +247,67 @@
"up_to_date": "A jorn", "up_to_date": "A jorn",
"no_retweet_hint": "La publicacion marcada coma pels seguidors solament o dirècte pòt pas èsser repetida" "no_retweet_hint": "La publicacion marcada coma pels seguidors solament o dirècte pòt pas èsser repetida"
}, },
"status": {
"reply_to": "Respondre à",
"replies_list": "Responsas:"
},
"user_card": { "user_card": {
"approve": "Validar",
"block": "Blocar", "block": "Blocar",
"blocked": "Blocat !", "blocked": "Blocat !",
"deny": "Refusar",
"favorites": "Favorits",
"follow": "Seguir", "follow": "Seguir",
"follow_sent": "Demanda enviada!",
"follow_progress": "Demanda…",
"follow_again": "Tornar enviar la demanda?",
"follow_unfollow": "Quitar de seguir",
"followees": "Abonaments", "followees": "Abonaments",
"followers": "Seguidors", "followers": "Seguidors",
"following": "Seguit !", "following": "Seguit !",
"follows_you": "Vos sèc !", "follows_you": "Vos sèc !",
"its_you": "Sètz vos!",
"media": "Mèdia",
"mute": "Amagar", "mute": "Amagar",
"muted": "Amagat", "muted": "Amagat",
"per_day": "per jorn", "per_day": "per jorn",
"remote_follow": "Seguir a distància", "remote_follow": "Seguir a distància",
"statuses": "Estatuts", "statuses": "Estatuts",
"approve": "Validar", "unblock": "Desblocar",
"deny": "Refusar" "unblock_progress": "Desblocatge...",
"block_progress": "Blocatge...",
"unmute": "Tornar mostrar",
"unmute_progress": "Afichatge...",
"mute_progress": "A amagar..."
}, },
"user_profile": { "user_profile": {
"timeline_title": "Flux utilizaire" "timeline_title": "Flux utilizaire",
}, "profile_does_not_exist": "Aqueste perfil existís pas.",
"features_panel": { "profile_loading_error": "Una error ses producha en cargant aqueste perfil."
"chat": "Discutida",
"gopher": "Gopher",
"media_proxy": "Servidor mandatari dels mèdias",
"scope_options": "Opcions d'encastres",
"text_limit": "Limit de tèxte",
"title": "Foncionalitats",
"who_to_follow": "Qui seguir"
}, },
"who_to_follow": { "who_to_follow": {
"more": "Mai", "more": "Mai",
"who_to_follow": "Qui seguir" "who_to_follow": "Qual seguir"
},
"tool_tip": {
"media_upload": "Enviar un mèdia",
"repeat": "Repetir",
"reply": "Respondre",
"favorite": "aimar",
"user_settings": "Paramètres utilizaire"
},
"upload":{
"error": {
"base": "Mandadís fracassat.",
"file_too_big": "Fichièr tròp grand [{filesize}{filesizeunit} / {allowedsize}{allowedsizeunit}]",
"default": "Tornatz ensajar mai tard"
},
"file_size_units": {
"B": "o",
"KiB": "Kio",
"MiB": "Mio",
"GiB": "Gio",
"TiB": "Tio"
}
} }
} }
+5
View File
@@ -86,6 +86,11 @@
"name_bio": "Imię i bio", "name_bio": "Imię i bio",
"new_password": "Nowe hasło", "new_password": "Nowe hasło",
"nsfw_clickthrough": "Włącz domyślne ukrywanie załączników o treści nieprzyzwoitej (NSFW)", "nsfw_clickthrough": "Włącz domyślne ukrywanie załączników o treści nieprzyzwoitej (NSFW)",
"oauth_tokens": "Tokeny OAuth",
"token": "Token",
"refresh_token": "Odśwież token",
"valid_until": "Ważne do",
"revoke_token": "Odwołać",
"panelRadius": "Panele", "panelRadius": "Panele",
"presets": "Gotowe motywy", "presets": "Gotowe motywy",
"profile_background": "Tło profilu", "profile_background": "Tło profilu",
+326 -17
View File
@@ -2,116 +2,425 @@
"chat": { "chat": {
"title": "Chat" "title": "Chat"
}, },
"features_panel": {
"chat": "Chat",
"gopher": "Gopher",
"media_proxy": "Proxy de mídia",
"scope_options": "Opções de privacidade",
"text_limit": "Limite de caracteres",
"title": "Funções",
"who_to_follow": "Quem seguir"
},
"finder": { "finder": {
"error_fetching_user": "Erro procurando usuário", "error_fetching_user": "Erro ao procurar usuário",
"find_user": "Buscar usuário" "find_user": "Buscar usuário"
}, },
"general": { "general": {
"apply": "Aplicar", "apply": "Aplicar",
"submit": "Enviar" "submit": "Enviar",
"more": "Mais",
"generic_error": "Houve um erro",
"optional": "opcional"
},
"image_cropper": {
"crop_picture": "Cortar imagem",
"save": "Salvar",
"cancel": "Cancelar"
}, },
"login": { "login": {
"login": "Entrar", "login": "Entrar",
"description": "Entrar com OAuth",
"logout": "Sair", "logout": "Sair",
"password": "Senha", "password": "Senha",
"placeholder": "p.e. lain", "placeholder": "p.e. lain",
"register": "Registrar", "register": "Registrar",
"username": "Usuário" "username": "Usuário",
"hint": "Entre para participar da discussão"
},
"media_modal": {
"previous": "Anterior",
"next": "Próximo"
}, },
"nav": { "nav": {
"about": "Sobre",
"back": "Voltar",
"chat": "Chat local", "chat": "Chat local",
"friend_requests": "Solicitações de seguidores",
"mentions": "Menções", "mentions": "Menções",
"dms": "Mensagens diretas",
"public_tl": "Linha do tempo pública", "public_tl": "Linha do tempo pública",
"timeline": "Linha do tempo", "timeline": "Linha do tempo",
"twkn": "Toda a rede conhecida" "twkn": "Toda a rede conhecida",
"user_search": "Busca de usuário",
"who_to_follow": "Quem seguir",
"preferences": "Preferências"
}, },
"notifications": { "notifications": {
"broken_favorite": "Status desconhecido, buscando...",
"favorited_you": "favoritou sua postagem", "favorited_you": "favoritou sua postagem",
"followed_you": "seguiu você", "followed_you": "seguiu você",
"load_older": "Carregar notificações antigas",
"notifications": "Notificações", "notifications": "Notificações",
"read": "Lido!", "read": "Lido!",
"repeated_you": "repetiu sua postagem" "repeated_you": "repetiu sua postagem",
"no_more_notifications": "Mais nenhuma notificação"
}, },
"post_status": { "post_status": {
"new_status": "Postar novo status",
"account_not_locked_warning": "Sua conta não está {0}. Qualquer pessoa pode te seguir para ver seus posts restritos.",
"account_not_locked_warning_link": "fechada",
"attachments_sensitive": "Marcar anexos como sensíveis",
"content_type": {
"plain_text": "Texto puro"
},
"content_warning": "Assunto (opcional)",
"default": "Acabei de chegar no Rio!", "default": "Acabei de chegar no Rio!",
"posting": "Publicando" "direct_warning": "Este post será visível apenas para os usuários mencionados.",
"posting": "Publicando",
"scope": {
"direct": "Direto - Enviar somente aos usuários mencionados",
"private": "Apenas para seguidores - Enviar apenas para seguidores",
"public": "Público - Enviar a linhas do tempo públicas",
"unlisted": "Não listado - Não enviar a linhas do tempo públicas"
}
}, },
"registration": { "registration": {
"bio": "Biografia", "bio": "Biografia",
"email": "Correio eletrônico", "email": "Correio eletrônico",
"fullname": "Nome para exibição", "fullname": "Nome para exibição",
"password_confirm": "Confirmação de senha", "password_confirm": "Confirmação de senha",
"registration": "Registro" "registration": "Registro",
"token": "Código do convite",
"captcha": "CAPTCHA",
"new_captcha": "Clique na imagem para carregar um novo captcha",
"username_placeholder": "p. ex. lain",
"fullname_placeholder": "p. ex. Lain Iwakura",
"bio_placeholder": "e.g.\nOi, sou Lain\nSou uma garota que vive no subúrbio do Japão. Você deve me conhecer da Rede.",
"validations": {
"username_required": "não pode ser deixado em branco",
"fullname_required": "não pode ser deixado em branco",
"email_required": "não pode ser deixado em branco",
"password_required": "não pode ser deixado em branco",
"password_confirmation_required": "não pode ser deixado em branco",
"password_confirmation_match": "deve ser idêntica à senha"
}
}, },
"settings": { "settings": {
"app_name": "Nome do aplicativo",
"attachmentRadius": "Anexos", "attachmentRadius": "Anexos",
"attachments": "Anexos", "attachments": "Anexos",
"autoload": "Habilitar carregamento automático quando a rolagem chegar ao fim.", "autoload": "Habilitar carregamento automático quando a rolagem chegar ao fim.",
"avatar": "Avatar", "avatar": "Avatar",
"avatarAltRadius": "Avatares (Notificações)", "avatarAltRadius": "Avatares (Notificações)",
"avatarRadius": "Avatares", "avatarRadius": "Avatares",
"background": "Plano de Fundo", "background": "Pano de Fundo",
"bio": "Biografia", "bio": "Biografia",
"blocks_tab": "Blocos",
"btnRadius": "Botões", "btnRadius": "Botões",
"cBlue": "Azul (Responder, seguir)", "cBlue": "Azul (Responder, seguir)",
"cGreen": "Verde (Repetir)", "cGreen": "Verde (Repetir)",
"cOrange": "Laranja (Favoritar)", "cOrange": "Laranja (Favoritar)",
"cRed": "Vermelho (Cancelar)", "cRed": "Vermelho (Cancelar)",
"change_password": "Mudar senha",
"change_password_error": "Houve um erro ao modificar sua senha.",
"changed_password": "Senha modificada com sucesso!",
"collapse_subject": "Esconder posts com assunto",
"composing": "Escrevendo",
"confirm_new_password": "Confirmar nova senha",
"current_avatar": "Seu avatar atual", "current_avatar": "Seu avatar atual",
"current_password": "Sua senha atual",
"current_profile_banner": "Sua capa de perfil atual", "current_profile_banner": "Sua capa de perfil atual",
"data_import_export_tab": "Importação/exportação de dados",
"default_vis": "Opção de privacidade padrão",
"delete_account": "Deletar conta",
"delete_account_description": "Deletar sua conta e mensagens permanentemente.",
"delete_account_error": "Houve um problema ao deletar sua conta. Se ele persistir, por favor entre em contato com o/a administrador/a da instância.",
"delete_account_instructions": "Digite sua senha no campo abaixo para confirmar a exclusão da conta.",
"avatar_size_instruction": "O tamanho mínimo recomendado para imagens de avatar é 150x150 pixels.",
"export_theme": "Salvar predefinições",
"filtering": "Filtragem", "filtering": "Filtragem",
"filtering_explanation": "Todas as postagens contendo estas palavras serão silenciadas, uma por linha.", "filtering_explanation": "Todas as postagens contendo estas palavras serão silenciadas, uma por linha.",
"follow_import": "Importar seguidas", "follow_export": "Exportar quem você segue",
"follow_export_button": "Exportar quem você segue para um arquivo CSV",
"follow_export_processing": "Processando. Em breve você receberá a solicitação de download do arquivo",
"follow_import": "Importar quem você segue",
"follow_import_error": "Erro ao importar seguidores", "follow_import_error": "Erro ao importar seguidores",
"follows_imported": "Seguidores importados! O processamento pode demorar um pouco.", "follows_imported": "Seguidores importados! O processamento pode demorar um pouco.",
"foreground": "Primeiro Plano", "foreground": "Primeiro Plano",
"general": "Geral",
"hide_attachments_in_convo": "Ocultar anexos em conversas", "hide_attachments_in_convo": "Ocultar anexos em conversas",
"hide_attachments_in_tl": "Ocultar anexos na linha do tempo.", "hide_attachments_in_tl": "Ocultar anexos na linha do tempo.",
"max_thumbnails": "Número máximo de miniaturas por post",
"hide_isp": "Esconder painel específico da instância",
"preload_images": "Pré-carregar imagens",
"use_one_click_nsfw": "Abrir anexos sensíveis com um clique",
"hide_post_stats": "Esconder estatísticas de posts (p. ex. número de favoritos)",
"hide_user_stats": "Esconder estatísticas do usuário (p. ex. número de seguidores)",
"hide_filtered_statuses": "Esconder posts filtrados",
"import_followers_from_a_csv_file": "Importe seguidores a partir de um arquivo CSV", "import_followers_from_a_csv_file": "Importe seguidores a partir de um arquivo CSV",
"import_theme": "Carregar pré-definição",
"inputRadius": "Campos de entrada",
"checkboxRadius": "Checkboxes",
"instance_default": "(padrão: {value})",
"instance_default_simple": "(padrão)",
"interface": "Interface",
"interfaceLanguage": "Idioma da interface",
"invalid_theme_imported": "O arquivo selecionado não é um tema compatível com o Pleroma. Nenhuma mudança no tema foi feita.",
"limited_availability": "Indisponível para seu navegador",
"links": "Links", "links": "Links",
"lock_account_description": "Restringir sua conta a seguidores aprovados",
"loop_video": "Repetir vídeos",
"loop_video_silent_only": "Repetir apenas vídeos sem som (como os \"gifs\" do Mastodon)",
"mutes_tab": "Silenciados",
"play_videos_in_modal": "Tocar vídeos diretamente no visualizador de mídia",
"use_contain_fit": "Não cortar o anexo na miniatura",
"name": "Nome", "name": "Nome",
"name_bio": "Nome & Biografia", "name_bio": "Nome & Biografia",
"nsfw_clickthrough": "Habilitar clique para ocultar anexos NSFW", "new_password": "Nova senha",
"notification_visibility": "Tipos de notificação para mostrar",
"notification_visibility_follows": "Seguidos",
"notification_visibility_likes": "Favoritos",
"notification_visibility_mentions": "Menções",
"notification_visibility_repeats": "Repetições",
"no_rich_text_description": "Remover formatação de todos os posts",
"no_blocks": "Sem bloqueios",
"no_mutes": "Sem silenciados",
"hide_follows_description": "Não mostrar quem estou seguindo",
"hide_followers_description": "Não mostrar quem me segue",
"show_admin_badge": "Mostrar distintivo de Administrador em meu perfil",
"show_moderator_badge": "Mostrar título de Moderador em meu perfil",
"nsfw_clickthrough": "Habilitar clique para ocultar anexos sensíveis",
"oauth_tokens": "Token OAuth",
"token": "Token",
"refresh_token": "Atualizar Token",
"valid_until": "Válido até",
"revoke_token": "Revogar",
"panelRadius": "Paineis", "panelRadius": "Paineis",
"pause_on_unfocused": "Parar transmissão quando a aba não estiver em primeiro plano",
"presets": "Predefinições", "presets": "Predefinições",
"profile_background": "Plano de fundo de perfil", "profile_background": "Pano de fundo de perfil",
"profile_banner": "Capa de perfil", "profile_banner": "Capa de perfil",
"profile_tab": "Perfil",
"radii_help": "Arredondar arestas da interface (em píxeis)", "radii_help": "Arredondar arestas da interface (em píxeis)",
"replies_in_timeline": "Respostas na linha do tempo",
"reply_link_preview": "Habilitar a pré-visualização de link de respostas ao passar o mouse.", "reply_link_preview": "Habilitar a pré-visualização de link de respostas ao passar o mouse.",
"reply_visibility_all": "Mostrar todas as respostas",
"reply_visibility_following": "Só mostrar respostas direcionadas a mim ou a usuários que sigo",
"reply_visibility_self": "Só mostrar respostas direcionadas a mim",
"saving_err": "Erro ao salvar configurações",
"saving_ok": "Configurações salvas",
"security_tab": "Segurança",
"scope_copy": "Copiar opções de privacidade ao responder (Mensagens diretas sempre copiam)",
"set_new_avatar": "Alterar avatar", "set_new_avatar": "Alterar avatar",
"set_new_profile_background": "Alterar o plano de fundo de perfil", "set_new_profile_background": "Alterar o plano de fundo de perfil",
"set_new_profile_banner": "Alterar capa de perfil", "set_new_profile_banner": "Alterar capa de perfil",
"settings": "Configurações", "settings": "Configurações",
"subject_input_always_show": "Sempre mostrar campo de assunto",
"subject_line_behavior": "Copiar assunto ao responder",
"subject_line_email": "Como em email: \"re: assunto\"",
"subject_line_mastodon": "Como o Mastodon: copiar como está",
"subject_line_noop": "Não copiar",
"post_status_content_type": "Postar tipo de conteúdo do status",
"status_content_type_plain": "Texto puro",
"stop_gifs": "Reproduzir GIFs ao passar o cursor em cima", "stop_gifs": "Reproduzir GIFs ao passar o cursor em cima",
"streaming": "Habilitar o fluxo automático de postagens quando ao topo da página", "streaming": "Habilitar o fluxo automático de postagens quando ao topo da página",
"text": "Texto", "text": "Texto",
"theme": "Tema", "theme": "Tema",
"theme_help": "Use cores em código hexadecimal (#rrggbb) para personalizar seu esquema de cores.", "theme_help": "Use cores em código hexadecimal (#rrggbb) para personalizar seu esquema de cores.",
"tooltipRadius": "Dicass/alertas", "theme_help_v2_1": "Você também pode sobrescrever as cores e opacidade de alguns componentes ao modificar o checkbox, use \"Limpar todos\" para limpar todas as modificações.",
"user_settings": "Configurações de Usuário" "theme_help_v2_2": "Alguns ícones sob registros são indicadores de fundo/contraste de textos, passe por cima para informações detalhadas. Tenha ciência de que os indicadores de contraste não funcionam muito bem com transparência.",
"tooltipRadius": "Dicas/alertas",
"upload_a_photo": "Enviar uma foto",
"user_settings": "Configurações de Usuário",
"values": {
"false": "não",
"true": "sim"
},
"notifications": "Notifications",
"enable_web_push_notifications": "Habilitar notificações web push",
"style": {
"switcher": {
"keep_color": "Manter cores",
"keep_shadows": "Manter sombras",
"keep_opacity": "Manter opacidade",
"keep_roundness": "Manter arredondado",
"keep_fonts": "Manter fontes",
"save_load_hint": "Manter as opções preserva as opções atuais ao selecionar ou carregar temas; também salva as opções ao exportar um tempo. Quanto todos os campos estiverem desmarcados, tudo será salvo ao exportar o tema.",
"reset": "Voltar ao padrão",
"clear_all": "Limpar tudo",
"clear_opacity": "Limpar opacidade"
},
"common": {
"color": "Cor",
"opacity": "Opacidade",
"contrast": {
"hint": "A taxa de contraste é {ratio}, {level} {context}",
"level": {
"aa": "padrão Nível AA (mínimo)",
"aaa": "padrão Nível AAA (recomendado)",
"bad": "nenhum padrão de acessibilidade"
},
"context": {
"18pt": "para textos longos (18pt+)",
"text": "para texto"
}
}
},
"common_colors": {
"_tab_label": "Comum",
"main": "Cores Comuns",
"foreground_hint": "Configurações mais detalhadas na aba\"Avançado\"",
"rgbo": "Ícones, acentuação, distintivos"
},
"advanced_colors": {
"_tab_label": "Avançado",
"alert": "Fundo de alerta",
"alert_error": "Erro",
"badge": "Fundo do distintivo",
"badge_notification": "Notificação",
"panel_header": "Topo do painel",
"top_bar": "Barra do topo",
"borders": "Bordas",
"buttons": "Botões",
"inputs": "Caixas de entrada",
"faint_text": "Texto esmaecido"
},
"radii": {
"_tab_label": "Arredondado"
},
"shadows": {
"_tab_label": "Luz e sombra",
"component": "Componente",
"override": "Sobrescrever",
"shadow_id": "Sombra #{value}",
"blur": "Borrado",
"spread": "Difusão",
"inset": "Inserção",
"hint": "Para as sombras você também pode usar --variável como valor de cor para utilizar variáveis do CSS3. Tenha em mente que configurar a opacidade não será possível neste caso.",
"filter_hint": {
"always_drop_shadow": "Atenção, esta sombra sempre utiliza {0} quando compatível com o navegador.",
"drop_shadow_syntax": "{0} não é compatível com o parâmetro {1} e a palavra-chave {2}.",
"avatar_inset": "Tenha em mente que combinar as sombras de inserção e a não-inserção em avatares pode causar resultados inesperados em avatares transparentes.",
"spread_zero": "Sombras com uma difusão > 0 aparecerão como se fossem definidas como 0.",
"inset_classic": "Sombras de inserção utilizarão {0}"
},
"components": {
"panel": "Painel",
"panelHeader": "Topo do painel",
"topBar": "Barra do topo",
"avatar": "Avatar do usuário (na visualização do perfil)",
"avatarStatus": "Avatar do usuário (na exibição de posts)",
"popup": "Dicas e notificações",
"button": "Botão",
"buttonHover": "Botão (em cima)",
"buttonPressed": "Botão (pressionado)",
"buttonPressedHover": "Botão (pressionado+em cima)",
"input": "Campo de entrada"
}
},
"fonts": {
"_tab_label": "Fontes",
"help": "Selecionar fonte dos elementos da interface. Para fonte \"personalizada\" você deve entrar exatamente o nome da fonte no sistema.",
"components": {
"interface": "Interface",
"input": "Campo de entrada",
"post": "Postar texto",
"postCode": "Texto monoespaçado em post (formatação rica)"
},
"family": "Nome da fonte",
"size": "Tamanho (em px)",
"weight": "Peso",
"custom": "Personalizada"
},
"preview": {
"header": "Pré-visualizar",
"content": "Conteúdo",
"error": "Erro de exemplo",
"button": "Botão",
"text": "Vários {0} e {1}",
"mono": "conteúdo",
"input": "Acabei de chegar no Rio!",
"faint_link": "manual útil",
"fine_print": "Leia nosso {0} para não aprender nada!",
"header_faint": "Está ok!",
"checkbox": "Li os termos e condições",
"link": "um belo link"
}
}
}, },
"timeline": { "timeline": {
"collapse": "Esconder",
"conversation": "Conversa", "conversation": "Conversa",
"error_fetching": "Erro buscando atualizações", "error_fetching": "Erro ao buscar atualizações",
"load_older": "Carregar postagens antigas", "load_older": "Carregar postagens antigas",
"no_retweet_hint": "Posts apenas para seguidores ou diretos não podem ser repetidos",
"repeated": "Repetido",
"show_new": "Mostrar novas", "show_new": "Mostrar novas",
"up_to_date": "Atualizado" "up_to_date": "Atualizado",
"no_more_statuses": "Sem mais posts",
"no_statuses": "Sem posts"
},
"status": {
"reply_to": "Responder a",
"replies_list": "Respostas:"
}, },
"user_card": { "user_card": {
"approve": "Aprovar",
"block": "Bloquear", "block": "Bloquear",
"blocked": "Bloqueado!", "blocked": "Bloqueado!",
"deny": "Negar",
"favorites": "Favoritos",
"follow": "Seguir", "follow": "Seguir",
"follow_sent": "Pedido enviado!",
"follow_progress": "Enviando…",
"follow_again": "Enviar solicitação novamente?",
"follow_unfollow": "Deixar de seguir",
"followees": "Seguindo", "followees": "Seguindo",
"followers": "Seguidores", "followers": "Seguidores",
"following": "Seguindo!", "following": "Seguindo!",
"follows_you": "Segue você!", "follows_you": "Segue você!",
"its_you": "É você!",
"media": "Mídia",
"mute": "Silenciar", "mute": "Silenciar",
"muted": "Silenciado", "muted": "Silenciado",
"per_day": "por dia", "per_day": "por dia",
"remote_follow": "Seguidor Remoto", "remote_follow": "Seguidor Remoto",
"statuses": "Postagens" "statuses": "Postagens",
"unblock": "Desbloquear",
"unblock_progress": "Desbloqueando...",
"block_progress": "Bloqueando...",
"unmute": "Retirar silêncio",
"unmute_progress": "Retirando silêncio...",
"mute_progress": "Silenciando..."
}, },
"user_profile": { "user_profile": {
"timeline_title": "Linha do tempo do usuário" "timeline_title": "Linha do tempo do usuário",
"profile_does_not_exist": "Desculpe, este perfil não existe.",
"profile_loading_error": "Desculpe, houve um erro ao carregar este perfil."
},
"who_to_follow": {
"more": "Mais",
"who_to_follow": "Quem seguir"
},
"tool_tip": {
"media_upload": "Envio de mídia",
"repeat": "Repetir",
"reply": "Responder",
"favorite": "Favoritar",
"user_settings": "Configurações do usuário"
},
"upload":{
"error": {
"base": "Falha no envio.",
"file_too_big": "Arquivo grande demais [{filesize}{filesizeunit} / {allowedsize}{allowedsizeunit}]",
"default": "Tente novamente mais tarde"
},
"file_size_units": {
"B": "B",
"KiB": "KiB",
"MiB": "MiB",
"GiB": "GiB",
"TiB": "TiB"
}
} }
} }
+5
View File
@@ -132,6 +132,11 @@
"show_admin_badge": "Показывать значок администратора в моем профиле", "show_admin_badge": "Показывать значок администратора в моем профиле",
"show_moderator_badge": "Показывать значок модератора в моем профиле", "show_moderator_badge": "Показывать значок модератора в моем профиле",
"nsfw_clickthrough": "Включить скрытие NSFW вложений", "nsfw_clickthrough": "Включить скрытие NSFW вложений",
"oauth_tokens": "OAuth токены",
"token": "Токен",
"refresh_token": "Рефреш токен",
"valid_until": "Годен до",
"revoke_token": "Удалить",
"panelRadius": "Панели", "panelRadius": "Панели",
"pause_on_unfocused": "Приостановить загрузку когда вкладка не в фокусе", "pause_on_unfocused": "Приостановить загрузку когда вкладка не в фокусе",
"presets": "Пресеты", "presets": "Пресеты",
+5
View File
@@ -134,6 +134,11 @@
"notification_visibility_repeats": "转发", "notification_visibility_repeats": "转发",
"no_rich_text_description": "不显示富文本格式", "no_rich_text_description": "不显示富文本格式",
"nsfw_clickthrough": "将不和谐附件隐藏,点击才能打开", "nsfw_clickthrough": "将不和谐附件隐藏,点击才能打开",
"oauth_tokens": "OAuth令牌",
"token": "代币",
"refresh_token": "刷新令牌",
"valid_until": "有效期至",
"revoke_token": "撤消",
"panelRadius": "面板", "panelRadius": "面板",
"pause_on_unfocused": "在离开页面时暂停时间线推送", "pause_on_unfocused": "在离开页面时暂停时间线推送",
"presets": "预置", "presets": "预置",
+2 -2
View File
@@ -84,12 +84,12 @@ export default function createPersistedState ({
setState(key, reducer(state, paths), storage) setState(key, reducer(state, paths), storage)
.then(success => { .then(success => {
if (typeof success !== 'undefined') { if (typeof success !== 'undefined') {
if (mutation.type === 'setOption') { if (mutation.type === 'setOption' || mutation.type === 'setCurrentUser') {
store.dispatch('settingsSaved', { success }) store.dispatch('settingsSaved', { success })
} }
} }
}, error => { }, error => {
if (mutation.type === 'setOption') { if (mutation.type === 'setOption' || mutation.type === 'setCurrentUser') {
store.dispatch('settingsSaved', { error }) store.dispatch('settingsSaved', { error })
} }
}) })
+5 -2
View File
@@ -11,6 +11,7 @@ import configModule from './modules/config.js'
import chatModule from './modules/chat.js' import chatModule from './modules/chat.js'
import oauthModule from './modules/oauth.js' import oauthModule from './modules/oauth.js'
import mediaViewerModule from './modules/media_viewer.js' import mediaViewerModule from './modules/media_viewer.js'
import oauthTokensModule from './modules/oauth_tokens.js'
import VueTimeago from 'vue-timeago' import VueTimeago from 'vue-timeago'
import VueI18n from 'vue-i18n' import VueI18n from 'vue-i18n'
@@ -29,8 +30,9 @@ const currentLocale = (window.navigator.language || 'en').split('-')[0]
Vue.use(Vuex) Vue.use(Vuex)
Vue.use(VueRouter) Vue.use(VueRouter)
Vue.use(VueTimeago, { Vue.use(VueTimeago, {
locale: currentLocale === 'ja' ? 'ja' : 'en', locale: currentLocale === 'cs' ? 'cs' : currentLocale === 'ja' ? 'ja' : 'en',
locales: { locales: {
'cs': require('../static/timeago-cs.json'),
'en': require('../static/timeago-en.json'), 'en': require('../static/timeago-en.json'),
'ja': require('../static/timeago-ja.json') 'ja': require('../static/timeago-ja.json')
} }
@@ -64,7 +66,8 @@ createPersistedState(persistedStateOptions).then((persistedState) => {
config: configModule, config: configModule,
chat: chatModule, chat: chatModule,
oauth: oauthModule, oauth: oauthModule,
mediaViewer: mediaViewerModule mediaViewer: mediaViewerModule,
oauthTokens: oauthTokensModule
}, },
plugins: [persistedState, pushNotifications], plugins: [persistedState, pushNotifications],
strict: false // Socket modifies itself, let's ignore this for now. strict: false // Socket modifies itself, let's ignore this for now.
+1
View File
@@ -8,6 +8,7 @@ const defaultState = {
collapseMessageWithSubject: undefined, // instance default collapseMessageWithSubject: undefined, // instance default
hideAttachments: false, hideAttachments: false,
hideAttachmentsInConv: false, hideAttachmentsInConv: false,
maxThumbnails: 16,
hideNsfw: true, hideNsfw: true,
preloadImage: true, preloadImage: true,
loopVideo: true, loopVideo: true,
+26
View File
@@ -0,0 +1,26 @@
const oauthTokens = {
state: {
tokens: []
},
actions: {
fetchTokens ({rootState, commit}) {
rootState.api.backendInteractor.fetchOAuthTokens().then((tokens) => {
commit('swapTokens', tokens)
})
},
revokeToken ({rootState, commit, state}, id) {
rootState.api.backendInteractor.revokeOAuthToken(id).then((response) => {
if (response.status === 201) {
commit('swapTokens', state.tokens.filter(token => token.id !== id))
}
})
}
},
mutations: {
swapTokens (state, tokens) {
state.tokens = tokens
}
}
}
export default oauthTokens
+12 -7
View File
@@ -10,6 +10,7 @@ const emptyTl = (userId = 0) => ({
visibleStatusesObject: {}, visibleStatusesObject: {},
newStatusCount: 0, newStatusCount: 0,
maxId: 0, maxId: 0,
minId: 0,
minVisibleId: 0, minVisibleId: 0,
loading: false, loading: false,
followers: [], followers: [],
@@ -117,16 +118,21 @@ const addNewStatuses = (state, { statuses, showImmediately = false, timeline, us
const timelineObject = state.timelines[timeline] const timelineObject = state.timelines[timeline]
const maxNew = statuses.length > 0 ? maxBy(statuses, 'id').id : 0 const maxNew = statuses.length > 0 ? maxBy(statuses, 'id').id : 0
const older = timeline && maxNew < timelineObject.maxId const minNew = statuses.length > 0 ? minBy(statuses, 'id').id : 0
const newer = timeline && maxNew > timelineObject.maxId && statuses.length > 0
const older = timeline && (minNew < timelineObject.minId || timelineObject.minId === 0) && statuses.length > 0
if (timeline && !noIdUpdate && statuses.length > 0 && !older) { if (!noIdUpdate && newer) {
timelineObject.maxId = maxNew timelineObject.maxId = maxNew
} }
if (!noIdUpdate && older) {
timelineObject.minId = minNew
}
// This makes sure that user timeline won't get data meant for other // This makes sure that user timeline won't get data meant for other
// user. I.e. opening different user profiles makes request which could // user. I.e. opening different user profiles makes request which could
// return data late after user already viewing different user profile // return data late after user already viewing different user profile
if (timeline === 'user' && timelineObject.userId !== userId) { if ((timeline === 'user' || timeline === 'media') && timelineObject.userId !== userId) {
return return
} }
@@ -255,12 +261,9 @@ const addNewStatuses = (state, { statuses, showImmediately = false, timeline, us
processor(status) processor(status)
}) })
// Keep the visible statuses sorted // Keep the visible statuses sorted
if (timeline) { if (timeline) {
sortTimeline(timelineObject) sortTimeline(timelineObject)
if ((older || timelineObject.minVisibleId <= 0) && statuses.length > 0) {
timelineObject.minVisibleId = minBy(statuses, 'id').id
}
} }
} }
@@ -303,6 +306,8 @@ const addNewNotifications = (state, { dispatch, notifications, older, visibleNot
setTimeout(notification.close.bind(notification), 5000) setTimeout(notification.close.bind(notification), 5000)
} }
} }
} else if (notification.seen) {
state.notifications.idStore[notification.id].seen = true
} }
}) })
} }
+72 -24
View File
@@ -72,19 +72,31 @@ export const mutations = {
}, },
// Because frontend doesn't have a reason to keep these stuff in memory // Because frontend doesn't have a reason to keep these stuff in memory
// outside of viewing someones user profile. // outside of viewing someones user profile.
clearFriendsAndFollowers (state, userKey) { clearFriends (state, userId) {
const user = state.usersObject[userKey] const user = state.usersObject[userId]
if (!user) { if (!user) {
return return
} }
user.friends = [] user.friends = []
user.followers = []
user.friendsPage = 0 user.friendsPage = 0
},
clearFollowers (state, userId) {
const user = state.usersObject[userId]
if (!user) {
return
}
user.followers = []
user.followersPage = 0 user.followersPage = 0
}, },
addNewUsers (state, users) { addNewUsers (state, users) {
each(users, (user) => mergeOrAdd(state.users, state.usersObject, user)) each(users, (user) => mergeOrAdd(state.users, state.usersObject, user))
}, },
saveBlocks (state, blockIds) {
state.currentUser.blockIds = blockIds
},
saveMutes (state, muteIds) {
state.currentUser.muteIds = muteIds
},
setUserForStatus (state, status) { setUserForStatus (state, status) {
status.user = state.usersObject[status.user.id] status.user = state.usersObject[status.user.id]
}, },
@@ -134,7 +146,39 @@ const users = {
getters, getters,
actions: { actions: {
fetchUser (store, id) { fetchUser (store, id) {
store.rootState.api.backendInteractor.fetchUser({ id }) return store.rootState.api.backendInteractor.fetchUser({ id })
.then((user) => store.commit('addNewUsers', [user]))
},
fetchBlocks (store) {
return store.rootState.api.backendInteractor.fetchBlocks()
.then((blocks) => {
store.commit('saveBlocks', map(blocks, 'id'))
store.commit('addNewUsers', blocks)
return blocks
})
},
blockUser (store, id) {
return store.rootState.api.backendInteractor.blockUser(id)
.then((user) => store.commit('addNewUsers', [user]))
},
unblockUser (store, id) {
return store.rootState.api.backendInteractor.unblockUser(id)
.then((user) => store.commit('addNewUsers', [user]))
},
fetchMutes (store) {
return store.rootState.api.backendInteractor.fetchMutes()
.then((mutedUsers) => {
each(mutedUsers, (user) => { user.muted = true })
store.commit('addNewUsers', mutedUsers)
store.commit('saveMutes', map(mutedUsers, 'id'))
})
},
muteUser (store, id) {
return store.state.api.backendInteractor.setUserMute({ id, muted: true })
.then((user) => store.commit('addNewUsers', [user]))
},
unmuteUser (store, id) {
return store.state.api.backendInteractor.setUserMute({ id, muted: false })
.then((user) => store.commit('addNewUsers', [user])) .then((user) => store.commit('addNewUsers', [user]))
}, },
addFriends ({ rootState, commit }, fetchBy) { addFriends ({ rootState, commit }, fetchBy) {
@@ -151,20 +195,19 @@ const users = {
}) })
}, },
addFollowers ({ rootState, commit }, fetchBy) { addFollowers ({ rootState, commit }, fetchBy) {
return new Promise((resolve, reject) => { const user = rootState.users.usersObject[fetchBy]
const user = rootState.users.usersObject[fetchBy] const page = user.followersPage || 1
const page = user.followersPage || 1 return rootState.api.backendInteractor.fetchFollowers({ id: user.id, page })
rootState.api.backendInteractor.fetchFollowers({ id: user.id, page }) .then((followers) => {
.then((followers) => { commit('addFollowers', { id: user.id, followers, page })
commit('addFollowers', { id: user.id, followers, page }) return followers
resolve(followers) })
}).catch(() => {
reject()
})
})
}, },
clearFriendsAndFollowers ({ commit }, userKey) { clearFriends ({ commit }, userId) {
commit('clearFriendsAndFollowers', userKey) commit('clearFriends', userId)
},
clearFollowers ({ commit }, userId) {
commit('clearFollowers', userId)
}, },
registerPushNotifications (store) { registerPushNotifications (store) {
const token = store.state.currentUser.credentials const token = store.state.currentUser.credentials
@@ -231,8 +274,14 @@ const users = {
store.commit('setToken', result.access_token) store.commit('setToken', result.access_token)
store.dispatch('loginUser', result.access_token) store.dispatch('loginUser', result.access_token)
} else { } else {
let data = await response.json() const data = await response.json()
let errors = humanizeErrors(JSON.parse(data.error)) let errors = JSON.parse(data.error)
// replace ap_id with username
if (errors.ap_id) {
errors.username = errors.ap_id
delete errors.ap_id
}
errors = humanizeErrors(errors)
store.commit('signUpFailure', errors) store.commit('signUpFailure', errors)
throw Error(errors) throw Error(errors)
} }
@@ -257,6 +306,8 @@ const users = {
const user = data const user = data
// user.credentials = userCredentials // user.credentials = userCredentials
user.credentials = accessToken user.credentials = accessToken
user.blockIds = []
user.muteIds = []
commit('setCurrentUser', user) commit('setCurrentUser', user)
commit('addNewUsers', [user]) commit('addNewUsers', [user])
@@ -273,11 +324,8 @@ const users = {
// Start getting fresh posts. // Start getting fresh posts.
store.dispatch('startFetching', { timeline: 'friends' }) store.dispatch('startFetching', { timeline: 'friends' })
// Get user mutes and follower info // Get user mutes
store.rootState.api.backendInteractor.fetchMutes().then((mutedUsers) => { store.dispatch('fetchMutes')
each(mutedUsers, (user) => { user.muted = true })
store.commit('addNewUsers', mutedUsers)
})
// Fetch our friends // Fetch our friends
store.rootState.api.backendInteractor.fetchFriends({ id: user.id }) store.rootState.api.backendInteractor.fetchFriends({ id: user.id })
+42 -1
View File
@@ -18,6 +18,7 @@ const MENTIONS_URL = '/api/statuses/mentions.json'
const DM_TIMELINE_URL = '/api/statuses/dm_timeline.json' const DM_TIMELINE_URL = '/api/statuses/dm_timeline.json'
const FOLLOWERS_URL = '/api/statuses/followers.json' const FOLLOWERS_URL = '/api/statuses/followers.json'
const FRIENDS_URL = '/api/statuses/friends.json' const FRIENDS_URL = '/api/statuses/friends.json'
const BLOCKS_URL = '/api/statuses/blocks.json'
const FOLLOWING_URL = '/api/friendships/create.json' const FOLLOWING_URL = '/api/friendships/create.json'
const UNFOLLOWING_URL = '/api/friendships/destroy.json' const UNFOLLOWING_URL = '/api/friendships/destroy.json'
const QVITTER_USER_PREF_URL = '/api/qvitter/set_profile_pref.json' const QVITTER_USER_PREF_URL = '/api/qvitter/set_profile_pref.json'
@@ -46,6 +47,7 @@ const MASTODON_USER_FAVORITES_TIMELINE_URL = '/api/v1/favourites'
import { each, map } from 'lodash' import { each, map } from 'lodash'
import { parseStatus, parseUser, parseNotification } from '../entity_normalizer/entity_normalizer.service.js' import { parseStatus, parseUser, parseNotification } from '../entity_normalizer/entity_normalizer.service.js'
import 'whatwg-fetch' import 'whatwg-fetch'
import { StatusCodeError } from '../errors/errors'
const oldfetch = window.fetch const oldfetch = window.fetch
@@ -243,7 +245,15 @@ const denyUser = ({id, credentials}) => {
const fetchUser = ({id, credentials}) => { const fetchUser = ({id, credentials}) => {
let url = `${USER_URL}?user_id=${id}` let url = `${USER_URL}?user_id=${id}`
return fetch(url, { headers: authHeaders(credentials) }) return fetch(url, { headers: authHeaders(credentials) })
.then((data) => data.json()) .then((response) => {
return new Promise((resolve, reject) => response.json()
.then((json) => {
if (!response.ok) {
return reject(new StatusCodeError(response.status, json, { url }, response))
}
return resolve(json)
}))
})
.then((data) => parseUser(data)) .then((data) => parseUser(data))
} }
@@ -519,6 +529,34 @@ const fetchMutes = ({credentials}) => {
}).then((data) => data.json()) }).then((data) => data.json())
} }
const fetchBlocks = ({page, credentials}) => {
return fetch(BLOCKS_URL, {
headers: authHeaders(credentials)
}).then((data) => {
if (data.ok) {
return data.json()
}
throw new Error('Error fetching blocks', data)
})
}
const fetchOAuthTokens = ({credentials}) => {
const url = '/api/oauth_tokens.json'
return fetch(url, {
headers: authHeaders(credentials)
}).then((data) => data.json())
}
const revokeOAuthToken = ({id, credentials}) => {
const url = `/api/oauth_tokens/${id}`
return fetch(url, {
headers: authHeaders(credentials),
method: 'DELETE'
})
}
const suggestions = ({credentials}) => { const suggestions = ({credentials}) => {
return fetch(SUGGESTIONS_URL, { return fetch(SUGGESTIONS_URL, {
headers: authHeaders(credentials) headers: authHeaders(credentials)
@@ -560,6 +598,9 @@ const apiService = {
fetchAllFollowing, fetchAllFollowing,
setUserMute, setUserMute,
fetchMutes, fetchMutes,
fetchBlocks,
fetchOAuthTokens,
revokeOAuthToken,
register, register,
getCaptcha, getCaptcha,
updateAvatar, updateAvatar,
@@ -54,8 +54,8 @@ const backendInteractorService = (credentials) => {
return apiService.denyUser({credentials, id}) return apiService.denyUser({credentials, id})
} }
const startFetching = ({timeline, store, userId = false}) => { const startFetching = ({timeline, store, userId = false, tag}) => {
return timelineFetcherService.startFetching({timeline, store, credentials, userId}) return timelineFetcherService.startFetching({timeline, store, credentials, userId, tag})
} }
const setUserMute = ({id, muted = true}) => { const setUserMute = ({id, muted = true}) => {
@@ -63,7 +63,10 @@ const backendInteractorService = (credentials) => {
} }
const fetchMutes = () => apiService.fetchMutes({credentials}) const fetchMutes = () => apiService.fetchMutes({credentials})
const fetchBlocks = (params) => apiService.fetchBlocks({credentials, ...params})
const fetchFollowRequests = () => apiService.fetchFollowRequests({credentials}) const fetchFollowRequests = () => apiService.fetchFollowRequests({credentials})
const fetchOAuthTokens = () => apiService.fetchOAuthTokens({credentials})
const revokeOAuthToken = (id) => apiService.revokeOAuthToken({id, credentials})
const getCaptcha = () => apiService.getCaptcha() const getCaptcha = () => apiService.getCaptcha()
const register = (params) => apiService.register(params) const register = (params) => apiService.register(params)
@@ -94,6 +97,9 @@ const backendInteractorService = (credentials) => {
startFetching, startFetching,
setUserMute, setUserMute,
fetchMutes, fetchMutes,
fetchBlocks,
fetchOAuthTokens,
revokeOAuthToken,
register, register,
getCaptcha, getCaptcha,
updateAvatar, updateAvatar,
@@ -0,0 +1,10 @@
import isFunction from 'lodash/isFunction'
const getComponentOptions = (Component) => (isFunction(Component)) ? Component.options : Component
const getComponentProps = (Component) => getComponentOptions(Component).props
export {
getComponentOptions,
getComponentProps
}
@@ -117,6 +117,9 @@ export const parseUser = (data) => {
output.statuses_count = data.statuses_count output.statuses_count = data.statuses_count
output.friends = [] output.friends = []
output.followers = [] output.followers = []
if (data.pleroma) {
output.follow_request_count = data.pleroma.follow_request_count
}
return output return output
} }
+14
View File
@@ -0,0 +1,14 @@
export function StatusCodeError (statusCode, body, options, response) {
this.name = 'StatusCodeError'
this.statusCode = statusCode
this.message = statusCode + ' - ' + (JSON && JSON.stringify ? JSON.stringify(body) : body)
this.error = body // legacy attribute
this.options = options
this.response = response
if (Error.captureStackTrace) { // required for non-V8 environments
Error.captureStackTrace(this)
}
}
StatusCodeError.prototype = Object.create(Error.prototype)
StatusCodeError.prototype.constructor = StatusCodeError
@@ -0,0 +1,21 @@
import apiService from '../api/api.service.js'
const fetchAndUpdate = ({ store, credentials }) => {
return apiService.fetchFollowRequests({ credentials })
.then((requests) => {
store.commit('setFollowRequests', requests)
}, () => {})
.catch(() => {})
}
const startFetching = ({credentials, store}) => {
fetchAndUpdate({ credentials, store })
const boundFetchAndUpdate = () => fetchAndUpdate({ credentials, store })
return setInterval(boundFetchAndUpdate, 10000)
}
const followRequestFetcher = {
startFetching
}
export default followRequestFetcher
@@ -16,7 +16,17 @@ const fetchAndUpdate = ({store, credentials, older = false}) => {
args['until'] = timelineData.minId args['until'] = timelineData.minId
} }
} else { } else {
args['since'] = timelineData.maxId // load unread notifications repeadedly to provide consistency between browser tabs
const notifications = timelineData.data
const unread = notifications.filter(n => !n.seen).map(n => n.id)
if (!unread.length) {
args['since'] = timelineData.maxId
} else {
args['since'] = Math.min(...unread) - 1
if (timelineData.maxId !== Math.max(...unread)) {
args['until'] = Math.max(...unread, args['since'] + 20)
}
}
} }
args['timeline'] = 'notifications' args['timeline'] = 'notifications'
@@ -21,7 +21,7 @@ const fetchAndUpdate = ({store, credentials, timeline = 'friends', older = false
const timelineData = rootState.statuses.timelines[camelCase(timeline)] const timelineData = rootState.statuses.timelines[camelCase(timeline)]
if (older) { if (older) {
args['until'] = until || timelineData.minVisibleId args['until'] = until || timelineData.minId
} else { } else {
args['since'] = timelineData.maxId args['since'] = timelineData.maxId
} }
BIN
View File
Binary file not shown.

Before

Width:  |  Height:  |  Size: 224 KiB

BIN
View File
Binary file not shown.

Before

Width:  |  Height:  |  Size: 323 KiB

+10
View File
@@ -0,0 +1,10 @@
[
"teď",
["%s s", "%s s"],
["%s min", "%s min"],
["%s h", "%s h"],
["%s d", "%s d"],
["%s týd", "%s týd"],
["%s měs", "%s měs"],
["%s r", "%s l"]
]
+30 -1139
View File
File diff suppressed because it is too large Load Diff