Refactoring

This commit is contained in:
Artur Gurgul 2025-06-08 21:57:33 +02:00
parent 1e3833f7d0
commit aa563e60ea
37 changed files with 472 additions and 2392 deletions

View file

@ -1,23 +0,0 @@
doctype html
html(lang="en")
head
meta(charset="utf-8")
meta(name="viewport" content="width=device-width, initial-scale=1")
title Artur Gurgul - #{title}
meta(name="author" content="Artur Gurgul")
meta(name="description" content="This is my notepad")
link(rel="shortcut icon" href="/favicon.png")
link(rel="alternate" type="application/atom+xml" title="#{site.data.theme.name}" href="#{site.url}/atom.xml")
link(rel="stylesheet" href="/static/css/all.css")
link(rel="stylesheet" href="/static/css/hightlight.css")
body
.container
.sidebar
include sidebar.pug
.scroll
.content
h1.title= title
#post!= content
.footer
include footer.pug

View file

@ -1,2 +0,0 @@
.disclaimer
p © Artur Gurgul, 2024 — Public Domain Licence

View file

@ -1,10 +0,0 @@
nav
h2(style="font-size: 20px; margin: 0px;") Hi. I'm
a(href="/") Artur Gurgul
h2(style="font-size: 15px; margin-top: -0.5em;") and this is my notepad.
hr.hr-text(data-content="Contents")
ul#blog-posts.posts
each page in pages.filter(it => it.hidden != true && it.title != undefined )
li
span »
a(href=page.url, style=(false ? "font-weight: bold;" : ""))= page.title

View file

@ -1,7 +0,0 @@
site:
title: Artur Gurgul - {{article.title}}
remote:
type: ssh
user: debian
host: artur.gurgul.pro
path: /var/www/artur.gurgul.pro

View file

