diff --git a/CHANGELOG.md b/CHANGELOG.md index 530daff9d..b63bda23c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,7 +2,9 @@ ## [Unreleased](https://github.com/pixelfed/pixelfed/compare/v0.10.9...dev) ### Added +- ActivityPubFetchService for signed GET requests ([8763bfc5](https://github.com/pixelfed/pixelfed/commit/8763bfc5)) - Custom content warnings for remote posts ([6afc61a4](https://github.com/pixelfed/pixelfed/commit/6afc61a4)) +- Thai translations ([https://github.com/pixelfed/pixelfed/commit/74cd536](https://github.com/pixelfed/pixelfed/commit/74cd536)) ### Updated - Updated PostComponent, fix remote urls ([42716ccc](https://github.com/pixelfed/pixelfed/commit/42716ccc)) @@ -14,6 +16,17 @@ - Updated PublicApiControllers, fix block/mutes filtering on public timeline ([08383dd4](https://github.com/pixelfed/pixelfed/commit/08383dd4)) - Updated FixUsernames command, fixes remote username search ([0f943f67](https://github.com/pixelfed/pixelfed/commit/0f943f67)) - Updated Timeline component, fix mod tools ([b1d5eb05](https://github.com/pixelfed/pixelfed/commit/b1d5eb05)) +- Updated Profile.vue component, fix pagination bug ([46767810](https://github.com/pixelfed/pixelfed/commit/46767810)) +- Updated purify config, fix microformats support ([877023fb](https://github.com/pixelfed/pixelfed/commit/877023fb)) +- Updated LikeController, fix likes_count bug ([996866cb](https://github.com/pixelfed/pixelfed/commit/996866cb)) +- Updated AccountController, added followRequestJson method ([483548e2](https://github.com/pixelfed/pixelfed/commit/483548e2)) +- Updated UserInvite model, added sender relation ([591a1929](https://github.com/pixelfed/pixelfed/commit/591a1929)) +- Updated migrations, added UIKit ([fcab5010](https://github.com/pixelfed/pixelfed/commit/fcab5010)) +- Updated AccountTransformer, added last_fetched_at attribute ([38b0233e](https://github.com/pixelfed/pixelfed/commit/38b0233e)) +- Updated StoryItemTransformer, increase story length to 5 seconds ([924e424c](https://github.com/pixelfed/pixelfed/commit/924e424c)) +- Updated StatusController, fix reblog_count bug ([1dc65e93](https://github.com/pixelfed/pixelfed/commit/1dc65e93)) +- Updated NotificationCard.vue component, add follow requests at top of card, remove card-header ([5e48ffca](https://github.com/pixelfed/pixelfed/commit/5e48ffca)) +- Updated RemoteProfile.vue component, add warning for empty profiles and last_fetched_at ([66f44a9d](https://github.com/pixelfed/pixelfed/commit/66f44a9d)) ## [v0.10.9 (2020-04-17)](https://github.com/pixelfed/pixelfed/compare/v0.10.8...v0.10.9) diff --git a/app/Http/Controllers/AccountController.php b/app/Http/Controllers/AccountController.php index 6f136c3ad..a554f17d8 100644 --- a/app/Http/Controllers/AccountController.php +++ b/app/Http/Controllers/AccountController.php @@ -327,6 +327,27 @@ class AccountController extends Controller return view('account.follow-requests', compact('followers')); } + public function followRequestsJson(Request $request) + { + $pid = Auth::user()->profile_id; + $followers = FollowRequest::whereFollowingId($pid)->orderBy('id','desc')->whereIsRejected(0)->get(); + $res = [ + 'count' => $followers->count(), + 'accounts' => $followers->take(10)->map(function($a) { + $actor = $a->actor; + return [ + 'id' => $actor->id, + 'username' => $actor->username, + 'avatar' => $actor->avatarUrl(), + 'url' => $actor->url(), + 'local' => $actor->domain == null, + 'following' => $actor->followedBy(Auth::user()->profile) + ]; + }) + ]; + return response()->json($res, 200, [], JSON_PRETTY_PRINT|JSON_UNESCAPED_SLASHES); + } + public function followRequestHandle(Request $request) { $this->validate($request, [ diff --git a/app/Http/Controllers/LikeController.php b/app/Http/Controllers/LikeController.php index fa3caaf7c..601286179 100644 --- a/app/Http/Controllers/LikeController.php +++ b/app/Http/Controllers/LikeController.php @@ -27,7 +27,7 @@ class LikeController extends Controller $profile = $user->profile; $status = Status::findOrFail($request->input('item')); - $count = $status->likes_count; + $count = $status->likes()->count(); if ($status->likes()->whereProfileId($profile->id)->count() !== 0) { $like = Like::whereProfileId($profile->id)->whereStatusId($status->id)->firstOrFail(); diff --git a/app/Http/Controllers/StatusController.php b/app/Http/Controllers/StatusController.php index ef1d59395..cf209bf15 100644 --- a/app/Http/Controllers/StatusController.php +++ b/app/Http/Controllers/StatusController.php @@ -175,7 +175,7 @@ class StatusController extends Controller ->whereIn('scope', ['public', 'unlisted']) ->findOrFail($request->input('item')); - $count = $status->shares_count; + $count = $status->shares()->count(); $exists = Status::whereProfileId(Auth::user()->profile->id) ->whereReblogOfId($status->id) diff --git a/app/Http/Controllers/UIKitController.php b/app/Http/Controllers/UIKitController.php new file mode 100644 index 000000000..bfa720dca --- /dev/null +++ b/app/Http/Controllers/UIKitController.php @@ -0,0 +1,10 @@ +belongsTo(User::class, 'user_id'); diff --git a/app/Profile.php b/app/Profile.php index e9907f710..7c8325ad6 100644 --- a/app/Profile.php +++ b/app/Profile.php @@ -18,7 +18,10 @@ class Profile extends Model */ public $incrementing = false; - protected $dates = ['deleted_at']; + protected $dates = [ + 'deleted_at', + 'last_fetched_at' + ]; protected $hidden = ['private_key']; protected $visible = ['id', 'user_id', 'username', 'name']; protected $fillable = ['user_id']; diff --git a/app/Services/ActivityPubFetchService.php b/app/Services/ActivityPubFetchService.php new file mode 100644 index 000000000..765c62765 --- /dev/null +++ b/app/Services/ActivityPubFetchService.php @@ -0,0 +1,59 @@ + 'application/activity+json, application/json', + 'User-Agent' => 'PixelfedBot - https://pixelfed.org' + ]; + + public static function queue() + { + return new self; + } + + public function signed($signed = true) + { + $this->signed = $signed; + return $this; + } + + public function actor($profile) + { + $this->actor = $profile; + return $this; + } + + public function url($url) + { + if(!Helpers::validateUrl($url)) { + throw new \Exception('Invalid URL'); + } + $this->url = $url; + return $this; + } + + public function get() + { + if($this->signed == true && $this->actor == null) { + throw new \Exception('Cannot sign request without actor'); + } + return $this->signedRequest(); + } + + protected function signedRequest() + { + $this->headers = HttpSignature::sign($this->actor, $this->url, false, $this->headers); + return Zttp::withHeaders($this->headers)->get($this->url)->body(); + } +} \ No newline at end of file diff --git a/app/Transformer/Api/AccountTransformer.php b/app/Transformer/Api/AccountTransformer.php index 0486495c8..16a45b97c 100644 --- a/app/Transformer/Api/AccountTransformer.php +++ b/app/Transformer/Api/AccountTransformer.php @@ -34,7 +34,8 @@ class AccountTransformer extends Fractal\TransformerAbstract 'local' => (bool) $local, 'is_admin' => (bool) $is_admin, 'created_at' => $profile->created_at->toJSON(), - 'header_bg' => $profile->header_bg + 'header_bg' => $profile->header_bg, + 'last_fetched_at' => optional($profile->last_fetched_at)->toJSON() ]; } diff --git a/app/Transformer/Api/StoryItemTransformer.php b/app/Transformer/Api/StoryItemTransformer.php index 1fbd9c37d..7c59ef22e 100644 --- a/app/Transformer/Api/StoryItemTransformer.php +++ b/app/Transformer/Api/StoryItemTransformer.php @@ -14,7 +14,7 @@ class StoryItemTransformer extends Fractal\TransformerAbstract return [ 'id' => (string) $item->id, 'type' => $item->type, - 'length' => $item->duration != 0 ? $item->duration : 3, + 'length' => 5, 'src' => $item->url(), 'preview' => null, 'link' => null, diff --git a/app/UIKit.php b/app/UIKit.php new file mode 100644 index 000000000..c47e2249c --- /dev/null +++ b/app/UIKit.php @@ -0,0 +1,21 @@ +where('k', $k)->first()->v; + } +} diff --git a/app/UserInvite.php b/app/UserInvite.php index c8761a775..6b9a839f9 100644 --- a/app/UserInvite.php +++ b/app/UserInvite.php @@ -6,10 +6,13 @@ use Illuminate\Database\Eloquent\Model; class UserInvite extends Model { + public function sender() + { + return $this->belongsTo(Profile::class, 'profile_id'); + } + public function url() { - $path = '/i/invite/code'; - $url = url($path, [$this->key, $this->token]); - return $url; + return url("/i/invite/code/{$this->key}/{$this->token}"); } } diff --git a/config/purify.php b/config/purify.php index da156c5f7..681f55dd6 100644 --- a/config/purify.php +++ b/config/purify.php @@ -68,8 +68,8 @@ return [ */ 'HTML.Allowed' => env('RESTRICT_HTML_TYPES', true) ? - 'a[href|title|rel],p,span,br' : - 'a[href|title|rel],p,span,strong,em,del,b,i,s,strike,h1,h2,h3,h4,h5,h6,ul,ol,li,br', + 'a[href|title|rel|class],p[class],span[class],br' : + 'a[href|title|rel|class],p[class],span[class],strong,em,del,b,i,s,strike,h1,h2,h3,h4,h5,h6,ul,ol,li,br', /* @@ -133,6 +133,27 @@ return [ 'AutoFormat.RemoveEmpty' => false, + 'Attr.AllowedClasses' => [ + 'h-feed', + 'h-entry', + 'h-cite', + 'h-card', + 'p-author', + 'p-name', + 'p-in-reply-to', + 'p-repost-of', + 'p-comment', + 'u-photo', + 'u-uid', + 'u-url', + 'dt-published', + 'e-content', + 'mention', + 'hashtag', + 'ellipsis', + 'invisible' + ], + 'Attr.AllowedRel' => [ 'noreferrer', 'noopener', diff --git a/database/migrations/2020_04_13_045435_create_uikit_table.php b/database/migrations/2020_04_13_045435_create_uikit_table.php new file mode 100644 index 000000000..3fa856ff7 --- /dev/null +++ b/database/migrations/2020_04_13_045435_create_uikit_table.php @@ -0,0 +1,39 @@ +bigIncrements('id'); + $table->string('k')->unique()->index(); + $table->text('v')->nullable(); + $table->json('meta')->nullable(); + // default value for rollbacks + $table->text('defv')->nullable(); + // delta history + $table->text('dhis')->nullable(); + $table->unsignedInteger('edit_count')->default(0)->nullable(); + $table->timestamps(); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::dropIfExists('uikit'); + } +} diff --git a/public/js/app.js b/public/js/app.js index b35bd672f..3a52f6073 100644 Binary files a/public/js/app.js and b/public/js/app.js differ diff --git a/public/js/profile.js b/public/js/profile.js index eff773157..83f4e338c 100644 Binary files a/public/js/profile.js and b/public/js/profile.js differ diff --git a/public/js/rempro.js b/public/js/rempro.js index 811a6e606..38f430809 100644 Binary files a/public/js/rempro.js and b/public/js/rempro.js differ diff --git a/public/js/status.js b/public/js/status.js index fe31e0097..6635bae54 100644 Binary files a/public/js/status.js and b/public/js/status.js differ diff --git a/public/js/timeline.js b/public/js/timeline.js index 82f897ce6..2ead8ca83 100644 Binary files a/public/js/timeline.js and b/public/js/timeline.js differ diff --git a/public/mix-manifest.json b/public/mix-manifest.json index 966872bdd..61b9e62c0 100644 Binary files a/public/mix-manifest.json and b/public/mix-manifest.json differ diff --git a/resources/assets/js/app.js b/resources/assets/js/app.js index 97b33d9bb..fcfa03e21 100644 --- a/resources/assets/js/app.js +++ b/resources/assets/js/app.js @@ -69,6 +69,31 @@ window.App.util = { return 0; } return new Intl.NumberFormat(locale, { notation: notation , compactDisplay: "short" }).format(count); + }), + timeAgo: (function(ts) { + let date = Date.parse(ts); + let seconds = Math.floor((new Date() - date) / 1000); + let interval = Math.floor(seconds / 31536000); + if (interval >= 1) { + return interval + "y"; + } + interval = Math.floor(seconds / 604800); + if (interval >= 1) { + return interval + "w"; + } + interval = Math.floor(seconds / 86400); + if (interval >= 1) { + return interval + "d"; + } + interval = Math.floor(seconds / 3600); + if (interval >= 1) { + return interval + "h"; + } + interval = Math.floor(seconds / 60); + if (interval >= 1) { + return interval + "m"; + } + return Math.floor(seconds) + "s"; }) }, filters: [ diff --git a/resources/assets/js/components/NotificationCard.vue b/resources/assets/js/components/NotificationCard.vue index b82d98770..b5a03bc97 100644 --- a/resources/assets/js/components/NotificationCard.vue +++ b/resources/assets/js/components/NotificationCard.vue @@ -2,19 +2,19 @@
-
-

