Adding article section traversal

This commit is contained in:
sairaj mote 2022-01-30 22:16:26 +05:30
parent a83d253bad
commit 1fd125f284
4 changed files with 306 additions and 132 deletions

View File

@ -219,6 +219,10 @@ ul {
display: flex;
}
.flex-wrap {
flex-wrap: wrap;
}
.grid {
display: grid;
}
@ -448,6 +452,7 @@ details summary {
-ms-user-select: none;
user-select: none;
cursor: pointer;
padding: 0.5rem 0;
}
details[open] > summary .down-arrow {
transform: rotate(180deg);
@ -563,7 +568,8 @@ sm-copy {
fill: var(--danger-color);
}
main {
#main {
overflow-y: hidden;
display: grid;
height: 100%;
grid-template-rows: auto 1fr;
@ -742,15 +748,41 @@ theme-toggle {
}
#article {
gap: 2rem 0;
display: flex;
padding: 1.5rem;
max-height: 100%;
overflow-y: auto;
}
#article main > section:last-of-type {
padding-bottom: 6rem;
}
#article_aside {
position: absolute;
left: 0;
top: 5rem;
padding: 1.5rem;
}
#article_map_container {
margin-top: 1.5rem;
display: grid;
gap: 0.5rem;
}
#article_map_container button {
line-height: 1.3;
width: 100%;
padding: 0.5rem 0;
text-align: left;
font-weight: 500;
font-size: 0.9rem;
color: rgba(var(--text-color), 0.8);
}
.hero-section {
display: grid;
gap: 0.5rem;
grid-template-columns: minmax(0, 1fr);
padding-top: 1.5rem;
}
#article_contributors {
@ -767,24 +799,19 @@ theme-toggle {
padding: 0.3rem 0.5rem;
}
#like_panel {
#action_panel {
position: fixed;
bottom: 0;
width: 100%;
padding: 1.5rem;
text-align: center;
justify-items: flex-start;
left: 0;
gap: 0.5rem;
align-items: center;
}
.up-vote {
display: grid;
grid-template-columns: auto 1fr;
position: relative;
padding: 0.8rem;
border-radius: 2rem;
background-color: var(--foreground-color);
box-shadow: 0 0.5rem 1rem rgba(0, 0, 0, 0.1);
border: solid rgba(var(--text-color), 0.2) thin;
padding: 0.5rem;
border-radius: 0.3rem;
}
.up-vote > * {
pointer-events: none;
@ -795,13 +822,6 @@ theme-toggle {
.up-vote:active .icon {
transform: scale(0.7);
}
.up-vote.liked {
background-color: var(--like-color);
color: #fff;
}
.up-vote.liked .icon {
fill: #fff;
}
.up-vote .expanding-heart,
.up-vote .ring {
grid-area: 1/1;
@ -809,8 +829,6 @@ theme-toggle {
.up-vote .icon {
grid-area: 1/1;
fill: var(--like-color);
height: 1.5rem;
width: 1.5rem;
transition: transform 0.2s;
}
@ -866,7 +884,7 @@ theme-toggle {
gap: 0.5rem;
border-radius: 0.5rem;
align-items: flex-start;
grid-template-columns: 1fr auto;
grid-template-columns: minmax(0, 1fr);
}
.request-card__title {
grid-area: 2/1;
@ -876,6 +894,7 @@ theme-toggle {
}
.request-card .flex {
grid-row: span 2;
margin-top: 0.5rem;
}
.request-card .publish-button {
text-transform: uppercase;
@ -1018,6 +1037,17 @@ theme-toggle {
width: 2.5rem;
}
#action_panel {
width: -webkit-min-content;
width: -moz-min-content;
width: min-content;
padding: 0.5rem;
border-radius: 0.5rem;
background-color: var(--foreground-color);
margin: 1.5rem;
box-shadow: 0 0.5rem 1rem rgba(0, 0, 0, 0.2);
}
.hide-on-mobile {
display: none;
}
@ -1098,7 +1128,41 @@ theme-toggle {
}
#article {
grid-template-columns: 1fr 60ch 1fr;
gap: 6vw;
padding: 0;
grid-template-columns: 16rem 1fr;
}
#article main {
width: 60ch;
padding: 1.5rem 0;
}
#article_aside {
width: 20rem;
padding: 1.5rem 2rem;
display: flex;
flex-direction: column;
position: -webkit-sticky;
position: sticky;
top: 0;
height: 100%;
overflow-y: auto;
}
#article_map_container {
margin-bottom: 3rem;
}
#action_panel {
position: relative;
margin-top: auto;
}
.request-card {
grid-template-columns: 1fr auto;
}
.request-card .flex {
margin-top: 0;
}
#preview_popup {
@ -1122,20 +1186,6 @@ theme-toggle {
display: none;
}
}
@media screen and (min-width: 64rem) {
#like_panel {
margin: 1.5rem;
width: 18vw;
border-radius: 0.8rem;
border: solid thin rgba(var(--text-color), 0.2);
justify-content: flex-start;
justify-items: flex-start;
text-align: left;
}
#like_panel .up-vote {
box-shadow: none;
}
}
@media (any-hover: hover) {
::-webkit-scrollbar {
width: 0.5rem;

2
css/main.min.css vendored

File diff suppressed because one or more lines are too long

View File

@ -212,6 +212,9 @@ ul {
.flex {
display: flex;
}
.flex-wrap {
flex-wrap: wrap;
}
.grid {
display: grid;
@ -433,6 +436,7 @@ details {
display: flex;
user-select: none;
cursor: pointer;
padding: 0.5rem 0;
}
&[open] > summary {
@ -534,7 +538,8 @@ sm-copy {
fill: var(--danger-color);
}
main {
#main {
overflow-y: hidden;
display: grid;
height: 100%;
grid-template-rows: auto 1fr;
@ -706,15 +711,40 @@ theme-toggle {
}
#article {
gap: 2rem 0;
padding-bottom: 6rem;
display: flex;
padding: 1.5rem;
max-height: 100%;
overflow-y: auto;
main {
& > section:last-of-type {
padding-bottom: 6rem;
}
}
}
#article_aside {
position: absolute;
left: 0;
top: 5rem;
padding: 1.5rem;
}
#article_map_container {
margin-top: 1.5rem;
display: grid;
gap: 0.5rem;
button {
line-height: 1.3;
width: 100%;
padding: 0.5rem 0;
text-align: left;
font-weight: 500;
font-size: 0.9rem;
color: rgba(var(--text-color), 0.8);
}
}
.hero-section {
display: grid;
gap: 0.5rem;
grid-template-columns: minmax(0, 1fr);
padding-top: 1.5rem;
}
#article_contributors {
flex-wrap: wrap;
@ -728,24 +758,18 @@ theme-toggle {
border-radius: 0.3rem;
padding: 0.3rem 0.5rem;
}
#like_panel {
#action_panel {
position: fixed;
bottom: 0;
width: 100%;
padding: 1.5rem;
text-align: center;
justify-items: flex-start;
left: 0;
gap: 0.5rem;
align-items: center;
}
.up-vote {
display: grid;
grid-template-columns: auto 1fr;
position: relative;
padding: 0.8rem;
border-radius: 2rem;
background-color: var(--foreground-color);
-webkit-box-shadow: 0 0.5rem 1rem rgba(0, 0, 0, 0.1);
box-shadow: 0 0.5rem 1rem rgba(0, 0, 0, 0.1);
border: solid rgba(var(--text-color), 0.2) thin;
padding: 0.5rem;
border-radius: 0.3rem;
& > * {
pointer-events: none;
}
@ -759,14 +783,6 @@ theme-toggle {
}
}
&.liked {
background-color: var(--like-color);
color: #fff;
.icon {
fill: #fff;
}
}
.expanding-heart,
.ring {
grid-area: 1/1;
@ -774,8 +790,6 @@ theme-toggle {
& .icon {
grid-area: 1/1;
fill: var(--like-color);
height: 1.5rem;
width: 1.5rem;
-webkit-transition: -webkit-transform 0.2s;
transition: -webkit-transform 0.2s;
transition: transform 0.2s;
@ -831,7 +845,7 @@ theme-toggle {
gap: 0.5rem;
border-radius: 0.5rem;
align-items: flex-start;
grid-template-columns: 1fr auto;
grid-template-columns: minmax(0, 1fr);
&__title {
grid-area: 2/1;
}
@ -840,6 +854,7 @@ theme-toggle {
}
.flex {
grid-row: span 2;
margin-top: 0.5rem;
}
.publish-button {
text-transform: uppercase;
@ -976,6 +991,14 @@ theme-toggle {
width: 2.5rem;
}
}
#action_panel {
width: min-content;
padding: 0.5rem;
border-radius: 0.5rem;
background-color: var(--foreground-color);
margin: 1.5rem;
box-shadow: 0 0.5rem 1rem rgba(0, 0, 0, 0.2);
}
.hide-on-mobile {
display: none;
}
@ -1049,9 +1072,39 @@ theme-toggle {
}
}
#article {
grid-template-columns: 1fr 60ch 1fr;
gap: 6vw;
padding: 0;
grid-template-columns: 16rem 1fr;
main {
width: 60ch;
padding: 1.5rem 0;
}
}
#article_aside {
width: 20rem;
padding: 1.5rem 2rem;
display: flex;
flex-direction: column;
position: sticky;
top: 0;
height: 100%;
overflow-y: auto;
}
#article_map_container {
margin-bottom: 3rem;
}
#action_panel {
position: relative;
margin-top: auto;
}
.request-card {
grid-template-columns: 1fr auto;
.flex {
margin-top: 0;
}
}
#preview_popup {
--width: 60ch;
}
@ -1071,20 +1124,6 @@ theme-toggle {
display: none;
}
}
@media screen and (min-width: 64rem) {
#like_panel {
margin: 1.5rem;
width: 18vw;
border-radius: 0.8rem;
border: solid thin rgba(var(--text-color), 0.2);
justify-content: flex-start;
justify-items: flex-start;
text-align: left;
.up-vote {
box-shadow: none;
}
}
}
@media (any-hover: hover) {
::-webkit-scrollbar {
width: 0.5rem;

View File

@ -92,7 +92,7 @@
<h4>Loading RM Times</h4>
</section>
</article>
<main class="hide">
<div id="main" class="hide">
<header id="main_header">
<button id="search_button" class="icon-only" title="Search for articles" onclick="toggleSearch()">
<svg class="icon" slot="icon" xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 0 24 24"
@ -203,14 +203,13 @@
<p class="empty-state">No articles written</p>
</section>
</article>
<article id="article" class="page page-layout hide">
<section class="hero-section">
<h1 id="article_title"></h1>
<div class="flex space-between align-center">
<div class="flex align-center">
<time id="published_time"></time><span class="bullet-point"></span><span
id="reading_time"></span>
</div>
<article id="article" class="page hide">
<aside id="article_aside">
<nav id="article_map" class="hide-on-mobile" aria-label="secondary navigation for article sections">
<h4>In this article</h4>
<ul id="article_map_container"></ul>
</nav>
<div id="action_panel" class="flex">
<button class="icon-only" title="Share article" onclick="sharePreview()">
<svg class="icon" 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">
@ -223,25 +222,43 @@
</g>
</svg>
</button>
<button id="upvote_button" class="up-vote" title="Give a like">
<svg class="icon" 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
d="M12 21.35l-1.45-1.32C5.4 15.36 2 12.28 2 8.5 2 5.42 4.42 3 7.5 3c1.74 0 3.41.81 4.5 2.09C13.09 3.81 14.76 3 16.5 3 19.58 3 22 5.42 22 8.5c0 3.78-3.4 6.86-8.55 11.54L12 21.35z" />
</svg>
<div id="like_count">Loading...</div>
</button>
</div>
</section>
<section id="article_body" class="grid"></section>
<section>
<h4>Article by -</h4>
<div id="article_contributors" class="flex"></div>
</section>
<section id="like_panel" class="grid gap-1-5">
<h4 class="hide-on-medium">Like the article to support creators.</h4>
<button id="upvote_button" class="button up-vote">
<svg class="icon" 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
d="M12 21.35l-1.45-1.32C5.4 15.36 2 12.28 2 8.5 2 5.42 4.42 3 7.5 3c1.74 0 3.41.81 4.5 2.09C13.09 3.81 14.76 3 16.5 3 19.58 3 22 5.42 22 8.5c0 3.78-3.4 6.86-8.55 11.54L12 21.35z" />
</svg>
<div id="like_count">Loading...</div>
</button>
</section>
</aside>
<main class="grid gap-2">
<section class="hero-section">
<h1 id="article_title"></h1>
<div class="flex align-center">
<div class="flex flex-wrap">
<time id="published_time" style="white-space: pre;"></time><time id="updated_time"></time>
</div>
<span class="bullet-point"></span><span id="reading_time" style="white-space: nowrap;"></span>
</div>
</section>
<details id="article_map_accordion" class="hide-on-desktop">
<summary class="space-between">
<h4>In this article</h4>
<svg class="icon down-arrow" xmlns="http://www.w3.org/2000/svg" height="24px"
viewBox="0 0 24 24" width="24px" fill="#000000">
<path d="M24 24H0V0h24v24z" fill="none" opacity=".87" />
<path d="M16.59 8.59L12 13.17 7.41 8.59 6 10l6 6 6-6-1.41-1.41z" />
</svg>
</summary>
</details>
<section id="article_body" class="grid"></section>
<section>
<h4>Article by -</h4>
<div id="article_contributors" class="flex"></div>
</section>
</main>
<button id="go_to_top" class="floating-button hide" onclick="goToTop()">
<svg class="icon" xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 0 24 24" width="24px"
fill="#000000">
@ -279,7 +296,7 @@
<p class="empty-state">No writers</p>
</section>
</article>
</main>
</div>
<sm-popup id="sign_in_popup">
<header slot="header" class="popup__header">
<button class="popup__header__close" onclick="hidePopup()">
@ -557,15 +574,25 @@
<li class="request-card">
<h4 class="request-card__title"></h4>
<time class="request-card__time"></time>
<div class="flex gap-0-5">
<button class="button icon-only preview-button" title="Preview article">
<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 6c3.79 0 7.17 2.13 8.82 5.5C19.17 14.87 15.79 17 12 17s-7.17-2.13-8.82-5.5C4.83 8.13 8.21 6 12 6m0-2C7 4 2.73 7.11 1 11.5 2.73 15.89 7 19 12 19s9.27-3.11 11-7.5C21.27 7.11 17 4 12 4zm0 5c1.38 0 2.5 1.12 2.5 2.5S13.38 14 12 14s-2.5-1.12-2.5-2.5S10.62 9 12 9m0-2c-2.48 0-4.5 2.02-4.5 4.5S9.52 16 12 16s4.5-2.02 4.5-4.5S14.48 7 12 7z" />
</svg>
</button>
<div class="flex space-between align-center gap-0-5">
<div class="flex gap-0-5">
<button class="icon-only preview-button" title="Preview article">
<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 6c3.79 0 7.17 2.13 8.82 5.5C19.17 14.87 15.79 17 12 17s-7.17-2.13-8.82-5.5C4.83 8.13 8.21 6 12 6m0-2C7 4 2.73 7.11 1 11.5 2.73 15.89 7 19 12 19s9.27-3.11 11-7.5C21.27 7.11 17 4 12 4zm0 5c1.38 0 2.5 1.12 2.5 2.5S13.38 14 12 14s-2.5-1.12-2.5-2.5S10.62 9 12 9m0-2c-2.48 0-4.5 2.02-4.5 4.5S9.52 16 12 16s4.5-2.02 4.5-4.5S14.48 7 12 7z" />
</svg>
</button>
<button class="icon-only delete-request" title="Delete request">
<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="M16 9v10H8V9h8m-1.5-6h-5l-1 1H5v2h14V4h-3.5l-1-1zM18 7H6v12c0 1.1.9 2 2 2h8c1.1 0 2-.9 2-2V7z" />
</svg>
</button>
</div>
<button class="button button--primary publish-button">Publish</button>
</div>
</li>
@ -908,22 +935,26 @@
case 'dashboard':
targetPage = 'dashboard'
await Promise.all([
floCloudAPI.requestObjectData('admin'),
floCloudAPI.requestObjectData('adminData'),
floCloudAPI.requestGeneralData('publishing_requests', {
callback: (d, e) => renderDashboard(d)
})
])
calculateVotes()
break;
}
if (pagesData.lastPage !== targetPage) {
if (targetPage === 'article') {
mobileQuery.addListener(handleMobileChange)
handleMobileChange(mobileQuery)
articleTitleObserver.observe(getRef('article_title'))
} else {
mobileQuery.removeListener(handleMobileChange)
articleTitleObserver.disconnect()
}
}
if (pageId !== 'loading') {
document.querySelector('main').classList.remove('hide')
getRef('main').classList.remove('hide')
}
document.querySelectorAll('.page').forEach(page => page.classList.add('hide'))
getRef(targetPage).classList.remove('hide')
@ -1023,7 +1054,7 @@
</script>
<script>
const relativeTime = new RelativeTime();
const relativeTime = new RelativeTime({ style: 'narrow' });
const render = {
articleCard(details) {
const { uid: articleID, category, title, readTime, published } = details
@ -1050,7 +1081,7 @@
},
requestCard(details) {
const { message: { articleID, category, title }, time, vectorClock } = details
if (!floGlobals.appObjects.adminData.publishedVc.hasOwnProperty(vectorClock)) {
if (!floGlobals.appObjects.adminData.publishedVc.hasOwnProperty(vectorClock) && !floGlobals.appObjects.adminData.rejectedVc.hasOwnProperty(vectorClock)) {
const clone = getRef('request_template').content.cloneNode(true).firstElementChild
clone.dataset.vc = vectorClock
clone.querySelector('.request-card__title').textContent = title
@ -1147,13 +1178,26 @@
const openedArticles = {}
async function renderArticle(articleID, firstLoad = true) {
const frag = document.createDocumentFragment()
const allArticles = await compactIDB.readData('appObjects', 'articlesContent')
const { title, published, readTime, contributors } = floGlobals.appObjects.rmTimes.articles[articleID]
const { title, published, readTime, contributors, updated } = floGlobals.appObjects.rmTimes.articles[articleID]
getRef('article_title').textContent = title
getRef('published_time').textContent = getFormattedTime(published, 'date-only')
getRef('published_time').textContent = `Published ${getFormattedTime(published, 'date-only')}${updated ? ", " : ''}`
if (updated)
getRef('updated_time').textContent = `Updated ${relativeTime.from(updated)}`
getRef('reading_time').textContent = `${readTime} Min read`
getRef('article_body').innerHTML = allArticles[articleID]
const frag = document.createDocumentFragment()
getRef('article_body').querySelectorAll('h3').forEach(heading => {
const headingText = heading.textContent
const headingID = floCrypto.randString(8)
heading.id = headingID
frag.append(createElement('li', {
innerHTML: `<button data-heading-id="${headingID}">${headingText}</button>`
}))
})
getRef('article_map_container').innerHTML = ''
getRef('article_map_container').append(frag)
contributors.forEach(id => {
frag.append(createElement('a', {
textContent: floGlobals.appObjects.rmTimes.articleWriters.hasOwnProperty(id) ? floGlobals.appObjects.rmTimes.articleWriters[id].name : id,
@ -1488,6 +1532,7 @@
}
entries.forEach(entry => {
if (entry.isIntersecting) {
if (getRef('go_to_top').classList.contains('hide')) return
animateTo(getRef('go_to_top'), [
{
transform: 'none',
@ -1520,7 +1565,36 @@
})
function goToTop() {
window.scrollTo(0, 0)
getRef('article').scroll({
top: 0,
behavior: 'smooth'
});
}
let isMobile = false
const mobileQuery = window.matchMedia('(max-width: 40rem)')
function handleMobileChange(e) {
if (e.matches) {
// Mobile view
isMobile = true
const original = getRef('article_map').querySelector('#article_map_container')
if (original) {
const clone = original.cloneNode(true)
original.remove()
getRef('article_map_accordion').append(clone)
}
} else {
// Desktop view
isMobile = false
const original = getRef('article_map_accordion').querySelector('#article_map_container')
if (original) {
const clone = original.cloneNode(true)
original.remove()
getRef('article_map').append(clone)
}
}
}
function generateCredentials() {
@ -1643,6 +1717,20 @@
textContent: title
}))
showPopup('preview_popup')
} else if (e.target.closest('.delete-request')) {
getConfirmation(`Delete this request?`).then(res => {
if (res) {
const button = e.target.closest('.delete-request');
const vc = button.closest('.request-card').dataset.vc;
floGlobals.appObjects.adminData.rejectedVc[vc] = true
floCloudAPI.updateObjectData('adminData')
.then(() => {
notify(`Deleted request`, 'success')
document.querySelector(`.request-card[data-vc="${vc}"]`).remove()
})
}
})
}
}
@ -1710,7 +1798,6 @@
}
async function calculateVotes() {
await floCloudAPI.requestObjectData('adminData')
const articlesVotesProm = []
const articleIDs = []
for (const articleKey in floGlobals.appObjects.rmTimes.articles) {
@ -1890,15 +1977,13 @@
getRef('article_analytics').addEventListener('click', handleAnalyticsClick);
getRef('writers_list').addEventListener('click', handleWritersClick);
document.querySelectorAll('.admin-option').forEach(elem => elem.classList.remove('hide'));
calculateVotes()
location.hash = '#/dashboard'
} else {
getRef('publishing_requests').removeEventListener('click', handleRequestClick)
getRef('article_analytics').removeEventListener('click', handleAnalyticsClick);
getRef('writers_list').removeEventListener('click', handleWritersClick);
document.querySelectorAll('.admin-option').forEach(elem => elem.classList.add('hide'))
}
if (location.hash.includes('sign_in') || location.hash.includes('sign_up'))
location.hash = floGlobals.isSubAdmin ? '#/dashboard' : '#/home'
console.log(result)
}).catch(error => console.error(error))
}