mobile-config-firefox/html_resources/selector.js

500 lines
13 KiB
JavaScript
Raw Normal View History

2020-06-01 19:24:22 +02:00
'use strict';
let DB = null;
function initDB(obj){
DB = obj;
Object.defineProperty(DB,"query",{value:function (q,list){
let nlist = [];
for(let key of list || this.keys){
2020-06-01 19:24:22 +02:00
if(this[key].includes(q)){
nlist.push(key)
}
}
return nlist
}});
Object.defineProperty(DB,"keys",{value:(Object.keys(DB).sort())});
2020-07-18 07:39:34 +02:00
2020-07-18 07:45:02 +02:00
Object.defineProperty(DB,"getTagsForFile",{value:function(name){return this[name]}});
2020-07-18 07:39:34 +02:00
2020-06-01 19:24:22 +02:00
return true
}
function fetchWithType(url){
return new Promise((resolve,reject)=>{
const ext = url.substring(url.lastIndexOf(".")+1);
let expected = (ext === "json") ? "application/json" : (ext === "css") ? "text/css" : null;
if(!expected){
reject("unsupported file extension");
}
fetch(url)
.then(response =>{
const contentType = response.headers.get('content-type');
if (!contentType || !contentType.includes(expected)) {
reject(`Oops, we got ${contentType} but expected ${expected}`);
}
if(ext === "json"){
response.json()
.then(r=>resolve(r))
}else{
response.text()
.then(r=>resolve(r))
}
},except => reject(except))
});
}
let currentCategory = new (function(){
let currentPrimaryNode = null;
let currentSecondaryNode = null;
// TODO make filenames store ONLY the top level fileNames
//
let currentTopLevelFileNames = null;
this.set = function(t,secondary){
if(secondary){
currentSecondaryNode && currentSecondaryNode.classList.remove("currentCategory");
currentSecondaryNode = t;
currentSecondaryNode.classList.add("currentCategory");
}else{
currentPrimaryNode && currentPrimaryNode.classList.remove("currentCategory");
currentPrimaryNode = t;
currentPrimaryNode.classList.add("currentCategory");
2020-07-19 12:51:44 +02:00
currentSecondaryNode && currentSecondaryNode.classList.remove("currentCategory");
currentSecondaryNode = null;
}
2020-07-18 10:24:59 +02:00
if(!secondary){
currentTopLevelFileNames = DB.query(t.textContent);
}
2020-06-01 19:24:22 +02:00
};
this.getFileNames = function(node,secondary){
if(secondary){
return DB.query(node.textContent,currentTopLevelFileNames)
}
return currentTopLevelFileNames
}
2020-06-01 19:24:22 +02:00
return this
})()
function getText(node){
2021-05-09 12:36:27 +02:00
return `${node.textContent}.css`
2020-06-01 19:24:22 +02:00
}
2020-07-18 07:39:34 +02:00
function getSecondaryCategories(list){
let a = [];
2020-07-18 07:45:02 +02:00
for (let file of list){
a.push(DB.getTagsForFile(file));
2020-07-18 07:39:34 +02:00
}
a = a.flat();
2020-07-18 07:39:34 +02:00
a.sort();
let ret = [];
let i = 0;
ret[0] = a[0];
for(let f of a){
if(ret[i] != f){
ret[++i] = f
}
}
return ret
}
function clearCodeBlock(){
const pre = document.getElementById("previewBox");
for(let el of Array.from(pre.childNodes)){
2020-09-04 09:23:55 +02:00
pre.removeChild(el)
}
return
}
2020-09-04 09:23:55 +02:00
function onCategoryClicked(categoryNode,isSecondary = false){
2020-06-01 19:24:22 +02:00
clearCodeBlock();
currentCategory.set(categoryNode,isSecondary);
2020-07-18 07:39:34 +02:00
let secondaryCategoriesNode = document.querySelector("#secondaryCategories");
2020-07-18 10:29:50 +02:00
let fileNames = currentCategory.getFileNames(categoryNode,isSecondary);
if(!isSecondary){
2020-07-18 10:29:50 +02:00
if(fileNames.length > 9){
let matchingSecondaries = getSecondaryCategories(fileNames);
2020-07-19 12:51:44 +02:00
for(let child of Array.from(secondaryCategoriesNode.children)){
matchingSecondaries.includes(child.textContent) ? child.classList.remove("hidden") : child.classList.add("hidden")
}
2020-07-19 12:51:44 +02:00
document.getElementById("categories").classList.add("blurred");
}else{
2020-07-19 12:51:44 +02:00
document.getElementById("categories").classList.remove("blurred");
2020-07-18 07:39:34 +02:00
}
}
2020-06-01 19:24:22 +02:00
for(let c of Array.from(document.querySelectorAll(".target"))){
2020-07-18 10:29:50 +02:00
fileNames.includes(getText(c)) ? c.classList.remove("hidden") : c.classList.add("hidden");
2020-06-01 19:24:22 +02:00
}
2020-07-19 12:51:44 +02:00
document.getElementById("targets").setAttribute("style",`--grid-rows:${Math.ceil(fileNames.length/3)}`)
2020-06-01 19:24:22 +02:00
}
2021-05-09 12:36:27 +02:00
async function onTargetClicked(target){
2020-06-01 19:24:22 +02:00
const codeBlock = document.querySelector("pre");
2021-05-09 12:36:27 +02:00
const text = typeof target === "string"
? target
: getText(target);
fetchWithType(`chrome/${text}`)
2020-09-02 10:42:30 +02:00
//.then(text => (codeBlock.textContent = text))
.then(text => Highlighter.parse(codeBlock,text))
.catch(e => console.log(e))
2020-06-01 19:24:22 +02:00
}
function onSomeClicked(e){
let cl = e.target.parentNode.id;
switch(cl){
case "categories":
onCategoryClicked(e.target);
break;
2020-07-18 07:39:34 +02:00
case "secondaryCategories":
onCategoryClicked(e.target,true/* isSecondary */);
break;
2020-06-01 19:24:22 +02:00
case "targets":
onTargetClicked(e.target);
break;
default:
break;
}
}
function createCategories(){
const CAT_PARENT = document.getElementById("categories");
2020-07-18 07:39:34 +02:00
const CAT_SECOND = document.getElementById("secondaryCategories");
2020-09-04 09:23:55 +02:00
CAT_PARENT.addEventListener("click",onSomeClicked,{passive:true});
CAT_SECOND.addEventListener("click",onSomeClicked,{passive:true});
2020-06-01 19:24:22 +02:00
const TAR_PARENT = document.getElementById("targets");
2020-09-04 09:23:55 +02:00
TAR_PARENT.addEventListener("click",onSomeClicked,{passive:true});
2020-06-01 19:24:22 +02:00
const createNode = function(name,type){
let node = document.createElement("div");
node.classList.add(type);
if(type === "target"){
2021-05-09 12:36:27 +02:00
2020-06-01 19:24:22 +02:00
let link = node.appendChild(document.createElement("a"));
node.classList.add("hidden");
link.href = `https://github.com/MrOtherGuy/firefox-csshacks/tree/master/chrome/${name}`;
link.title = "See on Github";
2020-06-05 12:38:27 +02:00
link.target = "_blank";
2021-05-09 12:36:27 +02:00
const content = name.substring(0,name.lastIndexOf("."));
node.append(content);
node.setAttribute("title",content);
2020-06-05 12:01:30 +02:00
}else{
2020-06-06 12:43:26 +02:00
node.textContent = name.name;
2020-07-18 07:45:02 +02:00
name.value > 0 && node.setAttribute("data-value",name.value);
2020-06-01 19:24:22 +02:00
}
return node;
}
const createCategory = name => createNode(name,"category");
const createTarget = name => createNode(name,"target");
const CAT_NAMES = (function(){
let list = [];
for(let key of Object.keys(DB)){
TAR_PARENT.appendChild(createNode(key,"target"));
let things = DB[key];
for(let t of things){
list.push(t)
}
}
list.sort();
let ret = [];
let ns = [0];
ret[0] = list[0];
let i = 0;
for(let item of list){
if(ret[i]!=item){
ret[++i]=item;
ns[i]=0;
}else{
2020-06-05 08:48:56 +02:00
ns[i] += (item === "legacy" ? -1 : 1);
2020-06-01 19:24:22 +02:00
}
}
2020-07-18 07:45:02 +02:00
let map = ret.map((a,i)=>({name:a,value:ns[i]+1}))
2020-06-06 12:43:26 +02:00
return map
//return map.sort((a,b)=>(a.value > b.value?-1:a.value < b.value ? 1:0))
2020-06-01 19:24:22 +02:00
})();
for(let cat of CAT_NAMES){
2020-06-06 12:43:26 +02:00
// CAT_PARENT.appendChild(createCategory(cat.name))
2020-07-18 07:39:34 +02:00
CAT_PARENT.appendChild(createNode(cat,"category"));
CAT_SECOND.appendChild(createNode(cat,"category"));
2020-06-01 19:24:22 +02:00
}
}
2020-09-02 10:42:30 +02:00
const Highlighter = new(function(){
2020-09-03 11:06:45 +02:00
const state = new (function(){
let current = 0;
let previous = 0;
this.now = ()=>current;
this.previous = ()=>previous;
this.set = function(a){ previous = current; current = a; return}
})();
2020-09-02 10:42:30 +02:00
let pointer = 0;
let token = "";
const selectorToClassMap = new Map([
[":","pseudo"],
["#","id"],
[".","class"],
["[","attribute"]]);
2020-09-02 10:42:30 +02:00
this.parse = function(targetNode,text){
clearCodeBlock();
let node = document.createElement("div");
function createElementFromToken(type,c){
if(token.length === 0 && !c){
return
}
let n = document.createElement("span");
if(type==="selector"){
let parts = token.split(/([\.#:\[]\w[\w-_"'=\]]*|\s\w[\w-_"'=\]]*)/);
for(let part of parts){
if(part.length === 0){
continue
}
let c = part[0];
switch (c){
case ":":
case "#":
case "[":
case ".":
let p = n.appendChild(document.createElement("span"));
p.className = selectorToClassMap.get(c);
p.textContent = part;
break;
default:
n.append(part);
}
}
} else if(type === "comment"){
let linksToFile = token.match(/[\w-\.]+\.css/g);
if(linksToFile && linksToFile.length){
let linkIdx = 0;
let fromIdx = 0;
while(linkIdx < linksToFile.length){
let part = linksToFile[linkIdx++];
let idx = token.indexOf(part);
n.append(token.substring(fromIdx,idx));
let link = document.createElement("a");
link.textContent = part;
link.href = `https://github.com/MrOtherGuy/firefox-csshacks/tree/master/chrome/${part}`;
link.target = "_blank";
n.appendChild(link);
fromIdx = idx + part.length;
}
n.append(token.substring(fromIdx));
}else{
n.textContent = c || token
}
}
else{
n.textContent = c || token;
}
n.className = (`token ${type}`);
token = "";
node.appendChild(n);
return
2020-09-02 10:42:30 +02:00
}
2020-09-02 10:42:30 +02:00
let c;
2020-09-03 11:06:45 +02:00
let curly = false;
2020-09-02 10:42:30 +02:00
while(pointer < text.length){
c = text[pointer];
2020-09-03 11:06:45 +02:00
const currentState = state.now();
curly = currentState != 2 && (c === "{" || c === "}");
if(!curly){
token+=c;
}
switch(currentState){
2020-09-02 10:42:30 +02:00
case 0:
switch(c){
case "/":
if(text[pointer+1] === "*"){
2020-09-03 11:06:45 +02:00
state.set(2);
if(token.length > 1){
token = token.slice(0,-1);
createElementFromToken("selector");
2020-09-03 11:06:45 +02:00
token += "/"
}
2020-09-02 10:42:30 +02:00
}
break;
case "{":
2020-09-03 11:06:45 +02:00
state.set(3);
createElementFromToken("selector");
2020-09-02 10:42:30 +02:00
break;
2020-09-03 11:06:45 +02:00
case "}":
createElementFromToken("text");
2020-09-03 11:06:45 +02:00
break;
2020-09-02 10:42:30 +02:00
case "@":
2020-09-03 11:06:45 +02:00
state.set(5);
2020-09-02 10:42:30 +02:00
}
break;
case 2:
switch(c){
case "*":
if(text[pointer+1] === "/"){
token += "/";
pointer++;
2020-09-03 11:06:45 +02:00
state.set(state.previous());
createElementFromToken("comment");
2020-09-02 10:42:30 +02:00
}
}
break;
case 3:
switch(c){
2020-09-03 11:06:45 +02:00
case "/":
if(text[pointer+1] === "*"){
state.set(2);
}
break;
2020-09-02 10:42:30 +02:00
case ":":
createElementFromToken("property");
2020-09-03 11:06:45 +02:00
state.set(4);
2020-09-02 10:42:30 +02:00
break;
case "}":
createElementFromToken("text");
2020-09-03 11:06:45 +02:00
state.set(0);
2020-09-02 10:42:30 +02:00
}
break;
case 4:
switch(c){
case ";":
createElementFromToken("value");
2020-09-03 11:06:45 +02:00
state.set(3);
2020-09-02 10:42:30 +02:00
break;
case "}":
createElementFromToken("value");
2020-09-03 11:06:45 +02:00
state.set(0);
2020-09-02 10:42:30 +02:00
}
break;
case 5:
switch(c){
case " ":
createElementFromToken("atrule");
2020-09-03 11:06:45 +02:00
state.set(6);
2020-09-02 10:42:30 +02:00
}
break;
case 6:
switch(c){
case ";":
case "{":
createElementFromToken("atvalue");
2020-09-03 11:06:45 +02:00
state.set(0);
2020-09-02 10:42:30 +02:00
}
break
default:
false
}
curly && createElementFromToken("curly",c);
2020-09-02 10:42:30 +02:00
pointer++
}
createElementFromToken("text");
2020-09-02 10:42:30 +02:00
token = "";
2020-09-03 11:06:45 +02:00
state.set(0);
2020-09-02 10:42:30 +02:00
pointer = 0;
targetNode.appendChild(node);
2020-09-02 10:42:30 +02:00
return
}
return this
})();
async function handleSearchQuery(){
2021-05-09 12:36:27 +02:00
let params = (new URL(document.location)).searchParams;
let param = params.get("tag");
if(param){
let cats = document.querySelectorAll("#categories > .category");
for(let cat of cats){
if(cat.textContent === param){
onCategoryClicked(cat);
return
}
}
return
}
param = params.get("file");
if(param){
let files = param.split(",").filter(a => DB.keys.includes(a));
if(files.length === 0 ){
return
}
const codeBlock = document.querySelector("pre");
let composedText = "";
for(let file of files){
composedText += await fetchWithType(`chrome/${file}`);
composedText += "\n";
2021-05-09 12:36:27 +02:00
}
Highlighter.parse(codeBlock,composedText)
2021-05-09 12:36:27 +02:00
}
}
function showUI(){
document.getElementById("placeholder").remove();
document.getElementById("ui").classList.remove("hidden");
}
2020-06-01 19:24:22 +02:00
document.onreadystatechange = (function () {
2021-05-09 12:36:27 +02:00
2020-06-01 19:24:22 +02:00
if (document.readyState === "complete") {
2021-05-09 12:36:27 +02:00
function linkClicked(ev){
if(ev.target instanceof HTMLAnchorElement){
let ref = ev.target.href;
if(!ref){
return
}
let fileName = ref.slice(ref.lastIndexOf("/"));
if(fileName.endsWith(".css")){
onTargetClicked(fileName);
ev.preventDefault();
}
}
}
document.getElementById("previewBox").addEventListener("click",linkClicked);
fetchWithType("html_resources/tagmap.json")
2021-05-09 12:36:27 +02:00
.then(initDB)
.then(createCategories)
.then(handleSearchQuery)
.then(showUI)
.catch(e => console.log(e))
2020-06-01 19:24:22 +02:00
}
});