diff --git a/apps/editor/src/__test__/unit/convertor.spec.ts b/apps/editor/src/__test__/unit/convertor.spec.ts
index 52702f2650..bef09ec536 100644
--- a/apps/editor/src/__test__/unit/convertor.spec.ts
+++ b/apps/editor/src/__test__/unit/convertor.spec.ts
@@ -2,7 +2,7 @@ import { source, oneLineTrim } from 'common-tags';
import { Context, MdNode, Parser, HTMLConvertorMap } from '@toast-ui/toastmark';
-import { Schema } from 'prosemirror-model';
+import { Node, Schema } from 'prosemirror-model';
import { createSpecs } from '@/wysiwyg/specCreator';
import Convertor from '@/convertors/convertor';
@@ -595,7 +595,20 @@ describe('Convertor', () => {
| ![altText](imgUrl) **mixed**
- [linkText](linkUrl) mixed
|
`;
- assertConverting(markdown, `${markdown}\n`);
+ const expected = source`
+ | thead |
+ | ----- |
+ | |
+ | - ordered
|
+ | |
+ | |
+ | - mixed
|
+ | - mixed
|
+ | foobaz |
+ | ![altText](imgUrl) **mixed**- [linkText](linkUrl) mixed
|
+ `;
+
+ assertConverting(markdown, `${expected}\n`);
});
it('table with unmatched html list', () => {
@@ -1061,4 +1074,28 @@ describe('Convertor', () => {
assertConverting(markdown, markdown);
});
});
+
+ it('should convert by using HTML tag when delimiter is not preceded an alphanumeric', () => {
+ const wwNodeJson = {
+ type: 'doc',
+ content: [
+ {
+ type: 'paragraph',
+ content: [
+ {
+ type: 'text',
+ marks: [{ type: 'strong' }],
+ text: '"test"',
+ },
+ { type: 'text', text: 'a' },
+ ],
+ },
+ ],
+ };
+ const wwNode = Node.fromJSON(schema, wwNodeJson);
+
+ const result = convertor.toMarkdownText(wwNode);
+
+ expect(result).toBe(`"test"a`);
+ });
});
diff --git a/apps/editor/src/convertors/toMarkdown/toMdConvertorState.ts b/apps/editor/src/convertors/toMarkdown/toMdConvertorState.ts
index 544a186f58..99991a348f 100644
--- a/apps/editor/src/convertors/toMarkdown/toMdConvertorState.ts
+++ b/apps/editor/src/convertors/toMarkdown/toMdConvertorState.ts
@@ -1,6 +1,6 @@
import { Node, Mark } from 'prosemirror-model';
-import { includes, escape, last } from '@/utils/common';
+import { includes, escape, last, isEndWithSpace, isStartWithSpace } from '@/utils/common';
import { WwNodeType, WwMarkType } from '@t/wysiwyg';
import {
@@ -10,6 +10,7 @@ import {
FirstDelimFn,
InfoForPosSync,
} from '@t/convertor';
+import { DEFAULT_TEXT_NOT_START_OR_END_WITH_SPACE } from '@/utils/constants';
export default class ToMdConvertorState {
private readonly nodeTypeConvertors: ToMdNodeTypeConvertorMap;
@@ -49,11 +50,27 @@ export default class ToMdConvertorState {
return /(^|\n)$/.test(this.result);
}
+ private isBetweenSpaces(parent: Node, index: number) {
+ const { content } = parent;
+
+ const isFrontNodeEndWithSpace =
+ index === 0 ||
+ isEndWithSpace(content.child(index - 1).text ?? DEFAULT_TEXT_NOT_START_OR_END_WITH_SPACE);
+
+ const isRearNodeStartWithSpace =
+ index >= content.childCount - 1 ||
+ isStartWithSpace(content.child(index + 1).text ?? DEFAULT_TEXT_NOT_START_OR_END_WITH_SPACE);
+
+ return isFrontNodeEndWithSpace && isRearNodeStartWithSpace;
+ }
+
private markText(mark: Mark, entering: boolean, parent: Node, index: number) {
const convertor = this.getMarkConvertor(mark);
if (convertor) {
- const { delim, rawHTML } = convertor({ node: mark, parent, index }, entering);
+ const betweenSpace = this.isBetweenSpaces(parent, entering ? index : index - 1);
+
+ const { delim, rawHTML } = convertor({ node: mark, parent, index }, entering, betweenSpace);
return (rawHTML as string) || (delim as string);
}
diff --git a/apps/editor/src/convertors/toMarkdown/toMdConvertors.ts b/apps/editor/src/convertors/toMarkdown/toMdConvertors.ts
index 71066e7b18..8f1a190404 100644
--- a/apps/editor/src/convertors/toMarkdown/toMdConvertors.ts
+++ b/apps/editor/src/convertors/toMarkdown/toMdConvertors.ts
@@ -208,29 +208,44 @@ export const toMdConvertors: ToMdConvertorMap = {
};
},
- strong({ node }, { entering }) {
+ strong({ node }, { entering }, betweenSpace) {
const { rawHTML } = node.attrs;
+ let delim = '**';
+
+ if (!betweenSpace) {
+ delim = entering ? '' : '';
+ }
return {
- delim: '**',
+ delim,
rawHTML: entering ? getOpenRawHTML(rawHTML) : getCloseRawHTML(rawHTML),
};
},
- emph({ node }, { entering }) {
+ emph({ node }, { entering }, betweenSpace) {
const { rawHTML } = node.attrs;
+ let delim = '*';
+
+ if (!betweenSpace) {
+ delim = entering ? '' : '';
+ }
return {
- delim: '*',
+ delim,
rawHTML: entering ? getOpenRawHTML(rawHTML) : getCloseRawHTML(rawHTML),
};
},
- strike({ node }, { entering }) {
+ strike({ node }, { entering }, betweenSpace) {
const { rawHTML } = node.attrs;
+ let delim = '~~';
+
+ if (!betweenSpace) {
+ delim = entering ? '' : '';
+ }
return {
- delim: '~~',
+ delim,
rawHTML: entering ? getOpenRawHTML(rawHTML) : getCloseRawHTML(rawHTML),
};
},
@@ -353,7 +368,7 @@ function createMarkTypeConvertors(convertors: ToMdConvertorMap) {
const markTypes = Object.keys(markTypeOptions) as WwMarkType[];
markTypes.forEach((type) => {
- markTypeConvertors[type] = (nodeInfo, entering) => {
+ markTypeConvertors[type] = (nodeInfo, entering, betweenSpace) => {
const markOption = markTypeOptions[type];
const convertor = convertors[type];
@@ -362,7 +377,9 @@ function createMarkTypeConvertors(convertors: ToMdConvertorMap) {
// When calling the converter without using `delim` and `rawHTML` values,
// the converter is called without parameters.
const runConvertor = convertor && nodeInfo && !isUndefined(entering);
- const params = runConvertor ? convertor!(nodeInfo as MarkInfo, { entering }) : {};
+ const params = runConvertor
+ ? convertor!(nodeInfo as MarkInfo, { entering }, betweenSpace)
+ : {};
return { ...params, ...markOption };
};
diff --git a/apps/editor/src/utils/common.ts b/apps/editor/src/utils/common.ts
index 343eecc8d7..cd38e93083 100644
--- a/apps/editor/src/utils/common.ts
+++ b/apps/editor/src/utils/common.ts
@@ -257,3 +257,15 @@ export function assign(targetObj: Record, obj: Record
export function getSortedNumPair(valueA: number, valueB: number) {
return valueA > valueB ? [valueB, valueA] : [valueA, valueB];
}
+
+export function isStartWithSpace(text: string) {
+ const reStartWithSpace = /^\s(\S*)/g;
+
+ return reStartWithSpace.test(text);
+}
+
+export function isEndWithSpace(text: string) {
+ const reEndWithSpace = /(\S*)\s$/g;
+
+ return reEndWithSpace.test(text);
+}
diff --git a/apps/editor/src/utils/constants.ts b/apps/editor/src/utils/constants.ts
index 2d6cd54983..78d5adf33f 100644
--- a/apps/editor/src/utils/constants.ts
+++ b/apps/editor/src/utils/constants.ts
@@ -20,3 +20,5 @@ export const reBR = /
/i;
export const reHTMLComment = /|/;
export const ALTERNATIVE_TAG_FOR_BR = '';
+
+export const DEFAULT_TEXT_NOT_START_OR_END_WITH_SPACE = 'a';
diff --git a/apps/editor/types/convertor.d.ts b/apps/editor/types/convertor.d.ts
index e8ef7d5fc0..d44db7d00d 100644
--- a/apps/editor/types/convertor.d.ts
+++ b/apps/editor/types/convertor.d.ts
@@ -111,7 +111,8 @@ export type ToMdNodeTypeConvertorMap = Partial ToMdConvertorReturnValues & ToMdMarkTypeOption;
export type ToMdMarkTypeConvertorMap = Partial>;
@@ -124,7 +125,8 @@ interface ToMdConvertorContext {
type ToMdConvertor = (
nodeInfo: NodeInfo | MarkInfo,
- context: ToMdConvertorContext
+ context: ToMdConvertorContext,
+ betweenSpace?: boolean
) => ToMdConvertorReturnValues;
export type ToMdConvertorMap = Partial>;