Version 9.9.6
This commit is contained in:
parent
cfe7e209b0
commit
c67aee4876
@ -1,76 +0,0 @@
|
||||
import { retrieveData } from "./storage.js";
|
||||
import sha256 from "./sha256.min.js";
|
||||
import XORCipher from "./xorc.js";
|
||||
import getBrowserFingerprint from "./identify.js"
|
||||
|
||||
|
||||
export const passwordHash = {
|
||||
toString: () => {
|
||||
let fp = getBrowserFingerprint( { hardwareOnly: true } );
|
||||
let data;
|
||||
try {
|
||||
data = window.sessionStorage.getItem(sha256(fp));
|
||||
} catch (e) {
|
||||
return "none";
|
||||
}
|
||||
if (data === null) return "none";
|
||||
return XORCipher.decode(fp, data);
|
||||
},
|
||||
|
||||
set: (pw) => {
|
||||
let fp = getBrowserFingerprint( { hardwareOnly: true } );
|
||||
window.sessionStorage.setItem(sha256(fp), XORCipher.encode(fp, pw));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
function setPassword() {
|
||||
let x = document.getElementById("loginForm");
|
||||
let pw = x.elements[0].value;
|
||||
|
||||
if (pw != "" || pw !== "undefined") {
|
||||
let pwOld = pw;
|
||||
passwordHash.set(sha256(pw));
|
||||
let templateFiles = retrieveData("templateFiles");
|
||||
if (templateFiles != "") {
|
||||
try {
|
||||
JSON.parse(templateFiles);
|
||||
} catch (e) {
|
||||
document.getElementById("wrongPWAlert").style.display = "block";
|
||||
const alertTimeout = setTimeout(() => {
|
||||
document.getElementById("wrongPWAlert").style.display = "none";
|
||||
}, 5000);
|
||||
passwordHash.set(pwOld);
|
||||
x.elements[0].value = "";
|
||||
return;
|
||||
}
|
||||
}
|
||||
//user logged in
|
||||
|
||||
document.getElementById("login").style.display = "none";
|
||||
window.sessionStorage.setItem(sha256("verified"), XORCipher.encode(sha256("passwordHash"), passwordHash));
|
||||
}
|
||||
}
|
||||
|
||||
export function getUsrId() {
|
||||
const fingerprint = getBrowserFingerprint( { hardwareOnly: true } );
|
||||
return cyrb53(fingerprint + passwordHash);
|
||||
}
|
||||
|
||||
const cyrb53 = (str, seed = 21) => {
|
||||
let h1 = 0xdeadbeef ^ seed,
|
||||
h2 = 0x41c6ce57 ^ seed;
|
||||
for (let i = 0, ch; i < str.length; i++) {
|
||||
ch = str.charCodeAt(i);
|
||||
h1 = Math.imul(h1 ^ ch, 2654435761);
|
||||
h2 = Math.imul(h2 ^ ch, 1597334677);
|
||||
}
|
||||
|
||||
h1 = Math.imul(h1 ^ (h1 >>> 16), 2246822507) ^ Math.imul(h2 ^ (h2 >>> 13), 3266489909);
|
||||
h2 = Math.imul(h2 ^ (h2 >>> 16), 2246822507) ^ Math.imul(h1 ^ (h1 >>> 13), 3266489909);
|
||||
|
||||
return 4294967296 * (2097151 & h2) + (h1 >>> 0);
|
||||
};
|
||||
|
||||
export default setPassword;
|
||||
@ -70,11 +70,16 @@ function buildField(obj, form, sidebarList) {
|
||||
|
||||
let div = document.createElement("DIV");
|
||||
div.classList.add("w3-section");
|
||||
div.classList.add("w3-flat-silver");
|
||||
div.classList.add("w3-left-align");
|
||||
|
||||
div.setAttribute("style", "padding: 10px");
|
||||
|
||||
let label = document.createElement("LABEL");
|
||||
label.style.display = "inline-block";
|
||||
label.style.width = "100%";
|
||||
label.style.paddingBottom = "5px";
|
||||
label.style.borderBottom = "thin solid #9e9e9e";
|
||||
label.style.fontWeight = "800";
|
||||
|
||||
let connectedListsArray = [];
|
||||
|
||||
@ -220,17 +225,23 @@ function buildField(obj, form, sidebarList) {
|
||||
if (obj.listCount == 0) {
|
||||
select3 = document.createElement("button");
|
||||
select3.setAttribute("value", "!none");
|
||||
select3.classList.add("w3-button", "w3-grey", "w3-left-align");
|
||||
select3.classList.add("w3-button", "w3-grey", "w3-left-align", "w3-padding-16");
|
||||
select3.id = obj.word.replace(/ /g, "_");
|
||||
select3.innerHTML = "Show";
|
||||
select3.innerHTML = connectedListsArray[0].word;
|
||||
select3.setAttribute("name", "clM-"+obj.word.replace(/ /g, "_"));
|
||||
select3.setAttribute("data-word", connectedListsArray[0].word);
|
||||
}
|
||||
|
||||
label = document.createElement("LABEL");
|
||||
label.innerHTML = '';
|
||||
//div.classList.add("w3-center");
|
||||
div.classList.replace("w3-flat-silver", "w3-flat-clouds");
|
||||
div.appendChild(label);
|
||||
div.appendChild(select3);
|
||||
} else {
|
||||
div.appendChild(label);
|
||||
div.appendChild(document.createElement("br"));
|
||||
|
||||
div.appendChild(select3);
|
||||
}
|
||||
|
||||
|
||||
|
||||
break;
|
||||
@ -298,6 +309,7 @@ function buildField(obj, form, sidebarList) {
|
||||
if (obj.cl !== undefined) {
|
||||
div.lastChild.setAttribute("name",
|
||||
(ltPlaceholder !== undefined) ? "cl-"+obj.word.replace(/ /g, "_") +":"+ltPlaceholder : "cl-"+obj.word.replace(/ /g, "_"));
|
||||
divContainer.classList.add("w3-animate-opacity")
|
||||
}
|
||||
|
||||
//append field to wrapper and add to mainForm
|
||||
@ -8,18 +8,6 @@ function createTemplate(template = false) {
|
||||
//set current page value in activeState object
|
||||
activeState.activePage = "createTemplate";
|
||||
|
||||
//check if user is authenticated and templateFilesArray is decryptable
|
||||
let tF = retrieveData("templateFiles");
|
||||
if (tF != "") {
|
||||
try {
|
||||
tF = JSON.parse(tF);
|
||||
} catch (e) {
|
||||
alert("Decryption failed; are you authenticated?");
|
||||
window.location.reload();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if (screen.width > 992) {
|
||||
document.getElementById("siteTitle").innerHTML = "Manage templates";
|
||||
@ -69,6 +57,9 @@ function createTemplate(template = false) {
|
||||
function loadTemplateSidebar(templates) {
|
||||
let sidebarList = document.createElement("ul");
|
||||
sidebarList.classList.add("w3-ul");
|
||||
if (!templates.includes('_textBlocks')) {
|
||||
templates.push('_textBlocks');
|
||||
}
|
||||
|
||||
|
||||
for (let template of templates) {
|
||||
@ -116,6 +116,10 @@ function clickSetForm(e) {
|
||||
}
|
||||
|
||||
function clickImportFiles() {
|
||||
if (activeState.localOnly) {
|
||||
createBookShelfDownload();
|
||||
return;
|
||||
}
|
||||
checkForStoredDataOnServer();
|
||||
document.getElementById("modalMsg").addEventListener("click", (e) => {
|
||||
if (e.target && e.target.tagName === "BUTTON") {
|
||||
@ -156,7 +160,7 @@ function clickImportFiles() {
|
||||
"Files saved to server <br><br> would you like to <a href='/storage/" +
|
||||
getUsrId() +
|
||||
".txt' style='text-decoration: underline;' download>download</a> them?"
|
||||
);
|
||||
,0);
|
||||
break;
|
||||
case "Delete":
|
||||
delStoredDataOnServer();
|
||||
@ -179,6 +183,29 @@ function clickImportFiles() {
|
||||
|
||||
}
|
||||
|
||||
function createBookShelfDownload() {
|
||||
let data = createBookShelf();
|
||||
let filename = data[0]['data'] + ".txt";
|
||||
document.getElementById("modalMsg").addEventListener("click", (e) => {
|
||||
if (e.target && e.target.tagName === "BUTTON") {
|
||||
var element = document.createElement('a');
|
||||
element.setAttribute('href', 'data:text/plain;charset=utf-8,' + encodeURIComponent(JSON.stringify(data)));
|
||||
element.setAttribute('download', filename);
|
||||
element.style.display = 'none';
|
||||
document.body.appendChild(element);
|
||||
element.click();
|
||||
document.body.removeChild(element);
|
||||
}
|
||||
});
|
||||
modalNotifier(
|
||||
"Since you are in local only mode you can only export a backup of your files \
|
||||
<br><br> \
|
||||
<div class='w3-container'> \
|
||||
<button class='w3-button w3-border w3-flat-wet-asphalt' >Export</button></div>"
|
||||
,0);
|
||||
|
||||
}
|
||||
|
||||
export {
|
||||
hideMenus,
|
||||
showMenu,
|
||||
@ -189,5 +216,5 @@ export {
|
||||
clickClearForm,
|
||||
modalNotifier,
|
||||
clickSetForm,
|
||||
clickImportFiles,
|
||||
clickImportFiles
|
||||
};
|
||||
@ -6,6 +6,7 @@ import {
|
||||
} from "./storage.js";
|
||||
import { loadTemplate } from "./web.js";
|
||||
import parseFormOnSubmit from "./parseForm.js";
|
||||
import { modalNotifier } from "./evts.js";
|
||||
|
||||
function buildFile() {
|
||||
createStorageObj();
|
||||
@ -14,26 +15,18 @@ function buildFile() {
|
||||
//set current page value in activeState object
|
||||
activeState.activePage = "files";
|
||||
|
||||
//check if user is authenticated and templateFilesArray is decryptable
|
||||
let tF = retrieveData("templateFiles");
|
||||
if (tF != "") {
|
||||
try {
|
||||
tF = JSON.parse(tF);
|
||||
} catch (e) {
|
||||
alert("Decryption failed; are you authenticated?");
|
||||
window.location.reload();
|
||||
return;
|
||||
}
|
||||
}
|
||||
//set templateFiles array
|
||||
let tF = JSON.parse(retrieveData("templateFiles"));
|
||||
|
||||
if (tF == null) {
|
||||
if (tF == null || tF.length == 0) {
|
||||
//console.log("none yet");
|
||||
alert("there are no saved texts yet");
|
||||
modalNotifier("there are no saved texts yet");
|
||||
return;
|
||||
}
|
||||
|
||||
if (screen.width > 992) {
|
||||
document.getElementById("siteTitle").innerHTML = "Template Gen";
|
||||
|
||||
if (screen.width < 993) {
|
||||
document.getElementById("siteTitle").innerHTML = "Saved files";
|
||||
} else {
|
||||
document.getElementById("siteTitle").innerHTML = "TG";
|
||||
}
|
||||
@ -83,7 +76,7 @@ function loadFileDivCallBack() {
|
||||
|
||||
let lT = activeState.loadedTemplate;
|
||||
let fN = activeState.fileName;
|
||||
let storageName = fN + "-" + lT;
|
||||
let storageName = fN + "_m21_" + lT;
|
||||
|
||||
let fileDisplay = document.createElement("DIV");
|
||||
fileDisplay.classList.add(
|
||||
@ -149,12 +142,19 @@ function loadFileDivCallBack() {
|
||||
}
|
||||
|
||||
function clickLoadFileDiv(fileName, template) {
|
||||
if (fileName == "_overflow") return;
|
||||
|
||||
if (fileName == "_clearAll") {
|
||||
clearAllFiles();
|
||||
return;
|
||||
}
|
||||
|
||||
document.getElementById("mainForm").innerHTML = "";
|
||||
loadFileDiv(fileName, template);
|
||||
}
|
||||
|
||||
function clearFileData(storData) {
|
||||
let fileName = storData.split("-")[0];
|
||||
let fileName = storData.split("_m21_")[0];
|
||||
|
||||
let tF = JSON.parse(retrieveData("templateFiles"));
|
||||
let newArray = [];
|
||||
@ -179,9 +179,11 @@ function clearFileData(storData) {
|
||||
function loadFileSidebar(tF) {
|
||||
let sidebarList = document.createElement("ul");
|
||||
sidebarList.classList.add("w3-ul");
|
||||
|
||||
for (let obj of tF) {
|
||||
let sidebarListItem = document.createElement("li");
|
||||
let c = 0;
|
||||
let sidebarItemsAmount = 10;
|
||||
let sidebarListItem;
|
||||
for (let obj of tF.reverse()) {
|
||||
sidebarListItem = document.createElement("li");
|
||||
sidebarListItem.classList.add(
|
||||
"w3-bar-item",
|
||||
"w3-padding-large",
|
||||
@ -189,12 +191,39 @@ function loadFileSidebar(tF) {
|
||||
);
|
||||
sidebarListItem.style.borderBottom = "1px solid #ddd";
|
||||
sidebarListItem.id = "sb-" + obj.fileName.replace(/:/g, "_");
|
||||
sidebarListItem.innerHTML =
|
||||
obj.fileName.replace(/_/g, " ") + " - " + obj.template.replace(/_/g, " ");
|
||||
|
||||
if (c > sidebarItemsAmount) {
|
||||
sidebarListItem.setAttribute("data-template", '_overflow');
|
||||
sidebarListItem.setAttribute("data-file", '_overflow');
|
||||
sidebarListItem.classList.add("w3-flat-clouds");
|
||||
sidebarListItem.classList.remove("w3-button");
|
||||
sidebarListItem.style.borderRight = "1px solid rgb(221, 221, 221)"
|
||||
sidebarListItem.innerHTML = tF.length - sidebarItemsAmount + " files not shown";
|
||||
sidebarList.appendChild(sidebarListItem);
|
||||
break;
|
||||
}
|
||||
sidebarListItem.innerHTML = obj.fileName.replace(/_/g, " ");
|
||||
sidebarListItem.setAttribute("data-file", obj.fileName);
|
||||
sidebarListItem.setAttribute("data-template", obj.template);
|
||||
sidebarList.appendChild(sidebarListItem);
|
||||
c++;
|
||||
}
|
||||
|
||||
sidebarListItem = document.createElement("li");
|
||||
sidebarListItem.classList.add(
|
||||
"w3-bar-item",
|
||||
"w3-padding-large",
|
||||
"w3-button"
|
||||
);
|
||||
sidebarListItem.style.borderBottom = "1px solid #ddd";
|
||||
sidebarListItem.setAttribute("data-template", '_clearAll');
|
||||
sidebarListItem.setAttribute("data-file", '_clearAll');
|
||||
sidebarListItem.classList.add("w3-flat-pomegranate", "w3-bottom");
|
||||
sidebarListItem.style.borderRight = "1px solid rgb(221, 221, 221)";
|
||||
sidebarListItem.style.width = "300px";
|
||||
sidebarListItem.innerHTML = "Clear all files";
|
||||
sidebarList.appendChild(sidebarListItem);
|
||||
|
||||
return sidebarList;
|
||||
}
|
||||
|
||||
@ -287,14 +316,17 @@ function formEvts(storageName) {
|
||||
function loadSpecificTemplate(storageName) {
|
||||
storeData(
|
||||
"userInputForce",
|
||||
retrieveData(storageName.split("-")[0], storageName.split("-")[1])
|
||||
retrieveData(storageName.split("_m21_")[0], storageName.split("_m21_")[1])
|
||||
);
|
||||
loadTemplate(storageName.split("-")[1]);
|
||||
//reset sidebar to clear events
|
||||
let sidebarDiv = document.getElementById("sidebar");
|
||||
sidebarDiv.replaceWith(sidebarDiv.cloneNode(true));
|
||||
loadTemplate(storageName.split("_m21_")[1]);
|
||||
}
|
||||
|
||||
function getPreviousFile(storageName) {
|
||||
|
||||
let orgFileName = storageName.split("-")[0];
|
||||
let orgFileName = storageName.split("_m21_")[0];
|
||||
let tF = JSON.parse(retrieveData("templateFiles"));
|
||||
let i = 0;
|
||||
let previousFile;
|
||||
@ -302,6 +334,10 @@ function getPreviousFile(storageName) {
|
||||
for (let obj of tF) {
|
||||
if (obj.fileName == orgFileName) {
|
||||
previousFile = tF[i-1];
|
||||
if (previousFile === undefined) {
|
||||
//get the next one if there is no previous one
|
||||
previousFile = tF[i+1];
|
||||
}
|
||||
break;
|
||||
}
|
||||
i++;
|
||||
@ -310,4 +346,15 @@ function getPreviousFile(storageName) {
|
||||
return (previousFile != undefined) ? previousFile : false;
|
||||
}
|
||||
|
||||
function clearAllFiles() {
|
||||
let tF = JSON.parse(retrieveData("templateFiles"));
|
||||
if (tF == null || tF.length == 0) {
|
||||
modalNotifier("there are no saved texts yet");
|
||||
return;
|
||||
}
|
||||
for (let storageName of tF) {
|
||||
clearFileData(storageName.fileName);
|
||||
}
|
||||
}
|
||||
|
||||
export { buildFile, loadFileDivCallBack };
|
||||
@ -1,17 +1,8 @@
|
||||
//class Form {
|
||||
// constructor () {
|
||||
// this.buildForm = buildForm;
|
||||
// this.retrieveForm = retrieveForm;
|
||||
// }
|
||||
//
|
||||
//
|
||||
//
|
||||
//}
|
||||
|
||||
import { storeData, createStorageObj } from "./storage.js";
|
||||
import parseInput from "./parseTemplate.js";
|
||||
import transformTemplateObject from "./buildForm.js";
|
||||
import { showSidebar, handleOnBlur } from "./evts.js";
|
||||
import { showSidebar, handleOnBlur, modalNotifier } from "./evts.js";
|
||||
import parseFormOnSubmit, { parseTextMarkups } from "./parseForm.js";
|
||||
|
||||
function buildForm(templateInput, loadOnly = false) {
|
||||
@ -243,6 +234,7 @@ function formEvts() {
|
||||
createStorageObj();
|
||||
e.target.className = e.target.className.replace(" w3-grey", " w3-flat-nephritis");
|
||||
e.target.style.pointerEvents = "none";
|
||||
modalNotifier(activeState.fileName + " saved", 2);
|
||||
const timeoutSave = setTimeout(() => {
|
||||
e.target.className = e.target.className.replace(" w3-flat-nephritis"," w3-grey");
|
||||
e.target.style.pointerEvents = "auto";
|
||||
@ -272,13 +264,15 @@ function formEvts() {
|
||||
}
|
||||
});
|
||||
|
||||
//add handle on blur event listener
|
||||
document.getElementById("mainForm").addEventListener("click", (e) => {
|
||||
if (e.target && e.target.matches("div.w3-section")) {
|
||||
//add handle on blur event listener to each form object
|
||||
let mainForm = document.getElementById("mainFormObj");
|
||||
for (let formElement of mainForm.children) {
|
||||
let id = formElement.firstChild.lastChild.id;
|
||||
document.getElementById(id).addEventListener("blur", (e) => {
|
||||
e.preventDefault;
|
||||
handleOnBlur(e.target);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function focusOnField(id) {
|
||||
@ -1,7 +1,7 @@
|
||||
import { hideMenus, showMenu, showSidebar, showTextBlocks, clickSetForm, clickImportFiles } from "./evts.js";
|
||||
import { buildFile } from "./files.js";
|
||||
import setPassword, { passwordHash } from "./scripts.js";
|
||||
import { getUsrId } from "./scripts.js";
|
||||
import setPassword, { passwordHash, sessionVerification } from "./scripts.js";
|
||||
import { getUsrId, logout } from "./scripts.js";
|
||||
import parseFormOnSubmit from "./parseForm.js";
|
||||
import { createStorageObj } from "./storage.js";
|
||||
import XORCipher from "./xorc.js";
|
||||
@ -16,6 +16,8 @@ window.activeState = {
|
||||
fileName: "",
|
||||
lastElement: "",
|
||||
serverFilesTs: "",
|
||||
lineBreak: 100,
|
||||
localOnly: true,
|
||||
templates: [],
|
||||
templateFieldTypes: [
|
||||
"simpleInput",
|
||||
@ -38,6 +40,7 @@ window.activeState = {
|
||||
function init() {
|
||||
|
||||
//check if user is logged in
|
||||
sessionVerification();
|
||||
let verfiedStatus = window.sessionStorage.getItem(sha256("verified"));
|
||||
if (verfiedStatus != null) {
|
||||
//user logged in
|
||||
@ -62,6 +65,12 @@ function init() {
|
||||
//print current version to footer
|
||||
printVersion();
|
||||
|
||||
//add sign out
|
||||
document
|
||||
.getElementById("logout")
|
||||
.addEventListener("click", logout);
|
||||
|
||||
|
||||
//adjust title for mobile use
|
||||
if (screen.width < 993) {
|
||||
document.getElementById("siteTitle").innerHTML = "TG";
|
||||
@ -12,9 +12,7 @@ function parseFormOnSubmit(returnJSON = false, parseOnly = false) {
|
||||
try {
|
||||
dataArray = JSON.parse(dataArray);
|
||||
} catch (e) {
|
||||
alert("Decryption failed; are you authenticated?");
|
||||
window.location.reload();
|
||||
return;
|
||||
return "";
|
||||
}
|
||||
}
|
||||
if (dataArray == null) {
|
||||
@ -61,6 +59,7 @@ function parseFormOnSubmit(returnJSON = false, parseOnly = false) {
|
||||
//get the complete unparsed template string from sessionstorage from loadTemplate
|
||||
let fullString = window.sessionStorage.getItem("fullString");
|
||||
|
||||
//define output buffer
|
||||
let b = "";
|
||||
|
||||
if (objects == null) {
|
||||
@ -70,13 +69,20 @@ function parseFormOnSubmit(returnJSON = false, parseOnly = false) {
|
||||
|
||||
//iterate through templateObjects and look for according formdata
|
||||
for (let obj of objects) {
|
||||
//compaire each obj with elements from mainFormObj
|
||||
for (let data of dataArray) {
|
||||
|
||||
//convert conList Master name to default name as set flag for appending connected list fields cl-name
|
||||
let conListFlag = false;
|
||||
//if obj is the connected list main selector
|
||||
if (data.name.split("-")[0] == "clM") {
|
||||
//if connected list main matches current object
|
||||
if (data.name.substring(4) === obj.word.replace(/ /g, "_") ) {
|
||||
//set flag for next iteration of loop
|
||||
conListFlag = true;
|
||||
data.name = data.name.substring(4);
|
||||
|
||||
//selection is not added to buffer
|
||||
if (data.value == "!none") {
|
||||
obj.result = "";
|
||||
continue;
|
||||
@ -87,45 +93,18 @@ function parseFormOnSubmit(returnJSON = false, parseOnly = false) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//if field matches current object
|
||||
if (obj.word.replace(/ /g, "_") === data.name) {
|
||||
let value = parseDataForResult(obj, data.value);
|
||||
obj.result = value;
|
||||
|
||||
}
|
||||
if (conListFlag) {
|
||||
if (obj.type == "conList") {
|
||||
|
||||
//check for button if only one item exists and search conlist item
|
||||
if (obj.listCount == 0) {
|
||||
for (let d of dataArray) {
|
||||
if (d.name.split(":!")[1] !== undefined) d.placeholder = "!"+d.name.split(":!")[1];
|
||||
d.name = d.name.split(":!")[0];
|
||||
if ("cl-"+obj[0].replace(/ /g, "_") == d.name && d.value != "") {
|
||||
//console.log(d, obj[0], data);
|
||||
if (data.value.replace(/ /g, "_") == d.name.substring(3)) {
|
||||
if (d.hasOwnProperty("placeholder")) d.value = d.placeholder + "\n" + d.value;
|
||||
let value = parseDataForResult(obj, d.value);
|
||||
obj.result = obj.result + "\n" + value;
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
//loop through dataArray and look for coresponding conlist items
|
||||
for (let i = 0; i <= obj.listCount; i++) {
|
||||
for (let d of dataArray) {
|
||||
if (d.name.split(":!")[1] !== undefined) d.placeholder = "!"+d.name.split(":!")[1];
|
||||
d.name = d.name.split(":!")[0];
|
||||
if ("cl-"+obj[i].replace(/ /g, "_") == d.name && d.value != "") {
|
||||
if (data.value.replace(/ /g, "_") == d.name.substring(3)) {
|
||||
if (d.hasOwnProperty("placeholder")) d.value = d.placeholder + "\n" + d.value;
|
||||
let value = parseDataForResult(obj, d.value);
|
||||
obj.result = obj.result + "\n" + value;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
//handle conlist elements for parsing each element
|
||||
if (conListFlag && obj.type == "conList") {
|
||||
let value = parseConListForResult(obj, data, dataArray);
|
||||
obj.result = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -193,7 +172,6 @@ function parseFormOnSubmit(returnJSON = false, parseOnly = false) {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//Plugin TextBlock Insertion according to file _textblocks.txt
|
||||
value = parseTextBlocks(value);
|
||||
|
||||
@ -201,9 +179,45 @@ function parseFormOnSubmit(returnJSON = false, parseOnly = false) {
|
||||
//handle placeholders like !l or !n and set it to final interpreted string for object
|
||||
value = parseTextMarkups(value);
|
||||
|
||||
//parse global linebreak after marked text was already fixed
|
||||
value = parseGlobalLineBreak(value);
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
function parseConListForResult(obj, data, dataArray) {
|
||||
//check for button if only one item exists and search conlist item
|
||||
if (obj.listCount == 0) {
|
||||
for (let d of dataArray) {
|
||||
if (d.name.split(":!")[1] !== undefined) d.placeholder = "!"+d.name.split(":!")[1];
|
||||
d.name = d.name.split(":!")[0];
|
||||
if ("cl-"+obj[0].replace(/ /g, "_") == d.name && d.value != "") {
|
||||
//console.log(d, obj[0], data);
|
||||
if (data.value.replace(/ /g, "_") == d.name.substring(3)) {
|
||||
if (d.hasOwnProperty("placeholder")) d.value = d.placeholder + "\n" + d.value;
|
||||
let value = parseDataForResult(obj, d.value);
|
||||
return obj.result + "\n" + value;
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
//loop through dataArray and look for coresponding conlist items
|
||||
for (let i = 0; i <= obj.listCount; i++) {
|
||||
for (let d of dataArray) {
|
||||
if (d.name.split(":!")[1] !== undefined) d.placeholder = "!"+d.name.split(":!")[1];
|
||||
d.name = d.name.split(":!")[0];
|
||||
if ("cl-"+obj[i].replace(/ /g, "_") == d.name && d.value != "") {
|
||||
if (data.value.replace(/ /g, "_") == d.name.substring(3)) {
|
||||
if (d.hasOwnProperty("placeholder")) d.value = d.placeholder + "\n" + d.value;
|
||||
let value = parseDataForResult(obj, d.value);
|
||||
return obj.result + "\n" + value;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function parseTextMarkups(data) {
|
||||
let dataArray = data.split("\n");
|
||||
let listFlag = false;
|
||||
@ -212,6 +226,7 @@ function parseFormOnSubmit(returnJSON = false, parseOnly = false) {
|
||||
let listNumberFlag = false;
|
||||
let listNumberFlagNum = 1;
|
||||
for (let i = 0; i < dataArray.length; i++) {
|
||||
if (dataArray[i] == "") continue;
|
||||
switch (dataArray[i]) {
|
||||
case "!l":
|
||||
listFlag = true;
|
||||
@ -245,21 +260,29 @@ function parseFormOnSubmit(returnJSON = false, parseOnly = false) {
|
||||
i = i - 1;
|
||||
break;
|
||||
default:
|
||||
|
||||
if (boldFlag) {
|
||||
dataArray[i] = "<b>" + dataArray[i] + "</b>";
|
||||
boldFlag = false;
|
||||
}
|
||||
if (listSubFlag) {
|
||||
dataArray[i] = " ○ " + dataArray[i];
|
||||
continue;
|
||||
}
|
||||
if (listFlag) {
|
||||
dataArray[i] = " • " + dataArray[i];
|
||||
}
|
||||
|
||||
//check if list indicator has been set and adjust userInput accordingly
|
||||
let listIndicator = "";
|
||||
|
||||
if (listNumberFlag) {
|
||||
dataArray[i] = " " + listNumberFlagNum + ". " + dataArray[i];
|
||||
listIndicator = " " + listNumberFlagNum + ". ";
|
||||
listNumberFlagNum++;
|
||||
}
|
||||
|
||||
if (listSubFlag && listIndicator == "") listIndicator = " ○ ";
|
||||
|
||||
if (listFlag && listIndicator == "") listIndicator = " • ";
|
||||
|
||||
//handle global linebreak and fit according to indicator according to list indicator
|
||||
|
||||
if (listIndicator != "") dataArray[i] = parseLineBreak(listIndicator + dataArray[i], listIndicator.length);
|
||||
|
||||
}
|
||||
}
|
||||
return dataArray.join("\n");
|
||||
@ -294,6 +317,62 @@ function parseFormOnSubmit(returnJSON = false, parseOnly = false) {
|
||||
return data;
|
||||
}
|
||||
|
||||
function parseGlobalLineBreak(data) {
|
||||
//parse each line of input with parseLineBreak return condensed string with newlines
|
||||
let parsedData = '';
|
||||
for (let line of data.split('\n')) {
|
||||
let parsedLine = parseLineBreak(line, 0, activeState.lineBreak);
|
||||
if (parsedData != '') {
|
||||
parsedData = parsedData + '\n' + parsedLine;
|
||||
} else {
|
||||
parsedData = parsedLine;
|
||||
}
|
||||
}
|
||||
return parsedData
|
||||
}
|
||||
|
||||
function parseLineBreak(line, intendation = 0, lineBreak = activeState.lineBreak - 5) {
|
||||
//add 5 chars buffer to fix list intendation issue
|
||||
//each input field gets parsed line by line twice once for list inputs and a second time for each input
|
||||
let lines;
|
||||
|
||||
if (line.length > lineBreak) {
|
||||
//create linebreak in between second to last word
|
||||
let correctedLineBreak;
|
||||
let newLineStart;
|
||||
let cLBt = lineBreak-(intendation*2)
|
||||
//find last space before linebreak
|
||||
correctedLineBreak = line.substring(0, cLBt).lastIndexOf(" ");
|
||||
//and fix the next lines start
|
||||
newLineStart = correctedLineBreak+1;
|
||||
//add to parsed output
|
||||
lines = line.substring(0, correctedLineBreak);
|
||||
//delete first parsed output from inputstring
|
||||
line = line.substring(newLineStart);
|
||||
|
||||
let intendationSpaces = '';
|
||||
//check if an intendation is given if so convert it to correct spaces
|
||||
if (intendation != 0) intendationSpaces = ' '.repeat(intendation+1);
|
||||
|
||||
//start loop to parse rest of the string
|
||||
while(line.length > lineBreak) {
|
||||
let cLBt = lineBreak-(intendation*2)
|
||||
correctedLineBreak = line.substring(0, cLBt).lastIndexOf(" ");
|
||||
newLineStart = correctedLineBreak+1;
|
||||
//add to output with intendation if given
|
||||
lines += "\n" + intendationSpaces + line.substring(0, correctedLineBreak);
|
||||
//delete from input
|
||||
line = line.substring(newLineStart);
|
||||
}
|
||||
//process rest of the string with correct intendation
|
||||
lines += "\n" + intendationSpaces + line;
|
||||
} else {
|
||||
//if string is within lineBreak just forward input to output
|
||||
lines = line;
|
||||
}
|
||||
return lines;
|
||||
}
|
||||
|
||||
|
||||
function loadTextBlocks() {
|
||||
let textBlocks = document.getElementById("textBlocks").innerText;
|
||||
@ -9,10 +9,24 @@ function parseInput(wordArray, objects, i) {
|
||||
//init Word Object
|
||||
var wordObj = {};
|
||||
|
||||
|
||||
let w = word.substring(1);
|
||||
|
||||
//bugfix if the title of an input has no space in it ex: %test=l:first word;l:second word;%1
|
||||
let oneWordFlag = false;
|
||||
if (word.substring(0, 1) == "%" && word.indexOf('=') != -1) {
|
||||
oneWordFlag = true;
|
||||
}
|
||||
|
||||
//for loop to escape spaces in simple input
|
||||
for (let j = i + 1; j < wordArray.length; j++) {
|
||||
for (let j = i+1; j < wordArray.length; j++) {
|
||||
//if title has no space then go back one word to include "=" ex:
|
||||
if (oneWordFlag) {
|
||||
j = i;
|
||||
oneWordFlag = false;
|
||||
} else {
|
||||
w = w + " " + wordArray[j];
|
||||
}
|
||||
//invoke look for gender specific template
|
||||
i = findGenderSpecificInput(wordArray, wordObj, j);
|
||||
//invoke look for list template
|
||||
167
js/9.9.6/scripts.js
Normal file
167
js/9.9.6/scripts.js
Normal file
@ -0,0 +1,167 @@
|
||||
import { retrieveData } from "./storage.js";
|
||||
import sha256 from "./sha256.min.js";
|
||||
import XORCipher from "./xorc.js";
|
||||
import getBrowserFingerprint from "./identify.js"
|
||||
|
||||
export const passwordHash = {
|
||||
name: "anae3Iegbai1ahLu",
|
||||
fp: getBrowserFingerprint( { hardwareOnly: true } ),
|
||||
toString: () => {
|
||||
let data;
|
||||
try {
|
||||
data = window.sessionStorage.getItem(sha256(passwordHash.name));
|
||||
} catch (e) {
|
||||
return "none";
|
||||
}
|
||||
if (data === null) return "none";
|
||||
return XORCipher.decode(passwordHash.name, data);
|
||||
},
|
||||
getId: () => {
|
||||
return passwordHash.fp;
|
||||
},
|
||||
set: (pw) => {
|
||||
window.sessionStorage.setItem(sha256(passwordHash.name), XORCipher.encode(passwordHash.name, pw));
|
||||
}
|
||||
}
|
||||
|
||||
//export const passwordHash = {
|
||||
// toString: () => {
|
||||
// let fp = getBrowserFingerprint( { hardwareOnly: true } );
|
||||
// let data;
|
||||
// try {
|
||||
// data = window.sessionStorage.getItem(sha256(fp));
|
||||
// } catch (e) {
|
||||
// return "none";
|
||||
// }
|
||||
// if (data === null) return "none";
|
||||
// return XORCipher.decode(fp, data);
|
||||
// },
|
||||
//
|
||||
// set: (pw) => {
|
||||
// let fp = getBrowserFingerprint( { hardwareOnly: true } );
|
||||
// window.sessionStorage.setItem(sha256(fp), XORCipher.encode(fp, pw));
|
||||
// }
|
||||
//}
|
||||
|
||||
|
||||
|
||||
function setPassword() {
|
||||
let x = document.getElementById("loginForm");
|
||||
let pw = x.elements[0].value;
|
||||
|
||||
if (pw != "" || pw !== "undefined") {
|
||||
let pwOld = pw;
|
||||
passwordHash.set(sha256(pw));
|
||||
let templateFiles = retrieveData("templateFiles");
|
||||
if (templateFiles != "") {
|
||||
try {
|
||||
JSON.parse(templateFiles);
|
||||
} catch (e) {
|
||||
document.getElementById("wrongPWAlert").style.display = "block";
|
||||
const alertTimeout = setTimeout(() => {
|
||||
document.getElementById("wrongPWAlert").style.display = "none";
|
||||
}, 5000);
|
||||
passwordHash.set(pwOld);
|
||||
x.elements[0].value = "";
|
||||
return;
|
||||
}
|
||||
}
|
||||
//user logged in
|
||||
|
||||
document.getElementById("login").style.display = "none";
|
||||
window.sessionStorage.setItem(sha256("verified"), XORCipher.encode(sha256("passwordHash"), passwordHash));
|
||||
setCookie(sha256("verified"), XORCipher.encode(sha256("passwordHash"), passwordHash), 10)
|
||||
}
|
||||
}
|
||||
|
||||
export function getUsrId() {
|
||||
const fingerprint = getBrowserFingerprint( { hardwareOnly: true } );
|
||||
return cyrb53(fingerprint + passwordHash);
|
||||
}
|
||||
|
||||
export function sessionVerification() {
|
||||
|
||||
//check if cookie exists
|
||||
if (getCookie(sha256("verified")) != null) {
|
||||
passwordHash.set(XORCipher.decode(sha256("passwordHash"), getCookie(sha256("verified"))));
|
||||
window.sessionStorage.setItem(sha256("verified"), XORCipher.encode(sha256("passwordHash"), passwordHash));
|
||||
}
|
||||
|
||||
let verfiedStatus = window.sessionStorage.getItem(sha256("verified"));
|
||||
let data;
|
||||
try {
|
||||
data = window.sessionStorage.getItem(sha256(passwordHash.name));
|
||||
} catch (e) {
|
||||
verfiedStatus = null;
|
||||
}
|
||||
if (data === null) verfiedStatus = null;
|
||||
|
||||
//if (verfiedStatus != data) verfiedStatus = null
|
||||
let vsString;
|
||||
let pnString;
|
||||
try {
|
||||
vsString = XORCipher.decode(sha256("passwordHash"), verfiedStatus);
|
||||
pnString = XORCipher.decode(passwordHash.name, data);
|
||||
if (vsString != pnString) verfiedStatus = null;
|
||||
} catch (e) {
|
||||
verfiedStatus = null;
|
||||
}
|
||||
|
||||
let tF = retrieveData("templateFiles");
|
||||
try {
|
||||
tF = JSON.parse(tF);
|
||||
} catch(e) {
|
||||
//verfiedStatus = null;
|
||||
}
|
||||
|
||||
return (verfiedStatus == null) ? false : true;
|
||||
|
||||
}
|
||||
|
||||
const cyrb53 = (str, seed = 21) => {
|
||||
let h1 = 0xdeadbeef ^ seed,
|
||||
h2 = 0x41c6ce57 ^ seed;
|
||||
for (let i = 0, ch; i < str.length; i++) {
|
||||
ch = str.charCodeAt(i);
|
||||
h1 = Math.imul(h1 ^ ch, 2654435761);
|
||||
h2 = Math.imul(h2 ^ ch, 1597334677);
|
||||
}
|
||||
|
||||
h1 = Math.imul(h1 ^ (h1 >>> 16), 2246822507) ^ Math.imul(h2 ^ (h2 >>> 13), 3266489909);
|
||||
h2 = Math.imul(h2 ^ (h2 >>> 16), 2246822507) ^ Math.imul(h1 ^ (h1 >>> 13), 3266489909);
|
||||
|
||||
return 4294967296 * (2097151 & h2) + (h1 >>> 0);
|
||||
};
|
||||
|
||||
function setCookie(cname, cvalue, exdays) {
|
||||
const d = new Date();
|
||||
d.setTime(d.getTime() + (exdays * 24 * 60 * 60 * 1000));
|
||||
let expires = "expires="+d.toUTCString();
|
||||
document.cookie = cname + "=" + cvalue + ";" + expires + ";path=/";
|
||||
}
|
||||
|
||||
export function getCookie(cname) {
|
||||
let name = cname + "=";
|
||||
let ca = document.cookie.split(';');
|
||||
for(let i = 0; i < ca.length; i++) {
|
||||
let c = ca[i];
|
||||
while (c.charAt(0) == ' ') {
|
||||
c = c.substring(1);
|
||||
}
|
||||
if (c.indexOf(name) == 0) {
|
||||
return c.substring(name.length, c.length);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
export function logout() {
|
||||
let id = sha256("verified");
|
||||
window.sessionStorage.setItem(id, "");
|
||||
document.cookie = id + "=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=/;";
|
||||
sessionVerification();
|
||||
document.getElementById("passwordField").value = "";
|
||||
document.getElementById("login").style.display = "block";
|
||||
}
|
||||
|
||||
export default setPassword;
|
||||
@ -22,7 +22,7 @@ function createStorageObj() {
|
||||
let userFileName = userFileNameField.value;
|
||||
let userFileNamePH = userFileNameField.getAttribute("placeholder");
|
||||
if (userFileName.length != 0) {
|
||||
activeState.fileName = userFileName;
|
||||
activeState.fileName = userFileName.replace;
|
||||
//clear old data as file switches to new filename
|
||||
if (userFileNamePH.length != 0) {
|
||||
clearData(userFileNamePH);
|
||||
@ -45,9 +45,9 @@ function storeData(name, data) {
|
||||
name = "userInput";
|
||||
}
|
||||
let lT = activeState.loadedTemplate;
|
||||
let key = sha256(name + "-" + lT);
|
||||
let key = sha256(name + "_m21_" + lT);
|
||||
if (name == "templateFiles") {
|
||||
key = sha256(name + "-");
|
||||
key = sha256(name + "_m21_");
|
||||
}
|
||||
window.localStorage.setItem(key, obfuscate(data));
|
||||
}
|
||||
@ -67,13 +67,13 @@ function retrieveData(type, template = "none") {
|
||||
let cdata;
|
||||
if (template == "none") {
|
||||
let lT = activeState.loadedTemplate;
|
||||
let key = sha256(type + "-" + lT);
|
||||
let key = sha256(type + "_m21_" + lT);
|
||||
if (type == "templateFiles") {
|
||||
key = sha256(type + "-");
|
||||
key = sha256(type + "_m21_");
|
||||
}
|
||||
cdata = window.localStorage.getItem(key);
|
||||
} else {
|
||||
let key = sha256(type + "-" + template);
|
||||
let key = sha256(type + "_m21_" + template);
|
||||
cdata = window.localStorage.getItem(key);
|
||||
}
|
||||
if (cdata != null) {
|
||||
@ -88,19 +88,20 @@ function clearData(type, template = "none") {
|
||||
let key;
|
||||
if (template == "none") {
|
||||
lT = activeState.loadedTemplate;
|
||||
key = sha256(type + "-" + lT);
|
||||
key = sha256(type + "_m21_" + lT);
|
||||
if (type == "templateFiles") {
|
||||
key = sha256(type + "-");
|
||||
key = sha256(type + "_m21_");
|
||||
}
|
||||
} else {
|
||||
lT = template;
|
||||
key = sha256(type + "-" + template);
|
||||
key = sha256(type + "_m21_" + template);
|
||||
}
|
||||
window.localStorage.removeItem(key);
|
||||
}
|
||||
|
||||
function getFileName() {
|
||||
let currentFileName = activeState.fileName;
|
||||
let lT = activeState.loadedTemplate;
|
||||
if (currentFileName == "none" || currentFileName == "") {
|
||||
let date = new Date();
|
||||
let current_hour = date.getHours();
|
||||
@ -115,21 +116,17 @@ function getFileName() {
|
||||
|
||||
let current_time = current_hour + ":" + current_minute;
|
||||
let current_date = current_year + "." + current_month;
|
||||
currentFileName = current_time + "_" + current_date;
|
||||
currentFileName = current_time + "_" + current_date + " " + lT;
|
||||
//console.log(currentFileName);
|
||||
}
|
||||
|
||||
let lT = activeState.loadedTemplate;
|
||||
let tF = retrieveData("templateFiles");
|
||||
if (tF != "") {
|
||||
let tF = null;
|
||||
try {
|
||||
tF = JSON.parse(tF);
|
||||
} catch (e) {
|
||||
alert("Decryption failed; are you authenticated?");
|
||||
window.location.reload();
|
||||
return;
|
||||
}
|
||||
tF = JSON.parse(retrieveData("templateFiles"));
|
||||
} catch(e) {
|
||||
tF = null;
|
||||
}
|
||||
|
||||
if (tF != null) {
|
||||
for (let tFi of tF) {
|
||||
if (tFi.fileName == currentFileName) {return currentFileName};
|
||||
@ -186,8 +183,13 @@ function createBookShelf() {
|
||||
for (let tFi of tF) {
|
||||
let data = retrieveData(tFi.fileName, tFi.template);
|
||||
bookShelf[i] = {};
|
||||
bookShelf[i].name = tFi.fileName + "-" + tFi.template;
|
||||
bookShelf[i].name = tFi.fileName + "_m21_" + tFi.template;
|
||||
if (activeState.localOnly) {
|
||||
bookShelf[i].data = data;
|
||||
} else {
|
||||
bookShelf[i].data = obfuscate(data);
|
||||
}
|
||||
|
||||
i++;
|
||||
}
|
||||
}
|
||||
@ -209,7 +211,7 @@ function importBookShelf() {
|
||||
for (let file of mainArray) {
|
||||
if (file.name == "hash") continue;
|
||||
window.localStorage.setItem(sha256(file.name), file.data);
|
||||
templateFilesArray.push({ fileName: file.name.split("-")[0], template: file.name.split("-")[1] });
|
||||
templateFilesArray.push({ fileName: file.name.split("_m21_")[0], template: file.name.split("_m21_")[1] });
|
||||
}
|
||||
window.localStorage.setItem(sha256("templateFiles-"), obfuscate(JSON.stringify(templateFilesArray)));
|
||||
}
|
||||
@ -1,9 +1,9 @@
|
||||
import buildForm from "./form.js";
|
||||
import { loadFileDivCallBack } from "./files.js";
|
||||
import { retrieveData, clearData, getFileName, importBookShelf } from "./storage.js";
|
||||
import { insertTextBlocks } from "./evts.js";
|
||||
import { insertTextBlocks, modalNotifier } from "./evts.js";
|
||||
import { createTemplate, createTemplateCallBack} from "./createTemplate.js";
|
||||
import { getUsrId } from "./scripts.js";
|
||||
import { getUsrId, sessionVerification } from "./scripts.js";
|
||||
|
||||
function loadTemplate(template, newFlag = false, loadOnly = false) {
|
||||
document.getElementById("siteTitle").innerHTML = template.replace(/_/g, " ");
|
||||
@ -45,7 +45,14 @@ function loadTemplate(template, newFlag = false, loadOnly = false) {
|
||||
cdata = retrieveData("userInputForce");
|
||||
}
|
||||
if (cdata != "") {
|
||||
retrieveForm(JSON.parse(cdata));
|
||||
let res = "";
|
||||
try {
|
||||
res = JSON.parse(cdata);
|
||||
} catch (e) {
|
||||
console.log("error", cdata);
|
||||
return;
|
||||
}
|
||||
retrieveForm(res);
|
||||
}
|
||||
|
||||
//select first object and focus on it
|
||||
@ -63,6 +70,12 @@ function loadTemplate(template, newFlag = false, loadOnly = false) {
|
||||
}
|
||||
|
||||
function loadNewTemplate(template) {
|
||||
|
||||
//sessionVerfication check
|
||||
if (!sessionVerification()) {
|
||||
modalNotifier("Error: Session is not authenticated...", 0);
|
||||
}
|
||||
|
||||
//set current page value in activeState object
|
||||
activeState.activePage = "template";
|
||||
|
||||
@ -77,7 +90,14 @@ function loadNavBar() {
|
||||
var xhttp = new XMLHttpRequest();
|
||||
xhttp.onreadystatechange = function () {
|
||||
if (this.readyState == 4 && this.status == 200) {
|
||||
let res = JSON.parse(this.responseText);
|
||||
let res = "";
|
||||
try {
|
||||
res = JSON.parse(this.responseText);
|
||||
} catch (e) {
|
||||
console.log("error", this.responseText);
|
||||
return;
|
||||
}
|
||||
|
||||
let divMob = document.getElementById("navMob");
|
||||
for (let x in res) {
|
||||
let aMob = document.createElement("a");
|
||||
@ -117,10 +137,18 @@ function initTextBlocks() {
|
||||
var xhttp = new XMLHttpRequest();
|
||||
xhttp.onreadystatechange = function () {
|
||||
if (this.readyState == 4 && this.status == 200) {
|
||||
let res = JSON.parse(this.responseText);
|
||||
let res = "";
|
||||
try {
|
||||
res = JSON.parse(this.responseText);
|
||||
} catch (e) {
|
||||
console.log("error", this.responseText)
|
||||
return;
|
||||
}
|
||||
const textBlocksHolder = document.getElementById("textBlocks");
|
||||
const divReg = document.getElementById("navTb");
|
||||
for (let x in res) {
|
||||
if (res[x][1].length < 1) continue;
|
||||
|
||||
let aReg = document.createElement("a");
|
||||
aReg.setAttribute("href", "#");
|
||||
aReg.classList.add("w3-bar-item", "w3-hide-small", "w3-padding-small");
|
||||
Loading…
Reference in New Issue
Block a user