Init from Manwell

This commit is contained in:
nazrin 2024-12-25 23:04:55 +00:00
commit 4ec6d1e14f
25 changed files with 1369 additions and 0 deletions

18
js/ace/ace.js Normal file

File diff suppressed because one or more lines are too long

9
js/ace/mode-css.js Normal file

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

9
js/ace/theme-monokai.js Normal file
View file

@ -0,0 +1,9 @@
define("ace/theme/monokai",["require","exports","module","ace/lib/dom"],function(e,t,n){t.isDark=!0,t.cssClass="ace-monokai",t.cssText=".ace-monokai .ace_gutter {background: #2F3129;color: #8F908A}.ace-monokai .ace_print-margin {width: 1px;background: #555651}.ace-monokai {background-color: #272822;color: #F8F8F2}.ace-monokai .ace_cursor {color: #F8F8F0}.ace-monokai .ace_marker-layer .ace_selection {background: #49483E}.ace-monokai.ace_multiselect .ace_selection.ace_start {box-shadow: 0 0 3px 0px #272822;}.ace-monokai .ace_marker-layer .ace_step {background: rgb(102, 82, 0)}.ace-monokai .ace_marker-layer .ace_bracket {margin: -1px 0 0 -1px;border: 1px solid #49483E}.ace-monokai .ace_marker-layer .ace_active-line {background: #202020}.ace-monokai .ace_gutter-active-line {background-color: #272727}.ace-monokai .ace_marker-layer .ace_selected-word {border: 1px solid #49483E}.ace-monokai .ace_invisible {color: #52524d}.ace-monokai .ace_entity.ace_name.ace_tag,.ace-monokai .ace_keyword,.ace-monokai .ace_meta.ace_tag,.ace-monokai .ace_storage {color: #F92672}.ace-monokai .ace_punctuation,.ace-monokai .ace_punctuation.ace_tag {color: #fff}.ace-monokai .ace_constant.ace_character,.ace-monokai .ace_constant.ace_language,.ace-monokai .ace_constant.ace_numeric,.ace-monokai .ace_constant.ace_other {color: #AE81FF}.ace-monokai .ace_invalid {color: #F8F8F0;background-color: #F92672}.ace-monokai .ace_invalid.ace_deprecated {color: #F8F8F0;background-color: #AE81FF}.ace-monokai .ace_support.ace_constant,.ace-monokai .ace_support.ace_function {color: #66D9EF}.ace-monokai .ace_fold {background-color: #A6E22E;border-color: #F8F8F2}.ace-monokai .ace_storage.ace_type,.ace-monokai .ace_support.ace_class,.ace-monokai .ace_support.ace_type {font-style: italic;color: #66D9EF}.ace-monokai .ace_entity.ace_name.ace_function,.ace-monokai .ace_entity.ace_other,.ace-monokai .ace_entity.ace_other.ace_attribute-name,.ace-monokai .ace_variable {color: #A6E22E}.ace-monokai .ace_variable.ace_parameter {font-style: italic;color: #FD971F}.ace-monokai .ace_string {color: #E6DB74}.ace-monokai .ace_comment {color: #75715E}.ace-monokai .ace_indent-guide {background: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAACCAYAAACZgbYnAAAAEklEQVQImWPQ0FD0ZXBzd/wPAAjVAoxeSgNeAAAAAElFTkSuQmCC) right repeat-y}";var r=e("../lib/dom");r.importCssString(t.cssText,t.cssClass)});
(function() {
window.require(["ace/theme/monokai"], function(m) {
if (typeof module == "object" && typeof exports == "object" && module) {
module.exports = m;
}
});
})();

1
js/ace/worker-css.js Normal file

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

167
js/editor.js Normal file
View file