@ -1,672 +0,0 @@
@font-face {
font-family: OpenSans;
font-weight: italic;
src: url('/media/opensans/opensans-italic-variable_font_wdth,wght.ttf');
}
@font-face {
font-family: OpenSans;
font-weight: normal;
src: url('/media/opensans/opensans-regular-variable_font-wdth,wght.ttf');
}
* {
font-family: -apple-system, BlinkMacSystemFont, sans-serif;
margin: 0;
padding: 0;
border: 0;
font-size: 100%;
font: inherit;
vertical-align: baseline;
font-weight: 500;
}
.title {
position: sticky; top:0;
background-color: white;
}
html {
scroll-behavior: smooth;
}
.container {
position: relative;
/* margin: 0 auto; */
padding: 0;
display: flex;
}
.sidebar {
width: 390px;
float: left;
}
body,
.content,
.container {
box-sizing: border-box;
}
body,
.container {
height: 100%;
}
.content {
width: 800px;
}
.sidebar {
box-sizing: border-box;
border-right: 1px solid #DDD;
height: 100%;
}
.scroll {
height: 100%;
width: 100%;
overflow-y: scroll
}
article,
aside,
details,
figcaption,
figure,
footer,
header,
hgroup,
menu,
nav,
section {
display: block;
}
body {
line-height: 1;
}
ol,
ul {
list-style: none;
}
blockquote,
q {
quotes: none;
}
blockquote:before,
blockquote:after,
q:before,
q:after {
content: '';
content: none;
}
table {
border-collapse: collapse;
border-spacing: 0;
}
body {
background: #fff;
font: 14px/21px -apple-system, BlinkMacSystemFont, sans-serif;
color: #444;
-webkit-font-smoothing: antialiased;
-webkit-text-size-adjust: 100%;
}
h1,
h2,
h3,
h4,
h5,
h6 {
color: #181818;
font-family: -apple-system, BlinkMacSystemFont, sans-serif;
font-weight: normal;
}
h1 a,
h2 a,
h3 a,
h4 a,
h5 a,
h6 a {
font-weight: inherit;
}
h1 {
font-size: 46px;
line-height: 50px;
margin-bottom: 14px;
}
h2 {
font-size: 35px;
line-height: 40px;
margin-bottom: 10px;
}
h3 {
font-size: 28px;
line-height: 34px;
margin-bottom: 8px;
}
h4 {
font-size: 21px;
line-height: 30px;
margin-bottom: 4px;
}
h5 {
font-size: 17px;
line-height: 24px;
}
h6 {
font-size: 14px;
line-height: 21px;
}
.subheader {
color: #777;
}
p {
margin: 0 0 20px 0;
}
p img {
margin: 0;
}
p.lead {
font-size: 21px;
line-height: 27px;
color: #777;
}
em {
font-style: italic;
}
strong {
font-weight: bold;
color: #333;
}
small {
font-size: 80%;
}
blockquote,
blockquote p {
font-size: 17px;
line-height: 24px;
color: #777;
font-style: italic;
}
blockquote {
margin: 0px;
padding: 0px 20px 0 15px;
border-left: 2px solid #ddd;
}
blockquote cite {
display: block;
font-size: 12px;
color: #555;
}
blockquote cite:before {
content: "\2014 \0020";
}
blockquote cite a,
blockquote cite a:visited,
blockquote cite a:visited {
color: #0060ad;
}
hr {
border: solid #ddd;
border-width: 1px 0 0;
clear: both;
margin: 10px 0 30px;
height: 0;
}
a,
a:visited {
color: #333;
text-decoration: underline;
outline: 0;
}
a:hover,
a:focus {
color: #000;
}
p a,
p a:visited {
line-height: inherit;
}
ul,
ol {
margin-bottom: 20px;
}
ul {
list-style: none outside;
}
ol {
list-style: decimal;
}
ol,
ul.square,
ul.circle,
ul.disc {
margin-left: 30px;
}
ul.square {
list-style: square outside;
}
ul.circle {
list-style: circle outside;
}
ul.disc {
list-style: disc outside;
}
ul ul,
ul ol,
ol ol,
ol ul {
margin: 4px 0 5px 30px;
font-size: 90%;
}
ul ul li,
ul ol li,
ol ol li,
ol ul li {
margin-bottom: 6px;
}
li {
line-height: 18px;
margin-bottom: 12px;
}
ul.large li {
line-height: 21px;
}
li p {
line-height: 21px;
}
img.scale-with-grid {
max-width: 100%;
height: auto;
}
ul.tabs {
display: block;
margin: 0 0 20px 0;
padding: 0;
border-bottom: solid 1px #ddd;
}
ul.tabs li {
display: block;
width: auto;
height: 30px;
padding: 0;
float: left;
margin-bottom: 0;
}
ul.tabs li a {
display: block;
text-decoration: none;
width: auto;
height: 29px;
padding: 0px 20px;
line-height: 30px;
border: solid 1px #ddd;
border-width: 1px 1px 0 0;
margin: 0;
background: #f5f5f5;
font-size: 13px;
}
ul.tabs li a.active {
background: #fff;
height: 30px;
position: relative;
top: -4px;
padding-top: 4px;
border-left-width: 1px;
margin: 0 0 0 -1px;
color: #111;
}
ul.tabs-content {
margin: 0;
display: block;
}
ul.tabs-content>li {
display: none;
}
ul.tabs-content>li.active {
display: block;
}
ul.tabs:before,
ul.tabs:after {
content: '\0020';
display: block;
overflow: hidden;
visibility: hidden;
width: 0;
height: 0;
}
ul.tabs:after {
clear: both;
}
ul.tabs {
zoom: 1;
}
label span,
legend span {
font-weight: normal;
font-size: 13px;
color: #444;
}
thead {
border-bottom: solid #0060ad;
font-weight: bold;
color: #0060ad;
}
thead th {
padding-left: 20px;
padding-right: 20px;
padding-top: 5px;
padding-bottom: 0px;
}
tbody td {
padding: 5px;
}
table {
margin-left: auto;
margin-right: auto;
}
html,
body {
height: 100%;
}
body {
font-size: 16px;
background-color: white;
color: #222222;
line-height: 24px;
margin: 0;
border-top: 7px solid #0060ad;
}
h1,
h2,
h3,
h4,
h5,
h6 {
color: #181818;
font-family: -apple-system, BlinkMacSystemFont, sans-serif;
font-weight: 600;
}
h1.title {
font-weight: 800;
}
h1 {
font-size: 32px;
line-height: 40px;
}
h2 {
font-size: 24px;
line-height: 30px;
}
h3 {
font-size: 21px;
line-height: 24px;
margin: 1em 0;
}
ul {
margin: 1em 0;
list-style: disc;
}
a {
color: #0060ad;
text-decoration: none;
}
a:hover {
color: #0060ad;
text-decoration: underline;
}
a:visited {
color: #0060ad;
}
table {
font-size: inherit;
font: 100%;
}
img {
display: block;
margin-left: auto;
margin-right: auto;
}
.posts {
padding: 20px;
}
ul.posts {
margin-top: 0;
list-style-type: none;
margin-bottom: 10px;
}
ul.posts li {
line-height: 22px;
font-size: 16px;
margin-bottom: 0px;
}
ul.posts span {
font-family: 'Lucida Console', 'Andale Mono', monospace;
color: #aaa;
padding-right: 5px;
font-size: 14px;
}
.site .footer {
font-size: 80%;
color: #666;
border-top: 4px solid #eee;
overflow: hidden;
}
nav h1,
nav h2 {
text-align: center;
}
#post pre {
border: 0px solid #ddd;
background-color: #005fad06 !important;
padding: 0 .4em;
margin-bottom: 20px !important;
border-color: #005fad43 !important;
}
#post ul,
#post ol {
margin-left: 1.35em;
}
#post code {
border: 1px solid #ddd;
background-color: #eef;
font-size: 85%;
padding: 0 .2em;
}
#post pre code {
border: none;
}
.sidebar {
padding-top: 25px;
font-family: -apple-system, BlinkMacSystemFont, sans-serif;
}
.sidebar p {
font-weight: 200;
}
.sidebar a {
font-weight: 600;
}
text {
font: 500 12px/22px -apple-system, BlinkMacSystemFont, sans-serif;
}
/* path, rect {
stroke: red;
} */
g {
font: 500 12px/22px -apple-system, BlinkMacSystemFont, sans-serif;
}
/* stroke-width="2" */
.content {
font: 400 16px/22px -apple-system, BlinkMacSystemFont, sans-serif;
padding-left: 40px;
padding-top: 25px;
min-height: 400px;
}
#home h2 {
color: #0060ad;
}
#post pre {
background-color: white;
border-left: 12px solid #eee;
padding: 0 .8em;
}
#post code {
background-color: transparent;
}
#stalker {
float: inherit;
}
.disclaimer {
color: #aaa;
font-weight: 700;
font-size: smaller;
text-align: center;
padding-top: 40px;
padding-bottom: 10px;
}
.highlight {
background-color: white;
color: #586e75;
font-weight: bold;
}
.highlight .c {
color: #586e75 !important;
font-style: italic !important
}
p:has(img) {
background-color: #005fad06;
border-left: 12px solid #005fad43;
}
p > img {
display: block;
margin: 0 auto;
padding-top: 12px;
padding-bottom: 12px;
}
p:has(svg) {
background-color: #005fad06;
border-left: 12px solid #005fad43;
}
p > svg {
display: block;
margin: 0 auto;
padding-top: 12px;
padding-bottom: 12px;
}
pre {
white-space: pre;
overflow: auto;
}
code,
pre {
font-family: Monaco, Menlo, Consolas, "Courier New", monospace;
}

