From e44ab70695dc1b28097b47a0c959bb12bf81bb35 Mon Sep 17 00:00:00 2001 From: Vincent Vanwaelscappel Date: Fri, 6 Jan 2023 10:44:57 +0100 Subject: [PATCH] . --- app/Console/Kernel.php | 14 +- app/Http/Controllers/FrontController.php | 219 ++++++++++++++++-- app/Jobs/DownloadAudioTracks.php | 10 +- app/Jobs/OptimizeMP3.php | 3 - app/Jobs/PitchShiftAudio.php | 8 +- app/Jobs/SyncNotion.php | 30 +++ app/Models/Collection.php | 4 + app/Notion/Song.php | 55 +++++ composer.json | 15 +- resources/css/app.sass | 38 +++ .../dancing-script-v24-latin-regular.woff2 | Bin 0 -> 23588 bytes resources/js/mmenu.js | 11 +- resources/js/player.js | 20 +- resources/views/layout.blade.php | 16 +- resources/views/notion_collection.blade.php | 18 ++ .../views/notion_collection_song.blade.php | 5 + resources/views/notion_menu.blade.php | 144 ++++++++++++ resources/views/notion_menu_song.blade.php | 6 + resources/views/notion_song.blade.php | 51 ++++ 19 files changed, 602 insertions(+), 65 deletions(-) create mode 100644 app/Jobs/SyncNotion.php create mode 100644 app/Notion/Song.php create mode 100644 resources/css/fonts/dancing-script-v24-latin-regular.woff2 create mode 100644 resources/views/notion_collection.blade.php create mode 100644 resources/views/notion_collection_song.blade.php create mode 100644 resources/views/notion_menu.blade.php create mode 100644 resources/views/notion_menu_song.blade.php create mode 100644 resources/views/notion_song.blade.php diff --git a/app/Console/Kernel.php b/app/Console/Kernel.php index 7fa6570..2bf8bf2 100644 --- a/app/Console/Kernel.php +++ b/app/Console/Kernel.php @@ -5,8 +5,7 @@ namespace App\Console; use Illuminate\Console\Scheduling\Schedule; use Illuminate\Foundation\Console\Kernel as ConsoleKernel; -class Kernel extends ConsoleKernel -{ +class Kernel extends ConsoleKernel { /** * The Artisan commands provided by your application. * @@ -19,12 +18,12 @@ class Kernel extends ConsoleKernel /** * Define the application's command schedule. * - * @param \Illuminate\Console\Scheduling\Schedule $schedule + * @param \Illuminate\Console\Scheduling\Schedule $schedule * @return void */ - protected function schedule(Schedule $schedule) - { + protected function schedule(Schedule $schedule) { $schedule->command('job:dispatchNow PitchShiftAudio')->hourly(); + $schedule->command('job:dispatchNow SyncNotion')->everyTenMinutes(); } /** @@ -32,9 +31,8 @@ class Kernel extends ConsoleKernel * * @return void */ - protected function commands() - { - $this->load(__DIR__.'/Commands'); + protected function commands() { + $this->load(__DIR__ . '/Commands'); require base_path('routes/console.php'); } diff --git a/app/Http/Controllers/FrontController.php b/app/Http/Controllers/FrontController.php index 39a7413..5df42fd 100644 --- a/app/Http/Controllers/FrontController.php +++ b/app/Http/Controllers/FrontController.php @@ -8,17 +8,20 @@ use App\Models\Song; use Cubist\Backpack\Http\Controllers\CubistPWAController; use Cubist\Util\Files\Files; use Illuminate\Support\Facades\Session; +use Notion\Blocks\BlockInterface; +use Notion\Blocks\Paragraph; +use Notion\Common\RichText; +use Notion\Notion; +use Notion\Pages\Properties\RichTextProperty; +use Notion\Pages\Properties\Title; -class FrontController extends Controller -{ - public static function defaultCollection() - { +class FrontController extends Controller { + public static function defaultCollection() { return redirect('/' . Collection::withoutGlobalScope('ownerclause')->find(1)->slug); } - public function collection($name) - { + public function collection($name) { $collection = Collection::withoutGlobalScope('ownerclause')->where('slug', $name)->first(); if (null === $collection) { return self::defaultCollection(); @@ -26,14 +29,27 @@ class FrontController extends Controller if ($p = $this->checkPassword($collection)) { return $p; } + + if ($collection->notion_key && $collection->notion_database) { + return $this->notionCollection($collection); + } + $lists = CollectionList::withoutGlobalScope('ownerclause')->where('collection', $collection->id)->get(); $songs = $this->_getSongsOfCollection($collection->id, $lists); return view('collection', ['menu' => true, 'songs' => $songs, 'collection' => $collection, 'collection_songs' => $songs, 'collection_lists' => $lists]); } - public function checkPassword(Collection $collection) - { + /** + * @throws \Exception + */ + public function notionCollection($collection) { + $songs = self::getSongsOfNotionCollection($collection); + return view('notion_collection', ['menu' => true, 'notion' => true, 'songs' => $songs, 'collection' => $collection, 'collection_songs' => $songs]); + } + + + public function checkPassword(Collection $collection) { if (!$collection->password) { return false; } @@ -51,13 +67,15 @@ class FrontController extends Controller return view('login', ['menu' => false, 'collection' => $collection, 'error' => $error]); } - public function song($collection, $song) - { + public function song($collection, $song) { /** @var Collection $collection */ $collection = Collection::withoutGlobalScope('ownerclause')->where('slug', $collection)->first(); if (null === $collection) { abort(404); } + if ($collection->notion_key && $collection->notion_database) { + return $this->notionSong($collection, $song); + } /** @var Song $song */ $song = Song::withoutGlobalScope('ownerclause')->where('slug', $song)->first(); if (null === $song) { @@ -66,6 +84,8 @@ class FrontController extends Controller if ($p = $this->checkPassword($collection)) { return $p; } + + $lists = CollectionList::withoutGlobalScope('ownerclause')->where('collection', $collection->id)->get(); $partition = false; $lyrics_html = ''; @@ -102,8 +122,21 @@ class FrontController extends Controller return view('song', ['menu' => true, 'lyrics_html' => $lyrics_html, 'song' => $song, 'collection' => $collection, 'partition' => $partition, 'collection_songs' => $this->_getSongsOfCollection($collection->id, $lists), 'collection_lists' => $lists]); } - protected function _getSongsOfCollection($id, $lists) - { + protected function notionSong($collection, $song) { + $partition = false; + + $u = $song . '.html'; + $songs = self::getSongsOfNotionCollection($collection); + foreach ($songs as $song) { + if ($song->getUrl() === $u) { + break; + } + } + $lyrics_html = $song->getLyrics(); + return view('notion_song', ['menu' => true, 'notion' => true, 'lyrics_html' => $lyrics_html, 'song' => $song, 'collection' => $collection, 'collection_songs' => $songs]); + } + + protected function _getSongsOfCollection($id, $lists) { /** @var Song $q */ $q = Song::withoutGlobalScope('ownerclause')->whereRaw('json_contains(collections, \'["' . $id . '"]\')'); foreach ($lists as $list) { @@ -113,8 +146,145 @@ class FrontController extends Controller } - public function manifest($collection) - { + /** + * @param $collection Collection + * @return \App\Notion\Song[] + * @throws \Exception + */ + public static function getSongsOfNotionCollection($collection, $force = false) { + $cacheKey = 'songs_notion_' . $collection->notion_database . '_' . $collection->id; + $ttl = 900; + if ($force) { + $v = self::_getSongsOfNotionCollection($collection); + cache()->set($cacheKey, $v, $ttl); + } else { + return cache()->remember($cacheKey, $ttl, function () use ($collection) { + return self::_getSongsOfNotionCollection($collection); + }); + } + } + + + protected static function _getSongsOfNotionCollection($collection) { + + $audios = ['mp3', 'm4a', 'wav']; + $notion = Notion::create($collection->notion_key); + $database = $notion->databases()->find($collection->notion_database); + $songs = $notion->databases()->queryAllPages($database); + + $res = []; + foreach ($songs as $song) { + $pageContents = ''; + try { + $contents = $notion->blocks()->findChildren($song->id); + foreach ($contents as $content) { + $pageContents .= self::_blockToHtml($content); + } + } catch (\Exception $e) { + + } + + $s = []; + + /** @var Title $titleProperty */ + $titleProperty = $song->getProprety('Titre'); + /** @var Number $orderProperty */ + $orderProperty = $song->getProprety('Ordre'); + /** @var RichTextProperty $lyricsProperty */ + $lyricsProperty = $song->getProprety('Paroles'); + /** @var \Notion\Pages\Properties\Files $filesProperty */ + $filesProperty = $song->getProprety('Files & media'); + + $s['id'] = $song->id; + $s['order'] = (int)$orderProperty->number; + $s['title'] = $titleProperty->toString(); + $s['lyrics'] = self::_blockToHtml($lyricsProperty); + $s['notes'] = $pageContents; + $s['notion_url'] = $song->url; + + + $s['files'] = []; + $s['audios'] = []; + foreach ($filesProperty->files as $file) { + $ext = self::parseExtensionFromAmazonURL($file->url); + if (in_array($ext, $audios)) { + $s['audios'][] = ['name' => self::parseFileNameFromAmazonURL($file->url), 'url' => $file->url, 'ext' => $ext]; + } else { + $s['files'][] = ['name' => self::parseFileNameFromAmazonURL($file->url), 'url' => $file->url, 'ext' => $ext]; + } + } + + $res[$s['order']] = new \App\Notion\Song($s, $collection->notion_database); + } + ksort($res); + return $res; + + } + + /** + * @param $block RichTextProperty|BlockInterface + * @return string + */ + protected static function _blockToHtml($block) { + $start = ''; + $end = ''; + if ($block instanceof Paragraph) { + $start = '

'; + $end = '

'; + } + $res = ''; + foreach ($block->text as $rt) { + $res .= self::_richTextToHtml($rt); + } + $res = $start . $res . $end; + return str_replace('
', '', $res); + } + + /** + * @param $rt RichText + * @return string + */ + protected static function _richTextToHtml($rt) { + $start = ''; + $end = ''; + if ($rt->annotations->isBold) { + $start = '' . $start; + $end = $end . ''; + } + if ($rt->annotations->isItalic) { + $start = '' . $start; + $end = $end . ''; + } + if ($rt->annotations->isUnderline) { + $start = '

' . $start; + $end = $end . '

'; + } + if ($rt->annotations->isCode) { + $start = '
' . $start; + $end = $end . '
'; + } + return $start . nl2br($rt->plainText) . $end; + } + + public static function parseFileNameFromAmazonURL($url) { + $u = parse_url($url); + $p = explode('/', $u['path']); + $f = array_pop($p); + $e = explode('.', $f); + array_pop($e); + $f = implode('.', $e); + return str_replace('_', ' ', $f); + } + + public static function parseExtensionFromAmazonURL($url) { + $u = parse_url($url); + $p = explode('/', $u['path']); + $f = array_pop($p); + $e = explode('.', $f); + return array_pop($e); + } + + public function manifest($collection) { $collection = Collection::withoutGlobalScope('ownerclause')->where('slug', $collection)->first(); if (null === $collection) { abort(404); @@ -133,21 +303,20 @@ class FrontController extends Controller $icons[count($icons) - 1]['purpose'] = 'maskable'; $res = ['name' => $collection->name, - 'short_name' => $collection->shortname ?: $collection->name, - 'description' => '', - 'display' => 'standalone', - 'orientation' => 'any', - 'background_color' => $collection->splashscreen_color, - 'theme_color' => $collection->theme_color, - 'start_url' => '/' . $collection->slug, - 'scope' => '/' . $collection->slug, - 'icons' => $icons]; + 'short_name' => $collection->shortname ?: $collection->name, + 'description' => '', + 'display' => 'standalone', + 'orientation' => 'any', + 'background_color' => $collection->splashscreen_color, + 'theme_color' => $collection->theme_color, + 'start_url' => '/' . $collection->slug, + 'scope' => '/' . $collection->slug, + 'icons' => $icons]; return response(json_encode($res))->header('Content-Type', 'application/manifest+json'); } - public function downloadAssets($songId) - { + public function downloadAssets($songId) { /** @var Song $song */ $song = Song::withoutGlobalScope('ownerclause')->find($songId); $fields = ['partition', 'lyrics_doc']; diff --git a/app/Jobs/DownloadAudioTracks.php b/app/Jobs/DownloadAudioTracks.php index 6fcf745..ad4fc64 100644 --- a/app/Jobs/DownloadAudioTracks.php +++ b/app/Jobs/DownloadAudioTracks.php @@ -3,19 +3,13 @@ namespace App\Jobs; use App\Models\Song; +use Cubist\Backpack\Jobs\Base; use Cubist\Util\Files\Files; use Cubist\Util\PHP; -use Illuminate\Bus\Queueable; -use Illuminate\Contracts\Queue\ShouldQueue; -use Illuminate\Foundation\Bus\Dispatchable; -use Illuminate\Queue\InteractsWithQueue; -use Illuminate\Queue\SerializesModels; use YoutubeDl\Options; use YoutubeDl\YoutubeDl; -class DownloadAudioTracks implements ShouldQueue -{ - use Dispatchable, InteractsWithQueue, Queueable, SerializesModels; +class DownloadAudioTracks extends Base{ public function handle() { diff --git a/app/Jobs/OptimizeMP3.php b/app/Jobs/OptimizeMP3.php index ee56e9e..26a905e 100644 --- a/app/Jobs/OptimizeMP3.php +++ b/app/Jobs/OptimizeMP3.php @@ -5,10 +5,7 @@ namespace App\Jobs; use App\Models\Song; use Cubist\Backpack\Jobs\Base; use Cubist\Util\CommandLine; -use Cubist\Util\Files\Files; use Cubist\Util\PHP; -use YoutubeDl\Options; -use YoutubeDl\YoutubeDl; class OptimizeMP3 extends Base { diff --git a/app/Jobs/PitchShiftAudio.php b/app/Jobs/PitchShiftAudio.php index 7488186..6fee92a 100644 --- a/app/Jobs/PitchShiftAudio.php +++ b/app/Jobs/PitchShiftAudio.php @@ -3,6 +3,7 @@ namespace App\Jobs; use App\Models\Song; +use Cubist\Backpack\Jobs\Base; use Cubist\Util\CommandLine; use Cubist\Util\PHP; use Illuminate\Bus\Queueable; @@ -11,14 +12,11 @@ use Illuminate\Foundation\Bus\Dispatchable; use Illuminate\Queue\InteractsWithQueue; use Illuminate\Queue\SerializesModels; -class PitchShiftAudio implements ShouldQueue -{ - use Dispatchable, InteractsWithQueue, Queueable, SerializesModels; +class PitchShiftAudio extends Base { public static $pitches = [1 => '+1', 2 => '+2', 3 => '+3', 4 => '+4', 5 => '+5', 6 => '+6', 7 => '-5', 8 => '-4', 9 => '-3', 10 => '-2', 11 => '-1']; - public function handle() - { + public function handle() { PHP::neverStop(); DownloadAudioTracks::dispatchSync(); diff --git a/app/Jobs/SyncNotion.php b/app/Jobs/SyncNotion.php new file mode 100644 index 0000000..147c2ce --- /dev/null +++ b/app/Jobs/SyncNotion.php @@ -0,0 +1,30 @@ +get() as $collection) { + if ($collection->notion_key && $collection->notion_database) { + FrontController::getSongsOfNotionCollection($collection, true); + } + } + } +} diff --git a/app/Models/Collection.php b/app/Models/Collection.php index 2815a60..6270f42 100644 --- a/app/Models/Collection.php +++ b/app/Models/Collection.php @@ -11,6 +11,7 @@ use Cubist\Backpack\Magic\Fields\SelectFromModel; use Cubist\Backpack\Magic\Fields\Slug; use Cubist\Backpack\Magic\Fields\Table; use Cubist\Backpack\Magic\Fields\Text; +use Cubist\Backpack\Magic\Fields\URL; use Cubist\Backpack\Magic\Models\CubistMagicAbstractModel; use Illuminate\Database\Eloquent\Builder; use Illuminate\Support\Facades\Auth; @@ -47,6 +48,9 @@ class Collection extends CubistMagicAbstractModel { $this->addField('agenda', FilesOrURL::class, 'Planning / Agenda', ['default' => false, 'database_default' => false]); $this->addField('organisation_name', Text::class, 'Label du lien "Répartition / organisation"', ['default' => false, 'database_default' => false]); $this->addField('organisation', FilesOrURL::class, 'Répartition / organisation', ['default' => false, 'database_default' => false]); + $this->addField('notion_key',Text::class,'Clé Notion'); + $this->addField('notion_database',Text::class,'Base de données Notion'); + $this->addField('notion_home',URL::class,'Home Notion'); } protected function _getFreeFileBaseDirectory() { diff --git a/app/Notion/Song.php b/app/Notion/Song.php new file mode 100644 index 0000000..101fefc --- /dev/null +++ b/app/Notion/Song.php @@ -0,0 +1,55 @@ +getTitle()) . '.html'; + } + + public function getArtist() { + return ''; + } + + public function getTitle() { + return $this->get('title'); + } + + public function getLyrics() { + return $this->get('lyrics'); + } + + public function getId() { + return $this->get('id'); + } + + public function getOrder() { + return $this->get('order'); + } + + public function getNotionURL(){ + return $this->get('notion_url'); + } + + public function hasSimpleChords(){ + return false; + } + + public function getAudioTracks() { + $res = $this->get('audios'); + foreach ($res as $k=>$v) { + $res[$k]['i']=$k; + $res[$k]['opturl']=$v['url']; + } + return $res; + } + + public function getFiles(){ + return $this->get('files'); + } + +} diff --git a/composer.json b/composer.json index 3ed4667..5713631 100644 --- a/composer.json +++ b/composer.json @@ -14,21 +14,26 @@ ], "license": "MIT", "require": { - "php": ">=7.4", + "php": ">=8.1", + "ext-dom": "*", "ext-json": "*", + "ext-libxml": "*", + "ext-redis": "*", "ext-simplexml": "*", "ext-tidy": "*", "ext-zip": "*", - "ext-libxml": "*", - "ext-dom": "*", - "ext-redis": "*", "cubist/cms-back": "dev-master", "fruitcake/laravel-cors": "^2.2", + "guzzlehttp/psr7": "^2.0", "league/csv": "^9.8", + "mariosimao/notion-sdk-php": "^1.0", "mxl/laravel-job": "^1.3", "norkunas/youtube-dl-php": "dev-master", "php-ffmpeg/php-ffmpeg": "^0.18.0", - "phpoffice/phpspreadsheet": "^1.23" + "phpoffice/phpspreadsheet": "^1.25" + }, + "replace": { + "cviebrock/laravel-elasticsearch": "*" }, "require-dev": { "barryvdh/laravel-ide-helper": "^2.10", diff --git a/resources/css/app.sass b/resources/css/app.sass index 54a9a9d..c1e914d 100644 --- a/resources/css/app.sass +++ b/resources/css/app.sass @@ -42,6 +42,12 @@ font-weight: 700 src: url('fonts/roboto-condensed-700.woff2') format('woff2') +@font-face + font-family: 'Dancing Script' + font-style: normal + font-weight: 400 + src: url('fonts/dancing-script-v24-latin-regular.woff2') format('woff2') + $slab: 'Roboto Slab', sans-serif .phpdebugbar @@ -106,11 +112,15 @@ main overflow: hidden text-overflow: ellipsis + span color: #000 @media (prefers-color-scheme: dark) color: #fff font-size: 0.65em + &.order + font-size: 1em + margin-right: 10px li list-style: none @@ -136,6 +146,17 @@ article.song @media (prefers-color-scheme: dark) color: #fff !important + &.fromnotion + margin: 0 !important + font-family: $slab !important + border-bottom: 1px solid currentColor + padding-bottom: 20px + + &.notes + font-family: "Dancing Script", cursive !important + padding-top: 20px + font-size: 1.25em + &.lyrics .lyrics @@ -206,10 +227,27 @@ article.song top: -0.25em opacity: 0.5 + h2 + font-size: 20px + margin: 20px 0 15px + + ul + li + list-style: none + h3 color: var(--theme-color) margin: 5px 0 + a.select_audio + cursor: pointer + + blockquote + border-left: 5px solid var(--theme-color) + padding-left: 20px + opacity: 0.6 + + .lyrics font-family: "Roboto Slab", serif font-weight: 400 diff --git a/resources/css/fonts/dancing-script-v24-latin-regular.woff2 b/resources/css/fonts/dancing-script-v24-latin-regular.woff2 new file mode 100644 index 0000000000000000000000000000000000000000..a78f79d458355b1a3272b98cc56df741f9b3bc63 GIT binary patch literal 23588 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 { $('body').addClass('init'); }); +setTimeout(function () { + $('body').addClass('init'); +}, 2000); +$(function () { + $('body').addClass('init'); +}); + $(window).on('beforeunload', function () { $('body').removeClass('init'); }); @@ -103,12 +110,11 @@ window.setOption = function (name, value) { $('input[name="' + n + '"]').prop('checked', value == '1'); } -function updateSelect(select) { +window.updateSelect = function (select) { var sv = $(select).val(); if (sv === null) { return; } - console.log('Set option from select', $(select).data('name'), sv); $(select).closest('.clickselect').find('span').text($(select).find('option[value="' + $(select).val() + '"]').text()); setOption($(select).data('name'), $(select).val()); updateSongView(); @@ -124,6 +130,7 @@ $(function () { return true; }); + $(document).on('change', '.checkbox-switch', function () { var name = $(this).attr('name'); var checked = $(this).is(':checked') ? '1' : '0'; diff --git a/resources/js/player.js b/resources/js/player.js index 37a9560..0046519 100644 --- a/resources/js/player.js +++ b/resources/js/player.js @@ -9,6 +9,16 @@ resetPlayers(); }); + $(document).on('click', '.select_audio', function () { + var e = $(this).attr('data-id').split('_'); + $('select[data-name="audio"]').val(e[1]); + updateSelect($('select[data-name="audio"]')); + resetPlayers(); + playAudio(); + return false; + }); + + function resetPlayers() { $.each(window.players, function (k, player) { player.stop(); @@ -30,7 +40,12 @@ }); }); $(document).on('click', '[data-action="play"]', function () { - $(this).hide(); + playAudio(); + return false; + }); + + function playAudio() { + $('[data-action="play"]').hide(); var audio = window.getOption('audio').toString(); var t = window.getOption('audio_' + audio + '_tone'); @@ -46,8 +61,7 @@ var p = window.players[pid]; $(p.elements.container).show(); p.play(); - return false; - }); + } function changeAudio() { $('li[data-audio]').hide(); diff --git a/resources/views/layout.blade.php b/resources/views/layout.blade.php index 11bf20e..43068c7 100644 --- a/resources/views/layout.blade.php +++ b/resources/views/layout.blade.php @@ -1,15 +1,19 @@ +@if(isset($notion) && $notion) +@include('notion_menu') +@else @include('menu') - +@endif + @yield('title') @@ -32,7 +36,7 @@ diff --git a/resources/views/notion_collection.blade.php b/resources/views/notion_collection.blade.php new file mode 100644 index 0000000..ff85e94 --- /dev/null +++ b/resources/views/notion_collection.blade.php @@ -0,0 +1,18 @@ +@extends('layout') +@php + $preload=[]; +@endphp +@section('title', $collection->name) + +@section('content') + @include('header',['title'=>$collection->name,'subtitle'=>'']) +

+ +
+@endsection diff --git a/resources/views/notion_collection_song.blade.php b/resources/views/notion_collection_song.blade.php new file mode 100644 index 0000000..6034632 --- /dev/null +++ b/resources/views/notion_collection_song.blade.php @@ -0,0 +1,5 @@ +@php + $songurl='/'.$collection->slug.'/'.$csong->getURL(); +@endphp +
  • {{$csong->getOrder()}} {{$csong->getTitle()}}{{$csong->getArtist()}} +
  • diff --git a/resources/views/notion_menu.blade.php b/resources/views/notion_menu.blade.php new file mode 100644 index 0000000..44c0be5 --- /dev/null +++ b/resources/views/notion_menu.blade.php @@ -0,0 +1,144 @@ +@php + $tones=\App\Field\Tone::getTones(); + $fontSizes=[0.5,0.6,0.7,0.8,0.9,1,1.1,1.25,1.5,1.75,2]; + $prefetch=[]; +@endphp + +@section('menu') + +@endsection + +@section('prefetch') + +@endsection diff --git a/resources/views/notion_menu_song.blade.php b/resources/views/notion_menu_song.blade.php new file mode 100644 index 0000000..334b59a --- /dev/null +++ b/resources/views/notion_menu_song.blade.php @@ -0,0 +1,6 @@ +@php + $songurl="/$collection->slug/".$csong->getUrl(); + $prefetch[]=$songurl; +@endphp +
  • {{trim($csong->getOrder().' - '.$csong->getTitle().' - '.$csong->getArtist(),' -')}} +
  • diff --git a/resources/views/notion_song.blade.php b/resources/views/notion_song.blade.php new file mode 100644 index 0000000..c3c0117 --- /dev/null +++ b/resources/views/notion_song.blade.php @@ -0,0 +1,51 @@ +@php + $audioTracks=$song->getAudioTracks(); + $simple=$song->hasSimpleChords(); +@endphp + +@extends('layout') +@section('title', $song->title.' - '.$song->artist.' - '. $collection->name) +@section('content') + @include('header',['title'=>$song->title,'subtitle'=>$song->artist]) +
    +
    {!! $lyrics_html !!}
    + @if($song->notes) +
    {!! $song->notes !!}
    + @endif +
    {{$song->credits}}
    + @if(count($audioTracks)) +

    💿 {{__('Audios')}}

    + + @endif + @if(count($song->getFiles())) +

    📄 {{__('Fichiers')}}

    + + @endif +
    + @if(count($audioTracks)) +
    + @foreach($audioTracks as $audio) + + @endforeach +
    + @endif +@endsection +@section('floating') + @if(count($audioTracks)) + + + + @endif +@endsection -- 2.39.5