Skip to content
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: 7 additions & 0 deletions .editorconfig
Original file line number Diff line number Diff line change
Expand Up @@ -18,3 +18,10 @@ indent_size = 4
[*.yml]
indent_style = space
indent_size = 2

[*.js]
ij_javascript_spaces_within_object_literal_braces = true
ij_javascript_keep_simple_blocks_in_one_line = true
ij_javascript_keep_simple_methods_in_one_line = true
Comment thread
NobodysNightmare marked this conversation as resolved.
ij_javascript_spaces_within_imports = true

2 changes: 1 addition & 1 deletion src/commonmark/commonmark.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,6 @@ import CommonMarkDataProcessor from './commonmarkdataprocessor';

// Simple plugin which loads the data processor.
export default function CommonMarkPlugin(editor) {
editor.data.processor = new CommonMarkDataProcessor(editor.editing.view.document);
editor.data.processor = new CommonMarkDataProcessor(editor);
}

123 changes: 87 additions & 36 deletions src/commonmark/commonmarkdataprocessor.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,16 +9,17 @@

/* eslint-env browser */

import {HtmlDataProcessor, ViewDomConverter} from '@ckeditor/ckeditor5-engine';
import {highlightedCodeBlock} from 'turndown-plugin-gfm';
import { HtmlDataProcessor, ViewDomConverter } from '@ckeditor/ckeditor5-engine';
import { highlightedCodeBlock } from 'turndown-plugin-gfm';
import TurndownService from 'turndown';
import {textNodesPreprocessor, linkPreprocessor, breaksPreprocessor} from './utils/preprocessor';
import {fixTasklistWhitespaces} from './utils/fix-tasklist-whitespaces';
import {hoistTaskListCheckboxes} from './utils/hoist-task-list-checkboxes';
import {fixBreaksInTables, fixBreaksInLists, fixBreaksOnRootLevel} from "./utils/fix-breaks";
import { textNodesPreprocessor, linkPreprocessor, breaksPreprocessor } from './utils/preprocessor';
import { fixTasklistWhitespaces } from './utils/fix-tasklist-whitespaces';
import { hoistTaskListCheckboxes } from './utils/hoist-task-list-checkboxes';
import { fixBreaksInTables, fixBreaksInLists, fixBreaksOnRootLevel } from "./utils/fix-breaks";
import markdownIt from 'markdown-it';
import markdownItTaskLists from 'markdown-it-task-lists';
import {isPageBreakNode, PAGE_BREAK_MARKDOWN} from "./utils/page-breaks";
import { isPageBreakNode, PAGE_BREAK_MARKDOWN } from "./utils/page-breaks";
import { getOPPath } from "../plugins/op-context/op-context";

export const originalSrcAttribute = 'data-original-src';

Expand Down Expand Up @@ -54,15 +55,51 @@ function workPackageRefInlineRule(state, silent) {
return true;
}

const WIKI_PAGE_LINK_RE = /(?:\\\[){3}([0-9]+):([^\\\n]+)(?:\\\]){3}/;

function wikiPageLinkInlineRule(state, silent, editor) {
const start = state.pos;
const src = state.src;

if (src.charCodeAt(start) !== 0x5c /* \ */) return false;

const word = src.slice(start);
const match = WIKI_PAGE_LINK_RE.exec(word);
if (!match) return false;
if (silent) return true;

const providerId = match[1];
const pageIdentifier = match[2];

const frameId = crypto.randomUUID();
const frameSrc = getOPPath(editor).wikiPageLinkMacro(providerId, pageIdentifier, frameId)

const html = `
<turbo-frame
id="${frameId}"
src="${frameSrc}"
data-provider-id="${providerId}"
data-page-identifier="${pageIdentifier}"
data-type="wiki-page-link"
></turbo-frame>`

const token = state.push('html_inline', '', 0);
token.content = html;
state.pos = start + match[0].length;
return true;
}

