]> _ Git - psq.git/commitdiff
wip #7697 @10:00
authorsoufiane <soufiane@cubedesigners.com>
Mon, 8 Sep 2025 09:00:24 +0000 (11:00 +0200)
committersoufiane <soufiane@cubedesigners.com>
Mon, 8 Sep 2025 09:00:24 +0000 (11:00 +0200)
15 files changed:
app/FileCollection.php
app/Http/Controllers/Controller.php
app/SearchableText.php
config/scout.php
resources/js/app.js
resources/js/components/FileSearch/FileHit.vue
resources/js/components/FileSearch/FileInstantSearch.vue
resources/js/components/FileSearch/Search.vue [new file with mode: 0644]
resources/js/components/Home/cover.vue
resources/sass/_fonts.scss
resources/sass/_home.scss
resources/sass/app.scss
resources/views/archives/index.blade.php
resources/views/home/index.blade.php
routes/web.php

index 06a59e385b1c3d0b6f9f1c6b876066b4ac9edc99..68abeb7af18540a0f4191d66b3efdde1114e6446 100644 (file)
@@ -35,4 +35,12 @@ class FileCollection extends Model
         return $this->name;
     }
 
+    public function fileByRef($refs)
+    {
+        $results = $this->files()->whereIn('id', $refs)
+            ->get(['id', 'title', 'file_slug', 'cover_url']);
+
+        return response()->json($results);
+    }
+
 }
index c343073c4e980d48ad1cb0c660904da1f011ce1b..a59cc79cdd1278f12460eb5225aee24b977b2dd6 100644 (file)
@@ -3,6 +3,7 @@
 namespace App\Http\Controllers;
 
 use App\FileCollection;
+use App\PdfFile;
 use Illuminate\Foundation\Auth\Access\AuthorizesRequests;
 use Illuminate\Foundation\Bus\DispatchesJobs;
 use Illuminate\Foundation\Validation\ValidatesRequests;
@@ -43,4 +44,10 @@ class Controller extends BaseController
             ->get()
         );
     }
+
+    public function getPdfBySlug() {
+        $refs = request('refs', []);
+        $pdfFile = new PdfFile();
+        return $pdfFile->whereIn('slug', $refs)->get()->toArray();
+    }
 }
index a80fc7e315e607db793702b80cebbe021d5d8fa2..a43ac0770a98671ba8f7435c95ac0c88e36b54f7 100644 (file)
@@ -4,6 +4,7 @@ namespace App;
 
 use Illuminate\Database\Eloquent\Model;
 use Laravel\Scout\Searchable;
