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: +

+
+ + +
+
+
+
+
+ <%- 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'); %> diff --git a/src/templates/index.ejs b/src/templates/index.ejs index 6de8eec03..599e6be8f 100644 --- a/src/templates/index.ejs +++ b/src/templates/index.ejs @@ -34,5 +34,6 @@ + <%- include(paths.toPartials + '/gtag.ejs'); %> diff --git a/src/templates/partials/gtag.ejs b/src/templates/partials/gtag.ejs new file mode 100644 index 000000000..91d2950b6 --- /dev/null +++ b/src/templates/partials/gtag.ejs @@ -0,0 +1,11 @@ +<% if (process.env.G_ID) { %> + + + +<% } %> \ No newline at end of file diff --git a/src/templates/partials/index-list-item.ejs b/src/templates/partials/index-list-item.ejs index b11a6a480..68069f114 100644 --- a/src/templates/partials/index-list-item.ejs +++ b/src/templates/partials/index-list-item.ejs @@ -15,9 +15,9 @@