Feature update and UX improvements
-- added sorting options for article explorer popup (sort alphabetically or chronologically ) - added page routing with article ID to enable history navigation components - added focus trapping for sm-popup (UX)
This commit is contained in:
parent
e155bfc341
commit
70b13b7f5a
363
components.js
363
components.js
@ -1007,7 +1007,6 @@ smPopup.innerHTML = `
|
|||||||
--height: auto;
|
--height: auto;
|
||||||
--min-width: auto;
|
--min-width: auto;
|
||||||
--min-height: auto;
|
--min-height: auto;
|
||||||
--body-padding: 1.5rem;
|
|
||||||
--backdrop-background: rgba(0, 0, 0, 0.6);
|
--backdrop-background: rgba(0, 0, 0, 0.6);
|
||||||
--border-radius: 0.8rem 0.8rem 0 0;
|
--border-radius: 0.8rem 0.8rem 0 0;
|
||||||
}
|
}
|
||||||
@ -1087,13 +1086,13 @@ smPopup.innerHTML = `
|
|||||||
-ms-flex: 1;
|
-ms-flex: 1;
|
||||||
flex: 1;
|
flex: 1;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
padding: var(--body-padding);
|
padding: var(--body-padding, 1.5rem);
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
}
|
}
|
||||||
.hide{
|
.hide{
|
||||||
opacity: 0;
|
opacity: 0;
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
visiblity: none;
|
visibility: none;
|
||||||
}
|
}
|
||||||
@media screen and (min-width: 640px){
|
@media screen and (min-width: 640px){
|
||||||
:host{
|
:host{
|
||||||
@ -1169,7 +1168,10 @@ customElements.define('sm-popup', class extends HTMLElement {
|
|||||||
this.touchEndY = 0;
|
this.touchEndY = 0;
|
||||||
this.touchStartTime = 0;
|
this.touchStartTime = 0;
|
||||||
this.touchEndTime = 0;
|
this.touchEndTime = 0;
|
||||||
this.touchEndAnimataion = undefined;
|
this.touchEndAnimation = undefined;
|
||||||
|
this.focusable
|
||||||
|
this.autoFocus
|
||||||
|
this.mutationObserver
|
||||||
|
|
||||||
this.popupContainer = this.shadowRoot.querySelector('.popup-container');
|
this.popupContainer = this.shadowRoot.querySelector('.popup-container');
|
||||||
this.popup = this.shadowRoot.querySelector('.popup');
|
this.popup = this.shadowRoot.querySelector('.popup');
|
||||||
@ -1183,6 +1185,7 @@ customElements.define('sm-popup', class extends HTMLElement {
|
|||||||
this.handleTouchMove = this.handleTouchMove.bind(this);
|
this.handleTouchMove = this.handleTouchMove.bind(this);
|
||||||
this.handleTouchEnd = this.handleTouchEnd.bind(this);
|
this.handleTouchEnd = this.handleTouchEnd.bind(this);
|
||||||
this.movePopup = this.movePopup.bind(this);
|
this.movePopup = this.movePopup.bind(this);
|
||||||
|
this.detectFocus = this.detectFocus.bind(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
static get observedAttributes() {
|
static get observedAttributes() {
|
||||||
@ -1231,6 +1234,8 @@ customElements.define('sm-popup', class extends HTMLElement {
|
|||||||
this.popup.style.transform = 'none';
|
this.popup.style.transform = 'none';
|
||||||
document.body.style.overflow = 'hidden';
|
document.body.style.overflow = 'hidden';
|
||||||
document.body.style.top = `-${window.scrollY}px`;
|
document.body.style.top = `-${window.scrollY}px`;
|
||||||
|
const elementToFocus = this.autoFocus || this.focusable[0];
|
||||||
|
elementToFocus.tagName.includes('SM-') ? elementToFocus.focusIn() : elementToFocus.focus();
|
||||||
return this.popupStack;
|
return this.popupStack;
|
||||||
}
|
}
|
||||||
hide() {
|
hide() {
|
||||||
@ -1279,13 +1284,13 @@ customElements.define('sm-popup', class extends HTMLElement {
|
|||||||
handleTouchMove(e) {
|
handleTouchMove(e) {
|
||||||
if (this.touchStartY < e.changedTouches[0].clientY) {
|
if (this.touchStartY < e.changedTouches[0].clientY) {
|
||||||
this.offset = e.changedTouches[0].clientY - this.touchStartY;
|
this.offset = e.changedTouches[0].clientY - this.touchStartY;
|
||||||
this.touchEndAnimataion = window.requestAnimationFrame(() => this.movePopup());
|
this.touchEndAnimation = window.requestAnimationFrame(() => this.movePopup());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
handleTouchEnd(e) {
|
handleTouchEnd(e) {
|
||||||
this.touchEndTime = e.timeStamp;
|
this.touchEndTime = e.timeStamp;
|
||||||
cancelAnimationFrame(this.touchEndAnimataion);
|
cancelAnimationFrame(this.touchEndAnimation);
|
||||||
this.touchEndY = e.changedTouches[0].clientY;
|
this.touchEndY = e.changedTouches[0].clientY;
|
||||||
this.popup.style.transition = 'transform 0.3s';
|
this.popup.style.transition = 'transform 0.3s';
|
||||||
this.threshold = this.popup.getBoundingClientRect().height * 0.3;
|
this.threshold = this.popup.getBoundingClientRect().height * 0.3;
|
||||||
@ -1314,6 +1319,20 @@ customElements.define('sm-popup', class extends HTMLElement {
|
|||||||
this.popup.style.transform = `translateY(${this.offset}px)`;
|
this.popup.style.transform = `translateY(${this.offset}px)`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
detectFocus(e) {
|
||||||
|
if (e.code === 'Tab') {
|
||||||
|
const lastElement = this.focusable[this.focusable.length - 1];
|
||||||
|
const firstElement = this.focusable[0];
|
||||||
|
if (e.shiftKey && document.activeElement === firstElement) {
|
||||||
|
e.preventDefault();
|
||||||
|
lastElement.tagName.includes('SM-') ? lastElement.focusIn() : lastElement.focus();
|
||||||
|
} else if (!e.shiftKey && document.activeElement === lastElement) {
|
||||||
|
e.preventDefault();
|
||||||
|
firstElement.tagName.includes('SM-') ? firstElement.focusIn() : firstElement.focus();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
connectedCallback() {
|
connectedCallback() {
|
||||||
this.popupBodySlot.addEventListener('slotchange', () => {
|
this.popupBodySlot.addEventListener('slotchange', () => {
|
||||||
this.forms = this.querySelectorAll('sm-form');
|
this.forms = this.querySelectorAll('sm-form');
|
||||||
@ -1340,16 +1359,26 @@ customElements.define('sm-popup', class extends HTMLElement {
|
|||||||
});
|
});
|
||||||
resizeObserver.observe(this);
|
resizeObserver.observe(this);
|
||||||
|
|
||||||
|
this.mutationObserver = new MutationObserver(entries => {
|
||||||
|
entries.forEach(mutation => {
|
||||||
|
this.focusable = this.querySelectorAll('sm-button:not([disabled]), button:not([disabled]), [href], sm-input, input, sm-select, select, sm-checkbox, sm-textarea, textarea, [tabindex]:not([tabindex="-1"])')
|
||||||
|
this.autoFocus = this.querySelector('[autofocus]')
|
||||||
|
})
|
||||||
|
})
|
||||||
|
this.mutationObserver.observe(this, { attributes: true, childList: true, subtree: true })
|
||||||
|
|
||||||
|
this.addEventListener('keydown', this.detectFocus);
|
||||||
this.popupHeader.addEventListener('touchstart', this.handleTouchStart, { passive: true });
|
this.popupHeader.addEventListener('touchstart', this.handleTouchStart, { passive: true });
|
||||||
this.popupHeader.addEventListener('touchmove', this.handleTouchMove, { passive: true });
|
this.popupHeader.addEventListener('touchmove', this.handleTouchMove, { passive: true });
|
||||||
this.popupHeader.addEventListener('touchend', this.handleTouchEnd, { passive: true });
|
this.popupHeader.addEventListener('touchend', this.handleTouchEnd, { passive: true });
|
||||||
}
|
}
|
||||||
disconnectedCallback() {
|
disconnectedCallback() {
|
||||||
|
this.removeEventListener('keydown', this.detectFocus);
|
||||||
this.popupHeader.removeEventListener('touchstart', this.handleTouchStart, { passive: true });
|
this.popupHeader.removeEventListener('touchstart', this.handleTouchStart, { passive: true });
|
||||||
this.popupHeader.removeEventListener('touchmove', this.handleTouchMove, { passive: true });
|
this.popupHeader.removeEventListener('touchmove', this.handleTouchMove, { passive: true });
|
||||||
this.popupHeader.removeEventListener('touchend', this.handleTouchEnd, { passive: true });
|
this.popupHeader.removeEventListener('touchend', this.handleTouchEnd, { passive: true });
|
||||||
resizeObserver.unobserve();
|
resizeObserver.unobserve();
|
||||||
|
this.mutationObserver.disconnect()
|
||||||
}
|
}
|
||||||
attributeChangedCallback(name) {
|
attributeChangedCallback(name) {
|
||||||
if (name === 'open') {
|
if (name === 'open') {
|
||||||
@ -2329,7 +2358,7 @@ smSelect.innerHTML = `
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
<div class="select" >
|
<div class="select">
|
||||||
<div class="selection">
|
<div class="selection">
|
||||||
<div class="selected-option-text"></div>
|
<div class="selected-option-text"></div>
|
||||||
<svg class="icon toggle" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="24" height="24"><path fill="none" d="M0 0h24v24H0z"/><path d="M12 13.172l4.95-4.95 1.414 1.414L12 16 5.636 9.636 7.05 8.222z"/></svg>
|
<svg class="icon toggle" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="24" height="24"><path fill="none" d="M0 0h24v24H0z"/><path d="M12 13.172l4.95-4.95 1.414 1.414L12 16 5.636 9.636 7.05 8.222z"/></svg>
|
||||||
@ -2345,6 +2374,7 @@ customElements.define('sm-select', class extends HTMLElement {
|
|||||||
mode: 'open'
|
mode: 'open'
|
||||||
}).append(smSelect.content.cloneNode(true))
|
}).append(smSelect.content.cloneNode(true))
|
||||||
|
|
||||||
|
this.focusIn = this.focusIn.bind(this)
|
||||||
this.reset = this.reset.bind(this)
|
this.reset = this.reset.bind(this)
|
||||||
this.open = this.open.bind(this)
|
this.open = this.open.bind(this)
|
||||||
this.collapse = this.collapse.bind(this)
|
this.collapse = this.collapse.bind(this)
|
||||||
@ -2413,6 +2443,10 @@ customElements.define('sm-select', class extends HTMLElement {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
focusIn() {
|
||||||
|
this.selection.focus()
|
||||||
|
}
|
||||||
|
|
||||||
open() {
|
open() {
|
||||||
this.optionList.classList.remove('hide')
|
this.optionList.classList.remove('hide')
|
||||||
this.optionList.animate(this.slideDown, this.animationOptions)
|
this.optionList.animate(this.slideDown, this.animationOptions)
|
||||||
@ -2757,6 +2791,10 @@ customElements.define('sm-checkbox', class extends HTMLElement {
|
|||||||
return this.getAttribute('value')
|
return this.getAttribute('value')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
focusIn() {
|
||||||
|
this.focus()
|
||||||
|
}
|
||||||
|
|
||||||
reset() {
|
reset() {
|
||||||
this.removeAttribute('checked')
|
this.removeAttribute('checked')
|
||||||
}
|
}
|
||||||
@ -3447,7 +3485,6 @@ textField.innerHTML = `
|
|||||||
align-items: center;
|
align-items: center;
|
||||||
}
|
}
|
||||||
.text{
|
.text{
|
||||||
padding: 0.6rem 0;
|
|
||||||
transition: background-color 0.3s;
|
transition: background-color 0.3s;
|
||||||
border-bottom: 0.15rem solid transparent;
|
border-bottom: 0.15rem solid transparent;
|
||||||
overflow-wrap: break-word;
|
overflow-wrap: break-word;
|
||||||
@ -3657,4 +3694,314 @@ customElements.define('text-field', class extends HTMLElement {
|
|||||||
this.editButton.removeEventListener('click', this.setEditable)
|
this.editButton.removeEventListener('click', this.setEditable)
|
||||||
this.saveButton.removeEventListener('click', this.setNonEditable)
|
this.saveButton.removeEventListener('click', this.setNonEditable)
|
||||||
}
|
}
|
||||||
|
})
|
||||||
|
const smMenu = document.createElement('template')
|
||||||
|
smMenu.innerHTML = `
|
||||||
|
<style>
|
||||||
|
*{
|
||||||
|
padding: 0;
|
||||||
|
margin: 0;
|
||||||
|
-webkit-box-sizing: border-box;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
:host{
|
||||||
|
display: -webkit-inline-box;
|
||||||
|
display: -ms-inline-flexbox;
|
||||||
|
display: inline-flex;
|
||||||
|
|
||||||
|
}
|
||||||
|
.menu{
|
||||||
|
display: -ms-grid;
|
||||||
|
display: grid;
|
||||||
|
place-items: center;
|
||||||
|
height: 2rem;
|
||||||
|
width: 2rem;
|
||||||
|
outline: none;
|
||||||
|
}
|
||||||
|
.icon {
|
||||||
|
position: absolute;
|
||||||
|
fill: rgba(var(--text-color), 0.7);
|
||||||
|
height: 2.4rem;
|
||||||
|
width: 2.4rem;
|
||||||
|
padding: 0.5rem;
|
||||||
|
border-radius: 2rem;
|
||||||
|
-webkit-transition: background 0.3s;
|
||||||
|
-o-transition: background 0.3s;
|
||||||
|
transition: background 0.3s;
|
||||||
|
}
|
||||||
|
.select{
|
||||||
|
position: relative;
|
||||||
|
display: -webkit-box;
|
||||||
|
display: -ms-flexbox;
|
||||||
|
display: flex;
|
||||||
|
-webkit-box-orient: vertical;
|
||||||
|
-webkit-box-direction: normal;
|
||||||
|
-ms-flex-direction: column;
|
||||||
|
flex-direction: column;
|
||||||
|
cursor: pointer;
|
||||||
|
width: 100%;
|
||||||
|
-webkit-tap-highlight-color: transparent;
|
||||||
|
}
|
||||||
|
.menu:focus .icon,
|
||||||
|
.focused{
|
||||||
|
background: rgba(var(--text-color), 0.1);
|
||||||
|
}
|
||||||
|
:host([align-options="left"]) .options{
|
||||||
|
left: 0;
|
||||||
|
}
|
||||||
|
:host([align-options="right"]) .options{
|
||||||
|
right: 0;
|
||||||
|
}
|
||||||
|
.options{
|
||||||
|
top: 100%;
|
||||||
|
padding: 0.3rem;
|
||||||
|
overflow: hidden auto;
|
||||||
|
position: absolute;
|
||||||
|
display: -webkit-box;
|
||||||
|
display: -ms-flexbox;
|
||||||
|
display: flex;
|
||||||
|
min-width: -webkit-max-content;
|
||||||
|
min-width: -moz-max-content;
|
||||||
|
min-width: max-content;
|
||||||
|
-webkit-box-orient: vertical;
|
||||||
|
-webkit-box-direction: normal;
|
||||||
|
-ms-flex-direction: column;
|
||||||
|
flex-direction: column;
|
||||||
|
background: var(--background, rgba(var(--background-color), 1));
|
||||||
|
border-radius: var(--border-radius, 0.5rem);
|
||||||
|
z-index: 1;
|
||||||
|
-webkit-box-shadow: 0 0.5rem 1.5rem -0.5rem rgba(0,0,0,0.3);
|
||||||
|
box-shadow: 0 0.5rem 1.5rem -0.5rem rgba(0,0,0,0.3);
|
||||||
|
bottom: auto;
|
||||||
|
}
|
||||||
|
.hide{
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
@media screen and (max-width: 640px){
|
||||||
|
.options{
|
||||||
|
position: fixed;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
top: auto;
|
||||||
|
border-radius: 0.5rem 0.5rem 0 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@media (hover: hover){
|
||||||
|
.menu:hover .icon{
|
||||||
|
background: rgba(var(--text-color), 0.1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
<div class="select">
|
||||||
|
<div class="menu" tabindex="0">
|
||||||
|
<svg class="icon" xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 0 24 24" width="24px" fill="#000000"><path d="M0 0h24v24H0V0z" fill="none"/><path d="M12 8c1.1 0 2-.9 2-2s-.9-2-2-2-2 .9-2 2 .9 2 2 2zm0 2c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2zm0 6c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2z"/></svg>
|
||||||
|
</div>
|
||||||
|
<div class="options hide">
|
||||||
|
<slot></slot>
|
||||||
|
</div>
|
||||||
|
</div>`;
|
||||||
|
customElements.define('sm-menu', class extends HTMLElement {
|
||||||
|
constructor() {
|
||||||
|
super()
|
||||||
|
this.attachShadow({
|
||||||
|
mode: 'open'
|
||||||
|
}).append(smMenu.content.cloneNode(true))
|
||||||
|
|
||||||
|
this.isOpen = false;
|
||||||
|
this.availableOptions
|
||||||
|
this.containerDimensions
|
||||||
|
this.animOptions = {
|
||||||
|
duration: 200,
|
||||||
|
easing: 'ease'
|
||||||
|
}
|
||||||
|
|
||||||
|
this.optionList = this.shadowRoot.querySelector('.options')
|
||||||
|
this.menu = this.shadowRoot.querySelector('.menu')
|
||||||
|
this.icon = this.shadowRoot.querySelector('.icon')
|
||||||
|
|
||||||
|
this.expand = this.expand.bind(this)
|
||||||
|
this.collapse = this.collapse.bind(this)
|
||||||
|
this.toggle = this.toggle.bind(this)
|
||||||
|
this.handleKeyDown = this.handleKeyDown.bind(this)
|
||||||
|
this.handleClickOutside = this.handleClickOutside.bind(this)
|
||||||
|
|
||||||
|
}
|
||||||
|
static get observedAttributes() {
|
||||||
|
return ['value']
|
||||||
|
}
|
||||||
|
get value() {
|
||||||
|
return this.getAttribute('value')
|
||||||
|
}
|
||||||
|
set value(val) {
|
||||||
|
this.setAttribute('value', val)
|
||||||
|
}
|
||||||
|
expand() {
|
||||||
|
if (!this.isOpen) {
|
||||||
|
this.optionList.classList.remove('hide')
|
||||||
|
this.optionList.animate([
|
||||||
|
{
|
||||||
|
transform: window.innerWidth < 640 ? 'translateY(1.5rem)' : 'translateY(-1rem)',
|
||||||
|
opacity: '0'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
transform: 'none',
|
||||||
|
opacity: '1'
|
||||||
|
},
|
||||||
|
], this.animOptions)
|
||||||
|
.onfinish = () => {
|
||||||
|
this.isOpen = true
|
||||||
|
this.icon.classList.add('focused')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
collapse() {
|
||||||
|
if (this.isOpen) {
|
||||||
|
this.optionList.animate([
|
||||||
|
{
|
||||||
|
transform: 'none',
|
||||||
|
opacity: '1'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
transform: window.innerWidth < 640 ? 'translateY(1.5rem)' : 'translateY(-1rem)',
|
||||||
|
opacity: '0'
|
||||||
|
},
|
||||||
|
], this.animOptions)
|
||||||
|
.onfinish = () => {
|
||||||
|
this.isOpen = false
|
||||||
|
this.icon.classList.remove('focused')
|
||||||
|
this.optionList.classList.add('hide')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
toggle() {
|
||||||
|
if (!this.isOpen) {
|
||||||
|
this.expand()
|
||||||
|
} else {
|
||||||
|
this.collapse()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
handleKeyDown(e) {
|
||||||
|
// If key is pressed on menu button
|
||||||
|
if (e.target === this) {
|
||||||
|
if (e.code === 'ArrowDown') {
|
||||||
|
e.preventDefault()
|
||||||
|
this.availableOptions[0].focus()
|
||||||
|
}
|
||||||
|
else if (e.code === 'Enter' || e.code === 'Space') {
|
||||||
|
e.preventDefault()
|
||||||
|
this.toggle()
|
||||||
|
}
|
||||||
|
} else { // If key is pressed over menu options
|
||||||
|
if (e.code === 'ArrowUp') {
|
||||||
|
e.preventDefault()
|
||||||
|
if (document.activeElement.previousElementSibling) {
|
||||||
|
document.activeElement.previousElementSibling.focus()
|
||||||
|
} else {
|
||||||
|
this.availableOptions[this.availableOptions.length - 1].focus()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (e.code === 'ArrowDown') {
|
||||||
|
e.preventDefault()
|
||||||
|
if (document.activeElement.nextElementSibling) {
|
||||||
|
document.activeElement.nextElementSibling.focus()
|
||||||
|
} else {
|
||||||
|
this.availableOptions[0].focus()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (e.code === 'Enter' || e.code === 'Space') {
|
||||||
|
e.preventDefault()
|
||||||
|
e.target.click()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
handleClickOutside(e) {
|
||||||
|
if (!this.contains(e.target) && e.button !== 2) {
|
||||||
|
this.collapse()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
connectedCallback() {
|
||||||
|
this.setAttribute('role', 'listbox')
|
||||||
|
this.setAttribute('aria-label', 'dropdown menu')
|
||||||
|
const slot = this.shadowRoot.querySelector('.options slot')
|
||||||
|
slot.addEventListener('slotchange', e => {
|
||||||
|
this.availableOptions = e.target.assignedElements()
|
||||||
|
this.containerDimensions = this.optionList.getBoundingClientRect()
|
||||||
|
});
|
||||||
|
this.addEventListener('click', this.toggle)
|
||||||
|
this.addEventListener('keydown', this.handleKeyDown)
|
||||||
|
document.addEventListener('mousedown', this.handleClickOutside)
|
||||||
|
}
|
||||||
|
disconnectedCallback() {
|
||||||
|
this.removeEventListener('click', this.toggle)
|
||||||
|
this.removeEventListener('keydown', this.handleKeyDown)
|
||||||
|
document.removeEventListener('mousedown', this.handleClickOutside)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// option
|
||||||
|
const menuOption = document.createElement('template')
|
||||||
|
menuOption.innerHTML = `
|
||||||
|
<style>
|
||||||
|
*{
|
||||||
|
padding: 0;
|
||||||
|
margin: 0;
|
||||||
|
-webkit-box-sizing: border-box;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
:host{
|
||||||
|
display: -webkit-box;
|
||||||
|
display: -ms-flexbox;
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
.option{
|
||||||
|
display: -webkit-box;
|
||||||
|
display: -ms-flexbox;
|
||||||
|
display: flex;
|
||||||
|
min-width: 100%;
|
||||||
|
padding: var(--padding, 0.6rem 1rem);
|
||||||
|
cursor: pointer;
|
||||||
|
overflow-wrap: break-word;
|
||||||
|
white-space: nowrap;
|
||||||
|
outline: none;
|
||||||
|
user-select: none;
|
||||||
|
border-radius: 0.3rem;
|
||||||
|
-webkit-box-align: center;
|
||||||
|
-ms-flex-align: center;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
:host(:focus){
|
||||||
|
outline: none;
|
||||||
|
background: rgba(var(--text-color), 0.1);
|
||||||
|
}
|
||||||
|
@media (any-hover: hover){
|
||||||
|
.option{
|
||||||
|
transition: background-color 0.2s;
|
||||||
|
}
|
||||||
|
.option:hover{
|
||||||
|
background: rgba(var(--text-color), 0.1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
<div class="option">
|
||||||
|
<slot></slot>
|
||||||
|
</div>`;
|
||||||
|
customElements.define('menu-option', class extends HTMLElement {
|
||||||
|
constructor() {
|
||||||
|
super()
|
||||||
|
this.attachShadow({
|
||||||
|
mode: 'open'
|
||||||
|
}).append(menuOption.content.cloneNode(true))
|
||||||
|
}
|
||||||
|
|
||||||
|
connectedCallback() {
|
||||||
|
this.setAttribute('role', 'option')
|
||||||
|
this.setAttribute('tabindex', '0')
|
||||||
|
this.addEventListener('keyup', e => {
|
||||||
|
if (e.code === 'Enter' || e.code === 'Space') {
|
||||||
|
e.preventDefault()
|
||||||
|
this.click()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
})
|
})
|
||||||
102
css/main.css
102
css/main.css
@ -22,8 +22,7 @@ body {
|
|||||||
}
|
}
|
||||||
body,
|
body,
|
||||||
body * {
|
body * {
|
||||||
--accent-color: #504dff;
|
--accent-color: rgb(0, 156, 78);
|
||||||
--accent-color--light: #eeeeff;
|
|
||||||
--text-color: 36, 36, 36;
|
--text-color: 36, 36, 36;
|
||||||
--background-color: 248, 248, 248;
|
--background-color: 248, 248, 248;
|
||||||
--foreground-color: rgb(255, 255, 255);
|
--foreground-color: rgb(255, 255, 255);
|
||||||
@ -36,8 +35,7 @@ body * {
|
|||||||
|
|
||||||
body[data-theme=dark],
|
body[data-theme=dark],
|
||||||
body[data-theme=dark] * {
|
body[data-theme=dark] * {
|
||||||
--accent-color: #a3a1ff;
|
--accent-color: rgb(14, 230, 122);
|
||||||
--accent-color--light: rgba(142, 140, 255, 0.06);
|
|
||||||
--text-color: 230, 230, 230;
|
--text-color: 230, 230, 230;
|
||||||
--text-color-light: 170, 170, 170;
|
--text-color-light: 170, 170, 170;
|
||||||
--background-color: 10, 10, 10;
|
--background-color: 10, 10, 10;
|
||||||
@ -501,6 +499,14 @@ sm-checkbox {
|
|||||||
-webkit-tap-highlight-color: transparent;
|
-webkit-tap-highlight-color: transparent;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
sm-menu {
|
||||||
|
--background: var(--foreground-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
menu-option {
|
||||||
|
font-size: 0.9rem;
|
||||||
|
}
|
||||||
|
|
||||||
.warning {
|
.warning {
|
||||||
background-color: khaki;
|
background-color: khaki;
|
||||||
color: rgba(0, 0, 0, 0.7);
|
color: rgba(0, 0, 0, 0.7);
|
||||||
@ -524,11 +530,11 @@ sm-checkbox {
|
|||||||
#main_header {
|
#main_header {
|
||||||
display: grid;
|
display: grid;
|
||||||
gap: 1rem;
|
gap: 1rem;
|
||||||
padding: 1rem 1.5rem;
|
padding: 1rem;
|
||||||
-webkit-box-align: center;
|
-webkit-box-align: center;
|
||||||
-ms-flex-align: center;
|
-ms-flex-align: center;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
grid-template-columns: auto 1fr auto auto;
|
grid-template-columns: 1fr auto auto;
|
||||||
grid-column: 1/-1;
|
grid-column: 1/-1;
|
||||||
background-color: var(--foreground-color);
|
background-color: var(--foreground-color);
|
||||||
}
|
}
|
||||||
@ -556,6 +562,24 @@ sm-checkbox {
|
|||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#article_list_popup {
|
||||||
|
--width: min(64rem, 100%);
|
||||||
|
--min-height: 70vh;
|
||||||
|
}
|
||||||
|
#article_list_popup::part(popup-header), #article_list_popup::part(popup-body) {
|
||||||
|
padding: 0.5rem;
|
||||||
|
}
|
||||||
|
#article_list_popup .popup__header {
|
||||||
|
padding: 1rem;
|
||||||
|
gap: 1rem;
|
||||||
|
padding-bottom: 0;
|
||||||
|
grid-template-columns: minmax(0, 1fr);
|
||||||
|
}
|
||||||
|
|
||||||
|
#article_list {
|
||||||
|
grid-template-columns: repeat(auto-fill, minmax(30ch, 1fr));
|
||||||
|
}
|
||||||
|
|
||||||
.article-link {
|
.article-link {
|
||||||
padding: 1rem;
|
padding: 1rem;
|
||||||
color: rgba(var(--text-color), 0.8);
|
color: rgba(var(--text-color), 0.8);
|
||||||
@ -577,6 +601,40 @@ sm-checkbox {
|
|||||||
color: rgba(var(--text-color), 0.8);
|
color: rgba(var(--text-color), 0.8);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#edit_sections_popup {
|
||||||
|
--body-padding: 1.2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
#section_list_container {
|
||||||
|
background-color: rgba(var(--text-color), 0.06);
|
||||||
|
margin: 1rem 0;
|
||||||
|
padding: 0 0.8rem;
|
||||||
|
border-radius: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.section-card {
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
.section-card:not(.section-card--new) {
|
||||||
|
padding: 0.8rem 0;
|
||||||
|
}
|
||||||
|
.section-card--new input {
|
||||||
|
border: none;
|
||||||
|
font-size: inherit;
|
||||||
|
background: inherit;
|
||||||
|
color: inherit;
|
||||||
|
width: 100%;
|
||||||
|
padding: 0.8rem 0;
|
||||||
|
}
|
||||||
|
.section-card--new input:focus {
|
||||||
|
outline: var(--accent-color) solid;
|
||||||
|
}
|
||||||
|
|
||||||
|
#insert_section_button {
|
||||||
|
-ms-flex-item-align: start;
|
||||||
|
align-self: flex-start;
|
||||||
|
}
|
||||||
|
|
||||||
#article_wrapper {
|
#article_wrapper {
|
||||||
display: -webkit-box;
|
display: -webkit-box;
|
||||||
display: -ms-flexbox;
|
display: -ms-flexbox;
|
||||||
@ -608,7 +666,7 @@ sm-checkbox {
|
|||||||
display: -webkit-box;
|
display: -webkit-box;
|
||||||
display: -ms-flexbox;
|
display: -ms-flexbox;
|
||||||
display: flex;
|
display: flex;
|
||||||
gap: 0.5rem;
|
gap: 1rem;
|
||||||
overflow-x: auto;
|
overflow-x: auto;
|
||||||
-ms-flex-negative: 0;
|
-ms-flex-negative: 0;
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
@ -623,7 +681,7 @@ sm-checkbox {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.content-card {
|
.content-card {
|
||||||
scroll-snap-align: center;
|
scroll-snap-align: start;
|
||||||
width: min(46ch, 100%);
|
width: min(46ch, 100%);
|
||||||
-ms-flex-negative: 0;
|
-ms-flex-negative: 0;
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
@ -806,7 +864,8 @@ sm-checkbox {
|
|||||||
background-color: #00e67650;
|
background-color: #00e67650;
|
||||||
}
|
}
|
||||||
.entry__changes .removed {
|
.entry__changes .removed {
|
||||||
background-color: #ff3a4a50;
|
-webkit-text-decoration-color: red;
|
||||||
|
text-decoration-color: red;
|
||||||
}
|
}
|
||||||
|
|
||||||
@media screen and (max-width: 40rem) and (any-hover: none) {
|
@media screen and (max-width: 40rem) and (any-hover: none) {
|
||||||
@ -819,6 +878,17 @@ sm-checkbox {
|
|||||||
--padding: 0.9rem 1.6rem;
|
--padding: 0.9rem 1.6rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#article_name_wrapper {
|
||||||
|
grid-row: 2/3;
|
||||||
|
grid-column: 1/-1;
|
||||||
|
-webkit-box-pack: start;
|
||||||
|
-ms-flex-pack: start;
|
||||||
|
justify-content: flex-start;
|
||||||
|
}
|
||||||
|
#article_name_wrapper sm-menu {
|
||||||
|
margin-left: auto;
|
||||||
|
}
|
||||||
|
|
||||||
.hide-on-mobile {
|
.hide-on-mobile {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
@ -861,6 +931,11 @@ sm-checkbox {
|
|||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#main_header {
|
||||||
|
padding: 1rem 1.5rem;
|
||||||
|
grid-template-columns: auto 1fr auto auto;
|
||||||
|
}
|
||||||
|
|
||||||
#main_page.active-sidebar {
|
#main_page.active-sidebar {
|
||||||
height: 100%;
|
height: 100%;
|
||||||
overflow-y: hidden;
|
overflow-y: hidden;
|
||||||
@ -871,6 +946,15 @@ sm-checkbox {
|
|||||||
height: 100%;
|
height: 100%;
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#article_list_popup .popup__header {
|
||||||
|
grid-template-columns: auto 1fr auto;
|
||||||
|
padding-bottom: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
#article_list_search {
|
||||||
|
width: 16rem;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@media (any-hover: hover) {
|
@media (any-hover: hover) {
|
||||||
::-webkit-scrollbar {
|
::-webkit-scrollbar {
|
||||||
|
|||||||
2
css/main.min.css
vendored
2
css/main.min.css
vendored
File diff suppressed because one or more lines are too long
@ -18,8 +18,7 @@ body {
|
|||||||
body {
|
body {
|
||||||
&,
|
&,
|
||||||
* {
|
* {
|
||||||
--accent-color: #504dff;
|
--accent-color: rgb(0, 156, 78);
|
||||||
--accent-color--light: #eeeeff;
|
|
||||||
--text-color: 36, 36, 36;
|
--text-color: 36, 36, 36;
|
||||||
--background-color: 248, 248, 248;
|
--background-color: 248, 248, 248;
|
||||||
--foreground-color: rgb(255, 255, 255);
|
--foreground-color: rgb(255, 255, 255);
|
||||||
@ -37,8 +36,7 @@ body {
|
|||||||
body[data-theme="dark"] {
|
body[data-theme="dark"] {
|
||||||
&,
|
&,
|
||||||
* {
|
* {
|
||||||
--accent-color: #a3a1ff;
|
--accent-color: rgb(14, 230, 122);
|
||||||
--accent-color--light: rgba(142, 140, 255, 0.06);
|
|
||||||
--text-color: 230, 230, 230;
|
--text-color: 230, 230, 230;
|
||||||
--text-color-light: 170, 170, 170;
|
--text-color-light: 170, 170, 170;
|
||||||
--background-color: 10, 10, 10;
|
--background-color: 10, 10, 10;
|
||||||
@ -440,6 +438,12 @@ sm-checkbox {
|
|||||||
--width: 1rem;
|
--width: 1rem;
|
||||||
-webkit-tap-highlight-color: transparent;
|
-webkit-tap-highlight-color: transparent;
|
||||||
}
|
}
|
||||||
|
sm-menu {
|
||||||
|
--background: var(--foreground-color);
|
||||||
|
}
|
||||||
|
menu-option {
|
||||||
|
font-size: 0.9rem;
|
||||||
|
}
|
||||||
.warning {
|
.warning {
|
||||||
background-color: khaki;
|
background-color: khaki;
|
||||||
color: rgba(0, 0, 0, 0.7);
|
color: rgba(0, 0, 0, 0.7);
|
||||||
@ -461,9 +465,9 @@ sm-checkbox {
|
|||||||
#main_header {
|
#main_header {
|
||||||
display: grid;
|
display: grid;
|
||||||
gap: 1rem;
|
gap: 1rem;
|
||||||
padding: 1rem 1.5rem;
|
padding: 1rem;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
grid-template-columns: auto 1fr auto auto;
|
grid-template-columns: 1fr auto auto;
|
||||||
grid-column: 1/-1;
|
grid-column: 1/-1;
|
||||||
background-color: var(--foreground-color);
|
background-color: var(--foreground-color);
|
||||||
}
|
}
|
||||||
@ -489,6 +493,24 @@ sm-checkbox {
|
|||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#article_list_popup {
|
||||||
|
--width: min(64rem, 100%);
|
||||||
|
--min-height: 70vh;
|
||||||
|
&::part(popup-header),
|
||||||
|
&::part(popup-body) {
|
||||||
|
padding: 0.5rem;
|
||||||
|
}
|
||||||
|
.popup__header {
|
||||||
|
padding: 1rem;
|
||||||
|
gap: 1rem;
|
||||||
|
padding-bottom: 0;
|
||||||
|
grid-template-columns: minmax(0, 1fr);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#article_list {
|
||||||
|
grid-template-columns: repeat(auto-fill, minmax(30ch, 1fr));
|
||||||
|
}
|
||||||
|
|
||||||
.article-link {
|
.article-link {
|
||||||
padding: 1rem;
|
padding: 1rem;
|
||||||
color: rgba(var(--text-color), 0.8);
|
color: rgba(var(--text-color), 0.8);
|
||||||
@ -509,6 +531,38 @@ sm-checkbox {
|
|||||||
color: rgba(var(--text-color), 0.8);
|
color: rgba(var(--text-color), 0.8);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#edit_sections_popup {
|
||||||
|
--body-padding: 1.2rem;
|
||||||
|
}
|
||||||
|
#section_list_container {
|
||||||
|
background-color: rgba(var(--text-color), 0.06);
|
||||||
|
margin: 1rem 0;
|
||||||
|
padding: 0 0.8rem;
|
||||||
|
border-radius: 0.5rem;
|
||||||
|
}
|
||||||
|
.section-card {
|
||||||
|
font-weight: 500;
|
||||||
|
&:not(.section-card--new) {
|
||||||
|
padding: 0.8rem 0;
|
||||||
|
}
|
||||||
|
&--new {
|
||||||
|
input {
|
||||||
|
border: none;
|
||||||
|
font-size: inherit;
|
||||||
|
background: inherit;
|
||||||
|
color: inherit;
|
||||||
|
width: 100%;
|
||||||
|
padding: 0.8rem 0;
|
||||||
|
&:focus {
|
||||||
|
outline: var(--accent-color) solid;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#insert_section_button {
|
||||||
|
align-self: flex-start;
|
||||||
|
}
|
||||||
|
|
||||||
#article_wrapper {
|
#article_wrapper {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
@ -531,7 +585,7 @@ sm-checkbox {
|
|||||||
|
|
||||||
.article-section {
|
.article-section {
|
||||||
display: flex;
|
display: flex;
|
||||||
gap: 0.5rem;
|
gap: 1rem;
|
||||||
overflow-x: auto;
|
overflow-x: auto;
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
scroll-snap-type: x mandatory;
|
scroll-snap-type: x mandatory;
|
||||||
@ -543,7 +597,7 @@ sm-checkbox {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
.content-card {
|
.content-card {
|
||||||
scroll-snap-align: center;
|
scroll-snap-align: start;
|
||||||
width: min(46ch, 100%);
|
width: min(46ch, 100%);
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
border-radius: 0.5rem;
|
border-radius: 0.5rem;
|
||||||
@ -704,7 +758,7 @@ sm-checkbox {
|
|||||||
background-color: #00e67650;
|
background-color: #00e67650;
|
||||||
}
|
}
|
||||||
.removed {
|
.removed {
|
||||||
background-color: #ff3a4a50;
|
text-decoration-color: red;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -719,6 +773,14 @@ sm-checkbox {
|
|||||||
sm-button {
|
sm-button {
|
||||||
--padding: 0.9rem 1.6rem;
|
--padding: 0.9rem 1.6rem;
|
||||||
}
|
}
|
||||||
|
#article_name_wrapper {
|
||||||
|
grid-row: 2/3;
|
||||||
|
grid-column: 1/-1;
|
||||||
|
justify-content: flex-start;
|
||||||
|
sm-menu {
|
||||||
|
margin-left: auto;
|
||||||
|
}
|
||||||
|
}
|
||||||
.hide-on-mobile {
|
.hide-on-mobile {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
@ -755,6 +817,10 @@ sm-checkbox {
|
|||||||
.hide-on-desktop {
|
.hide-on-desktop {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
#main_header {
|
||||||
|
padding: 1rem 1.5rem;
|
||||||
|
grid-template-columns: auto 1fr auto auto;
|
||||||
|
}
|
||||||
#main_page {
|
#main_page {
|
||||||
&.active-sidebar {
|
&.active-sidebar {
|
||||||
height: 100%;
|
height: 100%;
|
||||||
@ -767,6 +833,15 @@ sm-checkbox {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
#article_list_popup {
|
||||||
|
.popup__header {
|
||||||
|
grid-template-columns: auto 1fr auto;
|
||||||
|
padding-bottom: 1rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#article_list_search {
|
||||||
|
width: 16rem;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@media (any-hover: hover) {
|
@media (any-hover: hover) {
|
||||||
::-webkit-scrollbar {
|
::-webkit-scrollbar {
|
||||||
|
|||||||
259
index.html
259
index.html
@ -12,6 +12,7 @@
|
|||||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||||
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400..700&display=swap" rel="stylesheet">
|
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400..700&display=swap" rel="stylesheet">
|
||||||
<script src="purify.min.js" defer></script>
|
<script src="purify.min.js" defer></script>
|
||||||
|
<script src="https://cdn.jsdelivr.net/npm/fuse.js@6.4.6" defer></script>
|
||||||
<script id="floGlobals">
|
<script id="floGlobals">
|
||||||
/* Constants for FLO blockchain operations !!Make sure to add this at begining!! */
|
/* Constants for FLO blockchain operations !!Make sure to add this at begining!! */
|
||||||
const floGlobals = {
|
const floGlobals = {
|
||||||
@ -71,23 +72,36 @@
|
|||||||
<h4>Content Collab</h4>
|
<h4>Content Collab</h4>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div id="article_name_wrapper" class="flex align-center justify-center">
|
<div id="article_name_wrapper" class="flex gap-0-5 align-center justify-center">
|
||||||
<button title="Create new article" class="button__icon--left"
|
<button title="Show all articles list" onclick="showPopup('article_list_popup')">
|
||||||
onclick="showPopup('create_article_popup')">
|
|
||||||
<svg class="icon" xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 0 24 24" width="24px"
|
<svg class="icon" xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 0 24 24" width="24px"
|
||||||
fill="#000000">
|
fill="#000000">
|
||||||
<path d="M0 0h24v24H0V0z" fill="none" />
|
<path d="M0 0h24v24H0V0z" fill="none" />
|
||||||
<path d="M19 13h-6v6h-2v-6H5v-2h6V5h2v6h6v2z" />
|
<path
|
||||||
|
d="M4 8h4V4H4v4zm6 12h4v-4h-4v4zm-6 0h4v-4H4v4zm0-6h4v-4H4v4zm6 0h4v-4h-4v4zm6-10v4h4V4h-4zm-6 4h4V4h-4v4zm6 6h4v-4h-4v4zm0 6h4v-4h-4v4z" />
|
||||||
</svg>
|
</svg>
|
||||||
</button>
|
</button>
|
||||||
<text-field id="current_article_title"></text-field>
|
<text-field id="current_article_title"></text-field>
|
||||||
<button class="button__icon--right" onclick="showPopup('article_list_popup')">
|
<sm-menu align-options="right">
|
||||||
<svg class="icon" xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 0 24 24" width="24px"
|
<menu-option onclick="showPopup('create_article_popup')">
|
||||||
fill="#000000">
|
<svg class="icon button__icon--left" xmlns="http://www.w3.org/2000/svg" height="24px"
|
||||||
<path d="M24 24H0V0h24v24z" fill="none" opacity=".87" />
|
viewBox="0 0 24 24" width="24px" fill="#000000">
|
||||||
<path d="M16.59 8.59L12 13.17 7.41 8.59 6 10l6 6 6-6-1.41-1.41z" />
|
<path d="M0 0h24v24H0V0z" fill="none" />
|
||||||
</svg>
|
<path d="M19 13h-6v6h-2v-6H5v-2h6V5h2v6h6v2z" />
|
||||||
</button>
|
</svg>
|
||||||
|
Create new article
|
||||||
|
</menu-option>
|
||||||
|
<menu-option onclick="showPopup('edit_sections_popup')">
|
||||||
|
<svg class="icon button__icon--left" title="edit" xmlns="http://www.w3.org/2000/svg"
|
||||||
|
height="24px" viewBox="0 0 24 24" width="24px" fill="#000000">
|
||||||
|
<path d="M0 0h24v24H0z" fill="none"></path>
|
||||||
|
<path
|
||||||
|
d="M3 17.25V21h3.75L17.81 9.94l-3.75-3.75L3 17.25zM20.71 7.04c.39-.39.39-1.02 0-1.41l-2.34-2.34c-.39-.39-1.02-.39-1.41 0l-1.83 1.83 3.75 3.75 1.83-1.83z">
|
||||||
|
</path>
|
||||||
|
</svg>
|
||||||
|
Edit sections
|
||||||
|
</menu-option>
|
||||||
|
</sm-menu>
|
||||||
</div>
|
</div>
|
||||||
<theme-toggle></theme-toggle>
|
<theme-toggle></theme-toggle>
|
||||||
<button onclick="showPopup('user_popup')">
|
<button onclick="showPopup('user_popup')">
|
||||||
@ -134,25 +148,6 @@
|
|||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<div id="article_wrapper"></div>
|
<div id="article_wrapper"></div>
|
||||||
<!-- <div class="grid page-layout">
|
|
||||||
<div id="action_button_group" class="flex align-center gap-0-5">
|
|
||||||
<span>
|
|
||||||
<b>
|
|
||||||
Add
|
|
||||||
</b>
|
|
||||||
</span>
|
|
||||||
<button class="actionable-button" title="Add paragraph">
|
|
||||||
<svg class="icon" xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 0 24 24"
|
|
||||||
width="24px" fill="#000000">
|
|
||||||
<path d="M0 0h24v24H0V0z" fill="none" />
|
|
||||||
<path d="M14 17H4v2h10v-2zm6-8H4v2h16V9zM4 15h16v-2H4v2zM4 5v2h16V5H4z" />
|
|
||||||
</svg>
|
|
||||||
<span class="actionable-button__title">
|
|
||||||
Section
|
|
||||||
</span>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div> -->
|
|
||||||
<aside id="version_history_panel" class="flex direction-column hide-completely">
|
<aside id="version_history_panel" class="flex direction-column hide-completely">
|
||||||
<div class="flex align-center space-between">
|
<div class="flex align-center space-between">
|
||||||
<div class="flex align-center">
|
<div class="flex align-center">
|
||||||
@ -178,18 +173,24 @@
|
|||||||
</article>
|
</article>
|
||||||
<sm-popup id="article_list_popup">
|
<sm-popup id="article_list_popup">
|
||||||
<header slot="header" class="popup__header">
|
<header slot="header" class="popup__header">
|
||||||
<div class="grid">
|
<h3>All articles</h3>
|
||||||
<div class="flex align-center space-between">
|
<sm-input id="article_list_search" placeholder="Search articles" type="search" autofocus>
|
||||||
<h3>All articles</h3>
|
<svg slot="icon" class="icon" xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 0 24 24"
|
||||||
<sm-select label="Sort by:">
|
width="24px" fill="#000000">
|
||||||
<sm-option value="time">Date created</sm-option>
|
<path d="M0 0h24v24H0z" fill="none" />
|
||||||
<sm-option value="az">A-Z</sm-option>
|
<path
|
||||||
</sm-select>
|
d="M15.5 14h-.79l-.28-.27C15.41 12.59 16 11.11 16 9.5 16 5.91 13.09 3 9.5 3S3 5.91 3 9.5 5.91 16 9.5 16c1.61 0 3.09-.59 4.23-1.57l.27.28v.79l5 4.99L20.49 19l-4.99-5zm-6 0C7.01 14 5 11.99 5 9.5S7.01 5 9.5 5 14 7.01 14 9.5 11.99 14 9.5 14z" />
|
||||||
</div>
|
</svg>
|
||||||
<sm-input placeholder="Search articles" type="search"></sm-input>
|
</sm-input>
|
||||||
</div>
|
<sm-select id="sort_article_list" label="Sort by:">
|
||||||
|
<sm-option value="time">Most recent</sm-option>
|
||||||
|
<sm-option value="az">A-Z</sm-option>
|
||||||
|
</sm-select>
|
||||||
</header>
|
</header>
|
||||||
<div id="article_list"></div>
|
<div id="article_list" class="observe-empty-state grid"></div>
|
||||||
|
<h4 class="empty-state">
|
||||||
|
No related article
|
||||||
|
</h4>
|
||||||
</sm-popup>
|
</sm-popup>
|
||||||
<sm-popup id="create_article_popup">
|
<sm-popup id="create_article_popup">
|
||||||
<header slot="header" class="popup__header">
|
<header slot="header" class="popup__header">
|
||||||
@ -204,7 +205,7 @@
|
|||||||
<h3>Create article</h3>
|
<h3>Create article</h3>
|
||||||
</header>
|
</header>
|
||||||
<sm-form>
|
<sm-form>
|
||||||
<sm-input placeholder="Article title" required></sm-input>
|
<sm-input placeholder="Article title" required autofocus></sm-input>
|
||||||
<sm-checkbox checked>
|
<sm-checkbox checked>
|
||||||
<div class="grid button__icon--right gap-0-5">
|
<div class="grid button__icon--right gap-0-5">
|
||||||
Set as default
|
Set as default
|
||||||
@ -221,6 +222,44 @@
|
|||||||
<sm-button variant="primary">Create</sm-button>
|
<sm-button variant="primary">Create</sm-button>
|
||||||
</sm-form>
|
</sm-form>
|
||||||
</sm-popup>
|
</sm-popup>
|
||||||
|
<sm-popup id="edit_sections_popup" open>
|
||||||
|
<header slot="header" class="popup__header">
|
||||||
|
<button class="popup__header__close" onclick="hidePopup()">
|
||||||
|
<svg class="icon" xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 0 24 24" width="24px"
|
||||||
|
fill="#000000">
|
||||||
|
<path d="M0 0h24v24H0V0z" fill="none" />
|
||||||
|
<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>
|
||||||
|
</button>
|
||||||
|
<h3>Edit sections</h3>
|
||||||
|
</header>
|
||||||
|
<h5 class="label">Existing sections</h5>
|
||||||
|
<div id="section_list_container" class="observe-empty-state"></div>
|
||||||
|
<p class="empty-state">
|
||||||
|
There are no sections so far, you can add section with button below.
|
||||||
|
</p>
|
||||||
|
<button id="insert_section_button" onclick="insertEmptySection()" class="button">
|
||||||
|
<svg class="icon button__icon--left" xmlns="http://www.w3.org/2000/svg" enable-background="new 0 0 24 24"
|
||||||
|
height="24px" viewBox="0 0 24 24" width="24px" fill="#000000">
|
||||||
|
<g>
|
||||||
|
<rect fill="none" height="24" width="24" />
|
||||||
|
</g>
|
||||||
|
<g>
|
||||||
|
<g />
|
||||||
|
<g>
|
||||||
|
<path
|
||||||
|
d="M17,19.22H5V7h7V5H5C3.9,5,3,5.9,3,7v12c0,1.1,0.9,2,2,2h12c1.1,0,2-0.9,2-2v-7h-2V19.22z" />
|
||||||
|
<path d="M19,2h-2v3h-3c0.01,0.01,0,2,0,2h3v2.99c0.01,0.01,2,0,2,0V7h3V5h-3V2z" />
|
||||||
|
<rect height="2" width="8" x="7" y="9" />
|
||||||
|
<polygon points="7,12 7,14 15,14 15,12 12,12" />
|
||||||
|
<rect height="2" width="8" x="7" y="15" />
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
Add new section
|
||||||
|
</button>
|
||||||
|
</sm-popup>
|
||||||
<template id="section_template">
|
<template id="section_template">
|
||||||
<text-field class="heading"></text-field>
|
<text-field class="heading"></text-field>
|
||||||
<section class="article-section">
|
<section class="article-section">
|
||||||
@ -263,10 +302,11 @@
|
|||||||
<template id="quote_template">
|
<template id="quote_template">
|
||||||
<figure class="quote-template grid gap-0-5" contenteditable="false">
|
<figure class="quote-template grid gap-0-5" contenteditable="false">
|
||||||
<blockquote class="flex">
|
<blockquote class="flex">
|
||||||
<p class="quote" contenteditable="true"></p>
|
<p class="quote" contenteditable="true">Words can be like X-rays, if you use them properly—they’ll go
|
||||||
|
through anything. You read and you’re pierced.</p>
|
||||||
</blockquote>
|
</blockquote>
|
||||||
<figcaption class="flex"><span class="by" contenteditable="true"></span><cite class="citation"
|
<figcaption class="flex"><span class="by" contenteditable="true">Aldous Huxley</span><cite class="citation"
|
||||||
contenteditable="true"></cite></figcaption>
|
contenteditable="true">Brave New World</cite></figcaption>
|
||||||
</figure>
|
</figure>
|
||||||
<p><br></p>
|
<p><br></p>
|
||||||
</template>
|
</template>
|
||||||
@ -504,7 +544,6 @@
|
|||||||
|
|
||||||
window.addEventListener('hashchange', e => showPage(window.location.hash))
|
window.addEventListener('hashchange', e => showPage(window.location.hash))
|
||||||
window.addEventListener("load", () => {
|
window.addEventListener("load", () => {
|
||||||
showPage(window.location.hash)
|
|
||||||
document.body.classList.remove('hide-completely')
|
document.body.classList.remove('hide-completely')
|
||||||
document.querySelectorAll('sm-input[data-private-key]').forEach(input => input.customValidation = floCrypto.getPubKeyHex)
|
document.querySelectorAll('sm-input[data-private-key]').forEach(input => input.customValidation = floCrypto.getPubKeyHex)
|
||||||
document.addEventListener('keyup', (e) => {
|
document.addEventListener('keyup', (e) => {
|
||||||
@ -525,7 +564,8 @@
|
|||||||
async function showPage(targetPage, options = {}) {
|
async function showPage(targetPage, options = {}) {
|
||||||
const { firstLoad, hashChange } = options
|
const { firstLoad, hashChange } = options
|
||||||
let pageId
|
let pageId
|
||||||
let params
|
let params = {}
|
||||||
|
let searchParams
|
||||||
if (targetPage === '') {
|
if (targetPage === '') {
|
||||||
if (typeof myFloID === "undefined") {
|
if (typeof myFloID === "undefined") {
|
||||||
pageId = 'landing'
|
pageId = 'landing'
|
||||||
@ -549,8 +589,26 @@
|
|||||||
pageId = targetPage
|
pageId = targetPage
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (searchParams) {
|
||||||
|
const urlSearchParams = new URLSearchParams('?' + searchParams);
|
||||||
|
params = Object.fromEntries(urlSearchParams.entries());
|
||||||
|
pagesData.params = params
|
||||||
|
}
|
||||||
|
switch (pageId) {
|
||||||
|
case 'home':
|
||||||
|
if (!params.articleID)
|
||||||
|
params['articleID'] = floGlobals.appObjects.cc.defaultArticle
|
||||||
|
Promise.all([
|
||||||
|
floCloudAPI.requestObjectData(params.articleID),
|
||||||
|
floCloudAPI.requestGeneralData(`${params.articleID}_gd`)
|
||||||
|
]).then(() => {
|
||||||
|
hidePopup()
|
||||||
|
render.article(params.articleID)
|
||||||
|
window.history.replaceState('', '', `#/home?articleID=${params.articleID}`)
|
||||||
|
})
|
||||||
|
break
|
||||||
|
}
|
||||||
if (pagesData.lastPage !== pageId) {
|
if (pagesData.lastPage !== pageId) {
|
||||||
document.querySelectorAll('.mobile-page').forEach(elem => elem.classList.add('hide-on-mobile'))
|
|
||||||
pagesData.lastPage = pageId
|
pagesData.lastPage = pageId
|
||||||
if (!pagesData.openedPages.includes(pageId)) {
|
if (!pagesData.openedPages.includes(pageId)) {
|
||||||
pagesData.openedPages.push(pageId)
|
pagesData.openedPages.push(pageId)
|
||||||
@ -872,20 +930,23 @@
|
|||||||
const { title } = floGlobals.appObjects.cc.articleList[id]
|
const { title } = floGlobals.appObjects.cc.articleList[id]
|
||||||
const { writer, sections } = currentArticle
|
const { writer, sections } = currentArticle
|
||||||
const frag = document.createDocumentFragment()
|
const frag = document.createDocumentFragment()
|
||||||
|
const isSubAdmin = floGlobals.subAdmins.includes(myFloID)
|
||||||
let index = 0
|
let index = 0
|
||||||
for (const sectionID in sections) {
|
for (const sectionID in sections) {
|
||||||
frag.append(render.section(sectionID, sections[sectionID], index))
|
frag.append(render.section(sectionID, sections[sectionID], index))
|
||||||
index += 1
|
index += 1
|
||||||
}
|
}
|
||||||
if (!floGlobals.subAdmins.includes(myFloID)) {
|
if (!isSubAdmin) {
|
||||||
getRef('current_article_title').setAttribute('disabled', '')
|
getRef('current_article_title').setAttribute('disabled', '')
|
||||||
|
} else {
|
||||||
|
renderSectionList()
|
||||||
}
|
}
|
||||||
getRef('current_article_title').value = title
|
getRef('current_article_title').value = title
|
||||||
getRef('article_wrapper').innerHTML = ''
|
getRef('article_wrapper').innerHTML = ''
|
||||||
getRef('article_wrapper').append(frag)
|
getRef('article_wrapper').append(frag)
|
||||||
},
|
},
|
||||||
articleLink(uid, details, isDefaultArticle) {
|
articleLink(details, isDefaultArticle) {
|
||||||
const { timestamp, title } = details
|
const { uid, timestamp, title } = details
|
||||||
const link = createElement('a', {
|
const link = createElement('a', {
|
||||||
textContent: title,
|
textContent: title,
|
||||||
attributes: { href: `#/home?articleID=${uid}` },
|
attributes: { href: `#/home?articleID=${uid}` },
|
||||||
@ -894,7 +955,7 @@
|
|||||||
if (isDefaultArticle) {
|
if (isDefaultArticle) {
|
||||||
link.append(createElement('span', {
|
link.append(createElement('span', {
|
||||||
className: 'default-article',
|
className: 'default-article',
|
||||||
textContent: 'Active'
|
textContent: 'Actively written'
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
return link
|
return link
|
||||||
@ -933,7 +994,7 @@
|
|||||||
}
|
}
|
||||||
if (i - index > 1)
|
if (i - index > 1)
|
||||||
changed.splice((index + 1), (i - index - 1))
|
changed.splice((index + 1), (i - index - 1))
|
||||||
return `<span class="${type ? 'added' : 'removed'}">${consecutiveWords.join(' ')}</span>`
|
return `<s${type ? 'pan' : ''} class="${type ? 'added' : 'removed'}">${consecutiveWords.join(' ')}</s${type ? 'pan' : ''}>`
|
||||||
} else return word
|
} else return word
|
||||||
})
|
})
|
||||||
clone.querySelector('.entry__changes').innerHTML = DOMPurify.sanitize(final.join(' '))
|
clone.querySelector('.entry__changes').innerHTML = DOMPurify.sanitize(final.join(' '))
|
||||||
@ -959,6 +1020,16 @@
|
|||||||
section.querySelector('.article-section').append(frag)
|
section.querySelector('.article-section').append(frag)
|
||||||
return section
|
return section
|
||||||
},
|
},
|
||||||
|
sectionCard(details) {
|
||||||
|
const { title } = details
|
||||||
|
return createElement('div', {
|
||||||
|
className: 'section-card flex align-center',
|
||||||
|
innerHTML: `
|
||||||
|
<svg class="icon button__icon--left" xmlns="http://www.w3.org/2000/svg" enable-background="new 0 0 24 24" height="24px" viewBox="0 0 24 24" width="24px" fill="#000000"><g><rect fill="none" height="24" width="24"/></g><g><path d="M9,18h12v-2H9V18z M3,6v2h18V6H3z M9,13h12v-2H9V13z"/></g></svg>
|
||||||
|
${title}
|
||||||
|
`
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function parseArticleData() {
|
function parseArticleData() {
|
||||||
@ -994,18 +1065,74 @@
|
|||||||
currentArticle.uniqueEntries[entry]['iterations'].sort((a, b) => a.timestamp - b.timestamp)
|
currentArticle.uniqueEntries[entry]['iterations'].sort((a, b) => a.timestamp - b.timestamp)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
function renderSectionList() {
|
||||||
|
const frag = document.createDocumentFragment()
|
||||||
|
floGlobals.appObjects[currentArticle.id].sections.forEach(section => {
|
||||||
|
frag.append(render.sectionCard(section))
|
||||||
|
})
|
||||||
|
getRef('section_list_container').innerHTML = ''
|
||||||
|
getRef('section_list_container').append(frag)
|
||||||
|
}
|
||||||
|
function insertEmptySection() {
|
||||||
|
const emptySection = createElement('div', {
|
||||||
|
className: 'section-card section-card--new flex align-center',
|
||||||
|
innerHTML: `
|
||||||
|
<svg class="icon button__icon--left" xmlns="http://www.w3.org/2000/svg" enable-background="new 0 0 24 24" height="24px" viewBox="0 0 24 24" width="24px" fill="#000000"><g><rect fill="none" height="24" width="24"/></g><g><path d="M9,18h12v-2H9V18z M3,6v2h18V6H3z M9,13h12v-2H9V13z"/></g></svg>
|
||||||
|
<input placeholder="New section title"/>
|
||||||
|
<button class="remove">
|
||||||
|
<svg class="icon" xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 0 24 24" width="24px" fill="#000000">
|
||||||
|
<path d="M0 0h24v24H0V0z" fill="none"></path>
|
||||||
|
<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"></path>
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
`
|
||||||
|
})
|
||||||
|
getRef('section_list_container').append(emptySection)
|
||||||
|
}
|
||||||
|
|
||||||
function renderArticleList() {
|
function getArticleList() {
|
||||||
|
const articleList = floGlobals.appObjects.cc.articleList
|
||||||
|
const sortBy = getRef('sort_article_list').value
|
||||||
|
const arrayOfObject = []
|
||||||
|
for (const key in articleList) {
|
||||||
|
const { timestamp, title } = articleList[key]
|
||||||
|
arrayOfObject.push({
|
||||||
|
uid: key,
|
||||||
|
timestamp,
|
||||||
|
title
|
||||||
|
})
|
||||||
|
}
|
||||||
|
if (sortBy === 'az') {
|
||||||
|
arrayOfObject.sort((a, b) => a.title.localeCompare(b.title, 'en', { numeric: true, sensitivity: 'base' }))
|
||||||
|
} else {
|
||||||
|
arrayOfObject.sort((a, b) => b.timestamp - a.timestamp)
|
||||||
|
}
|
||||||
|
return arrayOfObject
|
||||||
|
}
|
||||||
|
|
||||||
|
getRef('article_list_search').addEventListener('input', e => {
|
||||||
|
const searchKey = e.target.value.trim()
|
||||||
|
const options = {
|
||||||
|
keys: ['title'],
|
||||||
|
threshold: 0
|
||||||
|
}
|
||||||
|
const fuse = new Fuse(getArticleList(), options)
|
||||||
|
renderArticleList(searchKey === '' ? undefined : fuse.search(searchKey).map(v => v.item))
|
||||||
|
})
|
||||||
|
getRef('sort_article_list').addEventListener('change', e => { renderArticleList() })
|
||||||
|
|
||||||
|
function renderArticleList(articleList) {
|
||||||
getRef('article_list').innerHTML = ``
|
getRef('article_list').innerHTML = ``
|
||||||
const frag = document.createDocumentFragment()
|
const frag = document.createDocumentFragment()
|
||||||
const { articleList, defaultArticle } = floGlobals.appObjects.cc
|
const defaultArticle = floGlobals.appObjects.cc.defaultArticle;
|
||||||
for (articleKey in articleList) {
|
(articleList || getArticleList()).forEach((article) => {
|
||||||
const isDefaultArticle = defaultArticle === articleKey
|
const isDefaultArticle = defaultArticle === article.uid
|
||||||
frag.prepend(render.articleLink(articleKey, articleList[articleKey], isDefaultArticle))
|
frag.append(render.articleLink(article, isDefaultArticle))
|
||||||
}
|
})
|
||||||
getRef('article_list').append(frag)
|
getRef('article_list').append(frag)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
function getIterationDetails(uid, targetIndex) {
|
function getIterationDetails(uid, targetIndex) {
|
||||||
let merged
|
let merged
|
||||||
const contributors = new Set()
|
const contributors = new Set()
|
||||||
@ -1111,7 +1238,7 @@
|
|||||||
`${origin.substring(0, startIndex)}${insertion}${origin.substring(endIndex)}`;
|
`${origin.substring(0, startIndex)}${insertion}${origin.substring(endIndex)}`;
|
||||||
const make = {
|
const make = {
|
||||||
bold() {
|
bold() {
|
||||||
document.execCommand('bold')
|
replaceSelectedText(createElement('strong'))
|
||||||
},
|
},
|
||||||
italic() {
|
italic() {
|
||||||
document.execCommand('italic')
|
document.execCommand('italic')
|
||||||
@ -1221,7 +1348,7 @@
|
|||||||
function createQuote() {
|
function createQuote() {
|
||||||
const quote = getRef('quote_template').content.cloneNode(true)
|
const quote = getRef('quote_template').content.cloneNode(true)
|
||||||
quote.querySelector('.quote').textContent = '"Words can be like X-rays, if you use them properly—they’ll go through anything. You read and you’re pierced."'
|
quote.querySelector('.quote').textContent = '"Words can be like X-rays, if you use them properly—they’ll go through anything. You read and you’re pierced."'
|
||||||
quote.querySelector('.by').textContent = `- Author`
|
quote.querySelector('.by').textContent = `- Aldous Huxley,`
|
||||||
quote.querySelector('.citation').textContent = 'Brave New World'
|
quote.querySelector('.citation').textContent = 'Brave New World'
|
||||||
return quote
|
return quote
|
||||||
}
|
}
|
||||||
@ -1240,14 +1367,8 @@
|
|||||||
await Promise.all([
|
await Promise.all([
|
||||||
floCloudAPI.requestObjectData('cc'),
|
floCloudAPI.requestObjectData('cc'),
|
||||||
])
|
])
|
||||||
Promise.all([
|
showPage(window.location.hash)
|
||||||
floCloudAPI.requestObjectData(floGlobals.appObjects.cc.defaultArticle),
|
renderArticleList()
|
||||||
floCloudAPI.requestGeneralData(`${floGlobals.appObjects.cc.defaultArticle}_gd`)
|
|
||||||
])
|
|
||||||
.then(() => {
|
|
||||||
render.article(floGlobals.appObjects.cc.defaultArticle)
|
|
||||||
renderArticleList()
|
|
||||||
})
|
|
||||||
|
|
||||||
console.log(result)
|
console.log(result)
|
||||||
// alert(`Welcome FLO_ID: ${ myFloID }`)
|
// alert(`Welcome FLO_ID: ${ myFloID }`)
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user