Skip to content

Instantly share code, notes, and snippets.

@thomasingalls
Created June 19, 2020 03:24
Show Gist options
  • Save thomasingalls/cb068500034c545d30432ca46e861122 to your computer and use it in GitHub Desktop.
Save thomasingalls/cb068500034c545d30432ca46e861122 to your computer and use it in GitHub Desktop.
JjYgZGy
const index = {};
let nanoid = (size = 21) => {
let id = ''
let bytes = crypto.getRandomValues(new Uint8Array(size))
// A compact alternative for `for (var i = 0; i < step; i++)`.
while (size--) {
// It is incorrect to use bytes exceeding the alphabet size.
// The following mask reduces the random byte in the 0-255 value
// range to the 0-63 value range. Therefore, adding hacks, such
// as empty string fallback or magic numbers, is unneccessary because
// the bitmask trims bytes down to the alphabet size.
let byte = bytes[size] & 63
if (byte < 36) {
// `0-9a-z`
id += byte.toString(36)
} else if (byte < 62) {
// `A-Z`
id += (byte - 26).toString(36).toUpperCase()
} else if (byte < 63) {
id += '_'
} else {
id += '-'
}
}
return id.replace(/\d/g, 'a');
}
function toggleBookmark(bookmarkTargetId) {
let els = document.querySelectorAll(`[data-bookmark-id="${bookmarkTargetId}"]`);
for (let el of els) {
let svg = el.querySelector('svg');
svg.setAttribute('viewBox', '0 0 512 512');
el.disabled = 'disabled';
let selected = el.classList.contains('selected');
if (selected) {
el.classList.toggle('selected');
} else {
el.classList.toggle('unselected');
}
el.classList.toggle('loading');
document.activeElement.blur();
setTimeout(() => {
el.disabled = null;
svg.setAttribute('viewBox', '0 0 352 512');
el.classList.toggle('loading');
if (selected) {
el.classList.toggle('unselected');
} else {
el.classList.toggle('selected');
}
el.focus();
}, 250);
}
}
function saveText(itemId) {
const el = document.querySelector(itemId);
const editTextSvg = el.querySelector('.edit svg');
editTextSvg.setAttribute('viewBox', '0 0 512 512');
editTextSvg.classList.toggle('loading');
setTimeout(() => {
editTextSvg.setAttribute('viewBox', '0 0 485 512');
editTextSvg.classList.toggle('loading');
}, 3250);
}
let bookmarkIcon = () => {
let svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
svg.setAttribute('aria-hidden', 'true');
svg.setAttribute('focusable', 'false');
svg.setAttribute('role', 'img');
svg.setAttribute('viewBox', '0 0 352 512');
// svg.setAttribute('aspect-ratio', 'XminYmin');
// svg.setAttribute('preserveAspectRatio', 'none');
svg.className.baseVal = 'squared';
let path = document.createElementNS('http://www.w3.org/2000/svg', 'path');
// path.setAttribute('d', 'M336 0H48C21.49 0 0 21.49 0 48v464l192-112 192 112V48c0-26.51-21.49-48-48-48zm0 428.43l-144-84-144 84V54a6 6 0 0 1 6-6h276c3.314 0 6 2.683 6 5.996V428.43z');
// path.setAttribute('fill', 'currentColor');
svg.appendChild(path);
return svg;
}
let handleIcon = () => {
let svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
svg.setAttribute('aria-hidden', 'true');
svg.setAttribute('focusable', 'false');
svg.setAttribute('role', 'img');
svg.setAttribute('viewBox', '0 0 352 512');
svg.className.baseVal = 'squared';
let path = document.createElementNS('http://www.w3.org/2000/svg', 'path');
path.setAttribute('d', 'M96 32H32C14.33 32 0 46.33 0 64v64c0 17.67 14.33 32 32 32h64c17.67 0 32-14.33 32-32V64c0-17.67-14.33-32-32-32zm0 160H32c-17.67 0-32 14.33-32 32v64c0 17.67 14.33 32 32 32h64c17.67 0 32-14.33 32-32v-64c0-17.67-14.33-32-32-32zm0 160H32c-17.67 0-32 14.33-32 32v64c0 17.67 14.33 32 32 32h64c17.67 0 32-14.33 32-32v-64c0-17.67-14.33-32-32-32zM288 32h-64c-17.67 0-32 14.33-32 32v64c0 17.67 14.33 32 32 32h64c17.67 0 32-14.33 32-32V64c0-17.67-14.33-32-32-32zm0 160h-64c-17.67 0-32 14.33-32 32v64c0 17.67 14.33 32 32 32h64c17.67 0 32-14.33 32-32v-64c0-17.67-14.33-32-32-32zm0 160h-64c-17.67 0-32 14.33-32 32v64c0 17.67 14.33 32 32 32h64c17.67 0 32-14.33 32-32v-64c0-17.67-14.33-32-32-32z');
path.setAttribute('fill', 'currentColor');
svg.appendChild(path);
return svg;
}
let closeIcon = () => {
let svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
svg.setAttribute('aria-hidden', 'true');
svg.setAttribute('focusable', 'false');
svg.setAttribute('role', 'img');
svg.setAttribute('viewBox', '0 0 352 512');
svg.className.baseVal = 'squared';
let path = document.createElementNS('http://www.w3.org/2000/svg', 'path');
path.setAttribute('d', 'M242.72 256l100.07-100.07c12.28-12.28 12.28-32.19 0-44.48l-22.24-22.24c-12.28-12.28-32.19-12.28-44.48 0L176 189.28 75.93 89.21c-12.28-12.28-32.19-12.28-44.48 0L9.21 111.45c-12.28 12.28-12.28 32.19 0 44.48L109.28 256 9.21 356.07c-12.28 12.28-12.28 32.19 0 44.48l22.24 22.24c12.28 12.28 32.2 12.28 44.48 0L176 322.72l100.07 100.07c12.28 12.28 32.2 12.28 44.48 0l22.24-22.24c12.28-12.28 12.28-32.19 0-44.48L242.72 256z');
path.setAttribute('fill', 'currentColor');
svg.appendChild(path);
return svg;
}
let editIcon = () => {
let svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
svg.setAttribute('aria-hidden', 'true');
svg.setAttribute('focusable', 'false');
svg.setAttribute('role', 'img');
svg.setAttribute('viewBox', '0 0 448 512');
svg.className.baseVal = 'squared';
let path = document.createElementNS('http://www.w3.org/2000/svg', 'path');
path.setAttribute('d', 'M400 480H48c-26.5 0-48-21.5-48-48V80c0-26.5 21.5-48 48-48h352c26.5 0 48 21.5 48 48v352c0 26.5-21.5 48-48 48zM238.1 177.9L102.4 313.6l-6.3 57.1c-.8 7.6 5.6 14.1 13.3 13.3l57.1-6.3L302.2 242c2.3-2.3 2.3-6.1 0-8.5L246.7 178c-2.5-2.4-6.3-2.4-8.6-.1zM345 165.1L314.9 135c-9.4-9.4-24.6-9.4-33.9 0l-23.1 23.1c-2.3 2.3-2.3 6.1 0 8.5l55.5 55.5c2.3 2.3 6.1 2.3 8.5 0L345 199c9.3-9.3 9.3-24.5 0-33.9z');
path.setAttribute('fill', 'currentColor');
svg.appendChild(path);
return svg;
}
let plusIcon = () => {
let svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
svg.setAttribute('aria-hidden', 'true');
svg.setAttribute('focusable', 'false');
svg.setAttribute('role', 'img');
svg.setAttribute('viewBox', '0 0 448 512');
svg.className.baseVal = 'squared';
let path = document.createElementNS('http://www.w3.org/2000/svg', 'path');
path.setAttribute('d', 'M416 208H272V64c0-17.67-14.33-32-32-32h-32c-17.67 0-32 14.33-32 32v144H32c-17.67 0-32 14.33-32 32v32c0 17.67 14.33 32 32 32h144v144c0 17.67 14.33 32 32 32h32c17.67 0 32-14.33 32-32V304h144c17.67 0 32-14.33 32-32v-32c0-17.67-14.33-32-32-32z');
path.setAttribute('fill', 'currentColor');
svg.appendChild(path);
return svg;
}
const bookmarkButton = ({ bookmarkTargetId }) => {
let bookmark = document.createElement('button');
bookmark.id = nanoid();
bookmark.setAttribute('aria-label', 'bookmark');
bookmark.dataset.bookmarked = false;
bookmark.dataset.bookmarkId = bookmarkTargetId;
bookmark.classList.add('bookmark');
bookmark.classList.add('unselected');
bookmark.appendChild(bookmarkIcon());
bookmark.addEventListener('click', (e) => {
toggleBookmark(bookmarkTargetId);
});
return bookmark;
}
const handleButton = ({ handleTargetId }) => {
let handle = document.createElement('button');
handle.id = nanoid();
handle.dataset.handleed = false;
handle.dataset.handleId = handleTargetId;
handle.classList.add('handle');
handle.appendChild(handleIcon());
handle.addEventListener('click', (e) => {
toggleBookmark(handleTargetId);
});
return handle;
}
const closeButton = ({ closeTargetId }) => {
let close = document.createElement('button');
close.id = nanoid();
close.setAttribute('aria-label', 'remove item');
close.classList.add('close');
close.appendChild(closeIcon());
close.addEventListener('click', (e) => {
let tgt = document.getElementById(closeTargetId);
tgt.classList.add('collapsed');
let nextClose = tgt?.closest('.row')?.nextSibling?.querySelector('.close');
let prevClose = tgt?.closest('.row')?.previousElementSibling?.querySelector('.close');
tgt.addEventListener('animationend', () => {
if (nextClose) {
nextClose.focus();
} else if (prevClose) {
prevClose?.focus();
} else {
document.activeElement.blur();
addText?.focus();
}
tgt?.remove();
});
});
return close;
}
const editButton = ({ editButtonTargetId }) => {
let edit = document.createElement('button');
edit.id = nanoid();
edit.setAttribute('aria-label', 'edit');
edit.classList.add('edit');
edit.appendChild(editIcon());
edit.addEventListener('click', (e) => {
let tgt = document.getElementById(editButtonTargetId);
let text = tgt?.closest('.row')?.querySelector('p');
text.contentEditable = 'plaintext-only';
text.setAttribute('aria-label', 'edit item');
text.setAttribute('aria-role', 'textbox');
text?.focus();
});
return edit;
}
const fillRow = (parent, textContent = '') => {
parent.classList.add('row');
let close = closeButton({ closeTargetId: parent.id });
let bookmark = bookmarkButton({ bookmarkTargetId: parent.id });
let text = document.createElement("p");
text.id = nanoid();
text.classList.add('text');
text.textContent = textContent;
let textArr = textContent.split(' ');
for (let word of textArr) {
if (word in index) {
index[word].push(parent.id);
} else {
index[word] = [parent.id];
}
}
text.addEventListener('blur', (e) => {
e.currentTarget.setAttribute('aria-role', 'paragraph');
e.currentTarget.removeAttribute('aria-label');
e.currentTarget.contentEditable = 'false';
saveText(`#${parent.id}`);
});
let edit = editButton({ editButtonTargetId: parent.id });
// parent.appendChild(handle);
parent.appendChild(bookmark);
parent.appendChild(text);
parent.appendChild(edit);
parent.appendChild(close);
return parent;
}
const body = document.body;
let root = document.createElement('div');
root.id = nanoid();
root.classList.add('root');
let addText = document.createElement('input');
addText.classList.add('add-text');
addText.addEventListener('keypress', (e) => {
// list.querySelectorAll('.row').forEach(el => {
// el.classList.add('hidden');
// })
if (e.target.value in index) {
let ids = index[e.target.value] || [];
for (let id of ids) {
let el = document.getElementById(id);
el.classList.add('hidden');
}
}
});
let list = document.createElement('ul');
list.id = nanoid();
list.classList.add('list');
let btnAddRow = document.createElement('button');
btnAddRow.id = nanoid();
btnAddRow.textContent = "new row";
btnAddRow.prepend(plusIcon());
btnAddRow.classList.add('btn-add-row');
btnAddRow.addEventListener('click', (e) => {
if (addText.value.trim() === "") return;
let row = document.createElement('li');
row.id = nanoid();
fillRow(row, addText.value);
list.prepend(row);
addText.value = '';
}, { passive: true });
body.addEventListener('keypress', (e) => {
switch (e.key) {
case 'Enter':
if (e.target === addText) {
btnAddRow.focus();
btnAddRow.click();
addText.focus();
}
break;
case 'b':
if (list.contains(e.target) && e.target.contentEditable !== true) {
e.preventDefault();
let row = e.target.closest('.row');
let bookmark = row.querySelector('.bookmark');
// bookmark.focus();
bookmark.click();
}
case 'n':
// let els = document.querySelector('.text[contenteditable="true"]');
if (list.contains(e.target) && e.target.contentEditable !== 'true') {
e.preventDefault();
addText.focus();
// btnAddRow.click();
addText.value = "";
}
break;
case 'j':
case "Down": // IE/Edge specific value
case "ArrowDown":
if (list.contains(e.target) && e.target.contentEditable !== 'true') {
let row = e.target.closest('.row');
let nextTgt = row.nextSibling?.querySelector('button');
nextTgt?.focus();
}
break;
case 'k':
case "Up":
case "ArrowUp":
if (list.contains(e.target) && e.target.contentEditable !== 'true') {
let row = e.target.closest('.row');
let nextTgt = row.previousElementSibling?.querySelector('button');
nextTgt?.focus();
}
break;
default:
return;
}
}, { passive: false });
let clearLink = document.createElement('a');
clearLink.textContent = 'Clear';
clearLink.setAttribute('href', '#');
let filterLink = document.createElement('a');
filterLink.textContent = 'Filter';
filterLink.setAttribute('href', '#');
let sortLink = document.createElement('a');
sortLink.textContent = 'Sort';
sortLink.setAttribute('href', '#');
let thing = document.createElement('div');
fillRow(thing, 'lol ');
let inputRow = document.createElement('div');
inputRow.classList.add('input-row');
inputRow.appendChild(addText);
inputRow.appendChild(btnAddRow);
let message = document.createElement('span');
let bulkActionsRow = document.createElement('div');
bulkActionsRow.classList.add('input-row');
bulkActionsRow.appendChild(clearLink);
bulkActionsRow.appendChild(filterLink);
bulkActionsRow.appendChild(sortLink);
bulkActionsRow.appendChild(message);
root.appendChild(bulkActionsRow);
root.appendChild(inputRow);
root.appendChild(list);
// body.appendChild(bookmarkIcon());
body.appendChild(root);
let strings = [
'one',
'two',
'three',
'four',
'five',
'six',
'seven',
'eight',
'nine',
'ten',
'eleven',
];
for (let i = 0; i < 2; i++) {
strings.push(...strings);
}
message.textContent = `${strings.length}`;
for (let str of strings) {
let row = document.createElement('li');
row.id = nanoid();
fillRow(row, str);
list.prepend(row);
}
body, html {
font-size: 16px;
}
body {
font-size: 1rem;
line-height: 1.5;
padding: 1rem;
}
.root {
margin: 0 auto;
max-width: 60ch;
}
.input-row {
display: flex;
}
.input-row + * {
margin-bottom: 1rem;
}
.add-text {
flex: 1;
padding: .25rem .5rem;
margin-right: .5rem;
}
.btn-add-row {
display: flex;
justify-content: space-around;
align-items: center;
}
.btn-add-row > svg {
margin: 0 8px 0 0;
}
.row {
margin: 0.5rem 0rem;
padding: .25rem;
box-shadow: 0 1px 3px rgba(0,0,0,0.12), 0 1px 2px rgba(0,0,0,0.24);
border-radius: 0.25rem;
background-color: #fafafa;
display: flex;
justify-content: space-between;
align-items: center;
transition-property: box-shadow 250ms cubic-bezier(.25,.8,.25,1);
/* transition-timing-function: ease-out; */
}
.row.collapsed {
animation: moveRowUpAndRemove 125ms 1 cubic-bezier(0.4, 0.0, 1, 1);
/* animation: moveRowUp 150ms 1 cubic-bezier(.25,.8,.25,1); */
/* disabled */
user-select: none;
}
.row.collapsed ~ .row {
animation: moveRowUp 125ms 1 cubic-bezier(0.4, 0.0, 1, 1);
/* animation-delay: 150ms; */
/* animation: moveRowUp 150ms 1 cubic-bezier(.25,.8,.25,1); */
user-select: none;
}
.row:hover {
/* background-color: #f0f0f0; */
box-shadow: 0 14px 28px rgba(0,0,0,0.25), 0 10px 10px rgba(0,0,0,0.22);
}
.list {
margin: 0;
padding: 0;
list-style-type: none;
user-select: none;
}
.hidden {
display: none;
}
.close {
--height: 2rem;
display: flex;
align-items: center;
justify-content: center;
background-color: transparent;
color: #999;
font-weight: bold;
cursor: pointer;
height: var(--height);
width: var(--height);
overflow: hidden;
border: 0;
will-change: color;
}
.close:hover {
color: #000;
}
.close:focus {
color: #000;
}
.edit {
--height: 2rem;
display: flex;
align-items: center;
justify-content: center;
background-color: transparent;
color: #999;
font-weight: bold;
cursor: pointer;
height: var(--height);
width: var(--height);
overflow: hidden;
border: 0;
will-change: color;
}
.edit:hover {
color: #000;
}
.edit:focus {
color: #000;
}
.bookmark {
--height: 2rem;
display: flex;
justify-content: center;
align-items: center;
background-color: transparent;
color: #999;
font-weight: bold;
cursor: pointer;
height: var(--height);
width: var(--height);
overflow: hidden;
padding: 0rem;
border: 0px;
will-change: color;
transition: d 0.35s;
}
.bookmark:hover {
color: #000;
}
.bookmark:focus {
color: #000;
}
.handle {
--height: 2rem;
display: flex;
justify-content: center;
align-items: center;
background-color: transparent;
color: #999;
font-weight: bold;
cursor: pointer;
height: var(--height);
width: var(--height);
overflow: hidden;
padding: 0rem;
border: 0px;
will-change: color;
}
.handle:hover {
color: #000;
}
.handle:focus {
color: #000;
}
.squared {
height: 1rem;
width: 1rem;
}
.text {
flex: 1;
padding: 0.25rem 0.5rem;
margin: 0 0.5rem;
cursor: default;
}
.text[contenteditable='true'] {
background-color: white;
}
.loading svg {
transform-origin: center;
animation: spin 1s infinite linear;
viewBox: '0 0 512 512';
}
.loading svg path {
d: path('M304 48c0 26.51-21.49 48-48 48s-48-21.49-48-48 21.49-48 48-48 48 21.49 48 48zm-48 368c-26.51 0-48 21.49-48 48s21.49 48 48 48 48-21.49 48-48-21.49-48-48-48zm208-208c-26.51 0-48 21.49-48 48s21.49 48 48 48 48-21.49 48-48-21.49-48-48-48zM96 256c0-26.51-21.49-48-48-48S0 229.49 0 256s21.49 48 48 48 48-21.49 48-48zm12.922 99.078c-26.51 0-48 21.49-48 48s21.49 48 48 48 48-21.49 48-48c0-26.509-21.491-48-48-48zm294.156 0c-26.51 0-48 21.49-48 48s21.49 48 48 48 48-21.49 48-48c0-26.509-21.49-48-48-48zM108.922 60.922c-26.51 0-48 21.49-48 48s21.49 48 48 48 48-21.49 48-48-21.491-48-48-48z');
}
.selected svg path {
d: path('M0 512V48C0 21.49 21.49 0 48 0h288c26.51 0 48 21.49 48 48v464L192 400 0 512z');
fill: currentColor;
}
.unselected svg path {
d: path('M336 0H48C21.49 0 0 21.49 0 48v464l192-112 192 112V48c0-26.51-21.49-48-48-48zm0 428.43l-144-84-144 84V54a6 6 0 0 1 6-6h276c3.314 0 6 2.683 6 5.996V428.43z');
fill: currentColor;
}
@keyframes spin {
from { transform: rotate(0deg); }
to { transform: rotate(360deg); }
}
@keyframes moveRowUp {
from { transform: translateY(0%); }
to { transform: translateY(calc(-100% - .25rem * 2)); }
}
@keyframes moveRowDown {
from { transform: translateY(calc(-100% - .25rem * 2)); }
to { transform: translateY(0%); }
}
@keyframes moveRowUpAndRemove {
from { transform: translateY(0%); opacity: 1; }
to { transform: translateY(calc(-100% - .25rem * 2)); opacity: .25; }
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment