Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
16 commits
Select commit Hold shift + click to select a range
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
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,5 @@ node_modules/
stats.json
dist/
test-results/

.idea/
27,314 changes: 9,436 additions & 17,878 deletions package-lock.json

Large diffs are not rendered by default.

8 changes: 4 additions & 4 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@
"@stackoverflow/tsconfig": "^1.0.0",
"@types/jest": "^29.5.12",
"@typescript-eslint/eslint-plugin": "^7.4.0",
"@types/markdown-it": "^14.0.0",
"@typescript-eslint/parser": "^7.4.0",
"clean-webpack-plugin": "^4.0.0",
"css-loader": "^7.1.1",
Expand Down Expand Up @@ -87,8 +88,7 @@
"@lezer/highlight": "^1.2.0",
"@lezer/markdown": "^1.2.0",
"@stackoverflow/stacks-icons": "^6.0.2",
"@types/markdown-it": "13.0.7",
"markdown-it": "^13.0.2",
"markdown-it": "^14.0.0",
"orderedmap": "^2.1.1",
"prosemirror-commands": "^1.5.2",
"prosemirror-highlightjs": "^0.9.1",
Expand All @@ -97,13 +97,13 @@
"prosemirror-keymap": "^1.2.2",
"prosemirror-lezer": "^0.0.5",
"prosemirror-markdown": "^1.12.0",
"prosemirror-model": "^1.19.4",
"prosemirror-model": "^1.24.1",
"prosemirror-schema-basic": "^1.2.2",
"prosemirror-schema-list": "^1.3.0",
"prosemirror-state": "^1.4.3",
"prosemirror-transform": "^1.8.0",
"prosemirror-utils": "^1.2.1-0",
"prosemirror-view": "^1.33.3"
"prosemirror-view": "^1.37.1"
},
"peerDependencies": {
"@stackoverflow/stacks": "^2.3.0",
Expand Down
2 changes: 1 addition & 1 deletion site/layout.html
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
<title>Stacks Editor</title>
<link
rel="icon"
href="data:image/svg+xml,<svg xmlns=%22http://www.w3.org/2000/svg%22 viewBox=%220 0 100 100%22><text y=%22.9em%22 font-size=%2290%22>🐸</text></svg>"
href="data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 100'><text y='.9em' font-size='90'>🐸</text></svg>"
/>
</head>

Expand Down
5 changes: 1 addition & 4 deletions site/sample-plugins/japanese-se.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,4 @@
import type MarkdownIt from "markdown-it";
import type ParserInline from "markdown-it/lib/parser_inline";
import type StateInline from "markdown-it/lib/rules_inline/state_inline";
import type Token from "markdown-it/lib/token";
import MarkdownIt, { ParserInline, StateInline, Token } from "markdown-it";
import type { EditorPlugin } from "../../src";

// simple proof of concept that adds furigana support from https://japanese.meta.stackexchange.com/questions/806/how-should-i-format-my-questions-on-japanese-language-se/807#807
Expand Down
3 changes: 2 additions & 1 deletion src/rich-text/plugins/link-editor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,8 @@ export class LinkEditor extends PluginInterfaceView<
this.validate((e.target as HTMLInputElement).value);
});
}
protected key: LinkEditorPluginKey;

declare protected key: LinkEditorPluginKey;

