mirror of
https://github.com/Kovah/LinkAce.git
synced 2025-04-21 07:22:20 +02:00
Add bulk editing for links, lists and tags (#26)
This commit is contained in:
parent
3075255b1a
commit
e785460e31
147
app/Http/Controllers/Models/BulkEditController.php
Normal file
147
app/Http/Controllers/Models/BulkEditController.php
Normal file
@ -0,0 +1,147 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Models;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Http\Requests\Models\BulkDeleteRequest;
|
||||
use App\Http\Requests\Models\BulkEditFormRequest;
|
||||
use App\Http\Requests\Models\BulkEditLinksRequest;
|
||||
use App\Http\Requests\Models\BulkEditListsRequest;
|
||||
use App\Http\Requests\Models\BulkEditTagsRequest;
|
||||
use App\Models\Link;
|
||||
use App\Models\LinkList;
|
||||
use App\Models\Tag;
|
||||
use App\Repositories\LinkRepository;
|
||||
use App\Repositories\ListRepository;
|
||||
use App\Repositories\TagRepository;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
|
||||
class BulkEditController extends Controller
|
||||
{
|
||||
public function form(BulkEditFormRequest $request)
|
||||
{
|
||||
$type = $request->input('type');
|
||||
$view = sprintf('models.%s.bulk-edit', $type);
|
||||
|
||||
$models = explode(',', $request->input('models'));
|
||||
|
||||
return view($view, [
|
||||
'models' => $models,
|
||||
'modelCount' => count($models),
|
||||
]);
|
||||
}
|
||||
|
||||
public function updateLinks(BulkEditLinksRequest $request)
|
||||
{
|
||||
$models = explode(',', $request->input('models'));
|
||||
$links = Link::whereIn('id', $models)->with([
|
||||
'tags:id',
|
||||
'lists:id',
|
||||
])->get();
|
||||
|
||||
$results = $links->map(function (Link $link) use ($request) {
|
||||
if (!auth()->user()->can('update', $link)) {
|
||||
Log::warning('Could not update ' . $link->id . ' during bulk update: Permission denied!');
|
||||
return null;
|
||||
}
|
||||
|
||||
$newTags = explode(',', $request->input('tags'));
|
||||
$newLists = explode(',', $request->input('lists'));
|
||||
|
||||
$linkData = $link->toArray();
|
||||
$linkData['tags'] = $request->input('tags_mode') === 'replace'
|
||||
? $newTags
|
||||
: array_merge($link->tags->pluck('id')->toArray(), $newTags);
|
||||
$linkData['lists'] = $request->input('lists_mode') === 'replace'
|
||||
? $newLists
|
||||
: array_merge($link->lists->pluck('id')->toArray(), $newLists);
|
||||
$linkData['visibility'] = $request->input('visibility') ?: $linkData['visibility'];
|
||||
|
||||
return LinkRepository::update($link, $linkData);
|
||||
});
|
||||
|
||||
$successCount = $results->filter(fn($e) => $e !== null)->count();
|
||||
|
||||
flash(trans('link.bulk_edit_success', ['success' => $successCount, 'selected' => $links->count()]));
|
||||
return redirect()->route('links.index');
|
||||
}
|
||||
|
||||
public function updateLists(BulkEditListsRequest $request)
|
||||
{
|
||||
$models = explode(',', $request->input('models'));
|
||||
$lists = LinkList::whereIn('id', $models)->get();
|
||||
|
||||
$results = $lists->map(function (LinkList $list) use ($request) {
|
||||
if (!auth()->user()->can('update', $list)) {
|
||||
Log::warning('Could not update list ' . $list->id . ' during bulk update: Permission denied!');
|
||||
return null;
|
||||
}
|
||||
|
||||
$listData = $list->toArray();
|
||||
$listData['visibility'] = $request->input('visibility') ?: $listData['visibility'];
|
||||
|
||||
return ListRepository::update($list, $listData);
|
||||
});
|
||||
|
||||
$successCount = $results->filter(fn($e) => $e !== null)->count();
|
||||
|
||||
flash(trans('list.bulk_edit_success', ['success' => $successCount, 'selected' => $lists->count()]));
|
||||
return redirect()->route('lists.index');
|
||||
}
|
||||
|
||||
public function updateTags(BulkEditTagsRequest $request)
|
||||
{
|
||||
$models = explode(',', $request->input('models'));
|
||||
$tags = Tag::whereIn('id', $models)->get();
|
||||
|
||||
$results = $tags->map(function (Tag $tag) use ($request) {
|
||||
if (!auth()->user()->can('update', $tag)) {
|
||||
Log::warning('Could not update tag ' . $tag->id . ' during bulk update: Permission denied!');
|
||||
return null;
|
||||
}
|
||||
|
||||
$tagData = $tag->toArray();
|
||||
$tagData['visibility'] = $request->input('visibility') ?: $tagData['visibility'];
|
||||
|
||||
return TagRepository::update($tag, $tagData);
|
||||
});
|
||||
|
||||
$successCount = $results->filter(fn($e) => $e !== null)->count();
|
||||
|
||||
flash(trans('tag.bulk_edit_success', ['success' => $successCount, 'selected' => $tags->count()]));
|
||||
return redirect()->route('tags.index');
|
||||
}
|
||||
|
||||
public function delete(BulkDeleteRequest $request)
|
||||
{
|
||||
$type = $request->input('type');
|
||||
$formModels = explode(',', $request->input('models'));
|
||||
$models = match ($type) {
|
||||
'links' => Link::whereIn('id', $formModels)->get(),
|
||||
'lists' => LinkList::whereIn('id', $formModels)->get(),
|
||||
'tags' => Tag::whereIn('id', $formModels)->get(),
|
||||
};
|
||||
|
||||
$results = $models->map(function ($model) use ($type) {
|
||||
if (!auth()->user()->can('delete', $model)) {
|
||||
Log::warning('Could not delete ' . $type . ' ' . $model->id . ' during bulk deletion: Permission denied!');
|
||||
return null;
|
||||
}
|
||||
return match ($type) {
|
||||
'links' => LinkRepository::delete($model),
|
||||
'lists' => ListRepository::delete($model),
|
||||
'tags' => TagRepository::delete($model)
|
||||
};
|
||||
});
|
||||
|
||||
$successCount = $results->filter(fn($e) => $e !== null)->count();
|
||||
|
||||
$message = match ($type) {
|
||||
'links' => 'link.bulk_delete_success',
|
||||
'lists' => 'list.bulk_delete_success',
|
||||
'tags' => 'tag.bulk_delete_success'
|
||||
};
|
||||
flash(trans($message, ['success' => $successCount, 'selected' => $models->count()]));
|
||||
return redirect()->route($type . '.index');
|
||||
}
|
||||
}
|
16
app/Http/Requests/Models/BulkDeleteRequest.php
Normal file
16
app/Http/Requests/Models/BulkDeleteRequest.php
Normal file
@ -0,0 +1,16 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Requests\Models;
|
||||
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
|
||||
class BulkDeleteRequest extends FormRequest
|
||||
{
|
||||
public function rules(): array
|
||||
{
|
||||
return [
|
||||
'type' => ['required', 'in:links,lists,tags'],
|
||||
'models' => ['required', 'string'],
|
||||
];
|
||||
}
|
||||
}
|
16
app/Http/Requests/Models/BulkEditFormRequest.php
Normal file
16
app/Http/Requests/Models/BulkEditFormRequest.php
Normal file
@ -0,0 +1,16 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Requests\Models;
|
||||
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
|
||||
class BulkEditFormRequest extends FormRequest
|
||||
{
|
||||
public function rules(): array
|
||||
{
|
||||
return [
|
||||
'type' => ['required', 'in:links,lists,tags'],
|
||||
'models' => ['required', 'string'],
|
||||
];
|
||||
}
|
||||
}
|
21
app/Http/Requests/Models/BulkEditLinksRequest.php
Normal file
21
app/Http/Requests/Models/BulkEditLinksRequest.php
Normal file
@ -0,0 +1,21 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Requests\Models;
|
||||
|
||||
use App\Rules\ModelVisibility;
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
|
||||
class BulkEditLinksRequest extends FormRequest
|
||||
{
|
||||
public function rules(): array
|
||||
{
|
||||
return [
|
||||
'models' => ['required', 'string'],
|
||||
'tags' => ['nullable', 'string'],
|
||||
'tags_mode' => ['required', 'in:append,replace'],
|
||||
'lists' => ['nullable', 'string'],
|
||||
'lists_mode' => ['required', 'in:append,replace'],
|
||||
'visibility' => ['nullable', new ModelVisibility],
|
||||
];
|
||||
}
|
||||
}
|
17
app/Http/Requests/Models/BulkEditListsRequest.php
Normal file
17
app/Http/Requests/Models/BulkEditListsRequest.php
Normal file
@ -0,0 +1,17 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Requests\Models;
|
||||
|
||||
use App\Rules\ModelVisibility;
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
|
||||
class BulkEditListsRequest extends FormRequest
|
||||
{
|
||||
public function rules(): array
|
||||
{
|
||||
return [
|
||||
'models' => ['required', 'string'],
|
||||
'visibility' => ['nullable', new ModelVisibility],
|
||||
];
|
||||
}
|
||||
}
|
17
app/Http/Requests/Models/BulkEditTagsRequest.php
Normal file
17
app/Http/Requests/Models/BulkEditTagsRequest.php
Normal file
@ -0,0 +1,17 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Requests\Models;
|
||||
|
||||
use App\Rules\ModelVisibility;
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
|
||||
class BulkEditTagsRequest extends FormRequest
|
||||
{
|
||||
public function rules(): array
|
||||
{
|
||||
return [
|
||||
'models' => ['required', 'string'],
|
||||
'visibility' => ['nullable', new ModelVisibility],
|
||||
];
|
||||
}
|
||||
}
|
@ -3,6 +3,7 @@
|
||||
namespace App\Settings;
|
||||
|
||||
use App\Enums\ModelAttribute;
|
||||
use App\Models\Link;
|
||||
use Spatie\LaravelSettings\Settings;
|
||||
|
||||
class UserSettings extends Settings
|
||||
@ -83,7 +84,7 @@ class UserSettings extends Settings
|
||||
'archive_private_backups_enabled' => true,
|
||||
'listitem_count' => 24,
|
||||
'darkmode_setting' => 2,
|
||||
'link_display_mode' => 1,
|
||||
'link_display_mode' => Link::DISPLAY_LIST_DETAILED,
|
||||
'links_new_tab' => false,
|
||||
'markdown_for_text' => true,
|
||||
'share_services' => true,
|
||||
|
@ -12,7 +12,8 @@ class VisibilityToggle extends Component
|
||||
private ?int $existingValue = null,
|
||||
private string $visibilitySetting = 'links_default_visibility',
|
||||
public string $inputClasses = '',
|
||||
public string $labelClasses = ''
|
||||
public string $labelClasses = '',
|
||||
public bool $unchangedOption = false
|
||||
) {
|
||||
}
|
||||
|
||||
|
@ -4,6 +4,7 @@ return [
|
||||
'links' => 'Links',
|
||||
'all_links' => 'All Links',
|
||||
'recent_links' => 'Recent Links',
|
||||
'update_links' => 'Update Links',
|
||||
'no_links' => 'No Links',
|
||||
|
||||
'add' => 'Add Link',
|
||||
@ -12,7 +13,7 @@ return [
|
||||
'details' => 'Link Details',
|
||||
'edit' => 'Edit Link',
|
||||
'update' => 'Update Link',
|
||||
'delete' => 'Delete Link',
|
||||
'delete' => 'Delete Link|Delete Links',
|
||||
|
||||
'public' => 'Public Link',
|
||||
'internal' => 'Internal Link',
|
||||
@ -49,6 +50,10 @@ return [
|
||||
'status_is_broken' => 'Link is marked as broken',
|
||||
'status_mark_working' => 'Mark as working',
|
||||
|
||||
'bulk_title' => 'You want to edit :count Link.|You want to edit :count Links.',
|
||||
'bulk_edit_success' => 'Successfully updated :success Links out of :selected selected ones.',
|
||||
'bulk_delete_success' => 'Successfully moved :success Links out of :selected selected ones to the trash.',
|
||||
|
||||
'added_successfully' => 'Link added successfully.',
|
||||
'added_connection_error' => 'The Link was added but a connection error occurred when trying to access the URL. Details can be found in the logs.',
|
||||
'added_request_error' => 'The Link was added but an error occurred when trying to request the URL, for example an invalid certificate. Details can be found in the logs.',
|
||||
|
@ -50,6 +50,8 @@ return [
|
||||
'continue_adding' => 'Continue Adding',
|
||||
|
||||
'visibility' => 'Visibility',
|
||||
'change_visibility' => 'Change Visibility',
|
||||
'dont_change_visibility' => 'Do not change Visibility',
|
||||
|
||||
'history' => 'History',
|
||||
'history_added' => 'Added <code>:newvalue</code> to :fieldname.',
|
||||
|
@ -4,12 +4,13 @@ return [
|
||||
'lists' => 'Lists',
|
||||
'all_lists' => 'All Lists',
|
||||
'recent_lists' => 'Recent Lists',
|
||||
'update_lists' => 'Update Lists',
|
||||
|
||||
'add' => 'Add List',
|
||||
'show' => 'Show List',
|
||||
'edit' => 'Edit List',
|
||||
'update' => 'Update List',
|
||||
'delete' => 'Delete List',
|
||||
'delete' => 'Delete List|Delete Lists',
|
||||
|
||||
'filter_lists' => 'Filter Lists...',
|
||||
|
||||
@ -29,6 +30,12 @@ return [
|
||||
|
||||
'no_lists' => 'No Lists',
|
||||
|
||||
'bulk_title' => 'You want to edit :count List.|You want to edit :count Lists.',
|
||||
'bulk_edit_success' => 'Successfully updated :success Lists out of :selected selected ones.',
|
||||
'bulk_delete_success' => 'Successfully moved :success Lists out of :selected selected ones to the trash.',
|
||||
'bulk_mode_append' => 'Append new Lists to existing ones',
|
||||
'bulk_mode_replace' => 'Replace existing Lists with new ones',
|
||||
|
||||
'number_links' => ':number Link in this List|:number Links in this List',
|
||||
|
||||
'added_successfully' => 'List added successfully.',
|
||||
|
@ -4,12 +4,13 @@ return [
|
||||
'tags' => 'Tags',
|
||||
'all_tags' => 'All Tags',
|
||||
'recent_tags' => 'Recent Tags',
|
||||
'update_tags' => 'Update Tags',
|
||||
|
||||
'add' => 'Add Tag',
|
||||
'show' => 'Show Tag',
|
||||
'edit' => 'Edit Tag',
|
||||
'update' => 'Update Tag',
|
||||
'delete' => 'Delete Tag',
|
||||
'delete' => 'Delete Tag|Delete Tags',
|
||||
|
||||
'filter_tags' => 'Filter Tags...',
|
||||
|
||||
@ -28,6 +29,12 @@ return [
|
||||
|
||||
'no_tags' => 'No Tags',
|
||||
|
||||
'bulk_title' => 'You want to edit :count Tag.|You want to edit :count Tags.',
|
||||
'bulk_edit_success' => 'Successfully updated :success Tags out of :selected selected ones.',
|
||||
'bulk_delete_success' => 'Successfully moved :success Tags out of :selected selected ones to the trash.',
|
||||
'bulk_mode_append' => 'Append new Tags to existing ones',
|
||||
'bulk_mode_replace' => 'Replace existing Tags with new ones',
|
||||
|
||||
'added_successfully' => 'Tag added successfully.',
|
||||
'updated_successfully' => 'Tag updated successfully.',
|
||||
'deleted_successfully' => 'Tag deleted successfully.',
|
||||
|
26
resources/assets/js/app.js
vendored
26
resources/assets/js/app.js
vendored
@ -1,28 +1,30 @@
|
||||
import { register } from './lib/views';
|
||||
|
||||
import Base from './components/Base';
|
||||
import UrlField from './components/UrlField';
|
||||
import LoadingButton from './components/LoadingButton';
|
||||
import BookmarkTimer from './components/BookmarkTimer';
|
||||
import TagsSelect from './components/TagsSelect';
|
||||
import SimpleSelect from './components/SimpleSelect';
|
||||
import ShareToggleAll from './components/ShareToggleAll';
|
||||
import BulkEdit from './components/BulkEdit';
|
||||
import GenerateCronToken from './components/GenerateCronToken';
|
||||
import UpdateCheck from './components/UpdateCheck';
|
||||
import Import from './components/Import';
|
||||
import LoadingButton from './components/LoadingButton';
|
||||
import ShareToggleAll from './components/ShareToggleAll';
|
||||
import SimpleSelect from './components/SimpleSelect';
|
||||
import TagsSelect from './components/TagsSelect';
|
||||
import UpdateCheck from './components/UpdateCheck';
|
||||
import UrlField from './components/UrlField';
|
||||
|
||||
// Register view components
|
||||
function registerViews () {
|
||||
register('#app', Base);
|
||||
register('input[id="url"]', UrlField);
|
||||
register('button[type="submit"]', LoadingButton);
|
||||
register('.bm-timer', BookmarkTimer);
|
||||
register('.tag-select', TagsSelect);
|
||||
register('.simple-select', SimpleSelect);
|
||||
register('.share-toggle', ShareToggleAll);
|
||||
register('.bulk-edit', BulkEdit);
|
||||
register('.cron-token', GenerateCronToken);
|
||||
register('.update-check', UpdateCheck);
|
||||
register('.import-form', Import);
|
||||
register('.share-toggle', ShareToggleAll);
|
||||
register('.simple-select', SimpleSelect);
|
||||
register('.tag-select', TagsSelect);
|
||||
register('.update-check', UpdateCheck);
|
||||
register('button[type="submit"]', LoadingButton);
|
||||
register('input[id="url"]', UrlField);
|
||||
}
|
||||
|
||||
if (document.readyState !== 'loading') {
|
||||
|
63
resources/assets/js/components/BulkEdit.js
vendored
Normal file
63
resources/assets/js/components/BulkEdit.js
vendored
Normal file
@ -0,0 +1,63 @@
|
||||
export default class BulkEdit {
|
||||
|
||||
constructor ($el) {
|
||||
this.$el = $el;
|
||||
this.$form = $el.querySelector('.bulk-edit-form');
|
||||
this.$submit = $el.querySelector('.bulk-edit-submit');
|
||||
this.$selectAll = $el.querySelector('.bulk-edit-select-all');
|
||||
this.$models = $el.querySelectorAll('.bulk-edit-model');
|
||||
|
||||
this.$form.querySelector('[name="type"]').value = $el.dataset.type;
|
||||
this.selectedModels = [];
|
||||
|
||||
this.init();
|
||||
}
|
||||
|
||||
init () {
|
||||
console.log('bulk edit init for ' + this.$models.length + ' models'); //@DEBUG
|
||||
this.$models.forEach($model => {
|
||||
$model.addEventListener('change', this.toggleBulkEdit.bind(this));
|
||||
});
|
||||
this.$selectAll.addEventListener('click', this.selectAll.bind(this));
|
||||
this.$submit.addEventListener('click', this.submitEdit.bind(this));
|
||||
}
|
||||
|
||||
toggleBulkEdit (event) {
|
||||
const newModel = event.target.dataset.id;
|
||||
if (event.target.checked) {
|
||||
this.selectedModels.push(newModel);
|
||||
} else {
|
||||
this.selectedModels = this.selectedModels.filter(existingModel => newModel !== existingModel);
|
||||
}
|
||||
this.toggleHeader();
|
||||
}
|
||||
|
||||
toggleHeader () {
|
||||
if (this.selectedModels.length > 0) {
|
||||
this.$form.classList.remove('visually-hidden');
|
||||
} else {
|
||||
this.$form.classList.add('visually-hidden');
|
||||
}
|
||||
}
|
||||
|
||||
selectAll () {
|
||||
if (this.selectedModels.length === this.$models.length) {
|
||||
this.selectedModels = [];
|
||||
this.$models.forEach($model => {
|
||||
$model.checked = false;
|
||||
});
|
||||
this.toggleHeader();
|
||||
} else {
|
||||
this.selectedModels = [];
|
||||
this.$models.forEach($model => {
|
||||
this.selectedModels.push($model.dataset.id);
|
||||
$model.checked = true;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
submitEdit () {
|
||||
this.$form.querySelector('[name="models"]').value = this.selectedModels.join(',');
|
||||
this.$form.submit();
|
||||
}
|
||||
}
|
22
resources/assets/sass/custom/_app.scss
vendored
22
resources/assets/sass/custom/_app.scss
vendored
@ -178,6 +178,28 @@ code {
|
||||
margin: 2px;
|
||||
}
|
||||
|
||||
.form-check.bulk-edit-model {
|
||||
width: 1rem;
|
||||
height: 1rem;
|
||||
}
|
||||
|
||||
.link-card .form-check.bulk-edit-model {
|
||||
position: absolute;
|
||||
top: .5rem;
|
||||
right: .5rem;
|
||||
opacity: .5;
|
||||
|
||||
&:hover,
|
||||
&:focus,
|
||||
&:checked {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
.link-card:hover .form-check.bulk-edit-model {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.link-thumbnail {
|
||||
box-shadow: inset 0 0 1px $secondary;
|
||||
display: flex;
|
||||
|
@ -1,6 +1,9 @@
|
||||
<div {{ $attributes }}>
|
||||
<label class="form-label {{ $labelClasses }}" for="visibility">@lang('linkace.visibility')</label>
|
||||
<select id="visibility" name="visibility" class="form-select {{ $inputClasses }}{{ $errors->has('visibility') ? ' is-invalid' : '' }}">
|
||||
@if($unchangedOption)
|
||||
<option value="">@lang('linkace.dont_change_visibility')</option>
|
||||
@endif
|
||||
<option value="{{ $public }}" {{ $publicSelected ? 'selected' : '' }}>
|
||||
@lang('attributes.visibility.' . $public)
|
||||
</option>
|
||||
|
87
resources/views/models/links/bulk-edit.blade.php
Normal file
87
resources/views/models/links/bulk-edit.blade.php
Normal file
@ -0,0 +1,87 @@
|
||||
@extends('layouts.app')
|
||||
|
||||
@section('content')
|
||||
<form action="{{ route('bulk-edit.update-links') }}" method="POST" class="card">
|
||||
@csrf
|
||||
<input type="hidden" name="models" value="{{ old('models', implode(',', $models)) }}">
|
||||
<header class="card-header">@choice('link.bulk_title', $modelCount, ['count' => $modelCount])</header>
|
||||
<div class="card-body">
|
||||
<div class="row row-cols-1 row-cols-md-2 row-gap-2">
|
||||
<div>
|
||||
<label class="form-label" for="tags">@lang('tag.update_tags')</label>
|
||||
<input name="tags" id="tags" type="text" placeholder="@lang('placeholder.tags_select')"
|
||||
class="tag-select"
|
||||
data-value="{{ Link::oldTaxonomyOutputWithoutLink('tags', []) }}"
|
||||
data-allow-creation="1" data-tag-type="tags">
|
||||
@if ($errors->has('tags'))
|
||||
<p class="invalid-feedback" role="alert">
|
||||
{{ $errors->first('tags') }}
|
||||
</p>
|
||||
@endif
|
||||
</div>
|
||||
<div>
|
||||
<label class="form-label" for="tags_mode">Mode</label>
|
||||
<select id="tags_mode" name="tags_mode" class="form-select {{ $errors->has('tags_mode') ? ' is-invalid' : '' }}">
|
||||
<option value="append" @selected(old('tags_mode') === 'append')>
|
||||
@lang('tag.bulk_mode_append')
|
||||
</option>
|
||||
<option value="replace" @selected(old('tags_mode') === 'replace')>
|
||||
@lang('tag.bulk_mode_replace')
|
||||
</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mt-4 row row-cols-1 row-cols-md-2 row-gap-2">
|
||||
<div>
|
||||
<label class="form-label" for="lists">@lang('list.update_lists')</label>
|
||||
<input name="lists" id="lists" type="text" placeholder="@lang('placeholder.list_select')"
|
||||
class="tag-select"
|
||||
data-value="{{ Link::oldTaxonomyOutputWithoutLink('lists', []) }}"
|
||||
data-allow-creation="1" data-tag-type="lists">
|
||||
@if ($errors->has('lists'))
|
||||
<p class="invalid-feedback" role="alert">
|
||||
{{ $errors->first('lists') }}
|
||||
</p>
|
||||
@endif
|
||||
</div>
|
||||
<div>
|
||||
<label class="form-label" for="lists_mode">Mode</label>
|
||||
<select id="lists_mode" name="lists_mode" class="form-select {{ $errors->has('lists_mode') ? ' is-invalid' : '' }}">
|
||||
<option value="append" @selected(old('lists_mode') === 'append')>
|
||||
@lang('list.bulk_mode_append')
|
||||
</option>
|
||||
<option value="replace" @selected(old('lists_mode') === 'replace')>
|
||||
@lang('list.bulk_mode_replace')
|
||||
</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mt-4 row">
|
||||
<x-forms.visibility-toggle class="col-6" :unchanged-option="true"/>
|
||||
</div>
|
||||
|
||||
<div class="mt-3 d-sm-flex align-items-center justify-content-end">
|
||||
<button type="submit" class="btn btn-primary">
|
||||
<x-icon.save class="me-2"/> @lang('link.update_links')
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<form action="{{ route('bulk-edit.delete') }}" method="POST" class="card mt-4">
|
||||
@csrf
|
||||
<input type="hidden" name="type" value="links">
|
||||
<input type="hidden" name="models" value="{{ implode(',', $models) }}">
|
||||
<header class="card-header">@choice('link.delete', $modelCount)</header>
|
||||
<div class="card-body">
|
||||
<div class="text-end">
|
||||
<button type="submit" class="btn btn-danger">
|
||||
<x-icon.save class="me-2"/> @choice('link.delete', $modelCount)
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
@endsection
|
@ -98,7 +98,7 @@
|
||||
<div class="d-sm-inline-block mb-3 mb-sm-0 me-auto">
|
||||
<button type="button" class="btn btn-sm btn-outline-danger"
|
||||
onclick="window.deleteLink.submit()">
|
||||
<x-icon.trash class="me-2"/> @lang('link.delete')
|
||||
<x-icon.trash class="me-2"/> @choice('link.delete', 1)
|
||||
</button>
|
||||
</div>
|
||||
|
||||
|
@ -17,7 +17,7 @@
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<section class="my-4">
|
||||
<section class="mb-4">
|
||||
@if($links->isNotEmpty())
|
||||
|
||||
<div class="link-wrapper">
|
||||
|
@ -1,5 +1,17 @@
|
||||
<div class="link-list row gy-4">
|
||||
@foreach($links as $link)
|
||||
@include('models.links.partials.single-card')
|
||||
@endforeach
|
||||
<div class="bulk-edit" data-type="links">
|
||||
<form class="bulk-edit-form visually-hidden text-end" action="{{ route('bulk-edit.form') }}" method="POST">
|
||||
@csrf()
|
||||
<input type="hidden" name="type">
|
||||
<input type="hidden" name="models">
|
||||
<div class="btn-group mt-1">
|
||||
<button type="button" class="bulk-edit-submit btn btn-outline-primary btn-xs">Edit</button>
|
||||
<button type="button" class="bulk-edit-select-all btn btn-outline-primary btn-xs">Select all</button>
|
||||
</div>
|
||||
</form>
|
||||
<div class="link-list row gy-4 mt-1">
|
||||
@foreach($links as $link)
|
||||
@include('models.links.partials.single-card')
|
||||
@endforeach
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
@ -1,5 +1,16 @@
|
||||
<div class="link-list list-group">
|
||||
@foreach($links as $link)
|
||||
@include('models.links.partials.single-detailed')
|
||||
@endforeach
|
||||
<div class="bulk-edit" data-type="links">
|
||||
<form class="bulk-edit-form visually-hidden text-end" action="{{ route('bulk-edit.form') }}" method="POST">
|
||||
@csrf()
|
||||
<input type="hidden" name="type">
|
||||
<input type="hidden" name="models">
|
||||
<div class="btn-group mt-1">
|
||||
<button type="button" class="bulk-edit-submit btn btn-outline-primary btn-xs">Edit</button>
|
||||
<button type="button" class="bulk-edit-select-all btn btn-outline-primary btn-xs">Select all</button>
|
||||
</div>
|
||||
</form>
|
||||
<div class="link-list list-group mt-3">
|
||||
@foreach($links as $link)
|
||||
@include('models.links.partials.single-detailed')
|
||||
@endforeach
|
||||
</div>
|
||||
</div>
|
||||
|
@ -1,5 +1,16 @@
|
||||
<ul class="link-list list-group">
|
||||
@foreach($links as $link)
|
||||
@include('models.links.partials.single-simple')
|
||||
@endforeach
|
||||
</ul>
|
||||
<div class="bulk-edit" data-type="links">
|
||||
<form class="bulk-edit-form visually-hidden text-end" action="{{ route('bulk-edit.form') }}" method="POST">
|
||||
@csrf()
|
||||
<input type="hidden" name="type">
|
||||
<input type="hidden" name="models">
|
||||
<div class="btn-group mt-1">
|
||||
<button type="button" class="bulk-edit-submit btn btn-outline-primary btn-xs">Edit</button>
|
||||
<button type="button" class="bulk-edit-select-all btn btn-outline-primary btn-xs">Select all</button>
|
||||
</div>
|
||||
</form>
|
||||
<ul class="link-list list-group mt-3">
|
||||
@foreach($links as $link)
|
||||
@include('models.links.partials.single-simple')
|
||||
@endforeach
|
||||
</ul>
|
||||
</div>
|
||||
|
@ -3,18 +3,19 @@
|
||||
@endphp
|
||||
<div class="link-card col-12 col-md-6 col-lg-4">
|
||||
<div class="h-100 card">
|
||||
|
||||
<div class="link-thumbnail-list-holder-detailed">
|
||||
<a href="{{ $link->url }}" {!! linkTarget() !!} class="link-thumbnail-list-detailed"
|
||||
<a href="{{ $link->url }}" {!! linkTarget() !!} class="link-thumbnail-list-detailed">
|
||||
@if($link->thumbnail)
|
||||
style="background-image: url('{{ $link->thumbnail }}');"
|
||||
@endif>
|
||||
<img src="{{ $link->thumbnail }}" alt="{{ $link->title }}" class="w-100 h-100 object-fit-cover" loading="lazy">
|
||||
@endif
|
||||
@if(!$link->thumbnail)
|
||||
<span class="link-thumbnail-placeholder link-thumbnail-placeholder-detailed">
|
||||
<x-icon.linkace-icon/>
|
||||
</span>
|
||||
@endif
|
||||
</a>
|
||||
<input type="checkbox" aria-label="Add link to bulk edit" class="bulk-edit-model form-check"
|
||||
data-id="{{ $link->id }}">
|
||||
</div>
|
||||
|
||||
<div class="card-body h-100 border-bottom-0">
|
||||
@ -25,7 +26,7 @@
|
||||
</div>
|
||||
|
||||
@if($link->tags->count() > 0)
|
||||
<div class="px-3">
|
||||
<div class="px-3 mb-3">
|
||||
@foreach($link->tags as $tag)
|
||||
<a href="{{ route('tags.show', [$tag]) }}" class="btn btn-light btn-xs">
|
||||
{{ $tag->name }}
|
||||
@ -40,23 +41,21 @@
|
||||
</div>
|
||||
|
||||
<div class="btn-group ms-auto me-2">
|
||||
<button type="button" class="btn btn-xs btn-md-sm btn-outline-secondary"
|
||||
<button type="button" class="btn btn-xs btn-md-sm btn-link"
|
||||
title="@lang('sharing.share_link')"
|
||||
data-bs-toggle="collapse" data-bs-target="#sharing-{{ $link->id }}"
|
||||
aria-expanded="false" aria-controls="sharing-{{ $link->id }}">
|
||||
<x-icon.share class="fw"/>
|
||||
<span class="visually-hidden">@lang('sharing.share_link')</span>
|
||||
</button>
|
||||
<a href="{{ route('links.show', [$link]) }}" class="btn btn-xs btn-outline-secondary"
|
||||
title="@lang('link.show')">
|
||||
<a href="{{ route('links.show', [$link]) }}" class="btn btn-xs btn-link" title="@lang('link.show')">
|
||||
@lang('linkace.show')
|
||||
</a>
|
||||
<a href="{{ route('links.edit', [$link]) }}" class="btn btn-xs btn-outline-secondary"
|
||||
title="@lang('link.edit')">
|
||||
<a href="{{ route('links.edit', [$link]) }}" class="btn btn-xs btn-link" title="@lang('link.edit')">
|
||||
@lang('linkace.edit')
|
||||
</a>
|
||||
<button type="submit" form="link-delete-{{ $link->id }}" title="@lang('link.delete')"
|
||||
class="btn btn-xs btn-outline-secondary">
|
||||
<button type="submit" form="link-delete-{{ $link->id }}" title="@choice('link.delete', 1)"
|
||||
class="btn btn-xs btn-link">
|
||||
@lang('linkace.delete')
|
||||
</button>
|
||||
</div>
|
||||
|
@ -35,27 +35,26 @@
|
||||
@lang('linkace.added') {!! $link->addedAt() !!}
|
||||
</div>
|
||||
|
||||
<div class="btn-group ms-2">
|
||||
<button type="button" class="btn btn-xs btn-outline-secondary" title="@lang('sharing.share_link')"
|
||||
<div class="btn-group ms-2 me-1">
|
||||
<button type="button" class="btn btn-xs btn-link" title="@lang('sharing.share_link')"
|
||||
data-bs-toggle="collapse" data-bs-target="#sharing-{{ $link->id }}"
|
||||
aria-expanded="false" aria-controls="sharing-{{ $link->id }}">
|
||||
<x-icon.share class="fw"/>
|
||||
<span class="visually-hidden">@lang('sharing.share_link')</span>
|
||||
</button>
|
||||
<a href="{{ route('links.show', [$link]) }}" class="btn btn-xs btn-outline-secondary"
|
||||
title="@lang('link.show')">
|
||||
<a href="{{ route('links.show', [$link]) }}" class="btn btn-xs btn-link" title="@lang('link.show')">
|
||||
@lang('linkace.show')
|
||||
</a>
|
||||
<a href="{{ route('links.edit', [$link]) }}" class="btn btn-xs btn-outline-secondary"
|
||||
title="@lang('link.edit')">
|
||||
<a href="{{ route('links.edit', [$link]) }}" class="btn btn-xs btn-link" title="@lang('link.edit')">
|
||||
@lang('linkace.edit')
|
||||
</a>
|
||||
<button type="submit" form="link-delete-{{ $link->id }}" title="@lang('link.delete')"
|
||||
class="btn btn-xs btn-outline-secondary">
|
||||
<button type="submit" form="link-delete-{{ $link->id }}" title="@choice('link.delete', 1)"
|
||||
class="btn btn-xs btn-link">
|
||||
@lang('linkace.delete')
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<input type="checkbox" aria-label="Add link to bulk edit" class="bulk-edit-model form-check"
|
||||
data-id="{{ $link->id }}">
|
||||
</div>
|
||||
</div>
|
||||
@if($shareLinks !== '')
|
||||
|
@ -4,6 +4,7 @@
|
||||
<li class="link-simple list-group-item">
|
||||
<div class="d-sm-flex align-items-center">
|
||||
<div class="me-4 one-line-sm">
|
||||
{!! $link->getIcon('me-1') !!}
|
||||
<a href="{{ $link->url }}" title="{{ $link->url }}" {!! linkTarget() !!}>
|
||||
{{ $link->title }}
|
||||
</a>
|
||||
@ -14,12 +15,14 @@
|
||||
<x-icon.info class="fw"/>
|
||||
<span class="visually-hidden">@lang('link.details')</span>
|
||||
</a>
|
||||
<button type="button" class="btn btn-xs btn-link" title="@lang('sharing.share_link')"
|
||||
<button type="button" class="btn btn-xs btn-link me-1" title="@lang('sharing.share_link')"
|
||||
data-bs-toggle="collapse" data-bs-target="#sharing-{{ $link->id }}"
|
||||
aria-expanded="false" aria-controls="sharing-{{ $link->id }}">
|
||||
<x-icon.share class="fw"/>
|
||||
<span class="visually-hidden">@lang('sharing.share_link')</span>
|
||||
</button>
|
||||
<input type="checkbox" aria-label="Add link to bulk edit" class="bulk-edit-model form-check"
|
||||
data-id="{{ $link->id }}">
|
||||
</div>
|
||||
</div>
|
||||
@if($shareLinks !== '')
|
||||
|
@ -59,7 +59,7 @@
|
||||
<x-icon.edit class="me-2"/>
|
||||
<span class="d-none d-sm-inline">@lang('linkace.edit')</span>
|
||||
</a>
|
||||
<button type="submit" form="link-delete-{{ $link->id }}" aria-label="@lang('link.delete')"
|
||||
<button type="submit" form="link-delete-{{ $link->id }}" aria-label="@choice('link.delete', 1)"
|
||||
class="btn btn-sm btn-outline-danger cursor-pointer">
|
||||
<x-icon.trash class="me-2"/>
|
||||
<span class="d-none d-sm-inline">@lang('linkace.delete')</span>
|
||||
|
36
resources/views/models/lists/bulk-edit.blade.php
Normal file
36
resources/views/models/lists/bulk-edit.blade.php
Normal file
@ -0,0 +1,36 @@
|
||||
@extends('layouts.app')
|
||||
|
||||
@section('content')
|
||||
|
||||
<form action="{{ route('bulk-edit.update-lists') }}" method="POST" class="card">
|
||||
@csrf
|
||||
<input type="hidden" name="models" value="{{ old('models', implode(',', $models)) }}">
|
||||
<header class="card-header">@choice('list.bulk_title', $modelCount, ['count' => $modelCount])</header>
|
||||
<div class="card-body">
|
||||
<div class="row">
|
||||
<x-forms.visibility-toggle class="col-6" :unchanged-option="true"/>
|
||||
</div>
|
||||
|
||||
<div class="mt-3 d-sm-flex align-items-center justify-content-end">
|
||||
<button type="submit" class="btn btn-primary">
|
||||
<x-icon.save class="me-2"/> @lang('list.update_lists')
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<form action="{{ route('bulk-edit.delete') }}" method="POST" class="card mt-4">
|
||||
@csrf
|
||||
<input type="hidden" name="type" value="links">
|
||||
<input type="hidden" name="models" value="{{ implode(',', $models) }}">
|
||||
<header class="card-header">@choice('list.delete', $modelCount)</header>
|
||||
<div class="card-body">
|
||||
<div class="text-end">
|
||||
<button type="submit" class="btn btn-danger">
|
||||
<x-icon.save class="me-2"/> @choice('list.delete', $modelCount)
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
@endsection
|
@ -56,7 +56,7 @@
|
||||
<div class="d-sm-inline-block mb-3 mb-sm-0 me-auto">
|
||||
<button type="button" class="btn btn-sm btn-outline-danger"
|
||||
onclick="window.deleteList.submit()">
|
||||
<x-icon.trash class="me-2"/> @lang('list.delete')
|
||||
<x-icon.trash class="me-2"/> @choice('list.delete', 1)
|
||||
</button>
|
||||
</div>
|
||||
|
||||
|
@ -35,10 +35,21 @@
|
||||
|
||||
@if($lists->isNotEmpty())
|
||||
|
||||
<div class="row mt-3">
|
||||
@foreach($lists as $list)
|
||||
@include('models.lists.partials.single')
|
||||
@endforeach
|
||||
<div class="bulk-edit" data-type="lists">
|
||||
<form class="bulk-edit-form visually-hidden text-end" action="{{ route('bulk-edit.form') }}" method="POST">
|
||||
@csrf()
|
||||
<input type="hidden" name="type">
|
||||
<input type="hidden" name="models">
|
||||
<div class="btn-group mt-1">
|
||||
<button type="button" class="bulk-edit-submit btn btn-outline-primary btn-xs">Edit</button>
|
||||
<button type="button" class="bulk-edit-select-all btn btn-outline-primary btn-xs">Select all</button>
|
||||
</div>
|
||||
</form>
|
||||
<div class="row mt-3">
|
||||
@foreach($lists as $list)
|
||||
@include('models.lists.partials.single')
|
||||
@endforeach
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@else
|
||||
|
@ -20,17 +20,16 @@
|
||||
@lang('link.no_links')
|
||||
@endif
|
||||
</div>
|
||||
<div class="btn-group ms-auto me-2">
|
||||
<a href="{{ route('lists.edit', ['list' => $list]) }}" class="btn btn-sm btn-link">
|
||||
<x-icon.edit/>
|
||||
<span class="visually-hidden">@lang('list.edit')</span>
|
||||
<div class="btn-group ms-auto me-1">
|
||||
<a href="{{ route('lists.edit', ['list' => $list]) }}" class="btn btn-xs btn-link">
|
||||
@lang('linkace.edit')
|
||||
</a>
|
||||
<button type="submit" form="list-delete-{{ $list->id }}" title="@lang('list.delete')"
|
||||
class="btn btn-sm btn-link">
|
||||
<x-icon.trash/>
|
||||
<span class="visually-hidden">@lang('list.delete')</span>
|
||||
<button type="submit" form="list-delete-{{ $list->id }}" class="btn btn-xs btn-link">
|
||||
@lang('linkace.delete')
|
||||
</button>
|
||||
</div>
|
||||
<input type="checkbox" aria-label="Add link to bulk edit" class="bulk-edit-model form-check me-2"
|
||||
data-id="{{ $list->id }}">
|
||||
|
||||
<form id="list-delete-{{ $list->id }}" method="POST" style="display: none;"
|
||||
action="{{ route('lists.destroy', ['list' => $list]) }}">
|
||||
|
@ -15,7 +15,7 @@
|
||||
@lang('linkace.edit')
|
||||
</a>
|
||||
<a onclick="event.preventDefault();document.getElementById('list-delete-{{ $list->id }}').submit();"
|
||||
class="btn btn-sm btn-outline-danger" aria-label="@lang('list.delete')">
|
||||
class="btn btn-sm btn-outline-danger" aria-label="@choice('list.delete', 1)">
|
||||
<x-icon.trash class="me-2"/>
|
||||
@lang('linkace.delete')
|
||||
</a>
|
||||
|
36
resources/views/models/tags/bulk-edit.blade.php
Normal file
36
resources/views/models/tags/bulk-edit.blade.php
Normal file
@ -0,0 +1,36 @@
|
||||
@extends('layouts.app')
|
||||
|
||||
@section('content')
|
||||
|
||||
<form action="{{ route('bulk-edit.update-tags') }}" method="POST" class="card">
|
||||
@csrf
|
||||
<input type="hidden" name="models" value="{{ old('models', implode(',', $models)) }}">
|
||||
<header class="card-header">@choice('tag.bulk_title', $modelCount, ['count' => $modelCount])</header>
|
||||
<div class="card-body">
|
||||
<div class="row">
|
||||
<x-forms.visibility-toggle class="col-6" :unchanged-option="true"/>
|
||||
</div>
|
||||
|
||||
<div class="mt-3 d-sm-flex align-items-center justify-content-end">
|
||||
<button type="submit" class="btn btn-primary">
|
||||
<x-icon.save class="me-2"/> @lang('tag.update_tags')
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<form action="{{ route('bulk-edit.delete') }}" method="POST" class="card mt-4">
|
||||
@csrf
|
||||
<input type="hidden" name="type" value="links">
|
||||
<input type="hidden" name="models" value="{{ implode(',', $models) }}">
|
||||
<header class="card-header">@choice('tag.delete', $modelCount)</header>
|
||||
<div class="card-body">
|
||||
<div class="text-end">
|
||||
<button type="submit" class="btn btn-danger">
|
||||
<x-icon.save class="me-2"/> @choice('tag.delete', $modelCount)
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
@endsection
|
@ -42,7 +42,7 @@
|
||||
<div class="d-sm-inline-block mb-3 mb-sm-0 me-auto">
|
||||
<button type="button" class="btn btn-sm btn-outline-danger"
|
||||
onclick="window.deleteTag.submit()">
|
||||
<x-icon.trash class="me-2"/> @lang('tag.delete')
|
||||
<x-icon.trash class="me-2"/> @choice('tag.delete', 1)
|
||||
</button>
|
||||
</div>
|
||||
|
||||
|
@ -8,17 +8,19 @@
|
||||
<td>
|
||||
{{ $tag->links_count }}
|
||||
</td>
|
||||
<td class="py-1 text-end">
|
||||
<div class="btn-group btn-group-sm">
|
||||
<a href="{{ route('tags.edit', [$tag]) }}" class="btn btn-link">
|
||||
<x-icon.edit class="fw"/>
|
||||
<span class="visually-hidden">@lang('tag.edit')</span>
|
||||
</a>
|
||||
<button type="submit" form="tag-delete-{{ $tag->id }}" title="@lang('tag.delete')"
|
||||
class="btn btn-link">
|
||||
<x-icon.trash class="fw"/>
|
||||
<span class="visually-hidden">@lang('tag.delete')</span>
|
||||
</button>
|
||||
<td class="py-1">
|
||||
<div class="mt-1 d-flex align-items-center justify-content-end">
|
||||
<div class="btn-group me-1">
|
||||
<a href="{{ route('tags.edit', [$tag]) }}" class="btn btn-xs btn-link">
|
||||
@lang('linkace.edit')
|
||||
</a>
|
||||
<button type="submit" form="tag-delete-{{ $tag->id }}" title="@choice('tag.delete', 1)"
|
||||
class="btn btn-xs btn-link">
|
||||
@lang('linkace.delete')
|
||||
</button>
|
||||
</div>
|
||||
<input type="checkbox" aria-label="Add link to bulk edit" class="bulk-edit-model form-check"
|
||||
data-id="{{ $tag->id }}">
|
||||
</div>
|
||||
|
||||
<form id="tag-delete-{{ $tag->id }}" method="POST" style="display: none;"
|
||||
|
@ -1,4 +1,4 @@
|
||||
<div class="table-responsive">
|
||||
<div class="bulk-edit table-responsive" data-type="tags">
|
||||
<table class="table mb-0">
|
||||
<thead>
|
||||
<tr>
|
||||
@ -8,7 +8,17 @@
|
||||
<th>
|
||||
{!! tableSorter(trans('link.links'), $route, 'links_count', $orderBy, $orderDir) !!}
|
||||
</th>
|
||||
<th></th>
|
||||
<th>
|
||||
<form class="bulk-edit-form visually-hidden text-end" action="{{ route('bulk-edit.form') }}" method="POST">
|
||||
@csrf()
|
||||
<input type="hidden" name="type">
|
||||
<input type="hidden" name="models">
|
||||
<div class="btn-group mt-1">
|
||||
<button type="button" class="bulk-edit-submit btn btn-xs btn-outline-primary">Edit</button>
|
||||
<button type="button" class="bulk-edit-select-all btn btn-xs btn-outline-primary">Select all</button>
|
||||
</div>
|
||||
</form>
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
|
@ -15,7 +15,7 @@
|
||||
@lang('linkace.edit')
|
||||
</a>
|
||||
<a onclick="event.preventDefault();document.getElementById('tag-delete-{{ $tag->id }}').submit();"
|
||||
class="btn btn-sm btn-outline-danger" aria-label="@lang('tag.delete')">
|
||||
class="btn btn-sm btn-outline-danger" aria-label="@choice('tag.delete', 1)">
|
||||
<x-icon.trash class="me-2"/>
|
||||
@lang('linkace.delete')
|
||||
</a>
|
||||
|
@ -21,6 +21,7 @@ use App\Http\Controllers\Guest\LinkController as GuestLinkController;
|
||||
use App\Http\Controllers\Guest\ListController as GuestListController;
|
||||
use App\Http\Controllers\Guest\TagController as GuestTagController;
|
||||
use App\Http\Controllers\Guest\UserController as GuestUserController;
|
||||
use App\Http\Controllers\Models\BulkEditController;
|
||||
use App\Http\Controllers\Models\LinkController;
|
||||
use App\Http\Controllers\Models\ListController;
|
||||
use App\Http\Controllers\Models\NoteController;
|
||||
@ -88,6 +89,17 @@ Route::group(['middleware' => ['auth']], function () {
|
||||
Route::resource('notes', NoteController::class)
|
||||
->except(['index', 'show', 'create']);
|
||||
|
||||
Route::post('bulk-edit', [BulkEditController::class, 'form'])
|
||||
->name('bulk-edit.form');
|
||||
Route::post('bulk-edit/update-links', [BulkEditController::class, 'updateLinks'])
|
||||
->name('bulk-edit.update-links');
|
||||
Route::post('bulk-edit/update-lists', [BulkEditController::class, 'updateLists'])
|
||||
->name('bulk-edit.update-lists');
|
||||
Route::post('bulk-edit/update-tags', [BulkEditController::class, 'updateTags'])
|
||||
->name('bulk-edit.update-tags');
|
||||
Route::post('bulk-edit/delete', [BulkEditController::class, 'delete'])
|
||||
->name('bulk-edit.delete');
|
||||
|
||||
Route::get('users/{user:name}', [UserController::class, 'show'])->name('users.show');
|
||||
|
||||
Route::post('links/toggle-check/{link}', [LinkController::class, 'updateCheckToggle'])
|
||||
|
@ -69,22 +69,6 @@ class SettingsEntryTest extends TestCase
|
||||
);
|
||||
}
|
||||
|
||||
public function testDisplayModeSettingsChange(): void
|
||||
{
|
||||
$settings = app(UserSettings::class);
|
||||
$settings->link_display_mode = 2;
|
||||
$settings->save();
|
||||
|
||||
$historyEntry = Audit::where('auditable_type', SettingsAudit::class)->with('auditable')->latest()->first();
|
||||
|
||||
$output = (new SettingsEntry($historyEntry))->render();
|
||||
|
||||
$this->assertStringContainsString(
|
||||
'Changed Link Display Mode for User 1 from <code>cards with less details</code> to <code>list with less details</code>',
|
||||
$output
|
||||
);
|
||||
}
|
||||
|
||||
public function testLocaleSettingsChange(): void
|
||||
{
|
||||
$settings = app(UserSettings::class);
|
||||
|
263
tests/Controller/Models/BulkEditControllerTest.php
Normal file
263
tests/Controller/Models/BulkEditControllerTest.php
Normal file
@ -0,0 +1,263 @@
|
||||
<?php
|
||||
|
||||
namespace Tests\Controller\Models;
|
||||
|
||||
use App\Enums\ModelAttribute;
|
||||
use App\Models\Link;
|
||||
use App\Models\LinkList;
|
||||
use App\Models\Tag;
|
||||
use App\Models\User;
|
||||
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
use Illuminate\Support\Facades\Queue;
|
||||
use Tests\Controller\Traits\PreparesTestData;
|
||||
use Tests\TestCase;
|
||||
|
||||
class BulkEditControllerTest extends TestCase
|
||||
{
|
||||
use RefreshDatabase;
|
||||
use PreparesTestData;
|
||||
|
||||
private User $user;
|
||||
|
||||
protected function setUp(): void
|
||||
{
|
||||
parent::setUp();
|
||||
|
||||
$this->user = User::factory()->create();
|
||||
$this->actingAs($this->user);
|
||||
|
||||
Queue::fake();
|
||||
}
|
||||
|
||||
public function testBulkEditForm(): void
|
||||
{
|
||||
$this->post('bulk-edit', [
|
||||
'type' => 'links',
|
||||
'models' => '2,9,42',
|
||||
])->assertOk()->assertSee('You want to edit 3 Links.');
|
||||
}
|
||||
|
||||
public function testLinksEdit(): void
|
||||
{
|
||||
Log::shouldReceive('warning')->once();
|
||||
$links = $this->prepareLinkTestData();
|
||||
|
||||
$otherUser = User::factory()->create();
|
||||
$otherLink = Link::factory()->for($otherUser)->create(['visibility' => ModelAttribute::VISIBILITY_PRIVATE]);
|
||||
|
||||
$this->post('bulk-edit/update-links', [
|
||||
'models' => '1,2,3,4',
|
||||
'tags' => '3',
|
||||
'tags_mode' => 'append',
|
||||
'lists' => '3',
|
||||
'lists_mode' => 'append',
|
||||
'visibility' => null,
|
||||
])
|
||||
->assertRedirect('links')
|
||||
->assertSessionHas('flash_notification.0.message', 'Successfully updated 3 Links out of 4 selected ones.');
|
||||
|
||||
array_walk($links, fn($link) => $link->refresh());
|
||||
|
||||
$this->assertEqualsCanonicalizing([1, 3], $links[0]->lists()->pluck('id')->sort()->toArray());
|
||||
$this->assertEqualsCanonicalizing([1, 2, 3], $links[1]->lists()->pluck('id')->sort()->toArray());
|
||||
$this->assertEqualsCanonicalizing([3], $links[2]->lists()->pluck('id')->sort()->toArray());
|
||||
|
||||
$this->assertEqualsCanonicalizing([1, 3], $links[0]->tags()->pluck('id')->toArray());
|
||||
$this->assertEqualsCanonicalizing([1, 2, 3], $links[1]->tags()->pluck('id')->toArray());
|
||||
$this->assertEqualsCanonicalizing([3], $links[2]->tags()->pluck('id')->toArray());
|
||||
|
||||
$this->assertEquals(ModelAttribute::VISIBILITY_PUBLIC, $links[0]->visibility);
|
||||
$this->assertEquals(ModelAttribute::VISIBILITY_INTERNAL, $links[1]->visibility);
|
||||
$this->assertEquals(ModelAttribute::VISIBILITY_PRIVATE, $links[2]->visibility);
|
||||
$this->assertEquals(ModelAttribute::VISIBILITY_PRIVATE, $otherLink->visibility);
|
||||
}
|
||||
|
||||
public function testAlternativeLinksEdit(): void
|
||||
{
|
||||
Log::shouldReceive('warning')->once();
|
||||
$links = $this->prepareLinkTestData();
|
||||
|
||||
$otherUser = User::factory()->create();
|
||||
$otherLink = Link::factory()->for($otherUser)->create(['visibility' => ModelAttribute::VISIBILITY_PRIVATE]);
|
||||
|
||||
$this->post('bulk-edit/update-links', [
|
||||
'models' => '1,2,3,4',
|
||||
'tags' => '2,3',
|
||||
'tags_mode' => 'replace',
|
||||
'lists' => '3',
|
||||
'lists_mode' => 'replace',
|
||||
'visibility' => ModelAttribute::VISIBILITY_INTERNAL,
|
||||
])
|
||||
->assertRedirect('links')
|
||||
->assertSessionHas('flash_notification.0.message', 'Successfully updated 3 Links out of 4 selected ones.');
|
||||
|
||||
array_walk($links, fn($link) => $link->refresh());
|
||||
|
||||
$this->assertEqualsCanonicalizing([3], $links[0]->lists()->pluck('id')->sort()->toArray());
|
||||
$this->assertEqualsCanonicalizing([3], $links[1]->lists()->pluck('id')->sort()->toArray());
|
||||
$this->assertEqualsCanonicalizing([3], $links[2]->lists()->pluck('id')->sort()->toArray());
|
||||
|
||||
$this->assertEqualsCanonicalizing([2, 3], $links[0]->tags()->pluck('id')->toArray());
|
||||
$this->assertEqualsCanonicalizing([2, 3], $links[1]->tags()->pluck('id')->toArray());
|
||||
$this->assertEqualsCanonicalizing([2, 3], $links[2]->tags()->pluck('id')->toArray());
|
||||
|
||||
$this->assertEquals(ModelAttribute::VISIBILITY_INTERNAL, $links[0]->visibility);
|
||||
$this->assertEquals(ModelAttribute::VISIBILITY_INTERNAL, $links[1]->visibility);
|
||||
$this->assertEquals(ModelAttribute::VISIBILITY_INTERNAL, $links[2]->visibility);
|
||||
$this->assertEquals(ModelAttribute::VISIBILITY_PRIVATE, $otherLink->visibility);
|
||||
}
|
||||
|
||||
public function testListsEdit(): void
|
||||
{
|
||||
Log::shouldReceive('warning')->once();
|
||||
$lists = $this->createTestLists($this->user);
|
||||
|
||||
$otherUser = User::factory()->create();
|
||||
$otherList = LinkList::factory()->for($otherUser)->create(['visibility' => ModelAttribute::VISIBILITY_PRIVATE]);
|
||||
|
||||
$this->post('bulk-edit/update-lists', [
|
||||
'models' => '1,2,3,4',
|
||||
'visibility' => null,
|
||||
])
|
||||
->assertRedirect('lists')
|
||||
->assertSessionHas('flash_notification.0.message', 'Successfully updated 3 Lists out of 4 selected ones.');
|
||||
|
||||
array_walk($lists, fn($list) => $list->refresh());
|
||||
|
||||
$this->assertEquals(ModelAttribute::VISIBILITY_PUBLIC, $lists[0]->visibility);
|
||||
$this->assertEquals(ModelAttribute::VISIBILITY_INTERNAL, $lists[1]->visibility);
|
||||
$this->assertEquals(ModelAttribute::VISIBILITY_PRIVATE, $lists[2]->visibility);
|
||||
$this->assertEquals(ModelAttribute::VISIBILITY_PRIVATE, $otherList->visibility);
|
||||
}
|
||||
|
||||
public function testAlternativeListsEdit(): void
|
||||
{
|
||||
Log::shouldReceive('warning')->once();
|
||||
$lists = $this->createTestLists($this->user);
|
||||
|
||||
$otherUser = User::factory()->create();
|
||||
$otherList = LinkList::factory()->for($otherUser)->create(['visibility' => ModelAttribute::VISIBILITY_PRIVATE]);
|
||||
|
||||
$this->post('bulk-edit/update-lists', [
|
||||
'models' => '1,2,3,4',
|
||||
'visibility' => 2,
|
||||
])
|
||||
->assertRedirect('lists')
|
||||
->assertSessionHas('flash_notification.0.message', 'Successfully updated 3 Lists out of 4 selected ones.');
|
||||
|
||||
array_walk($lists, fn($list) => $list->refresh());
|
||||
|
||||
$this->assertEquals(ModelAttribute::VISIBILITY_INTERNAL, $lists[0]->visibility);
|
||||
$this->assertEquals(ModelAttribute::VISIBILITY_INTERNAL, $lists[1]->visibility);
|
||||
$this->assertEquals(ModelAttribute::VISIBILITY_INTERNAL, $lists[2]->visibility);
|
||||
$this->assertEquals(ModelAttribute::VISIBILITY_PRIVATE, $otherList->visibility);
|
||||
}
|
||||
|
||||
public function testTagsEdit(): void
|
||||
{
|
||||
Log::shouldReceive('warning')->once();
|
||||
$tags = $this->createTestTags($this->user);
|
||||
|
||||
$otherUser = User::factory()->create();
|
||||
$otherTag = Tag::factory()->for($otherUser)->create(['visibility' => ModelAttribute::VISIBILITY_PRIVATE]);
|
||||
|
||||
$this->post('bulk-edit/update-tags', [
|
||||
'models' => '1,2,3,4',
|
||||
'visibility' => null,
|
||||
])
|
||||
->assertRedirect('tags')
|
||||
->assertSessionHas('flash_notification.0.message', 'Successfully updated 3 Tags out of 4 selected ones.');
|
||||
|
||||
array_walk($tags, fn($tag) => $tag->refresh());
|
||||
|
||||
$this->assertEquals(ModelAttribute::VISIBILITY_PUBLIC, $tags[0]->visibility);
|
||||
$this->assertEquals(ModelAttribute::VISIBILITY_INTERNAL, $tags[1]->visibility);
|
||||
$this->assertEquals(ModelAttribute::VISIBILITY_PRIVATE, $tags[2]->visibility);
|
||||
$this->assertEquals(ModelAttribute::VISIBILITY_PRIVATE, $otherTag->visibility);
|
||||
}
|
||||
|
||||
public function testAlternativeTagsEdit(): void
|
||||
{
|
||||
Log::shouldReceive('warning')->once();
|
||||
$tags = $this->createTestTags($this->user);
|
||||
|
||||
$otherUser = User::factory()->create();
|
||||
$otherTag = Tag::factory()->for($otherUser)->create(['visibility' => ModelAttribute::VISIBILITY_PRIVATE]);
|
||||
|
||||
$this->post('bulk-edit/update-tags', [
|
||||
'models' => '1,2,3,4',
|
||||
'visibility' => 2,
|
||||
])
|
||||
->assertRedirect('tags')
|
||||
->assertSessionHas('flash_notification.0.message', 'Successfully updated 3 Tags out of 4 selected ones.');
|
||||
|
||||
array_walk($tags, fn($tag) => $tag->refresh());
|
||||
|
||||
$this->assertEquals(ModelAttribute::VISIBILITY_INTERNAL, $tags[0]->visibility);
|
||||
$this->assertEquals(ModelAttribute::VISIBILITY_INTERNAL, $tags[1]->visibility);
|
||||
$this->assertEquals(ModelAttribute::VISIBILITY_INTERNAL, $tags[2]->visibility);
|
||||
$this->assertEquals(ModelAttribute::VISIBILITY_PRIVATE, $otherTag->visibility);
|
||||
}
|
||||
|
||||
public function testDeletion()
|
||||
{
|
||||
Log::shouldReceive('warning')->times(3);
|
||||
$otherUser = User::factory()->create();
|
||||
|
||||
$links = $this->createTestLinks($this->user);
|
||||
$otherLink = Link::factory()->for($otherUser)->create();
|
||||
$lists = $this->createTestLists($this->user);
|
||||
$otherList = LinkList::factory()->for($otherUser)->create();
|
||||
$tags = $this->createTestTags($this->user);
|
||||
$otherTag = Tag::factory()->for($otherUser)->create();
|
||||
|
||||
$this->post('bulk-edit/delete', [
|
||||
'models' => '1,2,4',
|
||||
'type' => 'links',
|
||||
])
|
||||
->assertRedirect('links')
|
||||
->assertSessionHas('flash_notification.0.message', 'Successfully moved 2 Links out of 3 selected ones to the trash.');
|
||||
|
||||
array_walk($links, fn($link) => $link->refresh());
|
||||
$this->assertNotNull($links[0]->deleted_at);
|
||||
$this->assertNotNull($links[1]->deleted_at);
|
||||
$this->assertNull($otherLink->deleted_at);
|
||||
|
||||
$this->post('bulk-edit/delete', [
|
||||
'models' => '1,2,4',
|
||||
'type' => 'lists',
|
||||
])
|
||||
->assertRedirect('lists')
|
||||
->assertSessionHas('flash_notification.1.message', 'Successfully moved 2 Lists out of 3 selected ones to the trash.');
|
||||
|
||||
array_walk($lists, fn($list) => $list->refresh());
|
||||
$this->assertNotNull($lists[0]->deleted_at);
|
||||
$this->assertNotNull($lists[1]->deleted_at);
|
||||
$this->assertNull($otherList->deleted_at);
|
||||
|
||||
$this->post('bulk-edit/delete', [
|
||||
'models' => '1,2,4',
|
||||
'type' => 'tags',
|
||||
])
|
||||
->assertRedirect('tags')
|
||||
->assertSessionHas('flash_notification.2.message', 'Successfully moved 2 Tags out of 3 selected ones to the trash.');
|
||||
|
||||
array_walk($tags, fn($tag) => $tag->refresh());
|
||||
$this->assertNotNull($tags[0]->deleted_at);
|
||||
$this->assertNotNull($tags[1]->deleted_at);
|
||||
$this->assertNull($otherTag->deleted_at);
|
||||
}
|
||||
|
||||
protected function prepareLinkTestData(): array
|
||||
{
|
||||
$links = $this->createTestLinks($this->user);
|
||||
$this->createTestTags($this->user);
|
||||
$this->createTestLists($this->user);
|
||||
$links[0]->lists()->sync([1]);
|
||||
$links[0]->tags()->sync([1]);
|
||||
$links[1]->lists()->sync([1, 2]);
|
||||
$links[1]->tags()->sync([1, 2]);
|
||||
return $links;
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user