Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Initial development #38

Merged
merged 5 commits into from
Jan 2, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 6 additions & 1 deletion CodeMirror6/CodeMirror6Wrapper.razor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -231,7 +231,7 @@ [JSInvokable] public async Task<List<CodeMirrorDiagnostic>> LintingRequestedFrom
/// <summary>
/// Life-cycle method invoked when the component is initialized.
/// </summary>
protected override void OnInitialized()
protected override async Task OnInitializedAsync()
{
Config = new(
Doc,
Expand All @@ -245,6 +245,11 @@ protected override void OnInitialized()
AutoFormatMarkdown,
ReplaceEmojiCodes
);
try {
await OnAfterRenderAsync(true); // try early initialization for Blazor WASM
}
catch (Exception) {
}
}

/// <summary>
Expand Down
4 changes: 4 additions & 0 deletions CodeMirror6/CodeMirrorJsInterop.cs
Original file line number Diff line number Diff line change
Expand Up @@ -368,6 +368,10 @@ internal Task SetMentionCompletions(List<CodeMirrorCompletion> mentionCompletion
"setMentionCompletions",
mentionCompletions
);

internal Task ForceRedraw() => cmJsInterop.ModuleInvokeVoidAsync(
"forceRedraw"
);
}

/// <summary>
Expand Down
2 changes: 1 addition & 1 deletion CodeMirror6/Models/CodeMirrorSetup.cs
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,7 @@ public CodeMirrorSetup()
[JsonPropertyName("previewImages")] public bool PreviewImages { get; init; } = true;

/// <summary>
/// Whether to enable mentions
/// Whether to enable mentions.
/// </summary>
[JsonPropertyName("allowMentions")] public bool AllowMentions { get; init; } = true;
}
28 changes: 14 additions & 14 deletions CodeMirror6/NodeLib/src/CmHorizontalRule.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,6 @@ import type { DecorationSet } from '@codemirror/view'
import { buildWidget } from './lib/codemirror-kit'
import { isCursorInRange } from './CmHelpers'

const hrWidget = () => buildWidget({
eq: () => false,
toDOM: () => {
const hr = document.createElement('hr');
hr.setAttribute('aria-hidden', 'true');
return hr;
},
})


/**
* Return the horizontal rule Extension if the supplied parameter is true
Expand All @@ -25,8 +16,16 @@ export const dynamicHrExtension = (enabled: boolean = true): Extension => {
if (!enabled)
return []

const hrDecoration = () => Decoration.replace({
widget: hrWidget(),
const createHRDecorationWidget = () => Decoration.replace({
widget: buildWidget({
eq: () => false,
toDOM: () => {
const hr = document.createElement('hr')
hr.setAttribute('aria-hidden', 'true')
return hr
},
ignoreEvent: () => false,
}),
})

const decorate = (state: EditorState) => {
Expand All @@ -41,7 +40,7 @@ export const dynamicHrExtension = (enabled: boolean = true): Extension => {
const hrRegex = /^-{3,}$/

if (hrRegex.test(lineText)) {
widgets.push(hrDecoration().range(line.from, line.to))
widgets.push(createHRDecorationWidget().range(line.from, line.to))
}
}
},
Expand All @@ -52,12 +51,13 @@ export const dynamicHrExtension = (enabled: boolean = true): Extension => {
}

const viewPlugin = ViewPlugin.define(() => ({}), {})

const stateField = StateField.define<DecorationSet>({
create(state) {
return decorate(state)
},
update(_references, { state }) {
return decorate(state)
update(_references, transaction) {
return decorate(transaction.state)
},
provide(field) {
return EditorView.decorations.from(field)
Expand Down
117 changes: 48 additions & 69 deletions CodeMirror6/NodeLib/src/CmHtml.ts
Original file line number Diff line number Diff line change
@@ -1,82 +1,61 @@
import { syntaxTree } from '@codemirror/language'
import { RangeSet, StateField } from '@codemirror/state'
import { RangeSet, StateField, RangeSetBuilder } from '@codemirror/state'
import { Decoration, EditorView, ViewPlugin } from '@codemirror/view'
import type { EditorState, Extension, Range } from '@codemirror/state'
import type { DecorationSet } from '@codemirror/view'
import { markdownLanguage } from "@codemirror/lang-markdown"
import { buildWidget } from './lib/codemirror-kit'
import { isCursorInRange } from './CmHelpers'

const htmlWidget = (content: string) => buildWidget({
eq: () => false,
toDOM: () => {
const container = document.createElement('span');
container.innerHTML = content; // Insert HTML content
return container;
},
})

export const viewInlineHtmlExtension = (enabled: boolean = true): Extension => {
if (!enabled)
return []

const htmlDecoration = (content: string) => Decoration.replace({
widget: htmlWidget(content),
import { isCursorInRange, isInCodeBlock } from './CmHelpers'


function createHtmlDecorationWidget(content: string) {
return Decoration.replace({
widget: buildWidget({
eq: (other) => other.content === content,
toDOM: () => {
const container = document.createElement('span')
container.innerHTML = content
return container
},
ignoreEvent: () => false,
content: content
}),
})
}

const decorate = (state: EditorState) => {
const widgets: Range<Decoration>[] = [];

if (enabled) {
let foundClosingTag = true
let htmlCode = ''
let paragraph = ''
let paragraphFrom = 0
let paragraphTo = 0
syntaxTree(state).iterate({
enter: ({ type, from, to }) => {
const text = state.sliceDoc(from, to)
if (type.name === 'Paragraph') {
paragraph = text
paragraphFrom = from
paragraphTo = to
}
else if (type.name === 'HTMLTag') {
foundClosingTag = !foundClosingTag
htmlCode += text
if (htmlCode !== '' && paragraph !== '' && foundClosingTag) {
if (!isCursorInRange(state, paragraphFrom, paragraphTo)) {
widgets.push(htmlDecoration(paragraph).range(paragraphFrom, paragraphTo))
export function htmlViewPlugin(enabled: boolean): Extension {
if (!enabled) return []
return ViewPlugin.define((view: EditorView) => {
return {
update: () => {
const builder = new RangeSetBuilder<Decoration>()
for (const { from, to } of view.visibleRanges) {
const text = view.state.doc.sliceString(from, to)

if (markdownLanguage.isActiveAt(view.state, from)) {
// recognize html spans (<span>...</span>) and decorate them
const spanRegex = /<span[^>]*>([^<]*)<\/span>/g
let match
while ((match = spanRegex.exec(text)) !== null) {
const start = from + match.index
const end = start + match[0].length
if (!isCursorInRange(view.state, start, end)) {
const isCode = isInCodeBlock(view.state, start)
if (!isCode) {
const spanText = match[1]
if (!spanText || spanText === "") continue
const widget = createHtmlDecorationWidget(match[0])
builder.add(start, end, widget)
}
}
htmlCode = ''
paragraph = ''
}
}
else {
if (!foundClosingTag) htmlCode += text
}
},
})
}
return builder.finish();
},
}

return widgets.length > 0 ? RangeSet.of(widgets) : Decoration.none;
}

const viewPlugin = ViewPlugin.define(() => ({}), {})

const stateField = StateField.define<DecorationSet>({
create(state) {
return decorate(state)
},
update(_references, { state }) {
return decorate(state)
},
provide(field) {
return EditorView.decorations.from(field)
},
},
{
decorations: plugin => plugin.update()
})

return [
viewPlugin,
stateField,
]
}
2 changes: 1 addition & 1 deletion CodeMirror6/NodeLib/src/CmMentionsView.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { markdownLanguage } from "@codemirror/lang-markdown";
import { markdownLanguage } from "@codemirror/lang-markdown"
import { EditorState, RangeSetBuilder, Extension } from '@codemirror/state'
import { EditorView, ViewPlugin, Decoration } from "@codemirror/view"
import { buildWidget } from "./lib/codemirror-kit"
Expand Down
18 changes: 13 additions & 5 deletions CodeMirror6/NodeLib/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import {
rectangularSelection, crosshairCursor, ViewUpdate,
lineNumbers, highlightActiveLineGutter, placeholder
} from "@codemirror/view"
import { EditorState } from "@codemirror/state"
import { EditorState, SelectionRange } from "@codemirror/state"
import {
indentWithTab, history, historyKeymap,
cursorSyntaxLeft, selectSyntaxLeft, selectSyntaxRight, cursorSyntaxRight, deleteLine,
Expand All @@ -19,7 +19,6 @@ import {
import { autocompletion, completionKeymap, closeBrackets, closeBracketsKeymap, Completion } from "@codemirror/autocomplete"
import { searchKeymap, highlightSelectionMatches } from "@codemirror/search"
import { linter, lintKeymap } from "@codemirror/lint"

import { CmInstance, CMInstances } from "./CmInstance"
import { CmConfiguration } from "./CmConfiguration"
import { getDynamicHeaderStyling } from "./CmDynamicMarkdownHeaderStyling"
Expand Down Expand Up @@ -47,7 +46,7 @@ import { mentionDecorationExtension } from "./CmMentionsView"
import { viewEmojiExtension } from "./CmEmojiView"
import { emojiCompletionExtension } from "./CmEmojiCompletion"
import { indentationMarkers } from '@replit/codemirror-indentation-markers'
import { viewInlineHtmlExtension } from "./CmHtml"
import { htmlViewPlugin } from "./CmHtml"

/**
* Initialize a new CodeMirror instance
Expand Down Expand Up @@ -82,7 +81,7 @@ export function initCodeMirror(
listsExtension(initialConfig.autoFormatMarkdown),
blockquote(),
viewEmojiExtension(initialConfig.autoFormatMarkdown),
viewInlineHtmlExtension(initialConfig.autoFormatMarkdown),
htmlViewPlugin(initialConfig.autoFormatMarkdown),
]),
CMInstances[id].tabSizeCompartment.of(EditorState.tabSize.of(initialConfig.tabSize)),
CMInstances[id].indentUnitCompartment.of(indentUnit.of(" ".repeat(initialConfig.tabSize))),
Expand Down Expand Up @@ -234,6 +233,15 @@ export function setLanguage(id: string, languageName: string) {

export function setMentionCompletions(id: string, mentionCompletions: Completion[]) {
setCachedCompletions(mentionCompletions)
forceRedraw(id)
}

export function forceRedraw(id: string) {
const view = CMInstances[id].view
const changes = view.state.changeByRange((range: SelectionRange) => {
return { range }
})
view.dispatch(view.state.update(changes))
}

export function setAutoFormatMarkdown(id: string, autoFormatMarkdown: boolean) {
Expand All @@ -249,7 +257,7 @@ export function setAutoFormatMarkdown(id: string, autoFormatMarkdown: boolean) {
listsExtension(autoFormatMarkdown),
blockquote(),
viewEmojiExtension(autoFormatMarkdown),
viewInlineHtmlExtension(autoFormatMarkdown),
htmlViewPlugin(autoFormatMarkdown),
])
})
}
Expand Down
1 change: 1 addition & 0 deletions Examples.BlazorServer/Pages/_Host.cshtml
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
<link rel="stylesheet" href="css/bootstrap/bootstrap.min.css" />
<link href="css/site.css" rel="stylesheet" />
<link href="Examples.BlazorServer.styles.css" rel="stylesheet" />
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.1.1/css/all.min.css">
<link rel="icon" type="image/png" href="favicon.png"/>
<component type="typeof(HeadOutlet)" render-mode="ServerPrerendered" />
</head>
Expand Down
1 change: 1 addition & 0 deletions Examples.BlazorWasm/wwwroot/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
<link href="css/app.css" rel="stylesheet" />
<link rel="icon" type="image/png" href="favicon.png" />
<link href="Examples.BlazorWasm.styles.css" rel="stylesheet" />
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.1.1/css/all.min.css">
</head>

<body>
Expand Down
Loading
Loading