"use strict"; /** PART#1 -- Menu classes * * ContextMenu shows a hidden menu upon right-click on a given element * * elt and menu must be defined in HTML code * items (if any) are added to those already in HTML * callback is invoked twice: when the menu is shown and when it is hidden * * Based on code contributed by Abdurrahman Rajab - FSMVU * Ref: https://dev.to/iamafro/how-to-create-a-custom-context-menu--5d7p */ class ContextMenu { /** * @param {HTMLElement} elt -- right-click to show menu * @param {HTMLElement} menu * @param {Array} items Array of string * @param {function} callback is invoked twice * @param {number} width of menu in px */ constructor(elt, menu, items, callback, width=200) { this.elt = elt this.menu = menu this.items = items this.width = width menu.style.width = (width)+'px' const _S = '', S_ = '' menu.innerHTML += items.map(i => _S+i+S_).join('\n') menu.onclick = (evt) => { evt.preventDefault() this.selectItem(evt.target) this.hide(); callback(this) } elt.oncontextmenu = evt => this.process(evt, callback) document.onkeydown = (evt) => { if (evt.key == 'Escape') ContextMenu.hideAll() } document.onclick = (evt) => { for (let e of document.querySelectorAll('.menu')) if (!e.hidden) { ContextMenu.hideAll() evt.preventDefault() return } } } /** Display menu at the position of the click or touch */ show(evt) { this.showAt(evt.clientX, evt.clientY) } /** Display menu at (x, y) */ showAt(x, y) { let cw = document.body.clientWidth x = Math.min(x, cw-this.width) // x+w<=cw this.menu.style.left = (x)+'px' this.menu.style.top = (y)+'px' this.menu.hidden = false } /** is menu hidden? */ get isHidden() { return this.menu.hidden } /** hide menu */ hide() { this.menu.hidden = true } /** t becomes the current item */ selectItem(t) { this.current = t } /** * Process left or right click * * @param {HTMLElement} evt * @param {function} callback if menu is shown */ process(evt, callback) { if (this.isHidden) { this.show(evt); callback(this) } else this.hide() evt.stopPropagation(); evt.preventDefault() } /** Hide all menus in the document */ static hideAll() { for (let e of document.querySelectorAll('.menu')) e.hidden = true } } /** SelectMenu shows a hidden menu upon click on elt * * Text of selected item is displayed in elt */ class SelectMenu extends ContextMenu { /** * @param {HTMLElement} elt -- selected item displayed here * @param {HTMLElement} menu * @param {Array} items * @param {function} callback * @param {number} width of menu */ constructor(elt, menu, items, callback, width) { super(elt, menu, items, callback, width) elt.style.width = (width)+'px' this.selectIndex(0) elt.oncontextmenu = '' //remove elt.onclick = evt => this.process(evt, callback) } /** Display menu just below elt -- event data not used */ show(evt) { let e = this.elt let x = e.offsetLeft - 1 let y = e.offsetTop + e.offsetHeight - 1 while (e && e.scrollTop==0) e = e.parentElement if (e) y -= e.scrollTop this.showAt(x, y) } /** i becomes the current index */ selectIndex(i) { const EM_SPACE = String.fromCharCode(8195) if (i<0 || i>=this.items.length) return this.index = i this.current = this.menu.children[i] this.elt.innerText = this.items[i]+EM_SPACE+'▾' } /** Find and select the item with text s */ selectText(s) { this.selectIndex(this.items.indexOf(s)) } /** Find and select the element t */ selectItem(t) { let a = [...this.menu.children] this.selectIndex(a.indexOf(t)) } } /** PART#2 -- Modal dialog classes */ /** PART#3 -- Cloud classes * * TabularData reads text and converts it to Array of objects */ class TabularData { constructor(sample, name) { this.name = name this.proto = Object.getPrototypeOf(sample) this.keys = Object.getOwnPropertyNames(sample) console.log(this.name, this) this.data = [] } readData(url, callback) { let k = this.keys let toArray = (t) => { for (let s of t.split('\n')) { if (!s) break //end of loop let b = s.split('\t'); //TAB let n = Math.min(k.length, b.length) let x = {} for (let i=0; i x.text()).then(toArray) } toString() { return this.keys.join(', ') } } // idea from A Rajab https://a0m0rajab.github.io/LearningQuest/googleDocs/submitForm.html // https://www.freecodecamp.org/news/cjn-google-sheets-as-json-endpoint/ // https://bionicteaching.com/silent-submission-of-google-forms/ class FormClient { constructor(url, entries) { this.FORM_URL = url + 'formResponse?usp=pp_url' this.entries = entries } submitData(...data) { //to Google Forms -- add one line if (data.length != this.entries.length) throw "Invalid data" let link = this.FORM_URL for (let i=0; i {post.parentNode.removeChild(post)} setTimeout(removeElement, 2000); } } class DocsClient { constructor(url) { this.DOCS_URL = url + 'pub?output=tsv' } //two ways to read from Google Sheets fetchData(success, failure) { //simplest method const mode = "no-cors" fetch(this.DOCS_URL, {mode}) .then(r => r.text()) .then(success).catch(failure) } tabularData(success) { //uses fetch in readData() const B = {time:0, user:0, topic:0, marks:0} const bm = new TabularData(B, 'Todo marks') bm.readData(this.DOCS_URL, t => {success(t, bm.data)}) } } /** PART#4 - various utilities * verifyTC, KeyHolder */ function verifyTC(s) { // ilk test için s string olmalı if (typeof s != 'string') s = String(s) // 11 basamaklı sayı (ilk basamak 0 olamaz) const E = /^[1-9][0-9]{10}$/ if (!E.test(s)) return 'test hata' // şimdi number[] gerekiyor s = [...s].map(i => Number(i)) // a, b, c değerlerine bakalım let a=0, b=0, c=s[10] for (let i=0; i<9; i+=2) { a += s[i]; b += s[i+1] } // (a+b)nin son basamağı c olmalı if ((a+b)%10 != c) return 'a+b hata' // (8a)nın son basamağı c olmalı return 8*a%10 != c? '8a hata' : true } class KeyHolder { constructor(name) { function askUser() { let k = prompt('(No quotes) Enter key for '+name) if (k) return k const ERR = 'You need an API key' console.error(ERR) if (window.out) out.innerText = ERR } this.name = name if (origin.startsWith('http') && localStorage) { if (!localStorage.keys) localStorage.keys = '{}' let keys = JSON.parse(localStorage.keys) if (!keys[name]) { keys[name] = askUser() localStorage.keys = JSON.stringify(keys) } this._key = keys[name] } else { //cannot use localStorage this._key = askUser() } } } //export { ContextMenu, SelectMenu, TabularData, verifyTC, KeyHolder }