mirror of
https://codeberg.org/claui/mobile-config-firefox.git
synced 2024-11-09 19:30:15 +00:00
Merge branch 'codeblock'
This commit is contained in:
commit
cdeb11acfc
6 changed files with 991 additions and 286 deletions
438
html_resources/code-block/code-block-highlighter.js
Normal file
438
html_resources/code-block/code-block-highlighter.js
Normal file
|
@ -0,0 +1,438 @@
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
class BaseHighlighter{
|
||||||
|
constructor(){
|
||||||
|
this.state = BaseHighlighter.createState();
|
||||||
|
}
|
||||||
|
reset(){
|
||||||
|
this.state.reset();
|
||||||
|
}
|
||||||
|
parse(){
|
||||||
|
if(!this.state.initialState){
|
||||||
|
throw "This highlighter is not initialized";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
static createState(){
|
||||||
|
return new (function(){
|
||||||
|
this.token = "";
|
||||||
|
let current = null;
|
||||||
|
let previous = null;
|
||||||
|
let initialState = null;
|
||||||
|
this.set = (a) => {
|
||||||
|
previous = current;
|
||||||
|
current = a;
|
||||||
|
return
|
||||||
|
}
|
||||||
|
this.now = () => current;
|
||||||
|
this.previous = ()=> previous;
|
||||||
|
this.initializeTo = (some) => { initialState = some }
|
||||||
|
this.reset = () => {
|
||||||
|
this.token = "";
|
||||||
|
previous = initialState;
|
||||||
|
current = initialState;
|
||||||
|
}
|
||||||
|
})();
|
||||||
|
}
|
||||||
|
static BaseState = Symbol("base");
|
||||||
|
}
|
||||||
|
|
||||||
|
class SimpleHighlighter extends BaseHighlighter{
|
||||||
|
constructor(){
|
||||||
|
super();
|
||||||
|
this.state.initializeTo(BaseHighlighter.BaseState);
|
||||||
|
}
|
||||||
|
static State = {
|
||||||
|
SingleQuote : Symbol("quote"),
|
||||||
|
DoubleQuote : Symbol("quote")
|
||||||
|
}
|
||||||
|
|
||||||
|
parse(info,targetNode){
|
||||||
|
if(info.content.length > 0){
|
||||||
|
SimpleHighlighter.parseSimple(info,this.state,targetNode);
|
||||||
|
}
|
||||||
|
if(this.state.token){
|
||||||
|
throw "simple token is not 0"
|
||||||
|
}
|
||||||
|
this.state.token = "";
|
||||||
|
}
|
||||||
|
|
||||||
|
static parseSimple(info,state,target){
|
||||||
|
let pointer = 0;
|
||||||
|
let currentState = state.now();
|
||||||
|
const length = info.content.length;
|
||||||
|
while(pointer < length){
|
||||||
|
let character = info.content[pointer++];
|
||||||
|
if(character === "\r"){
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
currentState = state.now();
|
||||||
|
switch(currentState){
|
||||||
|
case BaseHighlighter.BaseState:
|
||||||
|
switch(character){
|
||||||
|
case "\"":
|
||||||
|
target.append(state.token);
|
||||||
|
state.token = "\"";
|
||||||
|
state.set(SimpleHighlighter.State.DoubleQuote);
|
||||||
|
break;
|
||||||
|
case "'":
|
||||||
|
targetNode.append(state.token);
|
||||||
|
state.token = "'";
|
||||||
|
state.set(SimpleHighlighter.State.SingleQuote);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
state.token += character;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case SimpleHighlighter.State.SingleQuote:
|
||||||
|
switch(character){
|
||||||
|
case "'":
|
||||||
|
target.appendChild(SimpleHighlighter.createQuote()).textContent = state.token + "'";
|
||||||
|
state.token = "";
|
||||||
|
state.set(BaseHighlighter.BaseState);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
state.token += character;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case SimpleHighlighter.State.DoubleQuote:
|
||||||
|
switch(character){
|
||||||
|
case "\"":
|
||||||
|
target.appendChild(SimpleHighlighter.createQuote()).textContent = state.token + "\"";
|
||||||
|
state.token = "";
|
||||||
|
state.set(BaseHighlighter.BaseState);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
state.token += character;
|
||||||
|
}
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if(state.token.length > 0){
|
||||||
|
if(currentState === BaseHighlighter.BaseState){
|
||||||
|
target.append(state.token);
|
||||||
|
state.token = "";
|
||||||
|
}else{
|
||||||
|
target.appendChild(SimpleHighlighter.createQuote()).textContent = state.token;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static createQuote(){
|
||||||
|
let n = document.createElement("span");
|
||||||
|
n.className = "quote";
|
||||||
|
return n
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class CSSHighlighter extends BaseHighlighter{
|
||||||
|
constructor(){
|
||||||
|
super();
|
||||||
|
this.state.initializeTo(CSSHighlighter.State.Selector);
|
||||||
|
this.state.curly = false;
|
||||||
|
this.state.fnLevel = 0;
|
||||||
|
this.state.generateLinks = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
reset(){
|
||||||
|
this.state.reset();
|
||||||
|
this.state.curly = false;
|
||||||
|
this.state.fnLevel = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
parse(info,targetNode){
|
||||||
|
if(info.content.length > 0){
|
||||||
|
CSSHighlighter.parseCSS(info,this.state,targetNode);
|
||||||
|
}
|
||||||
|
if(this.state.token){
|
||||||
|
throw "CSS token is not 0"
|
||||||
|
}
|
||||||
|
this.state.token = "";
|
||||||
|
}
|
||||||
|
|
||||||
|
static parseCSS(info,state,targetNode){
|
||||||
|
|
||||||
|
state.generateLinks = (info.linkMatcher instanceof RegExp) && (typeof info.linkGenerator === "function");
|
||||||
|
|
||||||
|
if(state.generateLinks && info.linkChanged){
|
||||||
|
if (info.linkGenerator != state.linkGenerator){
|
||||||
|
state.linkGenerator = info.linkGenerator;
|
||||||
|
}
|
||||||
|
if (info.linkMatcher != state.linkMatcher){
|
||||||
|
state.linkMatcher = info.linkMatcher;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let currentState;
|
||||||
|
const chars = Array.from(info.content);
|
||||||
|
if(info.content.endsWith("\r\n")){
|
||||||
|
chars[chars.length - 2] = "\n";
|
||||||
|
chars.pop();
|
||||||
|
}
|
||||||
|
const length = chars.length;
|
||||||
|
let tokenStart = 0;
|
||||||
|
let pointer = 0;
|
||||||
|
while(pointer < length){
|
||||||
|
let character = chars[pointer];
|
||||||
|
currentState = state.now();
|
||||||
|
switch(currentState){
|
||||||
|
|
||||||
|
case CSSHighlighter.State.Selector:
|
||||||
|
switch(character){
|
||||||
|
case "/":
|
||||||
|
if(chars[pointer+1] === "*"){
|
||||||
|
state.set(CSSHighlighter.State.Comment);
|
||||||
|
if(pointer - tokenStart > 0){
|
||||||
|
state.token = chars.slice(tokenStart,pointer).join("");
|
||||||
|
CSSHighlighter.createElementFromToken(state,CSSHighlighter.State.Selector,targetNode);
|
||||||
|
}
|
||||||
|
tokenStart = pointer;
|
||||||
|
pointer++;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case "{":
|
||||||
|
state.set(CSSHighlighter.State.Property);
|
||||||
|
state.token = chars.slice(tokenStart,pointer).join("");
|
||||||
|
CSSHighlighter.createElementFromToken(state,CSSHighlighter.State.Selector,targetNode);
|
||||||
|
tokenStart = pointer + 1;
|
||||||
|
targetNode.appendChild(CSSHighlighter.addBracket("{"));
|
||||||
|
break;
|
||||||
|
case "}":
|
||||||
|
state.token = chars.slice(tokenStart,pointer).join("");
|
||||||
|
CSSHighlighter.createElementFromToken(state,CSSHighlighter.State.Text,targetNode);
|
||||||
|
tokenStart = pointer + 1;
|
||||||
|
|
||||||
|
targetNode.appendChild(CSSHighlighter.addBracket("}"));
|
||||||
|
|
||||||
|
break;
|
||||||
|
case "@":
|
||||||
|
state.set(CSSHighlighter.State.AtRule);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case CSSHighlighter.State.Comment:
|
||||||
|
if(character === "*"){
|
||||||
|
if(chars[pointer+1] === "/"){
|
||||||
|
pointer++;
|
||||||
|
state.token = chars.slice(tokenStart,pointer+1).join("");
|
||||||
|
state.set(state.previous());
|
||||||
|
CSSHighlighter.createElementFromToken(state,CSSHighlighter.State.Comment,targetNode);
|
||||||
|
|
||||||
|
tokenStart = pointer + 1;
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case CSSHighlighter.State.Property:
|
||||||
|
switch(character){
|
||||||
|
case "/":
|
||||||
|
if(chars[pointer+1] === "*"){
|
||||||
|
state.set(CSSHighlighter.State.Comment);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case ":":
|
||||||
|
state.token = chars.slice(tokenStart,pointer+1).join("");
|
||||||
|
CSSHighlighter.createElementFromToken(state,CSSHighlighter.State.Property,targetNode);
|
||||||
|
state.set(CSSHighlighter.State.Value);
|
||||||
|
tokenStart = pointer + 1;
|
||||||
|
break;
|
||||||
|
case "}":
|
||||||
|
state.token = chars.slice(tokenStart,pointer).join("");
|
||||||
|
CSSHighlighter.createElementFromToken(state,CSSHighlighter.State.Text,targetNode);
|
||||||
|
state.set(CSSHighlighter.State.Selector);
|
||||||
|
tokenStart = pointer + 1;
|
||||||
|
targetNode.appendChild(CSSHighlighter.addBracket("}"));
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case CSSHighlighter.State.Value:
|
||||||
|
let createToken = true;
|
||||||
|
let indexOffset = 1;
|
||||||
|
switch(character){
|
||||||
|
case ";":
|
||||||
|
state.set(CSSHighlighter.State.Property);
|
||||||
|
break;
|
||||||
|
case "}":
|
||||||
|
indexOffset = 0;
|
||||||
|
state.set(CSSHighlighter.State.Selector);
|
||||||
|
break;
|
||||||
|
case "(":
|
||||||
|
state.fnLevel++;
|
||||||
|
state.set(CSSHighlighter.State.Function);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
createToken = false;
|
||||||
|
}
|
||||||
|
if(createToken){
|
||||||
|
state.token = chars.slice(tokenStart,pointer+indexOffset).join("");
|
||||||
|
CSSHighlighter.createElementFromToken(state,CSSHighlighter.State.Value,targetNode);
|
||||||
|
tokenStart = pointer + 1;
|
||||||
|
if(indexOffset === 0){
|
||||||
|
targetNode.appendChild(CSSHighlighter.addBracket("}"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case CSSHighlighter.State.AtRule:
|
||||||
|
switch(character){
|
||||||
|
case " ":
|
||||||
|
state.token = chars.slice(tokenStart,pointer+1).join("");
|
||||||
|
CSSHighlighter.createElementFromToken(state,CSSHighlighter.State.AtRule,targetNode);
|
||||||
|
state.set(CSSHighlighter.State.AtValue);
|
||||||
|
tokenStart = pointer + 1;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case CSSHighlighter.State.AtValue:
|
||||||
|
let idxOffset = 0;
|
||||||
|
switch(character){
|
||||||
|
case ";":
|
||||||
|
indexOffset = 1;
|
||||||
|
case "{":
|
||||||
|
state.token = chars.slice(tokenStart,pointer + idxOffset).join("");
|
||||||
|
CSSHighlighter.createElementFromToken(state,CSSHighlighter.State.AtValue,targetNode);
|
||||||
|
state.set(CSSHighlighter.State.Selector);
|
||||||
|
tokenStart = pointer + 1;
|
||||||
|
if(!idxOffset){
|
||||||
|
targetNode.appendChild(CSSHighlighter.addBracket("{"));
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
}
|
||||||
|
break
|
||||||
|
case CSSHighlighter.State.Function:
|
||||||
|
switch(character){
|
||||||
|
case ")":
|
||||||
|
state.fnLevel--;
|
||||||
|
if(state.fnLevel === 0){
|
||||||
|
state.token = chars.slice(tokenStart,pointer).join("");
|
||||||
|
CSSHighlighter.createElementFromToken(state,CSSHighlighter.State.Function,targetNode);
|
||||||
|
tokenStart = pointer;
|
||||||
|
state.set(CSSHighlighter.State.Value);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case "}":
|
||||||
|
state.fnLevel = 0;
|
||||||
|
state.set(CSSHighlighter.State.Selector);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pointer++
|
||||||
|
}
|
||||||
|
if(pointer > tokenStart){
|
||||||
|
state.token = chars.slice(tokenStart,pointer).join("");
|
||||||
|
CSSHighlighter.createElementFromToken(state,currentState,targetNode);
|
||||||
|
}
|
||||||
|
state.token = "";
|
||||||
|
}
|
||||||
|
|
||||||
|
static State = {
|
||||||
|
Selector: Symbol("selector"),
|
||||||
|
Text: Symbol("text"),
|
||||||
|
Comment: Symbol("comment"),
|
||||||
|
Property: Symbol("property"),
|
||||||
|
Value: Symbol("value"),
|
||||||
|
AtRule: Symbol("atrule"),
|
||||||
|
AtValue: Symbol("atvalue"),
|
||||||
|
Function: Symbol("function"),
|
||||||
|
Curly: Symbol("curly")
|
||||||
|
}
|
||||||
|
|
||||||
|
static selectorToClassMap = new Map([
|
||||||
|
[":","pseudo"],
|
||||||
|
["#","id"],
|
||||||
|
[".","class"],
|
||||||
|
["[","attribute"]
|
||||||
|
]);
|
||||||
|
|
||||||
|
static addBracket(n){
|
||||||
|
let span = document.createElement("span");
|
||||||
|
span.className = "curly";
|
||||||
|
span.textContent = n;
|
||||||
|
return span
|
||||||
|
}
|
||||||
|
|
||||||
|
static createElementFromToken(state,type,targetNode){
|
||||||
|
if(state.token.length === 0){
|
||||||
|
return
|
||||||
|
}
|
||||||
|
let n = document.createElement("span");
|
||||||
|
switch(type){
|
||||||
|
case CSSHighlighter.State.Selector:
|
||||||
|
// This isn't exactly correct, but it works because parser treats \r\n sequences that follow a closed comment as "selector"
|
||||||
|
//rulesetUnderConstruction = createNewRuleset();
|
||||||
|
let parts = state.token.split(/([\.#:\[]\w[\w-_"'=\]]*|\s\w[\w-_"'=\]]*)/);
|
||||||
|
|
||||||
|
for(let part of parts){
|
||||||
|
if(part.length === 0){
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
let character = part[0];
|
||||||
|
switch (character){
|
||||||
|
case ":":
|
||||||
|
case "#":
|
||||||
|
case "[":
|
||||||
|
case ".":
|
||||||
|
let p = n.appendChild(document.createElement("span"));
|
||||||
|
p.className = CSSHighlighter.selectorToClassMap.get(character);
|
||||||
|
p.textContent = part;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
n.append(part);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break
|
||||||
|
case CSSHighlighter.State.Comment:
|
||||||
|
if(state.generateLinks){
|
||||||
|
let linksToFile = state.token.match(state.linkMatcher);
|
||||||
|
if(linksToFile && linksToFile.length){
|
||||||
|
const transformed = linksToFile.map(state.linkGenerator);
|
||||||
|
n.append(CSSHighlighter.createLinksFromMatchingToken(linksToFile,transformed,state));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
n.textContent = state.token;
|
||||||
|
break;
|
||||||
|
case CSSHighlighter.State.Value:
|
||||||
|
let startImportant = state.token.indexOf("!important");
|
||||||
|
if(startImportant === -1){
|
||||||
|
n.textContent = state.token;
|
||||||
|
}else{
|
||||||
|
n.textContent = state.token.substr(0,startImportant);
|
||||||
|
let importantTag = document.createElement("span");
|
||||||
|
importantTag.className = "important-tag";
|
||||||
|
importantTag.textContent = "!important";
|
||||||
|
n.appendChild(importantTag);
|
||||||
|
if(state.token.length > (9 + startImportant)){
|
||||||
|
n.append(state.token.substr(startImportant + 10))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case CSSHighlighter.State.Function:
|
||||||
|
n.textContent = state.token;
|
||||||
|
break
|
||||||
|
default:
|
||||||
|
n.textContent = state.token;
|
||||||
|
}
|
||||||
|
|
||||||
|
n.className = (`token ${type.description}`);
|
||||||
|
|
||||||
|
targetNode.appendChild(n);
|
||||||
|
return
|
||||||
|
}
|
||||||
|
static createLinksFromMatchingToken(parts,transformed,state){
|
||||||
|
let frag = new DocumentFragment();
|
||||||
|
let linkIdx = 0;
|
||||||
|
let fromIdx = 0;
|
||||||
|
while(linkIdx < parts.length){
|
||||||
|
let part = parts[linkIdx];
|
||||||
|
let idx = state.token.indexOf(part);
|
||||||
|
frag.append(state.token.substring(fromIdx,idx));
|
||||||
|
let link = document.createElement("a");
|
||||||
|
link.textContent = part;
|
||||||
|
link.href = transformed[linkIdx++];
|
||||||
|
link.target = "_blank";
|
||||||
|
frag.appendChild(link);
|
||||||
|
fromIdx = idx + part.length;
|
||||||
|
}
|
||||||
|
frag.append(state.token.substring(fromIdx));
|
||||||
|
return frag
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export { CSSHighlighter,SimpleHighlighter }
|
122
html_resources/code-block/code-block.css
Normal file
122
html_resources/code-block/code-block.css
Normal file
|
@ -0,0 +1,122 @@
|
||||||
|
:host{
|
||||||
|
font-family: monospace;
|
||||||
|
white-space: pre-wrap;
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
:host(.inline){
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
:host(.nowrap){
|
||||||
|
white-space: pre;
|
||||||
|
overflow-x: auto;
|
||||||
|
}
|
||||||
|
:host([data-name]){
|
||||||
|
--copy-button-top: 1.5em;
|
||||||
|
}
|
||||||
|
:host(.inline.copy-able){
|
||||||
|
--copy-button-block-padding: 0;
|
||||||
|
--codeblock-inline-padding: 40px;
|
||||||
|
}
|
||||||
|
caption{
|
||||||
|
text-align: start;
|
||||||
|
padding-inline-start: 3em;
|
||||||
|
}
|
||||||
|
caption:empty{
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
.outerBox{
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
table{
|
||||||
|
border: 1px solid;
|
||||||
|
border-collapse: collapse;
|
||||||
|
border-spacing: 0px;
|
||||||
|
width: 100%;
|
||||||
|
margin-inline-end: var(--codeblock-inline-padding,0);
|
||||||
|
}
|
||||||
|
tbody{
|
||||||
|
counter-reset: nlines 0;
|
||||||
|
color: lightblue;
|
||||||
|
background-color: rgb(40,37,43);
|
||||||
|
}
|
||||||
|
@keyframes hide-show{
|
||||||
|
0%{ opacity: 0 }
|
||||||
|
10%{ opacity: 1 }
|
||||||
|
90%{ opacity: 1 }
|
||||||
|
100%{ opacity: 0 }
|
||||||
|
}
|
||||||
|
.copy-button::before{
|
||||||
|
content: "Copy"
|
||||||
|
}
|
||||||
|
.copy-button.copy-success::before{
|
||||||
|
white-space:nowrap;
|
||||||
|
content: "Copied";
|
||||||
|
pointer-events: none;
|
||||||
|
animation: hide-show 2s linear forwards;
|
||||||
|
}
|
||||||
|
.copy-button{
|
||||||
|
padding: var(--copy-button-block-padding,0.3em) 0.3em;
|
||||||
|
opacity: 0.5;
|
||||||
|
color: silver;
|
||||||
|
border: 1px solid rgba(230,230,230,0.3);
|
||||||
|
position: absolute;
|
||||||
|
margin-top: var(--copy-button-top);
|
||||||
|
right: .3em;
|
||||||
|
cursor: pointer
|
||||||
|
}
|
||||||
|
.copy-button.copy-success,
|
||||||
|
.copy-button:hover{
|
||||||
|
background-color: dodgerblue;
|
||||||
|
color: black;
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.line-number{
|
||||||
|
border-inline-end: 1px solid;
|
||||||
|
background-color: rgba(0,0,0,0.1);
|
||||||
|
color: silver;
|
||||||
|
vertical-align: top;
|
||||||
|
width: 0;
|
||||||
|
text-align: end;
|
||||||
|
user-select: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media screen and (prefers-color-scheme: dark){
|
||||||
|
tbody{
|
||||||
|
/* We're cool and assume dark mode by default */
|
||||||
|
}
|
||||||
|
}
|
||||||
|
tr{
|
||||||
|
counter-increment: nlines;
|
||||||
|
}
|
||||||
|
td,tr{
|
||||||
|
border-block: none;
|
||||||
|
}
|
||||||
|
td{
|
||||||
|
padding: 1px 0.7em;
|
||||||
|
}
|
||||||
|
.line-number::before{
|
||||||
|
content: counter(nlines)
|
||||||
|
}
|
||||||
|
a{
|
||||||
|
color: dodgerblue;
|
||||||
|
}
|
||||||
|
a:visited{
|
||||||
|
color: orchid
|
||||||
|
}
|
||||||
|
tbody > div{ display: contents }
|
||||||
|
/* These should really be moved into separate highlighter css */
|
||||||
|
.comment{ color: rgb(50,180,90) }
|
||||||
|
.selector{ color: lavenderblush }
|
||||||
|
.pseudo{ color: rgb(200,180,250) }
|
||||||
|
.id{ color: rgb(240, 148, 138) }
|
||||||
|
.class{ color: aquamarine }
|
||||||
|
.attribute{ color: rgb(100,200,250) }
|
||||||
|
.atrule{ color: lime }
|
||||||
|
.atvalue{ color: lightblue }
|
||||||
|
.property{ color: palegoldenrod }
|
||||||
|
.value{ color: skyblue }
|
||||||
|
.curly{ color: chocolate }
|
||||||
|
.function{ color: silver }
|
||||||
|
.important-tag{ color: orange }
|
||||||
|
.quote{ color: burlywood }
|
400
html_resources/code-block/code-block.js
Normal file
400
html_resources/code-block/code-block.js
Normal file
|
@ -0,0 +1,400 @@
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
class CodeBlock extends HTMLElement{
|
||||||
|
constructor(){
|
||||||
|
super();
|
||||||
|
let template = document.getElementById("code-block-template");
|
||||||
|
let templateContent = template ? template.content : CodeBlock.Fragment();
|
||||||
|
let cloned = templateContent.cloneNode(true);
|
||||||
|
const shadowRoot = this.attachShadow({mode: 'open'})
|
||||||
|
.appendChild(cloned);
|
||||||
|
this.highlighter = {
|
||||||
|
ready: false,
|
||||||
|
waiting: false,
|
||||||
|
fn: null,
|
||||||
|
failed: false,
|
||||||
|
type: null,
|
||||||
|
empty: true,
|
||||||
|
linkGenerator: null,
|
||||||
|
linkMatcher: null
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
determineAndLoadContent(){
|
||||||
|
CodeBlock.getSource(this.src)
|
||||||
|
.then(
|
||||||
|
(data) => this.consumeData(data,CodeBlock.InsertMode.Replace),
|
||||||
|
(e) => this.consumeData({content:this.textContent},CodeBlock.InsertMode.Replace)
|
||||||
|
);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
get name(){
|
||||||
|
return this.dataset.name;
|
||||||
|
}
|
||||||
|
set name(some){
|
||||||
|
this.dataset.name = some;
|
||||||
|
this.shadowRoot.querySelector("caption").textContent = some;
|
||||||
|
}
|
||||||
|
|
||||||
|
connectedCallback(){
|
||||||
|
if(!this.isConnected || this.initialized){
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if(this.dataset.matchlinks){
|
||||||
|
let parts = this.dataset.matchlinks.split(" -> ");
|
||||||
|
// this is kinda sketchy
|
||||||
|
if(parts.length === 2){
|
||||||
|
try{
|
||||||
|
this.highlighter.linkMatcher = new RegExp(parts[0],"g");
|
||||||
|
this.highlighter.linkGenerator = (a) => (parts[1].replace("%s",a));
|
||||||
|
}catch(e){
|
||||||
|
console.warn(e);
|
||||||
|
this.highlighter.linkMatcher = null;
|
||||||
|
this.highlighter.linkGenerator = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if(this.dataset.name){
|
||||||
|
this.name = this.dataset.name
|
||||||
|
}
|
||||||
|
this.initialized = true;
|
||||||
|
|
||||||
|
if(this.copyable){
|
||||||
|
CodeBlock.addClipboardListenerTo(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
if(this.highlighter.empty && this.dataset.highlight){
|
||||||
|
CodeBlock.addHighlighterTo(this);
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
this.determineAndLoadContent();
|
||||||
|
}
|
||||||
|
|
||||||
|
get copyable(){
|
||||||
|
return this.classList.contains("copy-able")
|
||||||
|
}
|
||||||
|
|
||||||
|
static addHighlighterTo(elem){
|
||||||
|
if(elem instanceof CodeBlock){
|
||||||
|
elem.highlighter.empty = false;
|
||||||
|
switch(elem.dataset.highlight){
|
||||||
|
case "css":
|
||||||
|
case "simple":
|
||||||
|
elem.highlighter.type = elem.dataset.highlight;
|
||||||
|
elem.highlighter.waiting = true;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
console.warn("invalid highlighter");
|
||||||
|
elem.determineAndLoadContent();
|
||||||
|
return
|
||||||
|
}
|
||||||
|
import("./code-block-highlighter.js")
|
||||||
|
.then(it => {
|
||||||
|
switch(elem.highlighter.type){
|
||||||
|
case "css":
|
||||||
|
elem.highlighter.fn = new it.CSSHighlighter();
|
||||||
|
break;
|
||||||
|
case "simple":
|
||||||
|
elem.highlighter.fn = new it.SimpleHighlighter();
|
||||||
|
}
|
||||||
|
elem.highlighter.ready = true;
|
||||||
|
elem.highlighter.waiting = false;
|
||||||
|
elem.determineAndLoadContent()
|
||||||
|
})
|
||||||
|
.catch(e => {
|
||||||
|
console.error(e);
|
||||||
|
elem.highlighter.failed = true;
|
||||||
|
ele.highlighter.waiting = false;
|
||||||
|
elem.determineAndLoadContent()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
clearContent(){
|
||||||
|
let innerbox = this.codeBox;
|
||||||
|
while(innerbox.children.length){
|
||||||
|
innerbox.children[0].remove();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static addClipboardListenerTo(aBlock){
|
||||||
|
let copyButton = aBlock.copyButton;
|
||||||
|
if(copyButton){
|
||||||
|
return
|
||||||
|
}
|
||||||
|
copyButton = aBlock.shadowRoot.querySelector(".copy-button");
|
||||||
|
aBlock.copyButton = copyButton;
|
||||||
|
|
||||||
|
copyButton.addEventListener("click",(e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
try{
|
||||||
|
let writing = navigator.clipboard.writeText(aBlock.value);
|
||||||
|
writing.then(()=>{
|
||||||
|
copyButton.classList.add("copy-success");
|
||||||
|
setTimeout(()=>copyButton.classList.remove("copy-success"),2000);
|
||||||
|
});
|
||||||
|
|
||||||
|
}catch(e){
|
||||||
|
console.error("couldn't copy content to clipboard");
|
||||||
|
}
|
||||||
|
|
||||||
|
});
|
||||||
|
aBlock.copyButton.removeAttribute("hidden");
|
||||||
|
}
|
||||||
|
static getSource(some){
|
||||||
|
return new Promise((res, rej) => {
|
||||||
|
if(some && typeof some === "string"){
|
||||||
|
CodeBlock.TryLoadFile(some)
|
||||||
|
.then(res)
|
||||||
|
.catch((e)=>{
|
||||||
|
console.error(e);
|
||||||
|
rej(e)
|
||||||
|
})
|
||||||
|
}else{
|
||||||
|
setTimeout(()=>rej("argument must be a string"));
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
async setSource(some){
|
||||||
|
this.clearContent();
|
||||||
|
let res = await CodeBlock.getSource(some);
|
||||||
|
if(res.ok){
|
||||||
|
this.consumeData(res,CodeBlock.InsertMode.Replace);
|
||||||
|
}
|
||||||
|
return { ok: res.ok }
|
||||||
|
}
|
||||||
|
|
||||||
|
get src(){
|
||||||
|
return this.getAttribute("src")
|
||||||
|
}
|
||||||
|
set src(some){
|
||||||
|
this.setSource(some);
|
||||||
|
}
|
||||||
|
|
||||||
|
lines(){
|
||||||
|
const lines = this.codeBox.querySelectorAll("tr");
|
||||||
|
const lineCount = lines.length;
|
||||||
|
let currentLine = 0;
|
||||||
|
return {
|
||||||
|
next: function() {
|
||||||
|
return currentLine < lineCount ? {
|
||||||
|
value: lines[currentLine++],
|
||||||
|
done: false
|
||||||
|
} : { done: true }
|
||||||
|
},
|
||||||
|
[Symbol.iterator]: function() { return this; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
getNamedSection(name){
|
||||||
|
let i = 0;
|
||||||
|
let sections = this.codeBox.children;
|
||||||
|
while(i < sections.length){
|
||||||
|
if(sections[i].dataset.name === name){
|
||||||
|
return sections[i]
|
||||||
|
}
|
||||||
|
i++
|
||||||
|
}
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
async consumeData(some,insertMode){
|
||||||
|
const re = /.*\r?\n/g;
|
||||||
|
if(typeof some.content !== "string"){
|
||||||
|
some.content = some.content.toString();
|
||||||
|
}
|
||||||
|
this.textContent = "";
|
||||||
|
|
||||||
|
let innerbox = this.codeBox;
|
||||||
|
|
||||||
|
if(innerbox.children.length === 1 && innerbox.firstChild.textContent === ""){
|
||||||
|
insertMode = CodeBlock.InsertMode.Replace;
|
||||||
|
}
|
||||||
|
const INSERT_MODE = insertMode || some.insertMode || CodeBlock.InsertMode.Append;
|
||||||
|
|
||||||
|
const aDiv = document.createElement("div");
|
||||||
|
if(some.name){
|
||||||
|
aDiv.setAttribute("data-name",some.name);
|
||||||
|
}
|
||||||
|
|
||||||
|
const hasHighlighter = this.highlighter.ready;
|
||||||
|
const LIMIT = 10000; // Arbitrary limit of 10k lines
|
||||||
|
|
||||||
|
if(hasHighlighter){
|
||||||
|
this.highlighter.fn.reset();
|
||||||
|
const payload = {
|
||||||
|
"match" : re.exec(some.content),
|
||||||
|
"linkMatcher": this.highlighter.linkMatcher,
|
||||||
|
"linkGenerator": this.highlighter.linkGenerator,
|
||||||
|
"linkChanged": true,
|
||||||
|
};
|
||||||
|
Object.defineProperty(payload,"content",{get:()=>payload.match[0]});
|
||||||
|
let counter = 0;
|
||||||
|
let lastIdx = 0;
|
||||||
|
|
||||||
|
while(payload.match && (counter++ < LIMIT)){
|
||||||
|
aDiv.appendChild(CodeBlock.RowFragment.cloneNode(true));
|
||||||
|
this.highlighter.fn.parse(
|
||||||
|
payload,
|
||||||
|
aDiv.lastElementChild.lastChild
|
||||||
|
);
|
||||||
|
payload.linkChanged = false;
|
||||||
|
lastIdx = (payload.match.index + payload.match[0].length);
|
||||||
|
payload.match = re.exec(some.content);
|
||||||
|
}
|
||||||
|
// Handle case where the content does not end with newline
|
||||||
|
aDiv.appendChild(CodeBlock.RowFragment.cloneNode(true));
|
||||||
|
if(lastIdx < some.content.length){
|
||||||
|
payload.match = [some.content.slice(lastIdx)];
|
||||||
|
this.highlighter.fn.parse(
|
||||||
|
payload,
|
||||||
|
aDiv.lastElementChild.lastChild
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}else{
|
||||||
|
let match = re.exec(some.content);
|
||||||
|
let counter = 0;
|
||||||
|
let lastIdx = 0;
|
||||||
|
|
||||||
|
while(match && (counter++ < LIMIT)){
|
||||||
|
aDiv.appendChild(CodeBlock.RowFragment.cloneNode(true));
|
||||||
|
aDiv.lastElementChild.lastChild.textContent = match[0];
|
||||||
|
lastIdx = (match.index + match[0].length);
|
||||||
|
match = re.exec(some.content);
|
||||||
|
}
|
||||||
|
// Handle case where the content does not end with newline
|
||||||
|
aDiv.appendChild(CodeBlock.RowFragment.cloneNode(true));
|
||||||
|
if(lastIdx < some.content.length){
|
||||||
|
aDiv.lastElementChild.lastChild.textContent = some.content.slice(lastIdx);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
switch(INSERT_MODE){
|
||||||
|
case CodeBlock.InsertMode.Prepend:
|
||||||
|
aDiv.lastElementChild.lastElementChild.append("\n")
|
||||||
|
innerbox.insertBefore(aDiv,innerbox.firstChild);
|
||||||
|
break;
|
||||||
|
case CodeBlock.InsertMode.Replace:
|
||||||
|
this.clearContent();
|
||||||
|
case CodeBlock.InsertMode.Append:
|
||||||
|
// Push the first "line" of new section to the last line of old content, if old content exists
|
||||||
|
if(innerbox.lastElementChild){
|
||||||
|
let first = aDiv.firstChild.lastElementChild;
|
||||||
|
let lastRowContent = this.lastContentLine;
|
||||||
|
for(let one of Array.from(first.childNodes)){
|
||||||
|
lastRowContent.appendChild(one)
|
||||||
|
}
|
||||||
|
aDiv.firstChild.remove();
|
||||||
|
}
|
||||||
|
if(aDiv.children.length){
|
||||||
|
innerbox.appendChild(aDiv);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case CodeBlock.InsertMode.AppendLines:
|
||||||
|
if(aDiv.children.length){
|
||||||
|
let lastRowContent = this.lastContentLine;
|
||||||
|
if(lastRowContent){
|
||||||
|
lastRowContent.append("\n");
|
||||||
|
}
|
||||||
|
innerbox.appendChild(aDiv);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
console.warn("unimplemented insertMode")
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
get lastContentLine(){
|
||||||
|
return this.codeBox.lastElementChild?.lastChild.lastChild;
|
||||||
|
}
|
||||||
|
|
||||||
|
get codeBox(){
|
||||||
|
return this.shadowRoot.querySelector("tbody");
|
||||||
|
}
|
||||||
|
get value(){
|
||||||
|
return this.codeBox.textContent
|
||||||
|
}
|
||||||
|
|
||||||
|
get InsertModes(){
|
||||||
|
return CodeBlock.InsertMode
|
||||||
|
}
|
||||||
|
|
||||||
|
set value(thing){
|
||||||
|
if(typeof thing === "string"){
|
||||||
|
this.consumeData({content:thing,insertMode:CodeBlock.InsertMode.Replace});
|
||||||
|
}else if("content" in thing){
|
||||||
|
this.consumeData(thing,CodeBlock.InsertMode.Replace);
|
||||||
|
}else{
|
||||||
|
this.consumeData({content: thing.toString(), insertMode: CodeBlock.InsertMode.Replace});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
insertContent(thing){
|
||||||
|
if(typeof thing === "string"){
|
||||||
|
this.consumeData({content:thing});
|
||||||
|
}else if("content" in thing){
|
||||||
|
this.consumeData(thing);
|
||||||
|
}else{
|
||||||
|
this.consumeData({content: thing.toString()});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static InsertMode = {
|
||||||
|
Replace : Symbol("replace"),
|
||||||
|
Append : Symbol("append"),
|
||||||
|
AppendLines : Symbol("appendlines"),
|
||||||
|
Prepend : Symbol("prepend")
|
||||||
|
}
|
||||||
|
|
||||||
|
static async TryLoadFile(name){
|
||||||
|
let response = await fetch(name);
|
||||||
|
if(response.ok){
|
||||||
|
let content = await response.text();
|
||||||
|
return { content: content, ok: true }
|
||||||
|
}else{
|
||||||
|
throw {error: "no response", ok: false }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static RowFragment = (() => {
|
||||||
|
let frag = new DocumentFragment();
|
||||||
|
let tr = frag.appendChild(document.createElement("tr"));
|
||||||
|
tr.appendChild(document.createElement("td"));
|
||||||
|
tr.firstChild.setAttribute("class","line-number");
|
||||||
|
tr.appendChild(document.createElement("td"));
|
||||||
|
return frag
|
||||||
|
})();
|
||||||
|
|
||||||
|
static Fragment(){
|
||||||
|
let frag = new DocumentFragment();
|
||||||
|
let link = document.createElement("link");
|
||||||
|
link.setAttribute("as","style");
|
||||||
|
link.setAttribute("type","text/css");
|
||||||
|
link.setAttribute("rel","preload prefetch stylesheet");
|
||||||
|
// Change the relative stylesheet address here if required
|
||||||
|
link.setAttribute("href","html_resources/code-block/code-block.css");
|
||||||
|
frag.appendChild(link);
|
||||||
|
let outerBox = frag.appendChild(document.createElement("div"));
|
||||||
|
outerBox.setAttribute("part","outerBox");
|
||||||
|
outerBox.className = "outerBox";
|
||||||
|
let copyButton = outerBox.appendChild(document.createElement("div"));
|
||||||
|
copyButton.setAttribute("part","copyButton");
|
||||||
|
copyButton.className = "copy-button";
|
||||||
|
copyButton.setAttribute("hidden",true);
|
||||||
|
copyButton.setAttribute("role","button");
|
||||||
|
let table = document.createElement("table");
|
||||||
|
let caption = table.appendChild(document.createElement("caption"));
|
||||||
|
caption.setAttribute("part","title");
|
||||||
|
let content = table.appendChild(document.createElement("tbody"));
|
||||||
|
content.setAttribute("part","content");
|
||||||
|
outerBox.appendChild(table)
|
||||||
|
|
||||||
|
return frag
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
customElements.define("code-block",CodeBlock);
|
|
@ -55,14 +55,16 @@ pre:empty{ display: none }
|
||||||
white-space:nowrap;
|
white-space:nowrap;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
text-overflow: ellipsis;
|
text-overflow: ellipsis;
|
||||||
|
display: flex;
|
||||||
}
|
}
|
||||||
.target > a{
|
.target > a{
|
||||||
display:inline-block;
|
display:inline-block;
|
||||||
width: 1.2em;
|
width: 1.2em;
|
||||||
height: 80%;
|
height: 1.2em;
|
||||||
background-image: url("ext.svg");
|
background-image: url("ext.svg");
|
||||||
background-repeat: no-repeat;
|
background-repeat: no-repeat;
|
||||||
background-size: contain;
|
background-size: contain;
|
||||||
|
flex-shrink: 0;
|
||||||
}
|
}
|
||||||
.target:hover{ background-color: rgb(80,80,90) }
|
.target:hover{ background-color: rgb(80,80,90) }
|
||||||
.target.selected{ background-color: rgb(80,80,120) }
|
.target.selected{ background-color: rgb(80,80,120) }
|
||||||
|
@ -92,20 +94,6 @@ pre > div{
|
||||||
.categoryList:not(.blurred)+.categoryList,
|
.categoryList:not(.blurred)+.categoryList,
|
||||||
.hidden{ display: none !important }
|
.hidden{ display: none !important }
|
||||||
|
|
||||||
.comment{ color: rgb(50,180,90) }
|
|
||||||
.selector{ color: lavenderblush }
|
|
||||||
.pseudo{ color: rgb(200,180,250) }
|
|
||||||
.id{ color: rgb(240, 148, 138) }
|
|
||||||
.class{ color: skyblue }
|
|
||||||
.attribute{ color: rgb(120,230,170) }
|
|
||||||
.atrule{ color: lime }
|
|
||||||
.atvalue{ color: lightblue }
|
|
||||||
.property{ color: palegoldenrod }
|
|
||||||
.value{ color: skyblue }
|
|
||||||
.curly{ color: magenta }
|
|
||||||
.function{ color: silver }
|
|
||||||
.important-tag{ color: orange }
|
|
||||||
|
|
||||||
@keyframes showDelayed{ from{ visibility: hidden } to{ visibility: visibile }}
|
@keyframes showDelayed{ from{ visibility: hidden } to{ visibility: visibile }}
|
||||||
|
|
||||||
@keyframes loadingBar{ from{ background-size: 0% } to{ background-size: 100% } }
|
@keyframes loadingBar{ from{ background-size: 0% } to{ background-size: 100% } }
|
||||||
|
|
|
@ -38,7 +38,7 @@ function fetchWithType(url){
|
||||||
.then((obj) => resolve({file:url,content:obj}));
|
.then((obj) => resolve({file:url,content:obj}));
|
||||||
}else{
|
}else{
|
||||||
response.text()
|
response.text()
|
||||||
.then((text) => resolve({file:url,content:text}))
|
.then((text) => resolve({file:url,content:text,name:url}))
|
||||||
|
|
||||||
}
|
}
|
||||||
},except => reject(except))
|
},except => reject(except))
|
||||||
|
@ -103,14 +103,6 @@ function getSecondaryCategories(list){
|
||||||
return ret
|
return ret
|
||||||
}
|
}
|
||||||
|
|
||||||
function clearCodeBlock(){
|
|
||||||
const pre = document.getElementById("previewBox");
|
|
||||||
for(let el of Array.from(pre.childNodes)){
|
|
||||||
pre.removeChild(el)
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
function showMatchingTargets(fileNames,setSelected = false){
|
function showMatchingTargets(fileNames,setSelected = false){
|
||||||
let bonus = 0;
|
let bonus = 0;
|
||||||
for(let c of Array.from(document.querySelectorAll(".target"))){
|
for(let c of Array.from(document.querySelectorAll(".target"))){
|
||||||
|
@ -152,13 +144,27 @@ function onCategoryClicked(categoryNode,isSecondary = false){
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
async function onTargetClicked(target,append = false){
|
async function onTargetClicked(target,append = false){
|
||||||
const text = typeof target === "string"
|
const text = typeof target === "string"
|
||||||
? target
|
? target
|
||||||
: getText(target);
|
: getText(target);
|
||||||
|
|
||||||
fetchWithType(`chrome/${text}`)
|
fetchWithType(`chrome/${text}`)
|
||||||
.then(obj => Highlighter.parse(obj,append))
|
.then(obj => {
|
||||||
|
let box = document.getElementById("previewBox");
|
||||||
|
if(append){
|
||||||
|
if(obj.file.endsWith("window_control_placeholder_support.css")){
|
||||||
|
obj.insertMode = box.InsertModes.Prepend;
|
||||||
|
box.insertContent(obj);
|
||||||
|
}else{
|
||||||
|
obj.insertMode = box.InsertModes.AppendLines;
|
||||||
|
box.insertContent(obj);
|
||||||
|
}
|
||||||
|
}else{
|
||||||
|
box.value = obj
|
||||||
|
}
|
||||||
|
})
|
||||||
.catch(e => console.log(e))
|
.catch(e => console.log(e))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -178,7 +184,8 @@ function onFilenameClicked(box,ctrlKey){
|
||||||
}else{
|
}else{
|
||||||
if(ctrlKey){
|
if(ctrlKey){
|
||||||
selectedTarget.deselect(box);
|
selectedTarget.deselect(box);
|
||||||
let preview = document.querySelector(`[data-filename="chrome/${box.getAttribute("title")}.css"]`);
|
let previewbox = document.getElementById("previewBox");
|
||||||
|
let preview = previewbox.getNamedSection(`chrome/${box.getAttribute("title")}.css`);
|
||||||
if(preview){
|
if(preview){
|
||||||
preview.remove();
|
preview.remove();
|
||||||
}
|
}
|
||||||
|
@ -309,260 +316,6 @@ function createCategories(){
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const Highlighter = new(function(){
|
|
||||||
|
|
||||||
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}
|
|
||||||
})();
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
let pointer = 0;
|
|
||||||
let token = "";
|
|
||||||
|
|
||||||
const selectorToClassMap = new Map([
|
|
||||||
[":","pseudo"],
|
|
||||||
["#","id"],
|
|
||||||
[".","class"],
|
|
||||||
["[","attribute"]]);
|
|
||||||
|
|
||||||
this.parse = function(info,appendMode){
|
|
||||||
|
|
||||||
const targetNode = document.getElementById("previewBox");
|
|
||||||
|
|
||||||
!appendMode && clearCodeBlock();
|
|
||||||
|
|
||||||
let node = document.createElement("div");
|
|
||||||
node.setAttribute("data-filename",info.file);
|
|
||||||
|
|
||||||
function createNewRuleset(){
|
|
||||||
let ruleset = document.createElement("span");
|
|
||||||
ruleset.className = "ruleset";
|
|
||||||
node.appendChild(ruleset);
|
|
||||||
return ruleset
|
|
||||||
}
|
|
||||||
|
|
||||||
let rulesetUnderConstruction = createNewRuleset();
|
|
||||||
|
|
||||||
function createElementFromToken(type,c){
|
|
||||||
if(token.length === 0 && !c){
|
|
||||||
return
|
|
||||||
}
|
|
||||||
let n = document.createElement("span");
|
|
||||||
|
|
||||||
switch(type){
|
|
||||||
case "selector":
|
|
||||||
// This isn't exactly correct, but it works because parser treats \r\n sequences that follow a closed comment as "selector"
|
|
||||||
rulesetUnderConstruction = createNewRuleset();
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
break
|
|
||||||
case "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;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case "value":
|
|
||||||
let startImportant = token.indexOf("!");
|
|
||||||
if(startImportant === -1){
|
|
||||||
n.textContent = c || token;
|
|
||||||
}else{
|
|
||||||
n.textContent = token.substr(0,startImportant);
|
|
||||||
let importantTag = document.createElement("span");
|
|
||||||
importantTag.className = "important-tag";
|
|
||||||
importantTag.textContent = "!important";
|
|
||||||
n.appendChild(importantTag);
|
|
||||||
if(token.length > (9 + startImportant)){
|
|
||||||
n.append(";")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case "function":
|
|
||||||
n.textContent = c || token.slice(0,-1);
|
|
||||||
break
|
|
||||||
default:
|
|
||||||
n.textContent = c || token;
|
|
||||||
}
|
|
||||||
|
|
||||||
n.className = (`token ${type}`);
|
|
||||||
token = "";
|
|
||||||
rulesetUnderConstruction.appendChild(n);
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
let c;
|
|
||||||
let functionValueLevel = 0;
|
|
||||||
let curly = false;
|
|
||||||
|
|
||||||
while(pointer < info.content.length){
|
|
||||||
c = info.content[pointer];
|
|
||||||
|
|
||||||
const currentState = state.now();
|
|
||||||
curly = currentState != 2 && (c === "{" || c === "}");
|
|
||||||
if(!curly){
|
|
||||||
token+=c;
|
|
||||||
}
|
|
||||||
switch(currentState){
|
|
||||||
|
|
||||||
case 0:
|
|
||||||
switch(c){
|
|
||||||
case "/":
|
|
||||||
if(info.content[pointer+1] === "*"){
|
|
||||||
state.set(2);
|
|
||||||
if(token.length > 1){
|
|
||||||
token = token.slice(0,-1);
|
|
||||||
createElementFromToken("selector");
|
|
||||||
token += "/"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case "{":
|
|
||||||
state.set(3);
|
|
||||||
createElementFromToken("selector");
|
|
||||||
break;
|
|
||||||
case "}":
|
|
||||||
createElementFromToken("text");
|
|
||||||
break;
|
|
||||||
case "@":
|
|
||||||
state.set(5);
|
|
||||||
}
|
|
||||||
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 2:
|
|
||||||
switch(c){
|
|
||||||
case "*":
|
|
||||||
if(info.content[pointer+1] === "/"){
|
|
||||||
token += "/";
|
|
||||||
pointer++;
|
|
||||||
state.set(state.previous());
|
|
||||||
createElementFromToken("comment");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 3:
|
|
||||||
switch(c){
|
|
||||||
case "/":
|
|
||||||
if(info.content[pointer+1] === "*"){
|
|
||||||
state.set(2);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case ":":
|
|
||||||
createElementFromToken("property");
|
|
||||||
state.set(4);
|
|
||||||
break;
|
|
||||||
case "}":
|
|
||||||
createElementFromToken("text");
|
|
||||||
state.set(0);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case 4:
|
|
||||||
switch(c){
|
|
||||||
case ";":
|
|
||||||
createElementFromToken("value");
|
|
||||||
state.set(3);
|
|
||||||
break;
|
|
||||||
case "}":
|
|
||||||
createElementFromToken("value");
|
|
||||||
state.set(0);
|
|
||||||
break;
|
|
||||||
case "(":
|
|
||||||
createElementFromToken("value");
|
|
||||||
functionValueLevel++;
|
|
||||||
state.set(7);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case 5:
|
|
||||||
switch(c){
|
|
||||||
case " ":
|
|
||||||
createElementFromToken("atrule");
|
|
||||||
state.set(6);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case 6:
|
|
||||||
switch(c){
|
|
||||||
case ";":
|
|
||||||
case "{":
|
|
||||||
createElementFromToken("atvalue");
|
|
||||||
state.set(0);
|
|
||||||
}
|
|
||||||
break
|
|
||||||
case 7:
|
|
||||||
switch(c){
|
|
||||||
case ")":
|
|
||||||
functionValueLevel--;
|
|
||||||
if(functionValueLevel === 0){
|
|
||||||
createElementFromToken("function");
|
|
||||||
token = ")";
|
|
||||||
state.set(4);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case "}":
|
|
||||||
functionValueLevel = 0;
|
|
||||||
state.set(0)
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
false
|
|
||||||
}
|
|
||||||
|
|
||||||
curly && createElementFromToken("curly",c);
|
|
||||||
|
|
||||||
pointer++
|
|
||||||
}
|
|
||||||
createElementFromToken("text");
|
|
||||||
token = "";
|
|
||||||
state.set(0);
|
|
||||||
pointer = 0;
|
|
||||||
|
|
||||||
if(info.file.endsWith("support.css")){
|
|
||||||
targetNode.prepend(node);
|
|
||||||
}else{
|
|
||||||
targetNode.appendChild(node);
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
return this
|
|
||||||
})();
|
|
||||||
|
|
||||||
async function handleSearchQuery(){
|
async function handleSearchQuery(){
|
||||||
let params = (new URL(document.location)).searchParams;
|
let params = (new URL(document.location)).searchParams;
|
||||||
let param = params.get("tag");
|
let param = params.get("tag");
|
||||||
|
@ -579,7 +332,7 @@ async function handleSearchQuery(){
|
||||||
param = params.get("file");
|
param = params.get("file");
|
||||||
if(param){
|
if(param){
|
||||||
let files = param.split(",").filter(a => DB.keys.includes(a));
|
let files = param.split(",").filter(a => DB.keys.includes(a));
|
||||||
|
let box = document.getElementById("previewBox");
|
||||||
if(files.length === 0 ){
|
if(files.length === 0 ){
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -589,7 +342,10 @@ async function handleSearchQuery(){
|
||||||
Promise.all(promises)
|
Promise.all(promises)
|
||||||
.then(responses => {
|
.then(responses => {
|
||||||
showMatchingTargets(files,true);
|
showMatchingTargets(files,true);
|
||||||
responses.forEach(Highlighter.parse)
|
responses.forEach((res)=>{
|
||||||
|
res.insertMode = res.file.endsWith("window_control_placeholder_support.css") ? box.InsertModes.Prepend : box.InsertModes.AppendLines;
|
||||||
|
box.insertContent(res)
|
||||||
|
})
|
||||||
});
|
});
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -611,8 +367,8 @@ document.onreadystatechange = (function () {
|
||||||
|
|
||||||
if (document.readyState === "complete") {
|
if (document.readyState === "complete") {
|
||||||
function linkClicked(ev){
|
function linkClicked(ev){
|
||||||
if(ev.target instanceof HTMLAnchorElement){
|
if(ev.originalTarget instanceof HTMLAnchorElement){
|
||||||
let ref = ev.target.href;
|
let ref = ev.originalTarget.href;
|
||||||
if(!ref){
|
if(!ref){
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,7 +4,8 @@
|
||||||
<meta name="viewport" content="width=device-width"/>
|
<meta name="viewport" content="width=device-width"/>
|
||||||
<meta charset="UTF-8">
|
<meta charset="UTF-8">
|
||||||
<title>firefox-csshacks tag browser</title>
|
<title>firefox-csshacks tag browser</title>
|
||||||
<script type="text/javascript" src="html_resources/selector.js"></script>
|
<script type="module" src="html_resources/selector.js"></script>
|
||||||
|
<script type="module" src="html_resources/code-block/code-block.js"></script>
|
||||||
<link href="html_resources/main.css" rel="stylesheet" type="text/css"/>
|
<link href="html_resources/main.css" rel="stylesheet" type="text/css"/>
|
||||||
<link rel="icon" href="html_resources/favicon.svg" type="image/svg+xml">
|
<link rel="icon" href="html_resources/favicon.svg" type="image/svg+xml">
|
||||||
</head>
|
</head>
|
||||||
|
@ -21,7 +22,7 @@
|
||||||
<div id="targets">
|
<div id="targets">
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<pre id="previewBox"></pre>
|
<code-block data-highlight="css" data-matchlinks="[\w-\.]+\.css -> ./chrome/%s" id="previewBox" class="copy-able"></code-block>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
Loading…
Reference in a new issue