diff --git a/.env.example b/.env.example index 7a05118b..da603ceb 100644 --- a/.env.example +++ b/.env.example @@ -11,6 +11,8 @@ TESTING_MENU=true GOOGLE_ANALYTICS_ID= INFLUUENT_API_KEY= WOS_TOKEN= +ORCID_TOKEN= +ACADEMIC_ANALYTICS_KEY= DB_CONNECTION=mysql DB_HOST= diff --git a/app/Console/Commands/AddDoiToExistingPublications.php b/app/Console/Commands/AddDoiToExistingPublications.php new file mode 100644 index 00000000..b8d12927 --- /dev/null +++ b/app/Console/Commands/AddDoiToExistingPublications.php @@ -0,0 +1,216 @@ +argument('starting_character'); + + $profiles = Cache::remember( + "profiles-starting-with-{$starting_character}", + 15 * 60, + fn() => $this->profilesMissingDoi($starting_character) + ); + + $profiles_bar = $this->output->createProgressBar(count($profiles)); + $profiles_bar->setFormat('debug'); + $profiles_bar->start(); + + $this->lineAndLog("********** {$profiles->count()} Profiles found with publications without DOI ************ \n"); + + foreach ($profiles as $profile) { + + $profile_publications = $profile->data; + + $publications_bar = $this->output->createProgressBar(count($profile->data)); + $publications_bar->setFormat('debug', '\n'); + $publications_bar->start(); + + $aa_publications = $profile->cachedAAPublications(); + + $publications_found_in_url = $publications_found_in_title = $publications_found_in_aa = $doi_not_found_counter = 0; + + $this->lineAndLog("********** {$profile_publications->count()} Publications found without DOI for {$profile->full_name} ************\n"); + + foreach ($profile_publications as $publication) { + + $this->lineAndLog( "--------- {$publication->id} -----------\n"); + + $doi = $aa_title = null; + + // Search in the URL + if (!empty($publication->url)) { + $this->lineAndLog("Searching for DOI in URL..."); + $doi = $this->validateDoiRegex($publication->url); + + $this->verifyAndSaveDoi($publication, $doi, 'url', $publications_found_in_url, $publications_found_in_title, $publications_found_in_aa); + + } + + // Search in the title + if (is_null($doi) && !empty($publication->title)) { + $this->lineAndLog("Searching for DOI in the title..."); + $doi = $this->validateDoiRegex(strip_tags(html_entity_decode($publication->title))); + + $this->verifyAndSaveDoi($publication, $doi, 'title', $publications_found_in_url, $publications_found_in_title, $publications_found_in_aa); + + } + + // Match the AA Publication + if (is_null($doi)) { + $this->lineAndLog("Searching for DOI in Academic Analytics..."); + + $aa_pub_found = $profile->searchPublicationByTitleAndYear($publication->title, $publication->year, $aa_publications ?? $profile->cachedAAPublications()); + + $doi = $aa_pub_found[0]; + $aa_title = $aa_pub_found[1]; + + $this->verifyAndSaveDoi($publication, $doi, 'Academic Analytics', $publications_found_in_url, $publications_found_in_title, $publications_found_in_aa, $aa_title); + } + + if (is_null($doi)) { $doi_not_found_counter++; } + + $publications_bar->advance(); + } + + $publications_bar->finish(); + + $this->lineAndLogResults($publications_found_in_url, $publications_found_in_title, $publications_found_in_aa, $doi_not_found_counter, $profile->full_name); + + $profiles_bar->advance(); + + } + + $profiles_bar->finish(); + + return Command::SUCCESS; + } + + /** + * Verify given doi. If the doi is not null, updates the profile_data record and increment the counter of doi's found. + */ + public function verifyAndSaveDoi($publication, $doi, $search_field, &$publications_found_in_url, &$publications_found_in_title, &$publications_found_in_aa, $aa_title = null) + { + + if (is_null($doi)) { + $this->line("Not Found"); + $this->log("DOI not found in {$search_field}."); + } + else { + + $publication->updateData(['doi' => $doi]); + if (!is_null($aa_title)) { + $publication->insertData(['aa_title' => $aa_title]); + $this->log("Title {$publication->title} found as {$aa_title} in Academic Analytics Publications\n."); + } + + $publication->save(); + $this->lineAndLog("DOI {$doi} FOUND IN {$search_field} and updated."); + + switch ($search_field) + { + case 'title': + ++$publications_found_in_title; + break; + case 'url': + ++$publications_found_in_url; + break; + case 'Academic Analytics': + ++$publications_found_in_aa; + break; + } + + } + + } + + /** + * Validate DOI regex + * @return String + */ + public function validateDoiRegex($doi_expression) + { + $doi_regex= '/(10[.][0-9]{4,}[^\s"\/<>]*\/[^\s"<>]+)/'; + + $doi = null; + + preg_match($doi_regex, $doi_expression, $result); + + if (!empty($result[1])) { + $doi = rtrim(trim($result[1], "\xC2\xA0"), '.'); + } + + return $doi; + } + + /** + * Retrieves profiles with publications that have DOI missing + * + * @return Collection + */ + public function profilesMissingDoi(string $starting_character) + { + return Profile::where('last_name', 'like', strtolower($starting_character).'%') + ->orWhere('last_name', 'like', strtoupper($starting_character).'%') + ->withWhereHas('data', function($q) { + $q->where('type', 'publications') + ->whereNull('data->doi'); + })->get(); + } + + /** + * Output total numbers for each profile processed to the console and log file + */ + public function lineAndLogResults($publications_found_in_url, $publications_found_in_title, $publications_found_in_aa, $doi_not_found_counter, $profile_full_name) + { + $this->lineAndLog("TOTAL:"); + if ($publications_found_in_url > 0 ) { $this->lineAndLog("{$publications_found_in_url} DOI found by url and added successfully."); } + if ($publications_found_in_title > 0 ) { $this->lineAndLog("{$publications_found_in_title} DOI found by title and added successfully."); } + if ($publications_found_in_aa > 0 ) { $this->lineAndLog("{$publications_found_in_aa} DOI found in Academic Analytics and added successfully."); } + $this->lineAndLog("{$doi_not_found_counter} PUBLICATIONS NOT FOUND.", 'error'); + $this->lineAndLog("***************** End of Report for {$profile_full_name} ********************"); + } + + /** + * Output a message to the console and log file + */ + public function lineAndLog(string $message, string $type = 'info'): void + { + $this->line($message, $type); + Log::$type($message); + } + + /** + * Output a message to the log file + */ + public function Log(string $message, string $type = 'info'): void + { + Log::$type($message); + } +} diff --git a/app/Http/Controllers/ProfilesController.php b/app/Http/Controllers/ProfilesController.php index 1bbc461f..56d30e70 100644 --- a/app/Http/Controllers/ProfilesController.php +++ b/app/Http/Controllers/ProfilesController.php @@ -237,7 +237,6 @@ public function edit(Profile $profile, $section) ->route('profiles.show', $profile->slug) ->with('flash_message', 'Publications updated via ORCID.'); } - $data = $profile->data()->$section()->get(); // if no data, include one item to use as a template diff --git a/app/Http/Livewire/AcademicAnalyticsPublications.php b/app/Http/Livewire/AcademicAnalyticsPublications.php new file mode 100644 index 00000000..035559cd --- /dev/null +++ b/app/Http/Livewire/AcademicAnalyticsPublications.php @@ -0,0 +1,94 @@ + 'showModal', 'addToEditor', 'removeFromEditor', 'addAllToEditor', 'removeAllFromEditor']; + + public Profile $profile; + + public bool $modalVisible = false; + + public $importedPublications = []; + + public $perPage = 10; + + public bool $allChecked = false; + + public bool $transform = true; + + public $allPublicationsCount; + + public function showModal() + { + $this->modalVisible = true; + } + + public function addToEditor($publicationId) + { + array_push($this->importedPublications, $publicationId); + $this->reset('transform'); + $this->emit( 'alert', "Added to the Editor!", 'success'); + } + + public function removeFromEditor($publicationId) + { + if (($key = array_search($publicationId, $this->importedPublications)) !== false) { + unset($this->importedPublications[$key]); + } + $this->reset('transform'); + $this->emit('alert', "Removed from the Editor!", 'success'); + } + + public function addAllToEditor() + { + $pubs_to_import = $this->getNewAAPublications()->whereIn('imported', false); + $this->importedPublications = $pubs_to_import->pluck('id')->all(); + $this->allChecked = true; + $this->reset('transform'); + $this->emit('JSAddAllToEditor', $pubs_to_import); + } + + public function removeAllFromEditor() + { + $this->reset('importedPublications'); + $this->reset('allChecked'); + $this->transform = false; + } + + public function getNewAAPublications() + { + return $this->profile->cachedAAPublications() + ->whereNotIn('doi', $this->profile->publications->pluck('data.doi')->filter()->values()); + } + + public function getPublicationsProperty() + { + $aaPublications = $this->getNewAAPublications(); + + $this->allPublicationsCount = count($aaPublications); + + if ($this->transform) { + $aaPublications + ->whereIn('id', $this->importedPublications) + ->transform(fn($elem, $key) => $elem->imported = true); + } + + return $aaPublications->sortByDesc('sort_order')->paginate($this->perPage); + } + + public function render() + { + return view('livewire.academic-analytics-publications'); + } +} diff --git a/app/Http/Livewire/ShowModal.php b/app/Http/Livewire/ShowModal.php new file mode 100644 index 00000000..ef8257d0 --- /dev/null +++ b/app/Http/Livewire/ShowModal.php @@ -0,0 +1,18 @@ +emit('loadPublications'); + } +} diff --git a/app/Profile.php b/app/Profile.php index 915284b2..34c80563 100644 --- a/app/Profile.php +++ b/app/Profile.php @@ -4,8 +4,10 @@ use App\ProfileData; use App\ProfileStudent; +use App\Providers\AcademicAnalyticsAPIServiceProvider; use App\Student; use App\User; +use Doctrine\DBAL\Types\IntegerType; use Illuminate\Database\Eloquent\Model; use OwenIt\Auditing\Auditable as HasAudits; use OwenIt\Auditing\Contracts\Auditable; @@ -22,9 +24,9 @@ class Profile extends Model implements HasMedia, Auditable { - use HasAudits; - use HasFactory; - use InteractsWithMedia; + use HasAudits; + use HasFactory; + use InteractsWithMedia; use HasTags; use SoftDeletes; @@ -195,16 +197,19 @@ public function updateORCID() } else if($ref['external-id-type'] == "doi"){ $url = "http://doi.org/" . $ref['external-id-value']; + $doi = $ref['external-id-value']; } } - $record = ProfileData::firstOrCreate([ + $new_record = ProfileData::firstOrCreate([ 'profile_id' => $this->id, 'type' => 'publications', 'data->title' => $record['work-summary'][0]['title']['title']['value'], 'sort_order' => $record['work-summary'][0]['publication-date']['year']['value'] ?? null, + 'data->doi' => $doi, ],[ 'data' => [ 'url' => $url, + 'doi' => $doi, 'title' => $record['work-summary'][0]['title']['title']['value'], 'year' => $record['work-summary'][0]['publication-date']['year']['value'] ?? null, 'type' => ucwords(strtolower(str_replace('_', ' ', $record['work-summary'][0]['type']))), @@ -219,6 +224,123 @@ public function updateORCID() return true; } + /** + * Cache Academic Analytics publications for the current profile + * @return Collection + */ + public function cachedAAPublications() + { + return Cache::remember( + "profile{$this->id}-AA-pubs", + 15 * 60, + fn() => $this->getAcademicAnalyticsPublications() + ); + } + + /** + * Search for a publication by year and title within a given publications collection + * Return array with DOI and matching title + * @return Array + */ + public static function searchPublicationByTitleAndYear($title, $year, $publications) + { + $title = strip_tags(html_entity_decode($title)); + $publication_found = $aa_doi = $aa_title = null; + $publication_found = $publications->filter(function ($item) use ($title, $year) { + return (str_contains(strtolower($title), strtolower(strip_tags(html_entity_decode($item['data']['title'])))) && $year==$item['data']['year']); + similar_text(strtolower($title), strtolower(strip_tags(html_entity_decode($item['data']['title']))), $percent); + return (($percent > 80) && ($year==$item['data']['year'])); + }); + if ($publication_found->count() == 1) { + $aa_doi = $publication_found->first()->doi; + $aa_title = $publication_found->first()->title; + } + return [$aa_doi, $aa_title]; + } + + public function getAcademicAnalyticsPublications() + { + if(isset($this->information()->first()->data['academic_analytics_id'])) { + $academic_analytics_id = $this->information()->first()->data['academic_analytics_id']; + } + else { + $academic_analytics_id = $this->getAAPersonId(); + $this->information()->update(['data->academic_analytics_id' => $academic_analytics_id]); + $this->save(); + } + + $aa_url = "https://api.academicanalytics.com/person/$academic_analytics_id/articles"; + + $client = new Client(); + + $res = $client->get($aa_url, [ + 'headers' => [ + 'apikey' => config('app.academic_analytics_key'), + 'Accept' => 'application/json' + ], + 'http_errors' => false, // don't throw exceptions for 4xx,5xx responses + ]); + + //an error of some sort + if($res->getStatusCode() != 200){ + return false; + } + $datum = json_decode($res->getBody()->getContents(), true); + + $publications = collect(); + + foreach($datum as $key => $record) { + $url = NULL; + + if(isset($record['DOI'])) { + $doi = $record['DOI']; + $url = "http://doi.org/$doi"; + } + + $new_record = ProfileData::newModelInstance([ + 'type' => 'publications', + 'sort_order' => $record['ArticleYear'] ?? null, + 'data' => [ + 'doi' => $doi ?? null, + 'url' => $url ?? null, + 'title' => $record['ArticleTitle'], + 'year' => $record['ArticleYear'] ?? null, + 'type' => "JOURNAL_ARTICLE", //ucwords(strtolower(str_replace('_', ' ', $record['work-summary'][0]['type']))), + 'status' => 'Published' + ], + ]); + $new_record->id = $record['ArticleId']; + $new_record->imported = false; + $publications->push($new_record); + } + return $publications; + } + + public function getAAPersonId(){ + + $client_faculty_id = "{$this->user->name}@utdallas.edu"; + + $aa_url = "https://api.academicanalytics.com/person/GetPersonIdByClientFacultyId?clientFacultyId=$client_faculty_id"; + + $client = new Client(); + + $res = $client->get($aa_url, [ + 'headers' => [ + 'apikey' => config('app.academic_analytics_key'), + 'Accept' => 'application/json' + ], + 'http_errors' => false, // don't throw exceptions for 4xx,5xx responses + ]); + + //an error of some sort + if($res->getStatusCode() != 200){ + return false; + } + $datum = json_decode($res->getBody()->getContents(), true); + + return $datum['PersonId']; + } + public function updateDatum($section, $request) { $sort_order = count($request->data ?? []) + 1; @@ -292,7 +414,7 @@ public function updateDatum($section, $request) /** * Strips HTML tags from the specified data field. - * + * * This is only for output purposes and does not save. * * @param array $data_names : the names of data properties to strip tags from @@ -427,7 +549,7 @@ public function scopeContaining($query, $search, $type = null) /** * Query scope for Profiles that have the given tag (case-insensitive) - * + * * @param \Illuminate\Database\Eloquent\Builder $query * @param string $tag * @return \Illuminate\Database\Eloquent\Builder @@ -467,9 +589,9 @@ public function scopeFromSchoolId($query, $school_id) }); } /** - * Query scope for Profiles and eager load students whose application is pending review + * Query scope for Profiles and eager load students whose application is pending review * for a given semester. - * + * * @param \Illuminate\Database\Eloquent\Builder $query * @param string $semester * @return \Illuminate\Database\Eloquent\Builder @@ -484,9 +606,9 @@ public function scopeEagerStudentsPendingReviewWithSemester($query, $semester) } /** - * Query scope for Profiles with students whose application is pending review + * Query scope for Profiles with students whose application is pending review * for a given semester. - * + * * @param \Illuminate\Database\Eloquent\Builder $query * @param string $semester * @return \Illuminate\Database\Eloquent\Builder diff --git a/app/Providers/AppServiceProvider.php b/app/Providers/AppServiceProvider.php index 5091b6af..1f233794 100644 --- a/app/Providers/AppServiceProvider.php +++ b/app/Providers/AppServiceProvider.php @@ -5,6 +5,8 @@ use Illuminate\Support\ServiceProvider; use Illuminate\Support\Facades\Blade; use Illuminate\Pagination\Paginator; +use Illuminate\Support\Collection; +use Illuminate\Pagination\LengthAwarePaginator; use Illuminate\Support\Facades\View; use App\Setting; use Collective\Html\FormFacade as Form; @@ -24,6 +26,21 @@ public function boot() Paginator::defaultView('vendor.pagination.default'); Paginator::defaultSimpleView('vendor.pagination.simple-default'); + Collection::macro('paginate', function($perPage, $total = null, $page = null, $pageName = 'page') { + $page = $page ?: LengthAwarePaginator::resolveCurrentPage($pageName); + + return new LengthAwarePaginator( + $this->forPage($page, $perPage), + $total ?: $this->count(), + $perPage, + $page, + [ + 'path' => LengthAwarePaginator::resolveCurrentPath(), + 'pageName' => $pageName, + ] + ); + }); + Form::component('inlineErrors', 'errors.inline', ['field_name']); View::composer([ diff --git a/config/app.php b/config/app.php index fe3b8316..7cfdd9c9 100644 --- a/config/app.php +++ b/config/app.php @@ -67,6 +67,9 @@ /** API response cache-control headers */ 'api_cache_control' => env('API_CACHE_CONTROL', 'public;no_cache;etag'), + /** ACADEMIC ANALYTICS API KEY */ + 'academic_analytics_key' => env('ACADEMIC_ANALYTICS_KEY', false), + /* |-------------------------------------------------------------------------- | Application Debug Mode @@ -91,6 +94,7 @@ 'PUSHER_APP_KEY', 'PUSHER_APP_SECRET', 'AWS_SECRET', + 'ACADEMIC_ANALYTICS_KEY' ], '_SERVER' => [ 'APP_KEY', diff --git a/config/queue.php b/config/queue.php deleted file mode 100644 index 4d83ebd0..00000000 --- a/config/queue.php +++ /dev/null @@ -1,85 +0,0 @@ - env('QUEUE_DRIVER', 'sync'), - - /* - |-------------------------------------------------------------------------- - | Queue Connections - |-------------------------------------------------------------------------- - | - | Here you may configure the connection information for each server that - | is used by your application. A default configuration has been added - | for each back-end shipped with Laravel. You are free to add more. - | - */ - - 'connections' => [ - - 'sync' => [ - 'driver' => 'sync', - ], - - 'database' => [ - 'driver' => 'database', - 'table' => 'jobs', - 'queue' => 'default', - 'retry_after' => 90, - ], - - 'beanstalkd' => [ - 'driver' => 'beanstalkd', - 'host' => 'localhost', - 'queue' => 'default', - 'retry_after' => 90, - ], - - 'sqs' => [ - 'driver' => 'sqs', - 'key' => 'your-public-key', - 'secret' => 'your-secret-key', - 'prefix' => 'https://sqs.us-east-1.amazonaws.com/your-account-id', - 'queue' => 'your-queue-name', - 'region' => 'us-east-1', - ], - - 'redis' => [ - 'driver' => 'redis', - 'connection' => 'default', - 'queue' => 'default', - 'retry_after' => 90, - ], - - ], - - /* - |-------------------------------------------------------------------------- - | Failed Queue Jobs - |-------------------------------------------------------------------------- - | - | These options configure the behavior of failed queue job logging so you - | can control which database and table are used to store the jobs that - | have failed. You may change them to any database / table you wish. - | - */ - - 'failed' => [ - 'database' => env('DB_CONNECTION', 'mysql'), - 'table' => 'failed_jobs', - ], - -]; diff --git a/public/js/app.js b/public/js/app.js index e22a0c9d..5670e74b 100644 --- a/public/js/app.js +++ b/public/js/app.js @@ -104,6 +104,11 @@ var profiles = function ($, undefined) { var new_id = String(item_template.parentElement.dataset.nextRowId--); var new_item = item_template.cloneNode(true); new_item.dataset.rowId = new_id; + + if ('customId' in options) { + new_item.dataset.customId = options.customId; + } + (_new_item$querySelect = new_item.querySelectorAll('input:not([type="button"]), textarea, select')) === null || _new_item$querySelect === void 0 ? void 0 : _new_item$querySelect.forEach(function (el) { el.id = el.id.replace(old_id, new_id); el.setAttribute('name', el.name.replace(old_id, new_id)); @@ -155,6 +160,7 @@ var profiles = function ($, undefined) { } $(new_item).slideDown(); + return new_item; } }; /** @@ -211,7 +217,7 @@ var profiles = function ($, undefined) { }; /** * Display a dynamic toast alert - * + * * @param {String} message - the message to display * @param {String} type - alert type, e.g. primary, success, warning, danger, and etc. */ @@ -247,7 +253,7 @@ var profiles = function ($, undefined) { }; /** * Deobfuscate an email address - * + * * @param {String} obfuscated_mail_address - the obfuscated * @see App\Helpers\Utils for obfuscation strategy */ @@ -365,7 +371,7 @@ var profiles = function ($, undefined) { }; /** * Registers and enables any profile pickers on the page - * + * * @return {void} */ @@ -379,7 +385,7 @@ var profiles = function ($, undefined) { }; /** * Registers and enables any tag editors on the page. - * + * * @return {void} */ @@ -442,7 +448,7 @@ var profiles = function ($, undefined) { }; /** * Posts updated tags to the API URL. - * + * * @param {jQuery} $select the select element containing the tags * @return {void} */ diff --git a/public/mix-manifest.json b/public/mix-manifest.json index ec6d6ce2..68edc35b 100644 --- a/public/mix-manifest.json +++ b/public/mix-manifest.json @@ -1,5 +1,5 @@ { - "/js/app.js": "/js/app.js?id=b97e0046b3c3173a61892478e0c177c2", + "/js/app.js": "/js/app.js?id=57de5d7a8bbbcd7452301b844d56d13a", "/js/manifest.js": "/js/manifest.js?id=dc9ead3d7857b522d7de22d75063453c", "/css/app.css": "/css/app.css?id=a024f4849068ba5d9ae986461809379c", "/js/vendor.js": "/js/vendor.js?id=fd544e04e787f8d741ee219386d45238" diff --git a/resources/assets/js/app.js b/resources/assets/js/app.js index 97a1dbf6..fae5cdfb 100644 --- a/resources/assets/js/app.js +++ b/resources/assets/js/app.js @@ -90,6 +90,10 @@ var profiles = (function ($, undefined) { let new_item = item_template.cloneNode(true); new_item.dataset.rowId = new_id; + if('customId' in options) { + new_item.dataset.customId = options.customId; + } + new_item.querySelectorAll('input:not([type="button"]), textarea, select')?.forEach((el) => { el.id = el.id.replace(old_id, new_id); el.setAttribute('name', el.name.replace(old_id, new_id)); @@ -134,6 +138,8 @@ var profiles = (function ($, undefined) { item_template.parentElement.append(new_item); } $(new_item).slideDown(); + + return new_item; } } @@ -190,7 +196,7 @@ var profiles = (function ($, undefined) { /** * Display a dynamic toast alert - * + * * @param {String} message - the message to display * @param {String} type - alert type, e.g. primary, success, warning, danger, and etc. */ @@ -211,7 +217,7 @@ var profiles = (function ($, undefined) { flash_message.innerHTML = message; flash_container.appendChild(flash_message); - + flash_message.addEventListener('click', (e) => {e.target.style.display = 'none'}); $(flash_message).animate({opacity: 0}, { duration: 5000, @@ -221,7 +227,7 @@ var profiles = (function ($, undefined) { /** * Deobfuscate an email address - * + * * @param {String} obfuscated_mail_address - the obfuscated * @see App\Helpers\Utils for obfuscation strategy */ @@ -320,7 +326,7 @@ var profiles = (function ($, undefined) { /** * Registers and enables any profile pickers on the page - * + * * @return {void} */ let registerProfilePickers = () => { @@ -333,7 +339,7 @@ var profiles = (function ($, undefined) { /** * Registers and enables any tag editors on the page. - * + * * @return {void} */ var registerTagEditors = function () { @@ -391,7 +397,7 @@ var profiles = (function ($, undefined) { /** * Posts updated tags to the API URL. - * + * * @param {jQuery} $select the select element containing the tags * @return {void} */ diff --git a/resources/views/livewire/academic-analytics-publications.blade.php b/resources/views/livewire/academic-analytics-publications.blade.php new file mode 100644 index 00000000..191dcf5c --- /dev/null +++ b/resources/views/livewire/academic-analytics-publications.blade.php @@ -0,0 +1,82 @@ +
+ + + @push('scripts') + + @endpush + @stack('row-scripts') +
diff --git a/resources/views/livewire/partials/_import-aa-publication.blade.php b/resources/views/livewire/partials/_import-aa-publication.blade.php new file mode 100644 index 00000000..4e1027b4 --- /dev/null +++ b/resources/views/livewire/partials/_import-aa-publication.blade.php @@ -0,0 +1,108 @@ +
+ @if($pub->imported == true) + + @else + + @endif + + @pushOnce('row-scripts') + + @endPushOnce +
diff --git a/resources/views/profiles/edit/_autosort_info.blade.php b/resources/views/profiles/edit/_autosort_info.blade.php index 0233fba3..f92c093f 100644 --- a/resources/views/profiles/edit/_autosort_info.blade.php +++ b/resources/views/profiles/edit/_autosort_info.blade.php @@ -9,5 +9,9 @@ To manually sort all entries, leave the Year field empty (and include it as a part of @if(isset($suggestion)) {{ $suggestion }} @else another field @endisset instead). +
  • + To review your publications available for import from external sources: + +
  • diff --git a/resources/views/profiles/edit/layout.blade.php b/resources/views/profiles/edit/layout.blade.php index 0c971312..45417710 100644 --- a/resources/views/profiles/edit/layout.blade.php +++ b/resources/views/profiles/edit/layout.blade.php @@ -11,7 +11,6 @@
    @yield('form') -
    @include('profiles.edit._insert_button', ['type' => 'append']) diff --git a/resources/views/profiles/edit/publications.blade.php b/resources/views/profiles/edit/publications.blade.php index 9ec8d4e2..5aadce99 100644 --- a/resources/views/profiles/edit/publications.blade.php +++ b/resources/views/profiles/edit/publications.blade.php @@ -11,14 +11,20 @@
    @include('profiles.edit._actions')
    -
    +
    +
    + + +
    +