]> _ Git - psq.git/commitdiff
wait #7718 @17:00
authorsoufiane <soufiane@cubedesigners.com>
Tue, 16 Sep 2025 13:35:32 +0000 (15:35 +0200)
committersoufiane <soufiane@cubedesigners.com>
Tue, 16 Sep 2025 13:35:32 +0000 (15:35 +0200)
app/Http/Controllers/Controller.php
app/PhpVars.php
resources/js/app.js
resources/js/components/FileSearch/FileHit.vue
resources/js/components/FileSearch/FileInstantSearch.vue
resources/js/components/FileSearch/Search.vue
resources/sass/_ais.scss
routes/web.php

index a59cc79cdd1278f12460eb5225aee24b977b2dd6..c8bf54b64e52ae75f66fa93cb78c33a41d3cb836 100644 (file)
@@ -8,11 +8,14 @@ use Illuminate\Foundation\Auth\Access\AuthorizesRequests;
 use Illuminate\Foundation\Bus\DispatchesJobs;
 use Illuminate\Foundation\Validation\ValidatesRequests;
 use Illuminate\Routing\Controller as BaseController;
+use Typesense\Client;
 
 class Controller extends BaseController
 {
     use AuthorizesRequests, DispatchesJobs, ValidatesRequests;
 
+    private $client;
+
     public function __construct()
     {
 //        if(config('app.env') === 'production') {
@@ -45,8 +48,38 @@ class Controller extends BaseController
         );
     }
 
+    public function connect() {
+        $this->client = new Client(
+            [
+                'api_key'         => env('TYPESENSE_API_KEY', 'K4fae5KYZTVj6Wucp5q9'),
+                'nodes'           => [
+                    [
+                        'host'     => env('TYPESENSE_HOST', 'typesense.dev.prescription-quotidien.com'), // For Typesense Cloud use xxx.a1.typesense.net
+                        'port'     => '',      // For Typesense Cloud use 443
+                        'protocol' => env('TYPESENSE_PROTOCOL', 'https'),      // For Typesense Cloud use https
+                    ],
+                ],
+                'connection_timeout_seconds' => 2,
+            ]
+        );
+    }
+
     public function getPdfBySlug() {
-        $refs = request('refs', []);
+        if(null === $this->client) {
+            $this->connect();
+        }
+        $q = request('query', '');
+        $page = request('page', 1);
+        $searchParameters = [
+            'q'         => $q,
+            'query_by'  => 'text',
+            'per_page'  => 20,
+            'page'      => $page
+        ];
+
+        $typeSenseResults = $this->client->collections['fluidbooks']->documents->search($searchParameters);
+        $refs = array_map(function($hits) { return $hits['document']['reference']; }, $typeSenseResults['hits']);
+
         $pdfFile = new PdfFile();
         return $pdfFile->whereIn('slug', $refs)->get()->toArray();
     }
index b79b508f0915ed3e638529c0a08f766b5cbfe047..8960c9d91930086fc17276e962e6ab30c64d0740 100644 (file)
@@ -10,15 +10,15 @@ class PhpVars
     protected static function data()
     {
         return [
-            'algolia_app_id' => config('scout.algolia.id'),
-            'algolia_search_key' =>  config('scout.algolia.search_key'),
-            'algolia_prefix' => config('scout.prefix'),
+            'typesense_api_key' => config('scout.typesense.api_key'),
+            'typesense_host' =>  config('scout.typesense.host'),
+            'typesense_prefix' => config('scout.prefix'),
         ];
     }
 
     public static function output()
     {
-        $json = json_encode(self::data());
+        $json = ""; //json_encode(self::data());
 
         return 'const php_vars = '.$json;
 
index ab1d4dba94899d3e27497d8eb27da0aa97750890..51573cfabf6807912f75a205ae8da761b0112889 100644 (file)
@@ -31,7 +31,7 @@ window.Vue.use(Toasted);
  *
  * Eg. ./components/ExampleComponent.vue -> <example-component></example-component>
  */
-
+console.log(process.env.MIX_TYPESENSE_HOST)
 const files = require.context('./', true, /\.vue$/i)
 files.keys().map(key => Vue.component(key.split('/').pop().split('.')[0], files(key).default))
 
index bb350f47d82e579459a64e30ef2a2c5948ce415b..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 100644 (file)
@@ -1,38 +0,0 @@
-<template>
-
-    <div class="card w-100">
-        <img class="card-img-top" :src="hit.coverUrl" alt="Cover">
-        <div class="card-body">
-            <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>
-
-<script>
-    export default {
-        name: "FileHit",
-        props: ['hit'],
-        data(){
-            return {
-
-            }
-        },
-        computed: {
-            viewLink: function () {
-                let page = this.hit._highlightResult.content.matchLevel === 'none' ?
-                    '1' :
-                    this.hit.page;
-
-                return `/view/${this.hit.file.slug}#page=${page}`;
-            }
-
-        }
-    }
-</script>
-
-<style scoped>
-
-</style>
index 127da49b8161c0ab9177d6acd10543c76c71f8ea..64f276800ca23128ce5104f7db6a9d7299da784e 100644 (file)
 <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="py-4 xl:py-16 mb-4 xl:mb-24 bg-clearblue">
+                <div class="container">
+                    <h1 class="bigtitle !mb-4 xl:!mb-6 text-center xl:text-left">Nos archives</h1>
+                    <form action="" class="searchform flex items-center gap-[15px] px-6 py-3 bg-white border-1 border-[#DCE0F5] rounded-[3px]">
+                        <div>
+                            <img :src="SearchIcon" alt="Lancer la recherche" />
+                        </div>
+                        <input class="color-blue w-100 outline-0 font-medium" type="text" placeholder="Recherche..." v-model="query">
+                    </form>
+                </div>
+            </div>
+            <div class="container mb-16">
+                <div class="flex flex-wrap gap-x-[16px] md:gap-x-[32px] gap-y-7 md:gap-y-16" id="ais-Hits-container">
+                    <div class="item-pdf-archive" v-for="item in results" :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="">
+                            <a class="img-link cursor-pointer" target="_blank" href="">
+                                <img class="d-block cover-over box-shadow-cover" :src="item.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>
+                                <p class="font-bold bg-transparent">{{item.title}}</p>
                                 <a href="" class="underline font-medium">Lire cette édition</a>
                             </div>
                         </div>
                     </div>
-                </ais-hits>
-            </ais-instant-search>
+                </div>
+            </div>
+            <div
+                v-if="!isLastPage"
+                id="scroll-sentinel"
+                style="height: 1px;"
+            ></div>
         </template>
     </div>
 
 </template>
 
 <script>
-import TypesenseInstantSearchAdapter from "typesense-instantsearch-adapter";
-import axios from "axios";
-
-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;
-
-const originalSearch = searchClient.search.bind(searchClient);
-searchClient.search = async function(queries) {
-    const startTime = performance.now();
-
-    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 }));
-
-    const endTime = performance.now();
-
-    const elapsedTime = endTime - startTime;
-
-    console.log(`Le temps écoulé est de ${elapsedTime} ms`);
-
-    return tsResults;
-};
 