+use Typesense\LaravelTypesense\Tests\Fixtures\SearchableUserModel;
 
 /**
  * Class SearchableText
@@ -19,21 +20,40 @@ class SearchableText extends Model
 
     protected $guarded = [];
 
-    public function toSearchableArray()
+    public function toSearchableArray(): array
     {
         return [
-            'file' => [
-                'slug' => $this->file->slug,
-                'tags' => $this->file->fileTags()->pluck('content')->toArray(),
-                'collection' => (string) $this->file->collection,
-                'collection_slug' => $this->file->collection->slug,
-                'visible' => $this->file->is_visible && $this->file->published,
-                'title' => $this->file->title,
-                'cover' => $this->file->coverUrl,
-                'created_at' => $this->file->created_at->format('U'),
-            ],
+            'id' => (string) $this->file->id,
+            'file_slug' => $this->file->slug,
+            'file_tags' => $this->file->fileTags()->pluck('content')->toArray(),
+            'file_collection' => "fluidbooks",
+            'file_collection_slug' => $this->file->collection->slug,
+            'file_visible' => $this->file->is_visible && $this->file->published,
+            'file_title' => $this->file->title,
+            'file_cover' => $this->file->coverUrl,
+            'file_created_at' => $this->file->created_at->format('U'),
             'content' => $this->content,
             'page' => $this->page,
         ];
     }
+
+    public function getCollectionSchema(): array
+    {
+        return [
+            'name' => 'prod_searchable_texts',
+            'fields' => [
+                [
+                    'name' => '.*',
+                ],
+            ],
+            'default_sorting_field' => 'created_at',
+        ];
+    }
+
+    public function typesenseQueryBy(): array
+    {
+        return [
+            'title',
+        ];
+    }
 }
index be6973937d54dd8ca6acfa6eb4e7d83f2e5ce6df..8a9fadcb262522dca3f9db1a6a291c2cdc93836c 100644 (file)
@@ -1,5 +1,7 @@
 <?php
 
+use App\SearchableText;
+
 return [
 
     /*
@@ -96,14 +98,13 @@ return [
         'nodes'           => [
             [
                 'host'     => env('TYPESENSE_HOST', 'localhost'),
-                'port'     => env('TYPESENSE_PORT', '8108'),
-                'path'     => env('TYPESENSE_PATH', ''),
-                'protocol' => env('TYPESENSE_PROTOCOL', 'http'),
+                'port'     => '',
+                'protocol' => env('TYPESENSE_PROTOCOL', 'https'),
             ],
         ],
         'nearest_node'    => [
             'host'     => env('TYPESENSE_HOST', 'localhost'),
-            'port'     => env('TYPESENSE_PORT', '8108'),
+            'port'     => '',
             'path'     => env('TYPESENSE_PATH', ''),
             'protocol' => env('TYPESENSE_PROTOCOL', 'http'),
         ],
@@ -111,6 +112,29 @@ return [
         'healthcheck_interval_seconds' => 30,
         'num_retries'                  => 3,
         'retry_interval_seconds'       => 1,
+        'model-settings' => [
+            User::class => [
+                'collection-schema' => [
+                    'name'      => 'prod_searchable_texts',
+                    'fields' => [
+                        ['name' => 'id',                  'type' => 'int32'  ],
+                        ['name' => 'file_slug',           'type' => 'string' ],
+                        ['name' => 'file_tags',           'type' => 'string[]' ], // tableau de strings
+                        ['name' => 'file_collection',     'type' => 'string' ],
+                        ['name' => 'file_collection_slug','type' => 'string' ],
+                        ['name' => 'file_visible',        'type' => 'bool'   ],
+                        ['name' => 'file_title',          'type' => 'string' ],
+                        ['name' => 'file_cover',          'type' => 'string' ],
+                        ['name' => 'file_created_at',     'type' => 'int64'  ],
+                        ['name' => 'content',             'type' => 'string' ],
+                        ['name' => 'page',                'type' => 'int32'  ],
+                    ],
+                    'default_sorting_field' => 'id',
+                ],
+                'search-parameters' => [
+                    'query_by' => 'email',
+                ],
+            ]
+        ],
     ],
-
 ];
index 7d2283ae914713c25c365ba65ebee8b71bd2d0a4..ab1d4dba94899d3e27497d8eb27da0aa97750890 100644 (file)
@@ -9,8 +9,6 @@ const Swal = require('sweetalert2')
 window.Vue = require('vue');
 /** @todo fix vue material design issue */
 
-
-
 import 'selectize/dist/js/standalone/selectize.min.js';
 import 'selectize/dist/css/selectize.css';
 import '@fortawesome/fontawesome-free/css/all.min.css';
@@ -64,6 +62,10 @@ const app = new Vue({
     },
 });
 
