Building a new social (or a "chain" of them): your suggestions

Discussion in 'Chit Chat' started by fullautotrading, Jun 23, 2023.

  1. The post editor is one of the most crucial parts in my personal view because I just hate having to type into those small windows as if our brain could not contain more than some short self-important "tweet" ( = "I am too busy and important to articulate a thought") or "truth" ( = "All my biased opinions and propaganda coincide with the truth" ) :)

    That is why I like the ET layout, as it allows people to expand their thoughts and articulate a transparent discussion ... :)

    So I am giving it amplitude and comfort. I need to find a comfortable way to place the menu. So far I just placed it at the bottom, but probably not ideal.

    I might give the possibility to switch from bottom to left placement in the case... will see...


    upload_2023-7-3_0-27-23.png


    menu so far (placed at the bottom now, still thinking about it...):


    upload_2023-7-3_0-27-50.png
     
    #21     Jul 2, 2023
  2. I have been perfecting the fast login and notification features so that now we can fast login and go straight to any post or to any user profile on a simple notification.

    This has required the introduction of a few more fields in the login table:

    upload_2023-7-3_18-56-31.png

    These are relatively "tedious" to code (just think of pointing to a specific post. in a context where the posts are "paginated" and dynamically changing), but IMHO they can make a lot of difference to keep the users engaged and active. One tends to stick to social media where engagement is high and discussions heated... :)

    Also refining a bit the post editor:

    upload_2023-7-3_19-0-41.png


    a feature we have here (that I don't see at the moment on ET) is the possibility to resize (without resampling) images quickly after pasting them into the editor. One just clicks on it and then the image takes the size indicated on an input box (the one with 1300 inside in this case). Simple and effective.

    (This might be another reason why it is convenient to withhold the images locally, before the post upload.)

    On the other hand in "view mode", the click event on an image will behave similarly to ET, but with the difference that the picture is not enlarged to the original size out of the editor (which causes some page portions to be hidden), but inside the editor :).
     
    Last edited: Jul 3, 2023
    #22     Jul 3, 2023
  3. Today, I wanted to extend the editor functionality of resizing images on click to the videos too.

    It's not so immediate, as just detecting the click on the iframe element is not so straightforward as for images (there is no handler obviously).

    Anyway, I found a decent solution by adding a listener to the "blur" event.

    Here is a short video demo of a few seconds of the video (iframe) resize:



    Still working on making it fully intuitive, but it's a start ... :)

    upload_2023-7-4_0-46-53.png


    (ideal for Overnight, LOL :) )
     
    Last edited: Jul 3, 2023
    #23     Jul 3, 2023
  4. This morning, I am improving a bit the video/image embedding.

    In addition to the embedding code we have seen in the previous short demo with frame resize "on click", I have also added some self-embedding capabilities directly from the raw links.

    Here is a short video demo of a few seconds where I am testing the links below (including Gdrive and YouTube shorts):




    Links are:

    (to see a link, remove the underscore after h, which I placed to "fool" the ET auto embedding)
    ===

    GOOGLE DRIVE
    h_
    ttps://drive.google.com/file/d/15im6vn6V8KvkrXtmpHLLlnznPQaG04QR/view

    YOU TUBE
    h_ttps://www.youtube.com/watch?v=eXbR9bZ12-E&ab_channel=CicaloneSimone

    quick link
    h_ttps://youtu.be/65jffcgRS5k

    shorts
    h_ttps://www.youtube.com/shorts/uZLK1bLz1xo

    quick link
    h_ttps://youtu.be/c6Uo7ZWg1mI

    embed code
    <iframe width="1280" height="720" src="https://www.youtube.com/embed/Qa_4c9zrxf0" title="How to Create a Company | Elon Musk&#39;s 5 Rules" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" allowfullscreen></iframe>

    sample img
    https://www.datatime.eu/public/gbot/SampleImage.png


    upload_2023-7-4_15-21-52.png
     
    #24     Jul 4, 2023
  5. Today I made another significant step forward. I felt I needed a "site search", especially for the Rome version, where people also buy and sell stuff :)

    So I implemented a search as follows:

    1 search within threads' titles
    2 search within threads' keywords (I added a field for those)
    3 search posts within posts' keywords (I added a field for those)

    I am not searching for post content, as there is Google (not Bing) for that :)
    (Also I do not store posts in the DB, as they are exposed on the web, therefore, it would be inefficient to attempt to search them).

    The search returns 3 lists at the moment (2 lists of threads and 1 of posts) which provide direct links to the respective threads or posts.

    upload_2023-7-5_18-16-14.png

    For now, it's a start, in case I will refine it later. I needed to do now the basic functionality in order to think about what was needed in terms of DB fields.

    With the occasion, I have also improved the "fast-link" feature, by removing the thread page from the arguments, as it can be computed directly from the PostID, without the need of sending it. (The usefulness of this became apparent when from the search results, I only had the ID of the post and could not know a priori on what "page" (within the thread) the post is. So it's good to have).

    So in practice, I added 2 more fields (for keywords in the thread and post) and removed 1 field from the login table.

    This was relatively tedious to code, as I like to deal mostly with visual aspects. Anyway it needs to be there, so it cannot be always fun :)
    I like to generate all the DOM via JavaScript and never use any library.
    This is initially time-consuming, but after you lay the basis, then you have unlimited power of DOM manipulation and changes and maintenance become a piece of cake :)

    If you are curious about what looks like the code generating the page shown in the screenshot, I am attaching here the source for the open-source lovers :)

    Code:
    
    'use strict';
    
    class ParolaCercata_Control {
    
        crea_ParolaCercata_Control(IndiceSequenziale) {
    
            this.input_Keywords = document.createElement('input');
            this.input_Keywords.type = "text";
            this.input_Keywords.classList.add("SearchKeyword");
            setup_INPUT_WITH_BORDER(this.input_Keywords);
            this.input_Keywords.style.cssText += `display:inline-block; width:250px; height:16px; font-size:16px; margin:5px; margin-top:15px;`;
        }
    
    }
    
    class ResultEntryControl_Thread {
    
        constructor(forumThread) {
            this.forumThread = forumThread;
        }
    
        Crea_Controllo() {
    
            this.div_ThreadPanel = document.createElement('div');
            this.div_ThreadPanel.innerHTML = `Thread ID ${this.forumThread.ThreadTitle}, <span style="color:yellow; font-style:italic;">${this.forumThread.ThreadTitle}</span><br>
                                              <span style="color:cyan">stored keywords:</span> "${this.forumThread.KeyWordsForSearch}"`;
    
            this.div_ThreadPanel.style.cssText += `display:block; font-size:14px; margin-top:5px; cursor:pointer;
                                                   color:white; background-color:rgba(0,10,0,0.1); padding:10px; border-radius:5px; `;
    
            this.div_ThreadPanel.onclick = () => {
                window.location.href = `${_POST_PAGE_NAME}?thread=${this.forumThread.ThreadID}`;
            };
        }
    }
    
    class ResultEntryControl_Post {
    
        constructor(threadPost) {
            this.threadPost = threadPost;
        }
    
        Crea_Controllo() {
    
            this.div_PostPanel = document.createElement('div');
            this.div_PostPanel.innerHTML = `Post ID ${this.threadPost.PostID}, in thread id <span style="color:yellow; font-style:italic;">${this.threadPost.ThreadID}</span><br>
                                            <span style="color:cyan">stored keywords:</span> "${this.threadPost.KeyWordsForSearch}"`;
    
            this.div_PostPanel.style.cssText += `display:block; font-size:14px; margin-top:5px; cursor:pointer;
                                                 color:wheat; background-color:rgba(0,0,100,0.3); padding:10px; border-radius:5px; `;
    
            this.div_PostPanel.onclick = () => {
                window.location.href = `${_POST_PAGE_NAME}?Intention=LinkToPost&PostID=${this.threadPost.PostID}`;
            };
        }
    }
    
    showServerSideErrors(document);
    
    setBasicPageBehavior("Site search", _SiteJS.COLOR_HOMEPAGE_BACKGROUND, _SiteJS.SITE_ICON);
    
    //Collect server objects
    
    let UserInfo_Current = receiveServerObject(document, UserInfo, "Obj_UserInfo");
    let SiteSearchRequest_Current = receiveServerObject(document, Forum, "Obj_SiteSearchRequest");
    let SiteSearchResults_Current = receiveServerObject(document, ForumThread, "Obj_SiteSearchResults");
    
    
    //REQUEST PANEL
    
    
    //pannello contenitore globale
    let div_SiteSearchRequest_Panel = document.createElement('div');
    div_SiteSearchRequest_Panel.style.cssText = `display:block; background-color:rgba(255,255,255,0.2); padding:10px;`;
    
    
    //titolo
    let div_TitoloRicerca = document.createElement('div');
    div_TitoloRicerca.innerHTML = "Search words in posts or threads";
    div_TitoloRicerca.style.cssText = `display:block; color:white; background-color:white; text-align: center; background-color:rgba(255,255,255,0.2);
                                       font-size:32px; font-weight:bold; padding:10px; border-radius: 5px;`;
    
    div_SiteSearchRequest_Panel.appendChild(div_TitoloRicerca);
    
    
    let NumberOfKeyWordsForSearch = 5;
    
    //controlli keywords
    for (let i = 0; i < NumberOfKeyWordsForSearch; i++) {
    
        let pInput = new ParolaCercata_Control();
        pInput.crea_ParolaCercata_Control(i + 1);
    
        div_SiteSearchRequest_Panel.appendChild(pInput.input_Keywords);
    
    }
    
    
    //dove cercare ---------------------------------------------------------------------------------------------------------------------------------
    
    let label_ContenitoreTipoRicercaPOST_THREAD = document.createElement('label');
    label_ContenitoreTipoRicercaPOST_THREAD.innerHTML = "Dove cercare:";
    label_ContenitoreTipoRicercaPOST_THREAD.style.cssText = "display:block; margin-top:10px; margin-bottom:5px; color:lime;";
    
    
    //THREADS' TITLES
    let label_RicercaTHREADS_TITLE = document.createElement('label');
    label_RicercaTHREADS_TITLE.innerHTML = "Nei TITOLI delle THREADS";
    label_RicercaTHREADS_TITLE.style.cssText = "color:#aaffff; cursor:pointer; margin-left:10px; ";
    
    //radio
    let input_check_RicercaTHREADS_TITLES = document.createElement('input');
    input_check_RicercaTHREADS_TITLES.type = "checkbox";
    input_check_RicercaTHREADS_TITLES.name = 'SearchTypeTHREADS_POSTS';
    input_check_RicercaTHREADS_TITLES.style.cssText += "display:inline-block; width:15px; height:15px; cursor:pointer;";
    
    label_RicercaTHREADS_TITLE.appendChild(input_check_RicercaTHREADS_TITLES);
    
    //label in label per allineare
    label_ContenitoreTipoRicercaPOST_THREAD.appendChild(label_RicercaTHREADS_TITLE);
    
    //THREADS' KEYWORDS
    let label_RicercaTHREADS_KEYWORDS = document.createElement('label');
    label_RicercaTHREADS_KEYWORDS.innerHTML = "Nelle KEYWORDS delle Threads";
    label_RicercaTHREADS_KEYWORDS.style.cssText = "color:#aaffff; cursor:pointer; margin-left:10px; ";
    
    //radio
    let input_check_RicercaTHREADS_KEYWORDS = document.createElement('input');
    input_check_RicercaTHREADS_KEYWORDS.type = "checkbox";
    input_check_RicercaTHREADS_KEYWORDS.name = 'SearchTypeTHREADS_POSTS';
    input_check_RicercaTHREADS_KEYWORDS.style.cssText += "display:inline-block; width:15px; height:15px; cursor:pointer;";
    
    label_RicercaTHREADS_KEYWORDS.appendChild(input_check_RicercaTHREADS_KEYWORDS);
    
    //label in label per allineare
    label_ContenitoreTipoRicercaPOST_THREAD.appendChild(label_RicercaTHREADS_KEYWORDS);
    
    //POSTS KEYWORDS
    let label_RicercaPOSTS = document.createElement('label');
    label_RicercaPOSTS.innerHTML = "Nelle KEYWORDS dei POSTS";
    label_RicercaPOSTS.style.cssText = "color:#aaffff; cursor:pointer; margin-left:10px;";
    //radio
    let input_check_RicercaPOSTS_KEYWORDS = document.createElement('input');
    input_check_RicercaPOSTS_KEYWORDS.type = "checkbox";
    input_check_RicercaPOSTS_KEYWORDS.name = 'SearchTypeTHREADS_POSTS';
    input_check_RicercaPOSTS_KEYWORDS.style.cssText += "display:inline-block; width:15px; height:15px; cursor:pointer;";
    
    label_RicercaPOSTS.appendChild(input_check_RicercaPOSTS_KEYWORDS);
    
    //label in label per allineare
    label_ContenitoreTipoRicercaPOST_THREAD.appendChild(label_RicercaPOSTS);
    
    // ---------------------------------------------------------------------------------------------------------------------------------
    
    
    //tipo ricerca ---------------------------------------------------------------------------------------------------------------------------------
    
    let label_ContenitoreTipoRicercaAND_OR = document.createElement('label');
    label_ContenitoreTipoRicercaAND_OR.innerHTML = "Tipo di ricerca: OR (almeno una paola e' presente) o AND (tutte le parole sono simultaneamente presenti)";
    label_ContenitoreTipoRicercaAND_OR.style.cssText = "display:block; margin-top:10px; margin-bottom:5px; color:lime;";
    
    //OR
    let label_RicercaOR = document.createElement('label');
    label_RicercaOR.innerHTML = "OR";
    label_RicercaOR.style.cssText = "color:#aaffff; cursor:pointer; margin-left:10px; ";
    
    //radio
    let input_radio_RicercaOR = document.createElement('input');
    input_radio_RicercaOR.type = "radio";
    input_radio_RicercaOR.name = 'SearchTypeAND_OR';
    input_radio_RicercaOR.style.cssText += "display:inline-block; width:15px; height:15px; cursor:pointer;";
    
    label_RicercaOR.appendChild(input_radio_RicercaOR);
    
    //label in label per allineare
    label_ContenitoreTipoRicercaAND_OR.appendChild(label_RicercaOR);
    
    //AND
    let label_RicercaAND = document.createElement('label');
    label_RicercaAND.innerHTML = "AND";
    label_RicercaAND.style.cssText = "color:#aaffff; cursor:pointer; margin-left:10px;";
    //radio
    let input_radio_RicercaAND = document.createElement('input');
    input_radio_RicercaAND.type = "radio";
    input_radio_RicercaAND.name = 'SearchTypeAND_OR';
    input_radio_RicercaAND.style.cssText += "display:inline-block; width:15px; height:15px; cursor:pointer;";
    
    label_RicercaAND.appendChild(input_radio_RicercaAND);
    
    //label in label per allineare
    label_ContenitoreTipoRicercaAND_OR.appendChild(label_RicercaAND);
    
    //pannello search request
    div_SiteSearchRequest_Panel.appendChild(label_ContenitoreTipoRicercaPOST_THREAD);
    div_SiteSearchRequest_Panel.appendChild(label_ContenitoreTipoRicercaAND_OR);
    document.body.appendChild(div_SiteSearchRequest_Panel);
    // --------------------------------------------------------------------------------------------------------
    
    
    //bottone search
    let button_Search = document.createElement('button');
    setup_BUTTON_LOOK_ANIMATION(button_Search, "Search!");
    button_Search.style.cssText += "width:auto; margin-left:20px; font-size: 30px";
    document.body.appendChild(button_Search);
    
    
    //pannello contenitore globale dei risultati ------------------------------------------------------------
    let div_SiteSearchResults_Panel = document.createElement('div');
    div_SiteSearchResults_Panel.style.cssText = `display:block; background-color:rgba(255,255,255,0.2); padding:10px;`;
    document.body.appendChild(div_SiteSearchResults_Panel);
    
    
    button_Search.onclick = () => { creaNuovaSiteSearchRequest_DaInterfaccia() };
    
    AggiungiAnimazioneBottoni();
    
    
    //ricarica di richiesta e risultati se precedente ricerca esistente
    
    Carica_SiteSearchRequest_SuInterfaccia();
    Carica_SiteSearchResults_SuInterfaccia();
    
    
    async function creaNuovaSiteSearchRequest_DaInterfaccia() {
    
        //recupero le keyword
        let AllSearchWordInputs = div_SiteSearchRequest_Panel.getElementsByClassName('SearchKeyword');
    
        SiteSearchRequest_Current = new SiteSearchRequest();
    
        //tipo ricerca
        SiteSearchRequest_Current.RicercaInPost_Keywords = input_check_RicercaPOSTS_KEYWORDS.checked;
        SiteSearchRequest_Current.RicercaInThread_Titles = input_check_RicercaTHREADS_TITLES.checked;
        SiteSearchRequest_Current.RicercaInThread_Keywords = input_check_RicercaTHREADS_KEYWORDS.checked;
    
        SiteSearchRequest_Current.RicercaParoleConAND = input_radio_RicercaAND.checked;
    
    
        //lista parole
        SiteSearchRequest_Current.ParoleCercate = [];
        Array.from(AllSearchWordInputs).forEach(inputCheckbox => {
            //solo se non vuota
            if (inputCheckbox.value) {
                SiteSearchRequest_Current.ParoleCercate.push(inputCheckbox.value);
            }
        });
    
        //send request to server
        let myFormData = new FormData();
        myFormData.append("requestPurpose", "PerformSiteSearch");
        myFormData.append("SiteSearchRequest", JSON.stringify(SiteSearchRequest_Current));
    
        let xhr = await SendFormData(myFormData, _SEARCH_PAGE_NAME, "Requesting search");
    
        showServerSideErrors(xhr.response);
    
        if (xhr) {
    
            tempNotify(`Request sent successfully`, 30, 30, 1000);
    
            //ricezione oggetto risposta
            SiteSearchResults_Current = receiveServerObject(xhr.response, SiteSearchResults, "Obj_SiteSearchResults");
    
            Carica_SiteSearchResults_SuInterfaccia()
    
        } else { tempNotify(`Request failed`) }
    
    }
    
    function Carica_SiteSearchRequest_SuInterfaccia() {
    
        //reimpostazione valori su pannello richiesta
    
        if (!SiteSearchRequest_Current) {
    
            //Default values when no previous search done
            input_check_RicercaTHREADS_TITLES.checked = true;
            input_check_RicercaTHREADS_KEYWORDS.checked = true;
            input_check_RicercaPOSTS_KEYWORDS.checked = true;
            input_radio_RicercaOR.checked = true;
            input_radio_RicercaAND.checked = false;
    
            //input lascio vuoti per edit utente
    
        } else {
    
            //ricarica ricerca precedente
    
            input_check_RicercaTHREADS_TITLES.checked = SiteSearchRequest_Current.RicercaInThread_Titles;
            input_check_RicercaTHREADS_KEYWORDS.checked = SiteSearchRequest_Current.RicercaInThread_Keywords;
            input_check_RicercaPOSTS_KEYWORDS.checked = SiteSearchRequest_Current.RicercaInPost_Keywords;
            input_radio_RicercaOR.checked = !SiteSearchRequest_Current.RicercaParoleConAND;
            input_radio_RicercaAND.checked = SiteSearchRequest_Current.RicercaParoleConAND;
    
            //ricarica parole precedente ricerca negli slot disponibili
    
            let AllSearchWordInputs = div_SiteSearchRequest_Panel.getElementsByClassName('SearchKeyword');
    
            let wordIndex = 0;
            Array.from(AllSearchWordInputs).forEach(SearchwordInput => {
                if (SiteSearchRequest_Current.ParoleCercate.length > wordIndex) {
                    SearchwordInput.value = SiteSearchRequest_Current.ParoleCercate[wordIndex];
                    wordIndex++;
                }
            });
        }
    }
    
    function Carica_SiteSearchResults_SuInterfaccia() {
    
        //reset pannello risultati
        div_SiteSearchResults_Panel.innerHTML = "";
    
        if (!SiteSearchResults_Current) { return }
    
    
        //mostro parole cercate
    
        if (SiteSearchResults_Current.SiteSearchRequest.ParoleCercate) {
    
            SiteSearchResults_Current.SiteSearchRequest.ParoleCercate.forEach(p => {
                let div_parolaCercata = document.createElement('div');
                div_parolaCercata.style.cssText = `display:inline-block; color:red; background-color:white; margin:10px`;
                div_parolaCercata.innerHTML = `${p}`;
                div_SiteSearchResults_Panel.appendChild(div_parolaCercata);
            });
    
        } else {
    
            let div_parolaCercata = document.createElement('div');
            div_parolaCercata.style.cssText = `display:inline-block; color:red; background-color:white;`;
            div_parolaCercata.innerHTML = "List of searched words is empty";
            div_SiteSearchResults_Panel.appendChild(div_parolaCercata);
        }
    
    
        //titolo response
        let div_TitoloRispostaSearch = document.createElement('div');
        div_TitoloRispostaSearch.innerHTML = "Search Response";
        div_TitoloRispostaSearch.style.cssText = `display:block; color:white; background-color:yellow; text-align: center; background-color:rgba(0,0,100,0.2);
                                                      font-size:32px; font-weight:bold; padding:10px; border-radius: 5px; margin-bottom:10px; `;
        div_SiteSearchResults_Panel.appendChild(div_TitoloRispostaSearch);
    
    
        //Posts: parole nelle keywords
        let div_ContenitorePosts_ConParoleNelleKeywords = CreaContenitoreConListaPosts(SiteSearchResults_Current.Posts_ConParoleNelleKeywords, "POSTS with searched words in KEYWORDS");
        div_SiteSearchResults_Panel.appendChild(div_ContenitorePosts_ConParoleNelleKeywords);
    
    
        //Threads: parole nel titolo
        let div_ContenitoreThreads_ConParoleNelTitolo = CreaContenitoreConListaThreads(SiteSearchResults_Current.Threads_ConParoleNelTitolo, "THREADS with searched words in TITLE");
        div_SiteSearchResults_Panel.appendChild(div_ContenitoreThreads_ConParoleNelTitolo);
    
        //Threads: parole nelle keywords
        let div_ContenitoreThreads_ConParoleNelleKeywords = CreaContenitoreConListaThreads(SiteSearchResults_Current.Threads_ConParoleNelleKeywords, "THREADS with searched words in KEYWORDS");
        div_SiteSearchResults_Panel.appendChild(div_ContenitoreThreads_ConParoleNelleKeywords);
    
    }
    
    function CreaContenitoreConListaThreads(ListaThreads, titoloRisultati) {
    
        //contenitore
        let div_ContenitoreThreads = document.createElement('div');
    
    
        //div titolo
        let div_TitoloThreadResults = document.createElement('div');
        div_TitoloThreadResults.style.cssText = `display:block; color:white; background-color:rgba(255,255,255,0.2);
                                          font-size:16px; font-weight:bold; margin-bottom:10px; margin-top:20px; padding:10px; border-radius:5px;`;
    
    
        if (ListaThreads) {
            div_TitoloThreadResults.innerHTML = `${titoloRisultati} (found: ${ListaThreads.length})`;
            div_ContenitoreThreads.appendChild(div_TitoloThreadResults);
    
            //lista Threads_ConParole
            ListaThreads.forEach(t => {
    
                let rThread = new ResultEntryControl_Thread(t);
                rThread.Crea_Controllo();
                div_ContenitoreThreads.appendChild(rThread.div_ThreadPanel);
            });
    
        } else {
    
            div_TitoloThreadResults.innerHTML = `${titoloRisultati}: NONE`;
            div_ContenitoreThreads.appendChild(div_TitoloThreadResults);
        }
    
        return div_ContenitoreThreads;
    
    }
    
    function CreaContenitoreConListaPosts(ListaPosts, titoloRisultati) {
    
        //contenitore
        let div_ContenitorePosts = document.createElement('div');
    
    
        //div titolo
        let div_TitoloPostResults = document.createElement('div');
        div_TitoloPostResults.style.cssText = `display:block; color:white; background-color:rgba(255,255,255,0.2);
                                               font-size:16px; font-weight:bold; margin-bottom:10px; margin-top:20px; padding:10px; border-radius:5px;`;
    
        if (ListaPosts) {
    
            div_TitoloPostResults.innerHTML = `${titoloRisultati} (found: ${ListaPosts.length})`;
            div_ContenitorePosts.appendChild(div_TitoloPostResults);
    
            ListaPosts.forEach(p => {
                let rPost = new ResultEntryControl_Post(p);
                rPost.Crea_Controllo();
                div_ContenitorePosts.appendChild(rPost.div_PostPanel);
            });
    
        } else {
            div_TitoloPostResults.innerHTML = `${titoloRisultati}: NONE`;
            div_ContenitorePosts.appendChild(div_TitoloPostResults);
        }
    
        return div_ContenitorePosts;
    
    }
    
    
    

    Clearly, just an early draft for now, I will refine and restructure it in the next few days, but the basics are there.

    This page talks to a corresponding server-side page (here denoted as _SEARCH_PAGE_NAME), which receives the requests and sends them to the DB.

    The specific part where the talking happens is in this snippet on that page:

    Code:
    //send request to server
    let myFormData = new FormData();
    myFormData.append("requestPurpose", "PerformSiteSearch");
    myFormData.append("SiteSearchRequest", JSON.stringify(SiteSearchRequest_Current));
    
    let xhr = await SendFormData(myFormData, _SEARCH_PAGE_NAME, "Requesting search");
    
    showServerSideErrors(xhr.response);
    
    if (xhr) {
    
        tempNotify(`Request sent successfully`, 30, 30, 1000);
    
        //ricezione oggetto risposta
        SiteSearchResults_Current = receiveServerObject(xhr.response, SiteSearchResults, "Obj_SiteSearchResults");
    
        Carica_SiteSearchResults_SuInterfaccia();
    
    } else { tempNotify(`Request failed`) }

    It sends the search parameters to the server by using JSON.stringify(SiteSearchRequest_Current)), which is a "stringified" version of the object SiteSearchRequest_Current, where I stored the user search parameters. The server performs the search by querying the DB and sends back the results (xhr.response). The results (essentially, the 3 above-mentioned lists) are used to change on-the-fly the page (the DOM) (without reloading the page obviously).

    The SendFormData() function is simply an XMLHttpRequest() (xhr) wrapped in a "promise" to post a form of data.
     
    Last edited: Jul 5, 2023
    #25     Jul 5, 2023
  6. Today was also another full day. I refined the search features by adding the input interface for the search keywords, for both threads and posts.

    Now we have a field called "SearchKeyWords" in both the posts and threads table:

    upload_2023-7-7_0-38-28.png

    This way, one can indicate the most important keywords of the post or thread (which could be an ad to sell something or offer services in the "Rome version" of the social medium).

    So when we do the search:
    upload_2023-7-7_2-12-8.png

    we can retrieve the threads or the posts with the given search words either in the keywords or in the title. And clicking on the result entry we go straight to the post or thread:

    upload_2023-7-7_2-13-49.png


    For the input interface, I made a unique control which I used for both threads and posts. This has the advantage for me to keep it more maintainable and abstract and for the user to have the same interface:

    Post:

    upload_2023-7-7_2-15-21.png

    Thread:

    upload_2023-7-7_2-16-42.png

    Here is the code of the search word control you see in the screenshots:

    Code:
    'use strict';
    
    class InsertKeywordsControl {
    
        async startEdit(IsForThread, ThreadID, PostID) {
    
            let myFormData = new FormData();
            myFormData.append("requestPurpose", "KeywordsRetrieve");
    
            let targetPage;
            if (IsForThread) {
                myFormData.append("ThreadID", ThreadID);
                targetPage = _THREADS_PAGE_NAME;
            } else {
                myFormData.append("PostID", PostID);
                targetPage = _POSTS_PAGE_NAME;
            }
    
            //Retrieve Thread keywords from server
    
            let xhr = await SendFormData(myFormData, targetPage, "Retrieving keywords");
    
            if (xhr) {
    
                let storedKeywords = ReceiveServerHTMLString(xhr.response, "stringWithKeywords");
    
                if (storedKeywords) {
    
                    let storedKeywords_List = storedKeywords.split(";");
                    let storedKeywords_List_clean = storedKeywords_List.filter(e => e.trim());      //elimino stringhe vuote!
    
                    //ricarica keywords negli slot predisposti
    
                    let AllKeywordsInputs = this.div_cContainer_InsertKeywordsControl.getElementsByClassName('SearchKeyword');
    
                    let wordIndex = 0;
    
                    Array.from(AllKeywordsInputs).forEach(KeyWordRetrievedInput => {
    
                        if (storedKeywords_List_clean.length > wordIndex) {
                            KeyWordRetrievedInput.value = storedKeywords_List_clean[wordIndex];
                            wordIndex++;
                        } else {
                            KeyWordRetrievedInput.value = "";  //pulisco gli eventuali rimanenti
                        }
                    });
                }
    
            } else { tempNotify(`Failed to send log out request `) }
    
        }
    
    
        crea_InsertKeywordsControl(IsForThread, ThreadID, PostID) {
    
            this.div_cContainer_InsertKeywordsControl = document.createElement("div");
            this.div_cContainer_InsertKeywordsControl.style.cssText = `display:none; margin-top:5px; background-color:${_SiteJS.BACKGROUND_COLOR_THREAD_EACHPANEL};
                                                                       padding:10px; border:1px solid ${_SiteJS.COLOR_BORDER_THREAD_PANEL};`;
    
            this.div_cContainer_InsertKeywordsControl.onclick = (e) => { disableEventPropagation(e) };
    
    
            //input delle search words
            for (let i = 0; i < _NUMBER_OF_KEY_WORDS; i++) {
    
                let input_KeyWord = document.createElement('input');
                input_KeyWord.type = "text";
                input_KeyWord.classList.add("SearchKeyword");
                setup_INPUT_WITH_BORDER(input_KeyWord);
                input_KeyWord.style.cssText += `display:inline-block; width:100px; height:16px; font-size:16px; margin:5px; margin-top:15px;`;
    
                this.div_cContainer_InsertKeywordsControl.appendChild(input_KeyWord);
            }
    
    
            let div_ButtonsContainer = document.createElement('div');
            div_ButtonsContainer.style.cssText = `display:inline-block; background-color: rgba(0,0,0, 0.2); padding:5px; border-radius:5px; margin-top:10px;`;
    
    
            //Save and exit
            let button_SaveAndExit = document.createElement('button');
            setup_BUTTON_LOOK_ANIMATION(button_SaveAndExit, _SiteJS.ADict.RESERVED_NEW_SAVE_EXIT); //"Save and Exit";
            button_SaveAndExit.style.cssText += `display:inline-block; width:200px; margin:10px;`;
            div_ButtonsContainer.appendChild(button_SaveAndExit);
    
            //Exit with No Changes
            let button_ExitNoChanges = document.createElement('button');
            setup_BUTTON_LOOK_ANIMATION(button_ExitNoChanges, _SiteJS.ADict.RESERVED_NEW_CLOSE_NO_CHANGES);  // "Close with no changes";
            button_ExitNoChanges.style.cssText += `display:inline-block; width:200px; margin:10px;`;
            div_ButtonsContainer.appendChild(button_ExitNoChanges);
    
            this.div_cContainer_InsertKeywordsControl.appendChild(div_ButtonsContainer);
    
    
            button_SaveAndExit.onclick = async () => {
    
                let AllKeywordsInputs = this.div_cContainer_InsertKeywordsControl.getElementsByClassName('SearchKeyword');
    
                let KeywordsString = "";
    
                Array.from(AllKeywordsInputs).forEach(KeyWordRetrievedInput => {
                    if (KeyWordRetrievedInput.value) {
                        KeywordsString += `${KeyWordRetrievedInput.value};`
                    }
                });
    
                if (KeywordsString) {
    
                    let myFormData = new FormData();
                    myFormData.append("requestPurpose", "KeywordsStore");
                    myFormData.append("stringWithKeywords", KeywordsString);
    
                    let targetPage;
                    if (IsForThread) {
                        myFormData.append("ThreadID", ThreadID);
                        targetPage = _THREADS_PAGE_NAME;
                    } else {
                        myFormData.append("PostID", PostID);
                        targetPage = _POSTS_PAGE_NAME;
                    }
    
                    let xhr = await SendFormData(myFormData, targetPage, "Storing keywords");
    
                    if (xhr) {
    
                        tempNotify("Key words for search updated!");
                        this.div_cContainer_InsertKeywordsControl.style.display = "none";
    
                    } else { tempNotify(`Failed to update key words for search`) }
                }
            };
    
            button_ExitNoChanges.onclick = () => {
                this.div_cContainer_InsertKeywordsControl.style.display = "none";
            };
    
        }
    }
    

    What it does it to open a panel and retrieve the current keywords from the database.
    Then the user can add/modify them and, on save, they are sent back to the server to be stored in the DB.

    You can see from the async upload snippet:

    Code:
     if (KeywordsString) {
    
         let myFormData = new FormData();
         myFormData.append("requestPurpose", "KeywordsStore");
         myFormData.append("stringWithKeywords", KeywordsString);
    
         let targetPage;
         if (IsForThread) {
             myFormData.append("ThreadID", ThreadID);
             targetPage = _THREADS_PAGE_NAME;
         } else {
             myFormData.append("PostID", PostID);
             targetPage = _POSTS_PAGE_NAME;
         }
    
         let xhr = await SendFormData(myFormData, targetPage, "Storing keywords");
    
         if (xhr) {
    
             tempNotify("Key words for search updated!");
             this.div_cContainer_InsertKeywordsControl.style.display = "none";
    
         } else { tempNotify(`Failed to update key words for search`) }
    }
    that the code can do both posts or threads, based on the boolean flag IsForThread.

    Clearly, as usual, the upload is done via XMLHttpRequest, so that the page is not reloaded and only the DOM elements that need to be changed are affected.
     
    Last edited: Jul 6, 2023
    #26     Jul 6, 2023
  7. While my cat is diligently surveying the trading activity... :) I need to get some fresh calories for my brain to go forward with the site coding...

    What's better than pasta and a sip of cold homemade hibiscus kombucha? :)

    upload_2023-7-7_14-33-58.jpeg
     
    #27     Jul 7, 2023
  8. Today I worked on reorganizing my code and on the "source code" insert feature.

    Since at least one of the sites (the initial network pentacle :)) I am preparing will be "professionally" oriented :), I think it's useful to be able to paste source code within a post without messing it up.

    ET has this feature, and in fact, I have already used it in the previous posts to show some source code.

    My implementation and the final result are a bit different, however. Here on ET when we have to enter code, we paste it into a window like this:

    upload_2023-7-8_4-9-53.png

    Then the code is placed into a fixed text area.

    I had the idea to do this a bit differently: more visual. What I do is instead insert directly a resizable textarea in the post, and then the user can use it as he likes, pasting code and changing size, and editing at will.

    Here is a short demo of what I mean:



    This result has taken more time than I thought, as I needed to understand better (pretty much by reverse engineering) how the textarea worked within a contentEditable div and how the text information could be stored (in practice, what is needed is to save in the HTML file the preformatted information from the textarea control (which at runtime is in the "value" property of the textarea node), before sending it to the server and then restoring it in the textarea value, when the HTML file comes back for view/edit).

    So regrettably had to skip my planned exercises for today, I hope I can catch up tomorrow... :)
     
    #28     Jul 7, 2023
  9. While I was sleeping it occurred to me that the previous class we saw previously to specify some search words for either thread or posts could indeed be made more general (thus avoiding its mere twofold nature).

    In fact, as the site expands (in the far future :) ) we may need keywords to be stored for users too. We might even use it for the forums/categories (even though this would make sense only for sites with a large number of categories, like the Rome ad site, which has hundreds of categories). So I believe that it may be useful to envision a field for that too in all the tables (forums/threads/posts/users) and make it more general.

    This way we can reuse the same object we used to retrieve/store the keywords of the threads and the post, for the users' keywords too. So this is the new version of the class:

    Code:
    'use strict';
    
    class SearchWordsInputControl {
    
        crea_cSearchWordsInputControl(SearchRequestWithSearchWords_Current) {
    
            this.SearchRequestWithSearchWords_Current = SearchRequestWithSearchWords_Current;
    
            //pannello contenitore globale
            this.div_cSearchWordsInputControl_Container = document.createElement('div');
            this.div_cSearchWordsInputControl_Container.style.cssText = `background-color:rgba(250,250,255, 0.3); margin-top: 10px; margin-bottom: 10px; padding:10px; border-radius:5px`;
    
            //input delle search words
            for (let w = 0; w < _NUMBER_OF_SEARCH_WORDS; w++) {
    
                let input_SearchWord = document.createElement('input');
                input_SearchWord.type = "text";
                input_SearchWord.classList.add("SearchKeyword");
                setup_INPUT_WITH_BORDER(input_SearchWord);
                input_SearchWord.style.cssText += `width:200px; height:16px; font-size:16px; margin:5px; margin-top:15px;`;
    
                this.div_cSearchWordsInputControl_Container.appendChild(input_SearchWord);
            }
    
    
            //tipo ricerca AND OR ------------------------------------------------------------------------------------------------------------------------------
    
            let label_ContenitoreTipoRicercaAND_OR = document.createElement('label');
            label_ContenitoreTipoRicercaAND_OR.innerHTML = "Tipo di ricerca: OR (almeno una parola e' presente) o AND (tutte le parole sono simultaneamente presenti)";
            label_ContenitoreTipoRicercaAND_OR.style.cssText = "display:block; margin-top:10px; margin-bottom:5px; color:lime;";
    
            //OR
            let label_RicercaOR = document.createElement('label');
            label_RicercaOR.innerHTML = "OR";
            label_RicercaOR.style.cssText = "color:#aaffff; cursor:pointer; margin-left:10px; ";
    
            //radio
            this.input_radio_RicercaOR = document.createElement('input');
            this.input_radio_RicercaOR.type = "radio";
            this.input_radio_RicercaOR.name = 'SearchTypeAND_OR';
            this.input_radio_RicercaOR.style.cssText += "width:15px; height:15px; cursor:pointer;";
    
            label_RicercaOR.appendChild(this.input_radio_RicercaOR);
    
            //label in label per allineare
            label_ContenitoreTipoRicercaAND_OR.appendChild(label_RicercaOR);
    
            //AND
            let label_RicercaAND = document.createElement('label');
            label_RicercaAND.innerHTML = "AND";
            label_RicercaAND.style.cssText = "color:#aaffff; cursor:pointer; margin-left:10px;";
            //radio
            this.input_radio_RicercaAND = document.createElement('input');
            this.input_radio_RicercaAND.type = "radio";
            this.input_radio_RicercaAND.name = 'SearchTypeAND_OR';
            this.input_radio_RicercaAND.style.cssText += "width:15px; height:15px; cursor:pointer;";
    
            label_RicercaAND.appendChild(this.input_radio_RicercaAND);
    
            //label in label per allineare
            label_ContenitoreTipoRicercaAND_OR.appendChild(label_RicercaAND);
    
            //pannello search request
    
            this.div_cSearchWordsInputControl_Container.appendChild(label_ContenitoreTipoRicercaAND_OR);
    
            // --------------------------------------------------------------------------------------------------------
    
    
            //tipo ricerca Exact match/Like ------------------------------------------------------------------------------------------------------------------------------
    
            let label_ContenitoreTipoRicerca_LIKE_EXACT = document.createElement('label');
            label_ContenitoreTipoRicerca_LIKE_EXACT.innerHTML = "Match type";
            label_ContenitoreTipoRicerca_LIKE_EXACT.style.cssText = "display:block; margin-top:10px; margin-bottom:5px; color:lime;";
    
            //LIKE
            let label_Ricerca_LIKE = document.createElement('label');
            label_Ricerca_LIKE.innerHTML = "Like";
            label_Ricerca_LIKE.style.cssText = "color:#aaffff; cursor:pointer; margin-left:10px; ";
    
            //radio
            this.input_radio_Ricerca_LIKE = document.createElement('input');
            this.input_radio_Ricerca_LIKE.type = "radio";
            this.input_radio_Ricerca_LIKE.name = 'SearchTypeLIKEorEXACT';
            this.input_radio_Ricerca_LIKE.style.cssText += "width:15px; height:15px; cursor:pointer;";
    
            label_Ricerca_LIKE.appendChild(this.input_radio_Ricerca_LIKE);
    
            //label in label per allineare
            label_ContenitoreTipoRicerca_LIKE_EXACT.appendChild(label_Ricerca_LIKE);
    
    
            //EXACT
            let label_Ricerca_EXACT = document.createElement('label');
            label_Ricerca_EXACT.innerHTML = "Exact Match";
            label_Ricerca_EXACT.style.cssText = "color:#aaffff; cursor:pointer; margin-left:10px;";
            //radio
            this.input_radio_Ricerca_EXACT = document.createElement('input');
            this.input_radio_Ricerca_EXACT.type = "radio";
            this.input_radio_Ricerca_EXACT.name = 'SearchTypeLIKEorEXACT';
            this.input_radio_Ricerca_EXACT.style.cssText += "width:15px; height:15px; cursor:pointer;";
    
            label_Ricerca_EXACT.appendChild(this.input_radio_Ricerca_EXACT);
    
            //label in label per allineare
            label_ContenitoreTipoRicerca_LIKE_EXACT.appendChild(label_Ricerca_EXACT);
    
            //pannello search request
    
            this.div_cSearchWordsInputControl_Container.appendChild(label_ContenitoreTipoRicerca_LIKE_EXACT);
    
            // --------------------------------------------------------------------------------------------------------
    
        }
    
        loadSearchWordsToInterface() {
    
            //ricarica impostazioni ricerca precedente
    
            this.input_radio_RicercaOR.checked = this.SearchRequestWithSearchWords_Current.RicercaParoleConOR;
            this.input_radio_RicercaAND.checked = !this.SearchRequestWithSearchWords_Current.RicercaParoleConOR;
            this.input_radio_Ricerca_LIKE.checked = this.SearchRequestWithSearchWords_Current.RicercaParoleConLIKE;
            this.input_radio_Ricerca_EXACT.checked = !this.SearchRequestWithSearchWords_Current.RicercaParoleConLIKE;
    
            //ricarica parole precedente ricerca negli slot disponibili
    
            let AllSearchWordInputs = this.div_cSearchWordsInputControl_Container.getElementsByClassName('SearchKeyword');
    
            let wordIndex = 0;
            Array.from(AllSearchWordInputs).forEach(SearchwordInput => {
                if (this.SearchRequestWithSearchWords_Current.ParoleCercate.length > wordIndex) {
                    SearchwordInput.value = this.SearchRequestWithSearchWords_Current.ParoleCercate[wordIndex];
                    wordIndex++;
                }
            });
        }
    
        readSearchWordsFromInterfaceAndSearchProperties() {
    
            //lista parole
            this.SearchRequestWithSearchWords_Current.ParoleCercate = [];
    
            let AllSearchWordInputs = this.div_cSearchWordsInputControl_Container.getElementsByClassName('SearchKeyword');
            Array.from(AllSearchWordInputs).forEach(inputCheckbox => {
                //solo se non vuota
                let searchWord = inputCheckbox.value.trim();
                if (searchWord) {
                    this.SearchRequestWithSearchWords_Current.ParoleCercate.push(searchWord);
                }
            });
    
            //proprieta ricerca
            this.SearchRequestWithSearchWords_Current.RicercaParoleConOR = this.input_radio_RicercaOR.checked;
            this.SearchRequestWithSearchWords_Current.RicercaParoleConLIKE = this.input_radio_Ricerca_LIKE.checked;
    
        }
    
    }
    

    where the SearchRequestWithSearchWords object is (at the moment) defined as follows:

    Code:
    class SearchRequestWithSearchWords {
    
        ParoleCercate;  //array stringhe
        RicercaParoleConOR;
        RicercaParoleConLIKE;
    
        //ricerca post utente specifico
        ForUserProfile;
    
        //ricerca post tutti utenti
        RicercaInThread_Titles;
        RicercaInThread_Keywords;
        RicercaInPost_Keywords;
    
    
        constructor(ForUserProfile) {
    
            this.ParoleCercate = [];
            this.RicercaParoleConOR = true;
            this.RicercaParoleConLIKE = true;
    
            this.ForUserProfile = ForUserProfile;
    
            if (!this.ForUserProfile) {
                this.RicercaInThread_Titles = true;
                this.RicercaInThread_Keywords = true;
                this.RicercaInPost_Keywords = true;
            }
        }
    }

    Here is how it looks on our Site Search page:

    upload_2023-7-10_0-35-31.png


    This can also be immediately reused in a feature to search and filter (by keywords) all the posts of any user on the User Profile page.

    I am just adding that. Since this new feature will also require all the search functionalities, it is useful to "take them" out of the Search page and make a general class so we can reuse it on the User Profile page, and wherever we need it in the future in the site. Now since we have similar code on two different pages: the general post search page and the user profile page (for the search of the post of the user shown in the profile), we want to now unify these functionalities into a unique abstract object.

    (For instance, in the future we might include a search of users by keywords.)

    In order not to shoot ourselves in the balls and make our life harder when maintaining the code it is necessary to make now this little effort of abstraction. This will enormously simplify both the search and user profile page. In fact, changing the code at a later time becomes increasingly difficult, because one does not "hold it all in mind" and must therefore make, every time a change is needed, a huge effort to simply re-enter the "mindset".

    So the resulting class would hold a general (and further generalizable) search panel, where one can specify anything and all results will remain within the panel, which could be inserted as objects anywhere.

    In this new "control", we can also encapsulate the previously seen functionality to input/resume the search words.


    upload_2023-7-10_1-15-5.png


    Here is the brand-new search control:


    Code:
    'use strict';
    
    class ResultEntryControl_Post {
    
        constructor(threadPost, UserInfo_Current) {
            this.threadPost = threadPost;
            this.UserInfo_Current = UserInfo_Current;
        }
    
        Crea_cResultEntryControl_Post() {
    
            this.div_PostPanel = document.createElement('div');
            this.div_PostPanel.style.cssText = `background-color:rgba(255,255,255,0.2); margin:5px;`;
    
    
            //avatar OP
            let div_img = document.createElement("img");
            div_img.src = `${this.threadPost.AvatarFileUserWithFolder}${cacheBreaker_Time()}`;
            div_img.style.cssText = `display:inline-block; height:60px; margin-top:10px;`;
    
            this.div_PostPanel.appendChild(div_img);
    
            //ID e nick insieme
            let div_TitleAndNick = document.createElement("div");
            div_TitleAndNick.style.cssText = `display:inline-block; vertical-align: top; cursor:pointer; text-align:left; padding: 10px; margin-left:10px`;
    
            //ID post/autore
            let div_TitoloThread = document.createElement("div");
            div_TitoloThread.innerHTML = `Post ID: ${this.threadPost.PostID} in thread ID: ${this.threadPost.ThreadID} by user ${this.threadPost.UserAuthorID}`;
            div_TitoloThread.style.cssText = `font-size:12px; font-weight:bold; font-style:italic; color:${_SiteJS.COLOR_THREAD_TITLE};`;
    
            div_TitleAndNick.appendChild(div_TitoloThread);
    
            //nick
            let div_NicknameUser = document.createElement("div");
    
            let coloreNick;
            if ((this.UserInfo_Current) && (this.threadPost.UserAuthorID === this.UserInfo_Current.UserID)) {
                //se stesso
                div_NicknameUser.innerHTML = `${_myUserEmoji}${this.threadPost.NicknameAuthor}`;
                coloreNick = "yellow";
            } else {
                div_NicknameUser.innerHTML = `${this.threadPost.NicknameAuthor} `;
                coloreNick = "black";
            }
            div_NicknameUser.style.cssText = `margin-top:5px; font-size:12px; font-weight:bold; color:${coloreNick};`;
            div_TitleAndNick.appendChild(div_NicknameUser);
    
    
            //storedKeywords
            let div_storedKeywords = document.createElement("div");
            div_storedKeywords.innerHTML = `stored keywords:"${this.threadPost.SearchKeyWords}"`;
            div_storedKeywords.style.cssText = `margin-top:5px; font-size:12px; color: white;`;
    
            div_TitleAndNick.appendChild(div_storedKeywords);
    
            this.div_PostPanel.appendChild(div_TitleAndNick);
    
            this.div_PostPanel.onclick = () => {
                window.location.href = `${_POSTS_PAGE_NAME}?Intention=LinkToPost&PostID=${this.threadPost.PostID}`;
            };
        }
    }
    
    class ResultEntryControl_Thread {
    
        constructor(forumThread, UserInfo_Current) {
            this.forumThread = forumThread;
            this.UserInfo_Current = UserInfo_Current;
        }
    
        Crea_cResultEntryControl_Thread() {
    
            this.div_ThreadPanel = document.createElement('div');
            this.div_ThreadPanel.style.cssText = `background-color:rgba(255,255,255,0.2); margin:5px;`;
    
    
            //avatar OP
            let div_img = document.createElement("img");
            div_img.src = `${this.forumThread.AvatarAuthorWithFolder}${cacheBreaker_Time()}`;
            div_img.style.cssText = `display:inline-block; height:60px; margin-top:10px;`;
    
            this.div_ThreadPanel.appendChild(div_img);
    
            //titolo e nick insieme
            let div_TitleAndNick = document.createElement("div");
            div_TitleAndNick.style.cssText = `display:inline-block; vertical-align: top; cursor:pointer; text-align:left; padding: 10px; margin-left:10px`;
    
            //titolo Thread
            let div_TitoloThread = document.createElement("div");
            div_TitoloThread.innerHTML = `${this.forumThread.ThreadTitle}`;
            div_TitoloThread.style.cssText = `font-size:16px; font-weight:bold; font-style:italic; color:#bbffdd;`;
    
            div_TitleAndNick.appendChild(div_TitoloThread);
    
            //nick
            let div_NicknameUser = document.createElement("div");
    
    
            let coloreNick;
            if ((this.UserInfo_Current) && (this.forumThread.OpUserID === this.UserInfo_Current.UserID)) {
                //se stesso
                div_NicknameUser.innerHTML = `${_myUserEmoji}${this.forumThread.NicknameAuthor} user id: ${this.forumThread.OpUserID}`;
                coloreNick = "yellow";
            } else {
                div_NicknameUser.innerHTML = `${this.forumThread.NicknameAuthor} user id: ${this.forumThread.OpUserID}`;
                coloreNick = "black";
            }
            div_NicknameUser.style.cssText = `margin-top:5px; font-size:12px; font-weight:bold; color:${coloreNick};`;
    
    
            //storedKeywords
            let div_storedKeywords = document.createElement("div");
    
            if (this.forumThread.SearchKeyWords) {
                div_storedKeywords.innerHTML = `stored keywords:"${this.forumThread.SearchKeyWords}"`;
            } else {
                div_storedKeywords.innerHTML = `no keywords defined`;
            }
    
            div_storedKeywords.style.cssText = `margin-top:5px; font-size:12px; color: white;`;
    
            div_TitleAndNick.appendChild(div_storedKeywords);
    
            this.div_ThreadPanel.appendChild(div_TitleAndNick);
    
            this.div_ThreadPanel.onclick = () => {
    
                //window.location.href = `${_POSTS_PAGE_NAME}?thread=${this.forumThread.ThreadID}&ThreadPage=1`;    tentativo con pagina 1 (va bottom pero, per ora lascio stare)
                window.location.href = `${_POSTS_PAGE_NAME}?thread=${this.forumThread.ThreadID}`;
            };
        }
    }
    
    class SearchPanelWithSearchWords {
    
        crea_cSearchPanelWithSearchWords(SearchRequestWithSearchWords_Current, ResultOfSearchWithSearchWords_Current, UserID_UserSearched, UserInfo_Current, InitialDisplay) {
    
            this.SearchRequestWithSearchWords_Current = SearchRequestWithSearchWords_Current;
            this.ResultOfSearchWithSearchWords_Current = ResultOfSearchWithSearchWords_Current;
    
    
            this.UserID_UserSearched = UserID_UserSearched;
            this.UserInfo_Current = UserInfo_Current;           //just for visuals: to enlight posts of current user, if logged
    
            let titolo;
            if (this.SearchRequestWithSearchWords_Current.ForUserProfile) {
                titolo = "Search in post of this user (by keywords if specified)";
            } else {
                titolo = "Search posts keywords or threads titles/keywords";
            }
    
            //pannello contenitore globale
            this.div_cSearchPanelWithSearchWords_Container = document.createElement('div');
            this.div_cSearchPanelWithSearchWords_Container.style.cssText = `background-color:rgba(255,255,255,0.2); padding:10px;`;
            this.div_cSearchPanelWithSearchWords_Container.style.display = InitialDisplay;
    
            //titolo search
            let div_TitoloRicerca = document.createElement('div');
            div_TitoloRicerca.innerHTML = titolo;
            div_TitoloRicerca.style.cssText = `color:white; background-color:white; text-align: center; background-color:rgba(255,255,255,0.2);
                                               font-size:32px; font-weight:bold; padding:10px; border-radius:5px;`;
            this.div_cSearchPanelWithSearchWords_Container.appendChild(div_TitoloRicerca);
    
    
            //pannello search words input --------------------------------------------------
            this.mySearchWordsInputControl = new SearchWordsInputControl();
            this.mySearchWordsInputControl.crea_cSearchWordsInputControl(this.SearchRequestWithSearchWords_Current);
            this.div_cSearchPanelWithSearchWords_Container.appendChild(this.mySearchWordsInputControl.div_cSearchWordsInputControl_Container);
            //-------------------------------------------------------------------------------
    
    
            //bottone Search!
            this.button_Search = document.createElement('button');
            setup_BUTTON_LOOK_ANIMATION(this.button_Search, "Search!");
            this.button_Search.style.cssText += "width:auto; margin-left:20px; font-size: 30px";
            this.div_cSearchPanelWithSearchWords_Container.appendChild(this.button_Search);
    
            this.button_Search.onclick = () => { this.creaNuovaSearchRequestWithSearchWords_DaInterfaccia() };
    
            //pannello contenitore globale dei risultati ------------------------------------------------------------
            this.div_ResultOfSearchWithSearchWords_Panel = document.createElement('div');
            this.div_ResultOfSearchWithSearchWords_Panel.style.cssText = `background-color:rgba(255,255,255,0.2); padding:10px;`;
            this.div_cSearchPanelWithSearchWords_Container.appendChild(this.div_ResultOfSearchWithSearchWords_Panel);
    
            if (!this.SearchRequestWithSearchWords_Current.ForUserProfile) {   //profile search dos not need this
    
                //where to search ---------------------------------------------------------------------------------------------------------------------------------
                let label_ContenitoreTipoRicercaPOST_THREAD = document.createElement('label');
                label_ContenitoreTipoRicercaPOST_THREAD.innerHTML = "Dove cercare:";
                label_ContenitoreTipoRicercaPOST_THREAD.style.cssText = "display:block; margin-top:10px; margin-bottom:5px; color:lime;";
    
                //THREADS' TITLES
                let label_RicercaTHREADS_TITLE = document.createElement('label');
                label_RicercaTHREADS_TITLE.innerHTML = "Nei TITOLI delle THREADS";
                label_RicercaTHREADS_TITLE.style.cssText = "color:#aaffff; cursor:pointer; margin-left:10px; ";
    
                //radio
                this.input_check_RicercaTHREADS_TITLES = document.createElement('input');
                this.input_check_RicercaTHREADS_TITLES.type = "checkbox";
                this.input_check_RicercaTHREADS_TITLES.name = 'SearchTypeTHREADS_POSTS';
                this.input_check_RicercaTHREADS_TITLES.style.cssText += "width:15px; height:15px; cursor:pointer;";
    
                label_RicercaTHREADS_TITLE.appendChild(this.input_check_RicercaTHREADS_TITLES);
    
                //label in label per allineare
                label_ContenitoreTipoRicercaPOST_THREAD.appendChild(label_RicercaTHREADS_TITLE);
    
                //THREADS' KEYWORDS
                let label_RicercaTHREADS_KEYWORDS = document.createElement('label');
                label_RicercaTHREADS_KEYWORDS.innerHTML = "Nelle KEYWORDS delle Threads";
                label_RicercaTHREADS_KEYWORDS.style.cssText = "color:#aaffff; cursor:pointer; margin-left:10px; ";
    
                //radio
                this.input_check_RicercaTHREADS_KEYWORDS = document.createElement('input');
                this.input_check_RicercaTHREADS_KEYWORDS.type = "checkbox";
                this.input_check_RicercaTHREADS_KEYWORDS.name = 'SearchTypeTHREADS_POSTS';
                this.input_check_RicercaTHREADS_KEYWORDS.style.cssText += "width:15px; height:15px; cursor:pointer;";
    
                label_RicercaTHREADS_KEYWORDS.appendChild(this.input_check_RicercaTHREADS_KEYWORDS);
    
                //label in label per allineare
                label_ContenitoreTipoRicercaPOST_THREAD.appendChild(label_RicercaTHREADS_KEYWORDS);
    
                //POSTS KEYWORDS
                let label_RicercaPOSTS = document.createElement('label');
                label_RicercaPOSTS.innerHTML = "Nelle KEYWORDS dei POSTS";
                label_RicercaPOSTS.style.cssText = "color:#aaffff; cursor:pointer; margin-left:10px;";
                //radio
                this.input_check_RicercaPOSTS_KEYWORDS = document.createElement('input');
                this.input_check_RicercaPOSTS_KEYWORDS.type = "checkbox";
                this.input_check_RicercaPOSTS_KEYWORDS.name = 'SearchTypeTHREADS_POSTS';
                this.input_check_RicercaPOSTS_KEYWORDS.style.cssText += "width:15px; height:15px; cursor:pointer;";
    
                label_RicercaPOSTS.appendChild(this.input_check_RicercaPOSTS_KEYWORDS);
    
                //label in label per allineare
                label_ContenitoreTipoRicercaPOST_THREAD.appendChild(label_RicercaPOSTS);
    
                //tutti i comandi
                this.div_cSearchPanelWithSearchWords_Container.appendChild(label_ContenitoreTipoRicercaPOST_THREAD);
    
                // ---------------------------------------------------------------------------------------------------------------------------------
            }
    
    
            //caricamento eventuale richiesta di ricerca esistente
    
            //reimpostazione valori su pannello ricerca
            this.mySearchWordsInputControl.loadSearchWordsToInterface(this.SearchRequestWithSearchWords_Current);
    
            if (!this.SearchRequestWithSearchWords_Current.ForUserProfile) {
                this.input_check_RicercaTHREADS_TITLES.checked = this.SearchRequestWithSearchWords_Current.RicercaInThread_Titles;
                this.input_check_RicercaTHREADS_KEYWORDS.checked = this.SearchRequestWithSearchWords_Current.RicercaInThread_Keywords;
                this.input_check_RicercaPOSTS_KEYWORDS.checked = this.SearchRequestWithSearchWords_Current.RicercaInPost_Keywords;
            }
    
            //precedenti risultati se presenti
    
            this.carica_ResultOfSearchWithSearchWords_SuInterfaccia();
    
        }
    
    
        //Chiamata alla pressione del bottone search!
        async creaNuovaSearchRequestWithSearchWords_DaInterfaccia() {   //ForUserProfile is for search on single user's posts
    
            //fill search object from interface
            this.mySearchWordsInputControl.readSearchWordsFromInterfaceAndSearchProperties();
    
            //complete request properties with targets
            if (!this.SearchRequestWithSearchWords_Current.ForUserProfile) {
                //target di ricerca (non definiti per ricerca su user)
                this.SearchRequestWithSearchWords_Current.RicercaInPost_Keywords = this.input_check_RicercaPOSTS_KEYWORDS.checked;
                this.SearchRequestWithSearchWords_Current.RicercaInThread_Titles = this.input_check_RicercaTHREADS_TITLES.checked;
                this.SearchRequestWithSearchWords_Current.RicercaInThread_Keywords = this.input_check_RicercaTHREADS_KEYWORDS.checked;
            }
    
            let requestPurpose;
            let myTargetPage;
    
            let myFormData = new FormData();
    
            if (!this.SearchRequestWithSearchWords_Current.ForUserProfile) {
                requestPurpose = "PerformSearchInAllPostsOrThreads";
                myTargetPage = _SEARCH_PAGE_NAME;
                if (!SearchRequestWithSearchWords_Current.ParoleCercate) {
                    tempNotify("No search words");
                    return;
                }
            } else {
                requestPurpose = "PerformSearchInPostsOfSpecifiedUser";
                myTargetPage = _PROFILE_PAGE_NAME;
                myFormData.append("UserID", this.UserID_UserSearched);
                //for profile search, it is fine not to have search words (will find all post of the user)
            }
    
            myFormData.append("SearchRequestWithSearchWords", JSON.stringify(this.SearchRequestWithSearchWords_Current));
            myFormData.append("requestPurpose", requestPurpose);
    
            let xhr = await SendFormData(myFormData, myTargetPage, "Requesting search");
    
            if (xhr) {
    
                tempNotify(`Search successful`, 30, 30, 1000);
    
                //ricezione oggetto risposta
    
                this.ResultOfSearchWithSearchWords_Current = receiveServerObject(xhr.response, ResultOfSearchWithSearchWords, "Obj_ResultOfSearchWithSearchWords");
    
                this.carica_ResultOfSearchWithSearchWords_SuInterfaccia();
    
            }
        }
    
    
    
        //Risultati ricerca
        carica_ResultOfSearchWithSearchWords_SuInterfaccia() {
    
            //reset pannello risultati
            this.div_ResultOfSearchWithSearchWords_Panel.innerHTML = "";
    
            if (!this.ResultOfSearchWithSearchWords_Current) { return }
    
            //titolo response
            let div_TitoloRispostaSearch = document.createElement('div');
            div_TitoloRispostaSearch.innerHTML = "Search Response";
            div_TitoloRispostaSearch.style.cssText = `display:block; color:white; background-color:yellow; text-align:center; background-color:rgba(0,0,100,0.2);
                                                      font-size:32px; font-weight:bold; padding:10px; border-radius: 5px; margin-bottom:10px; `;
            this.div_ResultOfSearchWithSearchWords_Panel.appendChild(div_TitoloRispostaSearch);
    
    
            //show search words (debug only)
            if (this.ResultOfSearchWithSearchWords_Current.SearchRequestWithSearchWords) {
    
                this.ResultOfSearchWithSearchWords_Current.SearchRequestWithSearchWords.ParoleCercate.forEach(p => {
                    let div_parolaCercata = document.createElement('div');
                    div_parolaCercata.style.cssText = `display:inline-block; color:red; background-color:white; margin:10px`;
                    div_parolaCercata.innerHTML = `${p}`;
                    this.div_ResultOfSearchWithSearchWords_Panel.appendChild(div_parolaCercata);
                });
    
            } else {
                let div_parolaCercata = document.createElement('div');
                div_parolaCercata.style.cssText = `display:inline-block; color:red; background-color:white;`;
                div_parolaCercata.innerHTML = "List of searched words is empty";
                this.div_ResultOfSearchWithSearchWords_Panel.appendChild(div_parolaCercata);
            }
    
    
            if (this.SearchRequestWithSearchWords_Current.ForUserProfile) {
    
                //POSTS of user in viewed profile, parole nelle keywords
                let div_ContenitorePOSTS_UtenteSpecifico_ConParoleNelleKeyWords = this.creaContenitoreConListaPosts(this.ResultOfSearchWithSearchWords_Current.POSTS_UtenteSpecifico_ConParoleNelleKeyWords, `User's POSTS (with searched words in KEYWORDS) User id: ${this.UserID_UserSearched}`);
                this.div_ResultOfSearchWithSearchWords_Panel.appendChild(div_ContenitorePOSTS_UtenteSpecifico_ConParoleNelleKeyWords);
    
            } else {
    
                //Posts: parole nelle keywords
                let div_ContenitorePosts_ConParoleNelleKeywords = this.creaContenitoreConListaPosts(this.ResultOfSearchWithSearchWords_Current.Posts_ConParoleNelleKeywords, "POSTS with searched words in KEYWORDS");
                this.div_ResultOfSearchWithSearchWords_Panel.appendChild(div_ContenitorePosts_ConParoleNelleKeywords);
    
                //Threads: parole nel titolo
                let div_ContenitoreThreads_ConParoleNelTitolo = this.creaContenitoreConListaThreads(this.ResultOfSearchWithSearchWords_Current.Threads_ConParoleNelTitolo, "THREADS with searched words in TITLE");
                this.div_ResultOfSearchWithSearchWords_Panel.appendChild(div_ContenitoreThreads_ConParoleNelTitolo);
    
                //Threads: parole nelle keywords
                let div_ContenitoreThreads_ConParoleNelleKeywords = this.creaContenitoreConListaThreads(this.ResultOfSearchWithSearchWords_Current.Threads_ConParoleNelleKeywords, "THREADS with searched words in KEYWORDS");
                this.div_ResultOfSearchWithSearchWords_Panel.appendChild(div_ContenitoreThreads_ConParoleNelleKeywords);
            }
    
        }
    
    
    
    
        //Contenitori per risultati
        creaContenitoreConListaPosts(ListaPosts, titoloRisultati) {
    
            //contenitore
            let div_ContenitorePosts = document.createElement('div');
    
            //div titolo
            let div_TitoloPostResults = document.createElement('div');
            div_TitoloPostResults.style.cssText = `display:block; color:white; background-color:black;
                                                   font-size:14px; font-weight:bold; margin-bottom:10px; margin-top:20px; padding:10px; border-radius:5px;`;
    
            if (ListaPosts) {
    
                div_TitoloPostResults.innerHTML = `${titoloRisultati} (found: ${ListaPosts.length})`;
                div_ContenitorePosts.appendChild(div_TitoloPostResults);
    
                ListaPosts.forEach(p => {
                    let rPost = new ResultEntryControl_Post(p, this.UserInfo_Current);
                    rPost.Crea_cResultEntryControl_Post();
                    div_ContenitorePosts.appendChild(rPost.div_PostPanel);
                });
    
            } else {
                div_TitoloPostResults.innerHTML = `${titoloRisultati}: NONE`;
                div_ContenitorePosts.appendChild(div_TitoloPostResults);
            }
    
            return div_ContenitorePosts;
    
        }
    
        creaContenitoreConListaThreads(ListaThreads, titoloRisultati) {
    
            //contenitore
            let div_ContenitoreThreads = document.createElement('div');
    
            //div titolo
            let div_TitoloThreadResults = document.createElement('div');
            div_TitoloThreadResults.style.cssText = `display:block; color:white; background-color:black;
                                                     font-size:14px; font-weight:bold; margin-bottom:10px; margin-top:20px; padding:10px; border-radius:5px;`;
    
            if (ListaThreads) {
                div_TitoloThreadResults.innerHTML = `${titoloRisultati} (found: ${ListaThreads.length})`;
                div_ContenitoreThreads.appendChild(div_TitoloThreadResults);
    
                //lista Threads_ConParole
                ListaThreads.forEach(t => {
    
                    let rThread = new ResultEntryControl_Thread(t, this.UserInfo_Current);
                    rThread.Crea_cResultEntryControl_Thread();
                    div_ContenitoreThreads.appendChild(rThread.div_ThreadPanel);
                });
    
            } else {
    
                div_TitoloThreadResults.innerHTML = `${titoloRisultati}: NONE`;
                div_ContenitoreThreads.appendChild(div_TitoloThreadResults);
            }
    
            return div_ContenitoreThreads;
    
        }
    
    }
    

    In fact, this new reusable control simplifies enormously our code, as for instance the Search page (we have seen previously) now reduces to simply this :) :


    Code:
    'use strict';
    
    showServerSideErrors(document);
    
    setBasicPageBehavior("Site search", _SiteJS.COLOR_HOMEPAGE_BACKGROUND, _SiteJS.SITE_ICON);
    
    //Collect server objects
    let UserInfo_Current = receiveServerObject(document, UserInfo, "Obj_UserInfo");
    //ricerche
    let SearchRequestWithSearchWords_Current = receiveServerObject(document, SearchRequestWithSearchWords, "Obj_SearchRequestWithSearchWords");
    let ResultOfSearchWithSearchWords_Current = receiveServerObject(document, ResultOfSearchWithSearchWords, "Obj_ResultOfSearchWithSearchWords");
    
    
    let div_SearchRequestWithSearchWords_Panel = document.createElement('div');
    div_SearchRequestWithSearchWords_Panel.style.cssText = `background-color:rgba(255,255,255,0.2); padding:10px;`;
    
    let div_smallUserInfo_Top = crea_SMALL_USER_INFO();
    document.body.appendChild(div_smallUserInfo_Top);
    
    let div_goBottomButton = crea_GO_BOTTOM_BUTTON();
    document.body.appendChild(div_goBottomButton);
    
    let div_HomePage_button_Top = crea_HOME_BUTTON();
    document.body.appendChild(div_HomePage_button_Top);
    
    // Search panel -------------------------------------------------------------------------------------------------
    
    //se esiste una precedente richiesta uso quella, altrimenti ne creo una nuova
    if (!SearchRequestWithSearchWords_Current) { SearchRequestWithSearchWords_Current = new SearchRequestWithSearchWords(false) }
    let mySearchPanelWithSearchWords = new SearchPanelWithSearchWords();
    mySearchPanelWithSearchWords.crea_cSearchPanelWithSearchWords(SearchRequestWithSearchWords_Current, ResultOfSearchWithSearchWords_Current, undefined, UserInfo_Current, "block");
    document.body.appendChild(mySearchPanelWithSearchWords.div_cSearchPanelWithSearchWords_Container);
    //  -------------------------------------------------------------------------------------------------------------
    
    let div_smallUserInfo_bottom = crea_SMALL_USER_INFO();
    document.body.appendChild(div_smallUserInfo_bottom);
    
    let div_goTopButton = crea_GO_TOP_BUTTON();
    document.body.appendChild(div_goTopButton);
    
    let div_HomePage_button_bottom = crea_HOME_BUTTON();
    document.body.appendChild(div_HomePage_button_bottom);
    
    AggiungiAnimazioneBottoni();
    

    which is quite elegant imho. You can see the point where the new search control is used:

    Code:
    if (!SearchRequestWithSearchWords_Current) { SearchRequestWithSearchWords_Current = new SearchRequestWithSearchWords(false) }
    let mySearchPanelWithSearchWords = new SearchPanelWithSearchWords();
    mySearchPanelWithSearchWords.crea_cSearchPanelWithSearchWords(SearchRequestWithSearchWords_Current, ResultOfSearchWithSearchWords_Current, undefined, UserInfo_Current, "block");
    document.body.appendChild(mySearchPanelWithSearchWords.div_cSearchPanelWithSearchWords_Container);

    Note that this page (as the Search page) is also able to reload both the request properties (search words, etc. ) and the results of previous searches if the users return to this page :) They are in fact stored as session objects on the server side and every time sent back to the client.

    Now we can immediately use this general control also on the User Profile page, where we can perform a search on the post of the current user under view:

    upload_2023-7-10_0-58-4.png


    With the occasion of this small refactoring, I am also adding the option to perform a "like" (that is loose) or "exact match" search (essentially LIKE or CHARINDEX/InStr type functions, which are case insensitive, by default):

    On the server side a function generating this piece of the SQL query could look like this (this is just the WHERE part of the SELECT, relative to the search) [I am keeping into account possible SQL dialects variants for "CHARINDEX", as I am envisioning multiple DB connectivity]:

    Code:
    public string CreaSQL_WHERE_PerRicercaParole(SearchRequestWithSearchWords SearchRequestWithSearchWords, string NomeCampoCercato)
    {
    
        if (SearchRequestWithSearchWords.ParoleCercate.Count < 1)
            return null;
    
        string OperatoreLogico;
        if (SearchRequestWithSearchWords.RicercaParoleConOR)
        {
            OperatoreLogico = " OR ";
        }
        else
        {
            OperatoreLogico = " AND ";
        }
    
        bool UseLIKE = SearchRequestWithSearchWords.RicercaParoleConLIKE;
    
        string SQL_Ricerca_WithAND;
    
        if (UseLIKE)
        {
            string p = SearchRequestWithSearchWords.ParoleCercate(0);
            SQL_Ricerca_WithAND = NomeCampoCercato + " LIKE '%" + p + "%' ";     // case-insensitive search
            for (int i = 1, loopTo = SearchRequestWithSearchWords.ParoleCercate.Count - 1; i <= loopTo; i++)
            {
                p = SearchRequestWithSearchWords.ParoleCercate(i);
                SQL_Ricerca_WithAND += OperatoreLogico + NomeCampoCercato + " LIKE '%" + p + "%' ";
            }
        }
        else
        {
            // CHARINDEX  per SQL SERVER
            string FunzioneSottostringaDi;
            if (_USE_SQL_SERVER)
            {
                FunzioneSottostringaDi = "CHARINDEX";       // case-insensitive search
            }
            else
            {
                FunzioneSottostringaDi = "InStr";
            }           // case-insensitive search
    
            string p = SearchRequestWithSearchWords.ParoleCercate(0);
            SQL_Ricerca_WithAND = FunzioneSottostringaDi + "('" + p + "', " + NomeCampoCercato + ") > 0";
            for (int i = 1, loopTo1 = SearchRequestWithSearchWords.ParoleCercate.Count - 1; i <= loopTo1; i++)
                SQL_Ricerca_WithAND += OperatoreLogico + FunzioneSottostringaDi + "('" + p + "', " + NomeCampoCercato + ") > 0";
        }
        SQL_Ricerca_WithAND += " AND";
    
        return SQL_Ricerca_WithAND;
    
    }
    

    Well, we have a starting foundation... and not very far from our final goal ... :) I hope we can go live perhaps around September (if am still on the planet) ... In the meantime, I will need to do a lot of code refactoring to make it more abstract and maintainable...
     
    Last edited: Jul 9, 2023
    #29     Jul 9, 2023
  10. I am working on the error and messaging system. Later I will explain what I have done with some boring technical detail.

    It's the system that displays any kind of "messages" and "errors" to the users, either coming from the client or from the server.

    upload_2023-7-11_16-2-26.png


    In the meantime, I am thinking that since we have a site for Rome classified ads (around 3MM dwellers), I might as well make another relatively little effort and include another key city with another "local" site.

    I was actually thinking about New York (probably around 3 times the population) or Houston.

    Is there anyone from that side of the planet able to comment or provide suggestions ? :)


    [​IMG]


    Trading is also doing fine thanks to the scrupulous surveillance of my cat :):

    upload_2023-7-11_16-16-52.png


    Later I will update my journal thread here on ET. I am using currently only 34% of the funds (649K profit in 526 days, 2MM init cap) and will have to add some new layers.

    Stealing $$$ from the mkt with the options long/short approach is actually something that even my cat can do without much effort :)

    Not sure why so many people insist on predicting the market (which is obviously impossible to do for a consistent profit) or making bets when there is absolutely no need to. I guess probably some masochistic or ego thing at work.

    Mysteries of the human mind ... :)
     
    Last edited: Jul 11, 2023
    #30     Jul 11, 2023
    vanzandt likes this.