@ -0,0 +1,167 @@
let fileId = window.location.hash.substr(1);
let file = {};
let enableInput = document.getElementById("enable-input");
let nameInput = document.getElementById("name-input");
let linksBtn = document.getElementById("links-btn");
let saveBtn = document.getElementById("save-btn");
let editor = ace.edit("editor");
editor.setTheme("ace/theme/monokai");
File.load(fileId).then((f) => {
let info = (file = f).info;
enableInput.parentNode.classList.add("no-transition");
enableInput.checked = info.enabled;
setTimeout(()=>{enableInput.parentNode.classList.remove("no-transition")}, 0);
nameInput.value = info.name;
editor.setValue(info.content||"", -1);
if(info.type == "JS") {
editor.session.setMode("ace/mode/javascript");
} else {
editor.session.setMode("ace/mode/css");
}
if(info.links) {
info.links.forEach((link) => {
createLinkEntry(link);
});
createLinkEntry();
}
f.onChange = function(info){
enableInput.checked = info.enabled;
nameInput.value = info.name;
}
enableInput.addEventListener("input", editorSetEdited);
nameInput.addEventListener("input", editorSetEdited);
editor.session.on("change", editorSetEdited);
});
function editorSetEdited() {
saveBtn.classList.add("edited");
}
/* export */
let downloadLink = document.createElement("a");
downloadLink.style.display = "none";
document.body.appendChild(downloadLink);
let downloadBtn = document.getElementById("download-btn");
downloadBtn.addEventListener("click", () => {
save();
let info = file.info;
delete info.id;
downloadLink.href = "data:text/json;charset=utf-8," +
encodeURIComponent(JSON.stringify(info));
downloadLink.download = info.name + ".json";
downloadLink.click();
});
/* links */
let popupActive = false;
let popup = document.getElementById("links-popup");
let linksWrapper = document.getElementById("links-wrapper");
linksBtn.addEventListener("click", (e) => {
e.stopPropagation();
if(popupActive) {
popup.style.display = "none";
popupActive = false;
} else {
popup.style.display = "flex";
popupActive = true;
}
});
function linksClose() {
if(popupActive) {
popup.style.display = "none";
popupActive = false;
save();
}
}
document.getElementById("close-links-btn").
addEventListener("click", linksClose);
document.addEventListener("mousedown", linksClose);
window.addEventListener("keydown", (e) => {
if(e.key === "Escape") { linksClose(); }
});
popup.addEventListener("mousedown", (e) => {
e.stopPropagation();
});
function createLinkEntry(link = "") {
let empty = link == "";
let entry = document.createElement("div");
entry.className = "link-entry";
entry.innerHTML = `
<input type="text" value="${link}" placeholder="Add a target website"/>
<svg viewBox="0 0 24 24" title="Delete entry">
<path d="M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12 19 6.41z"/>
</svg>
`;
let entryInput = entry.querySelector("input");
entryInput.addEventListener("input", () => {
if(empty) {
entrySvg.style.display = "";
empty = false;
createLinkEntry();
}
editorSetEdited();
});
entryInput.addEventListener("blur", () => {
if(entryInput.value == "" && !empty) {
entry.remove();
}
});
let entrySvg = entry.querySelector("svg");
if(empty) { entrySvg.style.display = "none"; }
entrySvg.addEventListener("click", () => {
if(!empty) {
entry.remove();
editorSetEdited();
}
});
linksWrapper.appendChild(entry);
}
/* save */
enableInput.addEventListener("change", save);
function save() {
let info = file.info;
info.enabled = enableInput.checked;
info.name = nameInput.value;
info.content = editor.getValue();
let links = [];
let inputs = linksWrapper.querySelectorAll("input");
if(inputs) {
inputs.forEach((input) => {
if(input.value != "") {
links.push(input.value);
}
});
}
info.links = links;
file.save();
saveBtn.classList.remove("edited");
}
saveBtn.addEventListener("click", save);
window.addEventListener("keydown", (e) => {
if(e.ctrlKey && e.key=="s") {
e.preventDefault();
e.stopPropagation();
save();
}
}, true);

94
js/file.js Normal file
View file

@ -0,0 +1,94 @@
function File(id){
let _this = this;
if(!id) id = File.uuid();
let info = this.info = {id, enabled:true};
this.changeInfo = function(i){
for(let _i in i){
info[_i] = i[_i];
}
};
this.save = function(){
return new Promise(function(succ, err){
Storage.save({[File.PREFIX+info.id]:info}).then(_ => {
File.saveIndex(info.id, info.enabled).then(succ);
});
});
};
this.onChange = function(info){}; //Placeholder function
Storage.onChange(File.PREFIX+id, newInfo => {
_this.changeInfo(newInfo);
_this.onChange(newInfo);
});
}
File.PREFIX = "FILE-";
File.saveIndex = function(id, enabled){
return new Promise((succ, err) => {
File.loadIndex().then((index) => {
index[id] = enabled;
Storage.save({"INDEX":index}).then(succ);
});
});
};
File.loadIndex = function(){
return new Promise((succ, err) => {
Storage.load(["INDEX"]).then((result) => {
let index = result["INDEX"] || {};
succ(index);
});
});
};
File.uuid = function(){
return ([1e7]+-1e3+-4e3+-8e3+-1e11).replace(/[018]/g, c =>
(c ^ crypto.getRandomValues(new Uint8Array(1))[0] & 15 >> c / 4).toString(16)
)
};
File.remove = function(id){
return new Promise((succ, err) => {
File.loadIndex().then((index) => {
delete index[id];
Storage.save({"INDEX":index}).then(_ => {
Storage.remove([File.PREFIX+id]).then(succ);
});
});
});
};
File.load = function(id){
return new Promise((succ, err) => {
let innerID = File.PREFIX+id;
Storage.load([innerID]).then(result => {
if(!result[innerID]) err();
let file = new File(id);
file.changeInfo(result[innerID]);
succ(file);
});
});
};
File.loadAll = function(){
return new Promise((succ, err) => {
let files = [];
File.loadIndex().then((index) => {
for(let id in index){
files.push(File.load(id));
}
Promise.all(files).then((files) => {
succ(files);
});
});
});
};
File.loadEnabled = function(){
return new Promise((succ, err) => {
let files = [];
File.loadIndex().then((index) => {
for(let id in index){
if(index[id]) files.push(File.load(id));
}
Promise.all(files).then((files) => {
succ(files);
})
});
});
};

