aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.gitignore2
-rwxr-xr-xcomponents/post-preview.sh26
-rwxr-xr-xconfig.sh8
-rwxr-xr-xfooter.sh17
-rw-r--r--header.sh100
-rwxr-xr-xindex.sh39
-rwxr-xr-xnav.sh11
-rwxr-xr-xpages/article.sh21
-rwxr-xr-x[-rw-r--r--]pages/contato.sh23
-rwxr-xr-xpages/home.sh29
-rw-r--r--static/header.js137
-rw-r--r--static/styles.css (renamed from styles.css)86
-rwxr-xr-xutils/parse-article.sh18
13 files changed, 379 insertions, 138 deletions
diff --git a/.gitignore b/.gitignore
index eb2c80d..1661d60 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1 +1,3 @@
/pages/articles/*
+/artigos/*
+/articles/*
diff --git a/components/post-preview.sh b/components/post-preview.sh
new file mode 100755
index 0000000..dbbf663
--- /dev/null
+++ b/components/post-preview.sh
@@ -0,0 +1,26 @@
+#!/bin/bash
+
+source $SCRIPT_DIR/utils/parse-article.sh
+
+cat <<POST
+<section class="post-preview">
+ <div class="post-header-preview">
+ <h5 class="post-title">$ stat -c "%w" \\<br/> &gt;&ensp;$ARTICLE_FILE</h5>
+ <small>$ARTICLE_BYTES</small> <br/>
+ <time>$(stat --format="Criado em: %w" $ARTICLE_FILE)</time>
+
+ <ul class="tags">
+ $(
+ for i in "${ARTICLE_TAGS[@]}"; do
+ echo "<li class="tag">$i</li>"
+ done
+ )
+ </ul>
+ </div>
+
+ <p>$(strip-html-tags "$ARTICLE_CONTENT" | head -c 120)...</p>
+
+ <a href="/$ARTICLE_FILE">Ler artigo completo</a>
+</section>
+POST
+
diff --git a/config.sh b/config.sh
new file mode 100755
index 0000000..4cfed05
--- /dev/null
+++ b/config.sh
@@ -0,0 +1,8 @@
+#!/bin/bash
+
+# All relatives paths must end with slash and must not be start by dot and/or slash
+# Examplo
+# VAR_PATH=dir/subdir/
+
+# Folder where posts are stored, must be relative and suffed by a slash
+ARTICLES_PATH=artigos/
diff --git a/footer.sh b/footer.sh
index a7125bb..e7790cd 100755
--- a/footer.sh
+++ b/footer.sh
@@ -5,6 +5,19 @@ cat <<FOOTER
footer {
text-align: center;
}
+
+ footer h1 {
+ text-align: left;
+ }
+
+ #footer-command-prompt {
+ padding-top: 25px;
+ }
+
+ #footer-command-prompt h1 {
+ padding: 0px;
+ margin: 0px;
+ }
</style>
<footer>
@@ -14,5 +27,9 @@ cat <<FOOTER
Gateway: $GATEWAY_INTERFACE <br />
Pré-processador HTML: GNU Bash $BASH_VERSION
</p>
+
+ <div id="footer-command-prompt">
+ <h1 class="header-title">$ <span class="text"></span><span class="cursor after-prompt">&ensp;</span></h1>
+ </div>
</footer>
FOOTER
diff --git a/header.sh b/header.sh
index 2e9f467..a95aa47 100644
--- a/header.sh
+++ b/header.sh
@@ -2,105 +2,37 @@
cat <<HEADER
<style>
- header h1, header h2 {
+ .header-title {
+ font-size: 1.8rem;
text-align: left;
line-break: anywhere;
}
- header .separator-wrap {
- margin-top: 15px;
- }
-
- .cursor {
- background-color: var(--primary-fg);
- animation-name: blink-animation;
- animation-duration: 500ms;
- animation-iteration-count: infinite;
- animation-timing-function: cubic-bezier(0.83,-0.22, 0.54, 0.55);
- }
-
- @keyframes blink-animation {
- to {
- opacity: 0;
- }
- }
-
.mobile { display: none; }
@media screen and (max-width: 769px) {
.mobile { display: block; }
.desktop { display: none; }
+
+ .header-title {
+ font-size: 1.2rem;
+ }
}
</style>
<script type="text/javascript">
let mobileText = '$HEADER_TITLE_MOBILE';
let desktopText = '$HEADER_TITLE';
- let isMobile = window.innerWidth < 769;
-
- function hideContent() {
- let mainContainer = document.querySelector('main');
- let footer = document.querySelector('footer');
- let headerSeparator = document.querySelector('header > .separator-wrap');
- mainContainer.style.opacity = '0';
- footer.style.opacity = '0';
- headerSeparator.style.opacity = '0';
- }
-
- function showContent() {
- let mainContainer = document.querySelector('main');
- let footer = document.querySelector('footer');
- let headerSeparator = document.querySelector('header > .separator-wrap');
- mainContainer.style.transition = '500ms';
- mainContainer.style.opacity = '1';
- footer.style.transition = '500ms';
- footer.style.opacity = '1';
- headerSeparator.style.transition = '500ms';
- headerSeparator.style.opacity = '1';
-
- }
-
- function typingAnimation(el, text, index, cb) {
- let char = text[index];
- if (char == ';') {
- el.innerHTML += '<br />';
- } else if (char == ' ') {
- el.innerHTML += '&ensp;';
- } else {
- el.innerText += char;
- }
-
- if (index < text.length - 1) {
- setTimeout(() => typingAnimation(el, text, index + 1, cb), 30);
- } else {
- cb();
- }
- }
-
- document.addEventListener('DOMContentLoaded', function(event) {
- hideContent();
- let titleMobile = document.querySelector("#header-title-mobile > .text");
- let titleDesktop = document.querySelector('#header-title > .text');
-
- if (isMobile) {
- titleMobile.innerHTML = '';
- } else {
- titleDesktop.innerHTML = '';
- }
-
- setTimeout(() => {
- if (isMobile) {
- typingAnimation(titleMobile, mobileText, 0, showContent);
- } else {
- typingAnimation(titleDesktop, desktopText, 0, showContent);
- }
- }, 200);
- });
</script>
-
-<header>
- <h1 id="header-title" class="desktop">$ <span class="text">echo "Jefferson Julio" >> ./programadores</span><span class="cursor">&ensp;</span></h1>
- <h1 id="header-title-mobile" class="mobile">$ <span class="text">echo "Jefferson Julio" \\<br/> >> ./programadores</span><span class="cursor">&ensp;</span></h1>
- <div class="separator-wrap"><div class="separator"></div></div>
+<script type="text/javascript" src="/static/header.js"></script>
+<script type="text/javascript" src="/static/navigator.js"></script>
+
+<header id="top-header">
+ <h1 id="header-title" class="desktop header-title">
+ $ <span class="text">${HEADER_TITLE/;/<br\/>$&ensp;}<br/></span>
+ </h1>
+ <h1 id="header-title-mobile" class="mobile header-title">
+ $ <span class="text">${HEADER_TITLE_MOBILE/;/<br\/>$&ensp;}<br/></span>
+ </h1>
</header>
HEADER
diff --git a/index.sh b/index.sh
index 16eb31c..76d8c8d 100755
--- a/index.sh
+++ b/index.sh
@@ -4,14 +4,25 @@ if [ "$REQUEST_URI" = '/favicon.ico' ]; then
exit 0
fi
+SCRIPT_DIR="$( cd "$( dirname "$(readlink -f "$0")" )" >/dev/null 2>&1 && pwd )"
+source ./config.sh
+
RESPONSE_CONTENT_TYPE="text/html"
STATUS=200
-HEADER_TITLE='source ./programadores/Jefferson Júlio/site/home.sh'
-HEADER_TITLE_MOBILE='cd ./programadores/ && \\ ;> source Jefferson Júlio/\\ ;> site/home.sh'
+HEADER_TITLE='cd programadores/;cd Jefferson\\ Julio/;source jefferson.sh'
+HEADER_TITLE_MOBILE="$HEADER_TITLE"
urldecode() { : "${*//+/ }"; echo -e "${_//%/\\x}"; }
+status-ok () {
+ [ "$STATUS" -gt 199 ] && [ "$STATUS" -lt 300 ] && echo 1
+}
+
+strip-html-tags () {
+ echo "$1" | sed 's/<[^>]*>//g'
+}
+
html () {
cat <<HTML
<html lang="pt-br">
@@ -19,15 +30,13 @@ html () {
<meta charset="utf-8" />
<title>jefferson.sh</title>
<meta name="viewport" content="width=device-width,initial-scale=1" />
-
- <style>
- $(cat ./styles.css)
- </style>
+ <link rel="stylesheet" href="/static/styles.css" />
+ <link rel="manifest" href="/static/manifest.json">
</head>
<body>
- $(source ./nav.sh)
$(source ./header.sh)
+ $(source ./nav.sh)
<main>
$BODY
@@ -47,14 +56,14 @@ router () {
BODY=$(source ./pages/home.sh)
;;
/contato)
- HEADER_TITLE="source ./pages/contato.sh"
+ HEADER_TITLE="source pages/contato.sh"
HEADER_TITLE_MOBILE="$HEADER_TITLE"
BODY=$(source ./pages/contato.sh)
;;
- /artigo/*.txt)
- ARTICLE_FILE=./pages/articles/${REQUEST_URI/\/artigo\//}
- HEADER_TITLE="cat $ARTICLE_FILE"
- HEADER_TITLE_MOBILE="$HEADER_TITLE"
+ /$ARTICLES_PATH*.txt)
+ ARTICLE_FILE=.$REQUEST_URI
+ HEADER_TITLE="ARTICLE_FILE=$ARTICLE_FILE source pages/article.sh"
+ HEADER_TITLE_MOBILE="export ARTICLE_FILE=$ARTICLE_FILE;source pages/article.sh"
BODY=$(source ./pages/article.sh)
;;
/json)
@@ -69,9 +78,9 @@ JSON
;;
*)
STATUS=404
- HEADER_TITLE="cat .$REQUEST_URI"
- HEADER_TITLE_MOBILE="$HEADER_TITLE"
- BODY="<p class='err-404'>cat: .$REQUEST_URI: Arquivo ou diretório inexistente</p>"
+ HEADER_TITLE="REQUEST_FILE=.$REQUEST_URI source page.sh"
+ HEADER_TITLE_MOBILE="export REQUEST_FILE=.$REQUEST_URI;source page.sh"
+ BODY="<p class='err-404'>404: .$REQUEST_URI: Arquivo ou diretório inexistente</p>"
;;
esac
}
diff --git a/nav.sh b/nav.sh
index 5881bb3..2b0049d 100755
--- a/nav.sh
+++ b/nav.sh
@@ -3,7 +3,8 @@
cat <<NAV
<style>
.top-nav {
- position: relative;
+ position: sticky;
+ top: 0px;
display: inline-flex;
background-color: var(--primary-fg);
height: 40px;
@@ -31,8 +32,6 @@ cat <<NAV
.nav-links li {
position: relative;
height: 100%;
- padding-left: 5px;
- padding-right: 5px;
display: inline-flex;
align-items: center;
}
@@ -59,8 +58,10 @@ cat <<NAV
}
.nav-header {
- padding-top: 10px;
+ padding-top: 25px;
padding-bottom: 10px;
+ display: block;
+ text-align: center;
}
.nav-header a {
@@ -80,6 +81,7 @@ cat <<NAV
</style>
<nav class="top-nav">
+ <div class="pad-left"></div>
<span class="nav-header">
<a href="https://jefferson.sh">jefferson.sh</a>
</span>
@@ -87,5 +89,6 @@ cat <<NAV
<li> <a href="/">Início</a> </li>
<li> <a href="/contato">Contato</a> </li>
</ul>
+ <div class="pad-right"></div>
</nav>
NAV
diff --git a/pages/article.sh b/pages/article.sh
index 9832a76..b50d922 100755
--- a/pages/article.sh
+++ b/pages/article.sh
@@ -1,26 +1,11 @@
#!/bin/bash
-ARTICLE_TITLE=$(basename "$ARTICLE_FILE")
-
-# Pick all file content
-ARTICLE_FILE_CONTENT=$(cat "$ARTICLE_FILE")
-
-# The first 3 lines of the file are metadata information
-ARTICLE_METADATA=$(echo "$ARTICLE_FILE_CONTENT" | head -n 3)
-
-# Skip file metadata, first 3 lines
-ARTICLE_CONTENT=$(echo "$ARTICLE_FILE_CONTENT" | tail -n +3)
-
-# Tags are placed on the first line of the file, delimited by commas
-IFS=',' read -ra ARTICLE_TAGS <<< "$(echo "$ARTICLE_METADATA" | head -n 1)"
-
-ARTICLE_TIME=$(stat --format="Criado em: %w<br/>Última atualização: %z" "$ARTICLE_FILE")
-ARTICLE_BYTES=$(stat --format="%o bytes" "$ARTICLE_FILE")
+source $SCRIPT_DIR/utils/parse-article.sh
if [ $? -gt 0 ]; then
STATUS=404
cat <<ERR
-<p class="err-404">cat: $ARTICLE_FILE: Arquivo ou diretório inexistente</p>
+<p class="err-404">404: $ARTICLE_FILE: Arquivo ou diretório inexistente</p>
ERR
exit 0
fi
@@ -29,7 +14,7 @@ cat <<ARTICLE
<article class="container">
<section>
<div class="post-header">
- <h4 class="post-title">stat -c "%o %w %z" $ARTICLE_FILE</h4>
+ <h4 class="post-title">$ stat -c "%s %w %z" \\ <br/> &gt;&ensp;$ARTICLE_FILE</h4>
<small>$ARTICLE_BYTES</small> <br/>
<time>$ARTICLE_TIME</time> <br/>
diff --git a/pages/contato.sh b/pages/contato.sh
index 74d1ad4..b54a64e 100644..100755
--- a/pages/contato.sh
+++ b/pages/contato.sh
@@ -1,4 +1,5 @@
#!/bin/bash
+
cat <<PAGE
<style>
@media screen and (max-width: 600px) {
@@ -9,5 +10,27 @@ cat <<PAGE
</style>
<article class="contato-page container">
+ <section>
+ <h1>Formas de contato</h1>
+
+ <p>
+ Primeiramente, muito obrigado pelo interesse em me contatar! Você pode ver abaixo minhas
+ informações de contato, pode me chamar a qualquer hora, mas só respondo em horário
+ comercial. Se for algo urgente, escreva "URGENTE" sem aspas no título da mensagem
+ que tentarei fazer de tudo para atendê-lo o quanto antes.
+ </p>
+ </section>
+
+ <section>
+ <address>
+ Email: <a href="mailto:[email protected]">[email protected]</a> <br />
+ Linkedin:
+ <a href="https://www.linkedin.com/in/jefferson-j%C3%BAlio-56a30b149">
+ https://www.linkedin.com/in/jefferson-j%C3%BAlio-56a30b149
+ </a>
+ </address>
+ </section>
</article>
PAGE
+
+# Celular e Whatsapp: +55 (81) 97326-9793 <br />
diff --git a/pages/home.sh b/pages/home.sh
index ebb399d..c9a9966 100755
--- a/pages/home.sh
+++ b/pages/home.sh
@@ -1,25 +1,26 @@
#!/bin/bash
-print-all-articles () {
- for i in $(find ./pages/articles -type f -name '*.txt'); do
- ARTICLE_CONTENT=$(tail -n +3 $i)
- cat <<POST
-<section class="post-preview">
- <div class="post-header-preview">
- <h5 class="post-title">stat -c "%w %z" $i</h5>
- <time>$(stat --format="Criado em: %w<br/>Última atualização: %z" $i)</time>
- </div>
-
- <p>$(echo "$ARTICLE_CONTENT" | head -c 120)...</p>
-
- <a href="/artigo/$(basename $i)">Ler artigo completo</a>
+welcome () {
+ cat <<WELCOME
+<section class="conainer" id="welcome">
+ <header>
+ <h1>Bem-vindo ao meu blog :3</h1>
+ <h4>Aqui mostro o meu trabalho e escrevo sobre programação.</h4>
+ </header>
</section>
-POST
+WELCOME
+}
+
+print-all-articles () {
+ # Find all articles and sort by date of creation
+ for i in $(find $ARTICLES_PATH -type f -name '*.txt' -printf "%T@ %p\n" | sort -rn | cut -b 23-); do
+ ARTICLE_FILE="${i/* /}" source $SCRIPT_DIR/components/post-preview.sh
done
}
cat <<PAGE
<article class="home-feed container">
+ $(welcome)
$(print-all-articles)
</article>
PAGE
diff --git a/static/header.js b/static/header.js
new file mode 100644
index 0000000..06d868a
--- /dev/null
+++ b/static/header.js
@@ -0,0 +1,137 @@
+let isMobile = window.innerWidth < 769;
+let keyPressAudio = new Audio('/static/key-press.mp3');
+let headerAnimationTerminated = false;
+let cursorHTML = '<span class="cursor">&ensp;</span>';
+
+window.addEventListener('resize', () => {
+ isMobile = window.innerWidth < 769;
+});
+
+function hideTopNav() {
+ let topNav = document.querySelector('.top-nav');
+ topNav.style.opacity = '0';
+ topNav.style.display = 'none';
+}
+
+function hideContent() {
+ hideTopNav();
+ let mainContainer = document.querySelector('main');
+ let footer = document.querySelector('footer');
+ mainContainer.style.opacity = '0';
+ mainContainer.style.display = 'none';
+ footer.style.opacity = '0';
+ footer.style.display = 'none';
+}
+
+function showContent(cb) {
+ let mainContainer = document.querySelector('main');
+ let footer = document.querySelector('footer');
+ let topNav = document.querySelector('.top-nav');
+
+ topNav.style.transition = '100ms';
+ topNav.style.opacity = '1';
+ topNav.style.display = 'block';
+
+ setTimeout(() => {
+ mainContainer.style.transition = '500ms';
+ mainContainer.style.opacity = '1';
+ mainContainer.style.display = 'block';
+ setTimeout(() => {
+ footer.style.transition = '500ms';
+ footer.style.opacity = '1';
+ footer.style.display = 'block';
+ if (cb) cb();
+ }, 500);
+ }, 100);
+}
+
+function clearHideContentTransition() {
+ let mainContainer = document.querySelector('main');
+ let footer = document.querySelector('footer');
+ let topNav = document.querySelector('.top-nav');
+ topNav.style.transition = '0s';
+ mainContainer.style.transition = '0s';
+ footer.style.transition = '0s';
+}
+
+function typingAnimation(el, text, index, cb) {
+ let delay = 30;
+
+ let char = text[index];
+ if (char == ';') {
+ el.innerHTML += '<br />$ ';
+ !isMobile && keyPressAudio.play();
+ delay += 400;
+ } else if (char == ' ') {
+ el.innerHTML += '&ensp;';
+ } else {
+ el.innerText += char;
+ }
+
+ if (index < text.length - 1) {
+ setTimeout(() => typingAnimation(el, text, index + 1, cb), delay);
+ } else {
+ !isMobile && keyPressAudio.play();
+ el.innerHTML += '<br />';
+ setTimeout(() => {
+ if (cb) cb();
+ setTimeout(clearHideContentTransition, 1000);
+ }, 600);
+ }
+}
+
+function clearHeaderTerminal(cb) {
+ let titleMobile = document.querySelector("#header-title-mobile > .text");
+ let titleDesktop = document.querySelector('#header-title > .text');
+ let footerCommandPrompt = document.querySelector('#footer-command-prompt');
+
+ if (isMobile) {
+ titleMobile.innerHTML = '';
+ } else {
+ titleDesktop.innerHTML = '';
+ }
+
+ footerCommandPrompt.style.height = 'calc(100vh)';
+ hideTopNav();
+ footerCommandPrompt.querySelector('.after-prompt').scrollIntoView({
+ behavior: 'smooth',
+ });
+
+ setTimeout(() => {
+ if (cb) cb();
+ footerCommandPrompt.style.height = 'auto';
+ }, 1000);
+}
+
+function headerTerminalExecute(desktopText, mobileText, cb) {
+ window.scrollTo(0, 0);
+ headerAnimationTerminated = false;
+ let titleMobile = document.querySelector("#header-title-mobile > .text");
+ let titleDesktop = document.querySelector('#header-title > .text');
+
+ if (!mobileText) mobileText = desktopText;
+
+ setTimeout(() => {
+ if (isMobile) {
+ titleMobile.insertAdjacentHTML('afterend', cursorHTML);
+ typingAnimation(titleMobile, mobileText, 0, () => {
+ if (cb) cb();
+ headerAnimationTerminated = true;
+ document.querySelector('#header-title-mobile .cursor').remove();
+ });
+ } else {
+ titleDesktop.insertAdjacentHTML('afterend', cursorHTML);
+ typingAnimation(titleDesktop, desktopText, 0, () => {
+ if (cb) cb();
+ headerAnimationTerminated = true;
+ document.querySelector('#header-title .cursor').remove();
+ });
+ }
+ }, 200);
+}
+
+document.addEventListener('DOMContentLoaded', function() {
+ clearHeaderTerminal();
+ hideContent();
+ headerTerminalExecute(desktopText, mobileText, () => showContent(bindAllRelativeAnchors));
+});
diff --git a/styles.css b/static/styles.css
index b17d72f..e84dc56 100644
--- a/styles.css
+++ b/static/styles.css
@@ -8,6 +8,7 @@ body {
}
main {
display: block;
+ margin-top: 25px;
}
h1 {
font-size: 2em;
@@ -152,13 +153,41 @@ template {
html {
--primary-bg: #2b2b2b;
- --primary-fg: #65d84a;
+ /* --primary-fg: #65d84a; */
+ --primary-fg: #0eff5d;
background-color: var(--primary-bg);
color: var(--primary-fg);
font-family: 'VT323', monospace;
- font-size: 14px;
+ font-size: 16px;
+ position: relative;
+ overflow: auto;
+ /* scroll-behavior: smooth; */
+ min-height: 100vh;
+}
+
+@media screen and (max-width: 600px) {
+ html {
+ font-size: 14px;
+ }
}
+html:before {
+ content: ' ';
+ display: block;
+ position: absolute;
+ top: 0;
+ left: 0;
+ bottom: 0;
+ right: 0;
+ background: linear-gradient(
+ to bottom,
+ rgba(18, 16, 16, 0) 50%,
+ rgba(0, 0, 0, 0.25) 50%
+ );
+ background-size: 100% 3px;
+ z-index: 99;
+ pointer-events: none;
+}
.separator-wrap {
width: 100%;
@@ -221,11 +250,33 @@ header > h2 {
padding-right: 25px;
}
+.hidden {
+ display: none !important;
+}
+
+@keyframes blink-animation {
+ to {
+ opacity: 0;
+ }
+}
+
+.cursor {
+ background-color: var(--primary-fg);
+ animation-name: blink-animation;
+ animation-duration: 500ms;
+ animation-iteration-count: infinite;
+ animation-timing-function: cubic-bezier(0.83,-0.22, 0.54, 0.55);
+}
+
@media screen and (min-width: 1000px) {
- .container {
+ .container, [class^=err-] {
padding-left: 215px;
padding-right: 215px;
}
+
+ .pad-left, .pad-right {
+ width: 215px;
+ }
}
.post-header {
@@ -269,6 +320,11 @@ header > h2 {
margin-bottom: 15px;
}
+.post-header-preview .tags {
+ margin-top: 5px;
+ margin-bottom: 5px;
+}
+
.tag {
background-color: var(--primary-fg);
color: var(--primary-bg);
@@ -277,3 +333,27 @@ header > h2 {
padding-top: 1.5px;
padding-bottom: 1.5px;
}
+
+.post-header-preview .tag {
+ font-size: 0.75rem;
+}
+
+::-webkit-scrollbar {
+ width: 10px;
+ height: 10px;
+}
+::-webkit-scrollbar-thumb {
+ background: var(--primary-fg);
+ border-radius: 8px;
+}
+::-webkit-scrollbar-thumb:hover{
+ background: var(--primary-bg);
+}
+::-webkit-scrollbar-track{
+ background: var(--primary-bg);
+ border-radius: 7px;
+}
+
+::-webkit-scrollbar-track:hover {
+ background: var(--primary-fg);
+}
diff --git a/utils/parse-article.sh b/utils/parse-article.sh
new file mode 100755
index 0000000..c3e540a
--- /dev/null
+++ b/utils/parse-article.sh
@@ -0,0 +1,18 @@
+#!/bin/bash
+
+ARTICLE_TITLE=$(basename "$ARTICLE_FILE")
+
+# Pick all file content
+ARTICLE_FILE_CONTENT=$(cat "$ARTICLE_FILE")
+
+# The first 3 lines of the file are metadata information
+ARTICLE_METADATA=$(echo "$ARTICLE_FILE_CONTENT" | head -n 3)
+
+# Skip file metadata, first 3 lines
+ARTICLE_CONTENT=$(echo "$ARTICLE_FILE_CONTENT" | tail -n +3)
+
+# Tags are placed on the first line of the file, delimited by commas
+IFS=',' read -ra ARTICLE_TAGS <<< "$(echo "$ARTICLE_METADATA" | head -n 1)"
+
+ARTICLE_TIME=$(stat --format="Criado em: %w<br/>Última atualização: %z" "$ARTICLE_FILE")
+ARTICLE_BYTES=$(stat --format="%s bytes" "$ARTICLE_FILE")