Update Settings, add default license and enforced media descriptions

This commit is contained in:
Daniel Supernault 2021-07-23 09:47:14 -06:00
parent 27778e00c9
commit 67e3f6048f
No known key found for this signature in database
GPG key ID: 0DEF1C662C9033F7
9 changed files with 252 additions and 35 deletions

View file

@ -15,7 +15,8 @@ use App\{
Profile, Profile,
Place, Place,
Status, Status,
UserFilter UserFilter,
UserSetting
}; };
use App\Transformer\Api\{ use App\Transformer\Api\{
MediaTransformer, MediaTransformer,
@ -661,4 +662,25 @@ class ComposeController extends Controller
'finished' => $finished 'finished' => $finished
]; ];
} }
public function composeSettings(Request $request)
{
$uid = $request->user()->id;
return Cache::remember('profile:compose:settings:' . $uid, now()->addHours(12), function() use($uid) {
$res = UserSetting::whereUserId($uid)->first();
if(!$res) {
return [
'default_license' => null,
'media_descriptions' => false
];
}
return json_decode($res->compose_settings, true) ?? [
'default_license' => null,
'media_descriptions' => false
];
});
}
} }

View file

@ -7,6 +7,7 @@ use App\Following;
use App\ProfileSponsor; use App\ProfileSponsor;
use App\Report; use App\Report;
use App\UserFilter; use App\UserFilter;
use App\UserSetting;
use Auth, Cookie, DB, Cache, Purify; use Auth, Cookie, DB, Cache, Purify;
use Illuminate\Support\Facades\Redis; use Illuminate\Support\Facades\Redis;
use Carbon\Carbon; use Carbon\Carbon;
@ -221,7 +222,7 @@ class SettingsController extends Controller
$sponsors->sponsors = json_encode($res); $sponsors->sponsors = json_encode($res);
$sponsors->save(); $sponsors->save();
$sponsors = $res; $sponsors = $res;
return redirect(route('settings'))->with('status', 'Sponsor settings successfully updated!');; return redirect(route('settings'))->with('status', 'Sponsor settings successfully updated!');
} }
public function timelineSettings(Request $request) public function timelineSettings(Request $request)
@ -249,7 +250,52 @@ class SettingsController extends Controller
} else { } else {
Redis::zrem('pf:tl:replies', $pid); Redis::zrem('pf:tl:replies', $pid);
} }
return redirect(route('settings.timeline')); return redirect(route('settings'))->with('status', 'Timeline settings successfully updated!');;
}
public function mediaSettings(Request $request)
{
$setting = UserSetting::whereUserId($request->user()->id)->firstOrFail();
$compose = $setting->compose_settings ? json_decode($setting->compose_settings, true) : [
'default_license' => null,
'media_descriptions' => false
];
return view('settings.media', compact('compose'));
}
public function updateMediaSettings(Request $request)
{
$this->validate($request, [
'default' => 'required|int|min:1|max:16',
'sync' => 'nullable',
'media_descriptions' => 'nullable'
]);
$license = $request->input('default');
$sync = $request->input('sync') == 'on';
$media_descriptions = $request->input('media_descriptions') == 'on';
$setting = UserSetting::whereUserId($request->user()->id)->firstOrFail();
$compose = json_decode($setting->compose_settings, true);
$changed = false;
if(!isset($compose['default_license']) || $compose['default_license'] !== $license) {
$compose['default_license'] = (int) $license;
$changed = true;
}
if(!isset($compose['media_descriptions']) || $compose['media_descriptions'] !== $media_descriptions) {
$compose['media_descriptions'] = $media_descriptions;
$changed = true;
}
if($changed) {
$setting->compose_settings = json_encode($compose);
$setting->save();
Cache::forget('profile:compose:settings:' . $request->user()->id);
}
return redirect(route('settings'))->with('status', 'Media settings successfully updated!');
} }
} }

View file

@ -0,0 +1,46 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class AddComposeSettingsToUserSettingsTable extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::table('user_settings', function (Blueprint $table) {
$table->json('compose_settings')->nullable();
});
Schema::table('media', function (Blueprint $table) {
$table->text('caption')->change();
$table->index('profile_id');
$table->index('mime');
$table->index('license');
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::table('user_settings', function (Blueprint $table) {
$table->dropColumn('compose_settings');
});
Schema::table('media', function (Blueprint $table) {
$table->string('caption')->change();
$table->dropIndex('profile_id');
$table->dropIndex('mime');
$table->dropIndex('license');
});
}
}