149
js/index.js Normal file
View file

@ -0,0 +1,149 @@
function createFileEntry(fileObject, element){
let info = fileObject.info;
let file = document.createElement("div");
file.className = "file";
file.innerHTML = `
<label class="check" title="Enable or disable file"><input type="checkbox" ${info.enabled?"checked='true'":""}><span></span></label>
<span class="file-name">${info.name}</span>
<span class="file-type">${info.type}</span>
<div class="file-icons">
<span class="file-icon file-edit" title="Edit file">
<svg viewBox="0 0 48 48">
<path d="M6 34.5v7.5h7.5l22.13-22.13-7.5-7.5-22.13 22.13zm35.41-20.41c.78-.78.78-2.05 0-2.83l-4.67-4.67c-.78-.78-2.05-.78-2.83 0l-3.66 3.66 7.5 7.5 3.66-3.66z"/>
</svg>
</span>
<span class="file-icon file-delete" title="Delete file">
<svg viewBox="0 0 48 48">
<path d="M12 38c0 2.21 1.79 4 4 4h16c2.21 0 4-1.79 4-4v-24h-24v24zm26-30h-7l-2-2h-10l-2 2h-7v4h28v-4z"/>
</svg>
</span>
</div>
`;
let fileName = file.querySelector(".file-name");
let fileType = file.querySelector(".file-type");
let fileEnable = file.querySelector("input");
fileEnable.addEventListener("change", function(){
info.enabled = this.checked;
fileObject.save();
});
let fileEdit = file.querySelector(".file-icon.file-edit");
fileEdit.addEventListener("click", edit);
let fileDelete = file.querySelector(".file-icon.file-delete");
fileDelete.addEventListener("click", function(){
if(fileDelete.classList.contains("icon-confirm")){
File.remove(info.id).then(function(){
file.remove();
});
}else{
fileDelete.classList.add("icon-confirm");
setTimeout(function(){
fileDelete.classList.remove("icon-confirm");
}, 1000);
}
});
file.addEventListener("click", e => {
if(e.target.className=="file") edit();
});
element.appendChild(file);
fileObject.onChange = function(info){
fileEnable.checked = info.enabled;
fileName.textContent = info.name;
}
function edit(){
chrome.tabs.create({url: chrome.runtime.getURL("editor.html#"+info.id)});
}
}
let filesElement = document.getElementById("files");
File.loadAll().then(function(files){
files = files.sort((a, b) => {
if(a.info.name < b.info.name) return -1;
if(a.info.name > b.info.name) return 1;
return 0;
});
for(let i=0;i<files.length;i++){
createFileEntry(files[i], filesElement);
}
});
let newFileJS = document.getElementById("new-js");
newFileJS.addEventListener("click", () => {
let f = new File();
f.changeInfo({type: "JS", name: "New Script"});
f.save().then(() => {
createFileEntry(f, filesElement);
});
});
let newFileCSS = document.getElementById("new-css");
newFileCSS.addEventListener("click", () => {
let f = new File();
f.changeInfo({type: "CSS", name: "New Stylesheet"});
f.save().then(() => {
createFileEntry(f, filesElement);
});
});
/* file input */
function newFromFile(inFile) {
let info = {};
if(inFile.type == "text/javascript") {
info.type = "JS";
info.name = inFile.name.match(/(.*)\..*$/);
} else if(inFile.type == "text/css") {
info.type = "CSS";
info.name = inFile.name.match(/(.*)\..*$/);
} else if(inFile.type == "application/json") {
} else {
alert("Unsupported file type");
return;
}
let reader = new FileReader();
reader.addEventListener("load", () => {
if(inFile.type == "application/json") {
try {
info = JSON.parse(reader.result);
} catch(e) { alert("The uploaded json file is not valid"); }
} else {
info.content = reader.result;
}
let f = new File();
f.changeInfo(info);
f.save().then(() => {
createFileEntry(f, filesElement);
});
});
reader.readAsText(inFile);
}
document.body.addEventListener("dragover", (e) => {
e.preventDefault();
e.stopPropagation();
document.body.classList.add("drag-over");
});
document.body.addEventListener("dragleave", (e) => {
e.preventDefault();
e.stopPropagation();
document.body.classList.remove("drag-over");
});
document.body.addEventListener("drop", (e) => {
e.preventDefault();
e.stopPropagation();
document.body.classList.remove("drag-over");
newFromFile(e.dataTransfer.files[0]);
});
let inputFile = document.createElement("input");
inputFile.type = "file";
inputFile.addEventListener("change", () => {
newFromFile(inputFile.files[0]);
});
let newFileUp = document.getElementById("new-up");
newFileUp.addEventListener("click", () => { inputFile.click(); });

