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