- - Alerts - -

-
Loading...
-
+
+
+
+

+

{{followRequests.count}} Follow Requests

+

+
+
@@ -76,11 +76,20 @@ notifications: {}, notificationCursor: 2, notificationMaxId: 0, + profile: { + locked: false + }, + followRequests: null }; }, mounted() { + let self = this; this.fetchNotifications(); + setTimeout(function() { + self.profile = window._sharedData.curUser; + self.fetchFollowRequests(); + }, 500); }, updated() { @@ -138,29 +147,7 @@ }, timeAgo(ts) { - let date = Date.parse(ts); - let seconds = Math.floor((new Date() - date) / 1000); - let interval = Math.floor(seconds / 31536000); - if (interval >= 1) { - return interval + "y"; - } - interval = Math.floor(seconds / 604800); - if (interval >= 1) { - return interval + "w"; - } - interval = Math.floor(seconds / 86400); - if (interval >= 1) { - return interval + "d"; - } - interval = Math.floor(seconds / 3600); - if (interval >= 1) { - return interval + "h"; - } - interval = Math.floor(seconds / 60); - if (interval >= 1) { - return interval + "m"; - } - return Math.floor(seconds) + "s"; + return window.App.util.format.timeAgo(ts); }, mentionUrl(status) { @@ -219,6 +206,19 @@ } } }); + }, + + fetchFollowRequests() { + if(window._sharedData.curUser.locked == true) { + axios.get('/account/follow-requests.json') + .then(res => { + this.followRequests = res.data; + }) + } + }, + + redirect(url) { + window.location.href = url; } } } diff --git a/resources/assets/js/components/PostComponent.vue b/resources/assets/js/components/PostComponent.vue index 05aecb176..1432f83c4 100644 --- a/resources/assets/js/components/PostComponent.vue +++ b/resources/assets/js/components/PostComponent.vue @@ -1066,29 +1066,7 @@ export default { }, timeAgo(ts) { - let date = Date.parse(ts); - let seconds = Math.floor((new Date() - date) / 1000); - let interval = Math.floor(seconds / 31536000); - if (interval >= 1) { - return interval + "y"; - } - interval = Math.floor(seconds / 604800); - if (interval >= 1) { - return interval + "w"; - } - interval = Math.floor(seconds / 86400); - if (interval >= 1) { - return interval + "d"; - } - interval = Math.floor(seconds / 3600); - if (interval >= 1) { - return interval + "h"; - } - interval = Math.floor(seconds / 60); - if (interval >= 1) { - return interval + "m"; - } - return Math.floor(seconds) + "s"; + return App.util.format.timeAgo(ts); }, emojiReaction() { diff --git a/resources/assets/js/components/Profile.vue b/resources/assets/js/components/Profile.vue index 6e8594998..0acccfa68 100644 --- a/resources/assets/js/components/Profile.vue +++ b/resources/assets/js/components/Profile.vue @@ -760,8 +760,13 @@ self.ids.push(d.id); } }); + let max = Math.min(...this.ids); + if(max == this.max_id) { + $state.complete(); + return; + } this.min_id = Math.max(...this.ids); - this.max_id = Math.min(...this.ids); + this.max_id = max; $state.loaded(); this.loading = false; } else { diff --git a/resources/assets/js/components/RemoteProfile.vue b/resources/assets/js/components/RemoteProfile.vue index dc4250509..a0eb9f86a 100644 --- a/resources/assets/js/components/RemoteProfile.vue +++ b/resources/assets/js/components/RemoteProfile.vue @@ -33,8 +33,7 @@

{{profile.display_name}}

-

{{profile.acct}}

-

+

{{profile.acct}}

{{profile.statuses_count}} @@ -49,8 +48,10 @@ Followers

+

+

Last updated:

@@ -110,9 +111,18 @@
- +
@@ -191,6 +201,8 @@ warning: false, ctxMenuStatus: false, ctxMenuRelationship: false, + fetchingRemotePosts: false, + showMutualFollowers: false } }, @@ -451,6 +463,24 @@ swal('Error', 'Something went wrong. Please try again later.', 'error'); }); }, + + manuallyFetchRemotePosts($event) { + this.fetchingRemotePosts = true; + event.target.blur(); + swal( + 'Fetching Remote Posts', + 'Check back in a few minutes!', + 'info' + ); + }, + + timeAgo(ts, suffix = false) { + if(ts == null) { + return 'never'; + } + suffix = suffix ? ' ' + suffix : ''; + return App.util.format.timeAgo(ts) + suffix; + }, } } diff --git a/resources/lang/th/auth.php b/resources/lang/th/auth.php new file mode 100644 index 000000000..5011a85f5 --- /dev/null +++ b/resources/lang/th/auth.php @@ -0,0 +1,19 @@ + 'ข้อมูลไม่ตรงกับบันทึกของเรา', + 'throttle' => 'ลงชื่อเข้าหลายครั้งเกินไป โปรดลองอีกครั้งภายใน :seconds วินาที', + +]; diff --git a/resources/lang/th/exception.php b/resources/lang/th/exception.php new file mode 100644 index 000000000..448f1c3c9 --- /dev/null +++ b/resources/lang/th/exception.php @@ -0,0 +1,11 @@ + [ + 'invalid' => [ + 'album' => 'ต้องมีอย่างรูปหรือวีดิโอเป็นอย่างน้อย', + ], + ], + +]; \ No newline at end of file diff --git a/resources/lang/th/helpcenter.php b/resources/lang/th/helpcenter.php new file mode 100644 index 000000000..62877d1a2 --- /dev/null +++ b/resources/lang/th/helpcenter.php @@ -0,0 +1,26 @@ + 'Help Center', + 'whatsnew' => 'What\'s New', + + 'gettingStarted' => 'Getting Started', + 'sharingMedia' => 'Sharing Media', + 'profile' => 'Profile', + 'stories' => 'Stories', + 'hashtags' => 'Hashtags', + 'discover' => 'Discover', + 'directMessages' => 'Direct Messages', + 'timelines' => 'Timelines', + 'embed' => 'Embed', + + 'communityGuidelines' => 'Community Guidelines', + 'whatIsTheFediverse' => 'What is the fediverse?', + 'controllingVisibility' => 'Controlling Visibility', + 'blockingAccounts' => 'Blocking Accounts', + 'safetyTips' => 'Safety Tips', + 'reportSomething' => 'Report Something', + 'dataPolicy' => 'Data Policy' + +]; \ No newline at end of file diff --git a/resources/lang/th/navmenu.php b/resources/lang/th/navmenu.php new file mode 100644 index 000000000..3449b7d45 --- /dev/null +++ b/resources/lang/th/navmenu.php @@ -0,0 +1,19 @@ + 'ค้นหา', + 'home' => 'หน้าหลัก', + 'local' => 'Local', + 'network' => 'Network', + 'discover' => 'Discover', + 'viewMyProfile' => 'ดูโพรไฟล์', + 'myProfile' => 'โพรไฟล์ของฉัน', + 'myTimeline' => 'ทามไลน์ของฉัน', + 'publicTimeline' => 'ทามไลน์สาธารณะ', + 'remoteFollow' => 'Remote Follow', + 'settings' => 'ตั้งค่า', + 'admin' => 'ผู้ดูแล', + 'logout' => 'ออกจากระบบ', + 'directMessages' => 'ส่งข้อความ', + 'composePost' => 'สร้างโพสต์', +]; diff --git a/resources/lang/th/notification.php b/resources/lang/th/notification.php new file mode 100644 index 000000000..da9e15e4a --- /dev/null +++ b/resources/lang/th/notification.php @@ -0,0 +1,12 @@ + 'ถูกใจโพสต์ของคุณ', + 'likedComment' => 'ถูกใจความเห็นของคุณ', + 'startedFollowingYou' => 'ได้ติดตามคุณแล้ว', + 'commented' => 'ได้แสดงความเห็นโพสต์ของคุณ', + 'mentionedYou' => 'พูดถึงคุณ', + 'shared' => 'แชร์โพสต์ของคุณ', + +]; diff --git a/resources/lang/th/pagination.php b/resources/lang/th/pagination.php new file mode 100644 index 000000000..614f95181 --- /dev/null +++ b/resources/lang/th/pagination.php @@ -0,0 +1,19 @@ + '« ก่อนหน้า', + 'next' => 'ถัดไป »', + +]; diff --git a/resources/lang/th/passwords.php b/resources/lang/th/passwords.php new file mode 100644 index 000000000..20dfe0058 --- /dev/null +++ b/resources/lang/th/passwords.php @@ -0,0 +1,22 @@ + 'รหัสผ่านจำเป็นต้องมีอย่างน้อยหกตัวอักษร', + 'reset' => 'คุณได้เปลี่ยนรหัสผ่านเรียบร้อยแล้ว!', + 'sent' => 'หากอีเมลของคุณอยู่ในฐานข้อมูลของเราแล้ว ในอีกไม่กี่นาทีคุณจะได้รับอีเมลเพื่อแก้ใขรหัสผ่าน หากไม่พบอีเมล โปรดไปตรวจดูที่ถังขยะ', + 'token' => 'ใช้รหัสผ่านนี้ไม่ได้', + 'user' => 'หากอีเมลของคุณอยู่ในฐานข้อมูลของเราแล้ว ในอีกไม่กี่นาทีคุณจะได้รับอีเมลเพื่อแก้ใขรหัสผ่าน หากไม่พบอีเมล โปรดไปตรวจดูที่ถังขยะ', + +]; diff --git a/resources/lang/th/profile.php b/resources/lang/th/profile.php new file mode 100644 index 000000000..eaf694b92 --- /dev/null +++ b/resources/lang/th/profile.php @@ -0,0 +1,15 @@ + 'ผู้ใช้นี้ยังไม่มีโพสต์อะไร!', + 'emptyFollowers' => 'ผู้ใช้นี้ยังไม่มีผู้ติดตาม!', + 'emptyFollowing' => 'ผู้ใช้นี้ยังไม่ได้ติดตามใครเลย!', + 'emptySaved' => 'คุณยังไม่ได้บันทึกโพสต์ใด ๆ!', + 'savedWarning' => 'คุณเท่านั้นที่สามารถดูโพสต์นี้ได้', + 'privateProfileWarning' => 'บัญชีนี้เป็นบัญชีส่วนบุคคล', + 'alreadyFollow' => ':username ได้ติดตาม', + 'loginToSeeProfile' => 'ดูรูปและวีดิโอ', + + 'status.disabled.header' => 'บัญชีไม่สามารถใช้งานได้', + 'status.disabled.body' => 'เสียใจด้วยนะ บัญชีนี้ไม่สามารถใช้งานได้ โปรดลองใหม่อีกครั้ง', +]; diff --git a/resources/lang/th/site.php b/resources/lang/th/site.php new file mode 100644 index 000000000..54c40cd6a --- /dev/null +++ b/resources/lang/th/site.php @@ -0,0 +1,20 @@ + 'เกี่ยวกับ', + 'help' => 'ช่วยเหลือ', + 'language' => 'ภาษา', + 'fediverse' => 'Fediverse', + 'opensource' => 'Open Source', + 'terms' => 'ข้อกำหนด', + 'privacy' => 'ความเป็นส่วนตัว', + 'l10nWip' => 'เรากำลังอยู่ดำเนินการแปลเป็นภาษาของคุณ', + 'currentLocale' => 'สถานที่ปัจจุบัน', + 'selectLocale' => 'เลือกภาษาที่ได้รับการสนับสนุน', + 'contact' => 'ติดต่อ', + 'contact-us' => 'ติดต่อเรา', + 'places' => 'สถานที่', + 'profiles' => 'โพรไฟล์', + +]; diff --git a/resources/lang/th/timeline.php b/resources/lang/th/timeline.php new file mode 100644 index 000000000..79352add8 --- /dev/null +++ b/resources/lang/th/timeline.php @@ -0,0 +1,7 @@ + 'ทามไลน์ของคุณยังไม่มีอะไร!', + +]; diff --git a/resources/lang/th/validation.php b/resources/lang/th/validation.php new file mode 100644 index 000000000..77d230221 --- /dev/null +++ b/resources/lang/th/validation.php @@ -0,0 +1,122 @@ + 'The :attribute must be accepted.', + 'active_url' => 'The :attribute is not a valid URL.', + 'after' => 'The :attribute must be a date after :date.', + 'after_or_equal' => 'The :attribute must be a date after or equal to :date.', + 'alpha' => 'The :attribute may only contain letters.', + 'alpha_dash' => 'The :attribute may only contain letters, numbers, and dashes.', + 'alpha_num' => 'The :attribute may only contain letters and numbers.', + 'array' => 'The :attribute must be an array.', + 'before' => 'The :attribute must be a date before :date.', + 'before_or_equal' => 'The :attribute must be a date before or equal to :date.', + 'between' => [ + 'numeric' => 'The :attribute must be between :min and :max.', + 'file' => 'The :attribute must be between :min and :max kilobytes.', + 'string' => 'The :attribute must be between :min and :max characters.', + 'array' => 'The :attribute must have between :min and :max items.', + ], + 'boolean' => 'The :attribute field must be true or false.', + 'confirmed' => 'The :attribute confirmation does not match.', + 'date' => 'The :attribute is not a valid date.', + 'date_format' => 'The :attribute does not match the format :format.', + 'different' => 'The :attribute and :other must be different.', + 'digits' => 'The :attribute must be :digits digits.', + 'digits_between' => 'The :attribute must be between :min and :max digits.', + 'dimensions' => 'The :attribute has invalid image dimensions.', + 'distinct' => 'The :attribute field has a duplicate value.', + 'email' => 'The :attribute must be a valid email address.', + 'exists' => 'The selected :attribute is invalid.', + 'file' => 'The :attribute must be a file.', + 'filled' => 'The :attribute field must have a value.', + 'image' => 'The :attribute must be an image.', + 'in' => 'The selected :attribute is invalid.', + 'in_array' => 'The :attribute field does not exist in :other.', + 'integer' => 'The :attribute must be an integer.', + 'ip' => 'The :attribute must be a valid IP address.', + 'ipv4' => 'The :attribute must be a valid IPv4 address.', + 'ipv6' => 'The :attribute must be a valid IPv6 address.', + 'json' => 'The :attribute must be a valid JSON string.', + 'max' => [ + 'numeric' => 'The :attribute may not be greater than :max.', + 'file' => 'The :attribute may not be greater than :max kilobytes.', + 'string' => 'The :attribute may not be greater than :max characters.', + 'array' => 'The :attribute may not have more than :max items.', + ], + 'mimes' => 'The :attribute must be a file of type: :values.', + 'mimetypes' => 'The :attribute must be a file of type: :values.', + 'min' => [ + 'numeric' => 'The :attribute must be at least :min.', + 'file' => 'The :attribute must be at least :min kilobytes.', + 'string' => 'The :attribute must be at least :min characters.', + 'array' => 'The :attribute must have at least :min items.', + ], + 'not_in' => 'The selected :attribute is invalid.', + 'not_regex' => 'The :attribute format is invalid.', + 'numeric' => 'The :attribute must be a number.', + 'present' => 'The :attribute field must be present.', + 'regex' => 'The :attribute format is invalid.', + 'required' => 'The :attribute field is required.', + 'required_if' => 'The :attribute field is required when :other is :value.', + 'required_unless' => 'The :attribute field is required unless :other is in :values.', + 'required_with' => 'The :attribute field is required when :values is present.', + 'required_with_all' => 'The :attribute field is required when :values is present.', + 'required_without' => 'The :attribute field is required when :values is not present.', + 'required_without_all' => 'The :attribute field is required when none of :values are present.', + 'same' => 'The :attribute and :other must match.', + 'size' => [ + 'numeric' => 'The :attribute must be :size.', + 'file' => 'The :attribute must be :size kilobytes.', + 'string' => 'The :attribute must be :size characters.', + 'array' => 'The :attribute must contain :size items.', + ], + 'string' => 'The :attribute must be a string.', + 'timezone' => 'The :attribute must be a valid zone.', + 'unique' => 'The :attribute has already been taken.', + 'uploaded' => 'The :attribute failed to upload.', + 'url' => 'The :attribute format is invalid.', + + /* + |-------------------------------------------------------------------------- + | Custom Validation Language Lines + |-------------------------------------------------------------------------- + | + | Here you may specify custom validation messages for attributes using the + | convention "attribute.rule" to name the lines. This makes it quick to + | specify a specific custom language line for a given attribute rule. + | + */ + + 'custom' => [ + 'attribute-name' => [ + 'rule-name' => 'custom-message', + ], + ], + + /* + |-------------------------------------------------------------------------- + | Custom Validation Attributes + |-------------------------------------------------------------------------- + | + | The following language lines are used to swap attribute place-holders + | with something more reader friendly such as E-Mail Address instead + | of "email". This simply helps us make messages a little cleaner. + | + */ + + 'attributes' => [], + +]; diff --git a/routes/web.php b/routes/web.php index 39f5cc402..2799d10e7 100644 --- a/routes/web.php +++ b/routes/web.php @@ -272,6 +272,7 @@ Route::domain(config('pixelfed.domain.app'))->middleware(['validemail', 'twofact Route::get('activity', 'AccountController@notifications')->name('notifications'); Route::get('follow-requests', 'AccountController@followRequests')->name('follow-requests'); Route::post('follow-requests', 'AccountController@followRequestHandle'); + Route::get('follow-requests.json', 'AccountController@followRequestsJson'); }); Route::group(['prefix' => 'settings'], function () {