2020-10-21 01:23:55 +02:00
|
|
|
import { RuleOptions } from '../../MdToHtml';
|
2020-03-23 02:47:25 +02:00
|
|
|
|
2020-10-21 01:23:55 +02:00
|
|
|
let checkboxIndex_ = -1;
|
2020-03-23 02:47:25 +02:00
|
|
|
|
2020-11-12 21:13:28 +02:00
|
|
|
function pluginAssets(theme: any) {
|
2020-03-23 02:47:25 +02:00
|
|
|
return [
|
|
|
|
{
|
|
|
|
inline: true,
|
|
|
|
mime: 'text/css',
|
|
|
|
text: `
|
2020-10-21 01:23:55 +02:00
|
|
|
/*
|
|
|
|
FOR THE MARKDOWN EDITOR
|
|
|
|
*/
|
|
|
|
|
2020-03-23 02:47:25 +02:00
|
|
|
/* Remove the indentation from the checkboxes at the root of the document
|
|
|
|
(otherwise they are too far right), but keep it for their children to allow
|
|
|
|
nested lists. Make sure this value matches the UL margin. */
|
|
|
|
|
|
|
|
li.md-checkbox {
|
|
|
|
list-style-type: none;
|
|
|
|
}
|
|
|
|
|
|
|
|
li.md-checkbox input[type=checkbox] {
|
|
|
|
margin-left: -1.71em;
|
|
|
|
margin-right: 0.7em;
|
|
|
|
}
|
2020-10-21 01:23:55 +02:00
|
|
|
|
2020-03-23 02:47:25 +02:00
|
|
|
ul.joplin-checklist {
|
|
|
|
list-style:none;
|
|
|
|
}
|
|
|
|
|
2020-10-21 01:23:55 +02:00
|
|
|
/*
|
|
|
|
FOR THE RICH TEXT EDITOR
|
|
|
|
*/
|
|
|
|
|
2020-03-23 02:47:25 +02:00
|
|
|
ul.joplin-checklist li::before {
|
|
|
|
content:"\\f14a";
|
2020-05-17 16:34:42 +02:00
|
|
|
font-family:"Font Awesome 5 Free";
|
2020-03-23 02:47:25 +02:00
|
|
|
background-size: 16px 16px;
|
|
|
|
pointer-events: all;
|
|
|
|
cursor: pointer;
|
|
|
|
width: 1em;
|
|
|
|
height: 1em;
|
|
|
|
margin-left: -1.3em;
|
|
|
|
position: absolute;
|
2020-06-10 23:08:59 +02:00
|
|
|
color: ${theme.color};
|
2020-03-23 02:47:25 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
.joplin-checklist li:not(.checked)::before {
|
|
|
|
content:"\\f0c8";
|
|
|
|
}`,
|
|
|
|
},
|
|
|
|
];
|
2020-10-21 01:23:55 +02:00
|
|
|
}
|
2020-03-23 02:47:25 +02:00
|
|
|
|
2022-04-05 16:42:06 +02:00
|
|
|
function createPrefixTokens(Token: any, id: string, checked: boolean, label: string, postMessageSyntax: string, sourceToken: any, disabled: boolean): any[] {
|
2020-03-23 02:47:25 +02:00
|
|
|
let token = null;
|
|
|
|
const tokens = [];
|
|
|
|
|
|
|
|
// A bit hard to handle errors here and it's unlikely that the token won't have a valid
|
|
|
|
// map parameter, but if it does set it to a very high value, which will be more easy to notice
|
|
|
|
// in calling code.
|
|
|
|
const lineIndex = sourceToken.map && sourceToken.map.length ? sourceToken.map[0] : 99999999;
|
|
|
|
const checkedString = checked ? 'checked' : 'unchecked';
|
|
|
|
|
|
|
|
const labelId = `cb-label-${id}`;
|
|
|
|
|
|
|
|
const js = `
|
|
|
|
try {
|
|
|
|
if (this.checked) {
|
|
|
|
this.setAttribute('checked', 'checked');
|
|
|
|
} else {
|
|
|
|
this.removeAttribute('checked');
|
|
|
|
}
|
|
|
|
|
|
|
|
${postMessageSyntax}('checkboxclick:${checkedString}:${lineIndex}');
|
|
|
|
const label = document.getElementById("${labelId}");
|
|
|
|
label.classList.remove(this.checked ? 'checkbox-label-unchecked' : 'checkbox-label-checked');
|
|
|
|
label.classList.add(this.checked ? 'checkbox-label-checked' : 'checkbox-label-unchecked');
|
|
|
|
} catch (error) {
|
|
|
|
console.warn('Checkbox ${checkedString}:${lineIndex} error', error);
|
|
|
|
}
|
|
|
|
return true;
|
|
|
|
`;
|
|
|
|
|
|
|
|
token = new Token('checkbox_wrapper_open', 'div', 1);
|
|
|
|
token.attrs = [['class', 'checkbox-wrapper']];
|
|
|
|
tokens.push(token);
|
|
|
|
|
|
|
|
token = new Token('checkbox_input', 'input', 0);
|
|
|
|
token.attrs = [['type', 'checkbox'], ['id', id], ['onclick', js]];
|
2022-04-05 16:42:06 +02:00
|
|
|
if (disabled) token.attrs.push(['disabled', 'disabled']);
|
2020-03-23 02:47:25 +02:00
|
|
|
if (checked) token.attrs.push(['checked', 'checked']);
|
|
|
|
tokens.push(token);
|
|
|
|
|
|
|
|
token = new Token('label_open', 'label', 1);
|
|
|
|
token.attrs = [['id', labelId], ['for', id], ['class', `checkbox-label-${checkedString}`]];
|
|
|
|
tokens.push(token);
|
|
|
|
|
|
|
|
if (label) {
|
|
|
|
token = new Token('text', '', 0);
|
|
|
|
token.content = label;
|
|
|
|
tokens.push(token);
|
|
|
|
}
|
|
|
|
|
|
|
|
return tokens;
|
|
|
|
}
|
|
|
|
|
2020-11-12 21:13:28 +02:00
|
|
|
function createSuffixTokens(Token: any): any[] {
|
2020-03-23 02:47:25 +02:00
|
|
|
return [
|
|
|
|
new Token('label_close', 'label', -1),
|
|
|
|
new Token('checkbox_wrapper_close', 'div', -1),
|
|
|
|
];
|
|
|
|
}
|
|
|
|
|
2020-11-12 21:13:28 +02:00
|
|
|
function checkboxPlugin(markdownIt: any, options: RuleOptions) {
|
2020-10-21 01:23:55 +02:00
|
|
|
const renderingType = options.checkboxRenderingType || 1;
|
2020-03-23 02:47:25 +02:00
|
|
|
|
2020-11-12 21:13:28 +02:00
|
|
|
markdownIt.core.ruler.push('checkbox', (state: any) => {
|
2020-03-23 02:47:25 +02:00
|
|
|
const tokens = state.tokens;
|
|
|
|
const Token = state.Token;
|
|
|
|
|
|
|
|
const checkboxPattern = /^\[([x|X| ])\] (.*)$/;
|
|
|
|
let currentListItem = null;
|
|
|
|
let processedFirstInline = false;
|
|
|
|
const lists = [];
|
|
|
|
for (let i = 0; i < tokens.length; i++) {
|
|
|
|
const token = tokens[i];
|
|
|
|
|
|
|
|
if (token.type === 'bullet_list_open') {
|
|
|
|
lists.push(token);
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (token.type === 'bullet_list_close') {
|
|
|
|
lists.pop();
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (token.type === 'list_item_open') {
|
|
|
|
currentListItem = token;
|
|
|
|
processedFirstInline = false;
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (token.type === 'list_item_close') {
|
|
|
|
currentListItem = null;
|
|
|
|
processedFirstInline = false;
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Note that we only support list items that start with "-" (not with "*")
|
|
|
|
if (currentListItem && currentListItem.markup === '-' && !processedFirstInline && token.type === 'inline') {
|
|
|
|
processedFirstInline = true;
|
|
|
|
const firstChild = token.children && token.children.length ? token.children[0] : null;
|
|
|
|
if (!firstChild) continue;
|
|
|
|
|
|
|
|
const matches = checkboxPattern.exec(firstChild.content);
|
|
|
|
if (!matches || matches.length < 2) continue;
|
|
|
|
|
|
|
|
const checked = matches[1] !== ' ';
|
|
|
|
const label = matches.length >= 3 ? matches[2] : '';
|
|
|
|
|
|
|
|
const currentList = lists[lists.length - 1];
|
|
|
|
|
2020-10-21 01:23:55 +02:00
|
|
|
if (renderingType === 1) {
|
2020-03-23 02:47:25 +02:00
|
|
|
checkboxIndex_++;
|
|
|
|
const id = `md-checkbox-${checkboxIndex_}`;
|
|
|
|
|
|
|
|
// Prepend the text content with the checkbox markup and the opening <label> tag
|
|
|
|
// then append the </label> tag at the end of the text content.
|
|
|
|
|
2022-04-05 16:42:06 +02:00
|
|
|
const prefix = createPrefixTokens(Token, id, checked, label, options.postMessageSyntax, token, !!options.checkboxDisabled);
|
2020-03-23 02:47:25 +02:00
|
|
|
const suffix = createSuffixTokens(Token);
|
|
|
|
|
|
|
|
token.children = markdownIt.utils.arrayReplaceAt(token.children, 0, prefix);
|
|
|
|
token.children = token.children.concat(suffix);
|
|
|
|
|
|
|
|
// Add a class to the <li> container so that it can be targetted with CSS.
|
|
|
|
|
|
|
|
let itemClass = currentListItem.attrGet('class');
|
|
|
|
if (!itemClass) itemClass = '';
|
|
|
|
itemClass += ' md-checkbox joplin-checkbox';
|
|
|
|
currentListItem.attrSet('class', itemClass.trim());
|
|
|
|
} else {
|
|
|
|
const textToken = new Token('text', '', 0);
|
|
|
|
textToken.content = label;
|
|
|
|
const tokens = [];
|
|
|
|
tokens.push(textToken);
|
|
|
|
|
|
|
|
token.children = markdownIt.utils.arrayReplaceAt(token.children, 0, tokens);
|
|
|
|
|
|
|
|
const listClass = currentList.attrGet('class') || '';
|
|
|
|
if (listClass.indexOf('joplin-') < 0) currentList.attrSet('class', (`${listClass} joplin-checklist`).trim());
|
|
|
|
|
|
|
|
if (checked) {
|
|
|
|
currentListItem.attrSet('class', (`${currentListItem.attrGet('class') || ''} checked`).trim());
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
export default {
|
2020-10-21 01:23:55 +02:00
|
|
|
plugin: checkboxPlugin,
|
|
|
|
assets: pluginAssets,
|
2020-03-23 02:47:25 +02:00
|
|
|
};
|