my-portfolio/src/editor/js/editor.js
rodude123 a0567a25f5 Created individual categories page
Signed-off-by: rodude123 <rodude123@gmail.com>
2023-10-18 23:58:21 +01:00

1343 lines
52 KiB
JavaScript

let dateOptions = {month: 'short', year: 'numeric'};
let textareaLoaded = false;
let editors = {};
let posts = null;
document.addEventListener('DOMContentLoaded', () =>
{
// check if the userData is logged in, if not redirect to log in
fetch('/api/user/isLoggedIn').then(res =>
{
if (!res.ok)
{
window.location.href = './';
}
});
document.querySelector("#dateFromE").max = new Date().toISOString().split("T")[0];
document.querySelector("#dateFromW").max = new Date().toISOString().split("T")[0];
fetch("/api/timelineData/edu").then(res => res.json().then(json =>
{
if (res.ok)
{
for (let i = 0; i < json.length; i++)
{
addEduData(json[i].ID, json[i].startPeriod, json[i].endPeriod, json[i].grade, json[i].course);
}
return;
}
document.querySelector("#edu").innerHTML = "No education data found";
}));
fetch("/api/timelineData/work").then(res => res.json().then(json =>
{
if (res.ok)
{
for (let i = 0; i < json.length; i++)
{
let endPeriod = json[i].endPeriod === "0000-00-00" ? "Present" : json[i].endPeriod;
addWorkData(json[i].ID, json[i].startPeriod, endPeriod, json[i].companyName, json[i].area, json[i].title);
}
return;
}
document.querySelector("#edu").innerHTML = "No education data found";
}));
fetch("/api/projectData").then(res => res.json().then(json =>
{
if (res.ok)
{
json.forEach(item =>
{
addProject(item["ID"], (item["isMainProject"] === 1 ? "true" : "false"), (item["imgLocation"] === "") ? "../imgs/placeholder.png" : item["imgLocation"], item["title"], item["information"], item["projectLink"], item["gitLink"]);
})
return;
}
document.querySelector("#projList").innerHTML = "No project data found";
}))
fetch("/api/blog/post").then(res => res.json().then(json =>
{
if (res.ok)
{
posts = json;
json.forEach(item =>
{
addPostInfo(item["ID"], item["title"], item["dateCreated"], item["dateModified"]);
})
}
}));
// CKEditor stuff
createEditors("CKEditorAddPost", "CKEditorEditPost");
});
document.querySelector("body").addEventListener("click", () =>
{
if (textareaLoaded)
{
return;
}
const tx = document.querySelectorAll("main.editor textarea");
for (let i = 0; i < tx.length; i++)
{
console.log("height: " + tx[i].scrollHeight + "px");
tx[i].setAttribute("style", "height:" + (tx[i].scrollHeight) + "px;overflow-y:hidden;");
tx[i].oninput = e =>
{
e.target.style.height = "0";
e.target.style.height = (e.target.scrollHeight + 15) + "px";
};
}
textareaLoaded = true;
});
document.querySelector("#navOpen").addEventListener("click", e =>
{
document.querySelector("nav.sideNav").style.removeProperty("width");
document.querySelector("main.editor").style.removeProperty("margin-left");
e.target.style.removeProperty("visibility");
});
document.querySelector("#navClose").addEventListener("click", () =>
{
document.querySelector("nav.sideNav").style.width = "0";
document.querySelector("main.editor").style.marginLeft = "0";
document.querySelector("#navOpen").style.visibility = "visible";
});
document.querySelector("#addEdu").addEventListener("submit", e =>
{
e.preventDefault();
let data = new FormData();
data.append("dateFrom", document.querySelector("#dateFromE").value);
data.append("dateTo", document.querySelector("#dateToE").value);
data.append("grade", document.querySelector("#grade").value);
data.append("course", document.querySelector("#courseTitle").value);
fetch("/api/timelineData/edu", {
method: "POST",
body: data,
headers: {
"Authorization": "Bearer " + localStorage.getItem("token")
}
}).then(res => res.json().then(json =>
{
if (res.ok)
{
addEduData(json.ID, data.get("dateFrom"), data.get("dateTo"), data.get("grade"), data.get("course"), true);
document.querySelector("#addEdu").reset();
return;
}
if (res.status === 401)
{
window.location.href = "./";
return;
}
showErrorMessage(json.error, "edu");
}));
});
document.querySelector("#addWork").addEventListener("submit", e =>
{
e.preventDefault();
let data = new FormData();
data.append("dateFrom", document.querySelector("#dateFromW").value);
data.append("dateTo", document.querySelector("#dateToW").value);
data.append("companyName", document.querySelector("#company").value);
data.append("area", document.querySelector("#area").value);
data.append("title", document.querySelector("#jobTitle").value);
fetch("/api/timelineData/work", {
method: "POST",
body: data,
headers: {
"Authorization": "Bearer " + localStorage.getItem("token")
}
}).then(res => res.json().then(json =>
{
if (res.ok)
{
let endPeriod = data.get("dateTo") === "" ? "Present" : data.get("dateTo ");
addWorkData(json.ID, data.get("dateFrom"), endPeriod, data.get("companyName"), data.get("area"), data.get("title"), true);
document.querySelector("#addWork").reset();
return;
}
if (res.status === 401)
{
window.location.href = "./";
return;
}
showErrorMessage(json.error, "work");
}));
});
document.querySelector("#addProj").addEventListener("submit", e =>
{
e.preventDefault();
let data = new FormData();
data.append("title", document.querySelector("#projTitle").value);
data.append("isMainProject", document.querySelector("#isMainProject").checked ? "true" : "false");
data.append("information", document.querySelector("#projInfo").value);
data.append("projectLink", (document.querySelector("#projLink").value) ? document.querySelector("#projLink").value : "N/A");
data.append("gitLink", document.querySelector("#gitLink").value);
let imgData = new FormData();
imgData.append("img", document.querySelector("#projImg").files[0]);
let newProjectID = 0;
fetch("/api/projectData", {
method: "POST",
body: data,
headers: {
"Authorization": "Bearer " + localStorage.getItem("token")
}
}).then(res => res.json().then(newProjectData =>
{
if (res.ok)
{
if (imgData.get("img") === "undefined")
{
addProject(newProjectData.ID, data.get("isMainProject"),"../imgs/placeholder.png", data.get("title"), data.get("information"), data.get("projectLink"), data.get("gitLink"));
document.querySelector("#addProj").reset();
return;
}
newProjectID = newProjectData.ID;
return fetch("/api/projectImage/" + newProjectData.ID, {
method: "POST",
body: imgData,
headers: {
"Authorization": "Bearer " + localStorage.getItem("token")
}
});
}
if (res.status === 401)
{
window.location.href = "./";
return;
}
showErrorMessage(newProjectData.error, "proj");
}).then(res => res.json().then(newProjectImage =>
{
if (res.ok)
{
addProject(newProjectID, data.get("isMainProject"), newProjectImage.imgLocation, data.get("title"), data.get("information"), data.get("projectLink"), data.get("gitLink"));
document.querySelector("#addProj").reset();
return;
}
if (res.status === 401)
{
window.location.href = "./";
return;
}
showErrorMessage(newProjectImage.error, "proj");
})));
});
document.querySelector("#addPostForm").addEventListener("submit", e =>
{
e.preventDefault();
if (editors["CKEditorAddPost"].getData() === "")
{
showErrorMessage("Post body cannot be empty", "addPost");
return;
}
let data = new FormData();
data.append("title", document.querySelector("#postTitle").value);
data.append("featured", document.querySelector("#isFeatured").checked ? "1" : "0");
data.append("abstract", document.querySelector("#postAbstract").value);
data.append("body", editors["CKEditorAddPost"].getData());
data.append("dateCreated", new Date().toISOString().slice(0, 19).replace('T', ' '));
data.append('categories', document.querySelector('#postCategories').value.toLowerCase());
data.append("headerImg", document.querySelector("#headerImg").files[0]);
fetch("/api/blog/post", {
method: "POST",
body: data,
headers: {
"Authorization": "Bearer " + localStorage.getItem("token")
}
}).then(res => res.json().then(json =>
{
if (res.ok)
{
document.querySelector("#addPostForm").reset();
editors["CKEditorAddPost"].setData("");
addPostInfo(json.ID, data.get("title"), data.get("dateCreated"), data.get("dateModified"));
showSuccessMessage("Post added successfully", "addPost");
return;
}
if (res.status === 401)
{
window.location.href = "./";
return;
}
res.json().then(json => showErrorMessage(json.error, "addPost"));
}));
});
document.querySelector("#editPostForm").addEventListener("submit", e =>
{
e.preventDefault();
let id = document.querySelector("#editPostForm input[type='submit']").id;
if (id === "")
{
showErrorMessage("Currently not editing any post", "editPost");
return;
}
if (editors["CKEditorEditPost"].getData() === "")
{
showErrorMessage("Post body cannot be empty", "editPost");
return;
}
let data = {};
data["title"] = document.querySelector("#editPostTitle").value;
data["featured"] = document.querySelector("#editIsFeatured").checked ? "1" : "0";
data["abstract"] = document.querySelector("#editPostAbstract").value;
data["body"] = editors["CKEditorEditPost"].getData();
data["dateModified"] = new Date().toISOString().slice(0, 19).replace('T', ' ');
data['categories'] = document.querySelector('#editPostCategories').value.toLowerCase();
let imgData = new FormData();
imgData.append("headerImg", document.querySelector("#editHeaderImg").files[0]);
fetch("/api/blog/post/" + id, {
method: "PATCH",
body: JSON.stringify(data),
headers: {
"Content-Type": "application/json",
"Authorization": "Bearer " + localStorage.getItem("token")
}
}).then(res =>
{
if (res.ok)
{
if (imgData.get("headerImg") === "undefined")
{
document.querySelector("#editPostForm").reset();
document.querySelector("#editPostForm input[type='submit']").id = "";
editors["CKEditorEditPost"].setData("");
showSuccessMessage("Post edited successfully", "editPost");
return;
}
return fetch("/api/blog/headerImage/" + id, {
method: "POST",
body: imgData,
headers: {
"Authorization": "Bearer " + localStorage.getItem("token")
}
});
}
if (res.status === 401)
{
window.location.href = "./";
return;
}
res.json().then(json => showErrorMessage(json.error, "editPost"));
}).then(res => res.json().then(json =>
{
if (res.ok)
{
document.querySelector("#editPostForm").reset();
document.querySelector("#editPostForm input[type='submit']").id = "";
editors["CKEditorEditPost"].setData("");
showSuccessMessage("Post edited successfully", "editPost");
return;
}
if (res.status === 401)
{
window.location.href = "./";
return;
}
showErrorMessage(json.error.message, "editPost");
}));
});
document.querySelector("#goToCV").addEventListener("click", () =>
{
textareaLoaded = false;
addActiveClass("goToCV");
goToPage("curriculumVitae");
});
document.querySelector("#goToProjects").addEventListener("click", () =>
{
textareaLoaded = false;
addActiveClass("goToProjects");
goToPage("projects");
});
document.querySelector("#blog").addEventListener("click", () =>
{
// document.querySelector("#blog").classList.add("active");
document.querySelector("nav.sideNav ul li.dropdown ul").classList.toggle("active");
document.querySelector("#blog i.fa").classList.toggle("fa-caret-down");
document.querySelector("#blog i.fa").classList.toggle("fa-caret-right");
});
document.querySelector("#goToAddPost").addEventListener("click", () =>
{
textareaLoaded = false;
addActiveClass("goToAddPost");
goToPage("addPost");
document.querySelector("#blog").classList.add("active");
});
document.querySelector("#goToEditPost").addEventListener("click", () =>
{
textareaLoaded = false;
addActiveClass("goToEditPost");
goToPage("editPost");
document.querySelector("#blog").classList.add("active");
});
document.querySelector("#logout").addEventListener("click", () =>
{
fetch("/api/user/logout").then(res =>
{
if (res.ok)
{
window.location.reload();
}
});
});
document.querySelector("#eduError .close").addEventListener("click", () =>
document.querySelector("#eduError").classList.toggle("hidden"));
document.querySelector("#workError .close").addEventListener("click", () =>
document.querySelector("#workError").classList.toggle("hidden"));
document.querySelector("#projError .close").addEventListener("click", () =>
document.querySelector("#projError").classList.toggle("hidden"));
document.querySelector("#addPostError .close").addEventListener("click", () =>
document.querySelector("#addPostError").classList.toggle("hidden"));
document.querySelector("#addPostSuccess .close").addEventListener("click", () =>
document.querySelector("#addPostSuccess").classList.toggle("hidden"));
document.querySelector("#editPostError .close").addEventListener("click", () =>
document.querySelector("#editPostError").classList.toggle("hidden"));
document.querySelector("#editPostSuccess .close").addEventListener("click", () =>
document.querySelector("#editPostSuccess").classList.toggle("hidden"));
/**
* Goes to the page with the given id of the section
* @param {string} id - The id of the section to go to
*/
function goToPage(id)
{
document.querySelectorAll(".editor section").forEach(element =>
{
element.style.display = "none";
if (element.id === id)
{
element.style.display = "flex";
}
});
}
/**
* Removes the active class from all nav items and adds it to the one with the given id
* @param {string} id - The id to add the active class to
*/
function addActiveClass(id)
{
document.querySelectorAll("nav.sideNav ul li a").forEach(element =>
{
element.classList.remove("active");
if (element.id === id)
{
element.classList.add("active");
}
});
}
/**
* Toggles the editing mode of the project item with the given id
* @param id {number} - the id of the project item from the database
*/
function editProjectItem(id)
{
document.querySelector(`#projectItem${id}`).classList.toggle("editing");
document.querySelector(`#title${id}proj`).toggleAttribute("disabled");
document.querySelector(`#info${id}proj`).toggleAttribute("disabled");
}
/**
* Creates new CKEditor instances
* @param ids {string[]} - the ids of the divs to create editors for
*/
function createEditors(...ids)
{
ids.forEach(id =>
{
ClassicEditor.create(document.querySelector(`#${id}`), {
placeholder: "Write something amazing...",
simpleUpload: {
uploadUrl: '/api/blog/uploadPostImage',
headers: {
Authorization: "Bearer " + localStorage.getItem("token")
}
},
style: {
definitions: [
{
name: "Button Primary",
element: "a",
classes: ["btn", "btnPrimary"]
},
{
name: "Button Primary",
element: "button",
classes: ["btn", "btnPrimary"]
},
]
},
codeBlock: {
languages: [
{language: 'plaintext', label: 'Plain text'},
{language: 'abap', label: 'ABAP'},
{language: 'abnf', label: 'ABNF'},
{language: 'actionscript', label: 'ActionScript'},
{language: 'ada', label: 'Ada'},
{language: 'agda', label: 'Agda'},
{language: 'al', label: 'AL'},
{language: 'antlr4', label: 'ANTLR4'},
{language: 'apacheconf', label: 'Apache Configuration'},
{language: 'apex', label: 'Apex'},
{language: 'apl', label: 'APL'},
{language: 'applescript', label: 'AppleScript'},
{language: 'aql', label: 'AQL'},
{language: 'arduino', label: 'Arduino'},
{language: 'arff', label: 'ARFF'},
{language: 'asciidoc', label: 'AsciiDoc'},
{language: 'aspnet', label: 'ASP.NET (C#)'},
{language: 'asm6502', label: '6502 Assembly'},
{language: 'autohotkey', label: 'AutoHotkey'},
{language: 'autoit', label: 'AutoIt'},
{language: 'bash', label: 'Bash'},
{language: 'basic', label: 'BASIC'},
{language: 'batch', label: 'Batch'},
{language: 'bbcode', label: 'BBcode'},
{language: 'bison', label: 'Bison'},
{language: 'bnf', label: 'BNF'},
{language: 'brainfuck', label: 'Brainfuck'},
{language: 'brightscript', label: 'BrightScript'},
{language: 'bro', label: 'Bro'},
{language: 'c', label: 'C'},
{language: 'concurnas', label: 'Concurnas'},
{language: 'csharp', label: 'C#'},
{language: 'cpp', label: 'C++'},
{language: 'cil', label: 'CIL'},
{language: 'clojure', label: 'Clojure'},
{language: 'cmake', label: 'CMake'},
{language: 'coffeescript', label: 'CoffeeScript'},
{language: 'concurnas', label: 'Concurnas'},
{language: 'crystal', label: 'Crystal'},
{language: 'css-extras', label: 'CSS Extras'},
{language: 'css', label: 'CSS'},
{language: 'd', label: 'D'},
{language: 'dart', label: 'Dart'},
{language: 'dax', label: 'DAX'},
{language: 'dhall', label: 'Dhall'},
{language: 'diff', label: 'Diff'},
{language: 'django', label: 'Django/Jinja2'},
{language: 'dns-zone-file', label: 'DNS zone file'},
{language: 'docker', label: 'Docker'},
{language: 'ebnf', label: 'EBNF'},
{language: 'editorconfig', label: 'EditorConfig'},
{language: 'eiffel', label: 'Eiffel'},
{language: 'ejs', label: 'EJS'},
{language: 'elixir', label: 'Elixir'},
{language: 'elm', label: 'Elm'},
{language: 'etlua', label: 'Embedded Lua'},
{language: 'erb', label: 'ERB'},
{language: 'erlang', label: 'Erlang'},
{language: 'excel-formula', label: 'Excel Formula'},
{language: 'fsharp', label: 'F#'},
{language: 'factor', label: 'Factor'},
{language: 'firestore-security-rules', label: 'Firestore security rules'},
{language: 'flow', label: 'Flow'},
{language: 'fortran', label: 'Fortran'},
{language: 'ftl', label: 'FreeMarker Template Language'},
{language: 'gcode', label: 'G-code'},
{language: 'gdscript', label: 'GDScript'},
{language: 'gedcom', label: 'GEDCOM'},
{language: 'gherkin', label: 'Gherkin'},
{language: 'git', label: 'Git'},
{language: 'glsl', label: 'GLSL'},
{language: 'gml', label: 'GameMaker Language'},
{language: 'go', label: 'Go'},
{language: 'graphql', label: 'GraphQL'},
{language: 'groovy', label: 'Groovy'},
{language: 'haml', label: 'Haml'},
{language: 'handlebars', label: 'Handlebars'},
{language: 'haskell', label: 'Haskell'},
{language: 'haxe', label: 'Haxe'},
{language: 'hcl', label: 'HCL'},
{language: 'hlsl', label: 'HLSL'},
{language: 'http', label: 'HTTP'},
{language: 'hpkp', label: 'HTTP Public-Key-Pins'},
{language: 'hsts', label: 'HTTP Strict-Transport-Security'},
{language: 'ichigojam', label: 'IchigoJam'},
{language: 'icon', label: 'Icon'},
{language: 'ignore', label: 'Ignore'},
{language: 'inform7', label: 'Inform 7'},
{language: 'ini', label: 'Ini'},
{language: 'io', label: 'Io'},
{language: 'j', label: 'J'},
{language: 'java', label: 'Java'},
{language: 'javadoc', label: 'JavaDoc'},
{language: 'javadoclike', label: 'JavaDoc-like'},
{language: 'javascript', label: 'JavaScript'},
{language: 'javastacktrace', label: 'Java stack trace'},
{language: 'jolie', label: 'Jolie'},
{language: 'jq', label: 'JQ'},
{language: 'js-extras', label: 'JS Extras'},
{language: 'js-templates', label: 'JS Templates'},
{language: 'jsdoc', label: 'JSDoc'},
{language: 'json', label: 'JSON'},
{language: 'json5', label: 'JSON5'},
{language: 'jsonp', label: 'JSONP'},
{language: 'jsstacktrace', label: 'JS stack trace'},
{language: 'jsx', label: 'React JSX'},
{language: 'julia', label: 'Julia'},
{language: 'keyman', label: 'Keyman'},
{language: 'kotlin', label: 'Kotlin'},
{language: 'latex', label: 'LaTeX'},
{language: 'latte', label: 'Latte'},
{language: 'less', label: 'Less'},
{language: 'lilypond', label: 'LilyPond'},
{language: 'liquid', label: 'Liquid'},
{language: 'lisp', label: 'Lisp'},
{language: 'livescript', label: 'LiveScript'},
{language: 'llvm', label: 'LLVM IR'},
{language: 'log', label: 'Log file'},
{language: 'lolcode', label: 'LOLCODE'},
{language: 'lua', label: 'Lua'},
{language: 'makefile', label: 'Makefile'},
{language: 'markdown', label: 'Markdown'},
{language: 'markup-templating', label: 'Markup templating'},
{language: 'matlab', label: 'MATLAB'},
{language: 'mel', label: 'MEL'},
{language: 'mizar', label: 'Mizar'},
{language: 'mongodb', label: 'MongoDB'},
{language: 'monkey', label: 'Monkey'},
{language: 'moonscript', label: 'MoonScript'},
{language: 'n1ql', label: 'N1QL'},
{language: 'n4js', label: 'N4JS'},
{language: 'nand2tetris-hdl', label: 'Nand To Tetris HDL'},
{language: 'nasm', label: 'NASM'},
{language: 'neon', label: 'NEON'},
{language: 'nginx', label: 'nginx'},
{language: 'nim', label: 'Nim'},
{language: 'nix', label: 'Nix'},
{language: 'nsis', label: 'NSIS'},
{language: 'objectivec', label: 'Objective-C'},
{language: 'ocaml', label: 'OCaml'},
{language: 'opencl', label: 'OpenCL'},
{language: 'oz', label: 'Oz'},
{language: 'parigp', label: 'PARI/GP'},
{language: 'parser', label: 'Parser'},
{language: 'pascal', label: 'Pascal'},
{language: 'pascaligo', label: 'Pascaligo'},
{language: 'pcaxis', label: 'PC-Axis'},
{language: 'peoplecode', label: 'PeopleCode'},
{language: 'perl', label: 'Perl'},
{language: 'php', label: 'PHP'},
{language: 'phpdoc', label: 'PHPDoc'},
{language: 'php-extras', label: 'PHP Extras'},
{language: 'plsql', label: 'PL/SQL'},
{language: 'powerquery', label: 'PowerQuery'},
{language: 'powershell', label: 'PowerShell'},
{language: 'processing', label: 'Processing'},
{language: 'prolog', label: 'Prolog'},
{language: 'properties', label: '.properties'},
{language: 'protobuf', label: 'Protocol Buffers'},
{language: 'pug', label: 'Pug'},
{language: 'puppet', label: 'Puppet'},
{language: 'pure', label: 'Pure'},
{language: 'purebasic', label: 'PureBasic'},
{language: 'python', label: 'Python'},
{language: 'q', label: 'Q (kdb+ database)'},
{language: 'qml', label: 'QML'},
{language: 'qore', label: 'Qore'},
{language: 'r', label: 'R'},
{language: 'racket', label: 'Racket'},
{language: 'jsx', label: 'React JSX'},
{language: 'tsx', label: 'React TSX'},
{language: 'reason', label: 'Reason'},
{language: 'regex', label: 'Regex'},
{language: 'renpy', label: 'Ren\'py'},
{language: 'rest', label: 'reST (reStructuredText)'},
{language: 'rip', label: 'Rip'},
{language: 'roboconf', label: 'Roboconf'},
{language: 'robotframework', label: 'Robot Framework'},
{language: 'ruby', label: 'Ruby'},
{language: 'rust', label: 'Rust'},
{language: 'sas', label: 'SAS'},
{language: 'sass', label: 'Sass (Sass)'},
{language: 'scss', label: 'Sass (Scss)'},
{language: 'scala', label: 'Scala'},
{language: 'scheme', label: 'Scheme'},
{language: 'shell-session', label: 'Shell session'},
{language: 'smali', label: 'Smali'},
{language: 'smalltalk', label: 'Smalltalk'},
{language: 'smarty', label: 'Smarty'},
{language: 'solidity', label: 'Solidity (Ethereum)'},
{language: 'solution-file', label: 'Solution file'},
{language: 'soy', label: 'Soy (Closure Template)'},
{language: 'sparql', label: 'SPARQL'},
{language: 'splunk-spl', label: 'Splunk SPL'},
{language: 'sqf', label: 'SQF: Status Quo Function (Arma 3)'},
{language: 'sql', label: 'SQL'},
{language: 'stan', label: 'Stan'},
{language: 'stata', label: 'Stata'},
{language: 'step21', label: 'STEP Part 21'},
{language: 'stylus', label: 'Stylus'},
{language: 'swift', label: 'Swift'},
{language: 'tap', label: 'TAP'},
{language: 'tcl', label: 'Tcl'},
{language: 'textile', label: 'Textile'},
{language: 'toml', label: 'TOML'},
{language: 'tt2', label: 'Template Toolkit 2'},
{language: 'turtle', label: 'Turtle'},
{language: 'twig', label: 'Twig'},
{language: 'typescript', label: 'TypeScript'},
{language: 't4-cs', label: 'T4 Text Templates (C#)'},
{language: 't4-vb', label: 'T4 Text Templates (VB)'},
{language: 't4-templating', label: 'T4 templating'},
{language: 'unrealscript', label: 'UnrealScript'},
{language: 'vala', label: 'Vala'},
{language: 'vbnet', label: 'VB.Net'},
{language: 'velocity', label: 'Velocity'},
{language: 'verilog', label: 'Verilog'},
{language: 'vhdl', label: 'VHDL'},
{language: 'vim', label: 'vim'},
{language: 'visual-basic', label: 'Visual Basic'},
{language: 'warpscript', label: 'WarpScript'},
{language: 'wasm', label: 'WebAssembly'},
{language: 'wiki', label: 'Wiki markup'},
{language: 'xeora', label: 'Xeora'},
{language: 'xojo', label: 'Xojo (REALbasic)'},
{language: 'xquery', label: 'XQuery'},
{language: 'yaml', label: 'YAML'},
{language: 'zephir', label: 'Zephir'},
],
},
}).then(CKEditor =>
{
editors[id] = CKEditor;
}).catch(error =>
{
console.error('Oops, something went wrong!');
console.error('Please, report the following error on https://github.com/ckeditor/ckeditor5/issues with the build id and the error stack trace:');
console.warn('Build id: 1eo8ioyje2om-vgar4aghypdm');
console.error(error);
});
})
}
/**
* Edits the post with the given id
* @param {number} id - the id of the post from the database
*/
function editPostItem(id)
{
posts.forEach(post =>
{
if (post.ID === id)
{
document.querySelector("#editPostTitle").value = post.title;
document.querySelector("#editIsFeatured").checked = (post.featured === 1);
document.querySelector("#editPostCategories").value = post.categories;
document.querySelector("#editPostAbstract").value = post.abstract;
editors["CKEditorEditPost"].setData(post.body);
document.querySelector("#editPostForm input[type='submit']").id = id;
}
})
}
/**
* Shows respective error message for form
* @param {string} message - The error message to show
* @param {string} form - The form to show the error message for
*/
function showErrorMessage(message, form)
{
document.querySelector(`#${form}Error`).classList.remove("hidden");
document.querySelector(`#${form}Error div`).innerText = message;
}
/**
* Shows respective success message for form
* @param message - The success message to show
* @param form - The form to show the success message for
*/
function showSuccessMessage(message, form)
{
document.querySelector(`#${form}Success`).classList.remove("hidden");
document.querySelector(`#${form}Success div`).innerText = message;
}
/**
* Switches the timeline item between edit and view mode
* @param id - the id of the timeline item from the database
*/
function editCVItem(id)
{
textareaLoaded = false;
document.querySelector(`#timelineItem${id}`).classList.toggle("editing");
if (id.includes("e"))
{
document.querySelector(`#grade${id}`).toggleAttribute("disabled");
document.querySelector(`#course${id}`).toggleAttribute("disabled");
return;
}
document.querySelector(`#companyName${id}`).toggleAttribute("disabled");
document.querySelector(`#area${id}`).toggleAttribute("disabled");
document.querySelector(`#jobTitle${id}`).toggleAttribute("disabled");
}
/**
* Updates the education timeline item with the given id
* @param {number} ID - the id of the course timeline item from the database
* @param {string} startPeriod - the start date of the course
* @param {string} endPeriod - the end date of the course
* @param {string} grade - the grade of the course
* @param {string} course - the name of the course
* @param {boolean} prepend - whether to prepend the timeline item to the timeline
*/
function addEduData(ID, startPeriod, endPeriod, grade, course, prepend=false)
{
let id = ID + "e";
let timelineItem = document.createElement("form")
timelineItem.id = "timelineItem" + id;
timelineItem.classList.add("timelineItem");
timelineItem.onsubmit = e => updateEduItem(ID, e);
timelineItem.innerHTML = `
<div class="modifyBtnContainer">
<button class="edit" type="button" id="edit${id}" onclick="editCVItem('${id}')"><i class="fa-solid fa-pen-to-square"></i></button>
<button class="delete" type="button" id="delete${id}" onclick="deleteEduItem(${ID})"><i class="fa-solid fa-trash"></i></button>
</div>
<div class="dateContainer formControl">
<input type="date" name="dateFrom${id}" id="dateFrom${id}" onload="this.max = new Date().toISOString().split('T')[0]" value="${startPeriod}">
-
<input type="date" name="dateTo${id}" id="dateTo${id}" value="${endPeriod}">
</div>
<h3 class="timelineHeader" id="timelineHeader${id}">${new Date(startPeriod).toLocaleString('en-gb', dateOptions)} - ${new Date(endPeriod).toLocaleString('en-gb', dateOptions)}</h3>
<div class="gradeContainer formControl">
<label for="grade${id}">Grade:</label>
<input type="text" name="grade${id}" id="grade${id}" value="${grade}" disabled>
</div>
<div class="formControl">
<textarea class="courseText" name="course${id}" id="course${id}" cols="10" rows="3" disabled>${course}</textarea>
</div>
<div class="error hidden" id="eduError${id}">
<button class="close" type="button" onclick="this.parentElement.classList.toggle('hidden');">&times;</button>
<div></div>
</div>
<input type="submit" value="Change">
`;
if (prepend)
{
document.querySelector("#edu").prepend(timelineItem);
return;
}
document.getElementById("edu").appendChild(timelineItem);
}
/**
* Adds a new work timeline item to the page
* @param {number} ID - the id of the work timeline item from the database
* @param {string} startPeriod - the start date of the job
* @param {string} endPeriod - the end date of the job
* @param {string} companyName - the name of the company
* @param {string} area - the area of the company
* @param {string} jobTitle - the job title
* @param {boolean} prepend - whether to prepend the timeline item to the timeline
*/
function addWorkData(ID, startPeriod, endPeriod, companyName, area, jobTitle, prepend=false)
{
let id = ID + "w";
let timelineItem = document.createElement("form")
timelineItem.id = "timelineItem" + id;
timelineItem.classList.add("timelineItem");
timelineItem.onsubmit = e => updateWorkItem(ID, e);
timelineItem.innerHTML = `
<div class="modifyBtnContainer">
<button class="edit" type="button" id="edit${id}" onclick="editCVItem('${id}')"><i class="fa-solid fa-pen-to-square"></i></button>
<button class="delete" type="button" id="delete${id}" onclick="deleteWorkItem(${ID})"><i class="fa-solid fa-trash"></i></button>
</div>
<div class="dateContainer formControl">
<input type="date" name="dateFrom${id}" id="dateFrom${id}" onload="this.max = new Date().toISOString().split('T')[0]" value="${startPeriod}">
-
<input type="date" name="dateTo${id}" id="dateTo${id}" value="${endPeriod === 'Present' ? '' : endPeriod}">
</div>
<h3 class="timelineHeader" id="timelineHeader${id}">${new Date(startPeriod).toLocaleString('en-gb', dateOptions)} - ${endPeriod === 'Present' ? 'Present' : new Date(endPeriod).toLocaleString('en-gb', dateOptions)}</h3>
<div class="companyAreaContainer formControl">
<input type="text" name="companyName${id}" id="companyName${id}" value="${companyName}" disabled>
-
<input type="text" name="area${id}" id="area${id}" value="${area}" disabled>
</div>
<div class="formControl">
<textarea class="jobTitleText" name="jobTitle${id}" id="jobTitle${id}" cols="10" rows="3" disabled>${jobTitle}</textarea>
</div>
<div class="error hidden" id="workError${id}">
<button class="close" type="button" onclick="this.parentElement.classList.toggle('hidden');">&times;</button>
<div></div>
</div>
<input type="submit" value="Change">
`;
if (prepend)
{
document.querySelector("#work").prepend(timelineItem);
return;
}
document.getElementById("work").appendChild(timelineItem);
}
/**
* Updates the edu timeline item with the given id
* and data from the form
* @param {number} id - the id of the edu timeline item from the database
* @param {SubmitEvent} e - the event that triggered the function
*/
function updateEduItem(id, e)
{
e.preventDefault();
let data = {}
data["dateFrom"] = document.querySelector(`#dateFrom${id}e`).value;
data["dateTo"] = document.querySelector(`#dateTo${id}e`).value;
data["grade"] = document.querySelector(`#grade${id}e`).value;
data["course"] = document.querySelector(`#course${id}e`).value;
fetch("/api/timelineData/edu/" + id, {
method: "PATCH",
body: JSON.stringify(data),
headers: {
"Content-Type": "application/json",
"Authorization": "Bearer " + localStorage.getItem("token")
}
}).then(res =>
{
if (res.ok)
{
document.querySelector(`#timelineHeader${id}e`).innerHTML = new Date(document.querySelector(`#dateFrom${id}e`).value).toLocaleString('en-gb', dateOptions) + " - " + new Date(document.querySelector(`#dateTo${id}e`).value).toLocaleString('en-gb', dateOptions);
document.querySelector(`#timelineItem${id}e`).classList.toggle("editing");
document.querySelector(`#grade${id}e`).setAttribute("disabled", "");
document.querySelector(`#course${id}e`).setAttribute("disabled", "");
return;
}
if (res.status === 401)
{
window.location.href = "./";
return;
}
res.json().then(json =>
{
document.querySelector(`#eduError${id}e`).classList.remove("hidden");
document.querySelector(`#eduError${id}e div`).innerHTML = json.error;
});
})
}
/**
* Updates the work timeline item with the given id
* and data from the form
* @param {number} id - the id of the work timeline item from the database
* @param {SubmitEvent} e - the event that triggered the function
*/
function updateWorkItem(id, e)
{
e.preventDefault();
let data = {}
data["dateFrom"] = document.querySelector(`#dateFrom${id}w`).value;
data["dateTo"] = document.querySelector(`#dateTo${id}w`).value;
data["companyName"] = document.querySelector(`#companyName${id}w`).value;
data["area"] = document.querySelector(`#area${id}w`).value;
data["title"] = document.querySelector(`#jobTitle${id}w`).value;
fetch("/api/timelineData/work/" + id, {
method: "PATCH",
body: JSON.stringify(data),
headers: {
"Content-Type": "application/json",
"Authorization": "Bearer " + localStorage.getItem("token")
}
}).then(res =>
{
if(res.ok)
{
document.querySelector(`#timelineHeader${id}w`).innerHTML = new Date(document.querySelector(`#dateFrom${id}w`).value).toLocaleString('en-gb', dateOptions) + " - " + new Date(document.querySelector(`#dateTo${id}w`).value).toLocaleString('en-gb', dateOptions);
document.querySelector(`#timelineItem${id}w`).classList.toggle("editing");
document.querySelector(`#companyName${id}w`).setAttribute("disabled", "");
document.querySelector(`#area${id}w`).setAttribute("disabled", "");
document.querySelector(`#jobTitle${id}w`).setAttribute("disabled", "");
return;
}
if (res.status === 401)
{
window.location.href = "./";
return;
}
res.json().then(json =>
{
document.querySelector(`#workError${id}w`).classList.remove("hidden");
document.querySelector(`#workError${id}w div`).innerHTML = json.error;
});
});
}
/**
* Deletes the timeline item with the given id
* @param {number} id - the id of the timeline item
*/
function deleteEduItem(id)
{
fetch("/api/timelineData/edu/" + id, {
method: "DELETE",
headers: {
"Authorization": "Bearer " + localStorage.getItem("token")
}
}).then(res =>
{
if (res.ok)
{
document.querySelector(`#timelineItem${id}e`).remove();
return;
}
if (res.status === 401)
{
window.location.href = "./";
return;
}
res.json().then(json => alert(json.error));
});
}
/**
* Updates the timeline item with the given id
* @param {number} id - the id of the timeline item from the database
*/
function deleteWorkItem(id)
{
fetch("/api/timelineData/work/" + id, {
method: "DELETE",
headers: {
"Authorization": "Bearer " + localStorage.getItem("token")
}
}).then(res =>
{
if (res.ok)
{
document.querySelector(`#timelineItem${id}w`).remove();
return;
}
if (res.status === 401)
{
window.location.href = "./";
return;
}
res.json().then(json => alert(json.error));
});
}
/**
* Updates the project item with the given id
* and data from the form
* @param {number} id - the base id of the element
* @param {SubmitEvent} e - the event of the form that was submitted
*/
function updateProjectItem(id, e)
{
e.preventDefault();
let data = {}
data["title"] = document.querySelector(`#title${id}`).value;
data["isMainProject"] = document.querySelector(`#isMainProject${id}`).checked ? "true" : "false";
data["information"] = document.querySelector(`#info${id}`).value;
data["projectLink"] = document.querySelector(`#viewProj${id}`).value;
data["gitLink"] = document.querySelector(`#git${id}`).value;
let imgData = new FormData();
imgData.append("img", document.querySelector(`#img${id}`).files[0]);
fetch("/api/projectData/" + id, {
method: "PATCH",
body: JSON.stringify(data),
headers: {
"Content-Type": "application/json",
"Authorization": "Bearer " + localStorage.getItem("token")
}
}).then(res =>
{
if (res.ok)
{
if (imgData.get("img") === "undefined")
{
if (data["isMainProject"] === "true")
{
document.querySelectorAll(".isMainProject input").forEach(item => item.checked = false);
document.querySelector(`#isMainProject${id}`).checked = true;
document.querySelector("#projList").prepend(document.querySelector(`#projectItem${id}`));
}
document.querySelector(`#projectItem${id}`).classList.toggle("editing");
document.querySelector(`#title${id}`).setAttribute("disabled", "");
document.querySelector(`#info${id}`).setAttribute("disabled", "");
return;
}
console.log("updating image")
return fetch("/api/projectImage/" + id, {
method: "POST",
body: imgData,
headers: {
"Authorization": "Bearer " + localStorage.getItem("token")
}
});
}
if (res.status === 401)
{
window.location.href = "./";
return;
}
res.json().then(projectDataError =>
{
document.querySelector(`#projError${id}`).classList.remove("hidden");
document.querySelector(`#projError${id} div`).innerHTML = projectDataError.error;
});
}).then(res => res.json().then(updatedProjectImage =>
{
if (res.ok)
{
if (updatedProjectImage["isMainProject"] === "true")
{
document.querySelectorAll(".isMainProject input").forEach(item => item.checked = false);
document.querySelector(`#isMainProject${id}`).checked = true;
document.querySelector("#projList").prepend(document.querySelector(`#projectItem${id}`));
}
document.querySelector(`#projectItem${id}`).classList.toggle("editing");
document.querySelector(`#title${id}`).setAttribute("disabled", "");
document.querySelector(`#info${id}`).setAttribute("disabled", "");
document.querySelector(`#projectImage${id}`).src = updatedProjectImage.imgLocation;
return;
}
if (res.status === 401)
{
window.location.href = "./";
return;
}
document.querySelector(`#projError${id}`).classList.remove("hidden");
document.querySelector(`#projError${id} div`).innerHTML = updatedProjectImage.error;
}));
}
/**
* Deletes the project item with the given id
* @param id {number} - the id of the project item from the database
*/
function deleteProjectItem(id)
{
fetch("/api/projectData/" + id, {
method: "DELETE",
headers: {
"Authorization": "Bearer " + localStorage.getItem("token")
}
}).then(res =>
{
if (res.ok)
{
document.querySelector(`#projectItem${id}`).remove();
return;
}
if (res.status === 401)
{
window.location.href = "./";
return;
}
res.json().then(json => alert(json.error));
});
}
/**
* Adds a new project to the page
* @param {number} ID - the id of the project from the database
* @param {string} isMainProject - is it the main project
* @param {string} imgLocation - the relative path of the image
* @param {string} title - the title of the project
* @param {string} information - the information about the project
* @param {string} projectLink - the link to the project
* @param {string} gitLink - the link to the git repository
*/
function addProject(ID, isMainProject, imgLocation, title, information, projectLink, gitLink)
{
let projectItem = document.createElement("form");
let id = ID + "proj";
projectItem.id = "projectItem" + ID;
projectItem.classList.add("projItem");
projectItem.onsubmit = e => updateProjectItem(ID, e);
projectItem.innerHTML = `
<div class="modifyBtnContainer">
<button class="edit" type="button" id="edit${id}" onclick="editProjectItem(${ID})"><i class="fa-solid fa-pen-to-square"></i></button>
<button class="delete" type="button" id="delete${id}" onclick="deleteProjectItem(${ID})"><i class="fa-solid fa-trash"></i></button>
</div>
<img class="displayedImage" id="projectImage${id}" src="${imgLocation}" alt="image preivew of the project">
<div class="formControl imageContainer">
<input type="file" name="img${id}" id="img${id}">
</div>
<div class="formControl projectTitleContainer">
<input type="text" name="title${id}" id="title${id}" value="${title}" disabled>
</div>
<div class="formControl isMainProject">
<label class="checkContainer" for="isMainProject${id}">Is It The Main Project
<input type="checkbox" id="isMainProject${id}" name="isMainProject${id}" ${(isMainProject === "true" ? "checked=''" : "")}>
<span class="checkmark"></span>
</label>
</div>
<div class="formControl infoContainer">
<textarea name="info${id}" id="info${id}" disabled>${information}</textarea>
</div>
<div class="formControl viewProjContainer">
<input type="text" name="viewProj${id}" id="viewProj${id}" value="${projectLink}">
</div>
<div class="formControl gitContainer">
<input type="text" name="git${id}" id="git${id}" value="${gitLink}">
</div>
<div class="error hidden" id="projError${id}">
<button class="close" type="button" onclick="this.parentElement.classList.toggle('hidden');">&times;</button>
<div></div>
</div>
<input type="submit" value="Change">
<div class="linkContainer">
<a href="${(projectLink === "N/A") ? "#" : projectLink}" class="btn btnPrimary boxShadowIn boxShadowOut"${(projectLink === "N/A") ? "disabled=\"disabled\"" : ""}>View Project</a>
<a href="${(gitLink === "N/A") ? "#" : gitLink}" class="btn btnOutline boxShadowIn boxShadowOut">${(gitLink === "N/A") ? "disabled=\"disabled\"" : ""}Git</a>
</div>
`;
if (isMainProject === "true")
{
document.querySelectorAll(".isMainProject input").forEach(item => item.checked = false);
document.querySelector("#projList").prepend(projectItem);
return;
}
document.querySelector("#projList").appendChild(projectItem);
}
/**
* Deletes the post item with the given id
* @param {number} id - the id of the post item from the database
*/
function deletePostItem(id)
{
fetch("/api/blog/post/" + id, {
method: "DELETE",
headers: {
"Authorization": "Bearer " + localStorage.getItem("token")
}
}).then(res =>
{
if (res.ok)
{
document.querySelector(`#postInfo${id}`).remove();
return;
}
if (res.status === 401)
{
window.location.href = "./";
return;
}
res.json().then(json => alert(json.error));
});
}
/**
* Adds a new post info to the edit post table
* @param {number} ID - the id of the post
* @param {string} title - the title of the post
* @param {string} dateCreated - the date the post was created
* @param {string} dateModified - the date the post was modified
*/
function addPostInfo(ID, title, dateCreated, dateModified)
{
let postInfo = document.createElement("tr");
let id = ID + "post";
postInfo.id = "postInfo" + ID;
postInfo.innerHTML = `
<td>
${title}
</td>
<td>
${new Date(dateCreated).toLocaleDateString()}
</td>
<td>
${new Date(dateModified).toLocaleDateString()}
</td>
<td>
<button class="edit" type="button" id="edit${id}" onclick="editPostItem(${ID})"><i class="fa-solid fa-pen-to-square"></i></button>
<button class="delete" type="button" id="delete${id}" onclick="deletePostItem(${ID})"><i class="fa-solid fa-trash"></i></button>
</td>
`;
document.querySelector("#editPost table tbody").appendChild(postInfo);
}