Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Give users the option to add symbols #55

Closed
wants to merge 15 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions index.html
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@

<div class="search">
<input type="text" autofocus placeholder="Search..." />
<button id="add_symbol">+</button>
</div>

<div class="symbols"></div>
Expand Down
55 changes: 55 additions & 0 deletions symbols.css
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,10 @@
}
}

* {
--warning-fg-color: red;
}

*,
*:before,
*:after {
Expand Down Expand Up @@ -85,6 +89,11 @@ a {
background-color: var(--main-bg-color);
}

.search > button {
font-size: 3rem;
margin-left: 1rem;
}

.symbols {
max-width: 1200px;
margin: 0 auto;
Expand Down Expand Up @@ -118,9 +127,26 @@ a {
transition: background 200ms linear;
}

.symbol.drag {
opacity: 0.4;
}

.symbol.over {
border: 1px solid var(--border-color);
}

.symbol > .glyph {
font-size: 60cqw;
overflow: hidden;
width: 100%;
white-space: nowrap;
}

.symbol > .glyph > input {
width: 50%;
font-size: 3rem;
color: var(--main-fg-color);
background-color: var(--main-bg-color);
}

.symbol > .name {
Expand All @@ -132,11 +158,40 @@ a {
width: 100%;
overflow: hidden;
}
.symbol > .name,
.symbol.clicked > .copy {
display: block;
}

.symbol.clicked > .name,
.symbol > .copy {
display: none;
}


.symbol > .glyph > input {
color: var(--main-fg-color);
background-color: var(--main-bg-color);
}

.symbol:hover {
background-color: var(--hover-color);
}

.symbol:hover > .remove:after {
color: var(--warning-fg-color);
content: "❌";
font-size: 1.2rem;
position: absolute;
top: 0.3rem;
right: 0.5rem;
}

.symbol.drag:hover > .remove:after {
content: "";
}


.clicked {
background-color: var(--clicked-color) !important;
}
195 changes: 187 additions & 8 deletions symbols.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
const DISPLAY_BOX = "\u25A1";

const symbols = [
const symbols_default = [
/* samwho */
{
glyph: "©",
Expand Down Expand Up @@ -543,12 +543,24 @@ const symbols = [
}
];

let symbols = []

try {
// Catching invallid json, not an invalid symbol array
symbols = JSON.parse(window.localStorage.getItem("symbols"));
} catch(err) {
console.error(err);
}
if (!symbols) {
symbols = symbols_default;
}
Comment on lines +548 to +556
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was thinking about this earlier today: what happens when the default symbols are updated? I think currently the user won't see them, they'll load what's in local storage and completely ignore the defaults.

We probably need some sort of merging. Alternately, we track users' custom symbols separately to the defaults.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If you want to merge new defaults, with the existing symbols, we need to label either the user symbols or the defaults (which adds complexity) and then disable the editing of the defaults (which adds complexity) then it will be impossible for users to move the frequently used default symbols to the top of the list, resulting in users that will add existing default symbols also to the user symbols. Reducing the benefits of a list of default symbols.

Another method is to track all the changes made by the user (which adds way more complexity) and apply them every time the defaults are loaded (which is so complex I cannot imagine how to do that)

If you can program that, you're welcome to do that. I probably don't have the knowledge, and if I did my time is also limited.

Another solution would be to publish sets of symbols, including a default. When a set (e.g. the default set) is updated let the user load that set into their collection. That's something I could implement in a few hours. As a matter of fact, I already did. It's in pull request #59


function search(searchTerm) {
searchTerm = searchTerm?.toLowerCase() ?? "";

return symbols.filter((s) => {
/* Get hex representation of codepoint, e.g. 00A0 for &nbsp; or 20AC for € */
const codePoint = s.glyph.codePointAt(0).toString(16).padStart(4, 0);
const codePoint = s.glyph.codePointAt(0)?.toString(16).padStart(4, 0);
samwho marked this conversation as resolved.
Show resolved Hide resolved

const searchTerms = [
s.name,
Expand All @@ -561,6 +573,62 @@ function search(searchTerm) {
});
}

function addSymbol() {
symbols.unshift({
glyph: "",
name: "",
// For now: no display and search terms
});
// Better to just add the one div for the symbol
// For now this is fast enough.
renderSymbols();
editSymbol(document.getElementsByClassName("symbol")[0], "glyph");
}

function editSymbol(elem, classname) {
samwho marked this conversation as resolved.
Show resolved Hide resolved
const handleAction = (target) => {
symbols[target.dataset.index][target.dataset.classname] = target.value;
if (!symbols[target.dataset.index].name) {
symbols[target.dataset.index].name = symbols[target.dataset.index].glyph;
target.parentElement.parentElement.getElementsByClassName("name")[0].textContent = symbols[target.dataset.index].name;
}
target.parentElement.parentElement.title = symbols[target.dataset.index].name;
target.parentElement.textContent = target.value;
window.localStorage.setItem("symbols", JSON.stringify(symbols));
return true;
}

elem.classList.remove("clicked");
elemClass = elem.getElementsByClassName(classname)[0];
input = document.createElement("input");
input.dataset.classname = classname;
input.dataset.index = Array.from(elem.parentElement.children).indexOf(elem);
input.value = symbols[input.dataset.index][classname];
input.addEventListener("blur", (e) => handleAction(e.target))
input.addEventListener("keydown", (e) => {
if (e.key === "Enter") {
handleAction(e.target);
}
});
elemClass.innerHTML = '';
elemClass.appendChild(input);
input.focus();
}

function isNotEditingSymbol() {
return document
.getElementsByClassName("symbols")[0]
.getElementsByTagName("INPUT")
.length === 0;
}

function removeSymbol(elem) {
symbols.splice(Array.from(elem.parentElement.children).indexOf(elem), 1);
window.localStorage.setItem("symbols", JSON.stringify(symbols));
elem.remove();
}


function renderSymbols(searchTerm) {
const parent = document.querySelector(".symbols");
parent.innerHTML = "";
Expand All @@ -587,47 +655,127 @@ function renderSymbols(searchTerm) {
const elem = document.createElement("div");
const glyphElem = document.createElement("div");
const nameElem = document.createElement("div");
const copyElem = document.createElement("div");
const removeElem = document.createElement("div");

elem.classList = "symbol";
elem.tabIndex = 0;
elem.title = symbol.name;
elem.setAttribute("draggable", "true");

glyphElem.classList = "glyph";
glyphElem.textContent = symbol.display || symbol.glyph;

nameElem.classList = "name";
nameElem.textContent = symbol.name;

copyElem.classList = "copy";
copyElem.textContent = "Copied!";

removeElem.classList = "remove";

elem.appendChild(glyphElem);
elem.appendChild(nameElem);
elem.appendChild(copyElem);
elem.appendChild(removeElem);

const handleAction = () => {
if (elem.classList.contains("clicked")) {
if (elem.classList.contains("clicked") || !isNotEditingSymbol()) {
return;
}

navigator.clipboard.writeText(symbol.glyph);

console.log(`Copied ${symbol.name} (${symbol.glyph})!`);
nameElem.textContent = "Copied!";
elem.classList.add("clicked");

setTimeout(() => {
nameElem.textContent = symbol.name;
elem.classList.remove("clicked");
}, 1000);
};
elem.addEventListener("click", handleAction);
elem.addEventListener("keydown", (event) => {
if (event.key === "Enter" || event.key === " ") {
event.preventDefault();
handleAction();
if (isNotEditingSymbol()) {
switch (event.key) {
case " ":
case "Enter":
event.preventDefault();
handleAction();
break;
case "Delete":
removeSymbol(event.target);
break;
}
}
});
parent.appendChild(elem);
}
}

function handleDragStart(e) {
e.target.classList.add('drag');
document.getElementsByClassName("symbols")[0].dataset.dragIndex =
Array.from(e.target.parentElement.children).indexOf(e.target);
}


function handleDragEnd(e) {
e.target.classList.remove('drag');
Array.from(e.target.parentElement.children).forEach(elem => elem.classList.remove("over"));
}

function handleDragOver(e) {
e.preventDefault();
return false;
}

function handleDragEnter(e) {
Array.from(document.getElementsByClassName("over")).forEach(elem => elem.classList.remove("over"));
let target = e.target;
while (target) {
if (target.classList?.contains("symbol")) {
target.classList.add("over");
break;
}
target = target.parentElement
}
}

function handleDrop(e) {
e.preventDefault();
let target = e.target;
while (target) {
if (target.classList?.contains("symbol")) {
const parentElem = target.parentElement;
const dragIndex = parseInt(parentElem.dataset.dragIndex);
const dragTarget = Array.from(parentElem.children).indexOf(target);

if (!(dragIndex >= 0)) {
return false;
}

symbols.splice(dragTarget, 0, symbols.splice(dragIndex, 1)[0]);
window.localStorage.setItem("symbols", JSON.stringify(symbols));

if (dragIndex < dragTarget) {
if (target.nextElementSibling) {
target.nextElementSibling.before(
parentElem.children[dragIndex]
);
} else {
parentElem.appendChild(parentElem.children[dragIndex]);
}
} else {
target.before(parentElem.children[dragIndex]);
}
break;
}
target = target.parentElement;
}
e.stopPropagation();
return false;
}

document.addEventListener("DOMContentLoaded", () => {
const search = window.location.hash ? window.location.hash.substring(1) : "";
renderSymbols(search);
Expand All @@ -639,11 +787,42 @@ document.addEventListener("DOMContentLoaded", () => {
});
searchInput.addEventListener("blur", (e) => {
window.location.hash = e.target.value;
return false;
});

window.addEventListener("hashchange", () => {
const search = window.location.hash ? window.location.hash.substring(1) : "";
searchInput.value = search;
renderSymbols(search);
});

document.getElementById("add_symbol").addEventListener("click", () => addSymbol());

window.addEventListener("dblclick", (e) => {
let target = e.target;
while(target) {
if (target.classList?.contains("remove")) {
return removeSymbol(target.parentElement);
}
if (target.classList?.contains("glyph")) {
return editSymbol(target.parentElement, "glyph");
}
if (target.classList?.contains("name")) {
return editSymbol(target.parentElement, "name");
}
if (target.classList?.contains("copy")) {
return editSymbol(target.parentElement, "name");
}
if (target.classList?.contains("symbol")) {
return editSymbol(target, "glyph");
}
target = target.parentElement;
}
});

window.addEventListener("dragstart", handleDragStart);
window.addEventListener("dragover", handleDragOver);
window.addEventListener("dragenter", handleDragEnter);
window.addEventListener("dragend", handleDragEnd);
window.addEventListener("drop", handleDrop);
});