View file

@ -1,101 +0,0 @@
/*
XCode style (c) Angel Garcia <angelgarcia.mail@gmail.com>
*/
.hljs {
background: #fff;
color: black;
}
/* Gray DOCTYPE selectors like WebKit */
.xml .hljs-meta {
color: #c0c0c0;
}
.hljs-comment,
.hljs-quote {
color: #007400;
}
.hljs-tag,
.hljs-attribute,
.hljs-keyword,
.hljs-selector-tag,
.hljs-literal,
.hljs-name {
color: #aa0d91;
}
.hljs-variable,
.hljs-template-variable {
color: #3F6E74;
}
.hljs-code,
.hljs-string,
.hljs-meta .hljs-string {
color: #c41a16;
}
.hljs-regexp,
.hljs-link {
color: #0E0EFF;
}
.hljs-title,
.hljs-symbol,
.hljs-bullet,
.hljs-number {
color: #1c00cf;
}
.hljs-section,
.hljs-meta {
color: #643820;
}
.hljs-title.class_,
.hljs-class .hljs-title,
.hljs-type,
.hljs-built_in,
.hljs-params {
color: #5c2699;
}
.hljs-attr {
color: #836C28;
}
.hljs-subst {
color: #000;
}
.hljs-formula {
background-color: #eee;
font-style: italic;
}
.hljs-addition {
background-color: #baeeba;
}
.hljs-deletion {
background-color: #ffc8bd;
}
.hljs-selector-id,
.hljs-selector-class {
color: #9b703f;
}
.hljs-doctag,
.hljs-strong {
font-weight: bold;
}
.hljs-emphasis {
font-style: italic;
}

View file

@ -3,12 +3,12 @@ html(lang="en")
head
meta(charset="utf-8")
meta(name="viewport" content="width=device-width, initial-scale=1")
title Artur Gurgul - #{title}
title Artur Gurgul - #{page.title}
meta(name="author" content="Artur Gurgul")
meta(name="description" content="This is my notepad")
link(rel="shortcut icon" href="/favicon.png")
link(rel="alternate" type="application/atom+xml" title="#{site.data.theme.name}" href="#{site.url}/atom.xml")
link(rel="alternate" type="application/atom+xml" title=page.title href="#{site.url}/atom.xml")
link(rel="stylesheet" href="/static/css/all.css")
link(rel="stylesheet" href="/static/css/hightlight.css")
body
@ -17,7 +17,7 @@ html(lang="en")
include sidebar.pug
.scroll
.content
h1.title= title
#post!= content
h1.title= page.title
#post!= page.file.html
.footer
include footer.pug

