mirror of
https://github.com/pixelfed/pixelfed.git
synced 2025-01-10 14:10:46 +00:00
commit
8eb3a4cb46
16 changed files with 1474 additions and 872 deletions
|
@ -2,6 +2,10 @@
|
||||||
|
|
||||||
## [Unreleased](https://github.com/pixelfed/pixelfed/compare/v0.12.3...dev)
|
## [Unreleased](https://github.com/pixelfed/pixelfed/compare/v0.12.3...dev)
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
- WebGL photo filters ([#5374](https://github.com/pixelfed/pixelfed/pull/5374))
|
||||||
|
|
||||||
### OAuth
|
### OAuth
|
||||||
- Fix oauth oob (urn:ietf:wg:oauth:2.0:oob) support. ([8afbdb03](https://github.com/pixelfed/pixelfed/commit/8afbdb03))
|
- Fix oauth oob (urn:ietf:wg:oauth:2.0:oob) support. ([8afbdb03](https://github.com/pixelfed/pixelfed/commit/8afbdb03))
|
||||||
|
|
||||||
|
@ -19,6 +23,9 @@
|
||||||
- Update StatusStatelessTransformer, refactor the caption field to be compliant with the MastoAPI. Fixes #5364 ([79039ba5](https://github.com/pixelfed/pixelfed/commit/79039ba5))
|
- Update StatusStatelessTransformer, refactor the caption field to be compliant with the MastoAPI. Fixes #5364 ([79039ba5](https://github.com/pixelfed/pixelfed/commit/79039ba5))
|
||||||
- Update mailgun config, add endpoint and scheme ([271d5114](https://github.com/pixelfed/pixelfed/commit/271d5114))
|
- Update mailgun config, add endpoint and scheme ([271d5114](https://github.com/pixelfed/pixelfed/commit/271d5114))
|
||||||
- Update search and status logic to fix postgres bugs ([8c39ef4](https://github.com/pixelfed/pixelfed/commit/8c39ef4))
|
- Update search and status logic to fix postgres bugs ([8c39ef4](https://github.com/pixelfed/pixelfed/commit/8c39ef4))
|
||||||
|
- Update db, fix sqlite migrations ([#5379](https://github.com/pixelfed/pixelfed/pull/5379))
|
||||||
|
- Update CatchUnoptimizedMedia command, make 1hr limit opt-in ([99b15b73](https://github.com/pixelfed/pixelfed/commit/99b15b73))
|
||||||
|
- Update IG, fix Instagram import. Closes #5411 ([fd434aec](https://github.com/pixelfed/pixelfed/commit/fd434aec))
|
||||||
- ([](https://github.com/pixelfed/pixelfed/commit/))
|
- ([](https://github.com/pixelfed/pixelfed/commit/))
|
||||||
|
|
||||||
## [v0.12.4 (2024-11-08)](https://github.com/pixelfed/pixelfed/compare/v0.12.4...dev)
|
## [v0.12.4 (2024-11-08)](https://github.com/pixelfed/pixelfed/compare/v0.12.4...dev)
|
||||||
|
|
|
@ -40,10 +40,11 @@ class CatchUnoptimizedMedia extends Command
|
||||||
*/
|
*/
|
||||||
public function handle()
|
public function handle()
|
||||||
{
|
{
|
||||||
|
$hasLimit = (bool) config('media.image_optimize.catch_unoptimized_media_hour_limit');
|
||||||
Media::whereNull('processed_at')
|
Media::whereNull('processed_at')
|
||||||
->where('created_at', '>', now()->subHours(1))
|
->when($hasLimit, function($q, $hasLimit) {
|
||||||
->where('skip_optimize', '!=', true)
|
$q->where('created_at', '>', now()->subHours(1));
|
||||||
->whereNull('remote_url')
|
})->whereNull('remote_url')
|
||||||
->whereNotNull('status_id')
|
->whereNotNull('status_id')
|
||||||
->whereNotNull('media_path')
|
->whereNotNull('media_path')
|
||||||
->whereIn('mime', [
|
->whereIn('mime', [
|
||||||
|
@ -52,6 +53,7 @@ class CatchUnoptimizedMedia extends Command
|
||||||
])
|
])
|
||||||
->chunk(50, function($medias) {
|
->chunk(50, function($medias) {
|
||||||
foreach ($medias as $media) {
|
foreach ($medias as $media) {
|
||||||
|
if ($media->skip_optimize) continue;
|
||||||
ImageOptimize::dispatch($media);
|
ImageOptimize::dispatch($media);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
|
@ -3494,7 +3494,7 @@ class ApiV1Controller extends Controller
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
|
|
||||||
$defaultCaption = config_cache('database.default') === 'mysql' ? null : "";
|
$defaultCaption = "";
|
||||||
$content = $request->filled('status') ? strip_tags($request->input('status')) : $defaultCaption;
|
$content = $request->filled('status') ? strip_tags($request->input('status')) : $defaultCaption;
|
||||||
$cw = $user->profile->cw == true ? true : $request->boolean('sensitive', false);
|
$cw = $user->profile->cw == true ? true : $request->boolean('sensitive', false);
|
||||||
$spoilerText = $cw && $request->filled('spoiler_text') ? $request->input('spoiler_text') : null;
|
$spoilerText = $cw && $request->filled('spoiler_text') ? $request->input('spoiler_text') : null;
|
||||||
|
|
|
@ -1292,7 +1292,7 @@ class ApiV1Dot1Controller extends Controller
|
||||||
if ($user->last_active_at == null) {
|
if ($user->last_active_at == null) {
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
$defaultCaption = config_cache('database.default') === 'mysql' ? null : "";
|
$defaultCaption = "";
|
||||||
$content = $request->filled('status') ? strip_tags(Purify::clean($request->input('status'))) : $defaultCaption;
|
$content = $request->filled('status') ? strip_tags(Purify::clean($request->input('status'))) : $defaultCaption;
|
||||||
$cw = $user->profile->cw == true ? true : $request->boolean('sensitive', false);
|
$cw = $user->profile->cw == true ? true : $request->boolean('sensitive', false);
|
||||||
$spoilerText = $cw && $request->filled('spoiler_text') ? $request->input('spoiler_text') : null;
|
$spoilerText = $cw && $request->filled('spoiler_text') ? $request->input('spoiler_text') : null;
|
||||||
|
|
|
@ -55,14 +55,12 @@ class CommentController extends Controller
|
||||||
}
|
}
|
||||||
|
|
||||||
$reply = DB::transaction(function () use ($comment, $status, $profile, $nsfw) {
|
$reply = DB::transaction(function () use ($comment, $status, $profile, $nsfw) {
|
||||||
$defaultCaption = config_cache('database.default') === 'mysql' ? null : "";
|
|
||||||
|
|
||||||
$scope = $profile->is_private == true ? 'private' : 'public';
|
$scope = $profile->is_private == true ? 'private' : 'public';
|
||||||
$reply = new Status;
|
$reply = new Status;
|
||||||
$reply->profile_id = $profile->id;
|
$reply->profile_id = $profile->id;
|
||||||
$reply->is_nsfw = $nsfw;
|
$reply->is_nsfw = $nsfw;
|
||||||
$reply->caption = Purify::clean($comment);
|
$reply->caption = Purify::clean($comment);
|
||||||
$reply->rendered = $defaultCaption;
|
$reply->rendered = "";
|
||||||
$reply->in_reply_to_id = $status->id;
|
$reply->in_reply_to_id = $status->id;
|
||||||
$reply->in_reply_to_profile_id = $status->profile_id;
|
$reply->in_reply_to_profile_id = $status->profile_id;
|
||||||
$reply->scope = $scope;
|
$reply->scope = $scope;
|
||||||
|
|
|
@ -570,7 +570,7 @@ class ComposeController extends Controller
|
||||||
$status->cw_summary = $request->input('spoiler_text');
|
$status->cw_summary = $request->input('spoiler_text');
|
||||||
}
|
}
|
||||||
|
|
||||||
$defaultCaption = config_cache('database.default') === 'mysql' ? null : "";
|
$defaultCaption = "";
|
||||||
$status->caption = strip_tags($request->input('caption')) ?? $defaultCaption;
|
$status->caption = strip_tags($request->input('caption')) ?? $defaultCaption;
|
||||||
$status->rendered = $defaultCaption;
|
$status->rendered = $defaultCaption;
|
||||||
$status->scope = 'draft';
|
$status->scope = 'draft';
|
||||||
|
|
|
@ -22,6 +22,7 @@ class Media extends Model
|
||||||
protected $casts = [
|
protected $casts = [
|
||||||
'srcset' => 'array',
|
'srcset' => 'array',
|
||||||
'deleted_at' => 'datetime',
|
'deleted_at' => 'datetime',
|
||||||
|
'skip_optimize' => 'boolean'
|
||||||
];
|
];
|
||||||
|
|
||||||
public function status()
|
public function status()
|
||||||
|
|
|
@ -14,7 +14,7 @@ class ImportService
|
||||||
if($userId > 999999) {
|
if($userId > 999999) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if($year < 9 || $year > 23) {
|
if($year < 9 || $year > (int) now()->addYear()->format('y')) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if($month < 1 || $month > 12) {
|
if($month < 1 || $month > 12) {
|
||||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -24,6 +24,10 @@ return [
|
||||||
],
|
],
|
||||||
],
|
],
|
||||||
|
|
||||||
|
'image_optimize' => [
|
||||||
|
'catch_unoptimized_media_hour_limit' => env('PF_CATCHUNOPTIMIZEDMEDIA', false),
|
||||||
|
],
|
||||||
|
|
||||||
'hls' => [
|
'hls' => [
|
||||||
/*
|
/*
|
||||||
|--------------------------------------------------------------------------
|
|--------------------------------------------------------------------------
|
||||||
|
|
|
@ -2,6 +2,7 @@
|
||||||
|
|
||||||
use Illuminate\Database\Migrations\Migration;
|
use Illuminate\Database\Migrations\Migration;
|
||||||
use Illuminate\Database\Schema\Blueprint;
|
use Illuminate\Database\Schema\Blueprint;
|
||||||
|
use Illuminate\Support\Facades\DB;
|
||||||
use Illuminate\Support\Facades\Schema;
|
use Illuminate\Support\Facades\Schema;
|
||||||
|
|
||||||
return new class extends Migration
|
return new class extends Migration
|
||||||
|
@ -12,6 +13,9 @@ return new class extends Migration
|
||||||
public function up(): void
|
public function up(): void
|
||||||
{
|
{
|
||||||
Schema::table('group_posts', function (Blueprint $table) {
|
Schema::table('group_posts', function (Blueprint $table) {
|
||||||
|
if (DB::getDriverName() === 'sqlite') {
|
||||||
|
$table->dropUnique(['status_id']);
|
||||||
|
}
|
||||||
$table->dropColumn('status_id');
|
$table->dropColumn('status_id');
|
||||||
$table->dropColumn('reply_child_id');
|
$table->dropColumn('reply_child_id');
|
||||||
$table->dropColumn('in_reply_to_id');
|
$table->dropColumn('in_reply_to_id');
|
||||||
|
|
34
package-lock.json
generated
34
package-lock.json
generated
|
@ -44,6 +44,7 @@
|
||||||
"vue-loading-overlay": "^3.3.3",
|
"vue-loading-overlay": "^3.3.3",
|
||||||
"vue-timeago": "^5.1.2",
|
"vue-timeago": "^5.1.2",
|
||||||
"vue-tribute": "^1.0.7",
|
"vue-tribute": "^1.0.7",
|
||||||
|
"webgl-media-editor": "^0.0.1",
|
||||||
"zuck.js": "^1.6.0"
|
"zuck.js": "^1.6.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
@ -5841,6 +5842,12 @@
|
||||||
"resolved": "https://registry.npmjs.org/github-from-package/-/github-from-package-0.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/github-from-package/-/github-from-package-0.0.0.tgz",
|
||||||
"integrity": "sha512-SyHy3T1v2NUXn29OsWdxmK6RwHD+vkj3v8en8AOBZ1wBQ/hCAQ5bAQTD02kW4W9tUp/3Qh6J8r9EvntiyCmOOw=="
|
"integrity": "sha512-SyHy3T1v2NUXn29OsWdxmK6RwHD+vkj3v8en8AOBZ1wBQ/hCAQ5bAQTD02kW4W9tUp/3Qh6J8r9EvntiyCmOOw=="
|
||||||
},
|
},
|
||||||
|
"node_modules/gl-matrix": {
|
||||||
|
"version": "3.4.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/gl-matrix/-/gl-matrix-3.4.3.tgz",
|
||||||
|
"integrity": "sha512-wcCp8vu8FT22BnvKVPjXa/ICBWRq/zjFfdofZy1WSpQZpphblv12/bOQLBC1rMM7SGOFS9ltVmKOHil5+Ml7gA==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/glob": {
|
"node_modules/glob": {
|
||||||
"version": "7.2.3",
|
"version": "7.2.3",
|
||||||
"resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz",
|
"resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz",
|
||||||
|
@ -10163,6 +10170,15 @@
|
||||||
"b4a": "^1.6.4"
|
"b4a": "^1.6.4"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/throttle-debounce": {
|
||||||
|
"version": "5.0.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/throttle-debounce/-/throttle-debounce-5.0.2.tgz",
|
||||||
|
"integrity": "sha512-B71/4oyj61iNH0KeCamLuE2rmKuTO5byTOSVwECM5FA7TiAiAW+UqTKZ9ERueC4qvgSttUhdmq1mXC3kJqGX7A==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=12.22"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/thunky": {
|
"node_modules/thunky": {
|
||||||
"version": "1.1.0",
|
"version": "1.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/thunky/-/thunky-1.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/thunky/-/thunky-1.1.0.tgz",
|
||||||
|
@ -10240,6 +10256,12 @@
|
||||||
"integrity": "sha512-6rt+RN7aOi1nGMyC4Xa5DdYiukl2UWCbcJft7YhxReBGQD7OAM8Pbxw6YMo4r2diNEA8FEmu32YOn9rhaiE5yw==",
|
"integrity": "sha512-6rt+RN7aOi1nGMyC4Xa5DdYiukl2UWCbcJft7YhxReBGQD7OAM8Pbxw6YMo4r2diNEA8FEmu32YOn9rhaiE5yw==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
|
"node_modules/twgl.js": {
|
||||||
|
"version": "5.5.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/twgl.js/-/twgl.js-5.5.4.tgz",
|
||||||
|
"integrity": "sha512-6kFOmijOpmblTN9CCwOTCxK4lPg7rCyQjLuub6EMOlEp89Ex6yUcsMjsmH7andNPL2NE3XmHdqHeP5gVKKPhxw==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/twitter-text": {
|
"node_modules/twitter-text": {
|
||||||
"version": "2.0.5",
|
"version": "2.0.5",
|
||||||
"resolved": "https://registry.npmjs.org/twitter-text/-/twitter-text-2.0.5.tgz",
|
"resolved": "https://registry.npmjs.org/twitter-text/-/twitter-text-2.0.5.tgz",
|
||||||
|
@ -10727,6 +10749,18 @@
|
||||||
"node": ">= 8"
|
"node": ">= 8"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/webgl-media-editor": {
|
||||||
|
"version": "0.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/webgl-media-editor/-/webgl-media-editor-0.0.1.tgz",
|
||||||
|
"integrity": "sha512-TxnuRl3rpWa1Cia/pn+vh+0iz3yDNwzsrnRGJ61YkdZAYuimu2afBivSHv0RK73hKza6Y/YoRCkuEcsFmtxPNw==",
|
||||||
|
"license": "AGPL-3.0-only",
|
||||||
|
"dependencies": {
|
||||||
|
"cropperjs": "^1.6.2",
|
||||||
|
"gl-matrix": "^3.4.3",
|
||||||
|
"throttle-debounce": "^5.0.2",
|
||||||
|
"twgl.js": "^5.5.4"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/webidl-conversions": {
|
"node_modules/webidl-conversions": {
|
||||||
"version": "3.0.1",
|
"version": "3.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz",
|
||||||
|
|
|
@ -71,6 +71,7 @@
|
||||||
"vue-loading-overlay": "^3.3.3",
|
"vue-loading-overlay": "^3.3.3",
|
||||||
"vue-timeago": "^5.1.2",
|
"vue-timeago": "^5.1.2",
|
||||||
"vue-tribute": "^1.0.7",
|
"vue-tribute": "^1.0.7",
|
||||||
|
"webgl-media-editor": "^0.0.1",
|
||||||
"zuck.js": "^1.6.0"
|
"zuck.js": "^1.6.0"
|
||||||
},
|
},
|
||||||
"collective": {
|
"collective": {
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
<template>
|
<template>
|
||||||
<div class="compose-modal-component">
|
<div class="compose-modal-component">
|
||||||
<input type="file" id="pf-dz" name="media" class="w-100 h-100 d-none file-input" multiple="" v-bind:accept="config.uploader.media_types">
|
<input type="file" id="pf-dz" name="media" class="w-100 h-100 d-none file-input" multiple="" v-bind:accept="config.uploader.media_types" @input="onInputFile">
|
||||||
<canvas class="d-none" id="pr_canvas"></canvas>
|
<canvas class="d-none" id="pr_canvas"></canvas>
|
||||||
<img class="d-none" id="pr_img">
|
<img class="d-none" id="pr_img">
|
||||||
<div class="timeline">
|
<div class="timeline">
|
||||||
|
@ -184,7 +184,6 @@
|
||||||
</template>
|
</template>
|
||||||
<a v-if="!pageLoading && page == 'addText'" class="font-weight-bold text-decoration-none" href="#" @click.prevent="composeTextPost()">Post</a>
|
<a v-if="!pageLoading && page == 'addText'" class="font-weight-bold text-decoration-none" href="#" @click.prevent="composeTextPost()">Post</a>
|
||||||
<a v-if="!pageLoading && page == 'video-2'" class="font-weight-bold text-decoration-none" href="#" @click.prevent="compose()">Post</a>
|
<a v-if="!pageLoading && page == 'video-2'" class="font-weight-bold text-decoration-none" href="#" @click.prevent="compose()">Post</a>
|
||||||
<span v-if="!pageLoading && page == 'filteringMedia'" class="font-weight-bold text-decoration-none text-muted">Next</span>
|
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -342,8 +341,8 @@
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div v-else-if="page == 'cropPhoto'" class="w-100 h-100">
|
<div v-else-if="page == 'cropPhoto'" class="w-100 h-100">
|
||||||
<div v-if="ids.length > 0">
|
<div v-if="media.length > 0">
|
||||||
<vue-cropper
|
<vue-cropper
|
||||||
ref="cropper"
|
ref="cropper"
|
||||||
:relativeZoom="cropper.zoom"
|
:relativeZoom="cropper.zoom"
|
||||||
:aspectRatio="cropper.aspectRatio"
|
:aspectRatio="cropper.aspectRatio"
|
||||||
|
@ -358,37 +357,22 @@
|
||||||
|
|
||||||
<div v-else-if="page == 2" class="w-100 h-100">
|
<div v-else-if="page == 2" class="w-100 h-100">
|
||||||
<div v-if="media.length == 1">
|
<div v-if="media.length == 1">
|
||||||
<div slot="img" style="display:flex;min-height: 420px;align-items: center;">
|
<template v-if="media[0].type === 'image'">
|
||||||
<img :class="'d-block img-fluid w-100 ' + [media[carouselCursor].filter_class?media[carouselCursor].filter_class:'']" :src="media[carouselCursor].url" :alt="media[carouselCursor].description" :title="media[carouselCursor].description">
|
<media-editor-preview class="media-editor" :editor="editor" :sourceIndex="0" />
|
||||||
</div>
|
<hr>
|
||||||
<hr>
|
<media-editor-filter-menu class="media-editor" :editor="editor" :sourceIndex="0" />
|
||||||
<div v-if="ids.length > 0 && media[carouselCursor].type == 'image'" class="align-items-center px-2 pt-2">
|
</template>
|
||||||
<ul class="nav media-drawer-filters text-center">
|
<img v-else class="d-block img-fluid w-100" src="/storage/no-preview.png" :alt="media[carouselCursor].description" :title="media[carouselCursor].description">
|
||||||
<li class="nav-item">
|
|
||||||
<div class="p-1 pt-3">
|
|
||||||
<img :src="media[carouselCursor].url" width="100px" height="60px" v-on:click.prevent="toggleFilter($event, null)" class="cursor-pointer">
|
|
||||||
</div>
|
|
||||||
<a :class="[media[carouselCursor].filter_class == null ? 'nav-link text-primary active' : 'nav-link text-muted']" href="#" v-on:click.prevent="toggleFilter($event, null)">No Filter</a>
|
|
||||||
</li>
|
|
||||||
<li class="nav-item" v-for="(filter, index) in filters">
|
|
||||||
<div class="p-1 pt-3">
|
|
||||||
<div class="rounded" :class="filter[1]">
|
|
||||||
<img :src="media[carouselCursor].url" width="100px" height="60px" v-on:click.prevent="toggleFilter($event, filter[1])">
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<a :class="[media[carouselCursor].filter_class == filter[1] ? 'nav-link text-primary active' : 'nav-link text-muted']" href="#" v-on:click.prevent="toggleFilter($event, filter[1])">{{filter[0]}}</a>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
<div v-else-if="media.length > 1" class="d-flex-inline px-2 pt-2">
|
<div v-else-if="media.length > 1" class="d-flex-inline px-2 pt-2">
|
||||||
<ul class="nav media-drawer-filters text-center pb-3">
|
<ul class="nav media-drawer-filters text-center pb-3">
|
||||||
<li class="nav-item mx-md-4"> </li>
|
<li class="nav-item mx-md-4"> </li>
|
||||||
<li v-for="(m, i) in media" :key="m.id + ':' + carouselCursor" class="nav-item mx-md-4">
|
<li v-for="(m, i) in media" :key="i + (ids[i] || m.url || '')" class="nav-item mx-md-4">
|
||||||
<div class="nav-link" style="display:block;width:300px;height:300px;" @click="carouselCursor = i">
|
<div class="nav-link" style="display:block;width:300px;height:300px;" @click="carouselCursor = i">
|
||||||
<!-- <img :class="'d-block img-fluid w-100 ' + [m.filter_class?m.filter_class:'']" :src="m.url" :alt="m.description" :title="m.description"> -->
|
<!-- <img :class="'d-block img-fluid w-100 ' + [m.filter_class?m.filter_class:'']" :src="m.url" :alt="m.description" :title="m.description"> -->
|
||||||
<div :class="[m.filter_class?m.filter_class:'']" style="width:100%;height:100%;display:block;">
|
<div :class="[m.filter_class?m.filter_class:'']" style="width:100%;height:100%;display:block;">
|
||||||
<div :class="'rounded ' + [i == carouselCursor ? ' border border-primary shadow':'']" :style="'display:block;width:100%;height:100%;background-image: url(' + m.url + ');background-size:cover;'"></div>
|
<media-editor-preview v-if="m.type === 'image'" class="media-editor" :editor="editor" :sourceIndex="i" :class="'rounded ' + [i == carouselCursor ? ' border border-primary shadow':'']" style="width:100%;height:100%;" />
|
||||||
|
<img v-else class="d-block img-fluid w-100" src="/storage/no-preview.png" :alt="media[carouselCursor].description" :title="media[carouselCursor].description">
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div v-if="i == carouselCursor" class="text-center mb-0 small text-lighter font-weight-bold pt-2">
|
<div v-if="i == carouselCursor" class="text-center mb-0 small text-lighter font-weight-bold pt-2">
|
||||||
|
@ -402,21 +386,8 @@
|
||||||
<li class="nav-item mx-md-4"> </li>
|
<li class="nav-item mx-md-4"> </li>
|
||||||
</ul>
|
</ul>
|
||||||
<hr>
|
<hr>
|
||||||
<div v-if="ids.length > 0 && media[carouselCursor].type == 'image'" class="align-items-center px-2 pt-2">
|
<div v-if="media[carouselCursor].type == 'image'" class="align-items-center px-2 pt-2">
|
||||||
<ul class="nav media-drawer-filters text-center">
|
<media-editor-filter-menu class="media-editor" :editor="editor" :sourceIndex="carouselCursor" />
|
||||||
<li class="nav-item">
|
|
||||||
<div class="p-1 pt-3">
|
|
||||||
<img :src="media[carouselCursor].url" width="100px" height="60px" v-on:click.prevent="toggleFilter($event, null)" class="cursor-pointer">
|
|
||||||
</div>
|
|
||||||
<a :class="[media[carouselCursor].filter_class == null ? 'nav-link text-primary active' : 'nav-link text-muted']" href="#" v-on:click.prevent="toggleFilter($event, null)">No Filter</a>
|
|
||||||
</li>
|
|
||||||
<li class="nav-item" v-for="(filter, index) in filters">
|
|
||||||
<div class="p-1 pt-3">
|
|
||||||
<img :src="media[carouselCursor].url" width="100px" height="60px" :class="filter[1]" v-on:click.prevent="toggleFilter($event, filter[1])">
|
|
||||||
</div>
|
|
||||||
<a :class="[media[carouselCursor].filter_class == filter[1] ? 'nav-link text-primary active' : 'nav-link text-muted']" href="#" v-on:click.prevent="toggleFilter($event, filter[1])">{{filter[0]}}</a>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div v-else>
|
<div v-else>
|
||||||
|
@ -427,7 +398,7 @@
|
||||||
<div v-else-if="page == 3" class="w-100 h-100">
|
<div v-else-if="page == 3" class="w-100 h-100">
|
||||||
<div class="border-bottom mt-2">
|
<div class="border-bottom mt-2">
|
||||||
<div class="media px-3">
|
<div class="media px-3">
|
||||||
<img :src="media[0].url" width="42px" height="42px" :class="[media[0].filter_class?'mr-2 ' + media[0].filter_class:'mr-2']">
|
<img :src="media[0].preview_url" width="42px" height="42px" class="mr-2">
|
||||||
<div class="media-body">
|
<div class="media-body">
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label class="font-weight-bold text-muted small d-none">Caption</label>
|
<label class="font-weight-bold text-muted small d-none">Caption</label>
|
||||||
|
@ -780,7 +751,7 @@
|
||||||
<div v-else-if="page == 'video-2'" class="w-100 h-100">
|
<div v-else-if="page == 'video-2'" class="w-100 h-100">
|
||||||
<div v-if="video.title.length" class="border-bottom">
|
<div v-if="video.title.length" class="border-bottom">
|
||||||
<div class="media p-3">
|
<div class="media p-3">
|
||||||
<img :src="media[0].url" width="100px" height="70px" :class="[media[0].filter_class?'mr-2 ' + media[0].filter_class:'mr-2']">
|
<img :src="media[0].preview_url" width="100px" height="70px" class="mr-2">
|
||||||
<div class="media-body">
|
<div class="media-body">
|
||||||
<p class="font-weight-bold mb-1">{{video.title ? video.title.slice(0,70) : 'Untitled'}}</p>
|
<p class="font-weight-bold mb-1">{{video.title ? video.title.slice(0,70) : 'Untitled'}}</p>
|
||||||
<p class="mb-0 text-muted small">{{video.description ? video.description.slice(0,90) : 'No description'}}</p>
|
<p class="mb-0 text-muted small">{{video.description ? video.description.slice(0,90) : 'No description'}}</p>
|
||||||
|
@ -839,13 +810,6 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div v-else-if="page == 'filteringMedia'" class="w-100 h-100 py-5">
|
|
||||||
<div class="d-flex flex-column align-items-center justify-content-center py-5">
|
|
||||||
<b-spinner small />
|
|
||||||
<p class="font-weight-bold mt-3">Applying filters...</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- card-footers -->
|
<!-- card-footers -->
|
||||||
|
@ -875,13 +839,17 @@ import 'cropperjs/dist/cropper.css';
|
||||||
import Autocomplete from '@trevoreyre/autocomplete-vue'
|
import Autocomplete from '@trevoreyre/autocomplete-vue'
|
||||||
import '@trevoreyre/autocomplete-vue/dist/style.css'
|
import '@trevoreyre/autocomplete-vue/dist/style.css'
|
||||||
import VueTribute from 'vue-tribute'
|
import VueTribute from 'vue-tribute'
|
||||||
|
import { MediaEditor, MediaEditorPreview, MediaEditorFilterMenu } from 'webgl-media-editor/vue2'
|
||||||
|
import { filterEffects } from './filters';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
|
|
||||||
components: {
|
components: {
|
||||||
VueCropper,
|
VueCropper,
|
||||||
Autocomplete,
|
Autocomplete,
|
||||||
VueTribute
|
VueTribute,
|
||||||
|
MediaEditorPreview,
|
||||||
|
MediaEditorFilterMenu
|
||||||
},
|
},
|
||||||
|
|
||||||
data() {
|
data() {
|
||||||
|
@ -892,10 +860,9 @@ export default {
|
||||||
composeText: '',
|
composeText: '',
|
||||||
composeTextLength: 0,
|
composeTextLength: 0,
|
||||||
nsfw: false,
|
nsfw: false,
|
||||||
filters: [],
|
|
||||||
currentFilter: false,
|
|
||||||
ids: [],
|
ids: [],
|
||||||
media: [],
|
media: [],
|
||||||
|
files: [],
|
||||||
carouselCursor: 0,
|
carouselCursor: 0,
|
||||||
uploading: false,
|
uploading: false,
|
||||||
uploadProgress: 100,
|
uploadProgress: 100,
|
||||||
|
@ -923,7 +890,6 @@ export default {
|
||||||
},
|
},
|
||||||
|
|
||||||
namedPages: [
|
namedPages: [
|
||||||
'filteringMedia',
|
|
||||||
'cropPhoto',
|
'cropPhoto',
|
||||||
'tagPeople',
|
'tagPeople',
|
||||||
'addLocation',
|
'addLocation',
|
||||||
|
@ -1044,13 +1010,26 @@ export default {
|
||||||
collectionsPage: 1,
|
collectionsPage: 1,
|
||||||
collectionsCanLoadMore: false,
|
collectionsCanLoadMore: false,
|
||||||
spoilerText: undefined,
|
spoilerText: undefined,
|
||||||
isFilteringMedia: false,
|
|
||||||
filteringMediaTimeout: undefined,
|
|
||||||
filteringRemainingCount: 0,
|
|
||||||
isPosting: false,
|
isPosting: false,
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
created() {
|
||||||
|
this.editor = new MediaEditor({
|
||||||
|
effects: filterEffects,
|
||||||
|
onEdit: (index, {effect, intensity, crop}) => {
|
||||||
|
if (index >= this.files.length) return
|
||||||
|
const file = this.files[index]
|
||||||
|
|
||||||
|
this.$set(file, 'editState', { effect, intensity, crop })
|
||||||
|
},
|
||||||
|
onRenderPreview: (sourceIndex, previewUrl) => {
|
||||||
|
const media = this.media[sourceIndex]
|
||||||
|
if (media) media.preview_url = previewUrl
|
||||||
|
},
|
||||||
|
})
|
||||||
|
},
|
||||||
|
|
||||||
computed: {
|
computed: {
|
||||||
spoilerTextLength: function() {
|
spoilerTextLength: function() {
|
||||||
return this.spoilerText ? this.spoilerText.length : 0;
|
return this.spoilerText ? this.spoilerText.length : 0;
|
||||||
|
@ -1058,7 +1037,6 @@ export default {
|
||||||
},
|
},
|
||||||
|
|
||||||
beforeMount() {
|
beforeMount() {
|
||||||
this.filters = window.App.util.filters.sort();
|
|
||||||
axios.get('/api/compose/v0/settings')
|
axios.get('/api/compose/v0/settings')
|
||||||
.then(res => {
|
.then(res => {
|
||||||
this.composeSettings = res.data;
|
this.composeSettings = res.data;
|
||||||
|
@ -1075,8 +1053,12 @@ export default {
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
mounted() {
|
destroyed() {
|
||||||
this.mediaWatcher();
|
this.files.forEach(fileInfo => {
|
||||||
|
URL.revokeObjectURL(fileInfo.url);
|
||||||
|
})
|
||||||
|
this.files.length = this.media.length = 0
|
||||||
|
this.editor = undefined
|
||||||
},
|
},
|
||||||
|
|
||||||
methods: {
|
methods: {
|
||||||
|
@ -1156,39 +1138,55 @@ export default {
|
||||||
this.mode = 'text';
|
this.mode = 'text';
|
||||||
},
|
},
|
||||||
|
|
||||||
mediaWatcher() {
|
onInputFile(event) {
|
||||||
let self = this;
|
const input = event.target
|
||||||
$(document).on('change', '#pf-dz', function(e) {
|
const files = Array.from(input.files)
|
||||||
self.mediaUpload();
|
input.value = null;
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
mediaUpload() {
|
|
||||||
let self = this;
|
let self = this;
|
||||||
self.uploading = true;
|
|
||||||
let io = document.querySelector('#pf-dz');
|
files.forEach((file, i) => {
|
||||||
if(!io.files.length) {
|
if(self.media && self.media.length >= self.config.uploader.album_limit) {
|
||||||
self.uploading = false;
|
|
||||||
}
|
|
||||||
Array.prototype.forEach.call(io.files, function(io, i) {
|
|
||||||
if(self.media && self.media.length + i >= self.config.uploader.album_limit) {
|
|
||||||
swal('Error', 'You can only upload ' + self.config.uploader.album_limit + ' photos per album', 'error');
|
swal('Error', 'You can only upload ' + self.config.uploader.album_limit + ' photos per album', 'error');
|
||||||
self.uploading = false;
|
|
||||||
self.page = 2;
|
self.page = 2;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
let type = io.type;
|
|
||||||
let acceptedMimes = self.config.uploader.media_types.split(',');
|
let acceptedMimes = self.config.uploader.media_types.split(',');
|
||||||
let validated = $.inArray(type, acceptedMimes);
|
let validated = $.inArray(file.type, acceptedMimes);
|
||||||
if(validated == -1) {
|
if(validated == -1) {
|
||||||
swal('Invalid File Type', 'The file you are trying to add is not a valid mime type. Please upload a '+self.config.uploader.media_types+' only.', 'error');
|
swal('Invalid File Type', 'The file you are trying to add is not a valid mime type. Please upload a '+self.config.uploader.media_types+' only.', 'error');
|
||||||
self.uploading = false;
|
|
||||||
self.page = 2;
|
self.page = 2;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const type = file.type.replace(/\/.*/, '')
|
||||||
|
const url = URL.createObjectURL(file)
|
||||||
|
const preview_url = type === 'image' ? url : '/storage/no-preview.png'
|
||||||
|
|
||||||
|
this.files.push({ file, editState: undefined })
|
||||||
|
this.media.push({ url, preview_url, type })
|
||||||
|
})
|
||||||
|
|
||||||
|
if (this.media.length) {
|
||||||
|
this.page = 3
|
||||||
|
} else {
|
||||||
|
this.page = 2
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
async mediaUpload() {
|
||||||
|
this.uploading = true;
|
||||||
|
|
||||||
|
const uploadPromises = this.files.map(async (fileInfo, i) => {
|
||||||
|
let file = fileInfo.file
|
||||||
|
const media = this.media[i]
|
||||||
|
|
||||||
|
if (media.type === 'image' && fileInfo.editState) {
|
||||||
|
file = await this.editor.toBlob(i)
|
||||||
|
}
|
||||||
|
|
||||||
let form = new FormData();
|
let form = new FormData();
|
||||||
form.append('file', io);
|
form.append('file', file);
|
||||||
|
|
||||||
let xhrConfig = {
|
let xhrConfig = {
|
||||||
onUploadProgress: function(e) {
|
onUploadProgress: function(e) {
|
||||||
|
@ -1197,12 +1195,13 @@ export default {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
axios.post('/api/compose/v0/media/upload', form, xhrConfig)
|
const self = this
|
||||||
|
|
||||||
|
await axios.post('/api/compose/v0/media/upload', form, xhrConfig)
|
||||||
.then(function(e) {
|
.then(function(e) {
|
||||||
self.uploadProgress = 100;
|
self.uploadProgress = 100;
|
||||||
self.ids.push(e.data.id);
|
self.ids.push(e.data.id);
|
||||||
self.media.push(e.data);
|
Object.assign(media, e.data)
|
||||||
self.uploading = false;
|
|
||||||
setTimeout(function() {
|
setTimeout(function() {
|
||||||
// if(type === 'video/mp4') {
|
// if(type === 'video/mp4') {
|
||||||
// self.pageTitle = 'Edit Video Details';
|
// self.pageTitle = 'Edit Video Details';
|
||||||
|
@ -1216,131 +1215,100 @@ export default {
|
||||||
}).catch(function(e) {
|
}).catch(function(e) {
|
||||||
switch(e.response.status) {
|
switch(e.response.status) {
|
||||||
case 403:
|
case 403:
|
||||||
self.uploading = false;
|
|
||||||
io.value = null;
|
|
||||||
swal('Account size limit reached', 'Contact your admin for assistance.', 'error');
|
swal('Account size limit reached', 'Contact your admin for assistance.', 'error');
|
||||||
self.page = 2;
|
self.page = 2;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 413:
|
case 413:
|
||||||
self.uploading = false;
|
swal('File is too large', 'The file you uploaded has the size of ' + self.formatBytes(file.size) + '. Unfortunately, only images up to ' + self.formatBytes(self.config.uploader.max_photo_size * 1024) + ' are supported.\nPlease resize the file and try again.', 'error');
|
||||||
io.value = null;
|
|
||||||
swal('File is too large', 'The file you uploaded has the size of ' + self.formatBytes(io.size) + '. Unfortunately, only images up to ' + self.formatBytes(self.config.uploader.max_photo_size * 1024) + ' are supported.\nPlease resize the file and try again.', 'error');
|
|
||||||
self.page = 2;
|
self.page = 2;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 451:
|
case 451:
|
||||||
self.uploading = false;
|
|
||||||
io.value = null;
|
|
||||||
swal('Banned Content', 'This content has been banned and cannot be uploaded.', 'error');
|
swal('Banned Content', 'This content has been banned and cannot be uploaded.', 'error');
|
||||||
self.page = 2;
|
self.page = 2;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 429:
|
case 429:
|
||||||
self.uploading = false;
|
|
||||||
io.value = null;
|
|
||||||
swal('Limit Reached', 'You can upload up to 250 photos or videos per day and you\'ve reached that limit. Please try again later.', 'error');
|
swal('Limit Reached', 'You can upload up to 250 photos or videos per day and you\'ve reached that limit. Please try again later.', 'error');
|
||||||
self.page = 2;
|
self.page = 2;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 500:
|
case 500:
|
||||||
self.uploading = false;
|
|
||||||
io.value = null;
|
|
||||||
swal('Error', e.response.data.message, 'error');
|
swal('Error', e.response.data.message, 'error');
|
||||||
self.page = 2;
|
self.page = 2;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
self.uploading = false;
|
|
||||||
io.value = null;
|
|
||||||
swal('Oops, something went wrong!', 'An unexpected error occurred.', 'error');
|
swal('Oops, something went wrong!', 'An unexpected error occurred.', 'error');
|
||||||
self.page = 2;
|
self.page = 2;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
throw e
|
||||||
});
|
});
|
||||||
io.value = null;
|
|
||||||
self.uploadProgress = 0;
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
await Promise.all(uploadPromises).finally(() => {
|
||||||
|
this.uploadProgress = 0;
|
||||||
|
this.uploading = false;
|
||||||
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
toggleFilter(e, filter) {
|
async deleteMedia() {
|
||||||
this.media[this.carouselCursor].filter_class = filter;
|
|
||||||
this.currentFilter = filter;
|
|
||||||
},
|
|
||||||
|
|
||||||
deleteMedia() {
|
|
||||||
if(window.confirm('Are you sure you want to delete this media?') == false) {
|
if(window.confirm('Are you sure you want to delete this media?') == false) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
let id = this.media[this.carouselCursor].id;
|
let id = this.media[this.carouselCursor].id;
|
||||||
|
|
||||||
axios.delete('/api/compose/v0/media/delete', {
|
if (id) {
|
||||||
params: {
|
try {
|
||||||
id: id
|
await axios.delete('/api/compose/v0/media/delete', {
|
||||||
}
|
params: {
|
||||||
}).then(res => {
|
id: id
|
||||||
this.ids.splice(this.carouselCursor, 1);
|
}
|
||||||
this.media.splice(this.carouselCursor, 1);
|
})
|
||||||
if(this.media.length == 0) {
|
}
|
||||||
this.ids = [];
|
catch(err) {
|
||||||
this.media = [];
|
swal('Whoops!', 'An error occured when attempting to delete this, please try again', 'error');
|
||||||
this.carouselCursor = 0;
|
return
|
||||||
} else {
|
}
|
||||||
this.carouselCursor = 0;
|
}
|
||||||
}
|
this.ids.splice(this.carouselCursor, 1);
|
||||||
}).catch(err => {
|
this.media.splice(this.carouselCursor, 1);
|
||||||
swal('Whoops!', 'An error occured when attempting to delete this, please try again', 'error');
|
|
||||||
});
|
URL.revokeObjectURL(this.files[this.carouselCursor]?.url)
|
||||||
|
this.files.splice(this.carouselCursor, 1)
|
||||||
|
|
||||||
|
if(this.media.length == 0) {
|
||||||
|
this.ids = [];
|
||||||
|
this.media = [];
|
||||||
|
this.carouselCursor = 0;
|
||||||
|
} else {
|
||||||
|
this.carouselCursor = 0;
|
||||||
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
mediaReorder(dir) {
|
mediaReorder(dir) {
|
||||||
const m = this.media;
|
const prevIndex = this.carouselCursor
|
||||||
const cur = this.carouselCursor;
|
const newIndex = prevIndex + (dir === 'prev' ? -1 : 1)
|
||||||
const pla = m[cur];
|
|
||||||
let res = [];
|
|
||||||
let cursor = 0;
|
|
||||||
|
|
||||||
if(dir == 'prev') {
|
if (newIndex < 0 || newIndex >= this.media.length) return
|
||||||
if(cur == 0) {
|
|
||||||
for (let i = cursor; i < m.length - 1; i++) {
|
const [removedFile] = this.files.splice(prevIndex, 1)
|
||||||
res[i] = m[i+1];
|
const [removedMedia] = this.media.splice(prevIndex, 1)
|
||||||
}
|
const [removedId] = this.ids.splice(prevIndex, 1)
|
||||||
res[m.length - 1] = pla;
|
|
||||||
cursor = 0;
|
this.files.splice(newIndex, 0, removedFile)
|
||||||
} else {
|
this.media.splice(newIndex, 0, removedMedia)
|
||||||
res = this.handleSwap(m, cur, cur - 1);
|
this.ids.splice(newIndex, 0, removedId)
|
||||||
cursor = cur - 1;
|
this.carouselCursor = newIndex
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if(cur == m.length - 1) {
|
|
||||||
res = m;
|
|
||||||
let lastItem = res.pop();
|
|
||||||
res.unshift(lastItem);
|
|
||||||
cursor = m.length - 1;
|
|
||||||
} else {
|
|
||||||
res = this.handleSwap(m, cur, cur + 1);
|
|
||||||
cursor = cur + 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
this.$nextTick(() => {
|
|
||||||
this.media = res;
|
|
||||||
this.carouselCursor = cursor;
|
|
||||||
})
|
|
||||||
},
|
},
|
||||||
|
|
||||||
handleSwap(arr, index1, index2) {
|
async compose() {
|
||||||
if (index1 >= 0 && index1 < arr.length && index2 >= 0 && index2 < arr.length) {
|
|
||||||
const temp = arr[index1];
|
|
||||||
arr[index1] = arr[index2];
|
|
||||||
arr[index2] = temp;
|
|
||||||
return arr;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
compose() {
|
|
||||||
let state = this.composeState;
|
let state = this.composeState;
|
||||||
|
|
||||||
if(this.uploadProgress != 100 || this.ids.length == 0) {
|
if(this.files.length == 0) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1353,11 +1321,14 @@ export default {
|
||||||
switch(state) {
|
switch(state) {
|
||||||
case 'publish':
|
case 'publish':
|
||||||
this.isPosting = true;
|
this.isPosting = true;
|
||||||
let count = this.media.filter(m => m.filter_class && !m.hasOwnProperty('is_filtered')).length;
|
|
||||||
if(count) {
|
try {
|
||||||
this.applyFilterToMedia();
|
await this.mediaUpload().finally(() => this.isPosting = false)
|
||||||
return;
|
} catch {
|
||||||
|
this.isPosting = false;
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if(this.composeSettings.media_descriptions === true) {
|
if(this.composeSettings.media_descriptions === true) {
|
||||||
let count = this.media.filter(m => {
|
let count = this.media.filter(m => {
|
||||||
return !m.hasOwnProperty('alt') || m.alt.length < 2;
|
return !m.hasOwnProperty('alt') || m.alt.length < 2;
|
||||||
|
@ -1420,6 +1391,8 @@ export default {
|
||||||
this.defineErrorMessage(err);
|
this.defineErrorMessage(err);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
}).finally(() => {
|
||||||
|
this.isPosting = false;
|
||||||
});
|
});
|
||||||
return;
|
return;
|
||||||
break;
|
break;
|
||||||
|
@ -1488,10 +1461,6 @@ export default {
|
||||||
switch(this.mode) {
|
switch(this.mode) {
|
||||||
case 'photo':
|
case 'photo':
|
||||||
switch(this.page) {
|
switch(this.page) {
|
||||||
case 'filteringMedia':
|
|
||||||
this.page = 2;
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 'addText':
|
case 'addText':
|
||||||
this.page = 1;
|
this.page = 1;
|
||||||
break;
|
break;
|
||||||
|
@ -1526,10 +1495,6 @@ export default {
|
||||||
|
|
||||||
case 'video':
|
case 'video':
|
||||||
switch(this.page) {
|
switch(this.page) {
|
||||||
case 'filteringMedia':
|
|
||||||
this.page = 2;
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 'licensePicker':
|
case 'licensePicker':
|
||||||
this.page = 'video-2';
|
this.page = 'video-2';
|
||||||
break;
|
break;
|
||||||
|
@ -1550,10 +1515,6 @@ export default {
|
||||||
this.page = 1;
|
this.page = 1;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 'filteringMedia':
|
|
||||||
this.page = 2;
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 'textOptions':
|
case 'textOptions':
|
||||||
this.page = 'addText';
|
this.page = 'addText';
|
||||||
break;
|
break;
|
||||||
|
@ -1593,31 +1554,14 @@ export default {
|
||||||
this.page = 2;
|
this.page = 2;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 'filteringMedia':
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 'cropPhoto':
|
case 'cropPhoto':
|
||||||
this.pageLoading = true;
|
const { editState } = this.files[this.carouselCursor]
|
||||||
let self = this;
|
const croppedState = {
|
||||||
this.$refs.cropper.getCroppedCanvas({
|
...editState,
|
||||||
maxWidth: 4096,
|
crop: this.$refs.cropper.getData()
|
||||||
maxHeight: 4096,
|
}
|
||||||
fillColor: '#fff',
|
this.editor.setEditState(this.carouselCursor, croppedState)
|
||||||
imageSmoothingEnabled: false,
|
this.page = 2;
|
||||||
imageSmoothingQuality: 'high',
|
|
||||||
}).toBlob(function(blob) {
|
|
||||||
self.mediaCropped = true;
|
|
||||||
let data = new FormData();
|
|
||||||
data.append('file', blob);
|
|
||||||
data.append('id', self.ids[self.carouselCursor]);
|
|
||||||
let url = '/api/compose/v0/media/update';
|
|
||||||
axios.post(url, data).then(res => {
|
|
||||||
self.media[self.carouselCursor].url = res.data.url;
|
|
||||||
self.pageLoading = false;
|
|
||||||
self.page = 2;
|
|
||||||
}).catch(err => {
|
|
||||||
});
|
|
||||||
});
|
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 2:
|
case 2:
|
||||||
|
@ -1764,111 +1708,6 @@ export default {
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
applyFilterToMedia() {
|
|
||||||
// this is where the magic happens
|
|
||||||
let count = this.media.filter(m => m.filter_class).length;
|
|
||||||
if(count) {
|
|
||||||
this.page = 'filteringMedia';
|
|
||||||
this.filteringRemainingCount = count;
|
|
||||||
this.$nextTick(() => {
|
|
||||||
this.isFilteringMedia = true;
|
|
||||||
Promise.all(this.media.map(media => {
|
|
||||||
return this.applyFilterToMediaSave(media);
|
|
||||||
})).catch(err => {
|
|
||||||
console.error(err);
|
|
||||||
swal('Oops!', 'An error occurred while applying filters to your media. Please refresh the page and try again. If the problem persist, please try a different web browser.', 'error');
|
|
||||||
});
|
|
||||||
})
|
|
||||||
} else {
|
|
||||||
this.page = 3;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
async applyFilterToMediaSave(media) {
|
|
||||||
if(!media.filter_class) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Load image
|
|
||||||
const image = document.createElement('img');
|
|
||||||
image.src = media.url;
|
|
||||||
await new Promise((resolve, reject) => {
|
|
||||||
image.addEventListener('load', () => resolve());
|
|
||||||
image.addEventListener('error', () => {
|
|
||||||
reject(new Error('Failed to load image'));
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
// Create canvas
|
|
||||||
let canvas;
|
|
||||||
let usingOffscreenCanvas = false;
|
|
||||||
if('OffscreenCanvas' in window) {
|
|
||||||
canvas = new OffscreenCanvas(image.width, image.height);
|
|
||||||
usingOffscreenCanvas = true;
|
|
||||||
} else {
|
|
||||||
canvas = document.createElement('canvas');
|
|
||||||
canvas.width = image.width;
|
|
||||||
canvas.height = image.height;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Draw image with filter to canvas
|
|
||||||
const ctx = canvas.getContext('2d');
|
|
||||||
if (!ctx) {
|
|
||||||
throw new Error('Failed to get canvas context');
|
|
||||||
}
|
|
||||||
if (!('filter' in ctx)) {
|
|
||||||
throw new Error('Canvas filter not supported');
|
|
||||||
}
|
|
||||||
ctx.filter = App.util.filterCss[media.filter_class];
|
|
||||||
ctx.drawImage(image, 0, 0, image.width, image.height);
|
|
||||||
ctx.save();
|
|
||||||
|
|
||||||
// Convert canvas to blob
|
|
||||||
let blob;
|
|
||||||
if(usingOffscreenCanvas) {
|
|
||||||
blob = await canvas.convertToBlob({
|
|
||||||
type: media.mime,
|
|
||||||
quality: 1,
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
blob = await new Promise((resolve, reject) => {
|
|
||||||
canvas.toBlob(blob => {
|
|
||||||
if(blob) {
|
|
||||||
resolve(blob);
|
|
||||||
} else {
|
|
||||||
reject(
|
|
||||||
new Error('Failed to convert canvas to blob'),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}, media.mime, 1);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// Upload blob / Update media
|
|
||||||
const data = new FormData();
|
|
||||||
data.append('file', blob);
|
|
||||||
data.append('id', media.id);
|
|
||||||
await axios.post('/api/compose/v0/media/update', data);
|
|
||||||
media.is_filtered = true;
|
|
||||||
this.updateFilteringMedia();
|
|
||||||
},
|
|
||||||
|
|
||||||
updateFilteringMedia() {
|
|
||||||
this.filteringRemainingCount--;
|
|
||||||
this.filteringMediaTimeout = setTimeout(() => this.filteringMediaTimeoutJob(), 500);
|
|
||||||
},
|
|
||||||
|
|
||||||
filteringMediaTimeoutJob() {
|
|
||||||
if(this.filteringRemainingCount === 0) {
|
|
||||||
this.isFilteringMedia = false;
|
|
||||||
clearTimeout(this.filteringMediaTimeout);
|
|
||||||
setTimeout(() => this.compose(), 500);
|
|
||||||
} else {
|
|
||||||
clearTimeout(this.filteringMediaTimeout);
|
|
||||||
this.filteringMediaTimeout = setTimeout(() => this.filteringMediaTimeoutJob(), 1000);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
tagSearch(input) {
|
tagSearch(input) {
|
||||||
if (input.length < 1) { return []; }
|
if (input.length < 1) { return []; }
|
||||||
let self = this;
|
let self = this;
|
||||||
|
@ -2059,6 +1898,11 @@ export default {
|
||||||
this.collectionsCanLoadMore = true;
|
this.collectionsCanLoadMore = true;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
files(value) {
|
||||||
|
this.editor.setSources(value.map(f => f.file))
|
||||||
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
@ -2111,5 +1955,34 @@ export default {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
.media-editor {
|
||||||
|
background-color: transparent;
|
||||||
|
border: none !important;
|
||||||
|
box-shadow: none !important;
|
||||||
|
font-size: 12px;
|
||||||
|
|
||||||
|
--height-menu-row: 5rem;
|
||||||
|
--gap-preview: 0rem;
|
||||||
|
--height-menu-row-scroll: 10rem;
|
||||||
|
|
||||||
|
--color-bg-button: transparent; /*var(--light);*/
|
||||||
|
--color-bg-preview: transparent; /*var(--light-gray);*/
|
||||||
|
--color-bg-button-hover: var(--light-gray);
|
||||||
|
--color-bg-acc: var(--card-bg);
|
||||||
|
|
||||||
|
--color-fnt-default: var(--body-color);
|
||||||
|
--color-fnt-acc: var(--text-lighter);
|
||||||
|
|
||||||
|
--color-scrollbar-thumb: var(--light-gray);
|
||||||
|
--color-scrollbar-both: var(--light-gray) transparent;
|
||||||
|
|
||||||
|
--color-slider-thumb: var(--text-lighter);
|
||||||
|
--color-slider-progress: var(--light-gray);
|
||||||
|
--color-slider-track: var(--light);
|
||||||
|
|
||||||
|
--color-crop-outline: var(--light-gray);
|
||||||
|
--color-crop-dashed: #ffffffde;
|
||||||
|
--color-crop-overlay: #00000082;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
290
resources/assets/js/components/filters.js
vendored
Normal file
290
resources/assets/js/components/filters.js
vendored
Normal file
|
@ -0,0 +1,290 @@
|
||||||
|
export const filterEffects = [
|
||||||
|
{
|
||||||
|
name: '1984',
|
||||||
|
ops: [
|
||||||
|
{ type: 'sepia', intensity: 0.5 },
|
||||||
|
{ type: 'hue_rotate', angle: -30 },
|
||||||
|
{ type: 'adjust_color', brightness: 0, contrast: 0, saturation: 0.4 },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Azen',
|
||||||
|
ops: [
|
||||||
|
{ type: 'sepia', intensity: 0.2 },
|
||||||
|
{ type: 'adjust_color', brightness: 0.15, contrast: 0, saturation: 0.4 },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Astairo',
|
||||||
|
ops: [
|
||||||
|
{ type: 'sepia', intensity: 0.35 },
|
||||||
|
{ type: 'adjust_color', brightness: 0.2, contrast: 0.1, saturation: 0.3 },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Grasbee',
|
||||||
|
ops: [
|
||||||
|
{ type: 'sepia', intensity: 0.5 },
|
||||||
|
{ type: 'adjust_color', brightness: 0, contrast: 0.2, saturation: 0.8 },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Bookrun',
|
||||||
|
ops: [
|
||||||
|
{ type: 'sepia', intensity: 0.4 },
|
||||||
|
{ type: 'adjust_color', brightness: 0.1, contrast: 0.25, saturation: -0.1 },
|
||||||
|
{ type: 'hue_rotate', angle: -2 },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Borough',
|
||||||
|
ops: [
|
||||||
|
{ type: 'sepia', intensity: 0.25 },
|
||||||
|
{ type: 'adjust_color', brightness: 0.25, contrast: 0.25, saturation: 0 },
|
||||||
|
{ type: 'hue_rotate', angle: 5 },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Farms',
|
||||||
|
ops: [
|
||||||
|
{ type: 'sepia', intensity: 0.25 },
|
||||||
|
{ type: 'adjust_color', brightness: 0.25, contrast: 0.25, saturation: 0.35 },
|
||||||
|
{ type: 'hue_rotate', angle: -5 },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Hairsadone',
|
||||||
|
ops: [
|
||||||
|
{ type: 'sepia', intensity: 0.15 },
|
||||||
|
{ type: 'adjust_color', brightness: 0.25, contrast: 0.25, saturation: 0 },
|
||||||
|
{ type: 'hue_rotate', angle: 5 },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Cleana',
|
||||||
|
ops: [
|
||||||
|
{ type: 'sepia', intensity: 0.5 },
|
||||||
|
{ type: 'adjust_color', brightness: 0.25, contrast: 0.15, saturation: -0.1 },
|
||||||
|
{ type: 'hue_rotate', angle: -2 },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Catpatch',
|
||||||
|
ops: [
|
||||||
|
{ type: 'sepia', intensity: 0.35 },
|
||||||
|
{ type: 'adjust_color', brightness: 0, contrast: .5, saturation: 0.1 },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Earlyworm',
|
||||||
|
ops: [
|
||||||
|
{ type: 'sepia', intensity: 0.25 },
|
||||||
|
{ type: 'adjust_color', brightness: 0.25, contrast: 0.15, saturation: -0.1 },
|
||||||
|
{ type: 'hue_rotate', angle: -5 },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Plaid',
|
||||||
|
ops: [{ type: 'adjust_color', brightness: 0.1, contrast: 0.1, saturation: 0 }],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Kyo',
|
||||||
|
ops: [
|
||||||
|
{ type: 'sepia', intensity: 0.25 },
|
||||||
|
{ type: 'adjust_color', brightness: 0.2, contrast: 0.15, saturation: 0.35 },
|
||||||
|
{ type: 'hue_rotate', angle: -5 },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Yefe',
|
||||||
|
ops: [
|
||||||
|
{ type: 'sepia', intensity: 0.4 },
|
||||||
|
{ type: 'adjust_color', brightness: 0.2, contrast: 0.5, saturation: 0.4 },
|
||||||
|
{ type: 'hue_rotate', angle: -10 },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Godess',
|
||||||
|
ops: [
|
||||||
|
{ type: 'sepia', intensity: 0.5 },
|
||||||
|
{ type: 'adjust_color', brightness: 0.05, contrast: 0.05, saturation: 0.35 },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Yards',
|
||||||
|
ops: [
|
||||||
|
{ type: 'sepia', intensity: 0.25 },
|
||||||
|
{ type: 'adjust_color', brightness: 0.2, contrast: 0.2, saturation: 0.05 },
|
||||||
|
{ type: 'hue_rotate', angle: -15 },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Quill',
|
||||||
|
ops: [{ type: 'adjust_color', brightness: 0.25, contrast: -0.15, saturation: -1 }],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Juno',
|
||||||
|
ops: [
|
||||||
|
{ type: 'sepia', intensity: 0.35 },
|
||||||
|
{ type: 'adjust_color', brightness: 0.15, contrast: 0.15, saturation: 0.8 },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Rankine',
|
||||||
|
ops: [
|
||||||
|
{ type: 'sepia', intensity: 0.15 },
|
||||||
|
{ type: 'adjust_color', brightness: 0.1, contrast: 0.5, saturation: 0 },
|
||||||
|
{ type: 'hue_rotate', angle: -10 },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Mark',
|
||||||
|
ops: [
|
||||||
|
{ type: 'sepia', intensity: 0.25 },
|
||||||
|
{ type: 'adjust_color', brightness: 0.3, contrast: 0.2, saturation: 0.25 },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Chill',
|
||||||
|
ops: [{ type: 'adjust_color', brightness: 0, contrast: 0.5, saturation: 0.1 }],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Van',
|
||||||
|
ops: [
|
||||||
|
{ type: 'sepia', intensity: 0.25 },
|
||||||
|
{ type: 'adjust_color', brightness: 0.05, contrast: 0.05, saturation: 1 },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Apache',
|
||||||
|
ops: [
|
||||||
|
{ type: 'sepia', intensity: 0.35 },
|
||||||
|
{ type: 'adjust_color', brightness: 0.05, contrast: 0.05, saturation: 0.75 },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'May',
|
||||||
|
ops: [{ type: 'adjust_color', brightness: 0.15, contrast: 0.1, saturation: 0.1 }],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Ceres',
|
||||||
|
ops: [
|
||||||
|
{ type: 'adjust_color', brightness: 0.4, contrast: -0.05, saturation: -1 },
|
||||||
|
{ type: 'sepia', intensity: 0.35 },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Knoxville',
|
||||||
|
ops: [
|
||||||
|
{ type: 'sepia', intensity: 0.25 },
|
||||||
|
{ type: 'adjust_color', brightness: -0.1, contrast: 0.5, saturation: 0 },
|
||||||
|
{ type: 'hue_rotate', angle: -15 },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Felicity',
|
||||||
|
ops: [{ type: 'adjust_color', brightness: 0.25, contrast: 0.1, saturation: 0.1 }],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Sandblast',
|
||||||
|
ops: [
|
||||||
|
{ type: 'sepia', intensity: 0.15 },
|
||||||
|
{ type: 'adjust_color', brightness: 0.2, contrast: 0, saturation: 0 },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Daisy',
|
||||||
|
ops: [
|
||||||
|
{ type: 'sepia', intensity: 0.75 },
|
||||||
|
{ type: 'adjust_color', brightness: 0.25, contrast: -0.25, saturation: 0.4 },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Elevate',
|
||||||
|
ops: [
|
||||||
|
{ type: 'sepia', intensity: 0.25 },
|
||||||
|
{ type: 'adjust_color', brightness: 0.2, contrast: 0.25, saturation: -0.1 },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Nevada',
|
||||||
|
ops: [
|
||||||
|
{ type: 'sepia', intensity: 0.25 },
|
||||||
|
{ type: 'adjust_color', brightness: -0.1, contrast: 0.5, saturation: 0 },
|
||||||
|
{ type: 'hue_rotate', angle: -15 },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Futura',
|
||||||
|
ops: [
|
||||||
|
{ type: 'sepia', intensity: 0.15 },
|
||||||
|
{ type: 'adjust_color', brightness: 0.25, contrast: 0.25, saturation: 0.2 },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Sleepy',
|
||||||
|
ops: [
|
||||||
|
{ type: 'sepia', intensity: 0.15 },
|
||||||
|
{ type: 'adjust_color', brightness: 0.25, contrast: 0.25, saturation: 0.2 },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Steward',
|
||||||
|
ops: [
|
||||||
|
{ type: 'sepia', intensity: 0.35 },
|
||||||
|
{ type: 'adjust_color', brightness: 0.25, contrast: 0.1, saturation: 0.25 },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Savoy',
|
||||||
|
ops: [
|
||||||
|
{ type: 'sepia', intensity: 0.4 },
|
||||||
|
{ type: 'adjust_color', brightness: 0.2, contrast: -0.1, saturation: 0.4 },
|
||||||
|
{ type: 'hue_rotate', angle: -10 },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Blaze',
|
||||||
|
ops: [
|
||||||
|
{ type: 'sepia', intensity: 0.25 },
|
||||||
|
{ type: 'adjust_color', brightness: -0.05, contrast: 0.5, saturation: 0 },
|
||||||
|
{ type: 'hue_rotate', angle: -15 },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Apricot',
|
||||||
|
ops: [
|
||||||
|
{ type: 'sepia', intensity: 0.25 },
|
||||||
|
{ type: 'adjust_color', brightness: 0.1, contrast: 0.1, saturation: 0 },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Gloming',
|
||||||
|
ops: [
|
||||||
|
{ type: 'sepia', intensity: 0.35 },
|
||||||
|
{ type: 'adjust_color', brightness: 0.15, contrast: 0.2, saturation: 0.3 },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Walter',
|
||||||
|
ops: [
|
||||||
|
{ type: 'sepia', intensity: 0.35 },
|
||||||
|
{ type: 'adjust_color', brightness: 0.25, contrast: -0.2, saturation: 0.4 },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Poplar',
|
||||||
|
ops: [
|
||||||
|
{ type: 'adjust_color', brightness: 0.2, contrast: -0.15, saturation: -0.95 },
|
||||||
|
{ type: 'sepia', intensity: 0.5 },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Xenon',
|
||||||
|
ops: [
|
||||||
|
{ type: 'sepia', intensity: 0.45 },
|
||||||
|
{ type: 'adjust_color', brightness: 0.75, contrast: 0.25, saturation: 0.3 },
|
||||||
|
{ type: 'hue_rotate', angle: -5 },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
]
|
|
@ -6,32 +6,32 @@ return [
|
||||||
'comment' => 'Comentar',
|
'comment' => 'Comentar',
|
||||||
'commented' => 'Comentado',
|
'commented' => 'Comentado',
|
||||||
'comments' => 'Comentários',
|
'comments' => 'Comentários',
|
||||||
'like' => 'Curtir',
|
'like' => 'Gosto',
|
||||||
'liked' => 'Curtiu',
|
'liked' => 'Gostei',
|
||||||
'likes' => 'Curtidas',
|
'likes' => 'Gostos',
|
||||||
'share' => 'Compartilhar',
|
'share' => 'Partilhar',
|
||||||
'shared' => 'Compartilhado',
|
'shared' => 'Partilhado',
|
||||||
'shares' => 'Compartilhamentos',
|
'shares' => 'Partilhas',
|
||||||
'unshare' => 'Desfazer compartilhamento',
|
'unshare' => 'Despartilhar',
|
||||||
'bookmark' => 'Favoritar',
|
'bookmark' => 'Favorito',
|
||||||
|
|
||||||
'cancel' => 'Cancelar',
|
'cancel' => 'Cancelar',
|
||||||
'copyLink' => 'Copiar link',
|
'copyLink' => 'Copiar link',
|
||||||
'delete' => 'Apagar',
|
'delete' => 'Eliminar',
|
||||||
'error' => 'Erro',
|
'error' => 'Erro',
|
||||||
'errorMsg' => 'Algo deu errado. Por favor, tente novamente mais tarde.',
|
'errorMsg' => 'Algo correu mal. Por favor, tente novamente mais tarde.',
|
||||||
'oops' => 'Opa!',
|
'oops' => 'Oops!',
|
||||||
'other' => 'Outro',
|
'other' => 'Outro',
|
||||||
'readMore' => 'Leia mais',
|
'readMore' => 'Ler mais',
|
||||||
'success' => 'Sucesso',
|
'success' => 'Sucesso',
|
||||||
'proceed' => 'Continuar',
|
'proceed' => 'Continuar',
|
||||||
'next' => 'Próximo',
|
'next' => 'Seguinte',
|
||||||
'close' => 'Fechar',
|
'close' => 'Fechar',
|
||||||
'clickHere' => 'clique aqui',
|
'clickHere' => 'clique aqui',
|
||||||
|
|
||||||
'sensitive' => 'Sensível',
|
'sensitive' => 'Sensível',
|
||||||
'sensitiveContent' => 'Conteúdo sensível',
|
'sensitiveContent' => 'Conteúdo sensível',
|
||||||
'sensitiveContentWarning' => 'Esta publicação pode conter conteúdo inapropriado',
|
'sensitiveContentWarning' => 'Este post pode conter conteúdo sensível',
|
||||||
],
|
],
|
||||||
|
|
||||||
'site' => [
|
'site' => [
|
||||||
|
@ -40,27 +40,27 @@ return [
|
||||||
],
|
],
|
||||||
|
|
||||||
'navmenu' => [
|
'navmenu' => [
|
||||||
'search' => 'Pesquisar',
|
'search' => 'Pesquisa',
|
||||||
'admin' => 'Painel do Administrador',
|
'admin' => 'Painel de Administração',
|
||||||
|
|
||||||
// Timelines
|
// Timelines
|
||||||
'homeFeed' => 'Página inicial',
|
'homeFeed' => 'Inicio',
|
||||||
'localFeed' => 'Feed local',
|
'localFeed' => 'Feed local',
|
||||||
'globalFeed' => 'Feed global',
|
'globalFeed' => 'Feed global',
|
||||||
|
|
||||||
// Core features
|
// Core features
|
||||||
'discover' => 'Explorar',
|
'discover' => 'Descobrir',
|
||||||
'directMessages' => 'Mensagens privadas',
|
'directMessages' => 'Mensagens Diretas',
|
||||||
'notifications' => 'Notificações',
|
'notifications' => 'Notificações',
|
||||||
'groups' => 'Grupos',
|
'groups' => 'Grupos',
|
||||||
'stories' => 'Stories',
|
'stories' => 'Stories',
|
||||||
|
|
||||||
// Self links
|
// Self links
|
||||||
'profile' => 'Perfil',
|
'profile' => 'Perfil',
|
||||||
'drive' => 'Drive',
|
'drive' => 'Disco',
|
||||||
'settings' => 'Configurações',
|
'settings' => 'Definições',
|
||||||
'compose' => 'Criar novo',
|
'compose' => 'Criar novo',
|
||||||
'logout' => 'Sair',
|
'logout' => 'Terminar Sessão',
|
||||||
|
|
||||||
// Nav footer
|
// Nav footer
|
||||||
'about' => 'Sobre',
|
'about' => 'Sobre',
|
||||||
|
@ -70,139 +70,139 @@ return [
|
||||||
'terms' => 'Termos',
|
'terms' => 'Termos',
|
||||||
|
|
||||||
// Temporary links
|
// Temporary links
|
||||||
'backToPreviousDesign' => 'Voltar ao design anterior'
|
'backToPreviousDesign' => 'Voltar ao design antigo'
|
||||||
],
|
],
|
||||||
|
|
||||||
'directMessages' => [
|
'directMessages' => [
|
||||||
'inbox' => 'Caixa de entrada',
|
'inbox' => 'Caixa de Entrada',
|
||||||
'sent' => 'Enviadas',
|
'sent' => 'Enviadas',
|
||||||
'requests' => 'Solicitações'
|
'requests' => 'Pedidos'
|
||||||
],
|
],
|
||||||
|
|
||||||
'notifications' => [
|
'notifications' => [
|
||||||
'liked' => 'curtiu seu',
|
'liked' => 'gostou do seu',
|
||||||
'commented' => 'comentou em seu',
|
'commented' => 'comentou no seu',
|
||||||
'reacted' => 'reagiu ao seu',
|
'reacted' => 'reagiu ao seu',
|
||||||
'shared' => 'compartilhou seu',
|
'shared' => 'Partilhou o seu',
|
||||||
'tagged' => 'marcou você em um',
|
'tagged' => 'marcou você numa publicação',
|
||||||
|
|
||||||
'updatedA' => 'atualizou um(a)',
|
'updatedA' => 'atualizou',
|
||||||
'sentA' => 'enviou um',
|
'sentA' => 'enviou um',
|
||||||
|
|
||||||
'followed' => 'seguiu',
|
'followed' => 'seguiu',
|
||||||
'mentioned' => 'mencionou',
|
'mentioned' => 'mencionou',
|
||||||
'you' => 'você',
|
'you' => 'você',
|
||||||
|
|
||||||
'yourApplication' => 'Sua inscrição para participar',
|
'yourApplication' => 'A sua candidatura para se juntar',
|
||||||
'applicationApproved' => 'foi aprovado!',
|
'applicationApproved' => 'foi aprovado!',
|
||||||
'applicationRejected' => 'foi rejeitado. Você pode se inscrever novamente para participar em 6 meses.',
|
'applicationRejected' => 'foi rejeitado. Você pode inscrever-se novamente em 6 meses.',
|
||||||
|
|
||||||
'dm' => 'mensagem direta',
|
'dm' => 'dm',
|
||||||
'groupPost' => 'postagem do grupo',
|
'groupPost' => 'publicação de grupo',
|
||||||
'modlog' => 'histórico de moderação',
|
'modlog' => 'histórico de moderação',
|
||||||
'post' => 'publicação',
|
'post' => 'publicação',
|
||||||
'story' => 'história',
|
'story' => 'story',
|
||||||
'noneFound' => 'Sem notificação',
|
'noneFound' => 'Nenhuma notificação encontrada',
|
||||||
],
|
],
|
||||||
|
|
||||||
'post' => [
|
'post' => [
|
||||||
'shareToFollowers' => 'Compartilhar com os seguidores',
|
'shareToFollowers' => 'Partilhar com os seguidores',
|
||||||
'shareToOther' => 'Compartilhar com outros',
|
'shareToOther' => 'Partilhar com outros',
|
||||||
'noLikes' => 'Ainda sem curtidas',
|
'noLikes' => 'Ainda sem gostos',
|
||||||
'uploading' => 'Enviando',
|
'uploading' => 'A enviar',
|
||||||
],
|
],
|
||||||
|
|
||||||
'profile' => [
|
'profile' => [
|
||||||
'posts' => 'Publicações',
|
'posts' => 'Publicações',
|
||||||
'followers' => 'Seguidores',
|
'followers' => 'Seguidores',
|
||||||
'following' => 'Seguindo',
|
'following' => 'A seguir',
|
||||||
'admin' => 'Administrador',
|
'admin' => 'Admin',
|
||||||
'collections' => 'Coleções',
|
'collections' => 'Coleções',
|
||||||
'follow' => 'Seguir',
|
'follow' => 'Seguir',
|
||||||
'unfollow' => 'Deixar de seguir',
|
'unfollow' => 'Deixar de seguir',
|
||||||
'editProfile' => 'Editar Perfil',
|
'editProfile' => 'Editar Perfil',
|
||||||
'followRequested' => 'Solicitação de seguir enviada',
|
'followRequested' => 'Pedido para seguir enviado',
|
||||||
'joined' => 'Entrou',
|
'joined' => 'Juntou-se',
|
||||||
|
|
||||||
'emptyCollections' => 'Não conseguimos encontrar nenhuma coleção',
|
'emptyCollections' => 'Não conseguimos encontrar nenhuma coleção',
|
||||||
'emptyPosts' => 'Não encontramos nenhuma publicação',
|
'emptyPosts' => 'Não conseguimos encontrar nenhuma publicação',
|
||||||
],
|
],
|
||||||
|
|
||||||
'menu' => [
|
'menu' => [
|
||||||
'viewPost' => 'Ver publicação',
|
'viewPost' => 'Ver publicação',
|
||||||
'viewProfile' => 'Ver Perfil',
|
'viewProfile' => 'Ver perfil',
|
||||||
'moderationTools' => 'Ferramentas de moderação',
|
'moderationTools' => 'Ferramentas de moderação',
|
||||||
'report' => 'Denunciar',
|
'report' => 'Denunciar',
|
||||||
'archive' => 'Arquivo',
|
'archive' => 'Arquivar',
|
||||||
'unarchive' => 'Desarquivar',
|
'unarchive' => 'Retirar do arquivo',
|
||||||
'embed' => 'Incorporar',
|
'embed' => 'Incorporar',
|
||||||
|
|
||||||
'selectOneOption' => 'Selecione uma das opções a seguir',
|
'selectOneOption' => 'Selecione uma das seguintes opções',
|
||||||
'unlistFromTimelines' => 'Retirar das linhas do tempo',
|
'unlistFromTimelines' => 'Remover das cronologias',
|
||||||
'addCW' => 'Adicionar aviso de conteúdo',
|
'addCW' => 'Adicionar aviso de conteúdo',
|
||||||
'removeCW' => 'Remover aviso de conteúdo',
|
'removeCW' => 'Remover aviso de conteúdo',
|
||||||
'markAsSpammer' => 'Marcar como Spammer',
|
'markAsSpammer' => 'Marcar como spammer',
|
||||||
'markAsSpammerText' => 'Retirar das linhas do tempo + adicionar aviso de conteúdo às publicações antigas e futuras',
|
'markAsSpammerText' => 'Remover das cronologias e adicionar um aviso de conteúdo às publicações existentes e futuras',
|
||||||
'spam' => 'Lixo Eletrônico',
|
'spam' => 'Spam',
|
||||||
'sensitive' => 'Conteúdo sensível',
|
'sensitive' => 'Conteúdo Sensível',
|
||||||
'abusive' => 'Abusivo ou Prejudicial',
|
'abusive' => 'Abusivo ou prejudicial',
|
||||||
'underageAccount' => 'Conta de menor de idade',
|
'underageAccount' => 'Conta de menor de idade',
|
||||||
'copyrightInfringement' => 'Violação de direitos autorais',
|
'copyrightInfringement' => 'Violação de direitos de autor',
|
||||||
'impersonation' => 'Roubo de identidade',
|
'impersonation' => 'Roubo de Identidade',
|
||||||
'scamOrFraud' => 'Golpe ou Fraude',
|
'scamOrFraud' => 'Esquema ou fraude',
|
||||||
'confirmReport' => 'Confirmar denúncia',
|
'confirmReport' => 'Confirmar denúncia',
|
||||||
'confirmReportText' => 'Você realmente quer denunciar esta publicação?',
|
'confirmReportText' => 'Tem a certeza que deseja denunciar esta mensagem?',
|
||||||
'reportSent' => 'Denúncia enviada!',
|
'reportSent' => 'Denúncia enviada!',
|
||||||
'reportSentText' => 'Nós recebemos sua denúncia com sucesso.',
|
'reportSentText' => 'Recebemos com sucesso a sua denúncia.',
|
||||||
'reportSentError' => 'Houve um problema ao denunciar esta publicação.',
|
'reportSentError' => 'Ocorreu um erro ao denunciar este conteúdo.',
|
||||||
|
|
||||||
'modAddCWConfirm' => 'Você realmente quer adicionar um aviso de conteúdo a esta publicação?',
|
'modAddCWConfirm' => 'Tem a certeza que pretende adicionar um aviso de conteúdo à publicação?',
|
||||||
'modCWSuccess' => 'Aviso de conteúdo sensível adicionado com sucesso',
|
'modCWSuccess' => 'Adicionou com sucesso um aviso de conteúdo',
|
||||||
'modRemoveCWConfirm' => 'Você realmente quer remover o aviso de conteúdo desta publicação?',
|
'modRemoveCWConfirm' => 'Tem a certeza que pretende remover o aviso de conteúdo desta publicação?',
|
||||||
'modRemoveCWSuccess' => 'Aviso de conteúdo sensível removido com sucesso',
|
'modRemoveCWSuccess' => 'Removeu com sucesso o aviso de conteúdo',
|
||||||
'modUnlistConfirm' => 'Você realmente quer definir esta publicação como não listada?',
|
'modUnlistConfirm' => 'Tem a certeza que pretende deslistar este post?',
|
||||||
'modUnlistSuccess' => 'A publicação foi definida como não listada com sucesso',
|
'modUnlistSuccess' => 'Deslistou com sucesso este post',
|
||||||
'modMarkAsSpammerConfirm' => 'Você realmente quer denunciar este usuário por spam? Todas as suas publicações anteriores e futuras serão marcadas com um aviso de conteúdo e removidas das linhas do tempo.',
|
'modMarkAsSpammerConfirm' => 'Tem a certeza que deseja marcar este utilizador como spammer? Todos os posts existentes e futuros serão deslistados da timeline e o alerta de conteúdo será aplicado.',
|
||||||
'modMarkAsSpammerSuccess' => 'Perfil denunciado com sucesso',
|
'modMarkAsSpammerSuccess' => 'Marcou com sucesso esta conta como spammer',
|
||||||
|
|
||||||
'toFollowers' => 'para seguidores',
|
'toFollowers' => 'para Seguidores',
|
||||||
|
|
||||||
'showCaption' => 'Mostrar legenda',
|
'showCaption' => 'Mostar legenda',
|
||||||
'showLikes' => 'Mostrar curtidas',
|
'showLikes' => 'Mostrar Gostos',
|
||||||
'compactMode' => 'Modo compacto',
|
'compactMode' => 'Modo compacto',
|
||||||
'embedConfirmText' => 'Ao usar de forma “embed”, você concorda com nossas',
|
'embedConfirmText' => 'Ao utilizar este conteúdo, aceita os nossos',
|
||||||
|
|
||||||
'deletePostConfirm' => 'Você tem certeza que deseja excluir esta publicação?',
|
'deletePostConfirm' => 'Tem a certeza que pretende apagar esta publicação?',
|
||||||
'archivePostConfirm' => 'Tem certeza que deseja arquivar esta publicação?',
|
'archivePostConfirm' => 'Tem a certeza que pretende arquivar esta publicação?',
|
||||||
'unarchivePostConfirm' => 'Tem certeza que deseja desarquivar esta publicação?',
|
'unarchivePostConfirm' => 'Tem a certeza que pretende desarquivar este post?',
|
||||||
],
|
],
|
||||||
|
|
||||||
'story' => [
|
'story' => [
|
||||||
'add' => 'Adicionar Story'
|
'add' => 'Adicionar Storie'
|
||||||
],
|
],
|
||||||
|
|
||||||
'timeline' => [
|
'timeline' => [
|
||||||
'peopleYouMayKnow' => 'Pessoas que você talvez conheça',
|
'peopleYouMayKnow' => 'Pessoas que talvez conheça',
|
||||||
|
|
||||||
'onboarding' => [
|
'onboarding' => [
|
||||||
'welcome' => 'Boas vindas',
|
'welcome' => 'Bem-vindo',
|
||||||
'thisIsYourHomeFeed' => 'Esse é seu feed principal, onde as publicações das contas que você segue aparecem cronologicamente.',
|
'thisIsYourHomeFeed' => 'Este é o seu feed pessoal, com publicações em ordem cronológica das contas que segue.',
|
||||||
'letUsHelpYouFind' => 'Deixe-nos te ajudar a encontrar pessoas legais para seguir',
|
'letUsHelpYouFind' => 'Deixe-nos ajudar a encontrar algumas pessoas interessantes para seguir',
|
||||||
'refreshFeed' => 'Atualizar meu feed',
|
'refreshFeed' => 'Atualizar o meu feed',
|
||||||
],
|
],
|
||||||
],
|
],
|
||||||
|
|
||||||
'hashtags' => [
|
'hashtags' => [
|
||||||
'emptyFeed' => 'Não encontramos nenhuma publicação com esta hashtag'
|
'emptyFeed' => 'Não conseguimos encontrar publicações com essa hashtag'
|
||||||
],
|
],
|
||||||
|
|
||||||
'report' => [
|
'report' => [
|
||||||
'report' => 'Denunciar',
|
'report' => 'Denunciar',
|
||||||
'selectReason' => 'Selecione um motivo',
|
'selectReason' => 'Selecione uma razão',
|
||||||
'reported' => 'Denunciado',
|
'reported' => 'Denunciado',
|
||||||
'sendingReport' => 'Enviando denúncia',
|
'sendingReport' => 'A enviar denúncia',
|
||||||
'thanksMsg' => 'Agradecemos a denúncia; pessoas como você nos ajudam a manter a comunidade segura!',
|
'thanksMsg' => 'Obrigado pela denúncia, pessoas como você ajudam a manter a nossa comunidade segura!',
|
||||||
'contactAdminMsg' => 'Se quiser contatar um administrador por causa desta publicação ou denúncia',
|
'contactAdminMsg' => 'Se quiser entrar em contato com um administrador acerca desta publicação ou denúncia',
|
||||||
],
|
],
|
||||||
|
|
||||||
];
|
];
|
||||||
|
|
Loading…
Reference in a new issue