A Pen by Thomas Ingalls on CodePen.
Created
June 19, 2020 03:24
-
-
Save thomasingalls/cb068500034c545d30432ca46e861122 to your computer and use it in GitHub Desktop.
JjYgZGy
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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