feat: marked parse
This commit is contained in:
@@ -12,3 +12,15 @@ body {
|
||||
width: 800px;
|
||||
margin: auto;
|
||||
}
|
||||
|
||||
.mermaid {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.mermaid > svg {
|
||||
margin: auto;
|
||||
}
|
||||
|
||||
img {
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
612
assets/newsprint.css
Normal file
612
assets/newsprint.css
Normal file
@@ -0,0 +1,612 @@
|
||||
/* meyer reset -- http://meyerweb.com/eric/tools/css/reset/ , v2.0 | 20110126 | License: none (public domain) */
|
||||
|
||||
@include-when-export url(https://fonts.loli.net/css?family=PT+Serif:400,400italic,700,700italic&subset=latin,cyrillic-ext,cyrillic,latin-ext);
|
||||
|
||||
/* =========== */
|
||||
|
||||
/* pt-serif-regular - latin */
|
||||
@font-face {
|
||||
font-family: 'PT Serif';
|
||||
font-style: normal;
|
||||
font-weight: normal;
|
||||
src: local('PT Serif'), local('PTSerif-Regular'), url('./newsprint/pt-serif-v11-latin-regular.woff2') format('woff2');
|
||||
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
|
||||
}
|
||||
/* pt-serif-italic - latin */
|
||||
@font-face {
|
||||
font-family: 'PT Serif';
|
||||
font-style: italic;
|
||||
font-weight: normal;
|
||||
src: local('PT Serif Italic'), local('PTSerif-Italic'), url('./newsprint/pt-serif-v11-latin-italic.woff2') format('woff2');
|
||||
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
|
||||
}
|
||||
/* pt-serif-700 - latin */
|
||||
@font-face {
|
||||
font-family: 'PT Serif';
|
||||
font-style: normal;
|
||||
font-weight: bold;
|
||||
src: local('PT Serif Bold'), local('PTSerif-Bold'), url('./newsprint/pt-serif-v11-latin-700.woff2') format('woff2');
|
||||
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
|
||||
}
|
||||
/* pt-serif-700italic - latin */
|
||||
@font-face {
|
||||
font-family: 'PT Serif';
|
||||
font-style: italic;
|
||||
font-weight: bold;
|
||||
src: local('PT Serif Bold Italic'), local('PTSerif-BoldItalic'), url('./newsprint/pt-serif-v11-latin-700italic.woff2') format('woff2');
|
||||
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
|
||||
}
|
||||
|
||||
:root {
|
||||
--active-file-bg-color: #dadada;
|
||||
--active-file-bg-color: rgba(32, 43, 51, 0.63);
|
||||
--active-file-text-color: white;
|
||||
--bg-color: #f3f2ee;
|
||||
--text-color: #1f0909;
|
||||
--control-text-color: #444;
|
||||
--rawblock-edit-panel-bd: #e5e5e5;
|
||||
|
||||
--select-text-bg-color: rgba(32, 43, 51, 0.63);
|
||||
--select-text-font-color: white;
|
||||
}
|
||||
|
||||
pre {
|
||||
--select-text-bg-color: #36284e;
|
||||
--select-text-font-color: #fff;
|
||||
}
|
||||
|
||||
html {
|
||||
font-size: 16px;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
}
|
||||
|
||||
html, body {
|
||||
background-color: #f3f2ee;
|
||||
font-family: "PT Serif", 'Times New Roman', Times, serif;
|
||||
color: #1f0909;
|
||||
line-height: 1.5em;
|
||||
}
|
||||
|
||||
/*#write {
|
||||
overflow-x: auto;
|
||||
max-width: initial;
|
||||
padding-left: calc(50% - 17em);
|
||||
padding-right: calc(50% - 17em);
|
||||
}
|
||||
|
||||
@media (max-width: 36em) {
|
||||
#write {
|
||||
padding-left: 1em;
|
||||
padding-right: 1em;
|
||||
}
|
||||
}*/
|
||||
|
||||
#write {
|
||||
max-width: 40em;
|
||||
}
|
||||
|
||||
@media only screen and (min-width: 1400px) {
|
||||
#write {
|
||||
max-width: 914px;
|
||||
}
|
||||
}
|
||||
|
||||
ol li {
|
||||
list-style-type: decimal;
|
||||
list-style-position: outside;
|
||||
}
|
||||
ul li {
|
||||
list-style-type: disc;
|
||||
list-style-position: outside;
|
||||
}
|
||||
|
||||
ol,
|
||||
ul {
|
||||
list-style: none;
|
||||
}
|
||||
|
||||
blockquote,
|
||||
q {
|
||||
quotes: none;
|
||||
}
|
||||
blockquote:before,
|
||||
blockquote:after,
|
||||
q:before,
|
||||
q:after {
|
||||
content: '';
|
||||
content: none;
|
||||
}
|
||||
table {
|
||||
border-collapse: collapse;
|
||||
border-spacing: 0;
|
||||
}
|
||||
/* styles */
|
||||
|
||||
/* ====== */
|
||||
|
||||
/* headings */
|
||||
|
||||
h1,
|
||||
h2,
|
||||
h3,
|
||||
h4,
|
||||
h5,
|
||||
h6 {
|
||||
font-weight: bold;
|
||||
}
|
||||
h1 {
|
||||
font-size: 1.875em;
|
||||
/*30 / 16*/
|
||||
line-height: 1.6em;
|
||||
/* 48 / 30*/
|
||||
margin-top: 2em;
|
||||
}
|
||||
h2,
|
||||
h3 {
|
||||
font-size: 1.3125em;
|
||||
/*21 / 16*/
|
||||
line-height: 1.15;
|
||||
/*24 / 21*/
|
||||
margin-top: 2.285714em;
|
||||
/*48 / 21*/
|
||||
margin-bottom: 1.15em;
|
||||
/*24 / 21*/
|
||||
}
|
||||
h3 {
|
||||
font-weight: normal;
|
||||
}
|
||||
h4 {
|
||||
font-size: 1.125em;
|
||||
/*18 / 16*/
|
||||
margin-top: 2.67em;
|
||||
/*48 / 18*/
|
||||
}
|
||||
h5,
|
||||
h6 {
|
||||
font-size: 1em;
|
||||
/*16*/
|
||||
}
|
||||
h1 {
|
||||
border-bottom: 1px solid;
|
||||
margin-bottom: 1.875em;
|
||||
padding-bottom: 0.8125em;
|
||||
}
|
||||
/* links */
|
||||
|
||||
a {
|
||||
text-decoration: none;
|
||||
color: #065588;
|
||||
}
|
||||
a:hover,
|
||||
a:active {
|
||||
text-decoration: underline;
|
||||
}
|
||||
/* block spacing */
|
||||
|
||||
p,
|
||||
blockquote,
|
||||
.md-fences {
|
||||
margin-bottom: 1.5em;
|
||||
}
|
||||
h1,
|
||||
h2,
|
||||
h3,
|
||||
h4,
|
||||
h5,
|
||||
h6 {
|
||||
margin-bottom: 1.5em;
|
||||
}
|
||||
/* blockquote */
|
||||
|
||||
blockquote {
|
||||
font-style: italic;
|
||||
border-left: 5px solid;
|
||||
margin-left: 2em;
|
||||
padding-left: 1em;
|
||||
}
|
||||
/* lists */
|
||||
|
||||
ul,
|
||||
ol {
|
||||
margin: 0 0 1.5em 1.5em;
|
||||
}
|
||||
/* tables */
|
||||
.md-meta,.md-before, .md-after {
|
||||
color:#999;
|
||||
}
|
||||
|
||||
table {
|
||||
margin-bottom: 1.5em;
|
||||
/*24 / 16*/
|
||||
font-size: 1em;
|
||||
/* width: 100%; */
|
||||
}
|
||||
thead th,
|
||||
tfoot th {
|
||||
padding: .25em .25em .25em .4em;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
th {
|
||||
text-align: left;
|
||||
}
|
||||
td {
|
||||
vertical-align: top;
|
||||
padding: .25em .25em .25em .4em;
|
||||
}
|
||||
|
||||
code,
|
||||
.md-fences {
|
||||
background-color: #dadada;
|
||||
}
|
||||
|
||||
code {
|
||||
padding-left: 2px;
|
||||
padding-right: 2px;
|
||||
}
|
||||
|
||||
.md-fences {
|
||||
margin-left: 2em;
|
||||
margin-bottom: 3em;
|
||||
padding-left: 1ch;
|
||||
padding-right: 1ch;
|
||||
}
|
||||
|
||||
pre,
|
||||
code,
|
||||
tt {
|
||||
font-size: .875em;
|
||||
line-height: 1.714285em;
|
||||
}
|
||||
/* some fixes */
|
||||
|
||||
h1 {
|
||||
line-height: 1.3em;
|
||||
font-weight: normal;
|
||||
margin-bottom: 0.5em;
|
||||
}
|
||||
|
||||
p + ul,
|
||||
p + ol{
|
||||
margin-top: .5em;
|
||||
}
|
||||
|
||||
h3 + ul,
|
||||
h4 + ul,
|
||||
h5 + ul,
|
||||
h6 + ul,
|
||||
h3 + ol,
|
||||
h4 + ol,
|
||||
h5 + ol,
|
||||
h6 + ol {
|
||||
margin-top: .5em;
|
||||
}
|
||||
|
||||
li > ul,
|
||||
li > ol {
|
||||
margin-top: inherit;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
li ol>li {
|
||||
list-style-type: lower-alpha;
|
||||
}
|
||||
|
||||
li li ol>li{
|
||||
list-style-type: lower-roman;
|
||||
}
|
||||
|
||||
h2,
|
||||
h3 {
|
||||
margin-bottom: .75em;
|
||||
}
|
||||
hr {
|
||||
border-top: none;
|
||||
border-right: none;
|
||||
border-bottom: 1px solid;
|
||||
border-left: none;
|
||||
}
|
||||
h1 {
|
||||
border-color: #c5c5c5;
|
||||
}
|
||||
blockquote {
|
||||
border-color: #bababa;
|
||||
color: #656565;
|
||||
}
|
||||
|
||||
blockquote ul,
|
||||
blockquote ol {
|
||||
margin-left:0;
|
||||
}
|
||||
|
||||
.ty-table-edit {
|
||||
background-color: transparent;
|
||||
}
|
||||
thead {
|
||||
background-color: #dadada;
|
||||
}
|
||||
tr:nth-child(even) {
|
||||
background: #e8e7e7;
|
||||
}
|
||||
hr {
|
||||
border-color: #c5c5c5;
|
||||
}
|
||||
.task-list{
|
||||
padding-left: 1rem;
|
||||
}
|
||||
|
||||
.md-task-list-item {
|
||||
padding-left: 1.5rem;
|
||||
list-style-type: none;
|
||||
}
|
||||
|
||||
.md-task-list-item > input:before {
|
||||
content: '\221A';
|
||||
display: inline-block;
|
||||
width: 1.25rem;
|
||||
height: 1.6rem;
|
||||
vertical-align: middle;
|
||||
text-align: center;
|
||||
color: #ddd;
|
||||
background-color: #F3F2EE;
|
||||
}
|
||||
|
||||
.md-task-list-item > input:checked:before,
|
||||
.md-task-list-item > input[checked]:before{
|
||||
color: inherit;
|
||||
}
|
||||
|
||||
#write pre.md-meta-block {
|
||||
min-height: 1.875rem;
|
||||
color: #555;
|
||||
border: 0px;
|
||||
background: transparent;
|
||||
margin-top: -4px;
|
||||
margin-left: 1em;
|
||||
margin-top: 1em;
|
||||
}
|
||||
|
||||
.md-image>.md-meta {
|
||||
color: #9B5146;
|
||||
}
|
||||
|
||||
.md-image>.md-meta{
|
||||
font-family: Menlo, 'Ubuntu Mono', Consolas, 'Courier New', 'Microsoft Yahei', 'Hiragino Sans GB', 'WenQuanYi Micro Hei', serif;
|
||||
}
|
||||
|
||||
|
||||
#write>h3.md-focus:before{
|
||||
left: -1.5rem;
|
||||
color:#999;
|
||||
border-color:#999;
|
||||
}
|
||||
#write>h4.md-focus:before{
|
||||
left: -1.5rem;
|
||||
top: .25rem;
|
||||
color:#999;
|
||||
border-color:#999;
|
||||
}
|
||||
#write>h5.md-focus:before{
|
||||
left: -1.5rem;
|
||||
top: .0.3125rem;
|
||||
color:#999;
|
||||
border-color:#999;
|
||||
}
|
||||
#write>h6.md-focus:before{
|
||||
left: -1.5rem;
|
||||
top: 0.3125rem;
|
||||
color:#999;
|
||||
border-color:#999;
|
||||
}
|
||||
|
||||
.md-toc:focus .md-toc-content{
|
||||
margin-top: 19px;
|
||||
}
|
||||
|
||||
.md-toc-content:empty:before{
|
||||
color: #065588;
|
||||
}
|
||||
.md-toc-item {
|
||||
color: #065588;
|
||||
}
|
||||
#write div.md-toc-tooltip {
|
||||
background-color: #f3f2ee;
|
||||
}
|
||||
|
||||
#typora-sidebar {
|
||||
background-color: #f3f2ee;
|
||||
-webkit-box-shadow: 0 6px 12px rgba(0, 0, 0, 0.375);
|
||||
box-shadow: 0 6px 12px rgba(0, 0, 0, 0.375);
|
||||
}
|
||||
|
||||
.pin-outline #typora-sidebar {
|
||||
background: inherit;
|
||||
box-shadow: none;
|
||||
border-right: 1px dashed;
|
||||
}
|
||||
|
||||
.pin-outline #typora-sidebar:hover .outline-title-wrapper {
|
||||
border-left:1px dashed;
|
||||
}
|
||||
|
||||
.outline-item:hover {
|
||||
background-color: #dadada;
|
||||
border-left: 28px solid #dadada;
|
||||
border-right: 18px solid #dadada;
|
||||
}
|
||||
|
||||
.typora-node .outline-item:hover {
|
||||
border-right: 28px solid #dadada;
|
||||
}
|
||||
|
||||
.outline-expander:before {
|
||||
content: "\f0da";
|
||||
font-family: FontAwesome;
|
||||
font-size:14px;
|
||||
top: 1px;
|
||||
}
|
||||
|
||||
.outline-expander:hover:before,
|
||||
.outline-item-open>.outline-item>.outline-expander:before {
|
||||
content: "\f0d7";
|
||||
}
|
||||
|
||||
.modal-content {
|
||||
background-color: #f3f2ee;
|
||||
}
|
||||
|
||||
.auto-suggest-container ul li {
|
||||
list-style-type: none;
|
||||
}
|
||||
|
||||
/** UI for electron */
|
||||
|
||||
.megamenu-menu,
|
||||
#top-titlebar, #top-titlebar *,
|
||||
.megamenu-content {
|
||||
background: #f3f2ee;
|
||||
color: #1f0909;
|
||||
}
|
||||
|
||||
.megamenu-menu-header {
|
||||
border-bottom: 1px dashed #202B33;
|
||||
}
|
||||
|
||||
.megamenu-menu {
|
||||
box-shadow: none;
|
||||
border-right: 1px dashed;
|
||||
}
|
||||
|
||||
header, .context-menu, .megamenu-content, footer {
|
||||
font-family: "PT Serif", 'Times New Roman', Times, serif;
|
||||
color: #1f0909;
|
||||
}
|
||||
|
||||
#megamenu-back-btn {
|
||||
color: #1f0909;
|
||||
border-color: #1f0909;
|
||||
}
|
||||
|
||||
.megamenu-menu-header #megamenu-menu-header-title:before {
|
||||
color: #1f0909;
|
||||
}
|
||||
|
||||
.megamenu-menu-list li a:hover, .megamenu-menu-list li a.active {
|
||||
color: inherit;
|
||||
background-color: #e8e7df;
|
||||
}
|
||||
|
||||
.long-btn:hover {
|
||||
background-color: #e8e7df;
|
||||
}
|
||||
|
||||
#recent-file-panel tbody tr:nth-child(2n-1) {
|
||||
background-color: transparent !important;
|
||||
}
|
||||
|
||||
.megamenu-menu-panel tbody tr:hover td:nth-child(2) {
|
||||
color: inherit;
|
||||
}
|
||||
|
||||
.megamenu-menu-panel .btn {
|
||||
background-color: #D2D1D1;
|
||||
}
|
||||
|
||||
.btn-default {
|
||||
background-color: transparent;
|
||||
}
|
||||
|
||||
.typora-sourceview-on #toggle-sourceview-btn,
|
||||
.ty-show-word-count #footer-word-count {
|
||||
background: #c7c5c5;
|
||||
}
|
||||
|
||||
#typora-quick-open {
|
||||
background-color: inherit;
|
||||
}
|
||||
|
||||
.md-diagram-panel {
|
||||
margin-top: 8px;
|
||||
}
|
||||
|
||||
.file-list-item-file-name {
|
||||
font-weight: initial;
|
||||
}
|
||||
|
||||
.file-list-item-summary {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.file-list-item {
|
||||
color: #777;
|
||||
}
|
||||
|
||||
.file-list-item.active {
|
||||
background-color: inherit;
|
||||
color: black;
|
||||
}
|
||||
|
||||
.ty-side-sort-btn.active {
|
||||
background-color: inherit;
|
||||
}
|
||||
|
||||
.file-list-item.active .file-list-item-file-name {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.file-list-item{
|
||||
opacity:1 !important;
|
||||
}
|
||||
|
||||
.file-library-node.active>.file-node-background{
|
||||
background-color: rgba(32, 43, 51, 0.63);
|
||||
background-color: var(--active-file-bg-color);
|
||||
}
|
||||
|
||||
.file-tree-node.active>.file-node-content{
|
||||
color: white;
|
||||
color: var(--active-file-text-color);
|
||||
}
|
||||
|
||||
.md-task-list-item>input {
|
||||
margin-left: -1.7em;
|
||||
margin-top: calc(1rem - 12px);
|
||||
}
|
||||
|
||||
input {
|
||||
border: 1px solid #aaa;
|
||||
}
|
||||
|
||||
.megamenu-menu-header #megamenu-menu-header-title,
|
||||
.megamenu-menu-header:hover,
|
||||
.megamenu-menu-header:focus {
|
||||
color: inherit;
|
||||
}
|
||||
|
||||
.dropdown-menu .divider {
|
||||
border-color: #e5e5e5;
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
/* https://github.com/typora/typora-issues/issues/2046 */
|
||||
.os-windows-7 strong,
|
||||
.os-windows-7 strong {
|
||||
font-weight: 760;
|
||||
}
|
||||
|
||||
.ty-preferences .btn-default {
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
.ty-preferences .window-header {
|
||||
border-bottom: 1px dashed #202B33;
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
#sidebar-loading-template, #sidebar-loading-template.file-list-item {
|
||||
color: #777;
|
||||
}
|
||||
|
||||
.searchpanel-search-option-btn.active {
|
||||
background: #777;
|
||||
color: white;
|
||||
}
|
||||
BIN
assets/newsprint/pt-serif-v11-latin-700.woff2
Normal file
BIN
assets/newsprint/pt-serif-v11-latin-700.woff2
Normal file
Binary file not shown.
BIN
assets/newsprint/pt-serif-v11-latin-700italic.woff2
Normal file
BIN
assets/newsprint/pt-serif-v11-latin-700italic.woff2
Normal file
Binary file not shown.
BIN
assets/newsprint/pt-serif-v11-latin-italic.woff2
Normal file
BIN
assets/newsprint/pt-serif-v11-latin-italic.woff2
Normal file
Binary file not shown.
BIN
assets/newsprint/pt-serif-v11-latin-regular.woff2
Normal file
BIN
assets/newsprint/pt-serif-v11-latin-regular.woff2
Normal file
Binary file not shown.
10
package.json
10
package.json
@@ -77,6 +77,7 @@
|
||||
"js-yaml": "*",
|
||||
"jszip": "3.7.1",
|
||||
"lodash": "*",
|
||||
"marked": "^3.0.3",
|
||||
"mdurl": "*",
|
||||
"mkdirp": "*",
|
||||
"mongoose": "*",
|
||||
@@ -92,17 +93,11 @@
|
||||
"pluralize": "*",
|
||||
"redis": "3.1.2",
|
||||
"reflect-metadata": "0.1.13",
|
||||
"rehype-stringify": "9.0.2",
|
||||
"remark-gfm": "2.0.0",
|
||||
"remark-html": "^13.0.1",
|
||||
"remark-parse": "^5.0.0",
|
||||
"remark-rehype": "9.0.0",
|
||||
"rxjs": "7.3.0",
|
||||
"snakecase-keys": "4.0.2",
|
||||
"ts-morph": "*",
|
||||
"ua-parser-js": "0.7.28",
|
||||
"unified": "^9",
|
||||
"unist-builder": "^2",
|
||||
"xss": "^1.0.9",
|
||||
"yargs": "*",
|
||||
"zx": "4.2.0"
|
||||
},
|
||||
@@ -118,6 +113,7 @@
|
||||
"@types/ioredis": "4.27.2",
|
||||
"@types/jest": "27.0.1",
|
||||
"@types/lodash": "4.14.172",
|
||||
"@types/marked": "^3.0.0",
|
||||
"@types/mongoose-paginate-v2": "1.3.11",
|
||||
"@types/node": "16.9.1",
|
||||
"@types/nodemailer": "6.4.4",
|
||||
|
||||
782
pnpm-lock.yaml
generated
782
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
9
scripts/assets-push.sh
Normal file
9
scripts/assets-push.sh
Normal file
@@ -0,0 +1,9 @@
|
||||
|
||||
cd assets
|
||||
git init
|
||||
git add .
|
||||
git commit -m 'update assets'
|
||||
git remote add origin git@github.com:mx-space/assets.git
|
||||
git branch -M master
|
||||
git push -u origin master -f
|
||||
rm -rf .git
|
||||
@@ -7,7 +7,6 @@ import { Readable } from 'stream'
|
||||
import { Auth } from '~/common/decorator/auth.decorator'
|
||||
import { HTTPDecorators } from '~/common/decorator/http.decorator'
|
||||
import { ApiName } from '~/common/decorator/openapi.decorator'
|
||||
import { HttpService } from '~/processors/helper/helper.http.service'
|
||||
import { AssetService } from '~/processors/helper/hepler.asset.service'
|
||||
import { MongoIdDto } from '~/shared/dto/id.dto'
|
||||
import { CategoryModel } from '../category/category.model'
|
||||
@@ -20,7 +19,7 @@ import { MarkdownService } from './markdown.service'
|
||||
export class MarkdownController {
|
||||
constructor(
|
||||
private readonly service: MarkdownService,
|
||||
private readonly httpService: HttpService,
|
||||
|
||||
private readonly assetService: AssetService,
|
||||
) {}
|
||||
|
||||
@@ -154,13 +153,7 @@ export class MarkdownController {
|
||||
async renderArticle(@Param() params: MongoIdDto, @Res() reply: FastifyReply) {
|
||||
const { id } = params
|
||||
const { html: markdown, document } = await this.service.renderArticle(id)
|
||||
const [theme] = await Promise.all([
|
||||
this.httpService.axiosRef
|
||||
.get(
|
||||
'https://gitee.com/xun7788/mx-server-assets/raw/master/newsprint.css',
|
||||
)
|
||||
.then((r) => r.data),
|
||||
])
|
||||
|
||||
const style = await this.assetService.getAsset('markdown.css', {
|
||||
encoding: 'utf8',
|
||||
})
|
||||
@@ -176,14 +169,25 @@ export class MarkdownController {
|
||||
<meta name="referrer" content="no-referrer">
|
||||
<style>
|
||||
${style}
|
||||
${theme}
|
||||
</style>
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/gh/mx-space/assets@master/newsprint.css">
|
||||
<title>${document.title}</title>
|
||||
</head>
|
||||
<body>
|
||||
<h1>${document.title}</h1>
|
||||
<article>
|
||||
|
||||
<h1>${document.title}</h1>
|
||||
${markdown}
|
||||
</article>
|
||||
</body>
|
||||
<script src="https://cdn.jsdelivr.net/npm/mermaid/dist/mermaid.min.js"></script>
|
||||
<script>
|
||||
window.mermaid.initialize({
|
||||
theme: 'default',
|
||||
startOnLoad: false,
|
||||
})
|
||||
window.mermaid.init(undefined, '.mermaid')
|
||||
</script>
|
||||
</html>
|
||||
|
||||
`
|
||||
|
||||
@@ -3,21 +3,16 @@ import { ReturnModelType } from '@typegoose/typegoose'
|
||||
import { dump } from 'js-yaml'
|
||||
import JSZip from 'jszip'
|
||||
import { omit } from 'lodash'
|
||||
import normalize from 'mdurl/encode.js'
|
||||
import marked from 'marked'
|
||||
import { Types } from 'mongoose'
|
||||
import { InjectModel } from 'nestjs-typegoose'
|
||||
import html from 'remark-html'
|
||||
import markdown from 'remark-parse'
|
||||
import unified from 'unified'
|
||||
import u from 'unist-builder'
|
||||
import xss from 'xss'
|
||||
import { CategoryModel } from '../category/category.model'
|
||||
import { NoteModel } from '../note/note.model'
|
||||
import { PageModel } from '../page/page.model'
|
||||
import { PostModel } from '../post/post.model'
|
||||
import { DatatypeDto } from './markdown.dto'
|
||||
import { MarkdownYAMLProperty } from './markdown.interface'
|
||||
import rules from './rules'
|
||||
|
||||
@Injectable()
|
||||
export class MarkdownService {
|
||||
private logger: Logger
|
||||
@@ -226,57 +221,87 @@ ${text.trim()}
|
||||
}
|
||||
|
||||
private render(text: string) {
|
||||
const parser = unified()
|
||||
.use(markdown)
|
||||
.use(rules)
|
||||
// @ts-ignore
|
||||
.use(html, {
|
||||
allowDangerousHtml: true,
|
||||
handlers: {
|
||||
image: (h, node: any) => {
|
||||
// console.log(node)
|
||||
|
||||
const src = node.url as string
|
||||
const _alt = node.alt as string | undefined
|
||||
const alt = _alt?.match(/^[!¡]/) ? _alt!.replace(/^[¡!]/, '') : ''
|
||||
if (!alt) {
|
||||
return h(node, 'img', { src })
|
||||
marked.use({
|
||||
gfm: true,
|
||||
sanitize: false,
|
||||
extensions: [
|
||||
{
|
||||
level: 'inline',
|
||||
name: 'spoiler',
|
||||
start(src) {
|
||||
return src.match(/\|/)?.index
|
||||
},
|
||||
renderer(token) {
|
||||
// @ts-ignore
|
||||
return `<span class="spoiler" style="filter: invert(25%)">${this.parser.parseInline(
|
||||
token.text,
|
||||
)}\n</span>`
|
||||
},
|
||||
tokenizer(src, tokens) {
|
||||
const rule = /^\|\|([\s\S]+?)\|\|(?!\|)/ // Regex for the complete token
|
||||
const match = rule.exec(src)
|
||||
if (match) {
|
||||
return {
|
||||
// Token to generate
|
||||
type: 'spoiler', // Should match "name" above
|
||||
raw: match[0], // Text to consume from the source
|
||||
// @ts-ignore
|
||||
text: this.lexer.inlineTokens(match[1].trim()),
|
||||
}
|
||||
}
|
||||
return h(node, 'figure', {}, [
|
||||
h(node, 'img', { src: normalize(src) }),
|
||||
h.augment(
|
||||
node,
|
||||
u(
|
||||
'raw',
|
||||
`<figcaption style="text-align: center; margin: 1em auto;">${this.escapeHTMLTag(
|
||||
alt,
|
||||
)}</figcaption>`,
|
||||
),
|
||||
),
|
||||
])
|
||||
},
|
||||
spoiler: (h, node: any) => {
|
||||
return h(node, 'del', {
|
||||
class: 'spoiler',
|
||||
style: 'filter: invert(25%);',
|
||||
})
|
||||
},
|
||||
childTokens: ['text'],
|
||||
},
|
||||
} as any)
|
||||
{
|
||||
level: 'inline',
|
||||
name: 'mention',
|
||||
start(src) {
|
||||
return src.match(/\(/)?.index
|
||||
},
|
||||
renderer(token) {
|
||||
// @ts-ignore
|
||||
const username = this.parser.parseInline(token.text).slice(1)
|
||||
return `<a class="mention" rel="noreferrer nofollow" href="https://github.com/${username}" target="_blank">@${username}\n</a>`
|
||||
},
|
||||
tokenizer(src, tokens) {
|
||||
const rule = /^\((@(\w+\b))\)\s?(?!\[.*?\])/ // Regex for the complete token
|
||||
const match = rule.exec(src)
|
||||
if (match) {
|
||||
return {
|
||||
// Token to generate
|
||||
type: 'mention', // Should match "name" above
|
||||
raw: match[0], // Text to consume from the source
|
||||
text: this.lexer.inlineTokens(match[1].trim(), []),
|
||||
}
|
||||
}
|
||||
},
|
||||
childTokens: ['text'],
|
||||
},
|
||||
],
|
||||
|
||||
return parser.processSync(text).toString()
|
||||
}
|
||||
renderer: {
|
||||
image(src, title, _alt) {
|
||||
const alt = _alt?.match(/^[!¡]/) ? _alt!.replace(/^[¡!]/, '') : ''
|
||||
if (!alt) {
|
||||
return `<img src="${xss(src)}"/>`
|
||||
}
|
||||
return `<figure>
|
||||
<img src="${xss(src)}"/>
|
||||
<figcaption style="text-align: center; margin: 1em auto;">${xss(
|
||||
alt,
|
||||
)}</figcaption></figure>`
|
||||
},
|
||||
|
||||
private escapeHTMLTag(html: string) {
|
||||
const lt = /</g,
|
||||
gt = />/g,
|
||||
ap = /'/g,
|
||||
ic = /"/g
|
||||
return html
|
||||
.toString()
|
||||
.replace(lt, '<')
|
||||
.replace(gt, '>')
|
||||
.replace(ap, ''')
|
||||
.replace(ic, '"')
|
||||
code(code, lang) {
|
||||
if (lang == 'mermaid') {
|
||||
return '<div class="mermaid">' + code + '</div>'
|
||||
} else {
|
||||
return '<pre><code>' + code + '</code></pre>'
|
||||
}
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
return marked(text)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +0,0 @@
|
||||
import { mentions } from './mentions'
|
||||
import { spoiler } from './spoiler'
|
||||
|
||||
export const rules = { mentions, spoiler }
|
||||
|
||||
export default Object.values(rules)
|
||||
@@ -1,46 +0,0 @@
|
||||
/*
|
||||
* @Author: Innei
|
||||
* @Date: 2020-06-11 13:01:08
|
||||
* @LastEditTime: 2020-06-12 20:19:16
|
||||
* @LastEditors: Innei
|
||||
* @FilePath: /mx-web/common/markdown/rules/mentions.ts
|
||||
* @Coding with Love
|
||||
*/
|
||||
/**
|
||||
* parse (@username) to github user profile
|
||||
*/
|
||||
|
||||
function tokenizeMention(eat: any, value: string, silent?: boolean): any {
|
||||
const match = /\((@(\w+\b))\)\s(?!\[.*?\])/.exec(value)
|
||||
|
||||
if (match) {
|
||||
if (silent) {
|
||||
return true
|
||||
}
|
||||
try {
|
||||
return eat(match[0])({
|
||||
type: 'link',
|
||||
url: 'https://github.com/' + match[2],
|
||||
children: [{ type: 'text', value: match[1] }],
|
||||
})
|
||||
// eslint-disable-next-line no-empty
|
||||
} catch {}
|
||||
}
|
||||
}
|
||||
tokenizeMention.notInLink = true
|
||||
tokenizeMention.locator = locateMention
|
||||
function locateMention(value, fromIndex) {
|
||||
return value.indexOf('@', fromIndex)
|
||||
}
|
||||
function mentions(this: any) {
|
||||
const Parser = this.Parser as any
|
||||
const tokenizers = Parser.prototype.inlineTokenizers
|
||||
const methods = Parser.prototype.inlineMethods
|
||||
|
||||
// Add an inline tokenizer (defined in the following example).
|
||||
tokenizers.mention = tokenizeMention
|
||||
|
||||
// Run it just before `text`.
|
||||
methods.splice(methods.indexOf('text'), 0, 'mention')
|
||||
}
|
||||
export { mentions }
|
||||
@@ -1,42 +0,0 @@
|
||||
/*
|
||||
* @Author: Innei
|
||||
* @Date: 2020-06-11 13:31:05
|
||||
* @LastEditTime: 2020-09-02 20:03:18
|
||||
* @LastEditors: Innei
|
||||
* @FilePath: /mx-web/common/markdown/rules/spoiler.ts
|
||||
* @Coding with Love
|
||||
*/
|
||||
|
||||
function tokenizeSpoiler(eat: any, value: string, silent?: boolean): any {
|
||||
const match = /^\|\|([\s\S]+?)\|\|(?!\|)/.exec(value)
|
||||
|
||||
if (match) {
|
||||
if (silent) {
|
||||
return true
|
||||
}
|
||||
try {
|
||||
return eat(match[0])({
|
||||
type: 'spoiler',
|
||||
value: match[1],
|
||||
})
|
||||
// eslint-disable-next-line no-empty
|
||||
} catch {}
|
||||
}
|
||||
}
|
||||
tokenizeSpoiler.notInLink = true
|
||||
tokenizeSpoiler.locator = function (value, fromIndex) {
|
||||
return value.indexOf('||', fromIndex)
|
||||
}
|
||||
|
||||
function spoiler(this: any) {
|
||||
const Parser = this.Parser as any
|
||||
const tokenizers = Parser.prototype.inlineTokenizers
|
||||
const methods = Parser.prototype.inlineMethods
|
||||
|
||||
// Add an inline tokenizer (defined in the following example).
|
||||
tokenizers.spoiler = tokenizeSpoiler
|
||||
|
||||
// Run it just before `text`.
|
||||
methods.splice(methods.indexOf('text'), 0, 'spoiler')
|
||||
}
|
||||
export { spoiler }
|
||||
@@ -21,6 +21,9 @@ export class AssetService {
|
||||
}
|
||||
|
||||
public assetPath = path.resolve(process.cwd(), 'assets')
|
||||
// 在线资源的地址 `/` 结尾
|
||||
private onlineAssetPath =
|
||||
'https://cdn.jsdelivr.net/gh/mx-space/assets@master/'
|
||||
|
||||
private checkRoot() {
|
||||
if (!fs.existsSync(this.assetPath)) {
|
||||
@@ -48,7 +51,7 @@ export class AssetService {
|
||||
try {
|
||||
// 去线上拉取
|
||||
const { data } = await this.httpService.axiosRef.get(
|
||||
`https://gitee.com/xun7788/mx-server-assets/raw/master/` + path,
|
||||
this.onlineAssetPath + path,
|
||||
)
|
||||
|
||||
fs.mkdirSync(
|
||||
|
||||
Reference in New Issue
Block a user