New HyperMD implementation with custom behaviour.

This commit is contained in:
Peaceultime 2025-02-25 15:55:30 +01:00
parent 6abc467a43
commit 0b97e9a295
18 changed files with 1545 additions and 407 deletions

918
bun.lock

File diff suppressed because it is too large Load Diff

View File

@ -1,107 +1,157 @@
<script lang="ts">
const External = Annotation.define<boolean>();
import type { Editor, EditorConfiguration, EditorChange } from 'codemirror';
import { changeEnd } from 'codemirror';
import { fromTextArea } from 'hypermd';
import '#shared/hypermd.extend';
function onChange(cm: Editor, change: EditorChange)
{
if (changeEnd(change).line == cm.lastLine())
updateBottomMargin(cm);
}
function updateBottomMargin(cm: Editor)
{
let padding = "";
if (cm.lineCount() > 1)
{
//@ts-ignore
let totalH = cm.display.scroller.clientHeight - 30, lastLineH = cm.getLineHandle(cm.lastLine()).height;
padding = (totalH / 2 - lastLineH) + "px";
}
if (cm.state.scrollPastEndPadding != padding)
{
cm.state.scrollPastEndPadding = padding;
cm.display.lineSpace.parentNode.style.paddingBottom = padding;
}
}
</script>
<script setup lang="ts">
import { dropCursor, crosshairCursor, keymap, EditorView, ViewUpdate, placeholder as placeholderExtension } from '@codemirror/view';
import { Annotation, EditorState } from '@codemirror/state';
import { indentOnInput, syntaxHighlighting, defaultHighlightStyle, bracketMatching, foldKeymap } from '@codemirror/language';
import { history, defaultKeymap, historyKeymap } from '@codemirror/commands';
import { search, searchKeymap } from '@codemirror/search';
import { closeBrackets, closeBracketsKeymap, completionKeymap } from '@codemirror/autocomplete';
import { lintKeymap } from '@codemirror/lint';
const editor = useTemplateRef('editor');
const view = ref<EditorView>();
const state = ref<EditorState>();
const { placeholder, autofocus = false } = defineProps<{
const { placeholder, autofocus = false, gutters = true, format = 'd-any' } = defineProps<{
placeholder?: string
autofocus?: boolean
gutters?: boolean
format?: 'hypermd' | 'd-any'
}>();
const model = defineModel<string>();
const editor = ref<Editor>();
const input = useTemplateRef('input');
onMounted(() => {
if(editor.value)
if(input.value)
{
state.value = EditorState.create({
doc: model.value,
extensions: [
history(),
search(),
dropCursor(),
EditorState.allowMultipleSelections.of(true),
indentOnInput(),
syntaxHighlighting(defaultHighlightStyle, { fallback: true }),
bracketMatching(),
closeBrackets(),
crosshairCursor(),
placeholderExtension(placeholder ?? ''),
EditorView.lineWrapping,
keymap.of([
...closeBracketsKeymap,
...defaultKeymap,
...searchKeymap,
...historyKeymap,
...foldKeymap,
...completionKeymap,
...lintKeymap
]),
EditorView.updateListener.of((viewUpdate: ViewUpdate) => {
if (viewUpdate.docChanged && !viewUpdate.transactions.some(tr => tr.annotation(External)))
{
model.value = viewUpdate.state.doc.toString();
const e = editor.value = fromTextArea(input.value, {
mode: {
name: format,
hashtag: true,
toc: false,
math: false,
orgModeMarkup: false,
tokenTypeOverrides: {
hr: "line-HyperMD-hr line-background-HyperMD-hr-bg hr",
list1: "list-1",
list2: "list-2",
list3: "list-3",
code: "inline-code",
hashtag: "hashtag meta"
}
}),
EditorView.contentAttributes.of({spellcheck: "true"}),
]
});
view.value = new EditorView({
state: state.value,
parent: editor.value,
});
},
spellcheck: true,
autofocus: autofocus,
lineNumbers: false,
showCursorWhenSelecting: true,
indentUnit: 4,
autoCloseBrackets: true,
foldGutter: gutters,
theme: 'custom'
} as EditorConfiguration);
if(autofocus)
{
view.value.focus();
}
}
})
onBeforeUnmount(() => {
if (view.value) {
view.value?.destroy()
view.value = undefined
e.setValue(model.value ?? '');
updateBottomMargin(e);
e.on('change', onChange);
e.on('change', (cm: Editor, change: EditorChange) => model.value = cm.getValue());
e.on('refresh', updateBottomMargin);
}
});
watchEffect(() => {
if (model.value === void 0) {
return;
}
const currentValue = view.value ? view.value.state.doc.toString() : "";
if (view.value && model.value !== currentValue) {
view.value.dispatch({
changes: { from: 0, to: currentValue.length, insert: model.value || "" },
annotations: [External.of(true)],
});
const value = editor.value?.getValue();
if (editor.value && model.value !== value) {
editor.value.setValue(model.value ?? '');
editor.value.clearHistory();
}
});
</script>
<template>
<div ref="editor" class="flex flex-1 w-full justify-stretch items-stretch border border-light-35 dark:border-dark-35 caret-light-100 dark:caret-dark-100 py-2 px-1.5 font-sans text-base" />
<div :class="{ 'cancel-gutters': !gutters }" class="flex flex-1 w-full justify-stretch items-stretch !font-sans !text-base">
<textarea ref="input" class="hidden"></textarea>
</div>
</template>
<style>
.cm-editor
.CodeMirror
{
@apply bg-transparent;
@apply flex-1;
@apply flex-1 h-full;
@apply font-sans;
@apply text-light-100 dark:text-dark-100;
}
.cm-editor .cm-content
.cancel-gutters .CodeMirror-gutters
{
@apply caret-light-100;
@apply dark:caret-dark-100;
@apply hidden;
}
.cancel-gutters .CodeMirror-sizer
{
@apply ms-2;
}
.CodeMirror-gutters
{
@apply bg-transparent;
@apply border-transparent;
}
.CodeMirror-gutter-wrapper
{
@apply absolute top-0 bottom-0;
@apply flex justify-center items-center;
}
.CodeMirror-foldmarker
{
@apply text-light-100;
@apply dark:text-dark-100;
@apply ps-3;
text-shadow: none;
}
.hmd-inactive-line .cm-formatting-header, .hmd-inactive-line .cm-formatting-link, .hmd-inactive-line .cm-link-has-alias, .hmd-inactive-line .cm-link-alias-pipe
{
@apply hidden;
}
.CodeMirror-line
{
@apply text-base;
}
.CodeMirror-cursor
{
@apply border-light-100 dark:border-dark-100;
}
.CodeMirror-selected
{
@apply bg-light-35 dark:bg-dark-35;
}
.HyperMD-list-line-1 {
@apply !ps-0;
}
.HyperMD-list-line-2 {
@apply !ps-6;
}
.HyperMD-list-line-3 {
@apply !ps-12;
}
</style>

View File

@ -25,8 +25,8 @@
<span @mousedown.left="(e) => resizeNode(e, 1, 0, -1, 1)" id="sw" class="cursor-sw-resize absolute -bottom-4 -left-4 w-8 h-8"></span> <!-- South West -->
</div>
</div>
<div v-else style="outline-style: solid;" :class="[style.border, style.outline, { '!outline-4': focusing }]" class="outline-0 transition-[outline-width] border-2 bg-light-20 dark:bg-dark-20 overflow-hidden contain-strict w-full h-full flex" >
<Editor v-model="node.text" autofocus />
<div v-else style="outline-style: solid;" :class="[style.border, style.outline, { '!outline-4': focusing }]" class="outline-0 transition-[outline-width] border-2 bg-light-20 dark:bg-dark-20 overflow-hidden contain-strict w-full h-full flex py-2" >
<Editor v-model="node.text" autofocus :gutters="false"/>
</div>
<div v-if="!editing && node.type === 'group' && node.label !== undefined" @click.left="(e) => selectNode(e)" @dblclick.left="(e) => editNode(e)" :class="style.border" style="max-width: 100%; font-size: calc(18px * var(--zoom-multiplier))" class="origin-bottom-left tracking-wider border-4 truncate inline-block text-light-100 dark:text-dark-100 absolute bottom-[100%] mb-2 px-2 py-1 font-thin">{{ node.label }}</div>
<input v-else-if="editing && node.type === 'group'" v-model="node.label" @click="e => e.stopImmediatePropagation()" v-autofocus :class="[style.border, style.outline]" style="max-width: 100%; font-size: calc(18px * var(--zoom-multiplier))" class="origin-bottom-left tracking-wider border-4 truncate inline-block text-light-100 dark:text-dark-100 absolute bottom-[100%] appearance-none bg-transparent outline-4 mb-2 px-2 py-1 font-thin min-w-4" />

View File

@ -28,3 +28,9 @@ const { hash, pathname } = parseURL(href);
const { content } = useContent();
const overview = computed(() => content.value.find(e => e.path === pathname));
</script>
<style>
.cm-link {
@apply text-accent-blue inline-flex items-center cursor-pointer hover:text-opacity-85;
}
</style>

View File

@ -3,3 +3,26 @@
<slot />
</blockquote>
</template>
<style>
.HyperMD-quote
{
@apply before:hidden;
}
.HyperMD-quote.hmd-inactive-line
{
@apply before:block empty:before:!hidden !pb-2 !ps-4 !relative before:!absolute before:!-top-1 before:!-bottom-1 before:!left-0 before:!w-1 before:!bg-none before:!bg-light-30 dark:before:!bg-dark-30;
}
.HyperMD-quote.HyperMD-header
{
@apply before:!hidden;
}
.hmd-inactive-line .cm-formatting-quote
{
@apply !hidden;
}
.cm-quote
{
@apply text-light-100 dark:text-dark-100;
}
</style>

View File

@ -1,3 +1,10 @@
<template>
<code><slot /></code>
</template>
<style>
.cm-inline-code
{
@apply !border-none !bg-transparent !text-light-100 dark:!text-dark-100 !p-0;
}
</style>

View File

@ -1,5 +1,5 @@
<template>
<h1 :id="parseId(id)" class="text-5xl font-thin mt-3 mb-8 first:pt-0 pt-2 relative lg:right-8 sm:right-4 right-2">
<h1 :id="parseId(id)" class="text-5xl font-thin mt-3 mb-8 first:pt-0 pt-2">
<slot />
</h1>
</template>
@ -8,3 +8,14 @@
import { parseId } from '#shared/general.util';
const props = defineProps<{ id?: string }>()
</script>
<style>
.HyperMD-header-1
{
@apply text-5xl pt-4 pb-2 after:hidden;
}
.HyperMD-header-1 .cm-header
{
@apply font-thin;
}
</style>

View File

@ -1,5 +1,5 @@
<template>
<h2 :id="parseId(id)" class="text-4xl font-semibold mt-3 mb-6 ms-1 first:pt-0 pt-2 relative sm:right-4 right-2">
<h2 :id="parseId(id)" class="text-4xl font-semibold mt-3 mb-6 ms-1 first:pt-0 pt-2">
<slot />
</h2>
</template>
@ -10,3 +10,14 @@ const props = defineProps<{ id?: string }>()
const generate = computed(() => props.id)
</script>
<style>
.HyperMD-header-2
{
@apply !text-4xl !pt-4 !pb-2 !ps-1 leading-loose after:hidden;
}
.HyperMD-header-2 .cm-header
{
@apply font-semibold;
}
</style>

View File

@ -10,3 +10,14 @@ const props = defineProps<{ id?: string }>()
const generate = computed(() => props.id)
</script>
<style>
.HyperMD-header-3
{
@apply !text-2xl !font-bold !pt-1 after:!hidden;
}
.HyperMD-header-3 .cm-header
{
@apply font-bold;
}
</style>

View File

@ -8,3 +8,15 @@
import { parseId } from '#shared/general.util';
const props = defineProps<{ id?: string }>()
</script>
<style>
.HyperMD-header-4
{
@apply !text-xl font-semibold pt-1 after:hidden;
font-variant: small-caps;
}
.HyperMD-header-4 .cm-header
{
@apply font-semibold;
}
</style>

View File

@ -1,3 +1,10 @@
<template>
<Separator class="border-b border-light-35 dark:border-dark-35 m-4" />
</template>
<style>
.HyperMD-hr
{
@apply bg-light-35 dark:bg-dark-35 h-px;
}
</style>

View File

@ -1,3 +1,22 @@
<template>
<li class="before:absolute before:top-2 before:left-0 before:inline-block before:w-2 before:h-2 before:rounded before:bg-light-40 dark:before:bg-dark-40 relative ps-4"><slot /></li>
</template>
<style>
.HyperMD-list-line
{
@apply !py-1;
}
.HyperMD-list-line.hmd-inactive-line > span
{
@apply before:absolute before:top-2 before:left-0 before:inline-block before:w-2 before:h-2 before:rounded before:bg-light-40 dark:before:bg-dark-40 relative ps-4;
}
.hmd-inactive-line .cm-formatting-list
{
@apply hidden;
}
.cm-hmd-list-indent
{
@apply !hidden;
}
</style>

View File

@ -3,3 +3,14 @@
<slot></slot>
</span>
</template>
<style>
.cm-hashtag.cm-hashtag-begin
{
@apply bg-accent-blue bg-opacity-10 text-accent-blue text-sm pb-0.5 ps-1 rounded-l-[12px] border border-r-0 border-accent-blue border-opacity-30;
}
.cm-hashtag.cm-hashtag-end
{
@apply bg-accent-blue bg-opacity-10 text-accent-blue text-sm pb-0.5 pe-1 rounded-r-[12px] !rounded-se-none border border-l-0 border-accent-blue border-opacity-30;
}
</style>

BIN
db.sqlite

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -7,30 +7,34 @@
"dev": "NODE_TLS_REJECT_UNAUTHORIZED=0 bunx --bun nuxi dev"
},
"dependencies": {
"@atlaskit/pragmatic-drag-and-drop": "^1.4.0",
"@atlaskit/pragmatic-drag-and-drop": "^1.5.0",
"@atlaskit/pragmatic-drag-and-drop-hitbox": "^1.0.3",
"@iconify/vue": "^4.3.0",
"@lezer/highlight": "^1.2.1",
"@markdoc/markdoc": "^0.5.1",
"@nuxtjs/color-mode": "^3.5.2",
"@nuxtjs/sitemap": "^7.2.3",
"@nuxtjs/sitemap": "^7.2.5",
"@nuxtjs/tailwindcss": "^6.13.1",
"@types/codemirror": "^5.60.15",
"@vueuse/gesture": "^2.0.0",
"@vueuse/math": "^12.5.0",
"@vueuse/nuxt": "^12.5.0",
"codemirror": "^6.0.1",
"drizzle-orm": "^0.38.4",
"@vueuse/math": "^12.7.0",
"@vueuse/nuxt": "^12.7.0",
"codemirror": "5.65.18",
"drizzle-orm": "^0.39.3",
"hast": "^1.0.0",
"hast-util-heading": "^3.0.0",
"hast-util-heading-rank": "^3.0.0",
"hypermd": "^0.3.11",
"lodash.capitalize": "^4.2.1",
"mdast-util-find-and-replace": "^3.0.2",
"nodemailer": "^6.10.0",
"nuxt": "3.15.1",
"nuxt": "3.15.4",
"nuxt-security": "^2.1.5",
"radix-vue": "^1.9.12",
"radix-vue": "^1.9.15",
"rehype-raw": "^7.0.0",
"remark-breaks": "^4.0.0",
"remark-frontmatter": "^5.0.0",
"remark-gfm": "^4.0.0",
"remark-gfm": "^4.0.1",
"remark-ofm": "link:remark-ofm",
"remark-parse": "^11.0.0",
"remark-rehype": "^11.1.1",
@ -40,16 +44,16 @@
"unist-util-visit": "^5.0.0",
"vue": "^3.5.13",
"vue-router": "^4.5.0",
"zod": "^3.24.1"
"zod": "^3.24.2"
},
"devDependencies": {
"@types/bun": "^1.2.0",
"@types/bun": "^1.2.2",
"@types/lodash.capitalize": "^4.2.9",
"@types/nodemailer": "^6.4.17",
"@types/unist": "^3.0.3",
"better-sqlite3": "^11.8.1",
"bun-types": "^1.2.0",
"drizzle-kit": "^0.30.2",
"bun-types": "^1.2.2",
"drizzle-kit": "^0.30.4",
"mdast-util-to-string": "^4.0.0",
"rehype-stringify": "^10.0.1"
}

670
shared/hypermd.extend.ts Normal file
View File

@ -0,0 +1,670 @@
import { defineMode, type Mode, getMode, StringStream, startState } from "codemirror";
import type { MarkdownState } from "hypermd/mode/hypermd";
const EN = /^(?:[*\-+]|^[0-9]+([.)]))\s+/, SN = /^(?:(?:(?:aaas?|about|acap|adiumxtra|af[ps]|aim|apt|attachment|aw|beshare|bitcoin|bolo|callto|cap|chrome(?:-extension)?|cid|coap|com-eventbrite-attendee|content|crid|cvs|data|dav|dict|dlna-(?:playcontainer|playsingle)|dns|doi|dtn|dvb|ed2k|facetime|feed|file|finger|fish|ftp|geo|gg|git|gizmoproject|go|gopher|gtalk|h323|hcp|https?|iax|icap|icon|im|imap|info|ipn|ipp|irc[6s]?|iris(?:\.beep|\.lwz|\.xpc|\.xpcs)?|itms|jar|javascript|jms|keyparc|lastfm|ldaps?|magnet|mailto|maps|market|message|mid|mms|ms-help|msnim|msrps?|mtqp|mumble|mupdate|mvn|news|nfs|nih?|nntp|notes|oid|opaquelocktoken|palm|paparazzi|platform|pop|pres|proxy|psyc|query|res(?:ource)?|rmi|rsync|rtmp|rtsp|secondlife|service|session|sftp|sgn|shttp|sieve|sips?|skype|sm[bs]|snmp|soap\.beeps?|soldat|spotify|ssh|steam|svn|tag|teamspeak|tel(?:net)?|tftp|things|thismessage|tip|tn3270|tv|udp|unreal|urn|ut2004|vemmi|ventrilo|view-source|webcal|wss?|wtai|wyciwyg|xcon(?:-userid)?|xfire|xmlrpc\.beeps?|xmpp|xri|ymsgr|z39\.50[rs]?):(?:\/{1,3}|[a-z0-9%])|www\d{0,3}[.]|[a-z0-9.\-]+[.][a-z]{2,4}\/)(?:[^\s()<>]|\([^\s()<>]*\))+(?:\([^\s()<>]*\)|[^\s`*!()\[\]{};:'".,<>?«»“”‘’]))/i, xN = /^(?:(?:[^<>()[\]\\.,;:\s@\"`]+(?:\.[^<>()[\]\\.,;:\s@\"]+)*)|(?:\".+\"))@(?:(?:\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(?:(?:[a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))\b/, TN = /^(?:[^\u2000-\u206F\u2E00-\u2E7F'!"#$%&()*+,.:;<=>?@^`{|}~\[\]\\\s])+/;
const PN = /^\s*[^\|].*?\|.*[^|]\s*$/, IN = /^\s*[^\|].*\|/, FN = /^\|(?:[^|]+\|)+?\s*$/, ON = /^\|/, BN = /^\s*-+\s*:\s*$/, NN = /^\s*:\s*-+\s*$/, RN = /^\s*:\s*-+\s*:\s*$/, HN = /^\s*-+\s*$/;
const readSideRegex = /(?:([\u200F\p{sc=Arabic}\p{sc=Hebrew}\p{sc=Syriac}\p{sc=Thaana}])|([\u200E\p{sc=Armenian}\p{sc=Bengali}\p{sc=Bopomofo}\p{sc=Braille}\p{sc=Buhid}\p{sc=Canadian_Aboriginal}\p{sc=Cherokee}\p{sc=Cyrillic}\p{sc=Devanagari}\p{sc=Ethiopic}\p{sc=Georgian}\p{sc=Greek}\p{sc=Gujarati}\p{sc=Gurmukhi}\p{sc=Han}\p{sc=Hangul}\p{sc=Hanunoo}\p{sc=Hiragana}\p{sc=Kannada}\p{sc=Katakana}\p{sc=Khmer}\p{sc=Lao}\p{sc=Latin}\p{sc=Limbu}\p{sc=Malayalam}\p{sc=Mongolian}\p{sc=Myanmar}\p{sc=Ogham}\p{sc=Oriya}\p{sc=Runic}\p{sc=Sinhala}\p{sc=Tagalog}\p{sc=Tagbanwa}\p{sc=Tamil}\p{sc=Telugu}\p{sc=Thai}\p{sc=Tibetan}\p{sc=Yi}]))/u;
const readSide = function(e: string) {
var t = e.match(readSideRegex);
if (t) {
if (t[1])
return "rtl";
if (t[2])
return "ltr"
}
return "auto"
}
const isLetter = (e: string) => /[a-z]/i.test(e);
const clearSubstringWords = (str: string, substring: string) => str.replace(new RegExp("\\s?[^\\s]*".concat(substring, "[^\\s]*","g")), "");
const enum HashtagType {
NONE = 0,
NORMAL = 1,
WITH_SPACE = 2
}
const enum TableType {
NONE = 0,
SIMPLE = 1,
NORMAL = 2
}
const enum NextMaybe {
NONE = 0,
FRONT_MATTER = 1,
FRONT_MATTER_END = 2
}
const enum LinkType {
NONE = 0,
BARELINK = 1,
FOOTREF = 2,
NORMAL = 3,
FOOTNOTE = 4,
MAYBE_FOOTNOTE_URL = 5,
MAYBE_FOOTNOTE_URL_TITLE = 6,
BARELINK2 = 7,
FOOTREF2 = 8,
INTERNAL_LINK = 9,
EMBED = 10,
}
const CLASSES: Record<number, string> = {
1: "hmd-barelink",
7: "hmd-barelink2",
2: "hmd-barelink footref",
4: "hmd-barelink hmd-footnote line-HyperMD-footnote",
8: "hmd-footref2",
9: "hmd-internal-link",
10: "hmd-internal-link hmd-embed",
}
export declare type TokenFunc = (stream: CodeMirror.StringStream, state: HyperMDState) => string;
export declare type InnerModeExitChecker = (stream: CodeMirror.StringStream, state: HyperMDState) => {
endPos?: number;
skipInnerMode?: boolean;
style?: string;
} | null;
interface HyperMDState extends MarkdownState {
hmdTableRTL: boolean;
highlight: boolean;
hasAlias: boolean;
isAlias: boolean;
comment: boolean;
mathed: boolean;
internalEmbed: any;
internalLink: any;
inFootnote: boolean;
inlineFootnote: boolean;
wasHeading: boolean;
isHeading: boolean;
hmdTable: TableType;
hmdTableID: string | null;
hmdTableColumns: string[];
hmdTableCol: number;
hmdTableRow: number;
hmdOverride: TokenFunc | null;
hmdHashtag: HashtagType | boolean;
hmdInnerStyle: string;
hmdInnerExitChecker: InnerModeExitChecker | null;
hmdInnerMode: CodeMirror.Mode<any> | null;
hmdInnerState: any;
hmdLinkType: LinkType;
hmdNextMaybe: NextMaybe;
hmdNextState: HyperMDState | null;
hmdNextStyle: string | null;
hmdNextPos: number | null;
}
function resetTable(state: HyperMDState)
{
state.hmdTable = TableType.NONE,
state.hmdTableRTL = !1,
state.hmdTableColumns = [],
state.hmdTableID = null,
state.hmdTableCol = state.hmdTableRow = 0
}
defineMode('d-any', function(cm, config) {
const markdownMode: Mode<MarkdownState> = getMode(cm, { ...config, name: 'markdown' }) as any;
const mode: Mode<HyperMDState> = getMode(cm, { ...config, name: 'hypermd' }) as any;
config = Object.assign({}, {
front_matter: !0,
math: !0,
table: !0,
toc: !0,
orgModeMarkup: !0,
hashtag: !0,
fencedCodeBlockHighlighting: !0,
highlightFormatting: !0,
taskLists: !0,
strikethrough: !0,
emoji: !1,
highlight: !0,
headers: !0,
blockquotes: !0,
indentedCode: !0,
lists: !0,
hr: !0,
blockId: !0
}, config)
function modeOverride(stream: CodeMirror.StringStream, state: HyperMDState): string {
const exit = state.hmdInnerExitChecker!(stream, state);
const extraStyle = state.hmdInnerStyle;
let ans = (!exit || !exit.skipInnerMode) && state.hmdInnerMode!.token(stream, state.hmdInnerState) || "";
if (extraStyle) ans += " " + extraStyle;
if (exit) {
if (exit.style) ans += " " + exit.style;
if (exit.endPos) stream.pos = exit.endPos;
state.hmdInnerExitChecker = null;
state.hmdInnerMode = null;
state.hmdInnerState = null;
state.hmdOverride = null;
}
return ans.trim();
}
function advanceMarkdown(stream: CodeMirror.StringStream, state: HyperMDState) {
if (stream.eol() || state.hmdNextState) return false;
let oldStart = stream.start;
let oldPos = stream.pos;
stream.start = oldPos;
let newState = { ...state };
let newStyle = mode.token(stream, newState);
state.hmdNextPos = stream.pos;
state.hmdNextState = newState;
state.hmdNextStyle = newStyle;
stream.start = oldStart;
stream.pos = oldPos;
return true;
}
function createDummyMode(endTag: string): CodeMirror.Mode<void> {
return {
token(stream) {
let endTagSince = stream.string.indexOf(endTag, stream.start);
if (endTagSince === -1) stream.skipToEnd(); // endTag not in this line
else if (endTagSince === 0) stream.pos += endTag.length; // current token is endTag
else {
stream.pos = endTagSince;
if (stream.string.charAt(endTagSince - 1) === "\\") stream.pos++;
}
return null;
}
}
}
function createSimpleInnerModeExitChecker(endTag: string, retInfo?: ReturnType<InnerModeExitChecker>): InnerModeExitChecker {
if (!retInfo) retInfo = {};
return function (stream, state) {
if (stream.string.substring(stream.start, stream.start + endTag.length) === endTag) {
retInfo.endPos = stream.start + endTag.length;
return retInfo;
}
return null;
}
}
interface BasicInnerModeOptions {
skipFirstToken?: boolean
style?: string
}
interface InnerModeOptions1 extends BasicInnerModeOptions {
fallbackMode: () => CodeMirror.Mode<any>
exitChecker: InnerModeExitChecker
}
interface InnerModeOptions2 extends BasicInnerModeOptions {
endTag: string
}
type InnerModeOptions = InnerModeOptions1 | InnerModeOptions2
/**
* switch to another mode
*
* After entering a mode, you can then set `hmdInnerExitStyle` and `hmdInnerState` of `state`
*
* @returns if `skipFirstToken` not set, returns `innerMode.token(stream, innerState)`, meanwhile, stream advances
*/
function enterMode(stream: StringStream, state: HyperMDState, mode: string | CodeMirror.Mode<any> | null, opt: InnerModeOptions): string {
if (typeof mode === "string") mode = getMode(cm, mode);
if (!mode || mode["name"] === "null") {
if ('endTag' in opt) mode = createDummyMode(opt.endTag);
else if(typeof opt.fallbackMode === 'function') mode = opt.fallbackMode();
if (!mode) throw new Error("no mode");
}
state.hmdInnerExitChecker = ('endTag' in opt) ? createSimpleInnerModeExitChecker(opt.endTag) : opt.exitChecker;
state.hmdInnerStyle = opt.style ?? '';
state.hmdInnerMode = mode;
state.hmdOverride = modeOverride;
state.hmdInnerState = startState(mode);
let ans = opt.style || "";
if (!opt.skipFirstToken)
ans += " " + mode.token(stream, state.hmdInnerState);
return ans.trim();
}
const i: Record<string, any> = {
htmlBlock: null,
block: null
};
return {
name: 'd-any',
...mode,
token(stream, _state) {
const state = _state as HyperMDState;
stream.tabSize = 4;
if (state.hmdOverride)
return state.hmdOverride(stream, state);
if (state.hmdTable && " " === stream.peek())
{
if ("|" === stream.string[stream.pos - 1] && "\\" !== stream.string[stream.pos - 2])
return stream.match(/^ +/), "";
if (stream.match(/^ +\|/))
return stream.backUp(1), "";
}
if (state.hmdNextMaybe === NextMaybe.FRONT_MATTER)
{
if ("---" === stream.string)
return state.hmdNextMaybe = NextMaybe.FRONT_MATTER_END, enterMode(stream, state, "yaml", {
style: "hmd-frontmatter",
fallbackMode: function() {
return createDummyMode("---");
},
exitChecker: function(e, stream) {
return e.string.startsWith("---") && "" === e.string.substring(3).trim() ? (stream.hmdNextMaybe = NextMaybe.NONE,
{
endPos: e.string.length
}) : null;
}
});
state.hmdNextMaybe = NextMaybe.NONE;
}
let a = state.f === i.htmlBlock, c = -1 === state.code, u = state.quote, h = 0 === stream.start;
h && (state.inFootnote && state.hmdLinkType === LinkType.MAYBE_FOOTNOTE_URL || (state.hmdLinkType = LinkType.NONE),
state.inlineFootnote = !1,
state.wasHeading = state.isHeading,
state.isHeading = !1,
!state.code || 1 !== state.code && 2 !== state.code || (state.code = 0));
let d, p, f = state.linkText, m = state.linkHref, v = !(c || a), g = v && !(state.code || state.indentedCode || state.linkHref), y = "", b = !1, w = -1, k = !1;
if (v)
{
if (g && "\\" === stream.peek() && (k = !0), state.list && !state.header && "#" === stream.peek() && /^\s*[*\-+]\s$/.test(stream.string.substring(0, stream.pos)))
{
const C = stream.match(/^(#+)(?: |$)/, !0);
if (C)
{
const M = C[1].length;
return state.header = M, "formatting formatting-header formatting-header-" + M + " header header-" + M;
}
}
if (config.math && g && "$" === stream.peek() && (state.hmdLinkType === LinkType.NONE || state.hmdLinkType === LinkType.MAYBE_FOOTNOTE_URL) && !state.internalLink && !state.internalEmbed)
{
let E: string[] | null = stream.match(/^(\$)[^\s$]/, !1), S = stream.match(/^(\${2})/, !1), x = E ? "$" : "$$";
if (E)
{
let I = stream.string.slice(stream.pos + 1).match(/[^\\]\$(.|$)/)
if(!I || !I[0].match(/^[^\s\\]\$([^0-9]|$)/))
E = null;
}
let T = !1;
if (E || S)
{
if (0 !== stream.pos || state.mathed)
{
let D = "math";
state.quote && (D += " line-HyperMD-quote line-HyperMD-quote-" + state.quote + " line-HyperMD-quote-lazy");
const A = getMode(cm, {
name: "stex"
}), L = "stex" !== A.name;
return y += enterMode(stream, state, A, {
style: D,
skipFirstToken: L,
fallbackMode: function() {
return createDummyMode(x);
},
exitChecker: function(e, stream) {
let n = e.start, i = e.string, r = "formatting formatting-math formatting-math-end math-";
return stream.hmdTable && "|" === i[n] ? {
endPos: n,
style: r
} : i.substring(n, n + x.length) === x ? {
endPos: n + x.length,
style: r
} : null;
}
}),
E ? (L && (stream.pos += E[1].length),
y += " formatting formatting-math formatting-math-begin") : (L && (stream.pos += S[1].length),
y += " formatting formatting-math formatting-math-begin math-block")
}
T = !0, w = 0;
}
state.mathed = T;
}
if (g) {
state.internalLink ? (state.hmdLinkType = LinkType.INTERNAL_LINK,
state.internalLink = !1) : state.internalEmbed && (state.hmdLinkType = LinkType.EMBED,
state.internalEmbed = !1);
let P = state.hmdLinkType === LinkType.INTERNAL_LINK || state.hmdLinkType === LinkType.EMBED;
if (P)
if ("|" === stream.peek())
state.isAlias = !0,
w = stream.pos + 1,
y += " link-alias-pipe";
else if ("]" === stream.peek() && stream.match("]]", !1))
state.hmdLinkType = LinkType.NONE,
state.linkText = !1,
state.isAlias = !1,
state.hasAlias = !1,
w = stream.pos + 2,
y += " formatting-link formatting-link-end";
else {
b = !0,
state.isAlias && (y += " link-alias"),
state.hasAlias && !state.isAlias && (y += " link-has-alias");
let I = stream.match(/^([^|\]]*?)/, !1);
w = stream.pos + Math.max(1, I[0].length)
}
else if("!" === stream.peek() || "[" === stream.peek())
{
const I = stream.match(/^(!?\[\[)(.*?)]]/, !1)
if(I)
"!" === I[1].charAt(0) ? (y += " formatting-link formatting-link-start formatting-embed", state.internalEmbed = !0) : (y += " formatting-link formatting-link-start", state.internalLink = !0), w = stream.pos + I[1].length, state.hasAlias = I[2].includes("|");
}
if (state.hmdLinkType === LinkType.FOOTREF)
if (b = !0,
"]" === stream.peek())
state.hmdLinkType = LinkType.NONE,
w = stream.pos + 1,
y += " formatting formatting-link formatting-link-end " + CLASSES[LinkType.FOOTREF];
else {
let I = stream.match(/^([^\]]*?)/, !1);
w = stream.pos + Math.max(1, I[0].length)
}
else
{
const I = stream.peek() === "[" && stream.match(/^\[\^([^\]\s]*?)\](:?)/, false);
if(I && (h || I[2]))
{
stream.match("[^"),
y += " formatting formatting-link formatting-link-start",
state.hmdLinkType = LinkType.FOOTREF,
w = stream.pos;
}
}
if (config.blockId && "^" === stream.peek() && stream.match(/^\^([a-zA-Z0-9\-]+)$/))
return y += " blockid";
!state.inlineFootnote && "^" === stream.peek() && stream.match("^[", !1) ? (state.inlineFootnote = !0,
y += " inline-footnote-start formatting-inline-footnote",
w = stream.pos + 2) : state.inlineFootnote && !P && state.hmdLinkType === LinkType.NONE && !state.image && stream.match("]") && (state.inlineFootnote = !1,
y += " footref inline-footnote inline-footnote-end formatting-inline-footnote",
w = stream.pos),
"%" === stream.peek() && stream.match("%%", !1) ? (state.comment ? (y += " comment formatting comment-end",
state.comment = !1) : (y += " comment formatting comment-start",
state.comment = !0),
w = stream.pos + 2) : state.comment && (y += " comment")
}
if (g && (state.hmdLinkType || state.image || state.linkText || (isLetter(stream.peek()!) && stream.match(SN) || (p = stream.peek(),
!/[\s<>()[\]\\.,;:\s@\"`]/.test(p!) && stream.match(xN))) && (y += " url",
w = stream.pos)),
h && state.inFootnote) {
let F = stream.match(/^\s*/, !1)[0].replace(/\stream/g, " ").length;
F && F % stream.tabSize == 0 ? y += " line-HyperMD-footnote" : state.inFootnote = !1
}
let O = h && "[" === stream.peek() && stream.match(/^\[((?:[^\]\\]|\\.)*)\]:/, !1);
if (O) {
let B = O[1];
if ("^" !== B[0] || !/\s/.test(B))
return stream.match(/\[\^?/),
state.hmdLinkType = LinkType.FOOTNOTE,
state.formatting = "link",
state.linkText = !0,
y += "formatting formatting-link link " + CLASSES[LinkType.FOOTNOTE]
} else if (state.hmdLinkType === LinkType.FOOTNOTE) {
if ("]" === stream.peek() && stream.match("]:"))
return y += " formatting formatting-link link " + CLASSES[LinkType.FOOTNOTE],
state.linkText = !1,
state.inFootnote = !0,
state.hmdLinkType = LinkType.MAYBE_FOOTNOTE_URL,
//@ts-ignore
state.f = state.inline = markdownMode.startState().inline,
y;
y += " link " + CLASSES[LinkType.FOOTNOTE]
} else if (state.hmdLinkType === LinkType.MAYBE_FOOTNOTE_URL) {
if (stream.eatSpace())
return y;
if (isLetter(stream.peek()!) && stream.match(SN))
return y += " url hmd-footnote-url",
state.hmdLinkType = LinkType.MAYBE_FOOTNOTE_URL_TITLE,
y;
state.hmdLinkType = LinkType.NONE
} else if (state.hmdLinkType === LinkType.MAYBE_FOOTNOTE_URL_TITLE) {
if (stream.eatSpace())
return y;
if (state.hmdLinkType = LinkType.NONE,
stream.match(/^(["']).*?\1/) || stream.match(/^\([^)]*?\)/))
return y += " hmd-footnote-url-title"
}
}
if (state.hmdTable && "|" === stream.peek() && "\\" !== stream.string[stream.pos - 1] && function(e)
{
e.code = !1,
e.comment = !1,
e.em = !1,
e.formatting = !1,
e.highlight = !1,
e.hmdHashtag = !1,
e.hmdLinkType = LinkType.NONE,
e.isAlias = !1,
e.internalEmbed = !1,
e.internalLink = !1,
e.linkHref = !1,
e.linkText = !1,
e.linkTitle = !1,
e.strikethrough = !1,
e.strong = !1
}(state),
state.hmdNextState)
stream.pos = state.hmdNextPos!,
y += " " + (state.hmdNextStyle || ""),
Object.assign(state, state.hmdNextState),
state.hmdNextState = null,
state.hmdNextStyle = null,
state.hmdNextPos = null;
else {
let N = h && 0 !== stream.pos;
if (b) {
//@ts-ignore
let R = markdownMode.copyState(state), H = stream.pos;
y += " " + (markdownMode.token(stream, R) || ""),
stream.pos = H
} else
y += " " + (markdownMode.token(stream, state) || "");
//@ts-ignore
N && state.f === state.block && (state.f = state.inline = markdownMode.startState().inline),
state.inFootnote && (state.indentationDiff = 0)
}
y = function(e, text) {
return text ? (!config.hr && e.hr && (text = clearSubstringWords(text, "hr"),
e.hr = !1),
!config.headers && e.header && (text = clearSubstringWords(text, "header"),
e.header = 0),
!config.indentedCode && e.indentedCode && (text = clearSubstringWords(text, "inline-code"),
e.indentedCode = !1),
!config.blockquotes && e.quote && (text = clearSubstringWords(text, "quote"),
e.quote = 0),
!config.lists && e.list && (text = clearSubstringWords(text, "list"),
e.list = !1),
text) : text
}(state, y),
y.includes("formatting-task") && (y += " line-HyperMD-task-line"),
state.hmdHashtag && (y += " " + config.tokenTypeOverrides.hashtag),
-1 !== w && (stream.pos = w),
state.header && (state.isHeading = !0),
!i.htmlBlock && state.htmlState && (i.htmlBlock = state.f);
let V = state.f === i.htmlBlock
, z = -1 === state.code;
if (v = v && !(V || z),
g = g && v && !(state.code || state.indentedCode || state.linkHref),
state.hmdTable && V) {
let q = stream.current();
/(?:^|[^\\])\|/.test(q) && ("" === y.trim() || /string|attribute/.test(y)) && (V = !1,
a = !1,
state.htmlState = null,
state.block = i.block,
//@ts-ignore
state.f = state.inline = markdownMode.startState().inline,
stream.pos = "|" === q ? stream.start : stream.start + 1)
}
let U = stream.current();
if (V !== a && (V ? (y += " hmd-html-begin",
i.htmlBlock = state.f) : y += " hmd-html-end"),
(c || z) && (state.localMode && c || (y = y.replace("inline-code", "")),
y += " line-HyperMD-codeblock line-background-HyperMD-codeblock-bg hmd-codeblock",
z !== c && (z ? c || (y += " line-HyperMD-codeblock-begin line-background-HyperMD-codeblock-begin-bg") : y += " line-HyperMD-codeblock-end line-background-HyperMD-codeblock-end-bg")),
v) {
let _ = state.hmdTable;
if (h && _)
(_ == TableType.SIMPLE ? IN : ON).test(stream.string) ? (state.hmdTableCol = 0,
state.hmdTableRow++) : resetTable(state);
if (h && state.header && (/^(?:---+|===+)\s*$/.test(stream.string) && state.prevLine && state.prevLine.header ? y += " line-HyperMD-header-line line-HyperMD-header-line-" + state.header : y += " line-HyperMD-header line-HyperMD-header-" + state.header),
state.indentedCode && (y += " hmd-indented-code"),
state.quote) {
if (stream.match(/^\s*>/, !1) && !stream.eol() || (y += " line-HyperMD-quote line-HyperMD-quote-" + state.quote,
/^ {0,3}\>/.test(stream.string) || (y += " line-HyperMD-quote-lazy")),
h && (d = U.match(/^\s+/)))
return stream.pos = d![0].length,
(y += " hmd-indent-in-quote").trim();
if (state.quote > u)
{
const I = "[" === stream.peek() && stream.match(/^\[!([^\]]+)\]([+\-]?)(?:\s|$)/);
if(I)
y += " line-HyperMD-callout hmd-callout line-HyperMD-quote line-HyperMD-quote-" + state.quote
}
}
let W = (state.listStack[state.listStack.length - 1] || 0) + 3
, j = h && /^\s+$/.test(U) && (!1 !== state.list || stream.indentation() <= W)
, G = state.list && y.includes("formatting-list");
if (G || j && (!1 !== state.list || stream.match(EN, !1))) {
let K = state.listStack && state.listStack.length || 0;
if (j) {
if (stream.match(EN, !1))
!1 === state.list && K++;
else {
for (; K > 0 && stream.pos < state.listStack[K - 1]; )
K--;
if (!K)
return y.trim() || null;
y += " line-HyperMD-list-line-nobullet line-HyperMD-list-line line-HyperMD-list-line-".concat(K.toString())
}
y += " hmd-list-indent hmd-list-indent-".concat(K.toString())
} else
G && (y += " line-HyperMD-list-line line-HyperMD-list-line-".concat(K.toString()))
}
if (f !== state.linkText && (f || state.internalLink || state.internalEmbed ? state.hmdLinkType !== LinkType.FOOTNOTE && (state.hmdLinkType in CLASSES && (y += " " + CLASSES[state.hmdLinkType]),
state.hmdLinkType = LinkType.NONE) : (d = stream.match(/^([^\]]+)\](\(| ?\[|\:)?/, !1)) ? d[2] ? "[" !== d[2] && " [" !== d[2] || "]" !== stream.string.charAt(stream.pos + d[0].length) ? state.hmdLinkType = LinkType.NORMAL : state.hmdLinkType = LinkType.BARELINK2 : "^" !== d[1][0] || /\s/.test(d[1]) ? state.hmdLinkType = LinkType.BARELINK : state.hmdLinkType = LinkType.FOOTREF : state.hmdLinkType = LinkType.BARELINK),
m !== state.linkHref && (m ? state.hmdLinkType && (y += " " + CLASSES[state.hmdLinkType],
state.hmdLinkType = LinkType.NONE) : "[" === U && "]" !== stream.peek() && (state.hmdLinkType = LinkType.FOOTREF2)),
state.hmdLinkType !== LinkType.NONE && state.hmdLinkType in CLASSES && (y += " " + CLASSES[state.hmdLinkType]),
state.inlineFootnote && (y += " footref inline-footnote"),
k && U.length > 1) {
let Y = U.length - 1
, Z = y.replace("formatting-escape", "escape") + " hmd-escape-char";
return state.hmdOverride = function(e, stream) {
return e.pos += Y,
stream.hmdOverride = null,
Z.trim()
}
,
y += " hmd-escape-backslash",
stream.pos -= Y,
y
}
if (!y.trim() && config.table) {
let X = !1;
if ("|" === U.charAt(0) && (stream.pos = stream.start + 1,
U = "|",
X = !0),
!_ && state.prevLine && state.prevLine.stream && state.prevLine.stream.string.trim() && !state.wasHeading && (X = !1),
X) {
if (!_) {
PN.test(stream.string) ? _ = TableType.SIMPLE : FN.test(stream.string) && (_ = TableType.NORMAL);
let $: string[] | undefined = void 0;
if (_) {
let Q = stream.lookAhead(1);
if (_ === TableType.NORMAL ? FN.test(Q) ? Q = Q.replace(/^\s*\|/, "").replace(/\|\s*$/, "") : _ = TableType.NONE : _ === TableType.SIMPLE && (PN.test(Q) || (_ = TableType.NONE)),
_) {
$ = Q.split("|");
for (let J = 0; J < $.length; J++) {
let ee = $[J];
if (BN.test(ee))
ee = "right";
else if (NN.test(ee))
ee = "left";
else if (RN.test(ee))
ee = "center";
else {
if (!HN.test(ee)) {
_ = TableType.NONE;
break
}
ee = "default"
}
$[J] = ee
}
}
}
_ && (state.hmdTable = _,
state.hmdTableColumns = $!,
"rtl" === readSide(stream.string) && (state.hmdTableRTL = !0),
state.hmdTableRow = state.hmdTableCol = 0)
}
if (_) {
let te = state.hmdTableColumns.length - 1
, ne = state.hmdTableCol
, ee = state.hmdTableRow;
0 == ne && (y += " line-HyperMD-table-".concat(_.toString(), " line-HyperMD-table-row line-HyperMD-table-row-").concat(ee.toString()),
state.hmdTableRTL && (y += " line-HyperMD-table-rtl")),
_ === TableType.NORMAL && (0 === state.hmdTableCol && /^\s*\|$/.test(stream.string.slice(0, stream.pos)) || stream.match(/^\s*$/, !1)) ? y += " hmd-table-sep hmd-table-sep-dummy" : state.hmdTableCol < te && (y += " hmd-table-sep hmd-table-sep-".concat(ne.toString()),
state.hmdTableCol += 1)
}
}
}
if (_ && 1 === state.hmdTableRow && y.includes("emoji") && (y = ""),
g && "<" === U) {
let ie = null;
if ("!" === stream.peek() && stream.match(/^\![A-Z]+/) ? ie = ">" : "!" === stream.peek() && stream.match("![CDATA[") ? ie = "]]>" : "?" === stream.peek() && (ie = "?>"),
null != ie)
return enterMode(stream, state, null, {
endTag: ie,
style: (y + " comment hmd-cdata-html").trim()
})
}
if (config.hashtag && g)
if (state.hmdHashtag) {
let re = !1;
if (!(y = y.replace(/((formatting )?formatting-em|em) /g, "")).includes("formatting") && !/^\s*$/.test(U)) {
d = U.match(TN);
let oe = U.length - (d ? d[0].length : 0);
oe > 0 && (stream.backUp(oe),
re = !0)
}
if (re || (re = stream.eol()),
re || (re = !TN.test(stream.peek()!)),
re)
{
let le = stream.current();
y += " hashtag-end " + (le = "tag-" + le.replace(/[^_a-zA-Z0-9\-]/g, "")),
state.hmdHashtag = !1
}
} else if ("#" === U && !state.linkText && !state.image && (h || /^\s*$/.test(stream.string.charAt(stream.start - 1)))) {
let ae = stream.string.slice(stream.pos).replace(/\\./g, "")
, se = TN.exec(ae);
if (se && /[^0-9]/.test(se[0])) {
let le = "tag-" + se[0].replace(/[^_a-zA-Z0-9\-]/g, "");
state.hmdHashtag = !0,
y += " formatting formatting-hashtag hashtag-begin " + config.tokenTypeOverrides.hashtag + " " + le
}
}
}
return y.trim() || null;
},
}
}, 'd-any');