gitbook-plugin-tags-info
v1.0.12
Published
Gitbook tags-info for markhsu
Readme
如何製作文章標籤與分類
簡介
可以在文章最後呈現分類&標籤
專案架構
20250106_gitbook-plugin-tags-info
├─ .npmignore
├─ assets
│ └─ plugin.css
├─ CHANGELOG.md
├─ custom-release-notes-generator.js
├─ index.js
├─ LICENSE
├─ package-lock.json
├─ package.json
└─ README.md
目錄
一、事前準備
請先到github Fork這個專案到自己的本機目錄修改
https://github.com/billryan/gitbook-plugin-tags
二、操作步驟
STEP1:請先撰寫.gitlab-ci.yml
stages:
- semantic-release
- npm-deploy
variables:
NPM_REGISTRY_URL: "https://registry.npmjs.org/"
before_script:
- npm config set registry $NPM_REGISTRY_URL
semantic-release:
stage: semantic-release
image: node:20.18.0
rules:
- if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH
script:
- echo "//registry.npmjs.org/:_authToken=${NPM_TOKEN}" >> .npmrc
- npm install semver
- npm install -g --save-dev conventional-changelog-conventionalcommits semantic-release @semantic-release/changelog @semantic-release/gitlab @semantic-release/git @semantic-release/npm @semantic-release/release-notes-generator @semantic-release/gitlab-config
- export GL_TOKEN=${GL_TOKEN}
- GL_TOKEN=${GL_TOKEN} npx semantic-release
tags:
- docker
npm-deploy:
stage: npm-deploy
image: node:20.18.0
script:
- rm -rf node_modules/
- echo "//registry.npmjs.org/:_authToken=${NPM_TOKEN}" >> ~/.npmrc
- npm install
- npm publish
needs:
- semantic-release
STEP2:請先撰寫index.js
var tags_map = {};
var categories_map = {};
var slug = require('github-slugid');
var eol = require('os').EOL;
module.exports = {
book: {
assets: './assets',
css: [
"plugin.css"
]
},
hooks: {
"page:before": function(page) {
if (this.output.name != 'website') return page;
if (page.path === 'tags.md') {
for (var key in tags_map) {
if (tags_map.hasOwnProperty(key)) {
var tag_header = eol.concat('## ', key, eol);
page.content = page.content.concat(tag_header);
tags_map[key].forEach(function(e) {
var tag_body = eol.concat('- ', '[', e.title, ']', '(', e.url, ')');
page.content = page.content.concat(tag_body);
})
page.content = page.content.concat(eol);
}
}
for (var key in categories_map) {
if (categories_map.hasOwnProperty(key)) {
var cat_header = eol.concat('## ', key, eol);
page.content = page.content.concat(cat_header);
categories_map[key].forEach(function(e) {
var cat_body = eol.concat('- ', '[', e.title, ']', '(', e.url, ')');
page.content = page.content.concat(cat_body);
})
page.content = page.content.concat(eol);
}
}
return page;
}
var rawtags = '';
if (page.tags) {
rawtags = page.tags;
} else {
page.content = page.content.concat(eol);
var _tag_exist = page.content.match(/^\s*tags:\s*\[*(.*?)\]*$/im);
if (_tag_exist) rawtags = _tag_exist[1];
}
var rawcategories = '';
if (page.categories) {
rawcategories = page.categories;
} else {
page.content = page.content.concat(eol);
var _cat_exist = page.content.match(/^\s*categories:\s*\[*(.*?)\]*$/im);
if (_cat_exist) rawcategories = _cat_exist[1];
}
var created = '無';
var updated = '無';
var _created_exist = page.content.match(/^\s*created:\s*(.*)$/im);
if (_created_exist) created = _created_exist[1].trim();
var _updated_exist = page.content.match(/^\s*updated:\s*(.*)$/im);
if (_updated_exist) updated = _updated_exist[1].trim();
var tags = [];
if (rawtags) {
rawtags = ('' + rawtags).split(',');
rawtags.forEach(function(e) {
var tags_ = e.match(/^\s*['"]*\s*(.*?)\s*['"]*\s*$/)[1];
if (tags_) tags.push(tags_);
});
}
var categories = [];
if (rawcategories) {
rawcategories = ('' + rawcategories).split(',');
rawcategories.forEach(function(e) {
var cats_ = e.match(/^\s*['"]*\s*(.*?)\s*['"]*\s*$/)[1];
if (cats_) categories.push(cats_);
});
}
tags.forEach(function(e) {
if (!tags_map[e]) tags_map[e] = [];
tags_map[e].push({
url: page.path,
title: page.title
});
});
categories.forEach(function(e) {
if (!categories_map[e]) categories_map[e] = [];
categories_map[e].push({
url: page.path,
title: page.title
});
});
var tags_before_ = [];
var cats_before_ = [];
tags.forEach(function(e) {
if (page.type === 'markdown') {
tags_before_.push('<a href="/tags.html#' + slug(e) + '" class="tag-button"><i class="fa fa-tag" aria-hidden="true"></i> ' + e + '</a>');
} else {
tags_before_.push('link:/tags.html#' + slug(e) + '[' + e + ']');
}
});
categories.forEach(function(e) {
if (page.type === 'markdown') {
cats_before_.push('<a href="/tags.html#' + slug(e) + '" class="category-button"><i class="fa fa-folder" aria-hidden="true"></i> ' + e + '</a>');
} else {
cats_before_.push('link:/tags.html#' + slug(e) + '[' + e + ']');
}
});
var articleInfo = eol + '<div class="article-info">' +
'<div class="info-item"><span class="label">標題:</span> ' + page.title + '</div>' +
'<div class="info-item"><span class="label">作者:</span> MarkHSU</div>' +
'<div class="info-item"><span class="label">創建時間:</span> ' + created + '</div>' +
'<div class="info-item"><span class="label">修改時間:</span> ' + updated + '</div>' +
'<div class="info-item"><span class="label">文章連結:</span> <a href="' + page.path + '">' + page.path + '</a></div>' +
'<div class="info-item"><span class="label">版權聲明:</span> © 2025 MarkHSU. All rights reserved' +
'</div>' + eol;
var meta_format = eol.concat(eol, 'metastart', eol, articleInfo);
if (tags_before_.length > 0 || cats_before_.length > 0) {
meta_format += '<div class="meta-wrapper">';
if (tags_before_.length > 0) {
meta_format += '<div class="meta-section tags-section">' +
tags_before_.join(' ') +
'</div>';
}
if (cats_before_.length > 0) {
meta_format += '<div class="meta-section categories-section">' +
cats_before_.join(' ') +
'</div>';
}
}
meta_format += eol + 'metastop' + eol;
page.content = page.content.replace(/^\s?tags:\s?\[?(.*?)\]?$/im, eol);
page.content = page.content.replace(/^\s?categories:\s?\[?(.*?)\]?$/im, eol);
var placement = this.config.get('pluginsConfig.tags.placement') || 'top';
if (placement === 'bottom') {
page.content = page.content.concat(meta_format);
} else {
if (page.type === 'markdown') {
page.content = page.content.replace(/^#\s*(.*?)$/m, '#$1' + meta_format);
} else {
page.content = page.content.replace(/^=\s*(.*?)$/m, '=$1' + meta_format);
}
}
return page;
},
"page": function(page) {
page.content = page.content.replace(/<\/div>\s*metastop/g, '');
page.content = page.content.replace(/<\/div>\s*$/g, '');
page.content = page.content.replace(
/(<div class="paragraph">)?\s*<p>metastart<\/p>\s*(<\/div>)?/g,
'<div class="article-meta">'
);
page.content = page.content.replace(
/(<div class="paragraph">)?\s*<p>metastop<\/p>\s*(<\/div>)?/g,
'</div>'
);
page.content = page.content.replace('<strong>ADOCTAGS</strong>', '<i class="fa fa-tags" aria-hidden="true"></i> ');
page.content = page.content.replace('<strong>ADOCCATS</strong>', '<i class="fa fa-folder" aria-hidden="true"></i> ');
return page;
}
}
};
STEP3:請先撰寫assets\plugin.css
.article-info {
background-color: #f8f9fa;
border: 1px solid #e9ecef;
border-left: 3px solid #000;
border-radius: 5px;
padding: 15px;
margin-bottom: 20px;
text-align: left;
}
.info-item {
margin: 5px 0;
color: #000;
font-size: 14px;
}
.info-item .label {
font-weight: bold;
font-size: 14px;
margin-right: 5px;
color: #000;
}
.meta-wrapper {
display: flex;
justify-content: center;
align-items: center;
gap: 20px;
margin-top: 15px;
}
.meta-section {
display: flex;
align-items: center;
}
.tag-icon,
.category-icon {
margin-right: 10px;
color: #000;
}
.tag-item,
.category-item {
display: inline-block;
background: #fff;
border: 1px solid #ddd;
border-radius: 15px;
padding: 4px 12px;
margin: 0 4px;
font-size: 0.9em;
}
.tag-item a,
.category-item a {
color: #000;
text-decoration: none;
}
.tag-item:hover,
.category-item:hover {
background: #f0f0f0;
}
.article-meta .fa {
margin-right: 5px;
}
.article-meta a {
color: #000;
text-decoration: none;
}
.article-meta a:hover {
text-decoration: underline;
}
.tag-button, .category-button {
display: inline-flex;
align-items: center;
padding: 5px 12px;
margin: 2px;
border: none;
border-radius: 15px;
cursor: pointer;
font-size: 13px;
transition: all 0.2s ease;
}
.tag-button {
background-color: #e8f5e9 !important;
color: #2e7d32 !important;
}
.tag-button:hover {
background: transparent;
cursor: pointer;
border: 1px solid #e8f5e9;
color: #2e7d32;
transition: all 0.3ms;
text-decoration: none !important;
}
.category-button {
background-color: #e3f2fd !important;
color: #1976d2 !important;
}
.category-button:hover {
background: transparent;
cursor: pointer;
border: 1px solid #e3f2fd;
color: #1976d2;
transition: all 0.3ms;
text-decoration: none !important;
}
.tag-button i, .category-button i {
margin-right: 5px;
}
.meta-wrapper {
margin: 10px 0;
}
.meta-section {
margin: 5px 0;
}
.article-meta {
padding: 15px;
border-radius: 5px;
margin: 15px 0;
}
.article-info {
margin-bottom: 10px;
}
.info-item {
margin: 5px 0;
}
.info-item .label {
font-weight: bold;
margin-right: 5px;
}STEP4:請先撰寫package.json
{
"name": "gitbook-plugin-tags-info",
"version": "1.0.9",
"description": "Gitbook tags-info for markhsu",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"semantic-release": "semantic-release"
},
"repository": {
"type": "git",
"url": "git+https://markweb.idv.tw:10443/gitbooknpmproject/gitbook-plugin-tags-info.git"
},
"author": "小彥",
"license": "ISC",
"bugs": {
"url": "https://markweb.idv.tw:10443/gitbooknpmproject/gitbook-plugin-tags-info/-/issues"
},
"homepage": "https://markweb.idv.tw:10443/gitbooknpmproject/gitbook-plugin-tags-info/-/blob/master/README.md",
"dependencies": {
"semver": "^7.6.3"
},
"engines": {
"gitbook": ">=3.0.0"
},
"release": {
"extends": "@semantic-release/gitlab-config",
"analyzeCommits": {
"preset": "angular",
"releaseRules": [
{
"type": "feat",
"release": "minor"
},
{
"type": "fix",
"release": "patch"
},
{
"type": "docs",
"release": "patch"
},
{
"type": "style",
"release": "patch"
},
{
"type": "chore",
"release": "patch"
},
{
"type": "refactor",
"release": "patch"
},
{
"type": "test",
"release": "patch"
},
{
"type": "build",
"release": "patch"
},
{
"type": "ci",
"release": "patch"
}
]
},
"plugins": [
[
"@semantic-release/commit-analyzer",
{
"path": "./custom-release-notes-generator.js"
}
],
[
"@semantic-release/release-notes-generator",
{
"preset": "conventionalcommits",
"presetConfig": {
"types": [
{
"type": "feat",
"section": "✨ 新增功能"
},
{
"type": "fix",
"section": "🐞 錯誤修正"
},
{
"type": "perf",
"section": "🚀 效能調整"
},
{
"type": "revert",
"section": "⏪ 退版"
},
{
"type": "docs",
"section": "📃 文件調整",
"hidden": false
},
{
"type": "style",
"section": "🌈 樣式調整",
"hidden": false
},
{
"type": "chore",
"section": "🛠️ 重大更新",
"hidden": true
},
{
"type": "refactor",
"section": "🔨 程式碼重構",
"hidden": true
},
{
"type": "test",
"section": "🔬 單元測試",
"hidden": false
},
{
"type": "build",
"section": "🔧 程式重構",
"hidden": false
},
{
"type": "ci",
"section": "🐎 持續整合",
"hidden": false
},
{
"type": "other",
"section": "🔄 其他",
"hidden": false
}
]
},
"parserOpts": {
"noteKeywords": [
"BREAKING CHANGE",
"BREAKING CHANGES",
"BREAKING"
]
}
}
],
"@semantic-release/gitlab",
[
"@semantic-release/git",
{
"assets": [
"package.json",
"package-lock.json",
"CHANGELOG.md"
],
"message": "${nextRelease.type === 'major' ? '🛠️chore(release):這次是重大版更!!!' : '🐞feat/fix(release):這只是小版更!!!'}\n\n v${nextRelease.version} 新的專案版本已釋出!!! [skip ci]"
}
]
],
"prepare": [
"@semantic-release/changelog",
"@semantic-release/npm",
{
"path": "@semantic-release/git",
"assets": [
"package.json",
"package-lock.json",
"CHANGELOG.md"
],
"npmPublish": true,
"message": "${nextRelease.type === 'major' ? '🛠️chore(release):這次是重大版更!!!' : '🐞feat/fix(release):這只是小版更!!!'}\n\n v${nextRelease.version} 新的專案版本已釋出!!! [skip ci]"
}
],
"generateNotes": {
"path": "./custom-release-notes-generator.js"
}
}
}
三、完成結果