81
js/popup.js Normal file
View file

@ -0,0 +1,81 @@
let addNew = document.getElementById("add-new");
let section = document.getElementById("files");
let manage = document.getElementById("manage");
manage.addEventListener("click", e => {
chrome.tabs.create({ url: chrome.runtime.getURL("index.html") });
window.close();
});
chrome.tabs.query({active: true, currentWindow: true}, tabs => {
let tabID = tabs[0].id;
// Query file list
chrome.runtime.sendMessage({tabID, action: "get-files"}, files => {
if(!files || !files.length) return;
files.forEach(info => {
let file = new File(info.id);
file.changeInfo(info);
info = file.info;
let span = document.createElement("span");
span.className = "line btn";
span.addEventListener("click", e => {
if(e.target.classList.contains("btn")){
chrome.tabs.create({ url: chrome.runtime.getURL("editor.html#"+info.id)});
window.close();
}
});
let enable = document.createElement("label");
enable.className = "check small";
enable.innerHTML = `
<input id="enable-input" type="checkbox">
<span><span></span></span>
`;
let input = enable.children[0];
input.checked = info.enabled;
input.addEventListener("change", e => {
file.info.enabled = input.checked;
file.save();
});
span.appendChild(enable);
let name = document.createElement("span");
name.className = "line-item";
name.textContent = info.name;
span.appendChild(name);
let type = document.createElement("span");
type.className = "line-item";
type.textContent = info.type;
span.appendChild(type);
file.onChange = function(info){
input.checked = info.enabled;
name.textContent = info.name;
}
section.appendChild(span);
});
});
// Query URL
addNew.style.display = "none";
chrome.runtime.sendMessage({tabID, action: "get-url"}, url => {
if(!url) return;
addNew.style.display = "";
let addJS = document.getElementById("add-js");
let addCSS = document.getElementById("add-css");
addJS.addEventListener("click", e =>{add("JS")});
addCSS.addEventListener("click", e =>{add("CSS")});
function add(type){
let f = new File();
f.changeInfo({type, name: "New "+(type=="CSS"?"Stylesheet":"Script"), links:[url]});
f.save().then(_ => {
chrome.tabs.create({ url: chrome.runtime.getURL("editor.html#"+f.info.id)});
window.close();
});
}
});
});

34
js/storage.js Normal file
View file

@ -0,0 +1,34 @@
let Storage = {};
Storage.save = function(object){
return new Promise(function(succ, err){
chrome.storage.sync.set(object, succ);
});
}
Storage.load = function(objects){
return new Promise(function(succ, err){
chrome.storage.sync.get(objects, succ);
});
}
Storage.remove = function(objects){
return new Promise(function(succ, err){
chrome.storage.sync.remove(objects, succ);
});
}
Storage.callbacks = {};
Storage.onChange = function(key, fn){
if(!Storage.callbacks[key]) Storage.callbacks[key] = [];
Storage.callbacks[key].push(fn);
}
Storage.emit = function(key, newValue){
if(!Storage.callbacks[key]) return;
Storage.callbacks[key].forEach(x => {
x(newValue);
});
}
chrome.storage.onChanged.addListener(function(objects, area){
if(area!="sync") return;
for(let id in objects){
let newValue = objects[id].newValue;
if(newValue) { Storage.emit(id, newValue); }
}
});