diff --git a/public/static/settings.js b/public/static/settings.js new file mode 100644 index 0000000..87c4f58 --- /dev/null +++ b/public/static/settings.js @@ -0,0 +1,186 @@ +/* + UI: Method for selecting all search engines + + This function is called when user click on + `select all` and `deselect all` toogle element. + + - If select all is pressed toggle all options and insert all value + joined with comma to input which is saved in cookie. + then changes text to `deselect all`. + eg value: 'ddg,searx,' + + - If deselect all is pressed uncheck all options and insert empty + value to input which is saved in cookie. +*/ +function selectAllHandler(elem) { + let span = elem.parentElement.querySelector("span"); + let mainInput = document.getElementsByName("searchEng")[0]; + let prevStateSelectAll = span.innerText == "Select all" ? true : false; + document.querySelectorAll(".searchEng-elem").forEach((engine) => { + if (prevStateSelectAll) { + engine.querySelector('input[type="checkbox"]').checked = true; + } else { + engine.querySelector('input[type="checkbox"]').checked = false; + } + }); + if (prevStateSelectAll) { + let getValues = () => { + let value = ""; + document + .querySelectorAll('[data-isCheckbox]:not([data-value="all"])') + .forEach((elem) => { + value += elem.getAttribute("data-value") + ","; + }); + return value; + }; + mainInput.value = getValues(); + } else { + mainInput.value = ""; + } + span.innerText = prevStateSelectAll ? "Deselect all" : "Select all"; +} + +/* + UI: Filter settings as per category + + There are two elements one is `category` and `detail`. + Category contains `data-id` when a user click it + we need to show detail element having + `data-detailId` whose value is same as `data-id`. + + When a user clicks on a category on sidebar, view + settings containing `data-id` of sidebar element's `data-detailId` + and hide other settings + + - if `all` is clicked then show all settings. +*/ +document.querySelectorAll(".settings-sidebar .set-name").forEach((filter) => { + let target = filter.getAttribute("data-detailId"); + filter.addEventListener("click", () => { + try { + document.querySelector(".set-name.active").classList.remove("active"); + } catch (e) {} + filter.classList.add("active"); + if (target == "all") { + document.querySelectorAll(".set-item").forEach((elem) => { + elem.style.display = "block"; + }); + return; + } + document + .querySelectorAll('.set-item[data-id="' + target + '"]') + .forEach((elem) => { + elem.style.display = "block"; + }); + document + .querySelectorAll('.set-item:not([data-id="' + target + '"])') + .forEach((elem) => { + elem.style.display = "none"; + }); + }); +}); + + +/* + This function is called when a user click on submit button + it validates all user inputs and saves to a cookie. + + - if input having `required` attr and is empty it generates a + error text and insert above the setting element + + - else store setttings to a cookie. +*/ +function submitSettings() { + let form = document.settings; + let stopProceeding = false; + document.querySelectorAll(".errTxt").forEach((e) => e.remove()); + for (let i = 0; i < form.elements.length; i++) { + let input = form.elements[i]; + if (input.value == "" && input.hasAttribute("required")) { + stopProceeding = true; + let elem = input.parentElement.querySelector("[takeInput]"); + let errTxt = document.createElement("p"); + errTxt.classList.add("errTxt"); + errTxt.innerText = "This setting can't be empty!!"; + elem.classList.add("invalid"); + elem.parentElement.insertBefore(errTxt, elem); + let sidebarElement = input.closest(".set-item").getAttribute("data-id"); + document + .querySelector( + `.settings-sidebar .set-name[data-detailId="${sidebarElement}` + ) + .click(); + stopProceeding = true; + } + } + if (!stopProceeding) { + var expiration_date = new Date(); + expiration_date.setFullYear(expiration_date.getFullYear() + 1); + let formData = new FormData(document.querySelector("form")); + for (var [key, value] of formData.entries()) { + document.cookie = `${key}=${value}; expires=${expiration_date.toUTCString()}`; + } + } else { + return false; + } + // On settings saved successfully + alert("Settings saved succssfully!"); + window.location.reload(); +} + + +/* + This function will be called on page ready. + it iterates over saved cookies and if cookie name is + in the list of valid cookies then load it. + + - if cookie is searchEng(type=toggle/checkbox) we deselect + all checkboxes and select those which are stored in cookie. + + - if cookie is of type `select` we deselect default selected + option and then select option which is stored in cookie. +*/ +function loadUserSettings() { + let inputs = ["searchEng", "theme", "color-sch"]; + var keyValuePairs = document.cookie.split(";"); + for (var i = 0; i < keyValuePairs.length; i++) { + var name = keyValuePairs[i].substring(0, keyValuePairs[i].indexOf("=")); + var value = keyValuePairs[i].substring(keyValuePairs[i].indexOf("=") + 1); + name = name.trim(); + if (!inputs.includes(name)) { + continue; + } + let input = document.getElementsByName(name)[0]; + input.value = value; + if (name == "searchEng") { + // Unload all checked engines + document + .querySelectorAll(".searchEng-elem input[type=checkbox]") + .forEach((e) => { + e.checked = false; + }); + value = value.replace(" ", ""); + value.split(",").forEach((val) => { + if (!val) { + return; + } + document + .querySelector(`[data-isCheckbox][data-value="${val}"]`) + .parentElement.querySelector("input").checked = true; + }); + } else { + // Unload all selected options + document + .querySelector( + `[data-input="${name}"] .options span[data-value="${value}"]` + ) + .removeAttribute("selected"); + singleSelectClickHandler( + document.querySelector(`.options span[data-value="${value}"]`) + ); + } + } +} + +// Code where settings are loaded from cookie. +loadUserSettings(); diff --git a/public/static/themes/simple.css b/public/static/themes/simple.css index 17962d0..dfcda52 100644 --- a/public/static/themes/simple.css +++ b/public/static/themes/simple.css @@ -263,41 +263,320 @@ footer { /* Styles for the about page */ - .about-container article{ - font-size: 1.5rem; - color:var(--fg); - padding-bottom: 10px; - } +.about-container article{ + font-size: 1.5rem; + color:var(--fg); + padding-bottom: 10px; +} - .about-container article h1{ - color: var(--2); - font-size: 2.8rem; - } +.about-container article h1{ + color: var(--2); + font-size: 2.8rem; +} - .about-container article div{ - padding-bottom: 15px; - } +.about-container article div{ + padding-bottom: 15px; +} - .about-container a{ - color:var(--3); - } +.about-container a{ + color:var(--3); +} - .about-container article h2{ - color: var(--3); - font-size: 1.8rem; - padding-bottom: 10px; - } +.about-container article h2{ + color: var(--3); + font-size: 1.8rem; + padding-bottom: 10px; +} - .about-container p{ - color:var(--fg); - font-size: 1.6rem; - padding-bottom: 10px; - } +.about-container p{ + color:var(--fg); + font-size: 1.6rem; + padding-bottom: 10px; +} - .about-container h3{ - font-size: 1.5rem; - } +.about-container h3{ + font-size: 1.5rem; +} - .about-container { - width: 80%; - } +.about-container { + width: 80%; +} + +/* css for settings page t */ +.settings { + margin: 2rem; + width: 80%; + height: 100%; + color: var(--fg); +} + +.settings h1 { + color: var(--2); + font-size: 2.5rem; +} + + +.settings hr { + border-color: var(--3); + margin: .3rem 0 1rem 0; +} + +.settings-view { + display: flex; + flex: 1 0 auto; + margin: 1.5rem 0; +} + +.settings-sidebar { + width: 25%; + height: 100%; +} + +.settings-sidebar .set-name { + cursor: pointer; + font-size: 2rem; + display: block; + margin-right: .5rem; + margin-left: -.7rem; + padding: .7rem; + border-radius: 5px; + font-weight: bold; + margin-bottom: .5rem; +} + +.settings-sidebar .set-name:hover, .settings-sidebar .set-name.active { + background-color: rgba(255, 255, 255, 0.15); + +} + +.settings-detail { + border-left: 1.5px solid var(--3); + padding-left: 3rem; +} + +.settings-detail .set-item { + margin: 2rem 0; + margin-top: 0; +} + +.settings-detail .set-name { + font-size: 2rem; + font-weight: bold; + color: var(--4) +} + +.settings-detail .set-desc { + font-size: 1.5rem; + margin-bottom: .5rem; +} + +/* css for custom select */ +.custom-select, .options { + font-size: 1.5rem; + background-color: var(--bg); + width: 250px; + padding: 1rem 1.7rem; + border-radius: 7px; +} + +.custom-select { + position: relative; + vertical-align: middle; + margin: .7rem 0; +} + +.custom-select.invalid { + border: 1px solid red; +} + +.custom-select svg { + float: right; +} + +.options { + display: none; + position: absolute; + left: 0; + margin-top: 1.3rem; + width: 100%; + padding: .7rem 1rem; + cursor: pointer; + z-index: 3; + max-height: 15rem; + overflow-y: auto; +} + +.options span { + display: block; + padding: 1rem; + width: 100%; + border-radius: 5px; + vertical-align: middle; +} + +.options span:hover { + background-color: var(--1); +} + +.selected-multiple-option { + padding: .8rem; + border-radius: 5px; + background-color: var(--bg); + margin: .5rem .3rem; +} + +.selected-multiple-option svg { + width: 1.3rem; + height: 1.3rem; + vertical-align: middle; + margin-bottom: .5rem; + cursor: pointer; +} + +.select-multiple-show { + margin: 1rem 0; + font-size: 1.5rem; +} + +.underlined-text { + font-size: 1.7rem; + cursor: pointer; + margin-bottom: .5rem; + display: block; +} + +.settings .submit-btn { + padding: 1rem 2rem; + font-size: 1.5rem; + background: var(--3); + color: var(--bg); + border-radius: .5rem; + border: 2px solid transparent; + font-weight: bold; + transition: all .1s ease-out; + cursor: pointer; + box-shadow: 5px 5px; +} + +.settings .submit-btn:active { + outline: none; + box-shadow: none; + translate: 5px 5px; +} + +/* Css for error text */ + +.errTxt { + color: white; + background: red; + padding: .5rem; + border-radius: 5px; + font-size: 1.3rem; + width: max-content; +} + +/* Css for toggle element */ + +label, +label::before, +label::after { + transition: 200ms all ease-in-out 50ms; + box-sizing: border-box; + backface-visibility: hidden; +} + +input[type="checkbox"] { + display: none; +} + +/*Button is :CHECKED*/ + +input[type="checkbox"]:checked ~ div[data-isCheckbox] { + background: rgba(73,168,68,1); + box-shadow: 0 0 2px rgba(73,168,68,1); +} + +input[type="checkbox"]:checked ~ div[data-isCheckbox] label { + left: 25px; + transform: rotate(360deg); +} + + +/*shared*/ + +div[data-isCheckbox], +label { + border-radius: 50px; +} + + +/*'un':checked state*/ + +div[data-isCheckbox] { + height: 25px; + width: 50px; + background: rgba(43, 43, 43, 1); + position: relative; + box-shadow: 0 0 2px rgba(43,43,43,1); + +} + +label { + height: 20px; + width: 20px; + background: rgba(255, 255, 255, 1); + position: absolute; + top: 3px; + left: 3px; + cursor: pointer; +} + +label::before { + content: ''; + height: 15px; + width: 3px; + position: absolute; + top: calc(50% - 8px); + left: calc(50% - 1.5px); + transform: rotate(45deg); +} + +label::after { + content: ''; + height: 3px; + width: 15px; + position: absolute; + top: calc(50% - 2.5px); + left: calc(50% - 8px); + transform: rotate(45deg); +} + +label::before, +label::after{ + background: rgba(43,43,43,1); + border-radius: 5px; +} + +/* pesduo class on toggle */ + +input[type="checkbox"]:checked ~ div label::before{ + height: 15px; + top: calc(55% - 8px); + left: calc(60% - 2px); + background: rgba(73,168,68,1); +} + +input[type="checkbox"]:checked ~ div label::after{ + width: 7px; + top: calc(95% - 7px); + left: calc(22.5% - 2px); + background: rgba(73,168,68,1); +} + +.searchEng-elem { + display: flex; + gap: 3rem; + align-items: center; + font-size: 1.5rem; + margin: 1rem 0; +} diff --git a/public/static/ui_plugins.js b/public/static/ui_plugins.js new file mode 100644 index 0000000..53dddb5 --- /dev/null +++ b/public/static/ui_plugins.js @@ -0,0 +1,110 @@ +/* + Select plugin + Note: Only for single select option + Usage example: + ``` + +
+ Svg Icon here +
+ Simple +
+
+ ``` +*/ + +let svg_tick = ` + + + + + `; + +function toggleSelectOptions(elem, state) { + elem.classList.remove("invalid"); + try { elem.parentElement.querySelector('.errTxt').remove();} catch (error) {} + let options = elem.querySelector('.options'); + const pos = elem.getBoundingClientRect(); + const windowWidth = document.getElementsByTagName("body")[0].clientHeight; + if(pos.y + 250 > windowWidth) { + options.style.bottom = '40px'; + } else { options.style.bottom = null } + options.style.display = state != 'close' ? getComputedStyle(options).display == 'none' ? 'block': 'none' : 'none'; +} + +let selectElements = document.querySelectorAll('.custom-select').forEach(element => { + let value = element.getAttribute('data-default') + element.childNodes[0].nodeValue = value; + element.querySelector(`.options span[data-value="${value.toLowerCase()}"]`). + innerHTML = `${value} ${svg_tick}`; + element.addEventListener('click', (e) => {if (e.target === element) toggleSelectOptions(element)}); + element.addEventListener('focusout', (e) => {if (e.target === element) toggleSelectOptions(element, 'close')}); +}); + +function singleSelectClickHandler(elem) { + let selectDiv = elem.closest('.custom-select'); + let selectedOption = selectDiv.querySelector('span[selected]'); + let input = document.querySelector(`[name="${selectDiv.getAttribute('data-input')}"]`); + if (!elem.hasAttribute('selected')) { + if (selectedOption != null) { + selectedOption.removeAttribute('selected'); + selectedOption.querySelector('svg').remove(); + } + elem.setAttribute('selected', ''); + elem.innerHTML = `${elem.innerText} ${svg_tick}` + // Code where value is inserted to input + input.value = elem.getAttribute('data-value') ? elem.getAttribute('data-value') : ''; + selectDiv.childNodes[0].nodeValue = elem.innerText; + } else { + elem.removeAttribute('selected'); + elem.querySelector('svg').remove(); + selectDiv.childNodes[0].nodeValue = selectDiv.getAttribute('data-defaultIfNone'); + + input.value = ""; + } + selectDiv.blur(); +} + +let singleSelectOptions = document.querySelectorAll('.custom-select:not([data-multiple="1"])>.options span'); +for (let i = 0; i < singleSelectOptions.length; i++) { + singleSelectOptions[i].addEventListener('click', () => {singleSelectClickHandler(singleSelectOptions[i])}); + singleSelectOptions[i].setAttribute('id', 'option-'+i.toString()); +} + + +/* + Toggle switch plugin + Usage example: + ``` + +
+
+ +
+ +
+ Duck duck go +
+
+ +
+ +
+ Searx +
+
+ ``` +*/ + +document.querySelectorAll('[data-isCheckbox]:not([data-value="all"]) label').forEach(checkBoxLabel => { + checkBoxLabel.addEventListener('click', () => { + let checkBox = checkBoxLabel.parentElement; + let helperInput = checkBox.parentElement.querySelector('input[type="checkbox"]'); + let mainInput = document.getElementsByName(checkBox.getAttribute('data-input'))[0]; + if (helperInput.checked == true) { + mainInput.value = mainInput.value.replace(checkBox.getAttribute('data-value') + ',', ''); + } else { + mainInput.value += checkBox.getAttribute('data-value') + ','; + } + }); +}) diff --git a/public/templates/header.html b/public/templates/header.html index 92053b9..7e9ac8c 100644 --- a/public/templates/header.html +++ b/public/templates/header.html @@ -4,9 +4,26 @@ Websurfx - - + +
{{>navbar}}
+ diff --git a/public/templates/navbar.html b/public/templates/navbar.html index f5f581f..87d6550 100644 --- a/public/templates/navbar.html +++ b/public/templates/navbar.html @@ -1,5 +1,6 @@