mirror of
https://github.com/streetwriters/notesnook.git
synced 2025-12-29 08:29:16 +01:00
681 lines
16 KiB
JavaScript
Executable File
681 lines
16 KiB
JavaScript
Executable File
attachTitleInputListeners();
|
|
autosize();
|
|
function reactNativeEventHandler(type, value) {
|
|
if (window.ReactNativeWebView) {
|
|
window.ReactNativeWebView.postMessage(
|
|
JSON.stringify({
|
|
type: type,
|
|
value: value,
|
|
sessionId: sessionId
|
|
})
|
|
);
|
|
}
|
|
}
|
|
|
|
let DEFAULT_FONT_SIZE = '12pt';
|
|
let EDITOR_SETTINGS = {
|
|
fontSize: 12,
|
|
directionality: 'ltr'
|
|
};
|
|
|
|
function loadSettings() {
|
|
let settings = localStorage.getItem('editorSettings');
|
|
if (settings) {
|
|
settings = JSON.parse(settings);
|
|
EDITOR_SETTINGS = settings;
|
|
}
|
|
}
|
|
|
|
function changeDirection(rtl) {
|
|
loadSettings();
|
|
EDITOR_SETTINGS.directionality = 'ltr';
|
|
|
|
if (rtl) {
|
|
EDITOR_SETTINGS.directionality = 'rtl';
|
|
}
|
|
|
|
localStorage.setItem('editorSettings', JSON.stringify(EDITOR_SETTINGS));
|
|
if (rtl) {
|
|
tinymce.activeEditor.execCommand('mceDirectionRTL');
|
|
} else {
|
|
tinymce.activeEditor.execCommand('mceDirectionLTR');
|
|
}
|
|
reactNativeEventHandler('editorSettings', EDITOR_SETTINGS);
|
|
}
|
|
|
|
function changeFontSize(size) {
|
|
loadSettings();
|
|
DEFAULT_FONT_SIZE = `${size}pt`;
|
|
EDITOR_SETTINGS.fontSize = size;
|
|
localStorage.setItem('editorSettings', JSON.stringify(EDITOR_SETTINGS));
|
|
addStyle();
|
|
reactNativeEventHandler('editorSettings', EDITOR_SETTINGS);
|
|
}
|
|
|
|
function loadFontSize() {
|
|
loadSettings();
|
|
DEFAULT_FONT_SIZE = EDITOR_SETTINGS.fontSize + 'pt';
|
|
reactNativeEventHandler('editorSettings', EDITOR_SETTINGS);
|
|
}
|
|
|
|
let changeTimer = null;
|
|
let styleElement;
|
|
|
|
function addStyle() {
|
|
if (!tinymce.activeEditor) return;
|
|
if (!styleElement) {
|
|
let doc = tinymce.activeEditor.dom.doc;
|
|
styleElement = doc.head.appendChild(document.createElement('style'));
|
|
}
|
|
styleElement.innerHTML = `
|
|
body {
|
|
font-size:${DEFAULT_FONT_SIZE} !important;
|
|
}
|
|
`;
|
|
}
|
|
|
|
let undoTimer = null;
|
|
function onUndoChange() {
|
|
clearTimeout(undoTimer);
|
|
undoTimer = setTimeout(function () {
|
|
reactNativeEventHandler('history', {
|
|
undo: tinymce.activeEditor.undoManager.hasUndo(),
|
|
redo: tinymce.activeEditor.undoManager.hasRedo()
|
|
});
|
|
}, 1000);
|
|
}
|
|
|
|
function init_callback(_editor) {
|
|
editor = _editor;
|
|
setTheme();
|
|
|
|
editor.on('SelectionChange', function (e) {
|
|
selectchange();
|
|
});
|
|
|
|
editor.on('ClearUndos', onUndoChange);
|
|
editor.on('Undo', onUndoChange);
|
|
editor.on('Redo', onUndoChange);
|
|
editor.on('TypingUndos', onUndoChange);
|
|
editor.on('BeforeAddUndo', onUndoChange);
|
|
editor.on('AddUndo', onUndoChange);
|
|
editor.on('cut', function () {
|
|
onChange({ type: 'cut' });
|
|
onUndoChange();
|
|
});
|
|
editor.on('copy', onUndoChange);
|
|
editor.on('paste', function () {
|
|
onChange({ type: 'paste' });
|
|
});
|
|
|
|
editor.on('focus', function () {
|
|
reactNativeEventHandler('focus', 'editor');
|
|
});
|
|
|
|
editor.on('tap', function (e) {
|
|
if (e.target.classList.contains('mce-content-body') && !e.target.innerText.length > 0) {
|
|
e.preventDefault();
|
|
}
|
|
});
|
|
|
|
editor.on('ScrollIntoView', function (e) {
|
|
e.preventDefault();
|
|
e.elm.scrollIntoView({
|
|
behavior: 'smooth',
|
|
block: 'nearest'
|
|
});
|
|
});
|
|
|
|
editor.on('input ExecCommand ObjectResized Redo Undo ListMutation', onChange);
|
|
editor.on('keyup', function (e) {
|
|
if (e.key !== 'Backspace') return;
|
|
onChange(e);
|
|
});
|
|
}
|
|
|
|
const plugins = [
|
|
'checklist advlist autolink textpattern hr lists link noneditable image bettertable',
|
|
'searchreplace codeblock inlinecode keyboardquirks attachmentshandler collapsibleheaders',
|
|
'media imagetools table paste wordcount autoresize directionality blockescape contenthandler'
|
|
];
|
|
|
|
let isSafari = navigator.vendor.match(/apple/i);
|
|
|
|
let margins = '';
|
|
|
|
if (isSafari) {
|
|
margins = `margin-left:12px !important;
|
|
margin-right:12px !important;`;
|
|
}
|
|
|
|
const content_style = `
|
|
body {
|
|
font-family:"Open Sans";
|
|
overflow-x: hidden !important;
|
|
${margins}
|
|
}
|
|
|
|
|
|
span.diff-del {
|
|
background-color: #FDB0C0;
|
|
}
|
|
span.diff-ins {
|
|
background-color: #CAFFFB;
|
|
}
|
|
pre.codeblock {
|
|
overflow-x:auto;
|
|
}
|
|
img,
|
|
video {
|
|
max-width:100% !important;
|
|
height:auto !important;
|
|
border-radius:5px !important;
|
|
margin-bottom:10px;
|
|
}
|
|
|
|
|
|
.tox .tox-edit-area__iframe {
|
|
background-color:transparent !important;
|
|
}
|
|
|
|
.tox .tox-tbtn--select {
|
|
min-width: 120px;
|
|
}
|
|
|
|
body {
|
|
background-color:transparent !important;
|
|
font-size:${DEFAULT_FONT_SIZE}
|
|
}
|
|
.mce-preview-object,
|
|
iframe {
|
|
max-width:100% !important;
|
|
background-color:transparent !important;
|
|
height:auto !important;
|
|
border-radius:5px !important;
|
|
}
|
|
|
|
h1,
|
|
h2,
|
|
h3,
|
|
h4,
|
|
h5,
|
|
h6,
|
|
strong {
|
|
font-weight:600 !important;
|
|
position:relative;
|
|
padding-left:10px;
|
|
}
|
|
|
|
h1::before,
|
|
h2::before,
|
|
h3::before,
|
|
h4::before,
|
|
h5::before,
|
|
h6::before {
|
|
opacity: 1;
|
|
cursor: row-resize;
|
|
margin-right: 7px;
|
|
margin-left: -15px;
|
|
width: 24px;
|
|
height: 24px;
|
|
display: inline-block;
|
|
border: none;
|
|
position:absolute;
|
|
margin-left: -25px;
|
|
}
|
|
|
|
h1::before {
|
|
top:2px;
|
|
}
|
|
|
|
h2::before {
|
|
top:5px;
|
|
}
|
|
h3::before {
|
|
top:2px;
|
|
}
|
|
h4::before {
|
|
top:0px;
|
|
}
|
|
h5::before {
|
|
top:0px;
|
|
width: 20px;
|
|
height: 20px;
|
|
}
|
|
h6::before {
|
|
top:-1px;
|
|
width: 18px;
|
|
height: 18px;
|
|
}
|
|
|
|
.h {
|
|
display: none !important;
|
|
}
|
|
`;
|
|
|
|
function tableCellNodeOptions() {
|
|
let node = findNodeParent('td');
|
|
if (!node) {
|
|
node = findNodeParent('th');
|
|
}
|
|
if (!node) return;
|
|
let properties = {
|
|
width: node.style.width,
|
|
height: node.style.height,
|
|
backgroundColor: node.style.backgroundColor,
|
|
cellType: tinymce.activeEditor.queryCommandValue('mceTableCellType'),
|
|
colType: tinymce.activeEditor.queryCommandValue('mceTableColType')
|
|
};
|
|
reactNativeEventHandler('tablecelloptions', properties);
|
|
}
|
|
|
|
function findNodeParent(nodeName) {
|
|
let node = editor.selection.getNode();
|
|
let levels = 5;
|
|
for (let i = 0; i < levels; i++) {
|
|
if (!node) return;
|
|
if (node.nodeName.toLowerCase() === nodeName) {
|
|
return node;
|
|
} else {
|
|
node = node.parentNode;
|
|
}
|
|
}
|
|
return null;
|
|
}
|
|
|
|
function tableRowNodeOptions() {
|
|
let node = findNodeParent('tr');
|
|
|
|
if (node) {
|
|
let properties = {
|
|
backgroundColor: node.style.backgroundColor,
|
|
rowType: tinymce.activeEditor.queryCommandValue('mceTableRowType')
|
|
};
|
|
reactNativeEventHandler('tablerowoptions', properties);
|
|
}
|
|
}
|
|
|
|
function init_tiny(size) {
|
|
loadFontSize();
|
|
tinymce.init({
|
|
selector: '#tiny_textarea',
|
|
menubar: false,
|
|
min_height: size,
|
|
directionality: EDITOR_SETTINGS.directionality,
|
|
skin_url: 'dist/skins/notesnook',
|
|
content_css: 'dist/skins/notesnook',
|
|
attachmenthandler_download_attachment: function (hash) {
|
|
reactNativeEventHandler('attachment_download', hash);
|
|
},
|
|
custom_undo_redo_levels: 10,
|
|
plugins: plugins,
|
|
toolbar: false,
|
|
keep_styles: false,
|
|
paste_data_images: true,
|
|
statusbar: false,
|
|
textpattern_patterns: markdownPatterns,
|
|
contextmenu: false,
|
|
extended_valid_elements: `img[*|src=placeholder.svg]`,
|
|
invalid_styles: {
|
|
span: `--progress`
|
|
},
|
|
content_style: content_style,
|
|
browser_spellcheck: true,
|
|
autoresize_bottom_margin: 120,
|
|
table_toolbar:
|
|
'tcellprops trowprops | tableinsertrowafter tableinsertcolafter tabledeleterow tabledeletecol | tableconfig',
|
|
imagetools_toolbar: 'imagedownload | rotateleft rotateright flipv fliph | imageopts ',
|
|
placeholder: 'Start writing your note here',
|
|
object_resizing: true,
|
|
table_responsive_width: false,
|
|
table_sizing_mode: 'fixed',
|
|
table_column_resizing: 'resizetable',
|
|
resize: true,
|
|
mobile: {
|
|
resize: false,
|
|
object_resizing: false
|
|
},
|
|
image_description: false,
|
|
image_caption: false,
|
|
media_dimensions: false,
|
|
font_formats:
|
|
'Times New Roman=times new roman,times;' +
|
|
'Serif=serif;' +
|
|
'Open Sans=open sans;' +
|
|
'Classic=courier new;' +
|
|
'Mono=monospace;',
|
|
paste_postprocess: function (_, args) {
|
|
try {
|
|
window.processPastedContent(args.node);
|
|
} catch (e) {
|
|
console.error(e);
|
|
}
|
|
},
|
|
init_instance_callback: init_callback,
|
|
setup: setup_tiny
|
|
});
|
|
}
|
|
|
|
function setup_tiny(_editor) {
|
|
editor = _editor;
|
|
editor.ui.registry.addButton('deleteimage', {
|
|
icon: 'remove',
|
|
tooltip: 'Remove image',
|
|
onAction: function () {
|
|
editor.undoManager.transact(function () {
|
|
tinymce.activeEditor.execCommand('Delete');
|
|
});
|
|
},
|
|
onclick: function () {
|
|
editor.undoManager.transact(function () {
|
|
tinymce.activeEditor.execCommand('Delete');
|
|
});
|
|
}
|
|
});
|
|
|
|
editor.on('init', function (e) {
|
|
reactNativeEventHandler('status', true);
|
|
});
|
|
|
|
editor.ui.registry.addButton('deletevideo', {
|
|
icon: 'remove',
|
|
tooltip: 'Remove iframe',
|
|
onAction: function () {
|
|
editor.undoManager.transact(function () {
|
|
tinymce.activeEditor.execCommand('Delete');
|
|
});
|
|
},
|
|
onclick: function () {
|
|
editor.undoManager.transact(function () {
|
|
tinymce.activeEditor.execCommand('Delete');
|
|
});
|
|
}
|
|
});
|
|
|
|
editor.ui.registry.addContextToolbar('iframecontrols', {
|
|
predicate: function (node) {
|
|
return node.getAttribute('data-mce-object') === 'iframe';
|
|
},
|
|
items: 'deletevideo',
|
|
position: 'node',
|
|
scope: 'node'
|
|
});
|
|
|
|
editor.ui.registry.addButton('imageopts', {
|
|
icon: 'image-options',
|
|
tooltip: 'Image properties',
|
|
onAction: function () {
|
|
reactNativeEventHandler('imageoptions');
|
|
}
|
|
});
|
|
|
|
editor.ui.registry.addButton('tableconfig', {
|
|
icon: 'more-drawer',
|
|
tooltip: 'Table properties',
|
|
onAction: function (e) {
|
|
reactNativeEventHandler('tableconfig');
|
|
}
|
|
});
|
|
|
|
editor.ui.registry.addButton('trowprops', {
|
|
icon: 'table-row-properties',
|
|
tooltip: 'Row properties',
|
|
onAction: function (e) {
|
|
tableRowNodeOptions();
|
|
editor.blur();
|
|
}
|
|
});
|
|
|
|
editor.ui.registry.addButton('tcellprops', {
|
|
icon: 'table-cell-properties',
|
|
tooltip: 'Cell properties',
|
|
onAction: function (e) {
|
|
tableCellNodeOptions();
|
|
editor.blur();
|
|
}
|
|
});
|
|
|
|
editor.ui.registry.addButton('imagedownload', {
|
|
icon: 'save',
|
|
tooltip: 'Download image',
|
|
onAction: function (e) {
|
|
let node = tinymce.activeEditor.selection.getNode();
|
|
if (node.tagName === 'IMG' && node.dataset && node.dataset.hash) {
|
|
window.ReactNativeWebView.postMessage(
|
|
JSON.stringify({
|
|
type: 'attachment_download',
|
|
value: node.dataset.hash,
|
|
sessionId: sessionId
|
|
})
|
|
);
|
|
}
|
|
}
|
|
});
|
|
|
|
editor.ui.registry.addButton('imagepreview', {
|
|
icon: 'fullscreen',
|
|
tooltip: 'Preview image',
|
|
onAction: function () {
|
|
if (tinymce.activeEditor.selection.getNode().tagName === 'IMG') {
|
|
var xhr = new XMLHttpRequest();
|
|
xhr.responseType = 'blob';
|
|
|
|
xhr.onload = function () {
|
|
var recoveredBlob = xhr.response;
|
|
var reader = new FileReader();
|
|
reader.onload = function () {
|
|
var blobAsDataUrl = reader.result;
|
|
reactNativeEventHandler('imagepreview', blobAsDataUrl);
|
|
reader.abort();
|
|
xhr.abort();
|
|
};
|
|
reader.readAsDataURL(recoveredBlob);
|
|
};
|
|
xhr.open('GET', tinymce.activeEditor.selection.getNode().getAttribute('src'));
|
|
xhr.send();
|
|
}
|
|
}
|
|
});
|
|
}
|
|
|
|
let prevCount = 0;
|
|
|
|
function delay(base = 0) {
|
|
if (prevCount < 20000) return base + 100;
|
|
if (prevCount > 20000 && prevCount < 40000) return base + 250;
|
|
if (prevCount > 40000 && prevCount < 70000) return base + 500;
|
|
if (prevCount > 70000) return base + 1000;
|
|
}
|
|
|
|
let inputKeyTimer = 0;
|
|
function scrollSelectionIntoView(event) {
|
|
if (navigator.vendor.match(/apple/i)) return;
|
|
if (
|
|
event.type === 'input' &&
|
|
event.inputType !== 'deleteContentBackward' &&
|
|
event.data &&
|
|
event.data.endsWith('\n')
|
|
) {
|
|
clearTimeout(inputKeyTimer);
|
|
inputKeyTimer = setTimeout(function () {
|
|
let node = editor.selection.getNode();
|
|
if (node) {
|
|
node.scrollIntoView({ behavior: 'smooth', block: 'nearest' });
|
|
}
|
|
}, 1);
|
|
}
|
|
}
|
|
|
|
let noteedited = false;
|
|
const onChange = function (event) {
|
|
if (event.type && event.type.toLowerCase() === 'execcommand') {
|
|
if (
|
|
event.command.toLowerCase() === 'mcefocus' ||
|
|
event.command.toLowerCase() === 'mcerepaint' ||
|
|
event.command.toLowerCase() === 'mcedirectionrtl' ||
|
|
event.command.toLowerCase() === 'mcedirectionltr'
|
|
) {
|
|
return;
|
|
}
|
|
}
|
|
|
|
scrollSelectionIntoView(event);
|
|
|
|
if (isLoading) {
|
|
isLoading = false;
|
|
}
|
|
|
|
if (prevCount === 0) {
|
|
updateCount(0);
|
|
}
|
|
|
|
if (prevCount === 0 && event.type !== 'paste') return;
|
|
if (event.type !== 'compositionend') {
|
|
if (!noteedited) {
|
|
noteedited = true;
|
|
reactNativeEventHandler('noteedited');
|
|
}
|
|
}
|
|
|
|
clearTimeout(changeTimer);
|
|
changeTimer = null;
|
|
changeTimer = setTimeout(function () {
|
|
editor
|
|
.getHTML()
|
|
.then(function (html) {
|
|
reactNativeEventHandler('tiny', html);
|
|
})
|
|
.catch(function (e) {
|
|
reactNativeEventHandler('tinyerror', {
|
|
message: e?.message,
|
|
stack: e?.stack
|
|
});
|
|
});
|
|
|
|
onUndoChange();
|
|
selectchange();
|
|
}, delay());
|
|
};
|
|
|
|
let countTimer;
|
|
function updateCount(timer = 1000) {
|
|
countTimer = null;
|
|
|
|
if (!timer) {
|
|
let count = editor.countWords() || 0;
|
|
info = document.querySelector('.info-bar');
|
|
info.querySelector('#infowords').innerText = count + ' words';
|
|
prevCount = count;
|
|
} else {
|
|
countTimer = setTimeout(function () {
|
|
let count = editor.countWords() | 0;
|
|
info = document.querySelector('.info-bar');
|
|
info.querySelector('#infowords').innerText = count + ' words';
|
|
prevCount = count;
|
|
}, delay(timer));
|
|
}
|
|
}
|
|
|
|
function getNodeColor(element) {
|
|
if (element.style.color && element.style.color !== '') {
|
|
return element.style.color;
|
|
}
|
|
return null;
|
|
}
|
|
|
|
function getNodeBg(element) {
|
|
if (element.style.backgroundColor && element.style.backgroundColor !== '') {
|
|
return element.style.backgroundColor;
|
|
}
|
|
return null;
|
|
}
|
|
|
|
let selectionTimer = null;
|
|
function selectchange() {
|
|
clearTimeout(selectionTimer);
|
|
selectionTimer = null;
|
|
selectionTimer = setTimeout(function () {
|
|
updateCount();
|
|
let formats = Object.keys(editor.formatter.get());
|
|
let currentFormats = {};
|
|
editor.formatter.matchAll(formats).forEach(function (format) {
|
|
currentFormats[format] = true;
|
|
});
|
|
|
|
let node = editor.selection.getNode();
|
|
currentFormats.hilitecolor = getNodeBg(node);
|
|
currentFormats.forecolor = getNodeColor(node);
|
|
if (!currentFormats.hilitecolor || !currentFormats.forecolor) {
|
|
if (!/^(LI|UL|OL|DL|P|DIV)$/.test(node.nodeName)) {
|
|
for (var i = 0; i < node.children.length; i++) {
|
|
let item = editor.selection.getNode().children.item(i);
|
|
currentFormats.hilitecolor = getNodeBg(item);
|
|
currentFormats.forecolor = getNodeColor(item);
|
|
}
|
|
}
|
|
}
|
|
let range = editor.selection.getRng();
|
|
|
|
currentFormats.current = {
|
|
index: range.startOffset,
|
|
length: range.endOffset - range.startOffset
|
|
};
|
|
|
|
currentFormats.fontsize = editor.selection.getNode().style.fontSize;
|
|
|
|
if (currentFormats.fontsize === '') {
|
|
currentFormats.fontsize = DEFAULT_FONT_SIZE;
|
|
|
|
if (currentFormats.h2) {
|
|
currentFormats.fontsize = '18pt';
|
|
}
|
|
if (currentFormats.h3) {
|
|
currentFormats.fontsize = '14pt';
|
|
}
|
|
if (currentFormats.h4) {
|
|
currentFormats.fontsize = '12pt';
|
|
}
|
|
if (currentFormats.h5) {
|
|
currentFormats.fontsize = '10pt';
|
|
}
|
|
if (currentFormats.h6) {
|
|
currentFormats.fontsize = '8pt';
|
|
}
|
|
}
|
|
|
|
if (node.nodeName === 'A') {
|
|
currentFormats.link = node.getAttribute('href');
|
|
}
|
|
let isLinkNode = node.closest('a');
|
|
if (isLinkNode) {
|
|
currentFormats.link = isLinkNode.getAttribute('href');
|
|
}
|
|
|
|
currentFormats.fontname = editor.selection.getNode().style.fontFamily;
|
|
|
|
if (/^(LI|UL|OL|DL)$/.test(node.nodeName)) {
|
|
let listElm = editor.selection.getNode();
|
|
if (listElm.nodeName === 'LI') {
|
|
listElm = editor.dom.getParent(listElm, 'ol,ul');
|
|
}
|
|
|
|
let style = editor.dom.getStyle(listElm, 'listStyleType');
|
|
if (style === '') {
|
|
style = 'default';
|
|
}
|
|
if (listElm.nodeName === 'OL') {
|
|
currentFormats.ol = style;
|
|
} else {
|
|
if (!listElm) return;
|
|
if (listElm.className === 'tox-checklist') {
|
|
currentFormats.cl = true;
|
|
} else {
|
|
currentFormats.ul = style;
|
|
}
|
|
}
|
|
}
|
|
currentFormats.node = editor.selection.getNode().nodeName;
|
|
reactNativeEventHandler('selectionchange', currentFormats);
|
|
}, 50);
|
|
}
|