mirror of
https://github.com/Kovah/LinkAce.git
synced 2025-01-18 13:56:31 +01:00
Move the import form to fetch via JS to prevent request timeouts (#120)
This commit is contained in:
parent
907790da49
commit
d2a61c868e
@ -3,15 +3,17 @@
|
||||
namespace App\Http\Controllers\App;
|
||||
|
||||
use App\Helper\HtmlMeta;
|
||||
use App\Helper\LinkAce;
|
||||
use App\Helper\LinkIconMapper;
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Http\Requests\DoImportRequest;
|
||||
use App\Models\Link;
|
||||
use App\Models\Tag;
|
||||
use Carbon\Carbon;
|
||||
use Exception;
|
||||
use Illuminate\Contracts\Filesystem\FileNotFoundException;
|
||||
use Illuminate\Http\Response;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
use Illuminate\View\View;
|
||||
use Shaarli\NetscapeBookmarkParser\NetscapeBookmarkParser;
|
||||
|
||||
/**
|
||||
@ -21,12 +23,7 @@ use Shaarli\NetscapeBookmarkParser\NetscapeBookmarkParser;
|
||||
*/
|
||||
class ImportController extends Controller
|
||||
{
|
||||
/**
|
||||
* Show the application dashboard.
|
||||
*
|
||||
* @return Response
|
||||
*/
|
||||
public function getImport()
|
||||
public function getImport(): View
|
||||
{
|
||||
return view('actions.import.import');
|
||||
}
|
||||
@ -35,7 +32,7 @@ class ImportController extends Controller
|
||||
* Permanently delete entries for a model from the trash
|
||||
*
|
||||
* @param DoImportRequest $request
|
||||
* @return Response
|
||||
* @return JsonResponse
|
||||
* @throws FileNotFoundException
|
||||
*/
|
||||
public function doImport(DoImportRequest $request)
|
||||
@ -44,11 +41,24 @@ class ImportController extends Controller
|
||||
|
||||
$parser = new NetscapeBookmarkParser();
|
||||
|
||||
$links = $parser->parseString($data);
|
||||
try {
|
||||
$links = $parser->parseString($data);
|
||||
} catch (Exception $e) {
|
||||
Log::error($e->getMessage());
|
||||
|
||||
return response()->json([
|
||||
'success' => false,
|
||||
'message' => trans('import.import_error'),
|
||||
]);
|
||||
}
|
||||
|
||||
if (empty($links)) {
|
||||
flash(trans('import.import_empty'), 'warning');
|
||||
return redirect()->back();
|
||||
// This will never be reached at the moment because the bookmark parser is not capable of handling
|
||||
// empty bookmarks exports. See https://github.com/shaarli/netscape-bookmark-parser/issues/50
|
||||
return response()->json([
|
||||
'success' => false,
|
||||
'message' => trans('import.import_empty'),
|
||||
]);
|
||||
}
|
||||
|
||||
$userId = auth()->id();
|
||||
@ -93,11 +103,12 @@ class ImportController extends Controller
|
||||
$imported++;
|
||||
}
|
||||
|
||||
flash(trans('import.import_successfully', [
|
||||
'imported' => $imported,
|
||||
'skipped' => $skipped,
|
||||
]), 'success');
|
||||
|
||||
return redirect()->back();
|
||||
return response()->json([
|
||||
'success' => true,
|
||||
'message' => trans('import.import_successfully', [
|
||||
'imported' => $imported,
|
||||
'skipped' => $skipped,
|
||||
]),
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
2
resources/assets/js/app.js
vendored
2
resources/assets/js/app.js
vendored
@ -10,6 +10,7 @@ import ShareToggleAll from './components/ShareToggleAll';
|
||||
import GenerateApiToken from './components/GenerateApiToken';
|
||||
import GenerateCronToken from './components/GenerateCronToken';
|
||||
import UpdateCheck from './components/UpdateCheck';
|
||||
import Import from './components/Import';
|
||||
|
||||
// Register view components
|
||||
function registerViews () {
|
||||
@ -23,6 +24,7 @@ function registerViews () {
|
||||
register('.api-token', GenerateApiToken);
|
||||
register('.cron-token', GenerateCronToken);
|
||||
register('.update-check', UpdateCheck);
|
||||
register('.import-form', Import);
|
||||
}
|
||||
|
||||
if (document.readyState !== 'loading') {
|
||||
|
64
resources/assets/js/components/Import.js
vendored
Normal file
64
resources/assets/js/components/Import.js
vendored
Normal file
@ -0,0 +1,64 @@
|
||||
export default class Import {
|
||||
|
||||
constructor ($el) {
|
||||
this.$el = $el;
|
||||
this.$file = $el.querySelector('#import-file');
|
||||
|
||||
this.$submit = $el.querySelector('.import-submit');
|
||||
this.$submitProcessing = $el.querySelector('.import-submit-processing');
|
||||
this.$submitDefault = $el.querySelector('.import-submit-default');
|
||||
|
||||
this.$alertNetworkError = $el.querySelector('.import-alert-networkerror');
|
||||
this.$alertWarning = $el.querySelector('.import-alert-warning');
|
||||
this.$alertSuccess = $el.querySelector('.import-alert-success');
|
||||
|
||||
this.$submit.addEventListener('click', this.onSubmit.bind(this));
|
||||
}
|
||||
|
||||
onSubmit () {
|
||||
|
||||
this.toggleSubmitBtnState();
|
||||
|
||||
const formData = new FormData();
|
||||
formData.append('import-file', this.$file.files[0]);
|
||||
formData.append('_token', window.appData.user.token);
|
||||
|
||||
fetch(this.$el.dataset.action, {
|
||||
method: 'POST',
|
||||
credentials: 'same-origin',
|
||||
headers: {'Accept': 'application/json'},
|
||||
body: formData
|
||||
}).then((response) => {
|
||||
|
||||
if (response.ok === false) {
|
||||
console.log(response);
|
||||
this.$alertNetworkError.classList.remove('d-none');
|
||||
return response;
|
||||
}
|
||||
|
||||
return response.json();
|
||||
}).then((result) => {
|
||||
this.toggleSubmitBtnState();
|
||||
|
||||
if (result.ok === false) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (result.success) {
|
||||
this.$alertSuccess.innerText = result.message;
|
||||
this.$alertSuccess.classList.remove('d-none');
|
||||
} else {
|
||||
this.$alertWarning.innerText = result.message;
|
||||
this.$alertWarning.classList.remove('d-none');
|
||||
}
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
toggleSubmitBtnState (isProcessing) {
|
||||
this.$submit.disabled = !isProcessing;
|
||||
|
||||
this.$submitProcessing.classList.toggle('d-none');
|
||||
this.$submitDefault.classList.toggle('d-none');
|
||||
}
|
||||
}
|
2
resources/assets/js/fontawesome.js
vendored
2
resources/assets/js/fontawesome.js
vendored
@ -4,6 +4,7 @@ import { faBan } from '@fortawesome/free-solid-svg-icons/faBan';
|
||||
import { faBookmark } from '@fortawesome/free-solid-svg-icons/faBookmark';
|
||||
import { faCaretDown } from '@fortawesome/free-solid-svg-icons/faCaretDown';
|
||||
import { faCheck } from '@fortawesome/free-solid-svg-icons/faCheck';
|
||||
import { faCog } from '@fortawesome/free-solid-svg-icons/faCog';
|
||||
import { faEdit } from '@fortawesome/free-solid-svg-icons/faEdit';
|
||||
import { faEnvelope } from '@fortawesome/free-solid-svg-icons/faEnvelope';
|
||||
import { faExternalLinkAlt } from '@fortawesome/free-solid-svg-icons/faExternalLinkAlt';
|
||||
@ -150,6 +151,7 @@ export function initFontAwesome () {
|
||||
library.add(faBookmark);
|
||||
library.add(faCaretDown);
|
||||
library.add(faCheck);
|
||||
library.add(faCog);
|
||||
library.add(faEdit);
|
||||
library.add(faEnvelope);
|
||||
library.add(faExternalLinkAlt);
|
||||
|
@ -2,10 +2,13 @@
|
||||
return [
|
||||
'import' => 'Import',
|
||||
'start_import' => 'Start Import',
|
||||
'import_running' => 'Import running...',
|
||||
'import_file' => 'File for Import',
|
||||
|
||||
'import_help' => 'You can import your existing browser bookmarks here. Usually, bookmarks are exported into an .html file by your browser. Select the file here and start the import.<br>Depending on the number of bookmarks this process may take some time.',
|
||||
|
||||
'import_networkerror' => 'Something went wrong while trying to import the bookmarks. Please check your browser console for details or consult the application logs.',
|
||||
'import_error' => 'Something went wrong while trying to import the bookmarks. Please consult the application logs.',
|
||||
'import_empty' => 'Could not import any bookmarks. Either the uploaded file is corrupt or empty.',
|
||||
'import_successfully' => ':imported links imported successfully, :skipped skipped.',
|
||||
];
|
||||
|
@ -8,8 +8,7 @@
|
||||
</div>
|
||||
<div class="card-body">
|
||||
|
||||
<form id="import-form" action="{{ route('do-import') }}" method="post" enctype="multipart/form-data">
|
||||
@csrf
|
||||
<form class="import-form" data-action="{{ route('do-import') }}" data-csrf="{{ csrf_token() }}">
|
||||
|
||||
<p>@lang('import.import_help')</p>
|
||||
|
||||
@ -26,9 +25,21 @@
|
||||
@endif
|
||||
</div>
|
||||
|
||||
<button type="submit" class="btn btn-primary import-submit">
|
||||
<i class="fas fa-file-import mr-2"></i>
|
||||
@lang('import.start_import')
|
||||
<div class="import-alerts">
|
||||
<div class="import-alert-networkerror alert alert-danger d-none">@lang('import.import_error')</div>
|
||||
<div class="import-alert-warning alert alert-warning d-none"></div>
|
||||
<div class="import-alert-success alert alert-success d-none"></div>
|
||||
</div>
|
||||
|
||||
<button type="button" class="btn btn-primary import-submit">
|
||||
<span class="import-submit-processing d-none">
|
||||
<i class="fas fa-cog fa-spin mr-2"></i>
|
||||
@lang('import.import_running')
|
||||
</span>
|
||||
<span class="import-submit-default">
|
||||
<i class="fas fa-file-import mr-2"></i>
|
||||
@lang('import.start_import')
|
||||
</span>
|
||||
</button>
|
||||
|
||||
</form>
|
||||
|
@ -2,8 +2,6 @@
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
|
||||
<meta name="csrf-token" content="{{ csrf_token() }}">
|
||||
|
||||
<title>{{ systemsettings('system_page_title') ?: config('app.name', 'LinkAce') }}</title>
|
||||
|
||||
@include('partials.favicon')
|
||||
|
@ -1,6 +1,6 @@
|
||||
<?php
|
||||
|
||||
namespace Tests\Database;
|
||||
namespace Tests\Controller\App;
|
||||
|
||||
use App\Models\Link;
|
||||
use App\Models\User;
|
||||
@ -39,9 +39,14 @@ class ImportControllerTest extends TestCase
|
||||
|
||||
$response = $this->post('import', [
|
||||
'import-file' => $file,
|
||||
], [
|
||||
'Accept' => 'application/json',
|
||||
]);
|
||||
|
||||
$response->assertStatus(302);
|
||||
$response->assertStatus(200)
|
||||
->assertJson([
|
||||
'success' => true,
|
||||
]);
|
||||
|
||||
$linkCount = Link::count();
|
||||
$this->assertEquals(5, $linkCount);
|
||||
|
Loading…
x
Reference in New Issue
Block a user