UNPKG

love-on-the-route

Version:
10 lines (9 loc) 21.9 kB
(function(u,p){typeof exports=="object"&&typeof module<"u"?p(exports):typeof define=="function"&&define.amd?define(["exports"],p):(u=typeof globalThis<"u"?globalThis:u||self,p(u.LoveOnTheRoute={}))})(this,function(u){"use strict";var V=Object.defineProperty;var z=(u,p,C)=>p in u?V(u,p,{enumerable:!0,configurable:!0,writable:!0,value:C}):u[p]=C;var L=(u,p,C)=>z(u,typeof p!="symbol"?p+"":p,C);class p{constructor(e){L(this,"routes",[]);L(this,"contentElement");L(this,"defaultLanguage");L(this,"supportedLanguages");this.rootElement=e,this.contentElement=document.createElement("main"),this.contentElement.id="content",this.rootElement.appendChild(this.contentElement),window.addEventListener("popstate",this.render.bind(this)),this.setupNavigationListener()}addRoute(e,t,n,r){if(!e||!t||!n){console.error("Router: Invalid route parameters",{path:e,handler:!!t,title:n});return}const a={path:e,handler:t,title:n};if(r){const l=this.findRoute(r);l&&(l.children||(l.children=[]),l.children.push(a))}else this.routes.push(a)}autoGenerateRoutes(e){Object.entries(e).forEach(([t,n])=>{const r=`/${t.toLowerCase()}`,a=t.replace(/([A-Z])/g," $1").trim();this.addRoute(r,n.default,a)})}findRoute(e){return this.routes.find(n=>n.path===e)}navigate(e){if(!e||typeof e!="string"){console.error("Router: Invalid path for navigation",e);return}window.history.pushState({},"",e),this.render()}render(){const e=window.location.pathname;if(!this.contentElement){console.error("Router: Content element not found");return}if(this.handleMultilingualRedirect(e))return;const t=this.findRoute(e);this.contentElement.innerHTML="",t?t.handler():this.contentElement.innerHTML="<h1>404 - Page Not Found</h1>",window.dispatchEvent(new CustomEvent("routeChanged",{detail:{path:e}}))}setupNavigationListener(){document.body.addEventListener("click",e=>{if(e.target instanceof HTMLAnchorElement){const t=e.target.getAttribute("href");if(t&&(["http://","https://","mailto:","tel:","javascript:","ftp:","file:"].some(r=>t.startsWith(r))||t.startsWith("#")))return;e.preventDefault(),this.navigate(e.target.pathname)}})}setMultilingualConfig(e,t){if(!e||!Array.isArray(e)||e.length===0){console.error("[Love On The Route] Router: Valid supported languages array is required");return}this.supportedLanguages=e,this.defaultLanguage=t||e[0]}handleMultilingualRedirect(e){return!this.supportedLanguages||!this.defaultLanguage?!1:e==="/"&&this.routes.some(n=>this.supportedLanguages.some(r=>n.path.startsWith(`/${r}`)))?(this.navigate(`/${this.defaultLanguage}`),!0):!1}getRoutes(){return this.routes}}function C(o){return new p(o)}function E(o,e,t=""){if(!o){console.error("[Love On The Route] generateRoutes: Router instance is required");return}if(!e||!Array.isArray(e)){console.error("[Love On The Route] generateRoutes: Valid route configs array is required");return}if(e.length===0){console.warn("[Love On The Route] generateRoutes: No routes provided");return}e.forEach(n=>{if(!n||typeof n!="object"){console.error("[Love On The Route] generateRoutes: Invalid route config",n);return}if(!n.path||typeof n.path!="string"){console.error("[Love On The Route] generateRoutes: Route config must have a valid path",n);return}if(!n.component||typeof n.component!="function"){console.error("[Love On The Route] generateRoutes: Route config must have a valid component function",n);return}const r=t+n.path;o.addRoute(r,()=>{try{const a=n.component();if(!a||!(a instanceof HTMLElement)){console.error("[Love On The Route] Component must return a valid HTMLElement",n.path);return}const l=document.getElementById("content");l?l.appendChild(a):console.error("[Love On The Route] Content element '#content' not found in DOM")}catch(a){console.error("[Love On The Route] Error executing component for route",n.path,a)}},n.title,t||void 0),n.children&&E(o,n.children,r)}),j(o,e)}function j(o,e){try{const t=new Set;if(e.forEach(n=>{n.language&&t.add(n.language);const r=n.path.match(/^\/([a-z]{2})(?:\/|$)/);r&&t.add(r[1])}),t.size>0){const n=Array.from(t).sort(),r=n[0];typeof o.setMultilingualConfig=="function"&&o.setMultilingualConfig(n,r)}}catch(t){console.error("[Love On The Route] Error auto-configuring multilingual mode",t)}}function w(o){if(!o||typeof o!="object")return console.error("[Love On The Route] autoDiscoverPages: Components object is required"),[];if(Object.keys(o).length===0)return console.warn("[Love On The Route] autoDiscoverPages: No components found"),[];const e=["index.ts","PanicHeader.ts"];return Object.entries(o).filter(([n])=>{const r=n.split("/").pop()||"";return!e.includes(r)}).map(([n,r])=>{var c;const a=n.split("/"),l=((c=a.pop())==null?void 0:c.replace(".ts",""))||"",s=a[a.length-1];let i;return l.toLowerCase()==="home"?i="/":i=s&&s!=="pages"?`/${s.toLowerCase()}`:`/${l.toLowerCase()}`,{path:i,component:r.default,title:l.replace(/([A-Z])/g," $1").trim()}}).sort((n,r)=>n.path==="/"?-1:r.path==="/"?1:n.title.localeCompare(r.title))}function N(o,e=["en","fr"]){const t=["index.ts","PanicHeader.ts"],n=new Set;return{routes:Object.entries(o).filter(([l])=>{const s=l.split("/").pop()||"";return!t.includes(s)}).map(([l,s])=>{var v;const i=l.split("/"),c=((v=i.pop())==null?void 0:v.replace(".ts",""))||"";let h=null,d=[];for(let f=i.length-1;f>=0;f--){const O=i[f];if(e.includes(O)){h=O,d=i.slice(f+1);break}}h||(h=e[0]),n.add(h);let g;if(c.toLowerCase()==="home")g=`/${h}`;else{const f=d.length>0?d.join("/").toLowerCase():c.toLowerCase();g=`/${h}/${f}`}return{path:g,component:s.default,title:c.replace(/([A-Z])/g," $1").trim(),language:h}}).sort((l,s)=>{if(l.language!==s.language)return l.language.localeCompare(s.language);const i=l.path===`/${l.language}`,c=s.path===`/${s.language}`;return i&&!c?-1:!i&&c?1:l.title.localeCompare(s.title)}),languages:Array.from(n).sort()}}function q(o,e=["en","fr"]){if(Object.keys(o).some(r=>r.split("/").some(l=>e.includes(l)))){const{routes:r,languages:a}=N(o,e);return{routes:r,languages:a,isMultilingual:!0}}else return{routes:w(o),isMultilingual:!1}}function D(o,e={}){const{homeNames:t=["home","index","accueil","main"],supportedLanguages:n=["en","fr"]}=e;if(Object.keys(o).some(l=>l.split("/").some(i=>n.includes(i)))){const l=new Set;return{routes:Object.entries(o).map(([i,c])=>{var O;const h=i.split("/"),d=((O=h.pop())==null?void 0:O.replace(".ts",""))||"";let g=null,v=[];for(let R=h.length-1;R>=0;R--){const M=h[R];if(n.includes(M)){g=M,v=h.slice(R+1);break}}g||(g=n[0]),l.add(g);let f;if(t.some(R=>d.toLowerCase()===R.toLowerCase()))f=`/${g}`;else{const R=v.length>0?v.join("/").toLowerCase():d.toLowerCase();f=`/${g}/${R}`}return{path:f,component:c.default,title:d.replace(/([A-Z])/g," $1").trim(),language:g}}).sort((i,c)=>{if(i.language!==c.language)return i.language.localeCompare(c.language);const h=i.path===`/${i.language}`,d=c.path===`/${c.language}`;return h&&!d?-1:!h&&d?1:i.title.localeCompare(c.title)}),languages:Array.from(l).sort(),isMultilingual:!0}}else return{routes:Object.entries(o).map(([s,i])=>{var v;const c=s.split("/"),h=((v=c.pop())==null?void 0:v.replace(".ts",""))||"",d=c[c.length-1];let g;return t.some(f=>h.toLowerCase()===f.toLowerCase())?g="/":g=d&&d!=="pages"?`/${d.toLowerCase()}`:`/${h.toLowerCase()}`,{path:g,component:i.default,title:h.replace(/([A-Z])/g," $1").trim()}}).sort((s,i)=>s.path==="/"?-1:i.path==="/"?1:s.title.localeCompare(i.title)),isMultilingual:!1}}class A{constructor(e,t={}){L(this,"element");L(this,"logoConfig");this.routes=e,this.options=t,(!e||!Array.isArray(e))&&(console.error("[Love On The Route] LoveNav: Routes array is required"),this.routes=[]),(!t||typeof t!="object")&&(console.error("[Love On The Route] LoveNav: Options should be an object"),this.options={}),this.logoConfig=this.options.logo,this.logoConfig&&console.log("[Love On The Route] DEBUG: LoveNav initialisé avec logo:",this.logoConfig);try{this.element=document.createElement(this.options.tagName||"nav"),this.options.containerClass&&(this.element.className=this.options.containerClass),this.updateContent(),this.setupActiveStateListener()}catch(n){console.error("[Love On The Route] LoveNav: Error during initialization",n),this.element=document.createElement("nav")}}updateContent(){var e;try{if(!this.routes||this.routes.length===0){console.warn("[Love On The Route] LoveNav: No routes provided for navigation"),this.element.innerHTML="";return}let t="";if(this.logoConfig&&!this.options.separateLogoFromNav){const a=this.logoConfig.href||"/",l=this.logoConfig.linkClass||"logo-link",s=this.logoConfig.containerClass||"";console.log("[Love On The Route] DEBUG: Ajout du logo intégré:",{logoHref:a,logoClass:l,logoContainerClass:s,replacesHome:this.logoConfig.replacesHome}),t+=`<div class="${s}"> <a href="${a}" class="${l}" data-route="${a}" data-logo="true"> ${this.logoConfig.html} </a> </div>`}let n=this.routes;(e=this.logoConfig)!=null&&e.replacesHome&&(console.log("[Love On The Route] DEBUG: Logo replacesHome activé, routes avant filtrage:",this.routes),n=this.routes.filter(a=>{const l=a.path.toLowerCase(),s=a.title.toLowerCase(),i=l==="/"||l.match(/^\/[a-z]{2}$/)||s==="home"||s==="accueil";return i&&console.log("[Love On The Route] DEBUG: Route home exclue:",a),!i}),console.log("[Love On The Route] DEBUG: Routes après filtrage:",n));const r=n.filter(a=>a&&a.path&&a.title).map(a=>{const l=this.options.linkClass||"",s=a.path.replace(/"/g,"&quot;"),i=a.title.replace(/</g,"&lt;").replace(/>/g,"&gt;");return`<a href="${s}" class="${l}" data-route="${s}">${i}</a>`}).join("");t+=r,this.element.innerHTML=t,this.setupClickHandlers()}catch(t){console.error("[Love On The Route] LoveNav: Error updating content",t),this.element.innerHTML=""}}setupClickHandlers(){const e=this.element.querySelectorAll("a");console.log("[Love On The Route] DEBUG: Configuration des clics pour",e.length,"liens"),e.forEach((t,n)=>{const r=t.getAttribute("data-route"),a=t.getAttribute("data-logo")==="true";console.log(`[Love On The Route] DEBUG: Lien ${n}:`,{routePath:r,isLogo:a,innerHTML:t.innerHTML.substring(0,50)}),r?t.addEventListener("click",l=>{l.preventDefault(),console.log("[Love On The Route] DEBUG: Clic sur",a?"logo":"lien","vers:",r),window.history.pushState(null,"",r),window.dispatchEvent(new PopStateEvent("popstate")),console.log("[Love On The Route] DEBUG: Navigation déclenchée vers:",r)}):console.error("[Love On The Route] ERROR: Lien sans data-route:",t)})}setupActiveStateListener(){const e=()=>{const t=window.location.pathname;this.element.querySelectorAll("a").forEach(r=>{var s;const a=r.getAttribute("data-route"),l=r.getAttribute("data-logo")==="true";this.options.activeClass&&(l?t==="/"||t===a||((s=this.logoConfig)==null?void 0:s.href)&&t===this.logoConfig.href?r.classList.add(this.options.activeClass):r.classList.remove(this.options.activeClass):a===t?r.classList.add(this.options.activeClass):r.classList.remove(this.options.activeClass))})};window.addEventListener("popstate",e),window.addEventListener("routeChanged",e),e()}render(){return this.element}renderSeparate(){if(!this.logoConfig||!this.options.separateLogoFromNav)return{nav:this.element};const e=document.createElement("div");e.className=this.logoConfig.containerClass||"logo-container";const t=this.logoConfig.href||"/",n=this.logoConfig.linkClass||"logo-link";e.innerHTML=` <a href="${t}" class="${n}" data-route="${t}" data-logo="true"> ${this.logoConfig.html} </a> `;const r=e.querySelector("a");return r?(console.log("[Love On The Route] DEBUG: Configuration du clic logo séparé vers:",t),r.addEventListener("click",a=>{a.preventDefault(),console.log("[Love On The Route] DEBUG: Clic sur logo séparé vers:",t),window.history.pushState(null,"",t),window.dispatchEvent(new PopStateEvent("popstate")),console.log("[Love On The Route] DEBUG: Navigation logo séparé déclenchée vers:",t)})):console.error("[Love On The Route] ERROR: Logo séparé créé mais lien non trouvé"),{logo:e,nav:this.element}}updateRoutes(e){if(!e||!Array.isArray(e)){console.error("[Love On The Route] LoveNav: updateRoutes requires a valid routes array");return}try{this.routes=e,this.updateContent()}catch(t){console.error("[Love On The Route] LoveNav: Error updating routes",t)}}updateLogo(e){this.logoConfig=e,this.updateContent()}}function S(o){if(!o||typeof o!="object"){console.error("[Love On The Route] updateSEO: Configuration object is required");return}try{o.title&&(document.title=o.title,m("og:title",o.title),m("twitter:title",o.title)),o.description&&(m("description",o.description),m("og:description",o.description),m("twitter:description",o.description)),o.url&&(m("og:url",o.url),H(o.url)),o.image&&(m("og:image",o.image),m("twitter:image",o.image)),o.siteName&&m("og:site_name",o.siteName),o.type&&m("og:type",o.type),o.keywords&&o.keywords.length>0&&m("keywords",o.keywords.join(", "))}catch(e){console.error("[Love On The Route] updateSEO: Error updating SEO tags",e)}}function m(o,e){if(!o||typeof o!="string"){console.error("[Love On The Route] updateMetaTag: Valid name is required");return}if(e==null){console.error("[Love On The Route] updateMetaTag: Content is required");return}try{let t=document.querySelector(`meta[name="${o}"]`);t||(t=document.querySelector(`meta[property="${o}"]`)),t||(t=document.createElement("meta"),o.startsWith("og:")||o.startsWith("twitter:")?t.setAttribute("property",o):t.setAttribute("name",o),document.head.appendChild(t)),t.setAttribute("content",e)}catch(t){console.error("[Love On The Route] updateMetaTag: Error updating meta tag",o,t)}}function H(o){if(!o||typeof o!="string"){console.error("[Love On The Route] updateCanonicalLink: Valid URL is required");return}try{let e=document.querySelector('link[rel="canonical"]');e||(e=document.createElement("link"),e.rel="canonical",document.head.appendChild(e)),e.href=o}catch(e){console.error("[Love On The Route] updateCanonicalLink: Error updating canonical link",e)}}function k(){try{const o=document.querySelectorAll("meta[name], meta[property]"),e=document.querySelector('link[rel="canonical"]');o.forEach(t=>{const n=t.getAttribute("name")||t.getAttribute("property");n&&(n.startsWith("og:")||n.startsWith("twitter:")||["description","keywords"].includes(n))&&t.remove()}),e&&e.remove()}catch(o){console.error("[Love On The Route] resetSEO: Error resetting SEO tags",o)}}function P(o=["en","fr"]){if(!o||!Array.isArray(o)||o.length===0)return console.error("[Love On The Route] detectCurrentLanguage: Valid supported languages array is required"),"en";try{const t=window.location.pathname.split("/").filter(Boolean);if(t.length>0){const n=t[0];if(o.includes(n))return n}return o[0]||"en"}catch(e){return console.error("[Love On The Route] detectCurrentLanguage: Error detecting language",e),"en"}}function $(){try{const e=window.location.pathname.split("/").filter(Boolean);if(e.length>0){const t=e[0];if(t.length===2||t.length===3)return t}return"en"}catch(o){return console.error("[Love On The Route] getCurrentLanguage: Error detecting language",o),"en"}}let y=[],b="";function B(o){return!o||typeof o!="function"?(console.error("[Love On The Route] watchLanguageChanges: Valid callback function is required"),()=>{}):(y.push(o),y.length===1&&F(),()=>{const e=y.indexOf(o);e>-1&&y.splice(e,1),y.length===0&&U()})}function F(){b=$(),window.addEventListener("popstate",T);const o=history.pushState,e=history.replaceState;history.pushState=function(...t){o.apply(history,t),setTimeout(T,0)},history.replaceState=function(...t){e.apply(history,t),setTimeout(T,0)}}function U(){window.removeEventListener("popstate",T)}function T(){const o=$();o!==b&&(b=o,y.forEach(e=>{try{e(o)}catch(t){console.error("[Love On The Route] Error in language change callback:",t)}}))}function G(o,e){if(!o||!Array.isArray(o))return console.error("[Love On The Route] filterRoutesByCurrentLanguage: Valid routes array is required"),[];if(!e||e.length===0)return o;try{const t=P(e);return o.filter(n=>n.language?n.language===t:!0)}catch(t){return console.error("[Love On The Route] filterRoutesByCurrentLanguage: Error filtering routes",t),o}}function I(o){var l;if(!o||typeof o!="object")throw console.error("[Love On The Route] loveOnTheRoute: Configuration object is required"),new Error("Configuration object is required");if(!o.container)throw console.error("[Love On The Route] loveOnTheRoute: Container is required"),new Error("Container is required");const e=typeof o.container=="string"?document.querySelector(o.container):o.container;if(!e)throw console.error("[Love On The Route] loveOnTheRoute: Container not found",o.container),new Error(`Container not found: ${o.container}`);const t=C(e);if(!o.pages)throw console.error("[Love On The Route] loveOnTheRoute: Pages are required"),new Error("Pages are required. Use import.meta.glob('./pages/**/*.ts', { eager: true }) and pass the result to loveOnTheRoute({ pages: ... })");if(typeof o.pages!="object"||Object.keys(o.pages).length===0)throw console.error("[Love On The Route] loveOnTheRoute: Pages object is empty or invalid",o.pages),new Error("Pages object must contain at least one page component");const n=w(o.pages),r=n.map(s=>({...s,component:()=>{try{o.seoDefaults&&S({title:`${o.seoDefaults.siteName||""} | ${s.title}`.trim(),url:window.location.href,type:o.seoDefaults.type||"website",image:o.seoDefaults.image,siteName:o.seoDefaults.siteName});const i=s.component();return!i||!(i instanceof HTMLElement)?(console.error("[Love On The Route] Component must return a valid HTMLElement",s.path),document.createElement("div")):i}catch(i){console.error("[Love On The Route] Error in route component",s.path,i);const c=document.createElement("div");return c.innerHTML=`<h1>Error loading page</h1><p>Route: ${s.path}</p>`,c}}}));E(t,r);let a;if(o.generateNav)try{const s=n.map(i=>({path:i.path,title:i.title}));if(a=new A(s,o.navOptions||{}),(l=o.navOptions)!=null&&l.insertBefore){const i=typeof o.navOptions.insertBefore=="string"?document.querySelector(o.navOptions.insertBefore):o.navOptions.insertBefore;i&&i.parentNode?i.parentNode.insertBefore(a.render(),i):(console.warn("[Love On The Route] insertBefore target not found, inserting before container instead"),e.parentNode&&e.parentNode.insertBefore(a.render(),e))}else e.parentNode?e.parentNode.insertBefore(a.render(),e):console.warn("[Love On The Route] Container has no parent, navigation cannot be inserted")}catch(s){console.error("[Love On The Route] Error creating navigation",s),a=void 0}try{t.render(),window.location.pathname==="/"&&t.navigate("/")}catch(s){throw console.error("[Love On The Route] Error during router initialization",s),s}return{router:t,nav:a,updateSEO:S}}class W{constructor(e,t={}){L(this,"element");L(this,"currentLang","en");this.languages=e,this.options=t,(!e||!Array.isArray(e)||e.length===0)&&(console.error("[Love On The Route] LangSelector: Languages array is required and cannot be empty"),this.languages=[{code:"en",label:"English"}]),(!t||typeof t!="object")&&(console.error("[Love On The Route] LangSelector: Options should be an object"),this.options={});try{this.currentLang=this.detectCurrentLanguage(),this.element=document.createElement(this.options.tagName||"div"),this.options.containerClass&&(this.element.className=this.options.containerClass),this.updateContent(),this.setupLanguageListener()}catch(n){console.error("[Love On The Route] LangSelector: Error during initialization",n),this.element=document.createElement("div")}}detectCurrentLanguage(){var e;try{const n=window.location.pathname.split("/").filter(Boolean);if(n.length>0){const r=n[0];if(this.languages.some(a=>a.code===r))return r}return((e=this.languages[0])==null?void 0:e.code)||"en"}catch(t){return console.error("[Love On The Route] LangSelector: Error detecting current language",t),"en"}}updateContent(){const e=this.languages.map(t=>{const n=this.options.linkClass||"",r=t.code===this.currentLang&&this.options.activeClass||"",a=`${n} ${r}`.trim(),s=`${this.options.showFlags&&t.flag?t.flag+" ":""}${t.label}`;return`<a href="/${t.code}" class="${a}" data-lang="${t.code}">${s}</a>`}).join("");this.element.innerHTML=e}setupLanguageListener(){const e=()=>{this.currentLang=this.detectCurrentLanguage(),this.updateContent()};window.addEventListener("popstate",e),window.addEventListener("routeChanged",e),this.element.addEventListener("click",t=>{if(t.target instanceof HTMLAnchorElement){const n=t.target.getAttribute("data-lang");n&&(t.preventDefault(),this.switchLanguage(n))}})}switchLanguage(e){if(!e||typeof e!="string"){console.error("[Love On The Route] LangSelector: Valid language code is required");return}if(!this.languages.some(t=>t.code===e)){console.error("[Love On The Route] LangSelector: Language code not found in supported languages",e);return}try{const n=window.location.pathname.split("/").filter(Boolean);n.length>0&&this.languages.some(a=>a.code===n[0])&&n.shift();const r=`/${e}${n.length>0?"/"+n.join("/"):""}`;window.history.pushState({},"",r),window.dispatchEvent(new CustomEvent("routeChanged",{detail:{path:r,lang:e}}))}catch(t){console.error("[Love On The Route] LangSelector: Error switching language",t)}}render(){return this.element}getCurrentLanguage(){return this.currentLang}updateLanguages(e){if(!e||!Array.isArray(e)||e.length===0){console.error("[Love On The Route] LangSelector: updateLanguages requires a valid, non-empty languages array");return}try{this.languages=e,this.updateContent()}catch(t){console.error("[Love On The Route] LangSelector: Error updating languages",t)}}}u.LangSelector=W,u.LoveNav=A,u.autoDiscoverMultilingualPages=N,u.autoDiscoverPages=w,u.autoDiscoverPagesFlexible=D,u.autoDiscoverPagesIntelligent=q,u.createRouter=C,u.detectCurrentLanguage=P,u.filterRoutesByCurrentLanguage=G,u.generateRoutes=E,u.getCurrentLanguage=$,u.loveOnTheRoute=I,u.resetSEO=k,u.updateSEO=S,u.watchLanguageChanges=B,Object.defineProperty(u,Symbol.toStringTag,{value:"Module"})});