Adding hero image uploading feature

This commit is contained in:
sairaj mote 2022-02-07 00:19:42 +05:30
parent 5848e22bf5
commit 219b1e27bc
5 changed files with 203 additions and 105 deletions

View File

@ -3735,4 +3735,4 @@ customElements.define('tags-input', class extends HTMLElement {
this.input.removeEventListener('keydown', this.handleKeydown)
this.tagsWrapper.removeEventListener('click', this.handleClick)
}
})
})

View File

@ -2,7 +2,7 @@
padding: 0;
margin: 0;
box-sizing: border-box;
font-family: "Inter", sans-serif;
font-family: "Archivo", sans-serif;
}
:root {
@ -21,8 +21,8 @@ body {
body,
body * {
--accent-color: #256eff;
--text-color: 36, 36, 36;
--background-color: 248, 248, 248;
--text-color: 20, 20, 20;
--background-color: 255, 255, 255;
--foreground-color: rgb(255, 255, 255);
--danger-color: rgb(255, 75, 75);
--green: #1cad59;
@ -208,8 +208,9 @@ img {
font-size: 1.2rem;
}
.h3 {
font-size: 1rem;
h3 {
font-size: 1.2rem;
line-height: 1.3;
}
.h4 {
@ -362,7 +363,7 @@ img {
height: 0.4ch;
width: 0.4ch;
border-radius: 0.5em;
background-color: currentColor;
background-color: var(--accent-color);
}
.icon {
@ -596,7 +597,6 @@ sm-copy {
grid-template-columns: auto 1fr auto auto;
background-color: rgba(var(--background-color), 1);
z-index: 2;
border-bottom: thin solid rgba(var(--text-color), 0.1);
}
#expanding_search {
@ -663,11 +663,11 @@ theme-toggle {
border-radius: 0.5rem;
width: 5.2rem;
text-align: center;
background-color: rgba(var(--text-color), 0.02);
background-color: #e6352f08;
}
.category .icon {
margin-bottom: 1rem;
transition: transform 0.3s;
transition: transform 0.3s, background-color 0.3s;
transform-origin: top;
}
.category:hover .icon {
@ -687,20 +687,21 @@ theme-toggle {
.article-card__title {
line-height: 1.4;
}
.article-card .flex {
font-size: 0.75rem;
}
.article-card__category {
background-color: rgba(var(--text-color), 0.06);
background-color: #256eff08;
margin-right: 0.5rem;
border-radius: 0.2rem;
font-size: 0.75rem;
padding: 0.2rem 0.4rem;
font-weight: 500;
color: rgba(var(--text-color), 0.8);
color: var(--accent-color) !important;
justify-self: flex-start;
text-transform: capitalize;
}
.article-card .flex {
font-size: 0.75rem;
}
#trending_article_container,
#query_results_list,
@ -711,7 +712,6 @@ theme-toggle {
}
.trending-article {
counter-increment: trending;
gap: 0.5rem 1.5rem;
}
.trending-article a:first-of-type {
@ -762,8 +762,7 @@ theme-toggle {
#article {
display: flex;
padding: 1.5rem;
max-height: 100%;
flex-wrap: wrap;
overflow-y: auto;
}
#article main > section:last-of-type {
@ -793,9 +792,25 @@ theme-toggle {
}
.hero-section {
position: relative;
display: grid;
gap: 0.5rem;
grid-template-columns: minmax(0, 1fr);
margin-top: 1.5rem;
align-content: flex-end;
width: 100%;
}
.hero-section p {
color: inherit;
}
.hero-section .overlay {
position: absolute;
content: "";
top: 0;
left: 0;
width: 100%;
height: 100%;
background: linear-gradient(to top, rgba(0, 0, 0, 0.8), rgba(0, 0, 0, 0));
z-index: -1;
}
#article_image {
@ -930,7 +945,7 @@ theme-toggle {
#preview_popup h1,
#article h1 {
font-size: 1.6rem;
line-height: 1.3;
line-height: 1.1;
}
#preview_popup h3:not(:first-of-type),
#article h3:not(:first-of-type) {
@ -946,8 +961,7 @@ theme-toggle {
}
#preview_popup p,
#article p {
font-family: "noto serif", serif;
font-size: 1rem;
font-size: 1.1rem;
line-height: 1.8;
}
#preview_popup p > *,
@ -1072,6 +1086,10 @@ theme-toggle {
justify-self: flex-end;
}
#article main {
padding: 0 1.5rem;
}
.writer-card .writer-profile {
height: 2.5rem;
width: 2.5rem;
@ -1169,22 +1187,26 @@ theme-toggle {
#preview_popup h1,
#article h1 {
font-size: 2rem;
font-size: 2.2rem;
font-weight: 800;
}
#preview_popup h3,
#article h3 {
font-size: 1.4rem;
line-height: 1.3;
}
#article {
gap: 6vw;
padding: 0;
grid-template-columns: 16rem 1fr;
padding: 0 4vw;
}
#article main {
width: 60ch;
padding: 1.5rem 0;
margin-left: 4vw;
}
#article_aside {
width: 20rem;
padding: 1.5rem 2rem;
padding: 1.5rem;
display: flex;
flex-direction: column;
position: -webkit-sticky;
@ -1259,7 +1281,7 @@ button:not(.button--primary) {
}
.interact:hover,
button:not(.button--primary):hover {
background-color: rgba(var(--text-color), 0.06);
background-color: #4885ff10;
}
.button--primary {

2
css/main.min.css vendored

File diff suppressed because one or more lines are too long

View File

@ -2,7 +2,7 @@
padding: 0;
margin: 0;
box-sizing: border-box;
font-family: "Inter", sans-serif;
font-family: "Archivo", sans-serif;
}
:root {
@ -18,8 +18,8 @@ body {
&,
* {
--accent-color: #256eff;
--text-color: 36, 36, 36;
--background-color: 248, 248, 248;
--text-color: 20, 20, 20;
--background-color: 255, 255, 255;
--foreground-color: rgb(255, 255, 255);
--danger-color: rgb(255, 75, 75);
--green: #1cad59;
@ -199,8 +199,9 @@ img {
font-size: 1.2rem;
}
.h3 {
font-size: 1rem;
h3 {
font-size: 1.2rem;
line-height: 1.3;
}
.h4 {
@ -210,7 +211,13 @@ img {
.h5 {
font-size: 0.75rem;
}
h1,
h2,
h3,
h4,
h5 {
// font-family: "Spectral", serif;
}
.uppercase {
text-transform: uppercase;
}
@ -352,7 +359,7 @@ img {
height: 0.4ch;
width: 0.4ch;
border-radius: 0.5em;
background-color: currentColor;
background-color: var(--accent-color);
}
}
.icon {
@ -563,7 +570,6 @@ sm-copy {
grid-template-columns: auto 1fr auto auto;
background-color: rgba(var(--background-color), 1);
z-index: 2;
border-bottom: thin solid rgba(var(--text-color), 0.1);
}
#expanding_search {
position: absolute;
@ -624,10 +630,10 @@ theme-toggle {
border-radius: 0.5rem;
width: 5.2rem;
text-align: center;
background-color: rgba(var(--text-color), 0.02);
background-color: #e6352f08;
.icon {
margin-bottom: 1rem;
transition: transform 0.3s;
transition: transform 0.3s, background-color 0.3s;
transform-origin: top;
}
&:hover {
@ -649,21 +655,21 @@ theme-toggle {
&__title {
line-height: 1.4;
}
&__category {
background-color: rgba(var(--text-color), 0.06);
margin-right: 0.5rem;
border-radius: 0.2rem;
font-size: 0.75rem;
padding: 0.2rem 0.4rem;
font-weight: 500;
color: rgba(var(--text-color), 0.8);
justify-self: flex-start;
text-transform: capitalize;
}
.flex {
font-size: 0.75rem;
}
}
.article-card__category {
background-color: #256eff08;
margin-right: 0.5rem;
border-radius: 0.2rem;
font-size: 0.75rem;
padding: 0.2rem 0.4rem;
font-weight: 500;
color: var(--accent-color) !important;
justify-self: flex-start;
text-transform: capitalize;
}
#trending_article_container,
#query_results_list,
@ -673,7 +679,6 @@ theme-toggle {
grid-template-columns: repeat(auto-fill, minmax(16rem, 1fr));
}
.trending-article {
counter-increment: trending;
gap: 0.5rem 1.5rem;
a:first-of-type {
position: relative;
@ -723,8 +728,7 @@ theme-toggle {
#article {
display: flex;
padding: 1.5rem;
max-height: 100%;
flex-wrap: wrap;
overflow-y: auto;
main {
& > section:last-of-type {
@ -753,9 +757,25 @@ theme-toggle {
}
}
.hero-section {
position: relative;
display: grid;
gap: 0.5rem;
grid-template-columns: minmax(0, 1fr);
margin-top: 1.5rem;
align-content: flex-end;
width: 100%;
p {
color: inherit;
}
.overlay {
position: absolute;
content: "";
top: 0;
left: 0;
width: 100%;
height: 100%;
background: linear-gradient(to top, rgba(0, 0, 0, 0.8), rgba(0, 0, 0, 0));
z-index: -1;
}
}
#article_image {
margin-top: 1.5rem;
@ -888,7 +908,7 @@ theme-toggle {
#article {
h1 {
font-size: 1.6rem;
line-height: 1.3;
line-height: 1.1;
}
h3:not(:first-of-type) {
@ -901,8 +921,8 @@ theme-toggle {
}
}
p {
font-family: "noto serif", serif;
font-size: 1rem;
// font-family: "Roboto", sans-serif;
font-size: 1.1rem;
line-height: 1.8;
& > * {
font-family: inherit;
@ -1017,6 +1037,11 @@ theme-toggle {
#search_button {
justify-self: flex-end;
}
#article {
main {
padding: 0 1.5rem;
}
}
.writer-card {
.writer-profile {
height: 2.5rem;
@ -1104,21 +1129,24 @@ theme-toggle {
#preview_popup,
#article {
h1 {
font-size: 2rem;
font-size: 2.2rem;
font-weight: 800;
}
h3 {
font-size: 1.4rem;
line-height: 1.3;
}
}
#article {
gap: 6vw;
padding: 0;
grid-template-columns: 16rem 1fr;
padding: 0 4vw;
main {
width: 60ch;
padding: 1.5rem 0;
margin-left: 4vw;
}
}
#article_aside {
width: 20rem;
padding: 1.5rem 2rem;
padding: 1.5rem;
display: flex;
flex-direction: column;
position: sticky;
@ -1186,7 +1214,7 @@ theme-toggle {
button:not(.button--primary) {
transition: background-color 0.3s;
&:hover {
background-color: rgba(var(--text-color), 0.06);
background-color: #4885ff10;
}
}
.button--primary {

View File

@ -13,6 +13,9 @@
<link
href="https://fonts.googleapis.com/css2?family=Inter:wght@400..700&family=Noto+Serif:ital,wght@0,400;0,700;1,400;1,700&display=swap"
rel="stylesheet">
<link href="https://fonts.googleapis.com/css2?family=Archivo:wght@400..700&display=swap" rel="stylesheet">
<link href="https://fonts.googleapis.com/css2?family=Barlow:wght@400;500;700&display=swap" rel="stylesheet">
<link href="https://fonts.googleapis.com/css2?family=Merriweather+Sans:wght@400..800&display=swap" rel="stylesheet">
<script src="purify.min.js" defer></script>
<script src="https://cdn.jsdelivr.net/npm/fuse.js@6.4.6" defer></script>
<script id="floGlobals">
@ -259,6 +262,7 @@
<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">
@ -430,6 +434,11 @@
</header>
<section class="grid gap-1-5">
<div class="grid gap-1-5">
<label class="grid gap-0-5">
<h5>Select Hero image</h5>
<img src="" alt="" id="preview_image">
<input id="select_image" type="file" accept="image">
</label>
<div class="grid gap-0-5">
<h5>Title</h5>
<sm-input id="edit_title" required></sm-input>
@ -438,16 +447,12 @@
<h5>Select category</h5>
<sm-select id="edit_category"></sm-select>
</div>
<div class="grid gap-0-5">
<h5>Hero image URL</h5>
<sm-input id="edit_image" type="url"></sm-input>
</div>
</div>
<div class="grid gap-1-5">
<div class="grid gap-0-5">
<h5>Summary</h5>
<sm-textarea id="edit_summary" rows="4"></sm-textarea>
</div>
</div>
<div class="grid gap-1-5">
<div class="grid gap-0-5">
<h5>Add tags</h5>
<tags-input id="edit_tags" limit="10"></tags-input>
@ -544,8 +549,9 @@
</a>
<div class="flex align-center" style="margin-top: 0.3rem;">
<a href="" class="article-card__category interact"></a>
<span class="trending-article__read-time h5"></span><span class="bullet-point"></span><time
class="trending-article__published h5"></time>
<time class="trending-article__published h5"></time>
<span class="bullet-point"></span>
<span class="trending-article__read-time h5"></span>
</div>
</div>
</template>
@ -556,8 +562,9 @@
</a>
<div class="flex align-center" style="margin-top: 0.3rem;">
<a href="" class="article-card__category interact"></a>
<span class="article-card__read-time"></span><span class="bullet-point"></span>
<time class="article-card__published"></time>
<span class="bullet-point"></span>
<span class="article-card__read-time"></span>
</div>
</li>
</template>
@ -1164,11 +1171,13 @@
getRef('news_categories_list').innerHTML = ''
getRef('news_categories_list').append(frag)
const arrOfArticles = getArrayOfObj(floGlobals.appObjects.rmTimes.articles)
// Render trending article card
let sortedByVotes = arrOfArticles.sort((a, b) => b.votes - a.votes)
sortedByVotes.slice(0, 4).forEach(articleDetail => frag.append(render.trendingArticleCard(articleDetail)))
getRef('trending_article_container').innerHTML = ''
getRef('trending_article_container').append(frag)
// Render trending article card
compactIDB.readData('appObjects', 'heroImages').then(heroImages => {
sortedByVotes.slice(0, 4).forEach(articleDetail => frag.append(render.trendingArticleCard({ ...articleDetail, heroImage: heroImages[articleDetail.uid].thumbnail })))
getRef('trending_article_container').innerHTML = ''
getRef('trending_article_container').append(frag)
})
// render latest articles
const sortedArticles = sortedByVotes.slice(4).sort((a, b) => b.published - a.published)
sortedArticles.forEach(articleDetail => frag.append(render.articleCard(articleDetail)))
@ -1177,9 +1186,9 @@
},
async article(articleID, firstLoad = true) {
const frag = document.createDocumentFragment()
const allArticles = await compactIDB.readData('appObjects', 'articlesContent')
const { title, published, readTime, contributors, updated, summary, heroImage } = floGlobals.appObjects.rmTimes.articles[articleID]
getRef('article_image').src = heroImage
const [allArticles, heroImages] = await Promise.all([compactIDB.readData('appObjects', 'articlesContent'), compactIDB.readData('appObjects', 'heroImages')])
const { title, published, readTime, contributors, updated, summary } = floGlobals.appObjects.rmTimes.articles[articleID]
getRef('article_image').src = heroImages[articleID].full
getRef('article_title').textContent = title
getRef('article_summary').textContent = summary
getRef('published_time').textContent = `${getFormattedTime(published, 'date-only')}${updated ? `, Updated ${relativeTime.from(updated)}` : ''}`
@ -1276,8 +1285,10 @@
Promise.all([
floCloudAPI.requestObjectData('rmTimes'),
floCloudAPI.requestObjectData('articlesContent'),
floCloudAPI.requestObjectData('heroImages'),
]).then(() => {
delete floGlobals.appObjects.articlesContent
delete floGlobals.appObjects.heroImages
showPage(window.location.hash, { firstLoad: true })
})
}
@ -1294,19 +1305,6 @@
return arr
}
// implement image resizing
function resizeImage(img, maxWidth, maxHeight) {
const canvas = document.createElement('canvas')
const ctx = canvas.getContext('2d')
const ratio = 1
canvas.width = img.width * ratio
canvas.height = img.height * ratio
ctx.drawImage(img, 0, 0, canvas.width, canvas.height)
const dataURL = canvas.toDataURL('image/jpeg', 0.5)
return dataURL
}
const categories = [
{
title: 'Culture',
@ -1797,7 +1795,7 @@
const isPublished = floGlobals.appObjects.rmTimes.articles.hasOwnProperty(articleID)
getConfirmation(`${isPublished ? 'Update' : 'Publish'} article?`).then(res => {
if (res) {
const { title, category, summary, published, tags, contributors } = getArticleMetaData()
const { title, category, summary, published, tags, contributors, heroImage } = getArticleMetaData()
floGlobals.appObjects.adminData.publishedVc[vectorClock] = true
floGlobals.appObjects.articlesContent[articleID] = content
if (isPublished) {
@ -1816,10 +1814,12 @@
floGlobals.appObjects.rmTimes.articles[articleID].tags = tags
floGlobals.appObjects.rmTimes.articles[articleID].readTime = readTime
floGlobals.appObjects.rmTimes.articles[articleID].summary = summary
floGlobals.appObjects.heroImages[articleID] = heroImage
Promise.all([
floCloudAPI.updateObjectData('rmTimes'),
floCloudAPI.updateObjectData('adminData'),
floCloudAPI.updateObjectData('articlesContent'),
floCloudAPI.updateObjectData('heroImages'),
]).then(() => {
notify(`${isPublished ? 'Updated' : 'Published'} article`, 'success')
if (!isPublished) {
@ -1838,9 +1838,9 @@
const { message: { articleID, title, contributors } } = floGlobals.generalData[`publishing_requests|${floGlobals.adminID}|${floGlobals.application}`][vc]
const isPublished = floGlobals.appObjects.rmTimes.articles.hasOwnProperty(articleID)
if (isPublished)
setArticleMetaData(floGlobals.appObjects.rmTimes.articles[articleID])
setArticleMetaData(floGlobals.appObjects.rmTimes.articles[articleID], articleID)
else
setArticleMetaData({ title, contributors })
setArticleMetaData({ title, contributors }, articleID)
floGlobals.subAdminData = {
actionType: 'request',
articleID,
@ -1876,10 +1876,16 @@
}
}
function setArticleMetaData(details) {
const { category, title, tags, summary, published, contributors, heroImage } = details
async function setArticleMetaData(details, articleID) {
const { category, title, tags, summary, published, contributors } = details
const heroImage = floGlobals.appObjects.heroImages[articleID]
getRef('edit_title').value = title;
getRef('edit_image').value = heroImage || '';
if (heroImage) {
getRef('preview_image').src = heroImage.thumbnail
getRef('select_image').value = '';
} else {
getRef('preview_image').src = ''
}
getRef('edit_summary').value = summary || '';
getRef('edit_category').value = category || '';
getRef('edit_contributors').value = contributors || [];
@ -1891,7 +1897,7 @@
return {
title: getRef('edit_title').value.trim(),
category: getRef('edit_category').value,
heroImage: getRef('edit_image').value,
heroImage: currentSelectedImage,
summary: getRef('edit_summary').value.trim(),
published: new Date(getRef('edit_published').value).getTime(),
contributors: getRef('edit_contributors').value,
@ -1902,7 +1908,7 @@
if (e.target.closest('.edit-article')) {
const button = e.target.closest('.edit-article');
const articleID = button.dataset.articleId;
setArticleMetaData(floGlobals.appObjects.rmTimes.articles[articleID])
setArticleMetaData(floGlobals.appObjects.rmTimes.articles[articleID], articleID)
floGlobals.subAdminData = {
actionType: 'analytics',
articleID,
@ -1921,11 +1927,11 @@
floGlobals.appObjects.rmTimes.articles[articleID].title = title
floGlobals.appObjects.rmTimes.articles[articleID].tags = tags
floGlobals.appObjects.rmTimes.articles[articleID].summary = summary
floGlobals.appObjects.rmTimes.articles[articleID].heroImage = heroImage
floGlobals.appObjects.rmTimes.articles[articleID].contributors = contributors
floGlobals.appObjects.heroImages[articleID] = heroImage
Promise.all([
floCloudAPI.updateObjectData('rmTimes'),
floCloudAPI.updateObjectData('adminData'),
floCloudAPI.updateObjectData('heroImages'),
]).then(() => {
notify(`Updated article meta data`, 'success')
hidePopup()
@ -1991,7 +1997,10 @@
console.log('calculated votes')
})
})
floGlobals.appObjects.articlesContent = await compactIDB.readData('appObjects', 'articlesContent')
Promise.all([compactIDB.readData('appObjects', 'articlesContent'), compactIDB.readData('appObjects', 'heroImages')]).then(res => {
floGlobals.appObjects.articlesContent = res[0];
floGlobals.appObjects.heroImages = res[1];
})
}
getRef('section_selector').addEventListener('change', e => {
@ -2118,6 +2127,45 @@
}
}
let currentSelectedImage = {}
getRef('select_image').addEventListener('change', function (e) {
currentSelectedImage = {}
if (this.files.length === 0) return
const selectedFile = this.files[0]
const reader = new FileReader();
reader.onload = function (e) {
// show selected image in the preview
getRef('preview_image').src = e.target.result;
getRef('preview_image').addEventListener('load', function (event) {
// Dynamically create a canvas element
const canvas = createElement("canvas");
const context = canvas.getContext("2d");
const originalWidth = getRef('preview_image').width;
const originalHeight = getRef('preview_image').height;
const resizingFactor = parseFloat((1 / (originalWidth / 600)).toFixed(2));
const canvasWidth = originalWidth * resizingFactor;
const canvasHeight = originalHeight * resizingFactor;
canvas.width = canvasWidth;
canvas.height = canvasHeight;
context.drawImage(
getRef('preview_image'),
0,
0,
originalWidth * resizingFactor,
originalHeight * resizingFactor
);
currentSelectedImage['thumbnail'] = canvas.toDataURL(selectedFile.type);
}, { once: true });
currentSelectedImage.name = selectedFile.name
currentSelectedImage.type = selectedFile.type
currentSelectedImage.full = e.target.result
}
reader.readAsDataURL(this.files[0]);
})
</script>
<script id="onLoadStartUp">
function onLoadStartUp() {