View file

@ -134,20 +134,16 @@ export default {
window._sharedData.curUser = res.data; window._sharedData.curUser = res.data;
window.App.util.navatar(); window.App.util.navatar();
}); });
axios.get('/api/pixelfed/v1/notifications', { axios.get('/api/pixelfed/v1/notifications?pg=true')
params: {
pg: true
}
})
.then(res => { .then(res => {
let data = res.data.filter(n => { let data = res.data.filter(n => {
if(n.type == 'share' && !status) { if(n.type == 'share' && !n.status) {
return false; return false;
} }
if(n.type == 'comment' && !status) { if(n.type == 'comment' && !n.status) {
return false; return false;
} }
if(n.type == 'mention' && !status) { if(n.type == 'mention' && !n.status) {
return false; return false;
} }
return true; return true;
@ -167,8 +163,7 @@ export default {
} }
axios.get('/api/pixelfed/v1/notifications', { axios.get('/api/pixelfed/v1/notifications', {
params: { params: {
pg: true, max_id: this.notificationMaxId
page: this.notificationCursor
} }
}).then(res => { }).then(res => {
if(res.data.length) { if(res.data.length) {

View file

@ -100,10 +100,10 @@
v-for="(item, index) in availableLicenses" v-for="(item, index) in availableLicenses"
class="list-group-item cursor-pointer" class="list-group-item cursor-pointer"
:class="{ :class="{
'text-primary': licenseIndex === index, 'text-primary': licenseId === item.id,
'font-weight-bold': licenseIndex === index 'font-weight-bold': licenseId === item.id
}" }"
@click="toggleLicense(index)"> @click="toggleLicense(item)">
{{item.name}} {{item.name}}
</div> </div>
</div> </div>
@ -336,7 +336,13 @@
<p class="px-4 mb-0 py-2 cursor-pointer" @click="showTagCard()">Tag people</p> <p class="px-4 mb-0 py-2 cursor-pointer" @click="showTagCard()">Tag people</p>
</div> </div>
<div class="border-bottom"> <div class="border-bottom">
<p class="px-4 mb-0 py-2 cursor-pointer" @click="showLicenseCard()">Add license <span class="ml-2 badge badge-primary">NEW</span></p> <p class="px-4 mb-0 py-2 cursor-pointer" @click="showLicenseCard()">
<span>Add license <span class="ml-2 badge badge-primary">NEW</span></span>
<span class="float-right">
<a v-if="licenseTitle" href="#" @click.prevent="showLicenseCard()" class="btn btn-outline-secondary btn-sm small mr-3 mt-n1 disabled" style="font-size:10px;padding:3px;text-transform: uppercase" disabled>{{licenseTitle}}</a>
<a href="#" @click.prevent="showLicenseCard()" class="text-decoration-none"><i class="fas fa-chevron-right fa-lg text-lighter"></i></a>
</span>
</p>
</div> </div>
<div class="border-bottom"> <div class="border-bottom">
<p class="px-4 mb-0 py-2 cursor-pointer" @click="showLocationCard()" v-if="!place">Add location</p> <p class="px-4 mb-0 py-2 cursor-pointer" @click="showLocationCard()" v-if="!place">Add location</p>
@ -591,11 +597,11 @@
<span></span> <span></span>
<span>{{media[carouselCursor].license ? media[carouselCursor].license.length : 0}}/140</span> <span>{{media[carouselCursor].license ? media[carouselCursor].license.length : 0}}/140</span>
</p> --> </p> -->
<select class="form-control" v-model="licenseIndex"> <select class="form-control" v-model="licenseId">
<option <option
v-for="(item, index) in availableLicenses" v-for="(item, index) in availableLicenses"
:value="index" :value="item.id"
:selected="index === licenseIndex"> :selected="item.id == licenseId">
{{item.name}} {{item.name}}
</option> </option>
</select> </select>
@ -845,52 +851,79 @@ export default {
availableLicenses: [ availableLicenses: [
{ {
id: 1, id: 1,
name: "All Rights Reserved" name: "All Rights Reserved",
title: ""
}, },
{ {
id: 5, id: 5,
name: "Public Domain Work" name: "Public Domain Work",
title: ""
}, },
{ {
id: 6, id: 6,
name: "Public Domain Dedication (CC0)" name: "Public Domain Dedication (CC0)",
title: "CC0"
}, },
{ {
id: 11, id: 11,
name: "Attribution" name: "Attribution",
title: "CC BY"
}, },
{ {
id: 12, id: 12,
name: "Attribution-ShareAlike" name: "Attribution-ShareAlike",
title: "CC BY-SA"
}, },
{ {
id: 13, id: 13,
name: "Attribution-NonCommercial" name: "Attribution-NonCommercial",
title: "CC BY-NC"
}, },
{ {
id: 14, id: 14,
name: "Attribution-NonCommercial-ShareAlike" name: "Attribution-NonCommercial-ShareAlike",
title: "CC BY-NC-SA"
}, },
{ {
id: 15, id: 15,
name: "Attribution-NoDerivs" name: "Attribution-NoDerivs",
title: "CC BY-ND"
}, },
{ {
id: 16, id: 16,
name: "Attribution-NonCommercial-NoDerivs" name: "Attribution-NonCommercial-NoDerivs",
title: "CC BY-NC-ND"
} }
], ],
licenseIndex: 0, licenseIndex: 0,
video: { video: {
title: '', title: '',
description: '' description: ''
} },
composeSettings: {
default_license: null,
media_descriptions: false
},
licenseId: null,
licenseTitle: null
} }
}, },
beforeMount() { beforeMount() {
this.fetchProfile(); this.fetchProfile();
this.filters = window.App.util.filters; this.filters = window.App.util.filters;
axios.get('/api/compose/v0/settings')
.then(res => {
this.composeSettings = res.data;
this.licenseId = this.composeSettings.default_license;
if(this.licenseId > 10) {
this.licenseTitle = this.availableLicenses.filter(l => {
return l.id == this.licenseId;
}).map(l => {
return l.title;
})[0];
}
});
}, },
mounted() { mounted() {
@ -1064,6 +1097,16 @@ export default {
switch(state) { switch(state) {
case 'publish' : case 'publish' :
if(this.composeSettings.media_descriptions === true) {
let count = this.media.filter(m => {
return !m.hasOwnProperty('alt') || m.alt.length < 2;
});
if(count.length) {
swal('Missing media descriptions', 'You have enabled mandatory media descriptions. Please add media descriptions under Advanced settings to proceed. For more information, please see the media settings page.', 'warning');
return;
}
}
if(this.media.length == 0) { if(this.media.length == 0) {
swal('Whoops!', 'You need to add media before you can save this!', 'warning'); swal('Whoops!', 'You need to add media before you can save this!', 'warning');
return; return;
@ -1080,7 +1123,7 @@ export default {
place: this.place, place: this.place,
tagged: this.taggedUsernames, tagged: this.taggedUsernames,
optimize_media: this.optimizeMedia, optimize_media: this.optimizeMedia,
license: this.availableLicenses[this.licenseIndex].id, license: this.licenseId,
video: this.video video: this.video
}; };
axios.post('/api/compose/v0/publish', data) axios.post('/api/compose/v0/publish', data)
@ -1515,8 +1558,18 @@ export default {
this.page = 'licensePicker'; this.page = 'licensePicker';
}, },
toggleLicense(index) { toggleLicense(license) {
this.licenseIndex = index; this.licenseId = license.id;
if(this.licenseId > 10) {
this.licenseTitle = this.availableLicenses.filter(l => {
return l.id == this.licenseId;
}).map(l => {
return l.title;
})[0];
} else {
this.licenseTitle = null;
}
switch(this.mode) { switch(this.mode) {
case 'photo': case 'photo':

View file

@ -120,7 +120,7 @@
setTimeout(function() { setTimeout(function() {
self.profile = window._sharedData.curUser; self.profile = window._sharedData.curUser;
self.fetchFollowRequests(); self.fetchFollowRequests();
}, 500); }, 1500);
}, },
updated() { updated() {
@ -157,7 +157,7 @@
} }
axios.get('/api/pixelfed/v1/notifications', { axios.get('/api/pixelfed/v1/notifications', {
params: { params: {
page: this.notificationCursor max_id: this.notificationMaxId
} }
}).then(res => { }).then(res => {
if(res.data.length) { if(res.data.length) {

View file

@ -0,0 +1,49 @@
@extends('settings.template')
@section('section')
<div class="title">
<h3 class="font-weight-bold">Media</h3>
</div>
<hr>
<form method="post">
@csrf
<div class="form-group pb-3">
<label class="form-check-label font-weight-bold" for="">Default License</label>
<select class="form-control" name="default">
@foreach(App\Util\Media\License::get() as $license)
<option value="{{$license['id']}}" {{$compose['default_license'] == $license['id'] ? 'selected':''}}>
{{$license['name']}}
@if($license['id'] > 10)
({{$license['title']}})
@endif
</option>
@endforeach
</select>
<p class="text-muted small help-text">Set a default license for new posts.</p>
</div>
<div class="form-check pb-3">
<input class="form-check-input" type="checkbox" name="sync">
<label class="form-check-label font-weight-bold" for="">Sync Licenses</label>
<p class="text-muted small help-text">Update existing posts with your new default license. You can sync once every 24 hours.</p>
</div>
<div class="form-check pb-3">
<input class="form-check-input" type="checkbox" name="media_descriptions" {{$compose['media_descriptions'] == $license['id'] ? 'checked':''}}>
<label class="form-check-label font-weight-bold" for="">Require Media Descriptions</label>
<p class="text-muted small help-text">
Briefly describe your media to improve accessibility for vision impaired people. <br />
<span class="font-weight-bold">Not available for mobile or 3rd party apps at this time.</span>
</p>
</div>
<div class="form-group row mt-5 pt-5">
<div class="col-12 text-right">
<hr>
<button type="submit" class="btn btn-primary font-weight-bold py-0 px-5">Submit</button>
</div>
</div>
</form>
@endsection

View file

@ -14,6 +14,9 @@
<a class="nav-link font-weight-light text-muted" href="{{route('settings.invites')}}">Invites</a> <a class="nav-link font-weight-light text-muted" href="{{route('settings.invites')}}">Invites</a>
</li> </li>
@endif @endif
<li class="nav-item pl-3 {{request()->is('settings/media*')?'active':''}}">
<a class="nav-link font-weight-light text-muted" href="{{route('settings.media')}}">Media</a>
</li>
<li class="nav-item pl-3 {{request()->is('settings/notifications')?'active':''}}"> <li class="nav-item pl-3 {{request()->is('settings/notifications')?'active':''}}">
<a class="nav-link font-weight-light text-muted" href="{{route('settings.notifications')}}">Notifications</a> <a class="nav-link font-weight-light text-muted" href="{{route('settings.notifications')}}">Notifications</a>
</li> </li>

View file

@ -119,6 +119,7 @@ Route::domain(config('pixelfed.domain.app'))->middleware(['validemail', 'twofact
Route::post('/publish', 'ComposeController@store'); Route::post('/publish', 'ComposeController@store');
Route::post('/publish/text', 'ComposeController@storeText'); Route::post('/publish/text', 'ComposeController@storeText');
Route::get('/media/processing', 'ComposeController@mediaProcessingCheck'); Route::get('/media/processing', 'ComposeController@mediaProcessingCheck');
Route::get('/settings', 'ComposeController@composeSettings');
}); });
}); });
@ -429,6 +430,8 @@ Route::domain(config('pixelfed.domain.app'))->middleware(['validemail', 'twofact
Route::get('timeline', 'SettingsController@timelineSettings')->name('settings.timeline'); Route::get('timeline', 'SettingsController@timelineSettings')->name('settings.timeline');
Route::post('timeline', 'SettingsController@updateTimelineSettings'); Route::post('timeline', 'SettingsController@updateTimelineSettings');
Route::get('media', 'SettingsController@mediaSettings')->name('settings.media');
Route::post('media', 'SettingsController@updateMediaSettings');
}); });
Route::group(['prefix' => 'site'], function () { Route::group(['prefix' => 'site'], function () {