refactor: split view logic into many standalone pieces

split the code so as to make at least the answers part reusable.
also added grouped the code in new directories.
This commit is contained in:
zyachel 2022-07-23 15:54:24 +05:30
parent 12ecbf5296
commit e76605dd7a
13 changed files with 175 additions and 82 deletions

5
app.js
View file

@ -23,7 +23,6 @@ app.use(
helmet({
contentSecurityPolicy: {
directives: {
'img-src': ["'self'"],
'script-src': ["'self'", 'cdn.jsdelivr.net'],
},
},
@ -33,7 +32,9 @@ app.use(
// 2. SETTING VIEW ENGINE AND PATH TO STATIC ASSETS
app.set('view engine', 'pug');
const pathToViews = fileURLToPath(new URL('./views/pug', import.meta.url));
const pathToViews = fileURLToPath(
new URL('./views/pug/pages', import.meta.url)
);
app.set('views', pathToViews);
const pathToPublicDirectory = fileURLToPath(
new URL('./public', import.meta.url)

View file

@ -39,7 +39,9 @@ export const answers = catchAsyncErrors(async (req, res, next) => {
if (nonSlugRoutes.includes(slug)) return next();
const answersData = await getAnswers(slug);
const title = answersData.question.text.spans.map(span => span.text).join('');
const title = answersData.question.text.section[0].spans
.map(span => span.text)
.join('');
res.status(200).render('answers', {
data: answersData,

View file

@ -47,8 +47,8 @@ const getAnswers = async slug => {
credential: ansObj.node.answer.authorCredential?.translatedString,
// additionalCredentials: ansObj.node.answer?.credibilityFacts.map(),
},
OriginalQuestion: {
text: JSON.parse(ansObj.node.answer.question.title).sections[0],
originalQuestion: {
text: JSON.parse(ansObj.node.answer.question.title).sections,
url: ansObj.node.answer.question.url,
qid: ansObj.node.answer.question.qid,
isDeleted: ansObj.node.answer.question.isDeleted,
@ -58,7 +58,7 @@ const getAnswers = async slug => {
// main data object to be returned
const data = {
question: {
text: JSON.parse(rawData.title).sections[0],
text: JSON.parse(rawData.title).sections,
url: rawData.url,
qid: rawData.qid,
idDeleted: rawData.isDeleted,
@ -76,7 +76,7 @@ const getAnswers = async slug => {
questionObj => ({
qid: questionObj.qid,
url: questionObj.url,
text: JSON.parse(questionObj.title).sections[0],
text: JSON.parse(questionObj.title).sections,
})
),
};

View file

@ -40,11 +40,12 @@ html(lang='en')
body.body
-if (title !=='About')
a.skip-link(href="#main") Skip to main content
//- stuff that's visible on page goes here
include _header
//- MAIN CONTENT GOES HERE
include layout/_header
block content
p placeholder text
include _footer
include layout/_footer
//- including mathjax script only when some answer has math expressions(using the var from above)
if someAnswerContainsMath

View file

@ -1,33 +1,22 @@
//- putting spans in semantically correct tags(to the extent possible)
mixin spansChecker(spans)
each span in spans
- if(span.modifiers.math)
//- setting that var in the base.pug value to true here.
- someAnswerContainsMath = true;
span.answer__span-math= `\\(${span.text}\\)`
- else if (span.modifiers.code)
code.answer__span-code= span.text
- else if (span.modifiers.embed)
a.answer__span-link.answers__link(href=span.modifiers.embed.url)= span.modifiers.embed.title || 'link'
- else if (span.modifiers.link) //- removing quora.com from the link in case it is a quora.com link.
a.answer__span-link.answers__link(href=span.modifiers.link.url.split('https://www.quora.com')[1] || span.modifiers.link.url)=span.text
- else if (!!span.modifiers.bold && !!span.modifiers.italic) //- some people really like to add both bold and emphasis :shrug:
strong.answer__span-bold: em.answer__span-italic= span.text
- else if (!!span.modifiers.bold)
strong.answer__span-bold= span.text
- else if (!!span.modifiers.italic)
em.answer__span-italic= span.text
- else
span.answer__span-plain= span.text
//-//////////////////////////////////////////////////////
//- INCLUDES/EXTENDS
//-//////////////////////////////////////////////////////
include ../mixins/_formatText.pug
//-//////////////////////////////////////////////////////
//- LOCAL HELPER MIXINS
//-//////////////////////////////////////////////////////
//- mixin to format date and stuff
mixin addDate(date)
- const dateObj = new Date(date / 1000);
time.answer__metadata-data(datetime= dateObj.toISOString(), title=dateObj.toUTCString())= Intl.DateTimeFormat('en-US', {year: '2-digit', month: 'short', day: 'numeric' }).format(dateObj)
//- putting every answer inside the article tag because it is theorectically independent part of the page
//-//////////////////////////////////////////////////////
//- MAIN CONTENT
//-//////////////////////////////////////////////////////
article.answer
//- stuff about author
//- ABOUT AUTHOR
figure.answer__author
figcaption.answer__author-name
if answerObj.author.isAnon
@ -41,33 +30,22 @@ article.answer
img.answer__author-image(src=answerObj.author.avatar.replace('https://', "/api/v1/image/"), alt=`${answerObj.author.name}'s profile photo`, loading='lazy')
p.answer__author-credentials(aria-label=`${answerObj.author.name}'s credentials`)= answerObj.author.credential || ''
//- ORIGINAL QUESTION
h3.answer__question.heading.heading__tertiary
span Originally answered to 
a.answer__link.answers__link(href=answerObj.OriginalQuestion.url)
+spansChecker(answerObj.OriginalQuestion.text.spans)
a.answer__link.answers__link(href=answerObj.originalQuestion.url)
+formatText(answerObj.originalQuestion.text)
//- main answer goes here
//- ANSWER
.answer__text
//- putting paragraphs in semantically correct tags(to the extent possible)
each para in answerObj.text
-if(para.type==='image')
img.answer__para.answer__image(src=para.spans[0].modifiers.master_url.replace('https://', "/api/v1/image/"), alt='User embedded image', loading='lazy')
- else if (para.type==='code')
pre.answer__para.answer__code: code
+spansChecker(para.spans)
- else if(para.quoted)
blockquote.answer__para.answer__quote
+spansChecker(para.spans)
- else
p(class=`answer__para answer__${para.type}`)
+spansChecker(para.spans)
+formatText(answerObj.text)
//- for quora plus answers. since quora only shows half answer, we gotta warn viewer.
unless answerObj.isViewable
p.answer__unviewable
svg.answer__icon: use(href='/misc/sprite.svg#icon-danger')
| This is a Quora plus answer and hence full answer is not viewable.
//- metadata about answer(likes, shares, etc)
//- ANSWER METADATA
.answer__metadata
p.answer__metadata-item
svg.answer__icon: use(href='/misc/sprite.svg#icon-clock')

View file

@ -1,6 +1,12 @@
//-//////////////////////////////////////////////////////
//- MAIN CONTENT
//-//////////////////////////////////////////////////////
footer.footer(class=`${meta.title ==='About' ? 'footer__about' : ''}`)
block footer
//- more stuff will be prepended here on about page
//- EXTRA STUFF GOES HERE IF THE PAGE IS ABOUT PAGE
//- NAVIGATION
nav.footer__nav-box(aria-label='Primary navigation')
ul.footer__nav
- if (meta.title !=='About')
@ -9,7 +15,8 @@ footer.footer(class=`${meta.title ==='About' ? 'footer__about' : ''}`)
li.footer__nav-item: a.footer__nav-link.footer__link(href="https://github.com/zyachel/quetre") Source Code
li.footer__nav-item: a.footer__nav-link.footer__link(href="/privacy") Privacy
li.footer__nav-item: a.footer__nav-link.footer__link(href="#") Back to top
//- LICENSE
p.footer__license Licensed under 
a.footer__link(href="https://www.gnu.org/licenses/agpl-3.0.html") GNU AGPLv3
| .

View file

@ -1,13 +1,17 @@
//-//////////////////////////////////////////////////////
//- MAIN CONTENT
//-//////////////////////////////////////////////////////
header.header(class=`${meta.title === 'About' ? 'header__about': ''}`)
//- BAR INCLUDES BOTH LOGO AND NAV(WHICH'LL BE THERE ONLY ON ABOUT PAGE)
.header__bar
a.header__link.header__logo(href='/') Quetre
//- for nav on about page
block header__nav
//- BUTTON FOR CHANGING THEME
button.button.theme-changer.header__theme(aria-label='Change Theme')
svg.icon.icon__theme.theme-changer__icon.theme-changer__icon--sun: use(href='/misc/sprite.svg#icon-sun')
svg.icon.icon__theme.theme-changer__icon.theme-changer__icon--moon: use(href='/misc/sprite.svg#icon-moon')
//- for info line on about page
//- IF THE PAGE IS ABOUT PAGE, THE BLOCK BELOW WILL GET POPULATED
block header__info

View file

@ -0,0 +1,54 @@
//-//////////////////////////////////////////////////////
//- LOCAL HELPER MIXINS
//-//////////////////////////////////////////////////////
//- this mixin handles a single paragraph, which is passed to it by the main 'formatText' mixin
mixin spansChecker(spans)
each span in spans
//- handle tex equations
- if(span.modifiers.math)
//- setting that var in the base.pug value to true here, so that mathjax library is loaded in the end
- someAnswerContainsMath = true;
span.answer__span-math= `\\(${span.text}\\)`
//- handle small code text
- else if (span.modifiers.code)
code.answer__span-code= span.text
//- handle embedded content
- else if (span.modifiers.embed)
a.answer__span-link.answers__link(href=span.modifiers.embed.url)= span.modifiers.embed.title || 'link'
//- handle links
- else if (span.modifiers.link) //- removing quora.com from the link in case it is a quora.com link.
a.answer__span-link.answers__link(href=span.modifiers.link.url.split('https://www.quora.com')[1] || span.modifiers.link.url)=span.text
//- handle bold + italic text
- else if (!!span.modifiers.bold && !!span.modifiers.italic)
strong.answer__span-bold: em.answer__span-italic= span.text
//- handle bold text
- else if (!!span.modifiers.bold)
strong.answer__span-bold= span.text
//- handle italic text
- else if (!!span.modifiers.italic)
em.answer__span-italic= span.text
//- regular plain text
- else
span.answer__span-plain= span.text
//-//////////////////////////////////////////////////////
//- MAIN MIXIN TO FORMAT ANY TEXT
//-//////////////////////////////////////////////////////
mixin formatText(text)
each para in text
//- handle images
-if(para.type==='image')
img.answer__para.answer__image(src=para.spans[0].modifiers.master_url.replace('https://', "/api/v1/image/"), alt='User embedded image', loading='lazy')
//- handle code blocks
- else if (para.type==='code')
pre.answer__para.answer__code: code
+spansChecker(para.spans)
//- handle quotes
- else if(para.quoted)
blockquote.answer__para.answer__quote
+spansChecker(para.spans)
//- rest of the types that don't need/can't be put in a semantic html tag
- else
p(class=`answer__para answer__${para.type}`)
+spansChecker(para.spans)

View file

@ -1,5 +1,12 @@
extends base
//-//////////////////////////////////////////////////////
//- INCLUDES/EXTENDS
//-//////////////////////////////////////////////////////
extends ../base
//-//////////////////////////////////////////////////////
//- SECONDARY CONTENT
//-//////////////////////////////////////////////////////
//- this block adds nav bar to header
block header__nav
nav.header__nav-box(aria-label='Table of Contents'): ul.header__nav
li.header__nav-item
@ -11,14 +18,22 @@ block header__nav
li.header__nav-item
a.header__link.header__nav-link(href="https://github.com/zyachel/quetre") Source
//- this block makes header span full viewport and adds a one-liner hero text
block header__info
.header__info
h1.heading.heading__primary.header__hero A libre front-end for Quora
a.header__link.header__down(href='#features', aria-label='go to features section'): svg.icon.icon__down: use(href='/misc/sprite.svg#icon-down')
//- this block changes the footer a bit
block prepend footer
p.footer__logo Quetre
//-//////////////////////////////////////////////////////
//- MAIN CONTENT
//-//////////////////////////////////////////////////////
block content
main#main.main.about
//- FEATURES
section.about__features.features#features
h2.heading.heading__secondary.about__heading-secondary.features__heading Key features
.features__list
@ -49,6 +64,7 @@ block content
code /api/v1/
| after the domain name in the URL and get a JSON repsonse.
//- FAQs
section.about__faqs.faqs#faqs
h2.heading.heading__secondary.about__heading-secondary.faqs__heading FAQs
.faqs__list
@ -87,6 +103,7 @@ block content
a.about__link(href="https://lingva.ml/en/la/questions%20and%20answers%0A") in Latin
|.
//- CONTACT INFO
section.about__contact#contact
h2.heading.heading__secondary.about__heading-secondary.about__contact-heading Contact
address.about__contact-text Reach me via 
@ -94,5 +111,3 @@ block content
| or 
a.about__link(href="mailto:aricla@protonmail.com") Email
| in case you got any suggestions or feedback, or if you just want to drop a hi :)
block prepend footer
p.footer__logo Quetre

View file

@ -1,39 +1,43 @@
extends base
//-//////////////////////////////////////////////////////
//- INCLUDES/EXTENDS
//-//////////////////////////////////////////////////////
extends ../base
include ../mixins/_formatText
mixin checkMath(spans)
each span in spans
-if(span.modifiers?.math)
span= `\\(${span.text}\\)`
-else
span= span.text
block content
//-//////////////////////////////////////////////////////
//- MAIN CONTENT
//-//////////////////////////////////////////////////////
block content
main#main.main.answers
.answers__question-box
//- THIS QUESTION AND METADATA
.answers__question-box
h1.heading.heading__primary.answers__question
+checkMath(data.question.text.spans)
.answers__metadata
+formatText(data.question.text)
.answers__metadata
p.answers__answers-total= `${ data.numAnswers ? 'Total answers: ' + data.numAnswers : 'Unanswered'}`
p.answers__answers-shown Viewable answers: #{data.answers.length}
a.answers__question-link.answers__link(href='https://quora.com' + data.question.url) View on Quora
a.answers__question-link.answers__link(href='https://www.quora.com' + data.question.url) View on Quora
//- ANSWERS TO THIS QUESTION
.answers-box.answers__answers-box
h2.heading.heading__secondary.answers__answers-box-heading Answers
.answers-box__list
each answerObj in data.answers
include _answer
include ../layout/_answer
//- TOPICS TAGGED WITH THIS QUESTION
aside.topics.answers__topics
h2.heading.heading__secondary.answers__topic-heading Tagged Topics
ul.topics__list
each topic in data.topics
li.topics__item: a.topics__link.answers__link(href=topic.url)= topic.name
//- RELATED QUESTIONS
aside.related.answers__related
h2.heading.heading__secondary.answers__related-heading Related Questions
ul.related__questions
each question in data.relatedQuestions
li.related__question-item: a.related__question-link.answers__link(href=question.url)
+checkMath(question.text.spans)
+formatText(question.text)

View file

@ -1,15 +1,23 @@
extends base
//-//////////////////////////////////////////////////////
//- INCLUDES/EXTENDS
//-//////////////////////////////////////////////////////
extends ../base
//-//////////////////////////////////////////////////////
//- MAIN CONTENT
//-//////////////////////////////////////////////////////
block content
main#main.main.error
//- ABOUT ERROR
p.error__code= data.statusCode
p.error__message= data.message
//- will only apply in dev mode
//- ONLY FOR DEV MODE
if data.stack
.error__stack-box: pre.error__stack
code.error__text= data.stack
//- CHOICES TO USER
p.error__return Go back to the 
a.error__link(href="/") Home Page
|.

View file

@ -1,13 +1,21 @@
extends base
//-//////////////////////////////////////////////////////
//- INCLUDES/EXTENDS
//-//////////////////////////////////////////////////////
extends ../base
//-//////////////////////////////////////////////////////
//- MAIN CONTENT
//-//////////////////////////////////////////////////////
block content
main#main.main.privacy
h1.heading.heading__primary.privacy__heading-primary Privacy Policy
//- ONE LINER
section.privacy__short
h2.heading.heading__secondary.privacy__heading-secondary The short version
p.privacy__short-text Quetre doesn't collect any personally identifiable data.
//- EXPLAINER
section.faqs.privacy__long
h2.heading.heading__secondary.privacy__heading-secondary The long version
.faqs__list

View file

@ -1,11 +1,21 @@
extends base
//-//////////////////////////////////////////////////////
//- INCLUDES/EXTENDS
//-//////////////////////////////////////////////////////
extends ../base
//-//////////////////////////////////////////////////////
//- LOCAL HELPER MIXINS
//-//////////////////////////////////////////////////////
//- mixin to make number a bit more readable
mixin formatNumber(number, ...classes)
span(class=classes.join(' '))= new Intl.NumberFormat().format(number)
//-//////////////////////////////////////////////////////
//- MAIN CONTENT
//-//////////////////////////////////////////////////////
block content
main#main.main.topic
//- all info related to the topic
//- TOPIC NAME AND METADATA
.topic__stats.topic-stats
h1.heading.heading__primary.topic__heading.topic-stats__heading= data.name
img.topic-stats__image(src=data.image.replace('https://', "/api/v1/image/") alt=`image depicting ${data.name}`, loading='lazy')
@ -28,7 +38,8 @@ block content
span.topic-stats__metadata-data 18+
span.topic-stats__metadata-text  Adult Topic
a.topic__link(href='https://www.quora.com' + data.url) View on Quora
//- AUTHORS RELATED TO THE TOPIC AND METADATA
.topic__famous-authors.famous-authors
h2.heading.heading__secondary.famous-authors__heading Most viewed authors
.famous-authors__list
@ -60,6 +71,7 @@ block content
+formatNumber(author.numAnswers, 'famous-authors__metadata-data')
span.famous-authors__metadata-text  Answers
//- RELATED TOPICS
.topic__related-topics.related-topics
h2.heading.heading__secondary.related-topics__heading Related Topics
.related-topics__list
@ -72,4 +84,3 @@ block content
svg.icon.related-topics__icon: use(href='/misc/sprite.svg#icon-user')
+formatNumber(topic.numFollowers, 'related-topics__metadata-data')
span.topic-stats__metadata-text  Followers