diff --git a/.eslintrc.js b/.eslintrc.js index 31af1d8..f113339 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -10,7 +10,10 @@ module.exports = { sourceType: 'module', project: 'tsconfig.json' }, - plugins: [ ], + parser: '@typescript-eslint/parser', + plugins: ['@typescript-eslint'], + extends: ['eslint:recommended', 'plugin:@typescript-eslint/recommended', 'prettier'], + root: true, rules: { indent: ['error', 4, { SwitchCase: 1 }], }, diff --git a/js-src/carousel-ad.ts b/js-src/carousel-ad.ts new file mode 100644 index 0000000..c0d6bdc --- /dev/null +++ b/js-src/carousel-ad.ts @@ -0,0 +1,142 @@ +export interface Ad { + img: string, + text: string + href: string + seconds: number +} +export default class CarouselAd { + private currentAdNumber: number | null = null + private ad: Ad | null = null + private timeoutNumber: number | null = null + private getCarousel(): HTMLElement { + const carousel = document.querySelector('.carousel'); + if (carousel === null || !(carousel instanceof HTMLElement)) { + this.noMoreAds() + CarouselAd.fail('No carousel.') + } + return carousel + } + static fail(error: string): never { + throw new Error(error) + } + + public async run(): Promise { + this.loadOneAd() + try { + let start = 0 + let end = 0 + this.getCarousel().addEventListener('pointerdown', (event: MouseEvent) => { + start = event.pageX + console.log(start) + }) + this.getCarousel().addEventListener('pointerup', (event: MouseEvent) => { + end = event.pageX + console.log(end) + if (start - end > 100) { + if (this.timeoutNumber !== null) { + window.clearTimeout(this.timeoutNumber) + } + this.loadOneAd() + } else { + const a = this.retrieveLinkCarousel() + window.location.href = a.href + } + }) + + } catch (e) { + console.log(e) + return + } + } + + private noMoreAds() { + const carousel = this.getCarousel() + if (carousel !== null) { + carousel.remove(); + } + this.expandPageContents(); + if (this.timeoutNumber === null) { + return + } + window.clearTimeout(this.timeoutNumber) + } + + private expandPageContents() { + const pageContents = document.querySelector('div.page-contents'); + if (pageContents === null) { + return; + } + pageContents.classList.add('no-carousel'); + } + + private retrieveLinkCarousel(): HTMLAnchorElement { + const carousel = this.getCarousel() + const maybeA = carousel.querySelector('a') + if (maybeA !== null) { + return maybeA + } + const a = document.createElement('a') + a.addEventListener('click', (event: MouseEvent) => { + event.preventDefault() + }) + a.addEventListener('pointerdown', (event: MouseEvent) => { + event.preventDefault() + }) + a.addEventListener('pointerup', (event: MouseEvent) => { + event.preventDefault() + }) + carousel.innerHTML = "" + carousel.append(a) + return a + } + + private async loadOneAd() { + try { + const params = new URLSearchParams(); + if (this.currentAdNumber !== null) { + params.append('n', ""+this.currentAdNumber); + } + const response = await fetch('/next-ad.json?' + params) + const responseJson = await response.json() + this.currentAdNumber = responseJson.current_ad_number + this.ad = responseJson.ad + if (this.ad === null) { + this.noMoreAds() + return + } + const must_continue = responseJson.continue + const carousel = this.getCarousel() + if (must_continue === 0 + || carousel.offsetWidth === 0) { + this.noMoreAds(); + return; + } + const a = this.retrieveLinkCarousel() + a.innerHTML = "" + const image = document.createElement('img') + const text_container = document.createElement('div') + const text = document.createElement('h4') + const promoted = document.createElement('p') + + promoted.classList.add('promoted-tag') + promoted.innerText = "Promocionado" + image.src = this.ad.img + image.alt = "" + text.innerText = this.ad.text + a.href = this.ad.href + + a.append(image) + text_container.append(promoted) + text_container.append(text) + a.append(text_container) + this.timeoutNumber = window.setTimeout(() => { + this.loadOneAd() + }, this.ad.seconds * 1000) + } catch (e) { + this.timeoutNumber = window.setTimeout(() => { + this.loadOneAd() + }, 1000) + } + + } +} diff --git a/js-src/index.js b/js-src/index.js index 2270279..cc1fb63 100644 --- a/js-src/index.js +++ b/js-src/index.js @@ -1,5 +1,6 @@ "use strict"; import Tablesort from 'tablesort'; +import CarouselAd from '@burguillosinfo/carousel-ad' window.Tablesort = require('tablesort'); require('tablesort/src/sorts/tablesort.number'); @@ -13,7 +14,7 @@ document.addEventListener("DOMContentLoaded", function () { const tables = document.querySelectorAll('table') fillFarmaciaGuardia(); - loadAd() + new CarouselAd().run() addEasterEggAnimation() if (menu_expand !== null && mobile_foldable !== null && transparentFullscreenHide !== null && contentsWithoutMenu !== null) { @@ -362,79 +363,3 @@ function addEasterEggAnimation() { logoContainer.classList.toggle('active') }) } - -let current_ad_number = null - -function expand_page_contents() { - const pageContents = document.querySelector('div.page-contents'); - if (pageContents === null) { - return; - } - pageContents.classList.add('no-carousel'); -} - -function no_more_ads() { - const carousel = document.querySelector('.carousel'); - if (carousel !== null) { - carousel.remove(); - } - expand_page_contents(); -} - -function loadAd() { - const params = new URLSearchParams(); - if (current_ad_number !== null) { - params.append('n', ""+current_ad_number); - } - fetch('/next-ad.json?' + params).then((res) => { - return res.json() - }).then((res) => { - current_ad_number = res.current_ad_number - const ad = res.ad - const must_continue = res.continue - const carousel = document.querySelector('.carousel'); - if (must_continue === 0 - || carousel === null - || carousel.offsetWidth === 0) { - no_more_ads(); - return; - } - const a = _retrieveLinkCarousel(carousel) - a.innerHTML = "" - const image = document.createElement('img') - const text_container = document.createElement('div') - const text = document.createElement('h4') - const promoted = document.createElement('p') - - promoted.classList.add('promoted-tag') - promoted.innerText = "Promocionado" - image.src = ad.img - image.alt = "" - text.innerText = ad.text - a.href = ad.href - - a.append(image) - text_container.append(promoted) - text_container.append(text) - a.append(text_container) - - window.setTimeout(() => { - loadAd() - }, ad.seconds * 1000) - }).catch(() => { - window.setTimeout(() => { - loadAd() - }, 1000) - }); -} - -function _retrieveLinkCarousel(carousel) { - const maybeA = carousel.querySelector('a') - if (maybeA !== null) { - return maybeA - } - const a = document.createElement('a') - carousel.innerHTML = "" - carousel.append(a) - return a -} diff --git a/package.json b/package.json index 9e44e73..6928908 100644 --- a/package.json +++ b/package.json @@ -10,17 +10,22 @@ "author": "", "license": "AGPL-v3", "devDependencies": { - "@typescript-eslint/eslint-plugin": "^5.59.2", - "@typescript-eslint/parser": "^5.59.2", - "eslint": "^8.40.0", + "@typescript-eslint/eslint-plugin": "^5.62.0", + "@typescript-eslint/parser": "^5.62.0", + "eslint": "^8.53.0", + "eslint-config-prettier": "^9.0.0", "eslint-plugin-no-relative-import-paths": "^1.5.2", "husky": "^8.0.3", "lint-staged": "^14.0.1", "prettier": "^3.0.3", - "typescript": "^5.0.4", + "prettier-eslint": "^16.1.2", + "typescript": "^5.2.2", "webpack-cli": "^5.1.4" }, "dependencies": { - "tablesort": "^5.3.0" + "babel-loader": "^9.1.3", + "ol": "^8.1.0", + "tablesort": "^5.3.0", + "ts-loader": "^9.5.0" } } diff --git a/public/css/styles.css b/public/css/styles.css index 0e0e268..8d5ae2c 100644 --- a/public/css/styles.css +++ b/public/css/styles.css @@ -1,5 +1,6 @@ html { - height: 100%; } + height: 100%; + touch-action: none; } body { margin: 0; diff --git a/public/css/styles.scss b/public/css/styles.scss index 3c03542..1556fa8 100644 --- a/public/css/styles.scss +++ b/public/css/styles.scss @@ -13,6 +13,7 @@ $color_sidebar: #dcdcf5; html { height: 100%; + touch-action: none; } body { diff --git a/public/js/bundle.js b/public/js/bundle.js index 92bbe63..60511fc 100644 --- a/public/js/bundle.js +++ b/public/js/bundle.js @@ -16,7 +16,7 @@ /***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => { "use strict"; -eval("__webpack_require__.r(__webpack_exports__);\n/* harmony import */ var tablesort__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! tablesort */ \"./node_modules/tablesort/src/tablesort.js\");\n/* harmony import */ var tablesort__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(tablesort__WEBPACK_IMPORTED_MODULE_0__);\n\n\nwindow.Tablesort = __webpack_require__(/*! tablesort */ \"./node_modules/tablesort/src/tablesort.js\");\n__webpack_require__(/*! tablesort/src/sorts/tablesort.number */ \"./node_modules/tablesort/src/sorts/tablesort.number.js\");\n\nlet fakeSearchInput\nlet searchMobile\ndocument.addEventListener(\"DOMContentLoaded\", function () {\n const menu_expand = document.querySelector('a.menu-expand');\n const mobile_foldable = document.querySelector('nav.mobile-foldable');\n const transparentFullscreenHide = document.querySelector('div.transparent-fullscreen-hide');\n const contentsWithoutMenu = document.querySelector('div.contents-without-menu')\n const tables = document.querySelectorAll('table')\n\n fillFarmaciaGuardia();\n loadAd()\n addEasterEggAnimation()\n\n if (menu_expand !== null && mobile_foldable !== null && transparentFullscreenHide !== null && contentsWithoutMenu !== null) {\n mobile_foldable.toggleAttribute('aria-hidden')\n if (mobile_foldable.getAttribute('aria-hidden') !== null) {\n mobile_foldable.setAttribute('aria-hidden', true);\n }\n transparentFullscreenHide.addEventListener('click', () => {\n mobile_foldable.classList.remove('show');\n transparentFullscreenHide.classList.remove('show');\n menu_expand.classList.remove('active');\n contentsWithoutMenu.removeAttribute('aria-hidden')\n mobile_foldable.setAttribute('aria-hidden', true)\n });\n menu_expand.addEventListener('click', () => {\n menu_expand.classList.toggle('active');\n mobile_foldable.classList.toggle('show');\n transparentFullscreenHide.classList.toggle('show');\n contentsWithoutMenu.toggleAttribute('aria-hidden')\n if (contentsWithoutMenu.getAttribute('aria-hidden') !== null) {\n contentsWithoutMenu.setAttribute('aria-hidden', true);\n }\n mobile_foldable.toggleAttribute('aria-hidden')\n if (mobile_foldable.getAttribute('aria-hidden') !== null) {\n mobile_foldable.setAttribute('aria-hidden', true);\n }\n });\n }\n\n for (const table of tables) {\n const header = table.querySelector('tr');\n if (header !== null) {\n header.setAttribute('data-sort-method', 'none')\n for (const th of header.querySelectorAll('th')) {\n if (th.getAttribute('data-sort-method') == null) {\n th.setAttribute('data-sort-method', 'thead')\n }\n }\n }\n new (tablesort__WEBPACK_IMPORTED_MODULE_0___default())(table)\n }\n if (window !== undefined && window.Android !== undefined) {\n executeAndroidExclusiveCode(Android)\n } \n searchMobile = document.querySelector('nav.mobile-shortcuts div.search')\n if (searchMobile !== null) {\n fakeSearchInput = searchMobile.querySelector('input')\n addListenersSearch()\n }\n}, false);\n\nfunction fillFarmaciaGuardia() {\n const farmaciaName = document.querySelector('#farmacia-name');\n const farmaciaAddress = document.querySelector('#farmacia-address');\n if (farmaciaName !== null || farmaciaAddress !== null) {\n const port = _port()\n const url = new URL(window.location.protocol\n + \"//\"\n + window.location.hostname\n + port\n + '/farmacia-guardia.json');\n fetch(url).then(async (res) => {\n const farmacia = await res.json()\n if (farmaciaName !== null) {\n farmaciaName.innerText = farmacia.name;\n farmaciaAddress.innerText = farmacia.address;\n }\n })\n }\n}\n\nfunction addListenersSearch() {\n const searchInPage = document.querySelector('div.search-in-page')\n if (searchMobile !== null) {\n const searchIcon = searchMobile.querySelector('a.search-icon')\n searchIcon.addEventListener('click', (e) => {\n const searchOverlay = document.querySelector('div.search-overlay');\n const searchInput = searchOverlay.querySelector('div.search input');\n searchInput.value = fakeSearchInput.value;\n onSearchChange(e)\n onFakeSearchClick(e)\n return true;\n\n })\n fakeSearchInput.addEventListener('keyup', (e) => {\n if (searchInPage === null) {\n return;\n }\n if (fakeSearchInput.value === \"\") {\n searchInPage.classList.remove('active') \n } else {\n searchInPage.classList.add('active') \n }\n if (e.keyCode !== 13) {\n return false;\n }\n const searchOverlay = document.querySelector('div.search-overlay');\n const searchInput = searchOverlay.querySelector('div.search input');\n searchInput.value = fakeSearchInput.value;\n onSearchChange(e)\n onFakeSearchClick(e)\n return true;\n });\n }\n const nextResult = searchInPage.querySelector('a.down');\n const prevResult = searchInPage.querySelector('a.up');\n if (nextResult !== null && prevResult !== null) {\n nextResult.addEventListener('click', () => {\n searchInWebsite(fakeSearchInput.value, true);\n });\n prevResult.addEventListener('click', () => {\n searchInWebsite(fakeSearchInput.value, false);\n });\n }\n const exitSearch = document.querySelector('a.exit-search')\n const searchOverlay = document.querySelector('div.search-overlay');\n const searchInput = searchOverlay.querySelector('div.search input');\n fakeSearchInput.value = searchInput.value;\n exitSearch.addEventListener('click', onExitSearch)\n const search = document.querySelector('div.search-overlay div.search input');\n if (search !== null) {\n search.addEventListener('change', onSearchChange);\n }\n const searchIconDesktop = document.querySelector('nav.desktop a.search-icon');\n if (searchIconDesktop !== null) {\n searchIconDesktop.addEventListener('click', (e) => {\n onFakeSearchClick(e)\n })\n }\n}\n\nfunction searchInWebsite(value, isToBottom) {\n window.find(value, false, !isToBottom, true)\n const selection = window.getSelection()\n if (selection.anchorNode === null) {\n const pageContents = document.querySelector('div.page-contents'); \n pageContents.focus()\n searchInWebsite(value, isToBottom)\n }\n const anchorNode = selection.anchorNode.parentNode\n if (anchorNode.tagName !== null \n && anchorNode.tagName === \"INPUT\") {\n const pageContents = document.querySelector('div.page-contents'); \n pageContents.focus()\n searchInWebsite(value, isToBottom)\n }\n if (anchorNode !== null) {\n const pageContents = document.querySelector('div.page-contents'); \n const offsetTop = _getOffsetTopWithNParent(anchorNode, pageContents);\n pageContents.scroll(0, offsetTop - 150)\n }\n}\n\nfunction _getOffsetTopWithNParent(element, nParent, _carry = 0) {\n if (element === null) {\n return null;\n }\n if (element === nParent) {\n return _carry;\n }\n _carry += element.offsetTop\n return _getOffsetTopWithNParent(element.offsetParent, nParent, _carry)\n}\n\nfunction _port() {\n let port = window.location.port;\n if (port !== '') {\n port = ':' + port\n }\n return port;\n}\n\nfunction onSearchChange() {\n const search = document.querySelector('div.search-overlay div.search input');\n const searchResults = document.querySelector('div.search-overlay div.search-results');\n if (search === null || searchResults === null) {\n return;\n }\n const query = search.value;\n fakeSearchInput.value = search.value\n const port = _port()\n const url = new URL(window.location.protocol\n + \"//\"\n + window.location.hostname\n + port\n + '/search.json');\n url.searchParams.set('q', query);\n fetch(url).then(async (res) => {\n const json = await res.json()\n if (!json.ok) {\n noResults(searchResults);\n return\n }\n console.log(json.searchObjects.length)\n if (json.searchObjects.length < 1) {\n noResults(searchResults);\n return;\n }\n showResults(searchResults, json.searchObjects);\n })\n search.focus()\n}\n\nfunction showResults(searchResults, searchObjects) {\n searchResults.innerHTML = \"\";\n for (let searchObject of searchObjects) {\n const searchResultContainer = document.createElement('div')\n searchResultContainer.classList.add('search-result')\n const rowTitleUrlImageDiv = document.createElement('div');\n rowTitleUrlImageDiv.classList.add('row-title-url-image');\n const columnTitleUrl = document.createElement('div');\n columnTitleUrl.classList.add('column-title-url');\n const img = document.createElement('img')\n const title = document.createElement('b')\n const url = document.createElement('a')\n const content = document.createElement('p')\n\n title.innerText = searchObject.title\n let port = window.location.port;\n if (port !== '') {\n port = ':' + port\n }\n if (searchObject.url.match(/^\\//)) {\n searchObject.url = window.location.protocol \n + \"//\" + window.location.hostname \n + port\n + searchObject.url\n }\n let urlImage = searchObject.urlImage;\n if (urlImage !== null && urlImage.match(/^\\//)) {\n urlImage = window.location.protocol \n + \"//\" + window.location.hostname \n + port\n + urlImage\n }\n if (urlImage !== null) {\n img.alt = \"\"\n img.src = urlImage\n }\n\n url.href = searchObject.url\n url.innerText = searchObject.url\n content.innerText = searchObject.content\n\n if (urlImage !== null) {\n rowTitleUrlImageDiv.appendChild(img)\n }\n\n columnTitleUrl.appendChild(title);\n columnTitleUrl.appendChild(document.createElement('br'))\n columnTitleUrl.appendChild(url)\n\n rowTitleUrlImageDiv.appendChild(columnTitleUrl)\n\n searchResultContainer.appendChild(rowTitleUrlImageDiv)\n searchResultContainer.appendChild(content)\n searchResults.appendChild(searchResultContainer)\n }\n}\n\nfunction noResults(searchResults) {\n searchResults.innerHTML = \"\"\n const p = document.createElement('p')\n p.innerText = 'No se han encontrado resultados.'\n searchResults.appendChild(p)\n}\n\nfunction onExitSearch() {\n const searchOverlay = document.querySelector('div.search-overlay');\n if (searchOverlay !== null) {\n searchOverlay.classList.toggle('active');\n }\n}\n\nfunction onFakeSearchClick(e) {\n e.preventDefault();\n const searchOverlay = document.querySelector('div.search-overlay');\n if (searchOverlay === null) {\n return\n }\n searchOverlay.classList.toggle('active');\n const search = searchOverlay.querySelector('div.search input');\n if (search !== null) {\n search.focus()\n }\n return false;\n}\n\nfunction absoluteToHost(imageUrl) {\n if (imageUrl.match(/^\\//)) {\n imageUrl = window.location.protocol + \"//\" + window.location.host + imageUrl \n }\n return imageUrl.replace(/\\?.*$/, '');\n}\n\nfunction addListenerOpenInBrowserButton(android) {\n const openInBrowserLink = document.querySelector('a.open-in-browser')\n if (openInBrowserLink === null) {\n return\n }\n openInBrowserLink.addEventListener('click', () => {\n android.openInBrowser(window.location.href)\n })\n}\nfunction executeAndroidExclusiveCode(android) {\n document.querySelectorAll('*.android').forEach((element) => {\n element.classList.remove('android')\n })\n document.querySelectorAll('*.no-android-app').forEach((element) => {\n element.style.display = 'none';\n })\n addListenerOpenInBrowserButton(android)\n const pinToHomeUrl = document.querySelector('a.pin-to-home')\n if (pinToHomeUrl === null) {\n return;\n }\n pinToHomeUrl.addEventListener('click', () => {\n const url = new URL(window.location.href)\n const pathandQuery = url.pathname + url.search;\n const label = (url.pathname.replace(/^.*\\//g, '')\n .replace(/(?:^|-)\\w/g, function(character) {\n return character.toUpperCase() \n })\n .replace(/-/g, ' ')) + ' - Burguillos.info';\n const firstImg = document.querySelector('div.description img');\n let iconUrl;\n if (firstImg !== null) {\n if (!firstImg.src.match(/\\.svg(?:\\?|$)/)) {\n iconUrl = absoluteToHost(firstImg.src);\n }\n }\n if (iconUrl === undefined) {\n const imagePreview = document.querySelector('meta[name=\"image\"]');\n iconUrl = absoluteToHost(imagePreview.content);\n }\n android.pinPage(pathandQuery, label, iconUrl)\n })\n}\n\nfunction addEasterEggAnimation() {\n const logoContainer = document.querySelector('div.burguillos-logo-container')\n if (logoContainer === null) {\n return;\n }\n logoContainer.addEventListener('click', () => {\n logoContainer.classList.toggle('active')\n })\n}\n\nlet current_ad_number = null\n\nfunction expand_page_contents() {\n const pageContents = document.querySelector('div.page-contents'); \n if (pageContents === null) {\n return;\n }\n pageContents.classList.add('no-carousel');\n}\n\nfunction no_more_ads() {\n const carousel = document.querySelector('.carousel');\n if (carousel !== null) {\n carousel.remove();\n }\n expand_page_contents();\n}\n\nfunction loadAd() {\n const params = new URLSearchParams();\n if (current_ad_number !== null) {\n params.append('n', \"\"+current_ad_number);\n }\n fetch('/next-ad.json?' + params).then((res) => {\n return res.json()\n }).then((res) => {\n current_ad_number = res.current_ad_number\n const ad = res.ad\n const must_continue = res.continue\n const carousel = document.querySelector('.carousel');\n if (must_continue === 0\n || carousel === null\n || carousel.offsetWidth === 0) {\n no_more_ads();\n return;\n }\n const a = _retrieveLinkCarousel(carousel)\n a.innerHTML = \"\"\n const image = document.createElement('img')\n const text_container = document.createElement('div')\n const text = document.createElement('h4')\n const promoted = document.createElement('p')\n\n promoted.classList.add('promoted-tag')\n promoted.innerText = \"Promocionado\"\n image.src = ad.img\n image.alt = \"\"\n text.innerText = ad.text\n a.href = ad.href\n\n a.append(image)\n text_container.append(promoted)\n text_container.append(text)\n a.append(text_container)\n\n window.setTimeout(() => {\n loadAd()\n }, ad.seconds * 1000)\n }).catch(() => {\n window.setTimeout(() => {\n loadAd()\n }, 1000)\n });\n}\n\nfunction _retrieveLinkCarousel(carousel) {\n const maybeA = carousel.querySelector('a')\n if (maybeA !== null) {\n return maybeA\n }\n const a = document.createElement('a')\n carousel.innerHTML = \"\"\n carousel.append(a)\n return a\n}\n\n\n//# sourceURL=webpack://BurguillosInfo/./js-src/index.js?"); +eval("__webpack_require__.r(__webpack_exports__);\n/* harmony import */ var tablesort__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! tablesort */ \"./node_modules/tablesort/src/tablesort.js\");\n/* harmony import */ var tablesort__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(tablesort__WEBPACK_IMPORTED_MODULE_0__);\n/* harmony import */ var _burguillosinfo_carousel_ad__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! @burguillosinfo/carousel-ad */ \"./js-src/carousel-ad.ts\");\n\n\n\n\nwindow.Tablesort = __webpack_require__(/*! tablesort */ \"./node_modules/tablesort/src/tablesort.js\");\n__webpack_require__(/*! tablesort/src/sorts/tablesort.number */ \"./node_modules/tablesort/src/sorts/tablesort.number.js\");\nlet fakeSearchInput;\nlet searchMobile;\ndocument.addEventListener(\"DOMContentLoaded\", function () {\n const menu_expand = document.querySelector('a.menu-expand');\n const mobile_foldable = document.querySelector('nav.mobile-foldable');\n const transparentFullscreenHide = document.querySelector('div.transparent-fullscreen-hide');\n const contentsWithoutMenu = document.querySelector('div.contents-without-menu');\n const tables = document.querySelectorAll('table');\n fillFarmaciaGuardia();\n new _burguillosinfo_carousel_ad__WEBPACK_IMPORTED_MODULE_1__[\"default\"]().run();\n addEasterEggAnimation();\n if (menu_expand !== null && mobile_foldable !== null && transparentFullscreenHide !== null && contentsWithoutMenu !== null) {\n mobile_foldable.toggleAttribute('aria-hidden');\n if (mobile_foldable.getAttribute('aria-hidden') !== null) {\n mobile_foldable.setAttribute('aria-hidden', true);\n }\n transparentFullscreenHide.addEventListener('click', () => {\n mobile_foldable.classList.remove('show');\n transparentFullscreenHide.classList.remove('show');\n menu_expand.classList.remove('active');\n contentsWithoutMenu.removeAttribute('aria-hidden');\n mobile_foldable.setAttribute('aria-hidden', true);\n });\n menu_expand.addEventListener('click', () => {\n menu_expand.classList.toggle('active');\n mobile_foldable.classList.toggle('show');\n transparentFullscreenHide.classList.toggle('show');\n contentsWithoutMenu.toggleAttribute('aria-hidden');\n if (contentsWithoutMenu.getAttribute('aria-hidden') !== null) {\n contentsWithoutMenu.setAttribute('aria-hidden', true);\n }\n mobile_foldable.toggleAttribute('aria-hidden');\n if (mobile_foldable.getAttribute('aria-hidden') !== null) {\n mobile_foldable.setAttribute('aria-hidden', true);\n }\n });\n }\n for (const table of tables) {\n const header = table.querySelector('tr');\n if (header !== null) {\n header.setAttribute('data-sort-method', 'none');\n for (const th of header.querySelectorAll('th')) {\n if (th.getAttribute('data-sort-method') == null) {\n th.setAttribute('data-sort-method', 'thead');\n }\n }\n }\n new (tablesort__WEBPACK_IMPORTED_MODULE_0___default())(table);\n }\n if (window !== undefined && window.Android !== undefined) {\n executeAndroidExclusiveCode(Android);\n }\n searchMobile = document.querySelector('nav.mobile-shortcuts div.search');\n if (searchMobile !== null) {\n fakeSearchInput = searchMobile.querySelector('input');\n addListenersSearch();\n }\n}, false);\nfunction fillFarmaciaGuardia() {\n const farmaciaName = document.querySelector('#farmacia-name');\n const farmaciaAddress = document.querySelector('#farmacia-address');\n if (farmaciaName !== null || farmaciaAddress !== null) {\n const port = _port();\n const url = new URL(window.location.protocol + \"//\" + window.location.hostname + port + '/farmacia-guardia.json');\n fetch(url).then(async res => {\n const farmacia = await res.json();\n if (farmaciaName !== null) {\n farmaciaName.innerText = farmacia.name;\n farmaciaAddress.innerText = farmacia.address;\n }\n });\n }\n}\nfunction addListenersSearch() {\n const searchInPage = document.querySelector('div.search-in-page');\n if (searchMobile !== null) {\n const searchIcon = searchMobile.querySelector('a.search-icon');\n searchIcon.addEventListener('click', e => {\n const searchOverlay = document.querySelector('div.search-overlay');\n const searchInput = searchOverlay.querySelector('div.search input');\n searchInput.value = fakeSearchInput.value;\n onSearchChange(e);\n onFakeSearchClick(e);\n return true;\n });\n fakeSearchInput.addEventListener('keyup', e => {\n if (searchInPage === null) {\n return;\n }\n if (fakeSearchInput.value === \"\") {\n searchInPage.classList.remove('active');\n } else {\n searchInPage.classList.add('active');\n }\n if (e.keyCode !== 13) {\n return false;\n }\n const searchOverlay = document.querySelector('div.search-overlay');\n const searchInput = searchOverlay.querySelector('div.search input');\n searchInput.value = fakeSearchInput.value;\n onSearchChange(e);\n onFakeSearchClick(e);\n return true;\n });\n }\n const nextResult = searchInPage.querySelector('a.down');\n const prevResult = searchInPage.querySelector('a.up');\n if (nextResult !== null && prevResult !== null) {\n nextResult.addEventListener('click', () => {\n searchInWebsite(fakeSearchInput.value, true);\n });\n prevResult.addEventListener('click', () => {\n searchInWebsite(fakeSearchInput.value, false);\n });\n }\n const exitSearch = document.querySelector('a.exit-search');\n const searchOverlay = document.querySelector('div.search-overlay');\n const searchInput = searchOverlay.querySelector('div.search input');\n fakeSearchInput.value = searchInput.value;\n exitSearch.addEventListener('click', onExitSearch);\n const search = document.querySelector('div.search-overlay div.search input');\n if (search !== null) {\n search.addEventListener('change', onSearchChange);\n }\n const searchIconDesktop = document.querySelector('nav.desktop a.search-icon');\n if (searchIconDesktop !== null) {\n searchIconDesktop.addEventListener('click', e => {\n onFakeSearchClick(e);\n });\n }\n}\nfunction searchInWebsite(value, isToBottom) {\n window.find(value, false, !isToBottom, true);\n const selection = window.getSelection();\n if (selection.anchorNode === null) {\n const pageContents = document.querySelector('div.page-contents');\n pageContents.focus();\n searchInWebsite(value, isToBottom);\n }\n const anchorNode = selection.anchorNode.parentNode;\n if (anchorNode.tagName !== null && anchorNode.tagName === \"INPUT\") {\n const pageContents = document.querySelector('div.page-contents');\n pageContents.focus();\n searchInWebsite(value, isToBottom);\n }\n if (anchorNode !== null) {\n const pageContents = document.querySelector('div.page-contents');\n const offsetTop = _getOffsetTopWithNParent(anchorNode, pageContents);\n pageContents.scroll(0, offsetTop - 150);\n }\n}\nfunction _getOffsetTopWithNParent(element, nParent, _carry = 0) {\n if (element === null) {\n return null;\n }\n if (element === nParent) {\n return _carry;\n }\n _carry += element.offsetTop;\n return _getOffsetTopWithNParent(element.offsetParent, nParent, _carry);\n}\nfunction _port() {\n let port = window.location.port;\n if (port !== '') {\n port = ':' + port;\n }\n return port;\n}\nfunction onSearchChange() {\n const search = document.querySelector('div.search-overlay div.search input');\n const searchResults = document.querySelector('div.search-overlay div.search-results');\n if (search === null || searchResults === null) {\n return;\n }\n const query = search.value;\n fakeSearchInput.value = search.value;\n const port = _port();\n const url = new URL(window.location.protocol + \"//\" + window.location.hostname + port + '/search.json');\n url.searchParams.set('q', query);\n fetch(url).then(async res => {\n const json = await res.json();\n if (!json.ok) {\n noResults(searchResults);\n return;\n }\n console.log(json.searchObjects.length);\n if (json.searchObjects.length < 1) {\n noResults(searchResults);\n return;\n }\n showResults(searchResults, json.searchObjects);\n });\n search.focus();\n}\nfunction showResults(searchResults, searchObjects) {\n searchResults.innerHTML = \"\";\n for (let searchObject of searchObjects) {\n const searchResultContainer = document.createElement('div');\n searchResultContainer.classList.add('search-result');\n const rowTitleUrlImageDiv = document.createElement('div');\n rowTitleUrlImageDiv.classList.add('row-title-url-image');\n const columnTitleUrl = document.createElement('div');\n columnTitleUrl.classList.add('column-title-url');\n const img = document.createElement('img');\n const title = document.createElement('b');\n const url = document.createElement('a');\n const content = document.createElement('p');\n title.innerText = searchObject.title;\n let port = window.location.port;\n if (port !== '') {\n port = ':' + port;\n }\n if (searchObject.url.match(/^\\//)) {\n searchObject.url = window.location.protocol + \"//\" + window.location.hostname + port + searchObject.url;\n }\n let urlImage = searchObject.urlImage;\n if (urlImage !== null && urlImage.match(/^\\//)) {\n urlImage = window.location.protocol + \"//\" + window.location.hostname + port + urlImage;\n }\n if (urlImage !== null) {\n img.alt = \"\";\n img.src = urlImage;\n }\n url.href = searchObject.url;\n url.innerText = searchObject.url;\n content.innerText = searchObject.content;\n if (urlImage !== null) {\n rowTitleUrlImageDiv.appendChild(img);\n }\n columnTitleUrl.appendChild(title);\n columnTitleUrl.appendChild(document.createElement('br'));\n columnTitleUrl.appendChild(url);\n rowTitleUrlImageDiv.appendChild(columnTitleUrl);\n searchResultContainer.appendChild(rowTitleUrlImageDiv);\n searchResultContainer.appendChild(content);\n searchResults.appendChild(searchResultContainer);\n }\n}\nfunction noResults(searchResults) {\n searchResults.innerHTML = \"\";\n const p = document.createElement('p');\n p.innerText = 'No se han encontrado resultados.';\n searchResults.appendChild(p);\n}\nfunction onExitSearch() {\n const searchOverlay = document.querySelector('div.search-overlay');\n if (searchOverlay !== null) {\n searchOverlay.classList.toggle('active');\n }\n}\nfunction onFakeSearchClick(e) {\n e.preventDefault();\n const searchOverlay = document.querySelector('div.search-overlay');\n if (searchOverlay === null) {\n return;\n }\n searchOverlay.classList.toggle('active');\n const search = searchOverlay.querySelector('div.search input');\n if (search !== null) {\n search.focus();\n }\n return false;\n}\nfunction absoluteToHost(imageUrl) {\n if (imageUrl.match(/^\\//)) {\n imageUrl = window.location.protocol + \"//\" + window.location.host + imageUrl;\n }\n return imageUrl.replace(/\\?.*$/, '');\n}\nfunction addListenerOpenInBrowserButton(android) {\n const openInBrowserLink = document.querySelector('a.open-in-browser');\n if (openInBrowserLink === null) {\n return;\n }\n openInBrowserLink.addEventListener('click', () => {\n android.openInBrowser(window.location.href);\n });\n}\nfunction executeAndroidExclusiveCode(android) {\n document.querySelectorAll('*.android').forEach(element => {\n element.classList.remove('android');\n });\n document.querySelectorAll('*.no-android-app').forEach(element => {\n element.style.display = 'none';\n });\n addListenerOpenInBrowserButton(android);\n const pinToHomeUrl = document.querySelector('a.pin-to-home');\n if (pinToHomeUrl === null) {\n return;\n }\n pinToHomeUrl.addEventListener('click', () => {\n const url = new URL(window.location.href);\n const pathandQuery = url.pathname + url.search;\n const label = url.pathname.replace(/^.*\\//g, '').replace(/(?:^|-)\\w/g, function (character) {\n return character.toUpperCase();\n }).replace(/-/g, ' ') + ' - Burguillos.info';\n const firstImg = document.querySelector('div.description img');\n let iconUrl;\n if (firstImg !== null) {\n if (!firstImg.src.match(/\\.svg(?:\\?|$)/)) {\n iconUrl = absoluteToHost(firstImg.src);\n }\n }\n if (iconUrl === undefined) {\n const imagePreview = document.querySelector('meta[name=\"image\"]');\n iconUrl = absoluteToHost(imagePreview.content);\n }\n android.pinPage(pathandQuery, label, iconUrl);\n });\n}\nfunction addEasterEggAnimation() {\n const logoContainer = document.querySelector('div.burguillos-logo-container');\n if (logoContainer === null) {\n return;\n }\n logoContainer.addEventListener('click', () => {\n logoContainer.classList.toggle('active');\n });\n}\n\n//# sourceURL=webpack://BurguillosInfo/./js-src/index.js?"); /***/ }), @@ -38,6 +38,17 @@ eval("(function(){\n var cleanNumber = function(i) {\n return i.replace(/[^\ eval(";(function() {\n function Tablesort(el, options) {\n if (!(this instanceof Tablesort)) return new Tablesort(el, options);\n\n if (!el || el.tagName !== 'TABLE') {\n throw new Error('Element must be a table');\n }\n this.init(el, options || {});\n }\n\n var sortOptions = [];\n\n var createEvent = function(name) {\n var evt;\n\n if (!window.CustomEvent || typeof window.CustomEvent !== 'function') {\n evt = document.createEvent('CustomEvent');\n evt.initCustomEvent(name, false, false, undefined);\n } else {\n evt = new CustomEvent(name);\n }\n\n return evt;\n };\n\n var getInnerText = function(el,options) {\n return el.getAttribute(options.sortAttribute || 'data-sort') || el.textContent || el.innerText || '';\n };\n\n // Default sort method if no better sort method is found\n var caseInsensitiveSort = function(a, b) {\n a = a.trim().toLowerCase();\n b = b.trim().toLowerCase();\n\n if (a === b) return 0;\n if (a < b) return 1;\n\n return -1;\n };\n\n var getCellByKey = function(cells, key) {\n return [].slice.call(cells).find(function(cell) {\n return cell.getAttribute('data-sort-column-key') === key;\n });\n };\n\n // Stable sort function\n // If two elements are equal under the original sort function,\n // then there relative order is reversed\n var stabilize = function(sort, antiStabilize) {\n return function(a, b) {\n var unstableResult = sort(a.td, b.td);\n\n if (unstableResult === 0) {\n if (antiStabilize) return b.index - a.index;\n return a.index - b.index;\n }\n\n return unstableResult;\n };\n };\n\n Tablesort.extend = function(name, pattern, sort) {\n if (typeof pattern !== 'function' || typeof sort !== 'function') {\n throw new Error('Pattern and sort must be a function');\n }\n\n sortOptions.push({\n name: name,\n pattern: pattern,\n sort: sort\n });\n };\n\n Tablesort.prototype = {\n\n init: function(el, options) {\n var that = this,\n firstRow,\n defaultSort,\n i,\n cell;\n\n that.table = el;\n that.thead = false;\n that.options = options;\n\n if (el.rows && el.rows.length > 0) {\n if (el.tHead && el.tHead.rows.length > 0) {\n for (i = 0; i < el.tHead.rows.length; i++) {\n if (el.tHead.rows[i].getAttribute('data-sort-method') === 'thead') {\n firstRow = el.tHead.rows[i];\n break;\n }\n }\n if (!firstRow) {\n firstRow = el.tHead.rows[el.tHead.rows.length - 1];\n }\n that.thead = true;\n } else {\n firstRow = el.rows[0];\n }\n }\n\n if (!firstRow) return;\n\n var onClick = function() {\n if (that.current && that.current !== this) {\n that.current.removeAttribute('aria-sort');\n }\n\n that.current = this;\n that.sortTable(this);\n };\n\n // Assume first row is the header and attach a click handler to each.\n for (i = 0; i < firstRow.cells.length; i++) {\n cell = firstRow.cells[i];\n cell.setAttribute('role','columnheader');\n if (cell.getAttribute('data-sort-method') !== 'none') {\n cell.tabindex = 0;\n cell.addEventListener('click', onClick, false);\n\n if (cell.getAttribute('data-sort-default') !== null) {\n defaultSort = cell;\n }\n }\n }\n\n if (defaultSort) {\n that.current = defaultSort;\n that.sortTable(defaultSort);\n }\n },\n\n sortTable: function(header, update) {\n var that = this,\n columnKey = header.getAttribute('data-sort-column-key'),\n column = header.cellIndex,\n sortFunction = caseInsensitiveSort,\n item = '',\n items = [],\n i = that.thead ? 0 : 1,\n sortMethod = header.getAttribute('data-sort-method'),\n sortOrder = header.getAttribute('aria-sort');\n\n that.table.dispatchEvent(createEvent('beforeSort'));\n\n // If updating an existing sort, direction should remain unchanged.\n if (!update) {\n if (sortOrder === 'ascending') {\n sortOrder = 'descending';\n } else if (sortOrder === 'descending') {\n sortOrder = 'ascending';\n } else {\n sortOrder = that.options.descending ? 'descending' : 'ascending';\n }\n\n header.setAttribute('aria-sort', sortOrder);\n }\n\n if (that.table.rows.length < 2) return;\n\n // If we force a sort method, it is not necessary to check rows\n if (!sortMethod) {\n var cell;\n while (items.length < 3 && i < that.table.tBodies[0].rows.length) {\n if(columnKey) {\n cell = getCellByKey(that.table.tBodies[0].rows[i].cells, columnKey);\n } else {\n cell = that.table.tBodies[0].rows[i].cells[column];\n }\n\n // Treat missing cells as empty cells\n item = cell ? getInnerText(cell,that.options) : \"\";\n\n item = item.trim();\n\n if (item.length > 0) {\n items.push(item);\n }\n\n i++;\n }\n\n if (!items) return;\n }\n\n for (i = 0; i < sortOptions.length; i++) {\n item = sortOptions[i];\n\n if (sortMethod) {\n if (item.name === sortMethod) {\n sortFunction = item.sort;\n break;\n }\n } else if (items.every(item.pattern)) {\n sortFunction = item.sort;\n break;\n }\n }\n\n that.col = column;\n\n for (i = 0; i < that.table.tBodies.length; i++) {\n var newRows = [],\n noSorts = {},\n j,\n totalRows = 0,\n noSortsSoFar = 0;\n\n if (that.table.tBodies[i].rows.length < 2) continue;\n\n for (j = 0; j < that.table.tBodies[i].rows.length; j++) {\n var cell;\n\n item = that.table.tBodies[i].rows[j];\n if (item.getAttribute('data-sort-method') === 'none') {\n // keep no-sorts in separate list to be able to insert\n // them back at their original position later\n noSorts[totalRows] = item;\n } else {\n if (columnKey) {\n cell = getCellByKey(item.cells, columnKey);\n } else {\n cell = item.cells[that.col];\n }\n // Save the index for stable sorting\n newRows.push({\n tr: item,\n td: cell ? getInnerText(cell,that.options) : '',\n index: totalRows\n });\n }\n totalRows++;\n }\n // Before we append should we reverse the new array or not?\n // If we reverse, the sort needs to be `anti-stable` so that\n // the double negatives cancel out\n if (sortOrder === 'descending') {\n newRows.sort(stabilize(sortFunction, true));\n } else {\n newRows.sort(stabilize(sortFunction, false));\n newRows.reverse();\n }\n\n // append rows that already exist rather than creating new ones\n for (j = 0; j < totalRows; j++) {\n if (noSorts[j]) {\n // We have a no-sort row for this position, insert it here.\n item = noSorts[j];\n noSortsSoFar++;\n } else {\n item = newRows[j - noSortsSoFar].tr;\n }\n\n // appendChild(x) moves x if already present somewhere else in the DOM\n that.table.tBodies[i].appendChild(item);\n }\n }\n\n that.table.dispatchEvent(createEvent('afterSort'));\n },\n\n refresh: function() {\n if (this.current !== undefined) {\n this.sortTable(this.current, true);\n }\n }\n };\n\n if ( true && module.exports) {\n module.exports = Tablesort;\n } else {\n window.Tablesort = Tablesort;\n }\n})();\n\n\n//# sourceURL=webpack://BurguillosInfo/./node_modules/tablesort/src/tablesort.js?"); +/***/ }), + +/***/ "./js-src/carousel-ad.ts": +/*!*******************************!*\ + !*** ./js-src/carousel-ad.ts ***! + \*******************************/ +/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => { + +"use strict"; +eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export */ __webpack_require__.d(__webpack_exports__, {\n/* harmony export */ \"default\": () => (/* binding */ CarouselAd)\n/* harmony export */ });\nclass CarouselAd {\n constructor() {\n this.currentAdNumber = null;\n this.ad = null;\n this.timeoutNumber = null;\n }\n getCarousel() {\n const carousel = document.querySelector('.carousel');\n if (carousel === null || !(carousel instanceof HTMLElement)) {\n this.noMoreAds();\n CarouselAd.fail('No carousel.');\n }\n return carousel;\n }\n static fail(error) {\n throw new Error(error);\n }\n async run() {\n this.loadOneAd();\n try {\n let start = 0;\n let end = 0;\n this.getCarousel().addEventListener('pointerdown', (event) => {\n start = event.pageX;\n console.log(start);\n });\n this.getCarousel().addEventListener('pointerup', (event) => {\n end = event.pageX;\n console.log(end);\n if (start - end > 100) {\n if (this.timeoutNumber !== null) {\n window.clearTimeout(this.timeoutNumber);\n }\n this.loadOneAd();\n }\n else {\n const a = this.retrieveLinkCarousel();\n window.location.href = a.href;\n }\n });\n }\n catch (e) {\n console.log(e);\n return;\n }\n }\n noMoreAds() {\n const carousel = this.getCarousel();\n if (carousel !== null) {\n carousel.remove();\n }\n this.expandPageContents();\n if (this.timeoutNumber === null) {\n return;\n }\n window.clearTimeout(this.timeoutNumber);\n }\n expandPageContents() {\n const pageContents = document.querySelector('div.page-contents');\n if (pageContents === null) {\n return;\n }\n pageContents.classList.add('no-carousel');\n }\n retrieveLinkCarousel() {\n const carousel = this.getCarousel();\n const maybeA = carousel.querySelector('a');\n if (maybeA !== null) {\n return maybeA;\n }\n const a = document.createElement('a');\n a.addEventListener('click', (event) => {\n event.preventDefault();\n });\n a.addEventListener('pointerdown', (event) => {\n event.preventDefault();\n });\n a.addEventListener('pointerup', (event) => {\n event.preventDefault();\n });\n carousel.innerHTML = \"\";\n carousel.append(a);\n return a;\n }\n async loadOneAd() {\n try {\n const params = new URLSearchParams();\n if (this.currentAdNumber !== null) {\n params.append('n', \"\" + this.currentAdNumber);\n }\n const response = await fetch('/next-ad.json?' + params);\n const responseJson = await response.json();\n this.currentAdNumber = responseJson.current_ad_number;\n this.ad = responseJson.ad;\n if (this.ad === null) {\n this.noMoreAds();\n return;\n }\n const must_continue = responseJson.continue;\n const carousel = this.getCarousel();\n if (must_continue === 0\n || carousel.offsetWidth === 0) {\n this.noMoreAds();\n return;\n }\n const a = this.retrieveLinkCarousel();\n a.innerHTML = \"\";\n const image = document.createElement('img');\n const text_container = document.createElement('div');\n const text = document.createElement('h4');\n const promoted = document.createElement('p');\n promoted.classList.add('promoted-tag');\n promoted.innerText = \"Promocionado\";\n image.src = this.ad.img;\n image.alt = \"\";\n text.innerText = this.ad.text;\n a.href = this.ad.href;\n a.append(image);\n text_container.append(promoted);\n text_container.append(text);\n a.append(text_container);\n this.timeoutNumber = window.setTimeout(() => {\n this.loadOneAd();\n }, this.ad.seconds * 1000);\n }\n catch (e) {\n this.timeoutNumber = window.setTimeout(() => {\n this.loadOneAd();\n }, 1000);\n }\n }\n}\n\n\n//# sourceURL=webpack://BurguillosInfo/./js-src/carousel-ad.ts?"); + /***/ }) /******/ }); diff --git a/webpack.config.js b/webpack.config.js index bee5534..957cf2a 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -8,7 +8,7 @@ module.exports = { path: path.resolve(__dirname, 'public/js/') }, resolve: { - extensions: ['.js', '.ts'], + extensions: ['.js', '.jsx', '.ts', '.tsx'], roots: [ path.resolve(__dirname, 'js-src/') ], @@ -18,10 +18,20 @@ module.exports = { }, module: { rules: [ + { + test: /\.(?:tsx|ts)?$/, + use: 'ts-loader', + exclude: /node_modules/ + }, { test: /\.jpe?g|png$/, exclude: /node_modules/, use: ['url-loader', 'file-loader'] + }, + { + test: /\.(js|jsx)$/, + exclude: /node_modules/, + loader: 'babel-loader' } ] }