+$("#search").on("keydown", function(){
+    console.log($(this).val())
+})
+
 document.addEventListener("DOMContentLoaded", () => {
     const el = document.getElementById("openmenu")
     el.addEventListener("click", () => {
index 748cd24fe4812c433d95e83be18968da00897603..bb350f47d82e579459a64e30ef2a2c5948ce415b 100644 (file)
@@ -1,26 +1,12 @@
 <template>
 
     <div class="card w-100">
-        <img class="card-img-top" :src="hit.file.cover" alt="Cover">
+        <img class="card-img-top" :src="hit.coverUrl" alt="Cover">
         <div class="card-body">
-            <h5 class="card-title text-center" v-text="hit.file.title"></h5>
-
-            <p class="card-text">
-                <span class="badge badge-light mr-1" v-for="tag in hit.file.tags">
-                    <i class="fas fa-tag"></i>&nbsp;{{tag}}
-                </span>
-            </p>
-            <p class="card-text">
-                <ais-snippet attribute="content" :hit="hit" v-if="hit._highlightResult.content.matchLevel !== 'none'"/>
-            </p>
+            <h5 class="card-title text-center" v-text="hit.title"></h5>
         </div>
 
         <a :href="viewLink" class="btn btn-big mb-0" target="_blank"><i class="fas fa-book-open mr-1"></i>&nbspLire cette édition</a>
-
-
-
-
-
     </div>
 
 </template>
index 34aab29902f9949557cd13634a7cf2bff99c782c..127da49b8161c0ab9177d6acd10543c76c71f8ea 100644 (file)
@@ -1,74 +1,82 @@
 <template>
     <div>
+        <template>
+            <ais-instant-search :search-client="searchClient" index-name="fluidbooks">
+                <ais-search-box/>
+                <ais-hits v-slot="{items}">
+                    <div class="item-pdf-archive" v-for="item in items" :key="item.id">
+                        <div class="cover">
+                            <a class="img-link cursor-pointer" href="" target="_blank">
+                                <img class="d-block cover-over box-shadow-cover" :src="item.document.coverUrl" alt="">
+                                <div class="shadowcover">
+                                    <img class="max-h-[39px]" src="" />
+                                </div>
+                            </a>
+                            <div class="cover-title text-left bg-transparent max-xs:!-mt-1">
+                                <p class="font-bold bg-transparent">{{item.document.title}}</p>
+                                <a href="" class="underline font-medium">Lire cette édition</a>
+                            </div>
+                        </div>
+                    </div>
+                </ais-hits>
+            </ais-instant-search>
+        </template>
+    </div>
 
-        <ais-instant-search
-            :search-client="searchClient"
-            :index-name="prefix+'searchable_texts'"
-        >
-            <ais-configure
-                :hits-per-page.camel="12"
-                :filters="filters"
-            />
-            <div class="row">
+</template>
 
+<script>
+import TypesenseInstantSearchAdapter from "typesense-instantsearch-adapter";
+import axios from "axios";
 
-                <div class="col-md-12">
-                    <div class="px-3">
-                        <ais-search-box placeholder="Rechercher..." class="searchbox " />
+const typesenseInstantsearchAdapter = new TypesenseInstantSearchAdapter({
+    server: {
+        apiKey: process.env.MIX_TYPESENSE_API_KEY,
+        nodes: [
+            {
+                host: process.env.MIX_TYPESENSE_HOST,
+                port: "",
+                protocol: "https",
+            },
+        ],
+        cacheSearchResultsForSeconds: 2 * 60,
+    },
+    additionalSearchParameters: {
+        query_by: "text",
+    },
+});
+const searchClient = typesenseInstantsearchAdapter.searchClient;
 
-                    </div>
-                    <ais-stats class="mt-2 ml-3"></ais-stats>
+const originalSearch = searchClient.search.bind(searchClient);
+searchClient.search = async function(queries) {
+    const startTime = performance.now();
 
-                    <div class="my-4">
-                        <ais-infinite-hits :class-names="{
-                    'ais-InfiniteHits-list': 'row',
-                    'ais-InfiniteHits-item' : 'col-sm-3 mb-3'
-                }">
+    const tsResults = await originalSearch(queries);
+    const refs = tsResults.results[0].hits.map(h => h.reference)
+    const res = await axios.post("/retrieveLetter", { refs });
 
+    tsResults.results[0].hits = res.data.map(doc => ({ document: doc }));
 
-                            <file-hit
-                                slot="item"
-                                slot-scope="{item, index}"
-                                :key="index"
-                                :hit="item"
-                            >
-                            </file-hit>
+    const endTime = performance.now();
 
-                            <button
-                                slot="loadMore"
-                                slot-scope="{ page, isLastPage, refineNext }"
-                                :disabled="isLastPage"
-                                @click="refineNext"
-                                class="btn btn-big mt-5"
-                            >
-                                <i class="far fa-plus-square mr-1"></i>Voir plus
-                            </button>
-                        </ais-infinite-hits>
-                    </div>
-                </div>
-            </div>
-        </ais-instant-search>
-    </div>
+    const elapsedTime = endTime - startTime;
 
-</template>
+    console.log(`Le temps écoulé est de ${elapsedTime} ms`);
+
+    return tsResults;
+};
 
-<script>
-import Search from '../../mixins/SearchMixin';
 
 export default {
-    mixins: [Search],
     data(){
       return{
-          filters: "file.collection_slug:'psq' AND file.visible:true"
+          searchClient
       }
     },
     mounted(){
     },
     computed: {
-
-    }
-
-
+    },
 };
 </script>
 
diff --git a/resources/js/components/FileSearch/Search.vue b/resources/js/components/FileSearch/Search.vue
new file mode 100644 (file)
index 0000000..576be8e
--- /dev/null
@@ -0,0 +1,44 @@
+<template>
+    <input v-model="text" class="color-blue w-100 outline-0 font-medium" type="text" placeholder="Recherche..."/>
+</template>
+<script>
+import Typesense from 'typesense'
+
+export default {
+    name: "Search",
+    data() {
+        return {
+            text: '',
+            client: null
+        }
+    },
+    watch: {
+        text(newText) {
+            console.log(newText)
+            let searchParameters = {
+                'q'         : newText,
+                'query_by'  : 'text',
+                'per_page': 20,
+                'page': 1
+            }
+            this.client.collections('fluidbooks')
+                .documents()
+                .search(searchParameters)
+                .then(function (searchResults) {
+                    console.log(searchResults)
+                })
+        }
+    },
+    mounted() {
+        this.client = new Typesense.Client({
+            'nodes': [{
+                'host': process.env.MIX_TYPESENSE_HOST, // For Typesense Cloud use xxx.a1.typesense.net
+                'port': '',      // For Typesense Cloud use 443
+                'protocol': 'https'   // For Typesense Cloud use https
+            }],
+            'apiKey': process.env.MIX_TYPESENSE_API_KEY,
+            'connectionTimeoutSeconds': 3600
+        })
+    }
+}
+</script>
index b14e07367783d2c0e33649d375ead2688aeb1895..455788c350808ee00450082bd28949acb9b8ef9c 100644 (file)
@@ -34,9 +34,6 @@
         }),
         computed: {
             dimensions() {
-
-
-
                 return {
                     width: this.width,
                     height: Math.round(this.c_height)+'px'
index 307d38e6447e059253cd3d1e3b3b93132a2e2d70..8e220acdb83efbc914df702071d22833913ffbae 100644 (file)
@@ -1,7 +1,7 @@
 @font-face {
     font-family: 'Poppins';
-    src: url('public/fonts/poppins/Poppins-SemiBold.woff2') format('woff2'),
-    url('public/fonts/poppins/Poppins-SemiBold.woff') format('woff');
+    src: url('../../public/fonts/poppins/Poppins-SemiBold.woff2') format('woff2'),
+    url('../../public/fonts/poppins/Poppins-SemiBold.woff') format('woff');
     font-weight: 600;
     font-style: normal;
     font-display: swap;
@@ -9,8 +9,8 @@
 
 @font-face {
     font-family: 'Poppins';
-    src: url('public/fonts/poppins/Poppins-Medium.woff2') format('woff2'),
-    url('public/fonts/poppins/Poppins-Medium.woff') format('woff');
+    src: url('../../public/fonts/poppins/Poppins-Medium.woff2') format('woff2'),
+    url('../../public/fonts/poppins/Poppins-Medium.woff') format('woff');
     font-weight: 500;
     font-style: normal;
     font-display: swap;
@@ -18,8 +18,8 @@
 
 @font-face {
     font-family: 'Poppins';
-    src: url('public/fonts/poppins/Poppins-Regular.woff2') format('woff2'),
-    url('public/fonts/poppins/Poppins-Regular.woff') format('woff');
+    src: url('../../public/fonts/poppins/Poppins-Regular.woff2') format('woff2'),
+    url('../../public/fonts/poppins/Poppins-Regular.woff') format('woff');
     font-weight: normal;
     font-style: normal;
     font-display: swap;
@@ -27,8 +27,8 @@
 
 @font-face {
     font-family: 'Poppins';
-    src: url('public/fonts/poppins/Poppins-Bold.woff2') format('woff2'),
-    url('public/fonts/poppins/Poppins-Bold.woff') format('woff');
+    src: url('../../public/fonts/poppins/Poppins-Bold.woff2') format('woff2'),
+    url('../../public/fonts/poppins/Poppins-Bold.woff') format('woff');
     font-weight: bold;
     font-style: normal;
     font-display: swap;
@@ -36,8 +36,8 @@
 
 @font-face {
     font-family: 'Poppins';
-    src: url('public/fonts/poppins/Poppins-Light.woff2') format('woff2'),
-    url('public/fonts/poppins/Poppins-Light.woff') format('woff');
+    src: url('../../public/fonts/poppins/Poppins-Light.woff2') format('woff2'),
+    url('../../public/fonts/poppins/Poppins-Light.woff') format('woff');
     font-weight: 300;
     font-style: normal;
     font-display: swap;
index c059d7d717516941f9a76ca95fb29ba9df469b43..5eb973740e12b3e4c54d7ee993d189bd0c0adc16 100644 (file)
             }
             .embla__viewport {
                 overflow: hidden;
+                max-width: 1280px;
             }
             .embla__container {
                 display: flex;
index b0a4252a246a798dbfbac19c33d7419a26b3a95a..71ebabeacc1cc591a6f0638b6f178851eeca2e16 100644 (file)
@@ -5,7 +5,7 @@
 @import "tailwindcss";
 @import 'mixins';
 @import "ais";
-//@import "fonts";
+@import "fonts";
 @import "colors";
 @import "nav";
 @import "footer";
index b8866942aa50cb78e89a5a9935e0bb11061f4213..6d4416631c8eebe14e7981e04b9e06eee3d13830 100644 (file)
@@ -8,13 +8,13 @@
                 <div>
                     <img src="{{ @asset('img/search_icon.svg') }}" alt="Lancer la recherche" />
                 </div>
-                <input class="color-blue w-100 outline-0 font-medium" type="text" placeholder="Recherche..."/>
+                <!--<input class="color-blue w-100 outline-0 font-medium" type="text" placeholder="Recherche..."/>-->
             </form>
         </div>
     </div>
 @endsection
 @section('main')
-    <div class="container mb-16">
+    {{--<div class="container mb-16">
         <div class="flex flex-wrap gap-x-[16px] md:gap-x-[32px] gap-y-7 md:gap-y-16">
             @foreach($all_pdf as $key => $pdf)
                 <div class="item-pdf-archive">
                 </div>
             @endforeach
         </div>
-    </div>
+    </div>--}}
+    <file-instant-search></file-instant-search>
 @endsection
+
+<script>
+    import Search from "../../js/components/FileSearch/Search";
+    import FileInstantSearchSearch from "../../js/components/FileSearch/FileInstantSearch.vue";
+    export default {
+        components: {FileInstantSearchSearch}
+    }
+</script>
index 7f8972d0a7044805af1c9c059197cc51e2e81657..6af6eed9ab1b25545dd3057a16e4725b61eabcb4 100644 (file)
@@ -39,7 +39,8 @@
                         <div class="grid-1 gap-0">
                             <h1 class="bigtitle">{!! $settings->byKey('main_title') !!}</h1>
                             <p class="date !mt-6 xl:!mt-8">{{ ucfirst($last_pdf->first()->created_at->translatedFormat('l d F Y')) }}</p>
-                            <a href="/view/{{ $last_pdf->first()['slug'] }}" class="btnorange mt-4">Lire le quotidien</a>
+                            {{-- /view/{{ $last_pdf->first()['slug'] }} --}}
+                            <a href="https://hosting.fluidbook.com/psq-1952a" class="btnorange mt-4 {{ !Auth::check() ? 'notConnected' : '' }}" target="_blank">Lire le quotidien</a>
                         </div>
                         <x-cover :pdf="$last_pdf->first()" type="3" linkTo="read">
                         </x-cover>
                         <p class="font-semibold !mb-3">{!! $settings->byKey('subtitle_bloc_image_home') !!}</p>
                         {!! $settings->byKey('text_bloc_image_home') !!}
                     </div>
-                    <a href="#" class="linkunderline">{!! $settings->byKey('not_register_link') !!}</a>
+                    <a href="{{ route('contact.subscribe') }}" class="linkunderline">{!! $settings->byKey('not_register_link') !!}</a>
                 </div>
             </div>
             <div class="w-full max-xl:h-[398px] xl:w-[384px] shrink-0 overflow-hidden bg-blue relative radius">
                 <div class="p-64 flex flex-col justify-between items-center pdf-block relative h-full z-1">
                     <h3 class="title-feuilleter regulartitle">{{ $settings->byKey('title_quotidien_home') }}</h3>
-                    <a href="/view/{{ $last_pdf->first()['slug'] }}" class="btnorange fw-normal relative">Feuilleter un quotidien</a>
+                    {{-- /view/{{ $last_pdf->first()['slug'] }} --}}
+                    <a href="https://hosting.fluidbook.com/psq-1952a" class="btnorange fw-normal relative" target="_blank">Feuilleter un quotidien</a>
                 </div>
                 <img class="absolute max-xl:h-full xl:w-100 left-1/2 top-[45%] xl:top-[48%] -translate-1/2" src="{{ asset('/img/quotidien.jpg') }}" alt="" />
             </div>
index 02d47db4dff06a7969e222e82a69730b0e7002de..882afcfa689a07281b4a3f8ffdb8dca8dd41de98 100644 (file)
@@ -142,6 +142,8 @@ Route::domain(env('CLIENT_DOMAIN_NAME'))->group(function() {
 
     Route::get('devenez-annonceurs', 'AdvertisersController@index')->name('annonceurs.index');
     Route::post('devenez-annonceurs', 'AdvertisersController@requestMail')->name('annonceurs.store');
+
+    Route::post('retrieveLetter', 'Controller@getPdfBySlug');
 });
 
 /** Public routes + NGROK routes */