View file

@ -4,7 +4,7 @@ nav
h2(style="font-size: 15px; margin-top: -0.5em;") and this is my notepad.
hr.hr-text(data-content="Contents")
ul#blog-posts.posts
each page in pages.filter(it => it.hidden != true && it.title != undefined )
each page in context.pages
li
span &raquo;
a(href=page.url, style=(false ? "font-weight: bold;" : ""))= page.title
a(href=page.url, style=(page.isCurrent ? "font-weight: bold;" : ""))= page.title

View file

@ -1,28 +1,5 @@
@font-face {
font-family: Montserrat;
/* declare weights giving two values to specify a range */
font-weight: 400 800;
src: url(/static/fonts/Montserrat-VariableFont_wght.ttf);
}
@font-face {
font-family: Montserrat;
/* declare weights giving two values to specify a range */
font-weight: 400 800;
font-style: italic;
src: url(/static/fonts/Montserrat-Italic-VariableFont_wght.ttf);
}
@font-face {
font-family: Proto;
/* declare weights giving two values to specify a range */
font-weight: 400 800;
src: url(/static/fonts/0xProto-Regular.ttf);
}
* {
font-family: Montserrat, sans-serif;
font-family: -apple-system, BlinkMacSystemFont, sans-serif;
margin: 0;
padding: 0;
border: 0;
@ -136,7 +113,7 @@ h4,
h5,
h6 {
color: #181818;
font-family: Montserrat, sans-serif;
font-family: -apple-system, BlinkMacSystemFont, sans-serif;
font-weight: normal;
}
@ -460,7 +437,7 @@ h4,
h5,
h6 {
color: #181818;
font-family: Montserrat, sans-serif;
font-family: -apple-system, BlinkMacSystemFont, sans-serif;
font-weight: 600;
}
@ -531,7 +508,7 @@ ul.posts li {
}
ul.posts span {
font-family: 'Proto', monospace;
font-family: monospace;
color: #aaa;
padding-right: 5px;
font-size: 14px;
@ -576,7 +553,7 @@ nav h2 {
.sidebar {
padding-top: 25px;
font-family: Montserrat, sans-serif;
font-family: -apple-system, BlinkMacSystemFont, sans-serif;
}
.sidebar p {
@ -588,7 +565,7 @@ nav h2 {
}
text {
font: 500 12px/22px Montserrat, sans-serif;
font: 500 12px/22px -apple-system, BlinkMacSystemFont, sans-serif;
}
/* path, rect {
@ -596,12 +573,12 @@ text {
} */
g {
font: 500 12px/22px Montserrat, sans-serif;
font: 500 12px/22px -apple-system, BlinkMacSystemFont, sans-serif;
}
/* stroke-width="2" */
.content {
font: 400 16px/22px Montserrat, sans-serif;
font: 400 16px/22px -apple-system, BlinkMacSystemFont, sans-serif;
padding-left: 40px;
padding-top: 25px;
@ -678,7 +655,7 @@ pre {
code,
pre {
font-family: 'Proto', monospace;
font-family: monospace;
}
.hljs {

View file

@ -1,9 +0,0 @@
---
layout: default
title: Content
hidden: true
---
# Content
there is my awasome content

1417
package-lock.json generated

File diff suppressed because it is too large Load diff

View file

@ -33,25 +33,22 @@
},
"homepage": "https://github.com/artur-gurgul-pro/sajt#readme",
"dependencies": {
"chalk": "^5.3.0",
"commander": "^12.1.0",
"express": "^4.19.2",
"fs-extra": "^11.2.0",
"chalk": "^5.4.1",
"commander": "^14.0.0",
"express": "^5.1.0",
"fs-extra": "^11.3.0",
"gray-matter": "^4.0.3",
"highlight.js": "^11.10.0",
"highlight.js": "^11.11.1",
"js-yaml": "^4.1.0",
"marked": "^14.0.0",
"marked-highlight": "^2.1.4",
"pug": "^3.0.3",
"ssh2": "^1.15.0",
"ssh2-sftp-client": "^11.0.0"
"marked": "^15.0.12",
"marked-highlight": "^2.2.1",
"pug": "^3.0.3"
},
"devDependencies": {
"@types/express": "^5.0.0",
"@types/express": "^5.0.2",
"@types/js-yaml": "^4.0.9",
"@types/node": "^22.13.5",
"@types/node": "^22.15.30",
"@types/pug": "^2.0.10",
"electron": "^33.0.1",
"typescript": "^5.7.3"
"typescript": "^5.8.3"
}
}

View file

@ -1,28 +1,32 @@
import fs from 'fs'
import path from 'path'
import yaml from 'js-yaml'
import { fileURLToPath } from 'url'
export default class ProjectConfig {
public config: any = {}
public config: any
private __filename: string
private __dirname: string
constructor() {
}
public load() {
this.config = {
this.config = {
... this.read(),
buildDir: './.build',
ignore: [".build", ".sajt"]
}
this.config.remote.port = 22
this.config.remote.privateKey = fs.readFileSync(path.resolve(process.env.HOME ?? "", '.ssh/id_rsa'))
this.__filename = fileURLToPath(import.meta.url)
this.__dirname = path.dirname(this.__filename)
this.config.defaultProjectPath = path.join(this.__dirname, '..', 'empty')
}
read(): any {
const __dirname = process.cwd()
const configPath = path.join(__dirname, '.sajt/config.yaml')
if (!fs.existsSync(configPath)) {
return {}
}
const fileContents = fs.readFileSync(configPath, 'utf8')
return yaml.load(fileContents)
}

View file

@ -1,12 +1,8 @@
import { Command } from 'commander'
import chalk from 'chalk'
//import fs from 'fs-extra'
import path from 'path'
import Project from './project.js'
import { serve } from './serve.js'
const program = new Command()
let project = new Project()
program

View file

@ -1,3 +1,5 @@
import Parser , {File} from "./renderer.js"
import hljs from 'highlight.js'
import { marked } from 'marked'
import { markedHighlight } from 'marked-highlight'
@ -13,15 +15,19 @@ marked.use(markedHighlight({
}
}))
export function parseMD(file: string) {
const fileContents = fs.readFileSync(path.join("./", file), 'utf8')
export default class Markdown implements Parser {
name = "md"
loadAsHTML(filePath: string): File {
const fileContents = fs.readFileSync(path.join("./", filePath), 'utf8')
const { data: metadata, content: markdownContent } = matter(fileContents)
const htmlContent = marked(markdownContent)
const htmlContent = marked(markdownContent, {async: false})
return {
meta: metadata,
content: htmlContent
data: metadata,
html: htmlContent
}
}
}
const renderer = new marked.Renderer()

50
src/page.ts Normal file
View file

@ -0,0 +1,50 @@
import Renderer , {File} from "./renderer.js"
import path from 'path'
import utils from './utils.js'
export default class Page {
file: File
/// /example/path
dir: string[]
/// /example/path/file
path: string
/// /example/path/file.html
finalPath: string
/// file.html
fileName: string
hidden: boolean
title: string
layout: string
constructor(filePath: string, renderer: Renderer) {
this.file = renderer.loadAsHTML(filePath)
if (this.file.data?.path != null && this.file.data?.path != undefined) {
this.path = path.join("/", this.file.data?.path)
} else {
const parsedPath = path.parse(filePath)
const basePath = path.join("/", parsedPath.dir, parsedPath.name)
this.path = basePath
}
const parsedPath = path.parse(this.path)
parsedPath.ext = '.html'
parsedPath.base = `${parsedPath.name}${parsedPath.ext}`
this.finalPath = path.format(parsedPath)
const dirArray = utils.pathToArray(this.finalPath)
this.fileName = dirArray.pop() || ""
dirArray.shift()
this.dir = dirArray
this.hidden = this.file.data?.hidden || false
// TODO: if tilte do not exists search in markdown for # title
this.title = this.file.data.title
this.layout = this.file.data.layout || "default"
}
}

View file

@ -1,41 +1,83 @@
import { fileURLToPath } from 'url'
import path from 'path'
import { cp } from './utils.js'
import { build } from './site.js'
import ProjectConfig from './project-config.js'
import ProjectConfig from './config.js'
import fs from 'fs'
import pug from 'pug'
import utils from "./utils.js"
import Markdown from './markdown.js'
import Yaml from './yaml.js'
import Page from './page.js'
export default class Project {
public config = new ProjectConfig() // should be injected
private __filename: string
private __dirname: string
private DEFAULT_PROJECT_PATH: string
private config = new ProjectConfig()
pages: Page[]
constructor() {
// Get the directory of the current file
this.__filename = fileURLToPath(import.meta.url)
this.__dirname = path.dirname(this.__filename)
// Path relative to the script file's directory
this.DEFAULT_PROJECT_PATH = path.join(this.__dirname, '..', 'empty')
this.pages = this.loadPages()
console.log(this.pages.map(it=> {return {dir: it.dir, file: it.finalPath}}))
}
new() {
console.log("Initialize a new project")
console.log(this.DEFAULT_PROJECT_PATH)
cp(this.DEFAULT_PROJECT_PATH, ".")
console.log(this.config)
this.config.load()
}
existing() {
this.config.load()
cp(this.config.config.defaultProjectPath, ".")
}
build() {
this.config.load()
build(this.config.config)
utils.removeDirectorySync(this.config.config.buildDir)
utils.cp("./.sajt/static", path.join(this.config.config.buildDir, "static"))
for(const page of this.pages) {
this.compile(page)
}
}
loadPages(): Page[] {
const mdParser = new Markdown()
const yamlParser = new Yaml()
let listMD = utils.getAllFilesWithExtension('.',".md", this.config.config.ignore)
.map(path => new Page(path, mdParser))
let listYML = utils.getAllFilesWithExtension('.',".yml", this.config.config.ignore)
.map(path => new Page(path, yamlParser))
return listMD.concat(listYML)
}
compile(page: Page) {
const output = path.join(this.config.config.buildDir, page.finalPath)
const compiledFunction = pug.compileFile(`.sajt/layouts/${page.layout}.pug`)
const data = {
page,
context: this.context(page)
}
const dirname = path.dirname(output)
if (!fs.existsSync(dirname)) {
fs.mkdirSync(dirname, { recursive: true })
}
const html = compiledFunction(data)
fs.writeFileSync(output, html)
console.log(`> ${output}`)
}
context(page: Page): any {
return {
pages: this.pages.map(it => { return {
title: it.title,
isCurrent: page == it,
url: it.finalPath,
isDir: false
}})
}
}
}

10
src/renderer.ts Normal file
View file

@ -0,0 +1,10 @@
export interface File {
get html(): string
get data(): { [key: string]: any }
}
export default interface Renderer {
get name(): string
loadAsHTML(path: string): File
}

View file

@ -1,15 +1,11 @@
import express from 'express'
import { setWatcher } from './watch.js'
//import { webdavMiddleware } from "./webdav.js"
const app = express()
const PORT = process.env.PORT || 3000
export function serve() {
app.use(express.static('./.build'))
//app.use(webdavMiddleware)
setWatcher(url => {
console.log(url)
})

View file

@ -1,132 +0,0 @@
import fs from 'fs'
import pug from 'pug'
import path from 'path'
import { cp } from "./utils.js"
import { parseMarkdown } from './markdown.js'
import { getAllFilesWithExtension, pathToArray, parseYML } from './utils.js'
import { parseMD } from './markdown.js'
function removeDirectorySync(directory: string) {
try {
fs.rmSync(directory, { recursive: true, force: true })
console.log("Directory and its contents removed.")
} catch (err: any) {
console.error(`Error removing directory: ${err.message}`)
}
}
function compile(template: string, content: any, output: string) {
if (template == null) {
console.error("Template is not defined, loading the default")
template = "default"
}
const compiledFunction = pug.compileFile(`.sajt/layouts/${template}.pug`);
const data = {
...content,
site: {posts: []}
}
const dirname = path.dirname(output)
if (!fs.existsSync(dirname)) {
fs.mkdirSync(dirname, { recursive: true })
}
const html = compiledFunction(data)
fs.writeFileSync(output, html)
console.log(`HTML has been rendered and saved to ${output}`);
}
function compileData(template: string, content: object, output: string) {
const compiledFunction = pug.compileFile(`.sajt/layouts/${template}.pug`)
const dirname = path.dirname(output)
if (!fs.existsSync(dirname)) {
fs.mkdirSync(dirname, { recursive: true })
}
const html = compiledFunction(content)
fs.writeFileSync(output, html)
console.log(`HTML has been rendered and saved to ${output}`);
}
function readMetadata(ignore: string[]) {
let htmlExtension = "html"
let listMD = getAllFilesWithExtension('.',".md", ignore)
.map(f => { return {
pathMD: f,
type: "md",
data: {} as any,
md: parseMD(f)
} as any })
let listYML = getAllFilesWithExtension('.',".yml", ignore)
.map(f => { return {
pathMD: f,
type: "yml",
data: parseYML(f),
md: {meta: {}}
} as any })
let list = listMD.concat(listYML)
for(const site of list) {
if (site.md.meta?.path != null && site.md.meta?.path != undefined) {
site.path = path.join("/", site.md.meta.path)
} else {
const parsedPath = path.parse(site.pathMD)
const basePath = path.join("/", parsedPath.dir, parsedPath.name)
site.path = basePath
}
// add proper extension
const parsedPath = path.parse(site.path)
parsedPath.ext = htmlExtension.startsWith('.') ? htmlExtension : `.${htmlExtension}`
parsedPath.base = `${parsedPath.name}${parsedPath.ext}`
site.path = path.format(parsedPath)
// add dirs metadata
const dirArray = pathToArray(site.path)
site.fileName = dirArray.pop()
dirArray.shift()
site.dir = dirArray
site.meta = site.md.meta
site.hidden = site.data?.hidden || false
}
return list
}
export function build(config: any) {
removeDirectorySync(config.buildDir)
cp("./.sajt/static", path.join(config.buildDir, "static"))
let data = readMetadata(config.ignore)
let pages = data.map(site => {
return {
title: site.meta.title,
url: site.path
}
})
for(const site of data) {
if (site.type == "md") {
compile(site.meta.layout,
{
content: site.md.content,
title: site.meta.title,
hidden: false,
pages
},
path.join(config.buildDir, site.path))
} else if (site.type == "yml") {
let data = {...site.data}
delete data.layout
parseMarkdown(data)
compileData(site.data.layout,
{data, pages, hidden: data.hidden},
path.join(config.buildDir, site.path))
}
}
}

View file

@ -1,36 +0,0 @@
/*const Client = require('ssh2-sftp-client')
async function uploadDirectory(serverConfig, localDirPath) {
const sftp = new Client()
await sftp.connect(serverConfig)
try {
await upload(sftp, config, localDirPath, serverConfig.path)
} catch (err) {
console.error(`Error: ${err.message}`)
} finally {
await sftp.end()
console.log('Connection closed')
}
}
async function upload(sftp, config, localPath, remotePath) {
console.log('Connected to the server')
const files = fs.readdirSync(localPath)
for (const file of files) {
const localFilePath = path.join(localPath, file)
const remoteFilePath = `${remotePath}/${file}`
if (fs.statSync(localFilePath).isDirectory()) {
await sftp.mkdir(remoteFilePath, true)
await upload(sftp, config, localFilePath, remoteFilePath)
} else {
const fileContent = fs.readFileSync(localFilePath)
await sftp.put(Buffer.from(fileContent), remoteFilePath)
console.log(`File transferred successfully: ${localFilePath}`)
}
}
}
*/

View file

@ -1,12 +1,7 @@
import yaml from 'js-yaml'
import fs from 'fs'
import path from 'path'
export function parseYML(file: string) {
const fileContents = fs.readFileSync(file, 'utf8')
return yaml.load(fileContents)
}
export function getAllFilesWithExtension(directory: string, extension: string, excludes: string[]): string[] {
let results: string[] = []
function readDirectory(directory: string) {
@ -29,7 +24,6 @@ export function getAllFilesWithExtension(directory: string, extension: string, e
return results
}
// copyDirectory
export function cp(source: string, destination: string) {
fs.mkdirSync(destination, { recursive: true })
const items = fs.readdirSync(source)
@ -45,9 +39,23 @@ export function cp(source: string, destination: string) {
})
}
export function pathToArray(filePath: string) {
// Normalize the file path to handle different OS path separators
function removeDirectorySync(directory: string) {
try {
fs.rmSync(directory, { recursive: true, force: true })
console.log("Directory and its contents removed.")
} catch (err: any) {
console.error(`Error removing directory: ${err.message}`)
}
}
export function pathToArray(filePath: string):string[] {
const normalizedPath = path.normalize(filePath)
// Split the path into an array of directories
return normalizedPath.split(path.sep)
}
export default {
pathToArray,
cp,
getAllFilesWithExtension,
removeDirectorySync
}

View file

@ -2,34 +2,31 @@ import fs from 'fs'
import path from 'path'
export function setWatcher(callback: (url: string) => void) {
// Directory to watch
const directoryPath = './';
// Ensure the directory exists
const directoryPath = './'
if (!fs.existsSync(directoryPath)) {
fs.mkdirSync(directoryPath, { recursive: true });
fs.mkdirSync(directoryPath, { recursive: true })
}
console.log(`Watching for changes in: ${directoryPath}`);
// Watch the directory for changes
console.log(`Watching for changes in: ${directoryPath}`)
fs.watch(directoryPath, (eventType, filename) => {
if (filename) {
const fullPath = path.join(directoryPath, filename);
console.log(`File ${filename} has been ${eventType}`);
// Check if the file was added, changed, or deleted
const fullPath = path.join(directoryPath, filename)
console.log(`File ${filename} has been ${eventType}`)
fs.stat(fullPath, (err, stats) => {
if (err) {
if (err.code === 'ENOENT') {
console.log(`File ${filename} was deleted.`);
console.log(`File ${filename} was deleted.`)
} else {
console.error(`Error checking file status: ${err.message}`);
console.error(`Error checking file status: ${err.message}`)
}
} else {
if (stats.isFile()) {
console.log(`File ${filename} exists with size: ${stats.size} bytes`);
console.log(`File ${filename} exists with size: ${stats.size} bytes`)
}
}
});
})
} else {
console.log('Filename not provided, but change detected.');
console.log('Filename not provided, but change detected.')
}
});
})
}

View file

@ -1,31 +0,0 @@
/*
import { v2 as webdav } from 'webdav-server';
// 1. Create a user manager and add a user
const userManager = new webdav.SimpleUserManager()
const user = userManager.addUser('user', 'password', false)
// 2. Create a privilege manager
const privilegeManager = new webdav.SimplePathPrivilegeManager()
// 3. Configure the WebDAV server
const server = new webdav.WebDAVServer({
// HTTP Digest authentication for better security
httpAuthentication: new webdav.HTTPDigestAuthentication(userManager, 'default-realm'),
privilegeManager
})
// 4. Set up a physical file system folder (e.g., `./data`) as the root
const publicFileSystem = new webdav.PhysicalFileSystem('./')
server.setFileSystem('/', publicFileSystem, (success) => {
if (!success) {
console.error('Failed to set file system')
process.exit(1)
}
// Give the user all permissions on the root
privilegeManager.setRights(user, '/', ['all'])
});
export const webdavMiddleware = webdav.extensions.express("/", server)
*/

20
src/yaml.ts Normal file
View file

@ -0,0 +1,20 @@
import yaml from 'js-yaml'
import Renderer , {File} from "./renderer.js"
import fs from 'fs'
export default class Yaml implements Renderer {
name = "yml"
loadAsHTML(file: string): File {
const fileContents = fs.readFileSync(file, 'utf8')
// let data = {...site.data}
// delete data.layout
// parseMarkdown(data)
return {
data: yaml.load(fileContents) as { [key: string]: any },
html: ""
}
}
}

4
types/config.d.ts vendored
View file

@ -1,7 +1,7 @@
export default class ProjectConfig {
config: any;
private __filename;
private __dirname;
constructor();
load(): void;
getConfig(): any;
read(): any;
}

2
types/context.d.ts vendored Normal file
View file

@ -0,0 +1,2 @@
export default interface Context {
}

11
types/markdown.d.ts vendored
View file

@ -1,7 +1,6 @@
export declare function parseMD(file: string): {
meta: {
[key: string]: any;
};
content: string | Promise<string>;
};
import Parser, { File } from "./renderer.js";
export default class Markdown implements Parser {
name: string;
loadAsHTML(filePath: string): File;
}
export declare function parseMarkdown(obj: any): void;

12
types/page.d.ts vendored Normal file
View file

@ -0,0 +1,12 @@
import Renderer, { File } from "./renderer.js";
export default class Page {
file: File;
dir: string[];
path: string;
finalPath: string;
fileName: string;
hidden: boolean;
title: string;
layout: string;
constructor(filePath: string, renderer: Renderer);
}

10
types/parser.d.ts vendored Normal file
View file

@ -0,0 +1,10 @@
export interface File {
get html(): string;
get data(): {
[key: string]: any;
};
}
export default interface Parser {
get name(): string;
parse(path: string): File;
}

12
types/project.d.ts vendored
View file

@ -1,11 +1,11 @@
import ProjectConfig from './project-config.js';
import Page from './page.js';
export default class Project {
config: ProjectConfig;
private __filename;
private __dirname;
private DEFAULT_PROJECT_PATH;
private config;
pages: Page[];
constructor();
new(): void;
existing(): void;
build(): void;
loadPages(): Page[];
compile(page: Page): void;
context(page: Page): any;
}

10
types/renderer.d.ts vendored Normal file
View file

@ -0,0 +1,10 @@
export interface File {
get html(): string;
get data(): {
[key: string]: any;
};
}
export default interface Renderer {
get name(): string;
loadAsHTML(path: string): File;
}

9
types/utils.d.ts vendored
View file

@ -1,4 +1,11 @@
export declare function parseYML(file: string): unknown;
export declare function getAllFilesWithExtension(directory: string, extension: string, excludes: string[]): string[];
export declare function cp(source: string, destination: string): void;
declare function removeDirectorySync(directory: string): void;
export declare function pathToArray(filePath: string): string[];
declare const _default: {
pathToArray: typeof pathToArray;
cp: typeof cp;
getAllFilesWithExtension: typeof getAllFilesWithExtension;
removeDirectorySync: typeof removeDirectorySync;
};
export default _default;

5
types/yaml.d.ts vendored Normal file
View file

@ -0,0 +1,5 @@
import Renderer, { File } from "./renderer.js";
export default class Yaml implements Renderer {
name: string;
loadAsHTML(file: string): File;
}