+import SearchIcon from '../../../../public/img/search_icon.svg'
 
 export default {
+    props: ['isConnected'],
     data(){
-      return{
-          searchClient
-      }
+        return{
+            SearchIcon: SearchIcon,
+            isLastPage: false,
+            observer: null,
+            container: null,
+            query: null,
+            lastResults: [],
+            page: 1,
+            results: [],
+            isLoading: false
+        }
     },
-    mounted(){
+    mounted() {
+        this.container = document.getElementById('scroll-sentinel')
+        const $this = this
+
+        // Premier affichage
+        $this.searchArchive()
+
+        document.addEventListener('scroll', async function() {
+            const bottom = window.innerHeight + window.scrollY
+            if(!$this.isLoading && Math.round(bottom) >= Math.round($this.container.offsetTop) - 100) {
+                $this.isLoading = true
+                $this.page += 1
+                try {
+                    await $this.searchArchive()
+                }finally {
+                    $this.isLoading = false
+                }
+            }
+        })
     },
-    computed: {
+    watch: {
+        query(after, before) {
+            this.page = 1;
+            this.searchArchive();
+        }
     },
+    methods: {
+        searchArchive() {
+            const $this = this
+            return axios.get('/retrieveLetter', { params: { query: this.query, page: $this.page } })
+                .then(function(response) {
+                    if($this.page === 1) {
+                        $this.results = response.data;
+                    }else {
+                        $this.results = [...$this.results,...response.data];
+                    }
+                })
+                .catch(error => {
+                    console.log(error)
+                });
+        }
+    }
 };
 </script>
 
index 576be8ea25db402eda5e91cb0e469692ccb8e87f..97f54d582b85b7ea1c419c7b7be9055e83bc2f48 100644 (file)
@@ -1,44 +1,13 @@
 <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 8ce37746afb6394fa4642dcb2eadf51cc4a39695..c60ef449fc0dd09e0a44cf207db98c073ec0ff9b 100644 (file)
@@ -1,7 +1,29 @@
+.ais-SearchBox-form {
+    display: flex;
+    gap: 16px;
+    background-color: white;
+    border-radius: 3px;
+    border: 1px solid #DCE0F5;
+    padding: 17px 24px;
+}
 .ais-SearchBox-input {
-    @extend .form-control;
-    width: calc(100% - 80px);
+    width: 100%;
     display: inline-block;
+    outline: none;
+    font-weight: 500;
+    color: $blue;
+    line-height: 1;
+    &::placeholder {
+        color: $blue;
+    }
+}
+.ais-SearchBox-submit {
+    order: -1;
+    svg {
+        width: 13px;
+        height: 13px;
+        fill: #044e9c;
+    }
 }
 .ais-SearchBox-reset {
     display: none;
@@ -10,3 +32,8 @@ mark.ais-Snippet-highlighted, mark.ais-Highlight-highlighted, mark.mark {
     padding: 0;
     background-color: #fff252;
 }
+.ais-Pagination {
+    position: absolute;
+    visibility: hidden;
+}
+
index 565ae5fe0f5976cf2721aa60378c05ef7bd853f6..a28bac4bacf9ebd9ce520f1533b5187c83deedc9 100644 (file)
@@ -140,7 +140,7 @@ 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');
+    Route::match(['post','get'],'retrieveLetter', 'Controller@getPdfBySlug');
 });
 
 /** Public routes + NGROK routes */