mirror of
https://github.com/pixelfed/pixelfed.git
synced 2025-01-22 04:20:45 +00:00
commit
2617b15ffd
37 changed files with 593 additions and 69 deletions
13
CHANGELOG.md
13
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)
|
||||
|
|
|
@ -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, [
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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)
|
||||
|
|
10
app/Http/Controllers/UIKitController.php
Normal file
10
app/Http/Controllers/UIKitController.php
Normal file
|
@ -0,0 +1,10 @@
|
|||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
class UIKitController extends Controller
|
||||
{
|
||||
//
|
||||
}
|
|
@ -8,6 +8,8 @@ class ModLog extends Model
|
|||
{
|
||||
protected $visible = ['id'];
|
||||
|
||||
protected $fillable = ['*'];
|
||||
|
||||
public function admin()
|
||||
{
|
||||
return $this->belongsTo(User::class, 'user_id');
|
||||
|
|
|
@ -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'];
|
||||
|
|
59
app/Services/ActivityPubFetchService.php
Normal file
59
app/Services/ActivityPubFetchService.php
Normal file
|
@ -0,0 +1,59 @@
|
|||
<?php
|
||||
|
||||
namespace App\Services;
|
||||
|
||||
use Zttp\Zttp;
|
||||
use App\Profile;
|
||||
use App\Util\ActivityPub\Helpers;
|
||||
use App\Util\ActivityPub\HttpSignature;
|
||||
|
||||
class ActivityPubFetchService
|
||||
{
|
||||
public $signed = true;
|
||||
public $actor;
|
||||
public $url;
|
||||
public $headers = [
|
||||
'Accept' => '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();
|
||||
}
|
||||
}
|
|
@ -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()
|
||||
];
|
||||
}
|
||||
|
||||
|
|
|
@ -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,
|
||||
|
|
21
app/UIKit.php
Normal file
21
app/UIKit.php
Normal file
|
@ -0,0 +1,21 @@
|
|||
<?php
|
||||
|
||||
namespace App;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
class UIKit extends Model
|
||||
{
|
||||
protected $table = 'uikit';
|
||||
protected $fillable = [
|
||||
'k',
|
||||
'v',
|
||||
'defv',
|
||||
'dhis'
|
||||
];
|
||||
|
||||
public static function section($k)
|
||||
{
|
||||
return (new self)->where('k', $k)->first()->v;
|
||||
}
|
||||
}
|
|
@ -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}");
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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',
|
||||
|
|
39
database/migrations/2020_04_13_045435_create_uikit_table.php
Normal file
39
database/migrations/2020_04_13_045435_create_uikit_table.php
Normal file
|
@ -0,0 +1,39 @@
|
|||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
class CreateUikitTable extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function up()
|
||||
{
|
||||
Schema::create('uikit', function (Blueprint $table) {
|
||||
$table->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');
|
||||
}
|
||||
}
|
BIN
public/js/app.js
vendored
BIN
public/js/app.js
vendored
Binary file not shown.
BIN
public/js/profile.js
vendored
BIN
public/js/profile.js
vendored
Binary file not shown.
BIN
public/js/rempro.js
vendored
BIN
public/js/rempro.js
vendored
Binary file not shown.
BIN
public/js/status.js
vendored
BIN
public/js/status.js
vendored
Binary file not shown.
BIN
public/js/timeline.js
vendored
BIN
public/js/timeline.js
vendored
Binary file not shown.
Binary file not shown.
25
resources/assets/js/app.js
vendored
25
resources/assets/js/app.js
vendored
|
@ -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: [
|
||||
|
|
|
@ -2,19 +2,19 @@
|
|||
<div>
|
||||
<transition name="fade">
|
||||
<div class="card notification-card shadow-none border">
|
||||
<div class="card-header bg-white">
|
||||
<p class="mb-0 d-flex align-items-center justify-content-between">
|
||||
<span data-toggle="tooltip" data-placement="bottom"><i class="fas fa-redo fa-lg text-white"></i></span>
|
||||
<span class="small text-dark text-uppercase font-weight-bold">Alerts</span>
|
||||
<a class="text-decoration-none text-muted" href="/account/activity"><i class="fas fa-inbox fa-lg"></i></a>
|
||||
</p>
|
||||
</div>
|
||||
<div class="card-body loader text-center" style="height: 200px;">
|
||||
<div class="spinner-border" role="status">
|
||||
<span class="sr-only">Loading...</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-body pt-2 px-0 py-0 contents" style="max-height: 200px; overflow-y: scroll;">
|
||||
<div v-if="notifications.length > 0" class="card-body px-0 py-0 contents" style="max-height: 240px; overflow-y: scroll;">
|
||||
<div v-if="profile.locked" class="media align-items-center mt-n2 px-3 py-2 border-bottom border-lighter bg-light cursor-pointer" @click="redirect('/account/follow-requests')">
|
||||
<div class="media-body font-weight-light pt-2 small d-flex align-items-center justify-content-between">
|
||||
<p class="mb-0 text-lighter"><i class="fas fa-cog text-light"></i></p>
|
||||
<p class="text-center pt-1 mb-1 text-dark font-weight-bold"><strong>{{followRequests.count}}</strong> Follow Requests</p>
|
||||
<p class="mb-0 text-lighter"><i class="fas fa-chevron-right"></i></p>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="notifications.length > 0" class="media align-items-center px-3 py-2 border-bottom border-light" v-for="(n, index) in notifications">
|
||||
<img class="mr-2 rounded-circle" style="border:1px solid #ccc" :src="n.account.avatar" alt="" width="32px" height="32px" onerror="this.onerror=null;this.src='/storage/avatars/default.png';">
|
||||
<div class="media-body font-weight-light small">
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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() {
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -33,8 +33,7 @@
|
|||
</span>
|
||||
</div>
|
||||
<p class="pl-2 h4 font-weight-bold mb-1">{{profile.display_name}}</p>
|
||||
<p class="pl-2 font-weight-bold mb-1 text-muted">{{profile.acct}}</p>
|
||||
<p class="pl-2 text-muted small pt-3" v-html="profile.note"></p>
|
||||
<p class="pl-2 font-weight-bold mb-2 text-muted">{{profile.acct}}</p>
|
||||
<p class="pl-2 text-muted small d-flex justify-content-between">
|
||||
<span>
|
||||
<span class="font-weight-bold text-dark">{{profile.statuses_count}}</span>
|
||||
|
@ -49,8 +48,10 @@
|
|||
<span>Followers</span>
|
||||
</span>
|
||||
</p>
|
||||
<p class="pl-2 text-muted small pt-2" v-html="profile.note"></p>
|
||||
</div>
|
||||
</div>
|
||||
<p class="small text-lighter p-2">Last updated: <time :datetime="profile.last_fetched_at">{{timeAgo(profile.last_fetched_at, 'ago')}}</time></p>
|
||||
</div>
|
||||
<div class="col-12 col-md-8 pt-5">
|
||||
<div class="row">
|
||||
|
@ -110,9 +111,18 @@
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<!-- <div class="col-12 mt-4">
|
||||
<div v-if="feed.length == 0" class="col-12 mb-2">
|
||||
<div class="d-flex justify-content-center align-items-center bg-white border rounded" style="height:60vh;">
|
||||
<div class="text-center">
|
||||
<p class="mb-0 lead">No posts found.</p>
|
||||
<p class="">We haven't seen any posts from this account.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-else class="col-12 mt-4">
|
||||
<p class="text-center mb-0 px-0"><button class="btn btn-outline-primary btn-block font-weight-bold">Load More</button></p>
|
||||
</div> -->
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -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;
|
||||
},
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
|
19
resources/lang/th/auth.php
Normal file
19
resources/lang/th/auth.php
Normal file
|
@ -0,0 +1,19 @@
|
|||
<?php
|
||||
|
||||
return [
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Authentication Language Lines
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| The following language lines are used during authentication for various
|
||||
| messages that we need to display to the user. You are free to modify
|
||||
| these language lines according to your application's requirements.
|
||||
|
|
||||
*/
|
||||
|
||||
'failed' => 'ข้อมูลไม่ตรงกับบันทึกของเรา',
|
||||
'throttle' => 'ลงชื่อเข้าหลายครั้งเกินไป โปรดลองอีกครั้งภายใน :seconds วินาที',
|
||||
|
||||
];
|
11
resources/lang/th/exception.php
Normal file
11
resources/lang/th/exception.php
Normal file
|
@ -0,0 +1,11 @@
|
|||
<?php
|
||||
|
||||
return [
|
||||
|
||||
'compose' => [
|
||||
'invalid' => [
|
||||
'album' => 'ต้องมีอย่างรูปหรือวีดิโอเป็นอย่างน้อย',
|
||||
],
|
||||
],
|
||||
|
||||
];
|
26
resources/lang/th/helpcenter.php
Normal file
26
resources/lang/th/helpcenter.php
Normal file
|
@ -0,0 +1,26 @@
|
|||
<?php
|
||||
|
||||
return [
|
||||
|
||||
'helpcenter' => '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'
|
||||
|
||||
];
|
19
resources/lang/th/navmenu.php
Normal file
19
resources/lang/th/navmenu.php
Normal file
|
@ -0,0 +1,19 @@
|
|||
<?php
|
||||
|
||||
return [
|
||||
'search' => 'ค้นหา',
|
||||
'home' => 'หน้าหลัก',
|
||||
'local' => 'Local',
|
||||
'network' => 'Network',
|
||||
'discover' => 'Discover',
|
||||
'viewMyProfile' => 'ดูโพรไฟล์',
|
||||
'myProfile' => 'โพรไฟล์ของฉัน',
|
||||
'myTimeline' => 'ทามไลน์ของฉัน',
|
||||
'publicTimeline' => 'ทามไลน์สาธารณะ',
|
||||
'remoteFollow' => 'Remote Follow',
|
||||
'settings' => 'ตั้งค่า',
|
||||
'admin' => 'ผู้ดูแล',
|
||||
'logout' => 'ออกจากระบบ',
|
||||
'directMessages' => 'ส่งข้อความ',
|
||||
'composePost' => 'สร้างโพสต์',
|
||||
];
|
12
resources/lang/th/notification.php
Normal file
12
resources/lang/th/notification.php
Normal file
|
@ -0,0 +1,12 @@
|
|||
<?php
|
||||
|
||||
return [
|
||||
|
||||
'likedPhoto' => 'ถูกใจโพสต์ของคุณ',
|
||||
'likedComment' => 'ถูกใจความเห็นของคุณ',
|
||||
'startedFollowingYou' => 'ได้ติดตามคุณแล้ว',
|
||||
'commented' => 'ได้แสดงความเห็นโพสต์ของคุณ',
|
||||
'mentionedYou' => 'พูดถึงคุณ',
|
||||
'shared' => 'แชร์โพสต์ของคุณ',
|
||||
|
||||
];
|
19
resources/lang/th/pagination.php
Normal file
19
resources/lang/th/pagination.php
Normal file
|
@ -0,0 +1,19 @@
|
|||
<?php
|
||||
|
||||
return [
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Pagination Language Lines
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| The following language lines are used by the paginator library to build
|
||||
| the simple pagination links. You are free to change them to anything
|
||||
| you want to customize your views to better match your application.
|
||||
|
|
||||
*/
|
||||
|
||||
'previous' => '« ก่อนหน้า',
|
||||
'next' => 'ถัดไป »',
|
||||
|
||||
];
|
22
resources/lang/th/passwords.php
Normal file
22
resources/lang/th/passwords.php
Normal file
|
@ -0,0 +1,22 @@
|
|||
<?php
|
||||
|
||||
return [
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Password Reset Language Lines
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| The following language lines are the default lines which match reasons
|
||||
| that are given by the password broker for a password update attempt
|
||||
| has failed, such as for an invalid token or invalid new password.
|
||||
|
|
||||
*/
|
||||
|
||||
'password' => 'รหัสผ่านจำเป็นต้องมีอย่างน้อยหกตัวอักษร',
|
||||
'reset' => 'คุณได้เปลี่ยนรหัสผ่านเรียบร้อยแล้ว!',
|
||||
'sent' => 'หากอีเมลของคุณอยู่ในฐานข้อมูลของเราแล้ว ในอีกไม่กี่นาทีคุณจะได้รับอีเมลเพื่อแก้ใขรหัสผ่าน หากไม่พบอีเมล โปรดไปตรวจดูที่ถังขยะ',
|
||||
'token' => 'ใช้รหัสผ่านนี้ไม่ได้',
|
||||
'user' => 'หากอีเมลของคุณอยู่ในฐานข้อมูลของเราแล้ว ในอีกไม่กี่นาทีคุณจะได้รับอีเมลเพื่อแก้ใขรหัสผ่าน หากไม่พบอีเมล โปรดไปตรวจดูที่ถังขยะ',
|
||||
|
||||
];
|
15
resources/lang/th/profile.php
Normal file
15
resources/lang/th/profile.php
Normal file
|
@ -0,0 +1,15 @@
|
|||
<?php
|
||||
|
||||
return [
|
||||
'emptyTimeline' => 'ผู้ใช้นี้ยังไม่มีโพสต์อะไร!',
|
||||
'emptyFollowers' => 'ผู้ใช้นี้ยังไม่มีผู้ติดตาม!',
|
||||
'emptyFollowing' => 'ผู้ใช้นี้ยังไม่ได้ติดตามใครเลย!',
|
||||
'emptySaved' => 'คุณยังไม่ได้บันทึกโพสต์ใด ๆ!',
|
||||
'savedWarning' => 'คุณเท่านั้นที่สามารถดูโพสต์นี้ได้',
|
||||
'privateProfileWarning' => 'บัญชีนี้เป็นบัญชีส่วนบุคคล',
|
||||
'alreadyFollow' => ':username ได้ติดตาม',
|
||||
'loginToSeeProfile' => 'ดูรูปและวีดิโอ',
|
||||
|
||||
'status.disabled.header' => 'บัญชีไม่สามารถใช้งานได้',
|
||||
'status.disabled.body' => 'เสียใจด้วยนะ บัญชีนี้ไม่สามารถใช้งานได้ โปรดลองใหม่อีกครั้ง',
|
||||
];
|
20
resources/lang/th/site.php
Normal file
20
resources/lang/th/site.php
Normal file
|
@ -0,0 +1,20 @@
|
|||
<?php
|
||||
|
||||
return [
|
||||
|
||||
'about' => 'เกี่ยวกับ',
|
||||
'help' => 'ช่วยเหลือ',
|
||||
'language' => 'ภาษา',
|
||||
'fediverse' => 'Fediverse',
|
||||
'opensource' => 'Open Source',
|
||||
'terms' => 'ข้อกำหนด',
|
||||
'privacy' => 'ความเป็นส่วนตัว',
|
||||
'l10nWip' => 'เรากำลังอยู่ดำเนินการแปลเป็นภาษาของคุณ',
|
||||
'currentLocale' => 'สถานที่ปัจจุบัน',
|
||||
'selectLocale' => 'เลือกภาษาที่ได้รับการสนับสนุน',
|
||||
'contact' => 'ติดต่อ',
|
||||
'contact-us' => 'ติดต่อเรา',
|
||||
'places' => 'สถานที่',
|
||||
'profiles' => 'โพรไฟล์',
|
||||
|
||||
];
|
7
resources/lang/th/timeline.php
Normal file
7
resources/lang/th/timeline.php
Normal file
|
@ -0,0 +1,7 @@
|
|||
<?php
|
||||
|
||||
return [
|
||||
|
||||
'emptyPersonalTimeline' => 'ทามไลน์ของคุณยังไม่มีอะไร!',
|
||||
|
||||
];
|
122
resources/lang/th/validation.php
Normal file
122
resources/lang/th/validation.php
Normal file
|
@ -0,0 +1,122 @@
|
|||
<?php
|
||||
|
||||
return [
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Validation Language Lines
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| The following language lines contain the default error messages used by
|
||||
| the validator class. Some of these rules have multiple versions such
|
||||
| as the size rules. Feel free to tweak each of these messages here.
|
||||
|
|
||||
*/
|
||||
|
||||
'accepted' => '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' => [],
|
||||
|
||||
];
|
|
@ -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 () {
|
||||
|
|
Loading…
Reference in a new issue