/**
* This data processor implementation uses CommonMark as input/output data.
*
* @implements module:engine/dataprocessor/dataprocessor~DataProcessor
*/
export default class CommonMarkDataProcessor {
constructor(document) {
constructor(editor) {
const document = editor.editing.view.document;
this._htmlDP = new HtmlDataProcessor(document);
this._domConverter = new ViewDomConverter(document);
this.editor = editor;
}

/**
Expand All @@ -81,9 +118,14 @@ export default class CommonMarkDataProcessor {
});

// Use tasklist plugin
let parser = md.use(markdownItTaskLists, {label: true});
let parser = md.use(markdownItTaskLists, { label: true });

parser.inline.ruler.before('text', 'op_workpackage_ref', workPackageRefInlineRule);
parser.inline.ruler.before(
'text',
'op_wiki_page_link',
(state, silent) => wikiPageLinkInlineRule(state, silent, this.editor)
);

const previousRenderer = parser.renderer.rules.code_block;
md.renderer.rules.code_block = function (tokens, idx, options, env, self) {
Expand Down Expand Up @@ -207,36 +249,36 @@ export default class CommonMarkDataProcessor {
* @see
*/
turndownService.addRule('orderedListItems', {
filter: function(node) {
filter: function (node) {
if (node.nodeName !== 'LI') {
return false;
}

return !!node.closest('ol');
},
replacement: function (content, node, options) {
content = content
.replace(/^\n+/, '') // remove leading newlines
.replace(/\n+$/, '\n'); // replace trailing newlines with just a single one

var parent = node.parentNode;
var prefix = options.bulletListMarker + ' ';
var number = 1;
if (parent.nodeName === 'OL') {
var start = parent.getAttribute('start');
var index = Array.prototype.indexOf.call(parent.children, node);
number = start ? Number(start) + index : index + 1;
prefix = number + '. ';
}

// Calculate indentation based on the width of the number prefix
var indentWidth = prefix.length;
var indent = ' '.repeat(indentWidth);
content = content.replace(/\n/gm, '\n' + indent);

return (
prefix + content + (node.nextSibling && !/\n$/.test(content) ? '\n' : '')
);
content = content
.replace(/^\n+/, '') // remove leading newlines
.replace(/\n+$/, '\n'); // replace trailing newlines with just a single one

var parent = node.parentNode;
var prefix = options.bulletListMarker + ' ';
var number = 1;
if (parent.nodeName === 'OL') {
var start = parent.getAttribute('start');
var index = Array.prototype.indexOf.call(parent.children, node);
number = start ? Number(start) + index : index + 1;
prefix = number + '. ';
}

// Calculate indentation based on the width of the number prefix
var indentWidth = prefix.length;
var indent = ' '.repeat(indentWidth);
content = content.replace(/\n/gm, '\n' + indent);

return (
prefix + content + (node.nextSibling && !/\n$/.test(content) ? '\n' : '')
);
}
});

Expand Down Expand Up @@ -280,10 +322,10 @@ export default class CommonMarkDataProcessor {
replacement: function (_content, node) {
// Remove filler attribute, but keep empty lines
node.querySelectorAll('td br[data-cke-filler]').forEach((node) => {
if (node.nextElementSibling) {
node.removeAttribute('data-cke-filler');
}
});
if (node.nextElementSibling) {
node.removeAttribute('data-cke-filler');
}
});

return node.outerHTML;
}
Expand All @@ -306,6 +348,15 @@ export default class CommonMarkDataProcessor {
},
});

turndownService.addRule('wikiPageLink', {
filter: (node) => node.nodeName === 'TURBO-FRAME' && node.getAttribute('data-type') === 'wiki-page-link',
replacement: (_content, node) => {
const providerId = node.getAttribute('data-provider-id') || '';
const pageIdentifier = node.getAttribute('data-page-identifier') || '';
return `\\[\\[\\[${providerId}:${pageIdentifier}\\]\\]\\]`;
},
});