validate(href: string): boolean {
const valid = this.validateLink(href);
Expand Down
7 changes: 4 additions & 3 deletions src/rich-text/schema.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { MarkSpec, NodeSpec } from "prosemirror-model";
import { Attrs, MarkSpec, NodeSpec } from "prosemirror-model";
import { _t } from "../shared/localization";

//TODO this relies on Stacks classes, should we abstract?
Expand Down Expand Up @@ -448,7 +448,8 @@ const marks: {
{ tag: "em" },
{
style: "font-style",
getAttrs: (value) => value === "italic" && null,
getAttrs: (value: string): Attrs | false | null =>
value === "italic" && null,
},
],
toDOM() {
Expand All @@ -462,7 +463,7 @@ const marks: {
{ tag: "strong" },
{
style: "font-weight",
getAttrs: (value: string) =>
getAttrs: (value: string): Attrs | false | null =>
/^(bold(er)?|[5-9]\d{2,})$/.test(value) && null,
},
],
Expand Down
5 changes: 2 additions & 3 deletions src/shared/markdown-it/hardbreak-markup.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import MarkdownIt from "markdown-it";
import State from "markdown-it/lib/rules_core/state_core";
import Token from "markdown-it/lib/token";
import { StateCore, Token } from "markdown-it";

function addHardbreakMarkup(tokens: Token[], parent: Token = null) {
for (let i = 0; i < tokens.length; i++) {
Expand All @@ -27,7 +26,7 @@ function addHardbreakMarkup(tokens: Token[], parent: Token = null) {
* TODO UPSTREAM
*/
export function hardbreak_markup(md: MarkdownIt): void {
md.core.ruler.push("hardbreak-markup", function (state: State) {
md.core.ruler.push("hardbreak-markup", function (state: StateCore) {
addHardbreakMarkup(state.tokens);
return false;
});
Expand Down
51 changes: 31 additions & 20 deletions src/shared/markdown-it/html.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
import MarkdownIt from "markdown-it";
import State from "markdown-it/lib/rules_core/state_core";
import Token from "markdown-it/lib/token";
import MarkdownIt, { StateCore } from "markdown-it";
import {
blockElements,
selfClosingElements,
supportedTagAttributes,
TagType,
} from "../html-helpers";

const Token = MarkdownIt().core.State.prototype.Token;

interface TagInfo {
type: TagType;
isSelfClosing: boolean;
Expand Down Expand Up @@ -126,9 +126,12 @@ function getTagInfo(tag: string): TagInfo {
* @param tagInfo The tagInfo to use
* @param existing The token to alter; creates a new token if this is empty
*/
function tagInfoToToken(tagInfo: TagInfo, existing?: Token): Token {
function tagInfoToToken(
tagInfo: TagInfo,
existing?: MarkdownIt.Token
): MarkdownIt.Token {
// if a token was not passed in, create a new empty one
const token = existing || new Token("", "", 0);
const token: MarkdownIt.Token = existing || new Token("", "", 0);

// determine the markdown-it Token type for this tag
const postfix = tagInfo.isSelfClosing
Expand Down Expand Up @@ -200,7 +203,9 @@ type parsedBlockTokenInfo = {
* @param token The html_block token to parse
* @returns The parsed info if able, null if unable
*/
function isParseableHtmlBlockToken(token: Token): parsedBlockTokenInfo {
function isParseableHtmlBlockToken(
token: MarkdownIt.Token
): parsedBlockTokenInfo {
const content = token.content;
// checks if a token matches `<open>content</close>` OR `<br />`
const matches =
Expand Down Expand Up @@ -256,9 +261,9 @@ function isParseableHtmlBlockToken(token: Token): parsedBlockTokenInfo {
* @param parentType The type of the token's parent
*/
function wrapBareInlineToken(
token: Token,
token: MarkdownIt.Token,
parentType: TagType | null
): Token[] {
): MarkdownIt.Token[] {
// don't wrap block tokens
if (token.block) {
return [token];
Expand Down Expand Up @@ -286,7 +291,7 @@ function wrapBareInlineToken(
* Sanitizes a single token if it is of type `html_inline`; otherwise, its children are sanitized
* @param token The token to sanitize `html_inline` tokens in
*/
function sanitizeHtmlInlineToken(token: Token): Token {
function sanitizeHtmlInlineToken(token: MarkdownIt.Token): MarkdownIt.Token {
let tagInfo: TagInfo = null;

if (token.type === "html_inline") {
Expand Down Expand Up @@ -324,7 +329,9 @@ function sanitizeHtmlInlineToken(token: Token): Token {
* Can return `html_inline` tokens that were unable to be sanitized or have a missing pair
* @param tokens The tokens to sanitize
*/
function sanitizeInlineHtmlTokens(tokens: Token[]): Token[] {
function sanitizeInlineHtmlTokens(
tokens: MarkdownIt.Token[]
): MarkdownIt.Token[] {
tokens = tokens.map(sanitizeHtmlInlineToken).filter((t) => !!t);
for (let i = 0, len = tokens.length; i < len; i++) {
const openToken = tokens[i];
Expand Down Expand Up @@ -435,8 +442,8 @@ function sanitizeHtmlString(content: string) {
* Recursively sanitizes all `html_block` tokens by parsing the ones that are able to be simply parsed
* @param tokens The tokens to sanitize
*/
function sanitizeSimpleHtmlBlockTokens(tokens: Token[]) {
const retTokens: Token[] = [];
function sanitizeSimpleHtmlBlockTokens(tokens: MarkdownIt.Token[]) {
const retTokens: MarkdownIt.Token[] = [];

tokens.forEach((token) => {
let parsedInfo: parsedBlockTokenInfo = null;
Expand All @@ -454,12 +461,12 @@ function sanitizeSimpleHtmlBlockTokens(tokens: Token[]) {
return;
}

const newTokens: Token[] = [];
const newTokens: MarkdownIt.Token[] = [];

parsedInfo.tagInfo.forEach((tag, i, arr) => {
const lastInfo = arr[i - 1] as TagInfo;
const isInline = typeof tag === "string" || !tag.isBlock;
let tok: Token;
let tok: MarkdownIt.Token;

if (typeof tag === "string") {
const t = new Token("text", "", 0);
Expand Down Expand Up @@ -489,8 +496,10 @@ function sanitizeSimpleHtmlBlockTokens(tokens: Token[]) {
* Sanitize the content of `html_block` tokens by stripping out all unknown tags
* @param tokens The tokens to sanitize
*/
function sanitizeBlockHtmlTokens(tokens: Token[]): Token[] {
const retTokens: Token[] = [];
function sanitizeBlockHtmlTokens(
tokens: MarkdownIt.Token[]
): MarkdownIt.Token[] {
const retTokens: MarkdownIt.Token[] = [];

tokens.forEach((token) => {
if (token.children?.length) {
Expand Down Expand Up @@ -542,8 +551,10 @@ function sanitizeBlockHtmlTokens(tokens: Token[]): Token[] {
* Attempts to merge `html_block` tokens that were split by a newline character
* @param tokens The tokens to sanitize
*/
function mergeSplitBlockHtmlTokens(tokens: Token[]): Token[] {
const returnTokens: Token[] = [];
function mergeSplitBlockHtmlTokens(
tokens: MarkdownIt.Token[]
): MarkdownIt.Token[] {
const returnTokens: MarkdownIt.Token[] = [];

let splitCount = 0;
// get all split html_block indexes
Expand Down Expand Up @@ -579,7 +590,7 @@ function mergeSplitBlockHtmlTokens(tokens: Token[]): Token[] {
blockIndexes = blockIndexes.slice(0, -1);
}

let lastOpenToken: Token | null = null;
let lastOpenToken: MarkdownIt.Token | null = null;
tokens.forEach((t, i) => {
if (t.children && t.children.length) {
t.children = mergeSplitBlockHtmlTokens(t.children);
Expand Down Expand Up @@ -619,7 +630,7 @@ function mergeSplitBlockHtmlTokens(tokens: Token[]): Token[] {
* @param md
*/
export function html(md: MarkdownIt): void {
md.core.ruler.push("so-sanitize-html", function (state: State) {
md.core.ruler.push("so-sanitize-html", function (state: StateCore) {
// TODO there are a lot of loops here. Can we combine a few?
state.tokens = sanitizeInlineHtmlTokens(state.tokens);
state.tokens = sanitizeSimpleHtmlBlockTokens(state.tokens);
Expand Down
11 changes: 5 additions & 6 deletions src/shared/markdown-it/reference-link.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,4 @@
import MarkdownIt from "markdown-it";
import { normalizeReference } from "markdown-it/lib/common/utils";
import State from "markdown-it/lib/rules_core/state_core";
import Token from "markdown-it/lib/token";
import MarkdownIt, { StateCore, Token } from "markdown-it";

type LinkReference = { title?: string; href?: string };
type LinkReferences = { [key: string]: LinkReference };
Expand Down Expand Up @@ -41,7 +38,9 @@ function setLinkReference(
label = match[2];
}

const normalizedLabel = normalizeReference(label);
const normalizedLabel = new MarkdownIt().utils.normalizeReference(
label
);
reference = references[normalizedLabel];
const href =
token.type === "image"
Expand Down Expand Up @@ -117,7 +116,7 @@ function setLinkReferences(
* Searches for and marks links that point to a reference with a "reference" meta data
*/
export function reference_link(md: MarkdownIt): void {
md.core.ruler.push("link-reference", function (state: State) {
md.core.ruler.push("link-reference", function (state: StateCore) {
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
const references = state.env?.references as LinkReferences;

Expand Down
9 changes: 4 additions & 5 deletions src/shared/markdown-it/spoiler.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,5 @@
import MarkdownIt from "markdown-it/lib";
import StateBlock, {
ParentType,
} from "markdown-it/lib/rules_block/state_block";
import { isSpace } from "markdown-it/lib/common/utils";
import MarkdownIt, { StateBlock } from "markdown-it";
import ParentType = MarkdownIt.StateBlock.ParentType;

type BlockquoteExtOptions = {
followingCharRegex: RegExp;
Expand All @@ -22,6 +19,8 @@ function blockquoteExt(
endLine: number,
silent: boolean
): boolean {
const md = new MarkdownIt();
const isSpace = (code: number) => md.utils.isSpace(code);
// NOTE: we're keeping the source as close to upstream as possible, so ignore errors like this
// eslint-disable-next-line no-var
var adjustTab,
Expand Down
6 changes: 2 additions & 4 deletions src/shared/markdown-it/stack-language-comments.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,4 @@
import MarkdownIt from "markdown-it/lib";
import StateBlock from "markdown-it/lib/rules_block/state_block";
import State from "markdown-it/lib/rules_core/state_core";
import MarkdownIt, { StateCore, StateBlock } from "markdown-it";

function langCommentParser(
matcher: RegExp,
Expand Down Expand Up @@ -92,7 +90,7 @@ function stack_lang_all(
}

// TODO document what the language-all rules are
function sanitizeCodeBlockLangs(state: State) {
function sanitizeCodeBlockLangs(state: StateCore) {
// keep track of which "language-all" has been set
let currentLanguageAll: string = null;

Expand Down
3 changes: 1 addition & 2 deletions src/shared/markdown-it/tag-link.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import MarkdownIt from "markdown-it/lib";
import StateInline from "markdown-it/lib/rules_inline/state_inline";
import MarkdownIt, { StateInline } from "markdown-it";
import type { TagLinkOptions } from "../view";

function parse_tag_link(
Expand Down
6 changes: 2 additions & 4 deletions src/shared/markdown-it/tight-list.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,4 @@
import MarkdownIt from "markdown-it";
import State from "markdown-it/lib/rules_core/state_core";
import Token from "markdown-it/lib/token";
import MarkdownIt, { StateCore, Token } from "markdown-it";

function tightenList(tokens: Token[]) {
let iteratedElements = 0;
Expand Down Expand Up @@ -51,7 +49,7 @@ function tightenList(tokens: Token[]) {
* Searches for and marks tight lists with a "tight" attribute
*/
export function tight_list(md: MarkdownIt): void {
md.core.ruler.push("tight-list", function (state: State) {
md.core.ruler.push("tight-list", function (state: StateCore) {
tightenList(state.tokens);
return false;
});
Expand Down
3 changes: 1 addition & 2 deletions src/shared/markdown-parser.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import MarkdownIt from "markdown-it/lib";
import Token from "markdown-it/lib/token";
import MarkdownIt, { Token } from "markdown-it";
import { defaultMarkdownParser, MarkdownParser } from "prosemirror-markdown";
import { NodeType, Schema } from "prosemirror-model";
import { IExternalPluginProvider } from "./editor-plugin";
Expand Down
6 changes: 4 additions & 2 deletions src/shared/markdown-serializer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,8 @@ import {
supportedTagAttributes,
TagType,
} from "./html-helpers";
import { normalizeReference } from "markdown-it/lib/common/utils";
import { IExternalPluginProvider } from "./editor-plugin";
import MarkdownIt from "markdown-it";

// helper type so the code is a tad less messy
export type MarkdownSerializerNodes = ConstructorParameters<
Expand Down Expand Up @@ -47,7 +47,9 @@ class SOMarkdownSerializerState extends MarkdownSerializerState {
href: string,
title?: string
): void {
const normalizedLabel = normalizeReference(label);
const normalizedLabel = new MarkdownIt().utils.normalizeReference(
label
);

if (this.linkReferenceDefinitions[normalizedLabel]) {
return;
Expand Down
2 changes: 1 addition & 1 deletion src/shared/prosemirror-plugins/plugin-extensions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ export class StatefulPluginKey<T> extends PluginKey<T> {
}

export class StatefulPlugin<T> extends Plugin<T> {
spec: StatefulPluginSpec<T>;
declare spec: StatefulPluginSpec<T>;

constructor(spec: StatefulPluginSpec<T>) {
super(spec);
Expand Down
Loading
Loading