From 150183be0303bc54b4de42ee56c48e7071153911 Mon Sep 17 00:00:00 2001
From: nadev03 <142786072+nadev03@users.noreply.github.com>
Date: Tue, 20 Feb 2024 13:38:45 +0200
Subject: [PATCH] Multi songbook (#13)
* allow parse en songbook
* build index by `index.md`
* bugfix
* i18n
* remove obsolete
* i18n
* build multi language
* Delete multi.patch
* fix build multi songbook
* songbooks selector page
* fix build
* fix build
* songbook subtitles; fix sitemap
* bugfix songbook cache
* fix subtitle render
* custom contents titles; compact navigation data
* fix song template whitespaces
* try fix verses whitespaces
* Update pnpm-lock.yaml
* Update package.json
* bugfix index generation
* allow empty separators between verse texts
* Update pnpm-lock.yaml
* allow non bold verses
* Update pnpm-lock.yaml
* convert parentheses to non bold verse line
* Update pnpm-lock.yaml
* update subtitle markup
* update version
* render multiline title
* render page numbers on index
* update
* 404
* fix verses indents to css
* Update pnpm-lock.yaml
* bugfix
* Create gtag.ejs
* use google tag
* use `G_ID` variable
---
.github/workflows/build_and_deploy.yml | 2 +-
gulpfile.js | 331 ++++++++++++++-------
package.json | 11 +-
pnpm-lock.yaml | 125 +++++---
scripts/constants.js | 45 ++-
scripts/createHeadParts.js | 39 +--
scripts/i18n.js | 14 +
scripts/indexGenerator.js | 165 +++++++---
scripts/makeIndexList.js | 38 ++-
scripts/songConvertor.js | 123 ++++++--
scripts/songbookLoader.js | 43 ++-
scripts/utils.js | 17 ++
src/styles/partials/index-list-item.scss | 6 +
src/styles/partials/song-verse.scss | 22 ++
src/styles/song-page.scss | 20 +-
src/templates/404.ejs | 72 +++++
src/templates/index-list-page.ejs | 1 +
src/templates/index.ejs | 1 +
src/templates/partials/gtag.ejs | 11 +
src/templates/partials/index-list-item.ejs | 4 +-
src/templates/partials/navigation.ejs | 4 +-
src/templates/partials/prev-next-nav.ejs | 23 +-
src/templates/partials/return-home.ejs | 7 -
src/templates/partials/song-share.ejs | 4 +-
src/templates/partials/song-verse.ejs | 40 ++-
src/templates/sitemap.ejs | 7 +-
src/templates/song-page.ejs | 28 +-
src/templates/songbooks.ejs | 64 ++++
28 files changed, 934 insertions(+), 333 deletions(-)
create mode 100644 scripts/i18n.js
create mode 100644 scripts/utils.js
create mode 100644 src/templates/404.ejs
create mode 100644 src/templates/partials/gtag.ejs
delete mode 100644 src/templates/partials/return-home.ejs
create mode 100644 src/templates/songbooks.ejs
diff --git a/.github/workflows/build_and_deploy.yml b/.github/workflows/build_and_deploy.yml
index 53be4dc04..8e5841acf 100644
--- a/.github/workflows/build_and_deploy.yml
+++ b/.github/workflows/build_and_deploy.yml
@@ -16,7 +16,7 @@ jobs:
- name: Install and Build 🔧
run: |
npm i
- HOME_BASE_URL="${{ vars.HOME_BASE_URL }}" npm run build
+ HOME_BASE_URL="${{ vars.HOME_BASE_URL }}" G_ID="${{ vars.G_ID }}" npm run build
- name: Deploy 🚀
uses: JamesIves/github-pages-deploy-action@v4
with:
diff --git a/gulpfile.js b/gulpfile.js
index 81950fd28..1b185617f 100644
--- a/gulpfile.js
+++ b/gulpfile.js
@@ -1,3 +1,5 @@
+var fs = require('fs');
+
const ejs = require('gulp-ejs');
const gulp = require('gulp');
const path = require('path');
@@ -12,183 +14,303 @@ require('dotenv').config();
/**/
const { createHeadParts, createSongXMLParts } = require('./scripts/createHeadParts');
const {
+ getJSONContentsStream,
getJSONIndexStream,
makeSongHTML,
md2jsonConvertor
} = require('./scripts/songConvertor');
const { makeIndexList } = require('./scripts/makeIndexList');
-const { PATHS, ORIGIN } = require('./scripts/constants');
-const { readFile } = require('./scripts/ioHelpers');
-const { getSongsPath } = require('./scripts/songbookLoader');
+const { PATHS } = require('./scripts/constants');
+const { getSongsPath, getSongbookIdList, getSongbookInfo } = require('./scripts/songbookLoader');
+const { i18n } = require('./scripts/i18n');
+const { getTemplatePaths } = require('./scripts/utils');
const { BUILD, FILES, PAGES, SRC } = PATHS;
/**
*
*/
-gulp.task('sass', () => {
+gulp.task('sass', (done) => {
return gulp
.src(SRC.CSS_FILES + '/**/*.scss')
.pipe(sass().on('error', sass.logError))
- .pipe(gulp.dest(BUILD.CSS_FILES));
+ .pipe(gulp.dest(BUILD.CSS_FILES), done);
});
/**
*
*/
-gulp.task('copy-font', () => {
+gulp.task('copy-font', (done) => {
return gulp
.src(SRC.CSS_FILES + '/**/*.woff')
- .pipe(gulp.dest(BUILD.CSS_FILES));
+ .pipe(gulp.dest(BUILD.CSS_FILES), done);
});
/**
*
*/
-gulp.task('copy-img', () => {
- return gulp.src(SRC.IMG_FILES + '/**/*').pipe(gulp.dest(BUILD.IMG_FILES));
+gulp.task('copy-img', (done) => {
+ return gulp.src(SRC.IMG_FILES + '/**/*').pipe(gulp.dest(BUILD.IMG_FILES), done);
});
/**
*
*/
-gulp.task('html-folder', shell.task('mkdir -p ' + BUILD.HTML_FILES));
-
-/**
- *
- */
-gulp.task('html', async () => {
- const templatePromise = await readFile(
+gulp.task('html', (done) => {
+ const template = fs.readFileSync(
SRC.EJS_FILES + '/' + FILES.EJS.SONG_PAGE
- );
+ ).toString();
- return gulp
- .src([
- BUILD.JSON_FILES + '/**/*.json',
- '!' + BUILD.JSON_FILES + '/**/contentItems.json'
- ])
- .pipe(makeSongHTML(templatePromise))
- .pipe(
- rename({
- extname: '.html'
- })
- )
- .pipe(gulp.dest(BUILD.HTML_FILES));
+ const tasks = getSongbookIdList().map((songbook_id) => {
+ var task = (done) => gulp
+ .src([
+ BUILD.getJsonPath(songbook_id) + '/*.json',
+ '!**/' + FILES.JSON.CONTENTS,
+ '!**/' + FILES.JSON.INDEX
+ ])
+ .pipe(makeSongHTML(songbook_id, template))
+ .pipe(
+ rename({
+ extname: '.html'
+ })
+ )
+ .pipe(gulp.dest(BUILD.getSongbookRoot(songbook_id)), done);
+
+ task.displayName = "html " + songbook_id;
+ return task;
+ });
+
+ return gulp.series(...tasks, (seriesDone) => {
+ seriesDone();
+ done();
+ })();
});
/**
*
*/
gulp.task('md2json', (done) => {
- // TODO: had to use 'fs' to provide 'done' callback without async.
- var fs = require('fs');
- const templatePromise = fs.readFileSync(
- SRC.EJS_FILES + '/' + FILES.EJS.SONG_PAGE
- );
- return gulp
- .src(getSongsPath() + '/*.md')
- .pipe(md2jsonConvertor(templatePromise))
- .pipe(
- rename({
- extname: '.json'
- })
- )
- .pipe(gulp.dest(BUILD.JSON_FILES), done);
+ const tasks = getSongbookIdList().map(songbook_id => {
+ const templatePromise = fs.readFileSync(
+ SRC.EJS_FILES + '/' + FILES.EJS.SONG_PAGE
+ );
+ var task = (done) =>
+ gulp
+ .src(getSongsPath(songbook_id) + '/*.md')
+ .pipe(md2jsonConvertor(templatePromise))
+ .pipe(
+ rename({
+ extname: '.json'
+ })
+ )
+ .pipe(gulp.dest(BUILD.getJsonPath(songbook_id)), done);
+ task.displayName = "md2json " + songbook_id;
+ return task;
+ });
+
+ return gulp.series(...tasks, (seriesDone) => {
+ seriesDone();
+ done();
+ })();
});
/**
*
*/
-gulp.task('generate-index', (done) => {
- return getJSONIndexStream().pipe(gulp.dest(BUILD.JSON_FILES), done);
+gulp.task('generate-contents', (done) => {
+ const tasks = getSongbookIdList().map(songbook_id => {
+ var task = (done) => getJSONContentsStream(songbook_id).pipe(gulp.dest(BUILD.getJsonPath(songbook_id)), done);
+ task.displayName = "generate-contents " + songbook_id;
+ return task;
+ });
+
+ return gulp.series(...tasks, (seriesDone) => {
+ seriesDone();
+ done();
+ })();
});
/**
*
*/
-gulp.task('index', () => {
- const extChangeCmd = `mv ${BUILD.ROOT}/${FILES.EJS.INDEX} ${BUILD.ROOT}/${FILES.HTML.INDEX}`;
+gulp.task('generate-index', (done) => {
+ const tasks = getSongbookIdList().map(songbook_id => {
+ var task = (done) => getJSONIndexStream(songbook_id).pipe(gulp.dest(BUILD.getJsonPath(songbook_id)), done);
+ task.displayName = "generate-index " + songbook_id;
+ return task;
+ });
+
+ return gulp.series(...tasks, (seriesDone) => {
+ seriesDone();
+ done();
+ })();
+});
+
+function getSongooksRenderContext() {
+ var songbooks = getSongbookIdList().map(songbook_id => {
+ return {
+ title: getSongbookInfo(songbook_id).title,
+ subtitle: getSongbookInfo(songbook_id).subtitle,
+ contentsPath: PATHS.PAGES.getIndex(songbook_id)
+ };
+ });
const headParts = {
- title: 'Зміст',
- description: 'Сайт вайшнавських пісень',
- path: '/'
+ // TODO: ??
+ title: 'Vaishnava Songbook',
+ // TODO: ??
+ description: 'Vaishnava Songbook',
+ path: PATHS.PAGES.INDEX
};
const paths = {
- toCss: path.relative(BUILD.ROOT, BUILD.CSS_FILES),
- toImages: path.relative(BUILD.ROOT, BUILD.IMG_FILES),
- toPartials: path.join(process.cwd(), SRC.EJS_PARTIALS_FILES),
+ toCss: PATHS.RELATIVE.CSS,
+ toImages: PATHS.RELATIVE.IMG,
+ toPartials: path.join(process.cwd(), PATHS.SRC.EJS_PARTIALS_FILES),
toPages: {
- index: PAGES.INDEX,
- index_list: PAGES.INDEX_LIST
+ index: PATHS.PAGES.INDEX
}
};
+ return {
+ songbooks: songbooks,
+ headParts: createHeadParts(headParts),
+ paths: paths
+ };
+}
+
+/**
+ *
+ */
+gulp.task('songbooks', (done) => {
return gulp
- .src(SRC.EJS_FILES + '/' + FILES.EJS.INDEX)
- .pipe(
- ejs({
- categories: require(BUILD.INDEX_FILE),
- headParts: createHeadParts(headParts),
- paths: paths
- }).on('error', console.error)
- )
- .pipe(gulp.dest(BUILD.ROOT))
- .pipe(shell([extChangeCmd]));
+ .src([SRC.EJS_FILES + '/' + FILES.EJS.SONGBOOKS])
+ .pipe(
+ ejs(getSongooksRenderContext()).on('error', console.error)
+ )
+ .pipe(
+ rename({
+ basename: 'index',
+ extname: '.html'
+ })
+ )
+ .pipe(gulp.dest(BUILD.ROOT), done);
});
/**
*
*/
-gulp.task('index-list', () => {
- const extChangeCmd = `mv ${BUILD.ROOT}/${FILES.EJS.INDEX_LIST} ${BUILD.ROOT}/${FILES.HTML.INDEX_LIST}`;
+gulp.task('404', (done) => {
+ return gulp
+ .src([SRC.EJS_FILES + '/' + FILES.EJS.NOT_FOUND])
+ .pipe(
+ ejs(getSongooksRenderContext()).on('error', console.error)
+ )
+ .pipe(
+ rename({
+ basename: '404',
+ extname: '.html'
+ })
+ )
+ .pipe(gulp.dest(BUILD.ROOT), done);
+});
- const headParts = {
- title: 'Індекс А-Я',
- description: 'Сайт вайшнавських пісень',
- path: '/' + FILES.HTML.INDEX_LIST
- };
+/**
+ *
+ */
+gulp.task('songbook-contents', (done) => {
+ const tasks = getSongbookIdList().map(songbook_id => {
- const paths = {
- toCss: path.relative(BUILD.ROOT, BUILD.CSS_FILES),
- toImages: path.relative(BUILD.ROOT, BUILD.IMG_FILES),
- toPartials: path.join(process.cwd(), SRC.EJS_PARTIALS_FILES),
- toPages: {
- index: PAGES.INDEX,
- index_list: PAGES.INDEX_LIST
- }
- };
+ var current_i18n = i18n(songbook_id);
- return gulp
- .src(SRC.EJS_FILES + '/' + FILES.EJS.INDEX_LIST)
- .pipe(
- ejs({
- items: makeIndexList(require(BUILD.INDEX_FILE)),
- headParts: createHeadParts(headParts),
- paths: paths
- }).on('error', console.error)
- )
- .pipe(gulp.dest(BUILD.ROOT))
- .pipe(shell([extChangeCmd]));
+ const headParts = {
+ title: current_i18n('Contents'),
+ description: current_i18n('Vaishnava Songbook'),
+ path: PATHS.PAGES.getIndex(songbook_id)
+ };
+
+ const extChangeCmd = `mv ${BUILD.ROOT}/${FILES.EJS.CONTENTS} ${BUILD.ROOT}/${songbook_id}/${FILES.HTML.INDEX}`;
+
+ var task = (done) => gulp
+ .src(SRC.EJS_FILES + '/' + FILES.EJS.CONTENTS)
+ .pipe(
+ ejs({
+ categories: require(BUILD.getContentsFile(songbook_id)),
+ headParts: createHeadParts(headParts),
+ paths: getTemplatePaths(songbook_id),
+ i18n: current_i18n
+ }).on('error', console.error)
+ )
+ .pipe(gulp.dest(BUILD.ROOT))
+ // TODO: use rename?
+ .pipe(shell([extChangeCmd]), done);
+ task.displayName = "songbook-contents " + songbook_id;
+ return task;
+ });
+
+ return gulp.series(...tasks, (seriesDone) => {
+ seriesDone();
+ done();
+ })();
});
/**
*
*/
-gulp.task('sitemap', () => {
+gulp.task('songbook-index', (done) => {
+ const tasks = getSongbookIdList().map(songbook_id => {
+
+ const headParts = {
+ title: i18n(songbook_id)('Index'),
+ description: i18n(songbook_id)('Vaishnava Songbook'),
+ path: PATHS.PAGES.getIndexList(songbook_id)
+ };
+
+ const extChangeCmd = `mv ${BUILD.ROOT}/${FILES.EJS.INDEX_LIST} ${BUILD.ROOT}/${songbook_id}/${FILES.HTML.INDEX_LIST}`;
+
+ var task = (done) => gulp
+ .src(SRC.EJS_FILES + '/' + FILES.EJS.INDEX_LIST)
+ .pipe(
+ ejs({
+ items: makeIndexList(require(BUILD.getContentsFile(songbook_id)), require(BUILD.getIndexFile(songbook_id))),
+ headParts: createHeadParts(headParts),
+ paths: getTemplatePaths(songbook_id),
+ i18n: i18n(songbook_id)
+ }).on('error', console.error)
+ )
+ .pipe(gulp.dest(BUILD.ROOT + ''))
+ // TODO: use rename?
+ .pipe(shell([extChangeCmd]), done);
+ task.displayName = "songbook-index " + songbook_id;
+ return task;
+ });
+
+ return gulp.series(...tasks, (seriesDone) => {
+ seriesDone();
+ done();
+ })();
+});
+
+/**
+ *
+ */
+gulp.task('sitemap', (done) => {
const extChangeCmd = `mv ${BUILD.ROOT}/${FILES.EJS.SITEMAP} ${BUILD.ROOT}/${FILES.XML.SITEMAP}`;
+ var songList = getSongbookIdList().map(songbook_id => {
+ return createSongXMLParts(songbook_id, require(BUILD.getContentsFile(songbook_id)))
+ }).join('\n');
+
return gulp
.src(SRC.EJS_FILES + '/' + FILES.EJS.SITEMAP)
.pipe(
ejs({
- indexListPagePath: encodeURI(ORIGIN + '/' + FILES.HTML.INDEX_LIST),
- indexPagePath: encodeURI(ORIGIN + '/' + FILES.HTML.INDEX),
- songList: createSongXMLParts(require(BUILD.INDEX_FILE))
+ root: encodeURI(PATHS.PAGES.INDEX),
+ songList: songList,
+ i18n: i18n
}).on('error', console.error)
)
.pipe(gulp.dest(BUILD.ROOT))
- .pipe(shell([extChangeCmd]));
+ .pipe(shell([extChangeCmd]), done);
});
/**
@@ -202,11 +324,18 @@ gulp.task('clean', shell.task('rm -rf docs'));
gulp.task('build', (done) => {
runSequence(
'clean',
+ 'copy-img',
+ 'copy-font',
+ 'sass',
'md2json',
+ 'generate-contents',
'generate-index',
- 'index-list',
- ['html-folder', 'copy-img', 'copy-font'],
- ['sass', 'html', 'index', 'sitemap'],
+ 'html',
+ 'songbook-contents',
+ 'songbook-index',
+ 'sitemap',
+ 'songbooks',
+ '404',
done
);
});
@@ -218,6 +347,6 @@ gulp.task('watch', () => {
gulp.watch(SRC.CSS_FILES + '/**/*.scss', gulp.series(['sass']));
gulp.watch(
[SRC.MD_FILES + '/**/*.md', SRC.EJS_FILES + '/**/*.ejs'],
- gulp.series(['html', 'index'])
+ gulp.series(['html', 'songbook-contents', 'songbook-index', 'songbooks'])
);
});
diff --git a/package.json b/package.json
index f2c823a2f..dc41daf22 100644
--- a/package.json
+++ b/package.json
@@ -1,21 +1,22 @@
{
- "name": "markdown-to-html",
- "version": "0.1.0",
+ "name": "kirtan-mate",
+ "version": "0.3.0",
"scripts": {
"build": "gulp build",
"watch": "gulp watch"
},
"dependencies": {
- "dotenv": "^16.4.1",
+ "dotenv": "^16.4.4",
"ejs": "^3.1.9",
- "gaudiya-gitanjali-ua": "github:scsm-ua/gaudiya-gitanjali-ua#369e9f1b5b758080f31edd690e33721402a1443e",
+ "gaudiya-gitanjali-ua": "github:scsm-ua/gaudiya-gitanjali-ua",
"gulp-ejs": "^5.1.0",
"gulp-rename": "^2.0.0",
"gulp-sass": "^5.1.0",
"gulp-shell": "^0.8.0",
"gulp4-run-sequence": "^1.0.2",
+ "kirtan-guide-en": "github:scsm-ua/kirtan-guide-en",
"lodash.deburr": "^4.1.0",
- "sass": "^1.70.0",
+ "sass": "^1.71.0",
"vinyl-source-stream": "^2.0.0"
},
"devDependencies": {
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 9a64daa0d..bb73e9890 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -6,14 +6,14 @@ settings:
dependencies:
dotenv:
- specifier: ^16.4.1
- version: 16.4.1
+ specifier: ^16.4.4
+ version: 16.4.4
ejs:
specifier: ^3.1.9
version: 3.1.9
gaudiya-gitanjali-ua:
specifier: github:scsm-ua/gaudiya-gitanjali-ua
- version: github.com/scsm-ua/gaudiya-gitanjali-ua/369e9f1b5b758080f31edd690e33721402a1443e
+ version: github.com/scsm-ua/gaudiya-gitanjali-ua/9c20604cd8c087dd8ae56694d9af8259ca4e2fd7
gulp-ejs:
specifier: ^5.1.0
version: 5.1.0
@@ -29,12 +29,15 @@ dependencies:
gulp4-run-sequence:
specifier: ^1.0.2
version: 1.0.2
+ kirtan-guide-en:
+ specifier: github:scsm-ua/kirtan-guide-en
+ version: github.com/scsm-ua/kirtan-guide-en/e0b5e720bc1329b488b9571b5de6e119b30e57c7
lodash.deburr:
specifier: ^4.1.0
version: 4.1.0
sass:
- specifier: ^1.70.0
- version: 1.70.0
+ specifier: ^1.71.0
+ version: 1.71.0
vinyl-source-stream:
specifier: ^2.0.0
version: 2.0.0
@@ -321,12 +324,15 @@ packages:
unset-value: 1.0.0
dev: true
- /call-bind@1.0.5:
- resolution: {integrity: sha512-C3nQxfFZxFRVoJoGKKI8y3MOEo129NQ+FgQ08iye+Mk4zNZZGdjfs06bVTr+DBSlA66Q2VEcMki/cUCP4SercQ==}
+ /call-bind@1.0.7:
+ resolution: {integrity: sha512-GHTSNSYICQ7scH7sZ+M2rFopRoLh8t2bLSW6BbgrtLsahOIB5iyAVJf9GjWK3cYTDaMj4XdBpM1cA6pIS0Kv2w==}
+ engines: {node: '>= 0.4'}
dependencies:
+ es-define-property: 1.0.0
+ es-errors: 1.3.0
function-bind: 1.1.2
- get-intrinsic: 1.2.2
- set-function-length: 1.2.0
+ get-intrinsic: 1.2.4
+ set-function-length: 1.2.1
dev: true
/camelcase@3.0.0:
@@ -371,8 +377,8 @@ packages:
- supports-color
dev: true
- /chokidar@3.5.3:
- resolution: {integrity: sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==}
+ /chokidar@3.6.0:
+ resolution: {integrity: sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==}
engines: {node: '>= 8.10.0'}
dependencies:
anymatch: 3.1.3
@@ -535,21 +541,21 @@ packages:
engines: {node: '>= 0.10'}
dev: true
- /define-data-property@1.1.1:
- resolution: {integrity: sha512-E7uGkTzkk1d0ByLeSc6ZsFS79Axg+m1P/VsgYsxHgiuc3tFSj+MjMIwe90FC4lOAZzNBdY7kkO2P2wKdsQ1vgQ==}
+ /define-data-property@1.1.4:
+ resolution: {integrity: sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==}
engines: {node: '>= 0.4'}
dependencies:
- get-intrinsic: 1.2.2
+ es-define-property: 1.0.0
+ es-errors: 1.3.0
gopd: 1.0.1
- has-property-descriptors: 1.0.1
dev: true
/define-properties@1.2.1:
resolution: {integrity: sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==}
engines: {node: '>= 0.4'}
dependencies:
- define-data-property: 1.1.1
- has-property-descriptors: 1.0.1
+ define-data-property: 1.1.4
+ has-property-descriptors: 1.0.2
object-keys: 1.1.1
dev: true
@@ -580,8 +586,8 @@ packages:
engines: {node: '>=0.10.0'}
dev: true
- /dotenv@16.4.1:
- resolution: {integrity: sha512-CjA3y+Dr3FyFDOAMnxZEGtnW9KBR2M0JvvUtXNW+dYJL5ROWxP9DUHCwgFqpMk0OXCc0ljhaNTr2w/kutYIcHQ==}
+ /dotenv@16.4.4:
+ resolution: {integrity: sha512-XvPXc8XAQThSjAbY6cQ/9PcBXmFoWuw1sQ3b8HqUCR6ziGXjkTi//kB9SWa2UwqlgdAIuRqAa/9hVljzPehbYg==}
engines: {node: '>=12'}
dev: false
@@ -621,6 +627,18 @@ packages:
is-arrayish: 0.2.1
dev: true
+ /es-define-property@1.0.0:
+ resolution: {integrity: sha512-jxayLKShrEqqzJ0eumQbVhTYQM27CfT1T35+gCgDFoL82JLsXqTJ76zv6A0YLOgEnLUMvLzsDsGIrl8NFpT2gQ==}
+ engines: {node: '>= 0.4'}
+ dependencies:
+ get-intrinsic: 1.2.4
+ dev: true
+
+ /es-errors@1.3.0:
+ resolution: {integrity: sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==}
+ engines: {node: '>= 0.4'}
+ dev: true
+
/es5-ext@0.10.62:
resolution: {integrity: sha512-BHLqn0klhEpnOKSrzn/Xsz2UIW8j+cGmo9JLzr8BiUapV8hPL9+FliFqjwr9ngW7jWdnxv6eO+/LqyhJVqgrjA==}
engines: {node: '>=0.10'}
@@ -873,13 +891,15 @@ packages:
resolution: {integrity: sha512-3t6rVToeoZfYSGd8YoLFR2DJkiQrIiUrGcjvFX2mDw3bn6k2OtwHN0TNCLbBO+w8qTvimhDkv+LSscbJY1vE6w==}
dev: true
- /get-intrinsic@1.2.2:
- resolution: {integrity: sha512-0gSo4ml/0j98Y3lngkFEot/zhiCeWsbYIlZ+uZOVgzLyLaUw7wxUL+nCTP0XJvJg1AXulJRI3UJi8GsbDuxdGA==}
+ /get-intrinsic@1.2.4:
+ resolution: {integrity: sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ==}
+ engines: {node: '>= 0.4'}
dependencies:
+ es-errors: 1.3.0
function-bind: 1.1.2
has-proto: 1.0.1
has-symbols: 1.0.3
- hasown: 2.0.0
+ hasown: 2.0.1
dev: true
/get-value@2.0.6:
@@ -973,7 +993,7 @@ packages:
/gopd@1.0.1:
resolution: {integrity: sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==}
dependencies:
- get-intrinsic: 1.2.2
+ get-intrinsic: 1.2.4
dev: true
/graceful-fs@4.2.11:
@@ -1074,10 +1094,10 @@ packages:
engines: {node: '>=8'}
dev: false
- /has-property-descriptors@1.0.1:
- resolution: {integrity: sha512-VsX8eaIewvas0xnvinAe9bw4WfIeODpGYikiWYLH+dma0Jw6KHYqWiWfhQlgOVK8D6PvjubK5Uc4P0iIhIcNVg==}
+ /has-property-descriptors@1.0.2:
+ resolution: {integrity: sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==}
dependencies:
- get-intrinsic: 1.2.2
+ es-define-property: 1.0.0
dev: true
/has-proto@1.0.1:
@@ -1121,8 +1141,8 @@ packages:
kind-of: 4.0.0
dev: true
- /hasown@2.0.0:
- resolution: {integrity: sha512-vUptKVTpIJhcczKBbgnS+RtcuYMB8+oNzPK2/Hp3hanz8JmpATdmmgLgSaadVREkDm+e2giHwY3ZRkyjSIDDFA==}
+ /hasown@2.0.1:
+ resolution: {integrity: sha512-1/th4MHjnwncwXsIW6QMzlvYL9kG5e/CpVvLRZe4XPa8TOUNbCELqmvhDmnkNsAjwaG4+I8gJJL0JBvTTLO9qA==}
engines: {node: '>= 0.4'}
dependencies:
function-bind: 1.1.2
@@ -1179,7 +1199,7 @@ packages:
resolution: {integrity: sha512-YBUanLI8Yoihw923YeFUS5fs0fF2f5TSFTNiYAAzhhDscDa3lEqYuz1pDOEP5KvX94I9ey3vsqjJcLVFVU+3QA==}
engines: {node: '>= 0.10'}
dependencies:
- hasown: 2.0.0
+ hasown: 2.0.1
dev: true
/is-arrayish@0.2.1:
@@ -1207,14 +1227,14 @@ packages:
/is-core-module@2.13.1:
resolution: {integrity: sha512-hHrIjvZsftOsvKSn2TRYl63zvxsgE0K+0mYMoH6gD4omR5IWB2KynivBQczo3+wF1cCkjzvptnI9Q0sPU66ilw==}
dependencies:
- hasown: 2.0.0
+ hasown: 2.0.1
dev: true
/is-data-descriptor@1.0.1:
resolution: {integrity: sha512-bc4NlCDiCr28U4aEsQ3Qs2491gVq4V8G7MQyws968ImqjKuYtTJXrl7Vq7jsN7Ly/C3xj5KWFrY7sHNeDkAzXw==}
engines: {node: '>= 0.4'}
dependencies:
- hasown: 2.0.0
+ hasown: 2.0.1
dev: true
/is-descriptor@0.1.7:
@@ -1638,7 +1658,7 @@ packages:
resolution: {integrity: sha512-byy+U7gp+FVwmyzKPYhW2h5l3crpmGsxl7X2s8y43IgxvG4g3QZ6CffDtsNQy1WsmZpQbO+ybo0AlW7TY6DcBQ==}
engines: {node: '>= 0.4'}
dependencies:
- call-bind: 1.0.5
+ call-bind: 1.0.7
define-properties: 1.2.1
has-symbols: 1.0.3
object-keys: 1.1.1
@@ -2010,12 +2030,12 @@ packages:
ret: 0.1.15
dev: true
- /sass@1.70.0:
- resolution: {integrity: sha512-uUxNQ3zAHeAx5nRFskBnrWzDUJrrvpCPD5FNAoRvTi0WwremlheES3tg+56PaVtCs5QDRX5CBLxxKMDJMEa1WQ==}
+ /sass@1.71.0:
+ resolution: {integrity: sha512-HKKIKf49Vkxlrav3F/w6qRuPcmImGVbIXJ2I3Kg0VMA+3Bav+8yE9G5XmP5lMj6nl4OlqbPftGAscNaNu28b8w==}
engines: {node: '>=14.0.0'}
hasBin: true
dependencies:
- chokidar: 3.5.3
+ chokidar: 3.6.0
immutable: 4.3.5
source-map-js: 1.0.2
dev: false
@@ -2036,15 +2056,16 @@ packages:
resolution: {integrity: sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==}
dev: true
- /set-function-length@1.2.0:
- resolution: {integrity: sha512-4DBHDoyHlM1IRPGYcoxexgh67y4ueR53FKV1yyxwFMY7aCqcN/38M1+SwZ/qJQ8iLv7+ck385ot4CcisOAPT9w==}
+ /set-function-length@1.2.1:
+ resolution: {integrity: sha512-j4t6ccc+VsKwYHso+kElc5neZpjtq9EnRICFZtWyBsLojhmeF/ZBd/elqm22WJh/BziDe/SBiOeAt0m2mfLD0g==}
engines: {node: '>= 0.4'}
dependencies:
- define-data-property: 1.1.1
+ define-data-property: 1.1.4
+ es-errors: 1.3.0
function-bind: 1.1.2
- get-intrinsic: 1.2.2
+ get-intrinsic: 1.2.4
gopd: 1.0.1
- has-property-descriptors: 1.0.1
+ has-property-descriptors: 1.0.2
dev: true
/set-value@2.0.1:
@@ -2123,22 +2144,22 @@ packages:
resolution: {integrity: sha512-kN9dJbvnySHULIluDHy32WHRUu3Og7B9sbY7tsFLctQkIqnMh3hErYgdMjTYuqmcXX+lK5T1lnUt3G7zNswmZA==}
dependencies:
spdx-expression-parse: 3.0.1
- spdx-license-ids: 3.0.16
+ spdx-license-ids: 3.0.17
dev: true
- /spdx-exceptions@2.4.0:
- resolution: {integrity: sha512-hcjppoJ68fhxA/cjbN4T8N6uCUejN8yFw69ttpqtBeCbF3u13n7mb31NB9jKwGTTWWnt9IbRA/mf1FprYS8wfw==}
+ /spdx-exceptions@2.5.0:
+ resolution: {integrity: sha512-PiU42r+xO4UbUS1buo3LPJkjlO7430Xn5SVAhdpzzsPHsjbYVflnnFdATgabnLude+Cqu25p6N+g2lw/PFsa4w==}
dev: true
/spdx-expression-parse@3.0.1:
resolution: {integrity: sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q==}
dependencies:
- spdx-exceptions: 2.4.0
- spdx-license-ids: 3.0.16
+ spdx-exceptions: 2.5.0
+ spdx-license-ids: 3.0.17
dev: true
- /spdx-license-ids@3.0.16:
- resolution: {integrity: sha512-eWN+LnM3GR6gPu35WxNgbGl8rmY1AEmoMDvL/QD6zYmPWgywxWqJWNdLGT+ke8dKNWrcYgYjPpG5gbTfghP8rw==}
+ /spdx-license-ids@3.0.17:
+ resolution: {integrity: sha512-sh8PWc/ftMqAAdFiBu6Fy6JUOYjqDJBJvIhpfDMyHrr0Rbp5liZqd4TjtQ/RgfLjKFZb+LMx5hpml5qOWy0qvg==}
dev: true
/split-string@3.1.0:
@@ -2519,8 +2540,14 @@ packages:
yargs-parser: 5.0.1
dev: true
- github.com/scsm-ua/gaudiya-gitanjali-ua/369e9f1b5b758080f31edd690e33721402a1443e:
- resolution: {tarball: https://codeload.github.com/scsm-ua/gaudiya-gitanjali-ua/tar.gz/369e9f1b5b758080f31edd690e33721402a1443e}
+ github.com/scsm-ua/gaudiya-gitanjali-ua/9c20604cd8c087dd8ae56694d9af8259ca4e2fd7:
+ resolution: {tarball: https://codeload.github.com/scsm-ua/gaudiya-gitanjali-ua/tar.gz/9c20604cd8c087dd8ae56694d9af8259ca4e2fd7}
name: gaudiya-gitanjali-ua
version: 1.0.0
dev: false
+
+ github.com/scsm-ua/kirtan-guide-en/e0b5e720bc1329b488b9571b5de6e119b30e57c7:
+ resolution: {tarball: https://codeload.github.com/scsm-ua/kirtan-guide-en/tar.gz/e0b5e720bc1329b488b9571b5de6e119b30e57c7}
+ name: kirtan-guide-en
+ version: 1.0.0
+ dev: false
diff --git a/scripts/constants.js b/scripts/constants.js
index 584ec8490..627640fcf 100644
--- a/scripts/constants.js
+++ b/scripts/constants.js
@@ -1,8 +1,10 @@
const path = require('path');
const OUTPUT_DIR = 'docs';
+const CSS_DIR = 'css';
+const IMG_DIR = 'css';
const contentItems = 'contentItems.json';
-const indexListJson = 'index-list.json';
+const indexItems = 'indexItems.json';
const indexPath = 'index.html';
const indexListPath = 'index-list-page.html';
const sharingBanner = 'sharing-banner.png';
@@ -14,20 +16,33 @@ const ORIGIN = (process.env.HOME_BASE_URL || '');
*
*/
const PATHS = {
+ RELATIVE: {
+ CSS: ORIGIN + '/' + CSS_DIR,
+ IMG: ORIGIN + '/' + IMG_DIR,
+ toSongs: (songbook_id) => ORIGIN + '/' + songbook_id
+ },
BUILD: {
- CSS_FILES: OUTPUT_DIR + '/css',
+ CSS_FILES: OUTPUT_DIR + '/' + CSS_DIR,
HTML_FILES: OUTPUT_DIR + '/html',
- IMG_FILES: OUTPUT_DIR + '/images',
- JSON_FILES: OUTPUT_DIR + '/json',
- // `resolve` to use with `require`.
- INDEX_FILE: path.resolve(OUTPUT_DIR + '/json/' + contentItems),
- INDEX_LIST_FILE: path.resolve(OUTPUT_DIR + '/json/' + indexListJson),
+ IMG_FILES: OUTPUT_DIR + '/' + IMG_DIR,
+ getJsonPath: (songbook_id) => OUTPUT_DIR + '/json/' + songbook_id,
+ getSongbookRoot: (songbook_id) => OUTPUT_DIR + '/' + songbook_id,
+ getContentsFile: function(songbook_id) {
+ return path.resolve(OUTPUT_DIR, 'json', songbook_id, contentItems);
+ },
+ getIndexFile: function(songbook_id) {
+ return path.resolve(OUTPUT_DIR, 'json', songbook_id, indexItems);
+ },
ROOT: OUTPUT_DIR
},
FILES: {
EJS: {
- INDEX: 'index.ejs',
+ // TODO: rename
+ CONTENTS: 'index.ejs',
+ // TODO: rename
INDEX_LIST: 'index-list-page.ejs',
+ SONGBOOKS: 'songbooks.ejs',
+ NOT_FOUND: '404.ejs',
SITEMAP: 'sitemap.ejs',
SONG_PAGE: 'song-page.ejs'
},
@@ -36,10 +51,10 @@ const PATHS = {
INDEX_LIST: indexListPath
},
JSON: {
- INDEX: contentItems,
- INDEX_LIST: indexListJson
+ CONTENTS: contentItems,
+ INDEX: indexItems
},
- SHARING_BANNER: (process.env.HOME_BASE_URL || '') + '/images/' + sharingBanner,
+ SHARING_BANNER: ORIGIN + '/images/' + sharingBanner,
XML: {
SITEMAP: 'sitemap.xml'
}
@@ -48,12 +63,14 @@ const PATHS = {
CSS_FILES: 'src/styles',
EJS_FILES: 'src/templates',
EJS_PARTIALS_FILES: 'src/templates/partials',
- HTML_FILES: 'src/html',
IMG_FILES: 'src/images'
},
PAGES: {
- INDEX: ORIGIN + (process.env.EXPLICIT_INDEX ? ('/' + indexPath) : ''),
- INDEX_LIST: ORIGIN + '/' + indexListPath
+ INDEX: ORIGIN + (process.env.EXPLICIT_INDEX ? ('/' + indexPath) : ''),
+ // TODO: rename
+ getIndex: (songbook_id) => ORIGIN + '/' + songbook_id + (process.env.EXPLICIT_INDEX ? ('/' + indexPath) : ''),
+ // TODO: rename
+ getIndexList: (songbook_id) => ORIGIN + '/' + songbook_id + '/' + indexListPath
}
};
diff --git a/scripts/createHeadParts.js b/scripts/createHeadParts.js
index 2d38a7a36..ca82bf1b5 100644
--- a/scripts/createHeadParts.js
+++ b/scripts/createHeadParts.js
@@ -9,18 +9,17 @@ const { PATHS, ORIGIN } = require('./constants');
function createHeadParts({ title, description, path }) {
const imgSrc = PATHS.FILES.SHARING_BANNER;
const _title = title + ' | Kirtan Mate';
- const canonical = ORIGIN + path;
return `
${_title}
-
+
-
+
@@ -55,8 +54,8 @@ function getSchema(path, title) {
},
{
'@type': 'CollectionPage',
- '@id': ORIGIN + '/' + path,
- 'url': ORIGIN + '/' + path,
+ '@id': path,
+ 'url': path,
'name': title,
'isPartOf': {
'@id': ORIGIN + '/#website'
@@ -69,27 +68,31 @@ function getSchema(path, title) {
return ``;
}
+function getItemXML(path, priority) {
+ return `
+
+ ${encodeURI(path)}
+ weekly
+ ${priority}
+
+ `;
+}
+
/**
* Converts the list of categories into the list of song related XML parts.
* @param categories: TCategory[]
* @returns {string}
*/
-function createSongXMLParts(categories) {
- let result = '';
+function createSongXMLParts(songbook_id, categories) {
+ let indexes = getItemXML(PATHS.PAGES.getIndex(songbook_id), 1)
+ + getItemXML(PATHS.PAGES.getIndexList(songbook_id), 1);
- categories
+ let songs = categories
.flatMap((cat) => cat.items)
- .forEach((item) => (
- result += `
-
- ${encodeURI(ORIGIN + '/html/' + item.fileName)}
- weekly
- 0.8
-
- `
- ));
+ .map((item) => getItemXML(PATHS.RELATIVE.toSongs(songbook_id) + '/' + item.fileName, 0.8))
+ .join();
- return result;
+ return indexes + songs;
}
diff --git a/scripts/i18n.js b/scripts/i18n.js
new file mode 100644
index 000000000..8ccfb3ae5
--- /dev/null
+++ b/scripts/i18n.js
@@ -0,0 +1,14 @@
+const { getSongbooki18n } = require("./songbookLoader");
+
+module.exports.i18n = function(songbook_id) {
+
+ var i18n_dict = getSongbooki18n(songbook_id);
+
+ return function(text) {
+ if (i18n_dict && text in i18n_dict) {
+ return i18n_dict[text];
+ } else {
+ return text;
+ }
+ }
+}
diff --git a/scripts/indexGenerator.js b/scripts/indexGenerator.js
index 3e135f084..844f43c16 100644
--- a/scripts/indexGenerator.js
+++ b/scripts/indexGenerator.js
@@ -2,7 +2,7 @@ const fs = require('fs');
const path = require('path');
const { PATHS } = require('../scripts/constants');
-const { getContentsFilePath, getSongsPath } = require('./songbookLoader');
+const { getContentsFilePath, getSongsPath, getIndexFilePath } = require('./songbookLoader');
// Songs.
@@ -29,13 +29,12 @@ function processSong(filename) {
*/
function convertSongToJSON(text) {
var lines = text.split(/\n/);
- // Filter empty lines.
- lines = lines.filter((i) => i.trim());
-
+
// Song template.
var song = {
- title: null,
- author: null,
+ title: [],
+ author: [],
+ subtitle: null,
verses: []
};
@@ -53,21 +52,35 @@ function convertSongToJSON(text) {
}
var last_line_id;
+ var verse_separator;
// TODO: shikshastakam first verse has no number
lines.forEach((line) => {
var { line_id, line_value } = getSongLineInfo(line);
+ if (line_id && line_id !== 'verse_text') {
+ // Disable empty verse line.
+ verse_empty_line = false;
+ }
switch (line_id) {
case 'title':
- song.title = line_value;
+ song.title.push(line_value);
break;
case 'author':
- song.author = line_value;
+ song.author.push(line_value);
+ break;
+ case 'subtitle':
+ song.subtitle = line_value;
break;
case 'verse_number':
getLastVerse({ create_new: true }).number = line_value;
break;
case 'verse_text':
+ if (verse_empty_line) {
+ verse_empty_line = false;
+ getLastVerse({
+ create_new: false
+ }).text.push('');
+ }
getLastVerse({
create_new: last_line_id === 'translation'
}).text.push(line_value);
@@ -75,11 +88,30 @@ function convertSongToJSON(text) {
case 'translation':
getLastVerse().translation.push(line_value);
break;
+ case 'attribute':
+ var bits = line_value.split(/=/);
+ if (bits.length !== 2) {
+ console.error("Can't recognize attribute", line);
+ } else {
+ song.attributes = song.attributes || {};
+ song.attributes[bits[0].trim()] = bits[1].trim();
+ }
+ break;
default:
- // TODO: better errors processing.
- console.error("Can't recognize line id", line_id, line_value);
+ if (!line.trim()) {
+ // Empty line.
+ if (last_line_id === 'verse_text') {
+ verse_empty_line = true;
+ }
+ } else {
+ // TODO: better errors processing.
+ console.error("Can't recognize line id", line_id, line);
+ }
+ }
+ if (line_id) {
+ // Skip empty lines.
+ last_line_id = line_id;
}
- last_line_id = line_id;
});
return song;
@@ -88,9 +120,11 @@ function convertSongToJSON(text) {
/**/
const song_line_types = {
title: /^# (.+)/,
- author: /^## (.+)/,
+ subtitle: /^## (.+)/,
+ author: /^### (.+)/,
verse_number: /^#### (.+)/,
verse_text: /^ (.+)/,
+ attribute: /^> (.+)/,
translation: /^([^\s#].+)/
};
@@ -117,6 +151,7 @@ function postProcessSong(song) {
}
/**/
+const TAG_RE = /<[^>]+>/gm;
const NOTE_MD_REGEX = /\*\*\*(.*?)\*\*\*/gm;
const TERM_MD_REGEX = /\*{1,2}(.*?)\*{1,2}/gm;
@@ -128,6 +163,8 @@ const TERM_MD_REGEX = /\*{1,2}(.*?)\*{1,2}/gm;
function processTranslation(lines) {
return lines
.join('\n')
+ // Cleanup tags for safaty.
+ .replace(TAG_RE, '')
.replace(NOTE_MD_REGEX, '$1\n')
.replace(TERM_MD_REGEX, '$1')
.replaceAll('\\\n', '
')
@@ -154,10 +191,10 @@ function getSongLineInfo(line) {
}
// Index.
-function getIndexJSON() {
- var data = fs.readFileSync(getContentsFilePath());
+function getContentsJSON(songbook_id) {
+ var data = fs.readFileSync(getContentsFilePath(songbook_id));
var text = data.toString();
- var categories = convertIndexToJSON(text);
+ var categories = convertContentsToJSON(songbook_id, text);
// Filter empty categories.
categories = categories.filter((category) => category.items.length > 0);
@@ -166,7 +203,46 @@ function getIndexJSON() {
return categories;
}
+function getIndexJSON(songbook_id) {
+ var indexFilePath = getIndexFilePath(songbook_id);
+ if (fs.existsSync(indexFilePath)) {
+ var data = fs.readFileSync(indexFilePath);
+ var text = data.toString();
+ var index = convertIndexToJSON(text);
+
+ // console.log(JSON.stringify(index, null, 4));
+ return index;
+ } else {
+ // Fallback to empty.
+ return convertIndexToJSON('');
+ }
+}
+
function convertIndexToJSON(text) {
+ var lines = text.split(/\n/).filter(i => !!i);
+ var songs = {};
+
+ lines.forEach((line) => {
+
+ var match = line.match(/^\s?- \[([^\]]+)\]\(songs\/([^\)]+)\.md\)/);
+
+ if (match) {
+ var song_alias = match[1];
+ var song_id = match[2];
+ if (!(song_id in songs)) {
+ songs[song_id] = song_alias;
+ } else {
+ console.warn('- Duplicate index line', line);
+ }
+ } else {
+ console.warn('- No match in index for line', line);
+ }
+ });
+
+ return songs;
+}
+
+function convertContentsToJSON(songbook_id, text) {
var lines = text.split(/\n/);
var categories = [];
var last_line_id;
@@ -184,7 +260,7 @@ function convertIndexToJSON(text) {
}
lines.forEach((line) => {
- var { line_id, name } = getIndexLineInfo(line);
+ var { line_id, name, filename } = getIndexLineInfo(line);
switch (line_id) {
case 'name':
var cateogory = getLastCategory({ create_new: true });
@@ -192,12 +268,15 @@ function convertIndexToJSON(text) {
break;
case 'song':
getLastCategory().items.push({
- name: getSongName(name),
+ id: filename,
+ title: name,
+ name: getSongName(songbook_id, filename),
// TODO: trim
// TODO: replace tabs
// TODO: trim -
- aliasName: getSongFirstLine(name),
- fileName: name + '.html'
+ aliasName: getSongFirstLine(songbook_id, filename),
+ fileName: filename + '.html',
+ page: getSongPage(songbook_id, filename),
});
break;
default:
@@ -212,7 +291,7 @@ function convertIndexToJSON(text) {
const index_line_types = {
name: /^### (.+)/,
// Extract only filename without extension.
- song: /^\s?- \[[^\]]+\]\(songs\/([^\)]+)\.md\)/
+ song: /^\s?- \[([^\]]+)\]\(songs\/([^\)]+)\.md\)/
};
function getIndexLineInfo(line) {
@@ -221,49 +300,66 @@ function getIndexLineInfo(line) {
if (m) {
return {
line_id: id,
- name: m[1]
+ name: m[1],
+ filename: m[2]
};
}
}
return {
line_id: null,
- name: null
+ name: null,
+ filename: null
};
}
-var songs_cache = {};
+var songbooks_cache = {};
+
+function getSongJSON(songbook_id, filename) {
+
+ var songbook_cache = songbooks_cache[songbook_id] = songbooks_cache[songbook_id] || {};
-function getSongJSON(filename) {
- if (songs_cache[filename]) {
- return songs_cache[filename];
+ if (songbook_cache[filename]) {
+ return songbook_cache[filename];
}
- var filepath = path.resolve(PATHS.BUILD.JSON_FILES, filename + '.json');
+ var filepath = path.resolve(PATHS.BUILD.getJsonPath(songbook_id), filename + '.json');
if (!fs.existsSync(filepath)) {
// TODO: better errors processing.
console.error('Song JSON not found', filepath);
return;
}
- songs_cache[filename] = require(filepath);
- return songs_cache[filename];
+ songbook_cache[filename] = require(filepath);
+ return songbook_cache[filename];
}
-function getSongName(filename) {
- var song_json = getSongJSON(filename);
+function getSongName(songbook_id, filename) {
+ var song_json = getSongJSON(songbook_id, filename);
if (!song_json) {
return;
}
- return song_json.title;
+ return song_json.title[0];
}
-function getSongFirstLine(filename) {
- var song_json = getSongJSON(filename);
+function getSongPage(songbook_id, filename) {
+ var song_json = getSongJSON(songbook_id, filename);
+ if (!song_json) {
+ return;
+ }
+ return song_json.attributes?.page;
+}
+
+function getSongFirstLine(songbook_id, filename) {
+ var song_json = getSongJSON(songbook_id, filename);
if (!song_json) {
return;
}
var first_verse = song_json.verses.find((verse) => verse.number);
+ if (!first_verse) {
+ // Get first verse if no numbers.
+ first_verse = song_json.verses[0];
+ }
var first_line = first_verse?.text && first_verse.text[0];
if (!first_line) {
// TODO: better errors processing.
@@ -282,5 +378,6 @@ function getSongFirstLine(filename) {
/**/
module.exports = {
convertMDToJSON: convertSong,
+ getContentsJSON: getContentsJSON,
getIndexJSON: getIndexJSON
};
\ No newline at end of file
diff --git a/scripts/makeIndexList.js b/scripts/makeIndexList.js
index d5127fd36..6658f3233 100644
--- a/scripts/makeIndexList.js
+++ b/scripts/makeIndexList.js
@@ -7,17 +7,22 @@ const deburr = require('lodash.deburr');
* @param categories: TCategory[]
* @returns {TCategory[]}
*/
-function makeIndexList(categories) {
+function makeIndexList(categories, index) {
const list = new Map();
makeLineVersions(
- categories.flatMap((cat) => cat.items)
+ categories.flatMap((cat) => cat.items),
+ index
).sort((a, b) =>
getAliasCleaned(a).localeCompare(getAliasCleaned(b))
)
.forEach((item) => {
const firstLetter = getFirstLetter(item);
+ if (!firstLetter) {
+ console.warn('No first letter in', item);
+ }
+
if (!list.has(firstLetter)) {
return list.set(firstLetter, [item]);
}
@@ -30,11 +35,12 @@ function makeIndexList(categories) {
.sort(((a, b) => a[0].localeCompare(b[0])))
.map(([letter, items]) => ({
name: letter.toUpperCase(),
- items: items.map(({ aliasName, fileName, name }) => ({
+ items: items.map(({ aliasName, fileName, name, page }) => ({
// Swapping `aliasName` and `name`.
aliasName: name,
fileName: fileName,
- name: aliasName
+ title: aliasName,
+ page: page
}))
}));
}
@@ -45,21 +51,29 @@ function makeIndexList(categories) {
* @param items: TCategoryItem[]
* @returns {TCategoryItem[]}
*/
-function makeLineVersions(items) {
+function makeLineVersions(items, index) {
const arr = [];
- items.forEach((cat) => {
- const alias = processLineEnding(cat.aliasName);
- const idx = alias.indexOf(')');
+ items.forEach((item) => {
+
+ var alias;
+
+ if (index[item.id]) {
+ alias = index[item.id];
+ } else {
+ alias = processLineEnding(item.aliasName);
+ }
arr.push({
- ...cat,
+ ...item,
aliasName: alias
})
- if (~idx) {
+ // Do not put empty alias when all word in braces.
+ const idx = alias.indexOf(')');
+ if (idx > -1 && idx < alias.length - 1) {
arr.push({
- ...cat,
+ ...item,
aliasName: alias.slice(idx + 1).trim()
})
}
@@ -90,7 +104,7 @@ function processLineEnding(line) {
* @returns {string}
*/
function getFirstLetter(item) {
- return item.aliasName.startsWith('(')
+ return (item.aliasName.startsWith('(') || item.aliasName.startsWith('‘'))
? item.aliasName[1]
: item.aliasName[0];
}
diff --git a/scripts/songConvertor.js b/scripts/songConvertor.js
index 8b49e853d..7157475a9 100644
--- a/scripts/songConvertor.js
+++ b/scripts/songConvertor.js
@@ -4,10 +4,12 @@ const { Transform } = require('stream');
const VinylStream = require('vinyl-source-stream');
/**/
-const { convertMDToJSON, getIndexJSON } = require('./indexGenerator');
+const { convertMDToJSON, getContentsJSON, getIndexJSON } = require('./indexGenerator');
const { createHeadParts } = require('./createHeadParts');
-const { ORIGIN, PATHS } = require('./constants');
-const { BUILD, FILES, PAGES, SRC } = PATHS;
+const { PATHS, ORIGIN } = require('./constants');
+const { i18n } = require('./i18n');
+const { getTemplatePaths } = require('./utils');
+const { BUILD, FILES } = PATHS;
/*** JSON to HTML song conversion. ***/
@@ -15,14 +17,15 @@ const { BUILD, FILES, PAGES, SRC } = PATHS;
/**
*
*/
-function makeSongHTML(templatePromise) {
+function makeSongHTML(songbook_id, template) {
return new Transform({
objectMode: true,
transform(file, encoding, callback) {
try {
const htmlString = fillTemplate(
- templatePromise,
+ songbook_id,
+ template,
JSON.parse(file.contents.toString()),
file.path
);
@@ -38,54 +41,98 @@ function makeSongHTML(templatePromise) {
});
}
+function getSongsOrderedList(songbook_id) {
+ var contents = require(BUILD.getContentsFile(songbook_id));
+ var items = contents.flatMap((cat) => cat.items);
+ return items.map(i => i.fileName);
+}
+
/**
* @param template: string;
* @param content: TSongJSON;
* @param filePath: string;
* @return {string}
*/
-function fillTemplate(template, content, filePath) {
- const { author, title, verses } = content;
+function fillTemplate(songbook_id, template, content, filePath) {
+ // TODO: subtitle.
+ const { author, subtitle, title, verses, attributes } = content;
+
+ if (!verses) {
+ console.warn('No verse in ' + filePath);
+ return '';
+ }
+
const { text } = verses[0];
+ var pageTitle = title;
+ if (author && author.length) {
+ pageTitle += '. ' + author[0];
+ }
+ if (subtitle) {
+ pageTitle += '. ' + subtitle;
+ }
+
const headParts = {
- title: author ? title + '. ' + author : title,
+ title: pageTitle,
description: `${text[0]}\n${text[1]}...`,
- path: '/html/' + path.parse(filePath).name + '.html'
- };
-
- const paths = {
- toCss: path.relative(BUILD.HTML_FILES, BUILD.CSS_FILES),
- toImages: path.relative(BUILD.HTML_FILES, BUILD.IMG_FILES),
- toPartials: path.join(process.cwd(), SRC.EJS_PARTIALS_FILES),
- toPages: {
- index: PAGES.INDEX,
- index_list: PAGES.INDEX_LIST
- }
+ path: ORIGIN + '/' + songbook_id + '/' + path.parse(filePath).name + '.html'
};
return ejs.render(template, {
author: author,
- contentItems: JSON.stringify(require(BUILD.INDEX_FILE)),
- functions: {
- transformLine: transformLine
- },
+ subtitle: subtitle,
+ orderedSongs: JSON.stringify(getSongsOrderedList(songbook_id)),
headParts: createHeadParts(headParts),
- paths: paths,
+ paths: getTemplatePaths(songbook_id),
title: title,
- verses: verses
+ verses: verses,
+ attributes: attributes,
+ i18n: i18n(songbook_id),
+ transformLine: transformLine,
+ getLineIndentClass: getLineIndentClass
});
}
+const TAG_RE = /<[^>]+>/gi;
+const PARANTHESES_RE = /(\([^\)]+\))/gi;
+const PARANTHESES_START_RE = /(\([^\)]+)\s*$/gi; // ) End in next line.
+const PARANTHESES_END_RE = /^(\s*)([^\)]+\))/gi; // ( Start in prev line.
+
+
+function getLineIndentClass(text, prefix) {
+ var m = text.match(/^\s+/);
+ if (m) {
+ var count = Math.floor(m[0].length / 4);
+ if (count > 4) {
+ count = 4;
+ }
+ if (count) {
+ return prefix + '_indent_' + count;
+ }
+ }
+ return '';
+}
+
/**
* EJS trims lines even despite 'rmWhitespace: false'.
* But we want some verse lines have extra space in the beginning.
*/
-function transformLine(text) {
- return text
- .replaceAll(' ', ' ')
-}
+function transformLine(text, attributes) {
+
+ // Cleanup tags for safaty.
+ text = text.replace(TAG_RE, '')
+ if (attributes && attributes['verse parentheses'] === 'non bold') {
+ text = text.replace(PARANTHESES_RE, '$1')
+ text = text.replace(PARANTHESES_START_RE, '$1')
+ text = text.replace(PARANTHESES_END_RE, '$1$2')
+ }
+
+ // Try fix with indents.
+ // text = text.replaceAll(' ', ' ');
+
+ return text;
+}
/****************************/
/* Markdown to JSON section */
@@ -116,6 +163,19 @@ function md2json() {
});
}
+/****************************/
+/* Generate JSON contents */
+/****************************/
+
+/**
+ *
+ */
+function getJSONContentsStream(songbook_id) {
+ const stream = VinylStream(FILES.JSON.CONTENTS);
+ stream.end(JSON.stringify(getContentsJSON(songbook_id), null, 2));
+ return stream;
+}
+
/****************************/
/* Generate JSON index */
/****************************/
@@ -123,14 +183,15 @@ function md2json() {
/**
*
*/
-function getJSONIndexStream() {
+function getJSONIndexStream(songbook_id) {
const stream = VinylStream(FILES.JSON.INDEX);
- stream.end(JSON.stringify(getIndexJSON(), null, 2));
+ stream.end(JSON.stringify(getIndexJSON(songbook_id), null, 2));
return stream;
}
/**/
module.exports = {
+ getJSONContentsStream: getJSONContentsStream,
getJSONIndexStream: getJSONIndexStream,
makeSongHTML,
md2jsonConvertor: md2json,
diff --git a/scripts/songbookLoader.js b/scripts/songbookLoader.js
index f912299a8..c35203220 100644
--- a/scripts/songbookLoader.js
+++ b/scripts/songbookLoader.js
@@ -1,32 +1,55 @@
const path = require('path');
const fs = require('fs');
-var songbookPath;
+var songbooks = {};
-function findSongbook() {
+function findSongbooks() {
var modulesRootPath = path.resolve(__dirname, '../node_modules');
var modules_listing = fs.readdirSync(modulesRootPath).map(module_name => path.resolve(modulesRootPath, module_name));
for (const modulePath of modules_listing) {
var songbookInfoPath = path.resolve(modulePath, 'songbook.json');
if (fs.existsSync(songbookInfoPath)) {
- songbookPath = modulePath;
- return;
+ var songbookInfo = require(songbookInfoPath);
+ songbookInfo.path = modulePath;
+ songbooks[songbookInfo.slug] = songbookInfo;
+
+ console.log('--- Loaded songbook:', modulePath)
}
}
}
-function getContentsFilePath() {
- return songbookPath + '/contents.md';
+function getContentsFilePath(songbook_id) {
+ return songbooks[songbook_id].path + '/contents.md';
+}
+
+function getIndexFilePath(songbook_id) {
+ return songbooks[songbook_id].path + '/index.md';
+}
+
+function getSongsPath(songbook_id) {
+ return songbooks[songbook_id].path + '/songs';
+}
+
+function getSongbooki18n(songbook_id) {
+ return songbooks[songbook_id].i18n;
+}
+
+function getSongbookIdList() {
+ return Object.keys(songbooks);
}
-function getSongsPath() {
- return songbookPath + '/songs';
+function getSongbookInfo(songbook_id) {
+ return songbooks[songbook_id];
}
-findSongbook();
+findSongbooks();
module.exports = {
getContentsFilePath: getContentsFilePath,
- getSongsPath: getSongsPath
+ getIndexFilePath: getIndexFilePath,
+ getSongsPath: getSongsPath,
+ getSongbooki18n: getSongbooki18n,
+ getSongbookIdList: getSongbookIdList,
+ getSongbookInfo: getSongbookInfo
};
\ No newline at end of file
diff --git a/scripts/utils.js b/scripts/utils.js
new file mode 100644
index 000000000..6d7c879af
--- /dev/null
+++ b/scripts/utils.js
@@ -0,0 +1,17 @@
+const path = require('path');
+const { PATHS } = require('./constants');
+
+exports.getTemplatePaths = function(songbook_id) {
+ return {
+ toCss: PATHS.RELATIVE.CSS,
+ toImages: PATHS.RELATIVE.IMG,
+ toPartials: path.join(process.cwd(), PATHS.SRC.EJS_PARTIALS_FILES),
+ toSongs: PATHS.RELATIVE.toSongs(songbook_id),
+ toPages: {
+ // TODO: rename
+ index: PATHS.PAGES.getIndex(songbook_id),
+ // TODO: rename
+ index_list: PATHS.PAGES.getIndexList(songbook_id)
+ }
+ };
+};
\ No newline at end of file
diff --git a/src/styles/partials/index-list-item.scss b/src/styles/partials/index-list-item.scss
index 8bd8c81aa..dda30d661 100644
--- a/src/styles/partials/index-list-item.scss
+++ b/src/styles/partials/index-list-item.scss
@@ -52,6 +52,12 @@
margin: 0;
color: var(--title-color);
}
+ &__subtitle {
+ flex: 1;
+ line-height: 28px;
+ margin: 0;
+ color: var(--title-color);
+ }
&__suffix {
position: relative;
diff --git a/src/styles/partials/song-verse.scss b/src/styles/partials/song-verse.scss
index 45dc6d8c2..0e8fd742a 100644
--- a/src/styles/partials/song-verse.scss
+++ b/src/styles/partials/song-verse.scss
@@ -37,6 +37,28 @@
font-weight: 700;
line-height: 24px;
color: var(--title-color);
+ text-indent: -1em;
+ margin-left: 1em;
+
+ &_indent_1 {
+ margin-left: 2em;
+ }
+
+ &_indent_2 {
+ margin-left: 3em;
+ }
+
+ &_indent_3 {
+ margin-left: 4em;
+ }
+
+ &_indent_4 {
+ margin-left: 5em;
+ }
+ }
+
+ &__light {
+ font-weight: initial;
}
&__translation {
diff --git a/src/styles/song-page.scss b/src/styles/song-page.scss
index 4a69d94d0..71bc7be45 100644
--- a/src/styles/song-page.scss
+++ b/src/styles/song-page.scss
@@ -28,17 +28,33 @@
font-size: 20px;
line-height: 140%;
color: var(--title-color);
- margin: 0 0 16px 0;
+ margin: 0 0 0 0;
@include breakpoint(sm-up) {
font-size: 28px;
}
}
+ &__title_last {
+ margin: 0 0 16px 0;
+ }
+
&__author {
color: var(--text-color);
- font-size: 14px;
+ font-size: 16px;
font-weight: 300;
+ margin-top: 4px;
+ }
+
+ &__author_first {
+ margin-top: 16px;
+ }
+
+ &__subtitle {
+ color: var(--text-color);
+ font-size: 18px;
+ font-weight: 600;
+ margin-top: 16px;
}
&__main {
diff --git a/src/templates/404.ejs b/src/templates/404.ejs
new file mode 100644
index 000000000..55879693e
--- /dev/null
+++ b/src/templates/404.ejs
@@ -0,0 +1,72 @@
+
+
+
+ <%- headParts %>
+
+
+
+
+
+
+
+
+
+
+ <%- include(paths.toPartials + '/header.ejs'); %>
+
+
+
+
+
+
+
+
+
+
Page not found
+
+ This site does not contain the requested URL. Try to start navagation selecting songbook from the list below:
+
+
+
+
+
+ <% songbooks.forEach((songbook) => { %>
+
+ -
+
+
+
+
+
+ <% }); %>
+
+
+
+
+
+
+ <%- include(paths.toPartials + '/gtag.ejs'); %>
+
+
diff --git a/src/templates/index-list-page.ejs b/src/templates/index-list-page.ejs
index bb83cf05c..ae4f76a76 100644
--- a/src/templates/index-list-page.ejs
+++ b/src/templates/index-list-page.ejs
@@ -31,5 +31,6 @@
+<%- include(paths.toPartials + '/gtag.ejs'); %>