turndownService.addRule('openProjectMacros', {
filter: ['macro'],
replacement: (_content, node) => {
Expand Down
2 changes: 2 additions & 0 deletions src/op-plugins.js
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ import { PageBreak } from '@ckeditor/ckeditor5-page-break';
import { Autosave } from '@ckeditor/ckeditor5-autosave';
import OpContentRevisions from "./plugins/op-content-revisions/op-content-revisions";
import OPMacroWpQuickinfoPlugin from "./plugins/op-macro-wp-quickinfo/op-macro-wp-quickinfo-plugin";
import OpMacroWikiPageLinkPlugin from "./plugins/op-macro-wiki-page-link/op-macro-wiki-page-link-plugin";

// We divide our plugins into separate concerns here
// in order to enable / disable each group by configuration
Expand Down Expand Up @@ -93,6 +94,7 @@ export const builtinPlugins = [
OPSourceCodePlugin,
OpContentRevisions,
OPMacroWpQuickinfoPlugin,
OpMacroWikiPageLinkPlugin,
CodeBlockPlugin,

CommonMark,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
import { Plugin } from "@ckeditor/ckeditor5-core";
import { toWidget } from "@ckeditor/ckeditor5-widget";
import { getOPPath } from "../op-context/op-context";

const MODEL_ELEMENT_NAME = 'op-macro-wiki-page-link';

export default class OpMacroWikiPageLinkPlugin extends Plugin {
static get pluginName() {
return 'OpMacroWikiPageLink';
}

init() {
const editor = this.editor;
const conversion = editor.conversion;

editor.model.schema.register(MODEL_ELEMENT_NAME, {
allowWhere: '$text',
isInline: true,
isObject: true,
allowAttributes: ['providerId', 'pageIdentifier'],
});

conversion.for('upcast').elementToElement({
view: { name: 'turbo-frame', attributes: { 'data-type': 'wiki-page-link' } },
model: (viewElement, { writer }) => {
const providerId = viewElement.getAttribute('data-provider-id');
const pageIdentifier = viewElement.getAttribute('data-page-identifier');
return writer.createElement(MODEL_ELEMENT_NAME, { providerId, pageIdentifier });
},
});

conversion.for('dataDowncast').elementToElement({
model: MODEL_ELEMENT_NAME,
view: (modelElement, { writer }) => {
const providerId = modelElement.getAttribute('providerId');
const pageIdentifier = modelElement.getAttribute('pageIdentifier');
const frameId = crypto.randomUUID();

const container = writer.createContainerElement(
'turbo-frame',
{
id: frameId,
src: this.macroUrl(providerId, pageIdentifier, frameId),
'data-provider-id': providerId,
'data-page-identifier': pageIdentifier,
'data-type': 'wiki-page-link',
});

const ref = `[[[${providerId}:${pageIdentifier}]]]`;
writer.insert(writer.createPositionAt(container, 0), writer.createText(ref));
return container;
},
});

conversion.for('editingDowncast').elementToElement({
model: MODEL_ELEMENT_NAME,
view: (modelElement, { writer }) => {
const providerId = modelElement.getAttribute('providerId');
const pageIdentifier = modelElement.getAttribute('pageIdentifier');
const frameId = crypto.randomUUID();

const wrapper = writer.createContainerElement('span', {
class: 'my-wiki-page-link-widget',
});

const raw = writer.createRawElement(
'turbo-frame',
{
id: frameId,
src: this.macroUrl(providerId, pageIdentifier, frameId),
'data-provider-id': providerId,
'data-page-identifier': pageIdentifier,
'data-type': 'wiki-page-link',
},
() => { },
);

writer.insert(writer.createPositionAt(wrapper, 0), raw);
return toWidget(wrapper, writer, { label: `[[[${providerId}:${pageIdentifier}]]]` });
}
});
}

macroUrl(providerId, pageIdentifier, frameId) {
return getOPPath(this.editor).wikiPageLinkMacro(providerId, pageIdentifier, frameId)
}
}
15 changes: 11 additions & 4 deletions tests/commonmark/_utils/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,10 @@
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/

import { _stringifyView as stringify } from "@ckeditor/ckeditor5-engine";
import { EditingView, StylesProcessor } from "@ckeditor/ckeditor5-engine";

import MarkdownDataProcessor from '../../../src/commonmark/commonmarkdataprocessor';
import {_stringifyView as stringify} from "@ckeditor/ckeditor5-engine";
import {StylesProcessor, ViewDocument} from "@ckeditor/ckeditor5-engine";

/**
* Tests MarkdownDataProcessor.
Expand All @@ -20,9 +21,9 @@ import {StylesProcessor, ViewDocument} from "@ckeditor/ckeditor5-engine";
* @returns {module:engine/view/documentfragment~DocumentFragment}
*/
export function testDataProcessor(markdown, viewString, normalizedMarkdown, options) {
const viewDocument = new ViewDocument(new StylesProcessor());
const editor = createTestEditor();

const dataProcessor = new MarkdownDataProcessor(viewDocument);
const dataProcessor = new MarkdownDataProcessor(editor);

if (options && options.setup) {
options.setup(dataProcessor);
Expand All @@ -46,6 +47,12 @@ export function testDataProcessor(markdown, viewString, normalizedMarkdown, opti
return viewFragment;
}

export function createTestEditor() {
const view = new EditingView(new StylesProcessor());

return { editing: { view } };
}

function cleanHtml(html) {
// Space between table elements.
html = html.replace(/(th|td|tr)>\s+<(\/?(?:th|td|tr))/g, '$1><$2');
Expand Down
36 changes: 17 additions & 19 deletions tests/commonmark/escaping.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,25 +4,24 @@
*/

import MarkdownDataProcessor from '../../src/commonmark/commonmarkdataprocessor';
import {_stringifyView as stringify} from '@ckeditor/ckeditor5-engine';
import {testDataProcessor} from './_utils/utils.js';
import {StylesProcessor, ViewDocument} from "@ckeditor/ckeditor5-engine";
import { _stringifyView as stringify } from '@ckeditor/ckeditor5-engine';
import { testDataProcessor, createTestEditor } from './_utils/utils.js';

const testCases = {
'backslash': {test: '\\\\', result: '\\'},
'underscore': {test: '\\_', result: '_'},
'left brace': {test: '\\{', result: '{'},
'right brace': {test: '\\}', result: '}'},
'left bracket': {test: '\\[', result: '['},
'right bracket': {test: '\\]', result: ']'},
'left paren': {test: '\\(', result: '('},
'right paren': {test: '\\)', result: ')'},
'greater than': {test: '\\>', result: '>'},
'hash': {test: '\\#', result: '#'},
'period': {test: '\\.', result: '.'},
'exclamation mark': {test: '\\!', result: '!'},
'plus': {test: '\\+', result: '+'},
'minus': {test: '\\-', result: '-'}
'backslash': { test: '\\\\', result: '\\' },
'underscore': { test: '\\_', result: '_' },
'left brace': { test: '\\{', result: '{' },
'right brace': { test: '\\}', result: '}' },
'left bracket': { test: '\\[', result: '[' },
'right bracket': { test: '\\]', result: ']' },
'left paren': { test: '\\(', result: '(' },
'right paren': { test: '\\)', result: ')' },
'greater than': { test: '\\>', result: '>' },
'hash': { test: '\\#', result: '#' },
'period': { test: '\\.', result: '.' },
'exclamation mark': { test: '\\!', result: '!' },
'plus': { test: '\\+', result: '+' },
'minus': { test: '\\-', result: '-' }
};

describe('Commonmark', () => {
Expand All @@ -31,8 +30,7 @@ describe('Commonmark', () => {
let dataProcessor;

beforeEach(() => {
const viewDocument = new ViewDocument(new StylesProcessor());
dataProcessor = new MarkdownDataProcessor(viewDocument);
dataProcessor = new MarkdownDataProcessor(createTestEditor());
});

for (const key in testCases) {
Expand Down
Loading