Skip to content
Projects
Groups
Snippets
Help
This project
Loading...
Sign in / Register
Toggle navigation
W
wiki-js
Project
Project
Details
Activity
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
1
Issues
1
List
Board
Labels
Milestones
Merge Requests
1
Merge Requests
1
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Registry
Registry
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
Jacklull
wiki-js
Commits
80b1cbff
You need to sign in or sign up before continuing.
Unverified
Commit
80b1cbff
authored
May 13, 2023
by
NGPixel
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
feat: editor + page rendering improvements
parent
593822e0
Hide whitespace changes
Inline
Side-by-side
Showing
8 changed files
with
651 additions
and
44 deletions
+651
-44
markdown.mjs
server/renderers/markdown.mjs
+5
-5
markdown-it-imsize.mjs
server/renderers/modules/markdown-it-imsize.mjs
+259
-0
PageHeader.vue
ux/src/components/PageHeader.vue
+24
-14
page-contents.scss
ux/src/css/page-contents.scss
+95
-18
en.json
ux/src/i18n/locales/en.json
+2
-0
Index.vue
ux/src/pages/Index.vue
+2
-2
markdown.js
ux/src/renderers/markdown.js
+5
-5
markdown-it-imsize.js
ux/src/renderers/modules/markdown-it-imsize.js
+259
-0
No files found.
server/renderers/markdown.mjs
View file @
80b1cbff
...
...
@@ -10,9 +10,9 @@ import mdSub from 'markdown-it-sub'
import
mdMark
from
'markdown-it-mark'
import
mdMultiTable
from
'markdown-it-multimd-table'
import
mdFootnote
from
'markdown-it-footnote'
// import mdImsize from 'markdown-it-imsize'
import
katex
from
'katex'
import
underline
from
'./modules/markdown-it-underline.mjs'
import
mdImsize
from
'./modules/markdown-it-imsize.mjs'
import
mdUnderline
from
'./modules/markdown-it-underline.mjs'
// import 'katex/dist/contrib/mhchem'
import
twemoji
from
'twemoji'
import
plantuml
from
'./modules/plantuml.mjs'
...
...
@@ -51,7 +51,7 @@ export async function render (input, config) {
}
else
if
([
'mermaid'
,
'plantuml'
].
includes
(
lang
))
{
return
`<pre class="codeblock-
${
lang
}
"><code>
${
escape
(
str
)}
</code></pre>`
}
else
{
const
highlighted
=
lang
?
hljs
.
highlight
(
str
,
{
language
:
lang
,
ignoreIllegals
:
true
})
:
hljs
.
highlightAuto
(
str
)
const
highlighted
=
lang
?
hljs
.
highlight
(
str
,
{
language
:
lang
,
ignoreIllegals
:
true
})
:
{
value
:
str
}
const
lineCount
=
highlighted
.
value
.
match
(
/
\n
/g
).
length
const
lineNums
=
lineCount
>
1
?
`<span aria-hidden="true" class="line-numbers-rows">
${
times
(
lineCount
,
n
=>
'<span></span>'
).
join
(
''
)}
</span>`
:
''
return
`<pre class="codeblock
${
lineCount
>
1
&&
'line-numbers'
}
"><code class="language-
${
lang
}
">
${
highlighted
.
value
}${
lineNums
}
</code></pre>`
...
...
@@ -70,10 +70,10 @@ export async function render (input, config) {
.
use
(
mdSub
)
.
use
(
mdMark
)
.
use
(
mdFootnote
)
//
.use(mdImsize)
.
use
(
mdImsize
)
if
(
config
.
underline
)
{
md
.
use
(
u
nderline
)
md
.
use
(
mdU
nderline
)
}
if
(
config
.
mdmultiTable
)
{
...
...
server/renderers/modules/markdown-it-imsize.mjs
0 → 100644
View file @
80b1cbff
// Adapted from markdown-it-imsize plugin by @tatsy
// Original source https://github.com/tatsy/markdown-it-imsize/blob/master/lib/index.js
function
renderImSize
(
state
,
silent
)
{
let
attrs
let
code
let
label
let
pos
let
ref
let
res
let
title
let
width
=
''
let
height
=
''
let
token
let
tokens
let
start
let
href
=
''
const
oldPos
=
state
.
pos
const
max
=
state
.
posMax
if
(
state
.
src
.
charCodeAt
(
state
.
pos
)
!==
0x21
/* ! */
)
{
return
false
}
if
(
state
.
src
.
charCodeAt
(
state
.
pos
+
1
)
!==
0x5B
/* [ */
)
{
return
false
}
const
labelStart
=
state
.
pos
+
2
const
labelEnd
=
state
.
md
.
helpers
.
parseLinkLabel
(
state
,
state
.
pos
+
1
,
false
)
// parser failed to find ']', so it's not a valid link
if
(
labelEnd
<
0
)
{
return
false
}
pos
=
labelEnd
+
1
if
(
pos
<
max
&&
state
.
src
.
charCodeAt
(
pos
)
===
0x28
/* ( */
)
{
//
// Inline link
//
// [link]( <href> "title" )
// ^^ skipping these spaces
pos
++
for
(;
pos
<
max
;
pos
++
)
{
code
=
state
.
src
.
charCodeAt
(
pos
)
if
(
code
!==
0x20
&&
code
!==
0x0A
)
{
break
}
}
if
(
pos
>=
max
)
{
return
false
}
// [link]( <href> "title" )
// ^^^^^^ parsing link destination
start
=
pos
res
=
state
.
md
.
helpers
.
parseLinkDestination
(
state
.
src
,
pos
,
state
.
posMax
)
if
(
res
.
ok
)
{
href
=
state
.
md
.
normalizeLink
(
res
.
str
)
if
(
state
.
md
.
validateLink
(
href
))
{
pos
=
res
.
pos
}
else
{
href
=
''
}
}
// [link]( <href> "title" )
// ^^ skipping these spaces
start
=
pos
for
(;
pos
<
max
;
pos
++
)
{
code
=
state
.
src
.
charCodeAt
(
pos
)
if
(
code
!==
0x20
&&
code
!==
0x0A
)
{
break
}
}
// [link]( <href> "title" )
// ^^^^^^^ parsing link title
res
=
state
.
md
.
helpers
.
parseLinkTitle
(
state
.
src
,
pos
,
state
.
posMax
)
if
(
pos
<
max
&&
start
!==
pos
&&
res
.
ok
)
{
title
=
res
.
str
pos
=
res
.
pos
// [link]( <href> "title" )
// ^^ skipping these spaces
for
(;
pos
<
max
;
pos
++
)
{
code
=
state
.
src
.
charCodeAt
(
pos
)
if
(
code
!==
0x20
&&
code
!==
0x0A
)
{
break
}
}
}
else
{
title
=
''
}
// [link]( <href> "title" =WxH )
// ^^^^ parsing image size
if
(
pos
-
1
>=
0
)
{
code
=
state
.
src
.
charCodeAt
(
pos
-
1
)
// there must be at least one white spaces
// between previous field and the size
if
(
code
===
0x20
)
{
res
=
parseImageSize
(
state
.
src
,
pos
,
state
.
posMax
)
if
(
res
.
ok
)
{
width
=
res
.
width
height
=
res
.
height
pos
=
res
.
pos
// [link]( <href> "title" =WxH )
// ^^ skipping these spaces
for
(;
pos
<
max
;
pos
++
)
{
code
=
state
.
src
.
charCodeAt
(
pos
)
if
(
code
!==
0x20
&&
code
!==
0x0A
)
{
break
}
}
}
}
}
if
(
pos
>=
max
||
state
.
src
.
charCodeAt
(
pos
)
!==
0x29
/* ) */
)
{
state
.
pos
=
oldPos
return
false
}
pos
++
}
else
{
//
// Link reference
//
if
(
typeof
state
.
env
.
references
===
'undefined'
)
{
return
false
}
// [foo] [bar]
// ^^ optional whitespace (can include newlines)
for
(;
pos
<
max
;
pos
++
)
{
code
=
state
.
src
.
charCodeAt
(
pos
)
if
(
code
!==
0x20
&&
code
!==
0x0A
)
{
break
}
}
if
(
pos
<
max
&&
state
.
src
.
charCodeAt
(
pos
)
===
0x5B
/* [ */
)
{
start
=
pos
+
1
pos
=
state
.
md
.
helpers
.
parseLinkLabel
(
state
,
pos
)
if
(
pos
>=
0
)
{
label
=
state
.
src
.
slice
(
start
,
pos
++
)
}
else
{
pos
=
labelEnd
+
1
}
}
else
{
pos
=
labelEnd
+
1
}
// covers label === '' and label === undefined
// (collapsed reference link and shortcut reference link respectively)
if
(
!
label
)
{
label
=
state
.
src
.
slice
(
labelStart
,
labelEnd
)
}
ref
=
state
.
env
.
references
[
state
.
md
.
utils
.
normalizeReference
(
label
)]
if
(
!
ref
)
{
state
.
pos
=
oldPos
return
false
}
href
=
ref
.
href
title
=
ref
.
title
}
//
// We found the end of the link, and know for a fact it's a valid link;
// so all that's left to do is to call tokenizer.
//
if
(
!
silent
)
{
state
.
pos
=
labelStart
state
.
posMax
=
labelEnd
const
newState
=
new
state
.
md
.
inline
.
State
(
state
.
src
.
slice
(
labelStart
,
labelEnd
),
state
.
md
,
state
.
env
,
tokens
=
[]
)
newState
.
md
.
inline
.
tokenize
(
newState
)
token
=
state
.
push
(
'image'
,
'img'
,
0
)
token
.
attrs
=
attrs
=
[[
'src'
,
href
],
[
'alt'
,
''
]]
token
.
children
=
tokens
if
(
title
)
{
attrs
.
push
([
'title'
,
title
])
}
if
(
width
!==
''
)
{
attrs
.
push
([
'width'
,
width
])
}
if
(
height
!==
''
)
{
attrs
.
push
([
'height'
,
height
])
}
}
state
.
pos
=
pos
state
.
posMax
=
max
return
true
}
function
parseNextNumber
(
str
,
pos
,
max
)
{
let
code
const
start
=
pos
const
result
=
{
ok
:
false
,
pos
,
value
:
''
}
code
=
str
.
charCodeAt
(
pos
)
while
((
pos
<
max
&&
(
code
>=
0x30
/* 0 */
&&
code
<=
0x39
/* 9 */
))
||
code
===
0x25
/* % */
)
{
code
=
str
.
charCodeAt
(
++
pos
)
}
result
.
ok
=
true
result
.
pos
=
pos
result
.
value
=
str
.
slice
(
start
,
pos
)
return
result
}
function
parseImageSize
(
str
,
pos
,
max
)
{
let
code
const
result
=
{
ok
:
false
,
pos
:
0
,
width
:
''
,
height
:
''
}
if
(
pos
>=
max
)
{
return
result
}
code
=
str
.
charCodeAt
(
pos
)
if
(
code
!==
0x3d
/* = */
)
{
return
result
}
pos
++
// size must follow = without any white spaces as follows
// (1) =300x200
// (2) =300x
// (3) =x200
code
=
str
.
charCodeAt
(
pos
)
if
(
code
!==
0x78
/* x */
&&
(
code
<
0x30
||
code
>
0x39
)
/* [0-9] */
)
{
return
result
}
// parse width
const
resultW
=
parseNextNumber
(
str
,
pos
,
max
)
pos
=
resultW
.
pos
// next charactor must be 'x'
code
=
str
.
charCodeAt
(
pos
)
if
(
code
!==
0x78
/* x */
)
{
return
result
}
pos
++
// parse height
const
resultH
=
parseNextNumber
(
str
,
pos
,
max
)
pos
=
resultH
.
pos
result
.
width
=
resultW
.
value
result
.
height
=
resultH
.
value
result
.
pos
=
pos
result
.
ok
=
true
return
result
}
export
default
(
md
)
=>
{
md
.
inline
.
ruler
.
before
(
'emphasis'
,
'image'
,
renderImSize
)
}
ux/src/components/PageHeader.vue
View file @
80b1cbff
...
...
@@ -116,14 +116,16 @@
:href='siteStore.docsBase + `/editor/${editorStore.editor}`'
target='_blank'
type='a'
)
)
q-tooltip
{{
t
(
`common.actions.viewDocs`
)
}}
q-btn.q-ml-sm.acrylic-btn(
icon='las la-cog'
flat
color='grey'
:aria-label='t(`editor.settings`)'
@click='openEditorSettings'
)
)
q-tooltip
{{
t
(
`editor.settings`
)
}}
template(v-if='editorStore.isActive || editorStore.hasPendingChanges')
q-btn.acrylic-btn.q-ml-sm(
flat
...
...
@@ -139,8 +141,8 @@
flat
icon='las la-check'
color='positive'
label='Create Page
'
aria-label='Create Page
'
:label='t(`editor.createPage`)
'
:aria-label='t(`editor.createPage`)
'
no-caps
@click='createPage'
)
...
...
@@ -149,19 +151,21 @@
flat
icon='las la-check'
color='positive'
label='Save Changes
'
aria-label='Save Changes
'
:label='t(`common.actions.saveChanges`)
'
:aria-label='t(`common.actions.saveChanges`)
'
:disabled='!editorStore.hasPendingChanges'
no-caps
@click='saveChanges'
)
@click.exact='saveChanges(false)'
@click.ctrl.exact='saveChanges(true)'
)
q-tooltip
{{
t
(
`editor.saveAndCloseTip`
)
}}
template(v-else-if='userStore.can(`edit:pages`)')
q-btn.acrylic-btn.q-ml-md(
flat
icon='las la-edit'
color='deep-orange-9'
label='Edit
'
aria-label='Edit
'
:label='t(`common.actions.edit`)
'
:aria-label='t(`common.actions.edit`)
'
no-caps
@click='editPage'
)
...
...
@@ -258,7 +262,7 @@ async function discardChanges () {
$q
.
loading
.
hide
()
}
async
function
saveChanges
()
{
async
function
saveChanges
(
closeAfter
=
false
)
{
if
(
siteStore
.
features
.
reasonForChange
!==
'off'
)
{
$q
.
dialog
({
component
:
defineAsyncComponent
(()
=>
import
(
'../components/PageReasonForChangeDialog.vue'
)),
...
...
@@ -269,14 +273,14 @@ async function saveChanges () {
editorStore
.
$patch
({
reasonForChange
:
reason
})
saveChangesCommit
()
saveChangesCommit
(
closeAfter
)
})
}
else
{
saveChangesCommit
()
saveChangesCommit
(
closeAfter
)
}
}
async
function
saveChangesCommit
()
{
async
function
saveChangesCommit
(
closeAfter
=
false
)
{
$q
.
loading
.
show
()
try
{
await
pageStore
.
pageSave
()
...
...
@@ -284,6 +288,12 @@ async function saveChangesCommit () {
type
:
'positive'
,
message
:
'Page saved successfully.'
})
if
(
closeAfter
)
{
editorStore
.
$patch
({
isActive
:
false
,
editor
:
''
})
}
}
catch
(
err
)
{
$q
.
notify
({
type
:
'negative'
,
...
...
ux/src/css/page-contents.scss
View file @
80b1cbff
.page-contents
{
color
:
#424242
;
font-size
:
1
4
px
;
font-size
:
1
6
px
;
>
*
:first-child
{
margin-top
:
0
;
...
...
@@ -15,7 +15,7 @@
// ---------------------------------
a
{
color
:
$blue
;
color
:
$blue
-8
;
&
.is-internal-link.is-invalid-page
{
color
:
$red-8
;
...
...
@@ -40,7 +40,7 @@
}
@at-root
.body--dark
&
{
color
:
$blue-
2
;
color
:
$blue-
4
;
}
}
...
...
@@ -51,6 +51,7 @@
h1
,
h2
,
h3
,
h4
,
h5
,
h6
{
padding
:
0
;
margin
:
0
;
font-weight
:
400
;
position
:
relative
;
line-height
:
normal
;
...
...
@@ -65,14 +66,11 @@
}
}
P
+
h2
{
margin-top
:
12px
;
}
h1
{
font-size
:
3em
;
font-weight
:
500
;
padding
:
12px
0
;
// color: var(--q-primary);
}
h2
{
font-size
:
2
.4em
;
...
...
@@ -92,6 +90,29 @@
font-size
:
1
.25em
;
}
*
+
h1
{
margin-top
:
.5em
;
padding-top
:
.5em
;
// border-top: 2px solid var(--q-primary);
position
:
relative
;
&
:
:
before
{
position
:
absolute
;
width
:
100%
;
height
:
1px
;
content
:
' '
;
background
:
linear-gradient
(
to
right
,
var
(
--
q-primary
)
,
transparent
);
top
:
0
;
left
:
-16px
;
}
}
*
:not
(
h1
)
+
h2
{
margin-top
:
.5em
;
padding-top
:
.5em
;
border-top
:
1px
dotted
#CCC
;
}
.toc-anchor
{
display
:
none
;
position
:
absolute
;
...
...
@@ -107,12 +128,8 @@
// ---------------------------------
p
{
padding
:
1rem
0
0
0
;
margin
:
0
;
@at-root
.page-contents
>
div
>
p
:first-child
{
padding-top
:
0
;
}
padding
:
0
;
margin
:
.3em
0
1em
0
;
}
// ---------------------------------
...
...
@@ -120,7 +137,7 @@
// ---------------------------------
blockquote
{
padding
:
0
1rem
1rem
1r
em
;
padding
:
1em
1em
.3em
1
em
;
background-color
:
$blue-grey-1
;
border-left
:
55px
solid
$blue-grey-5
;
border-radius
:
.5rem
;
...
...
@@ -239,21 +256,29 @@
// ---------------------------------
ol
,
ul
:not
(
.tabset-tabs
)
{
padding-top
:
1rem
;
width
:
100%
;
li
>
p
{
&
:first-child
{
margin-top
:
0
;
}
&
:last-child
{
margin-bottom
:
0
;
}
}
@at-root
.is-rtl
&
{
padding-left
:
0
;
padding-right
:
1
r
em
;
padding-right
:
1em
;
}
li
>
ul
,
li
>
ol
{
padding-top
:
.5rem
;
padding-left
:
1
r
em
;
padding-left
:
1em
;
@at-root
.is-rtl
&
{
padding-left
:
0
;
padding-right
:
1
r
em
;
padding-right
:
1em
;
}
}
...
...
@@ -413,6 +438,58 @@
}
}
// ---------------------------------
// TASK LISTS
// ---------------------------------
.contains-task-list
{
padding-left
:
1em
;
}
.task-list-item
{
position
:
relative
;
list-style-type
:
none
;
&
-checkbox
[
disabled
]
{
width
:
1
.1rem
;
height
:
1
.1rem
;
top
:
2px
;
position
:
relative
;
margin-right
:
.4em
;
background-color
:
$dark-5
;
border-width
:
0
;
&
:
:
after
{
position
:
absolute
;
left
:
0
;
top
:
0
;
font-family
:
"Material Design Icons"
;
font-size
:
1
.25em
;
font-weight
:
normal
;
content
:
'\F0131'
;
color
:
$grey-10
;
display
:
block
;
border
:
none
;
background-color
:
#FFF
;
line-height
:
1em
;
cursor
:
default
;
@at-root
.body--dark
&
{
color
:
#FFF
;
background-color
:
$dark-6
;
}
}
&
[
checked
]
::after
{
content
:
'\F0C52'
;
}
}
.contains-task-list
{
padding
:
.5rem
0
0
1
.5rem
;
}
}
// ---------------------------------
// CODE
// ---------------------------------
...
...
ux/src/i18n/locales/en.json
View file @
80b1cbff
...
...
@@ -1417,6 +1417,7 @@
"editor.conflict.whatToDo"
:
"What do you want to do?"
,
"editor.conflict.whatToDoLocal"
:
"Use your current local version and ignore the latest changes."
,
"editor.conflict.whatToDoRemote"
:
"Use the remote version (latest) and discard your changes."
,
"editor.createPage"
:
"Create Page"
,
"editor.markup.admonitionDanger"
:
"Danger / Important Admonition"
,
"editor.markup.admonitionInfo"
:
"Info / Note Admonition"
,
"editor.markup.admonitionSuccess"
:
"Tip / Success Admonition"
,
...
...
@@ -1554,6 +1555,7 @@
"editor.save.processing"
:
"Rendering"
,
"editor.save.saved"
:
"Saved"
,
"editor.save.updateSuccess"
:
"Page updated successfully."
,
"editor.saveAndCloseTip"
:
"Ctrl / Cmd + Click to save and close"
,
"editor.select.cannotChange"
:
"This cannot be changed once the page is created."
,
"editor.select.customView"
:
"or create a custom view?"
,
"editor.select.title"
:
"Which editor do you want to use for this page?"
,
...
...
ux/src/pages/Index.vue
View file @
80b1cbff
...
...
@@ -229,12 +229,12 @@ const state = reactive({
const
thumbStyle
=
{
right
:
'2px'
,
borderRadius
:
'5px'
,
backgroundColor
:
'#000'
,
backgroundColor
:
$q
.
dark
.
isActive
?
'#FFF'
:
'#000'
,
width
:
'5px'
,
opacity
:
0.15
}
const
barStyle
=
{
backgroundColor
:
'#FAFAFA'
,
backgroundColor
:
$q
.
dark
.
isActive
?
'#161b22'
:
'#FAFAFA'
,
width
:
'9px'
,
opacity
:
1
}
...
...
ux/src/renderers/markdown.js
View file @
80b1cbff
...
...
@@ -10,9 +10,9 @@ import mdSub from 'markdown-it-sub'
import
mdMark
from
'markdown-it-mark'
import
mdMultiTable
from
'markdown-it-multimd-table'
import
mdFootnote
from
'markdown-it-footnote'
// import mdImsize from 'markdown-it-imsize'
import
katex
from
'katex'
import
underline
from
'./modules/markdown-it-underline'
import
mdUnderline
from
'./modules/markdown-it-underline'
import
mdImsize
from
'./modules/markdown-it-imsize'
import
'katex/dist/contrib/mhchem'
import
twemoji
from
'twemoji'
import
plantuml
from
'./modules/plantuml'
...
...
@@ -52,7 +52,7 @@ export class MarkdownRenderer {
}
else
if
([
'mermaid'
,
'plantuml'
].
includes
(
lang
))
{
return
`<pre class="codeblock-
${
lang
}
"><code>
${
escape
(
str
)}
</code></pre>`
}
else
{
const
highlighted
=
lang
?
hljs
.
highlight
(
str
,
{
language
:
lang
,
ignoreIllegals
:
true
})
:
hljs
.
highlightAuto
(
str
)
const
highlighted
=
lang
?
hljs
.
highlight
(
str
,
{
language
:
lang
,
ignoreIllegals
:
true
})
:
{
value
:
str
}
const
lineCount
=
highlighted
.
value
.
match
(
/
\n
/g
).
length
const
lineNums
=
lineCount
>
1
?
`<span aria-hidden="true" class="line-numbers-rows">
${
times
(
lineCount
,
n
=>
'<span></span>'
).
join
(
''
)}
</span>`
:
''
return
`<pre class="codeblock
${
lineCount
>
1
&&
'line-numbers'
}
"><code class="language-
${
lang
}
">
${
highlighted
.
value
}${
lineNums
}
</code></pre>`
...
...
@@ -71,10 +71,10 @@ export class MarkdownRenderer {
.
use
(
mdSub
)
.
use
(
mdMark
)
.
use
(
mdFootnote
)
//
.use(mdImsize)
.
use
(
mdImsize
)
if
(
config
.
underline
)
{
this
.
md
.
use
(
u
nderline
)
this
.
md
.
use
(
mdU
nderline
)
}
if
(
config
.
mdmultiTable
)
{
...
...
ux/src/renderers/modules/markdown-it-imsize.js
0 → 100644
View file @
80b1cbff
// Adapted from markdown-it-imsize plugin by @tatsy
// Original source https://github.com/tatsy/markdown-it-imsize/blob/master/lib/index.js
function
renderImSize
(
state
,
silent
)
{
let
attrs
let
code
let
label
let
pos
let
ref
let
res
let
title
let
width
=
''
let
height
=
''
let
token
let
tokens
let
start
let
href
=
''
const
oldPos
=
state
.
pos
const
max
=
state
.
posMax
if
(
state
.
src
.
charCodeAt
(
state
.
pos
)
!==
0x21
/* ! */
)
{
return
false
}
if
(
state
.
src
.
charCodeAt
(
state
.
pos
+
1
)
!==
0x5B
/* [ */
)
{
return
false
}
const
labelStart
=
state
.
pos
+
2
const
labelEnd
=
state
.
md
.
helpers
.
parseLinkLabel
(
state
,
state
.
pos
+
1
,
false
)
// parser failed to find ']', so it's not a valid link
if
(
labelEnd
<
0
)
{
return
false
}
pos
=
labelEnd
+
1
if
(
pos
<
max
&&
state
.
src
.
charCodeAt
(
pos
)
===
0x28
/* ( */
)
{
//
// Inline link
//
// [link]( <href> "title" )
// ^^ skipping these spaces
pos
++
for
(;
pos
<
max
;
pos
++
)
{
code
=
state
.
src
.
charCodeAt
(
pos
)
if
(
code
!==
0x20
&&
code
!==
0x0A
)
{
break
}
}
if
(
pos
>=
max
)
{
return
false
}
// [link]( <href> "title" )
// ^^^^^^ parsing link destination
start
=
pos
res
=
state
.
md
.
helpers
.
parseLinkDestination
(
state
.
src
,
pos
,
state
.
posMax
)
if
(
res
.
ok
)
{
href
=
state
.
md
.
normalizeLink
(
res
.
str
)
if
(
state
.
md
.
validateLink
(
href
))
{
pos
=
res
.
pos
}
else
{
href
=
''
}
}
// [link]( <href> "title" )
// ^^ skipping these spaces
start
=
pos
for
(;
pos
<
max
;
pos
++
)
{
code
=
state
.
src
.
charCodeAt
(
pos
)
if
(
code
!==
0x20
&&
code
!==
0x0A
)
{
break
}
}
// [link]( <href> "title" )
// ^^^^^^^ parsing link title
res
=
state
.
md
.
helpers
.
parseLinkTitle
(
state
.
src
,
pos
,
state
.
posMax
)
if
(
pos
<
max
&&
start
!==
pos
&&
res
.
ok
)
{
title
=
res
.
str
pos
=
res
.
pos
// [link]( <href> "title" )
// ^^ skipping these spaces
for
(;
pos
<
max
;
pos
++
)
{
code
=
state
.
src
.
charCodeAt
(
pos
)
if
(
code
!==
0x20
&&
code
!==
0x0A
)
{
break
}
}
}
else
{
title
=
''
}
// [link]( <href> "title" =WxH )
// ^^^^ parsing image size
if
(
pos
-
1
>=
0
)
{
code
=
state
.
src
.
charCodeAt
(
pos
-
1
)
// there must be at least one white spaces
// between previous field and the size
if
(
code
===
0x20
)
{
res
=
parseImageSize
(
state
.
src
,
pos
,
state
.
posMax
)
if
(
res
.
ok
)
{
width
=
res
.
width
height
=
res
.
height
pos
=
res
.
pos
// [link]( <href> "title" =WxH )
// ^^ skipping these spaces
for
(;
pos
<
max
;
pos
++
)
{
code
=
state
.
src
.
charCodeAt
(
pos
)
if
(
code
!==
0x20
&&
code
!==
0x0A
)
{
break
}
}
}
}
}
if
(
pos
>=
max
||
state
.
src
.
charCodeAt
(
pos
)
!==
0x29
/* ) */
)
{
state
.
pos
=
oldPos
return
false
}
pos
++
}
else
{
//
// Link reference
//
if
(
typeof
state
.
env
.
references
===
'undefined'
)
{
return
false
}
// [foo] [bar]
// ^^ optional whitespace (can include newlines)
for
(;
pos
<
max
;
pos
++
)
{
code
=
state
.
src
.
charCodeAt
(
pos
)
if
(
code
!==
0x20
&&
code
!==
0x0A
)
{
break
}
}
if
(
pos
<
max
&&
state
.
src
.
charCodeAt
(
pos
)
===
0x5B
/* [ */
)
{
start
=
pos
+
1
pos
=
state
.
md
.
helpers
.
parseLinkLabel
(
state
,
pos
)
if
(
pos
>=
0
)
{
label
=
state
.
src
.
slice
(
start
,
pos
++
)
}
else
{
pos
=
labelEnd
+
1
}
}
else
{
pos
=
labelEnd
+
1
}
// covers label === '' and label === undefined
// (collapsed reference link and shortcut reference link respectively)
if
(
!
label
)
{
label
=
state
.
src
.
slice
(
labelStart
,
labelEnd
)
}
ref
=
state
.
env
.
references
[
state
.
md
.
utils
.
normalizeReference
(
label
)]
if
(
!
ref
)
{
state
.
pos
=
oldPos
return
false
}
href
=
ref
.
href
title
=
ref
.
title
}
//
// We found the end of the link, and know for a fact it's a valid link;
// so all that's left to do is to call tokenizer.
//
if
(
!
silent
)
{
state
.
pos
=
labelStart
state
.
posMax
=
labelEnd
const
newState
=
new
state
.
md
.
inline
.
State
(
state
.
src
.
slice
(
labelStart
,
labelEnd
),
state
.
md
,
state
.
env
,
tokens
=
[]
)
newState
.
md
.
inline
.
tokenize
(
newState
)
token
=
state
.
push
(
'image'
,
'img'
,
0
)
token
.
attrs
=
attrs
=
[[
'src'
,
href
],
[
'alt'
,
''
]]
token
.
children
=
tokens
if
(
title
)
{
attrs
.
push
([
'title'
,
title
])
}
if
(
width
!==
''
)
{
attrs
.
push
([
'width'
,
width
])
}
if
(
height
!==
''
)
{
attrs
.
push
([
'height'
,
height
])
}
}
state
.
pos
=
pos
state
.
posMax
=
max
return
true
}
function
parseNextNumber
(
str
,
pos
,
max
)
{
let
code
const
start
=
pos
const
result
=
{
ok
:
false
,
pos
,
value
:
''
}
code
=
str
.
charCodeAt
(
pos
)
while
((
pos
<
max
&&
(
code
>=
0x30
/* 0 */
&&
code
<=
0x39
/* 9 */
))
||
code
===
0x25
/* % */
)
{
code
=
str
.
charCodeAt
(
++
pos
)
}
result
.
ok
=
true
result
.
pos
=
pos
result
.
value
=
str
.
slice
(
start
,
pos
)
return
result
}
function
parseImageSize
(
str
,
pos
,
max
)
{
let
code
const
result
=
{
ok
:
false
,
pos
:
0
,
width
:
''
,
height
:
''
}
if
(
pos
>=
max
)
{
return
result
}
code
=
str
.
charCodeAt
(
pos
)
if
(
code
!==
0x3d
/* = */
)
{
return
result
}
pos
++
// size must follow = without any white spaces as follows
// (1) =300x200
// (2) =300x
// (3) =x200
code
=
str
.
charCodeAt
(
pos
)
if
(
code
!==
0x78
/* x */
&&
(
code
<
0x30
||
code
>
0x39
)
/* [0-9] */
)
{
return
result
}
// parse width
const
resultW
=
parseNextNumber
(
str
,
pos
,
max
)
pos
=
resultW
.
pos
// next charactor must be 'x'
code
=
str
.
charCodeAt
(
pos
)
if
(
code
!==
0x78
/* x */
)
{
return
result
}
pos
++
// parse height
const
resultH
=
parseNextNumber
(
str
,
pos
,
max
)
pos
=
resultH
.
pos
result
.
width
=
resultW
.
value
result
.
height
=
resultH
.
value
result
.
pos
=
pos
result
.
ok
=
true
return
result
}
export
default
(
md
)
=>
{
md
.
inline
.
ruler
.
before
(
'emphasis'
,
'image'
,
renderImSize
)
}
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment