Skip to content

Commit

Permalink
markdown: add support for html tags
Browse files Browse the repository at this point in the history
  • Loading branch information
Wattenberger committed Aug 16, 2022
1 parent 471400d commit ac94714
Show file tree
Hide file tree
Showing 6 changed files with 158 additions and 104 deletions.
97 changes: 75 additions & 22 deletions blocks/file-blocks/markdown-edit/copy-widget.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,26 +19,34 @@ import { FileContext, FolderContext } from "@githubnext/blocks";

interface HtmlWidgetParams {
url: string;
tag: string;
context: FileContext | FolderContext;
}

class HtmlWidget extends WidgetType {
export class HtmlWidget extends WidgetType {
readonly text;
readonly context;

constructor({ text }: HtmlWidgetParams) {
constructor({ text, context }: HtmlWidgetParams) {
super();

this.text = text;
this.context = context;
}

eq(htmlWidget: HtmlWidget) {
return htmlWidget.text === this.text;
return htmlWidget.context === this.context;
}

toDOM() {
const container = document.createElement("span");
container.className = "cm-html-container";
container.innerHTML = this.text;
const srcStringsRegex = /src="([^"]*)"/g;
const parsedText = this.text.replace(srcStringsRegex, (match, p1) => {
const url = parseImageUrl(p1, this.context);
return `src="${url}"`;
});
container.innerHTML = parsedText;
return container;
}

Expand All @@ -61,15 +69,21 @@ export const copy = ({
onScrollTo: (from) => void;
}): Extension => {
const headerDecoration = ({ level, id }: { level: string; id: string }) =>
Decoration.mark({
Decoration.line({
class: `cm-copy-header cm-copy-header--${level}`,
attributes: {
id,
},
});
const htmlTagDecoration = ({ text }: { text: string }) =>
const htmlTagDecoration = ({
text,
context,
}: {
text: string;
context: FileContext | FolderContext;
}) =>
Decoration.widget({
widget: new HtmlWidget({ text }),
widget: new HtmlWidget({ text, context }),
});
const htmlTagTextDecoration = () =>
Decoration.mark({
Expand Down Expand Up @@ -139,18 +153,14 @@ export const copy = ({
const tree = syntaxTree(state);
tree.iterate({
enter: ({ type, from, to }) => {
if (type.name === "Comment") console.log(type.name);
if (from === undefined || to === undefined) return;
if (type.name.startsWith("ATXHeading")) {
const text = state.doc.sliceString(from, to);
const level = type.name.split("Heading")[1];
let id = slugifyHeading(text);
const newDecoration = headerDecoration({ level, id });
widgets.push(
newDecoration.range(
state.doc.lineAt(from).from,
state.doc.lineAt(to).to
)
);
widgets.push(newDecoration.range(state.doc.lineAt(from).from));
} else if (type.name === "SetextHeading2") {
const newDecoration = horizontalRuleDecorationAfter();
widgets.push(newDecoration.range(from, to));
Expand Down Expand Up @@ -200,7 +210,8 @@ export const copy = ({
// widgets.push(newDecoration.range(from));
} else if (["HTMLTag", "HTMLBlock"].includes(type.name)) {
let text = state.doc.sliceString(from, to);
const tag = /<\/*(?<tag>.*?)[>\s]/.exec(text)?.groups?.tag;
const tag = /<(?<tag>[^/\s>]*)/.exec(text)?.groups?.tag;
console.log(tag);
if (tag === "a") {
const linkRegexHtml =
/<a.*?href="(?<url>.*?)".*?>(?<text>.*?)[<\/a>]*/;
Expand Down Expand Up @@ -242,21 +253,51 @@ export const copy = ({
const newAltDecoration = linkDecoration(text, "", "");
widgets.push(newAltDecoration.range(from, to));
}
} else if (["i", "b", "u", "details", "summary"].includes(tag)) {
} else if (
[
"i",
"b",
"u",
"details",
"summary",
"video",
"img",
"br",
"h1",
"h2",
"h3",
"h4",
"h5",
"h6",
"p",
"table",
"center",
"pre",
"code",
"ul",
"ol",
"li",
].includes(tag)
) {
const endOfTagRegex = new RegExp(`(</${tag}\s*>)|(\s*/>)`);
let endOfTag = endOfTagRegex.exec(text);
const tagsWithNoEndNeeded = ["br", "img"];
if (!endOfTag) {
const subsequentText = state.doc.sliceString(to, to + 1000);
const matches = endOfTagRegex.exec(subsequentText);
const matchIndex = subsequentText.indexOf(matches?.[0]);
if (matchIndex === -1) return;
to = to + matchIndex + matches?.[0].length;
text = state.doc.sliceString(from, to);
const newDecoration = htmlTagDecoration({ text });
widgets.push(newDecoration.range(from, from));
const newAltDecoration = htmlTagTextDecoration({ text });
widgets.push(newAltDecoration.range(from, to));
if (matchIndex !== -1) {
to = to + matchIndex + matches?.[0].length;
text = state.doc.sliceString(from, to);
} else if (!tagsWithNoEndNeeded.includes(tag)) {
console.log("no end tag found for", tag);
return;
}
}
const newDecoration = htmlTagDecoration({ text, context });
widgets.push(newDecoration.range(from, from));
const newAltDecoration = htmlTagTextDecoration({ text });
widgets.push(newAltDecoration.range(from, to));
}
} else if (type.name === "HorizontalRule") {
const newDecoration = horizontalRuleDecorationAfter();
Expand Down Expand Up @@ -464,3 +505,15 @@ const slugifyHeading = (str: string) =>
.replace(/\[([^\]]+)\]\(([^\)]+)\)/g, (match, text, url) => text)
.trim()
);

export const parseImageUrl = (
url: string,
context: FileContext | FolderContext
) => {
if (!url.startsWith("http")) {
const pathRoot = context.path.split("/").slice(0, -1).join("/");
return `https://raw.githubusercontent.com/${context.owner}/${context.repo}/${context.sha}/${pathRoot}/${url}`;
} else {
return url;
}
};
59 changes: 10 additions & 49 deletions blocks/file-blocks/markdown-edit/image-widget.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@ import { syntaxTree } from "@codemirror/language";
import {
EditorState,
Extension,
StateField,
Range,
RangeSet,
StateField,
} from "@codemirror/state";
import {
Decoration,
Expand All @@ -24,20 +24,23 @@ class ImageWidget extends WidgetType {
readonly url;
readonly width;
readonly height;
readonly alt;

constructor({ url, width, height }: ImageWidgetParams) {
constructor({ url, width, height, alt }: ImageWidgetParams) {
super();

this.url = url;
this.width = width;
this.height = height;
this.alt = alt;
}

eq(imageWidget: ImageWidget) {
return (
imageWidget.url === this.url &&
imageWidget.width === this.width &&
imageWidget.height === this.height
imageWidget.height === this.height &&
imageWidget.alt === this.alt
);
}

Expand All @@ -58,6 +61,7 @@ class ImageWidget extends WidgetType {
image.style.width = parseStyle(this.width) || "auto";
image.style.maxWidth = "100%";
image.style.height = parseStyle(this.height) || "auto";
image.alt = this.alt || "";

return figure;
}
Expand All @@ -73,7 +77,6 @@ export const images = ({
context: FileContext | FolderContext;
}): Extension => {
const imageRegex = /!\[(?<alt>.*?)\]\((?<url>.*?)\)/;
const imageRegexHtml = /<img.*?src="(?<url>.*?)".*?>/;

const imageDecoration = (imageWidgetParams: ImageWidgetParams) =>
Decoration.widget({
Expand All @@ -85,11 +88,6 @@ export const images = ({
class: "cm-image",
});

const imageAltDecoration = () =>
Decoration.mark({
class: "cm-image-alt",
});

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

Expand All @@ -104,39 +102,13 @@ export const images = ({
const heightRegex = /height="(?<height>.*?)"/;
const widthResult = widthRegex.exec(result.groups.url);
const heightResult = heightRegex.exec(result.groups.url);
widgets.push(
imageDecoration({
url: parseImageUrl(result.groups.url, context),
width: widthResult?.groups?.width,
height: heightResult?.groups?.height,
}).range(state.doc.lineAt(from).from)
);
widgets.push(
imageTextDecoration().range(
state.doc.lineAt(from).from,
state.doc.lineAt(to).to
)
);
let alt = result.groups.alt || text;
const altIndex = from + text.indexOf(alt);
widgets.push(
imageAltDecoration().range(altIndex, altIndex + alt.length)
);
}
} else if (["HTMLBlock", "Paragraph"].includes(type.name)) {
const text = state.doc.sliceString(from, to);
const result = imageRegexHtml.exec(text);

if (result && result.groups && result.groups.url) {
const widthRegex = /width="(?<width>.*?)"/;
const heightRegex = /height="(?<height>.*?)"/;
const widthResult = widthRegex.exec(text);
const heightResult = heightRegex.exec(text);
widgets.push(
imageDecoration({
url: result.groups.url,
url: parseImageUrl(result.groups.url, context),
width: widthResult?.groups?.width,
height: heightResult?.groups?.height,
alt,
}).range(state.doc.lineAt(from).from)
);
widgets.push(
Expand All @@ -145,13 +117,6 @@ export const images = ({
state.doc.lineAt(to).to
)
);
const altRegex = /alt="(?<alt>.*?)"/;
const altResult = altRegex.exec(text);
let alt = altResult?.groups?.alt || text;
const altIndex = from + text.indexOf(alt);
widgets.push(
imageAltDecoration().range(altIndex, altIndex + alt.length)
);
}
}
},
Expand All @@ -167,11 +132,7 @@ export const images = ({
return RangeSet.of(sortedWidgets);
};

const imagesTheme = EditorView.baseTheme({
".cm-image-backdrop": {
backgroundColor: "var(--ink-internal-block-background-color)",
},
});
const imagesTheme = EditorView.baseTheme({});

const imagesField = StateField.define<DecorationSet>({
create(state) {
Expand Down
Loading

0 comments on commit ac94714

Please sign in to comment.