This commit is contained in:
杜恒 2021-03-29 11:02:01 +08:00
parent e97641ca99
commit fc470d46ea
9 changed files with 2489 additions and 2435 deletions

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

View File

@ -1,7 +1,7 @@
import { undo, redo } from '@codemirror/history';
export default class JoeAction {
constructor() {
$('body').append(`
constructor() {
$('body').append(`
<div class="cm-modal">
<div class="cm-modal__wrapper">
<div class="cm-modal__wrapper-header">
@ -16,186 +16,186 @@ export default class JoeAction {
</div>
</div>
`);
$('.cm-modal__wrapper-footer--cancle, .cm-modal__wrapper-header--close').on('click', () => $('.cm-modal').removeClass('active'));
$('.cm-modal__wrapper-footer--confirm').on('click', () => {
this.options.confirm();
$('.cm-modal').removeClass('active');
});
}
_openModal(options = {}) {
const _options = {
title: '提示',
innerHtml: '内容',
hasFooter: true,
confirm: () => {},
handler: () => {}
};
this.options = Object.assign(_options, options);
$('.cm-modal__wrapper-header--text').html(this.options.title);
$('.cm-modal__wrapper-bodyer').html(this.options.innerHtml);
this.options.hasFooter ? $('.cm-modal__wrapper-footer').show() : $('.cm-modal__wrapper-footer').hide();
$('.cm-modal').addClass('active');
this.options.handler();
}
_getLineCh(cm) {
const head = cm.state.selection.main.head;
const line = cm.state.doc.lineAt(head);
return head - line.from;
}
_replaceSelection(cm, str) {
cm.dispatch(cm.state.replaceSelection(str));
}
_setCursor(cm, pos) {
cm.dispatch({ selection: { anchor: pos } });
}
_getSelection(cm) {
return cm.state.sliceDoc(cm.state.selection.main.from, cm.state.selection.main.to);
}
_insetAmboText(cm, str) {
const cursor = cm.state.selection.main.head;
const selection = this._getSelection(cm);
this._replaceSelection(cm, ` ${str + selection + str} `);
if (selection === '') this._setCursor(cm, cursor + str.length + 1);
cm.focus();
}
_createTableLists(cm, url, activeTab = '', modalTitle) {
$.ajax({
url,
dataType: 'json',
success: res => {
let tabbarStr = '';
let listsStr = '';
for (let key in res) {
const arr = res[key].split(' ');
tabbarStr += `<div class="tabbar-item ${key === activeTab ? 'active' : ''}" data-show="${key}">${key}</div>`;
listsStr += `<div class="lists ${key === activeTab ? 'active' : ''}" data-show="${key}">${arr.map(item => `<div class="lists-item" data-text="${item}">${item}</div>`).join(' ')}</div>`;
}
this._openModal({
title: modalTitle,
hasFooter: false,
innerHtml: `<div class="tabbar">${tabbarStr}</div>${listsStr}`,
handler: () => {
$('.cm-modal__wrapper-bodyer .tabbar-item').on('click', function () {
const activeTab = $(this);
const show = activeTab.attr('data-show');
const tabbar = $('.cm-modal__wrapper-bodyer .tabbar');
activeTab.addClass('active').siblings().removeClass('active');
tabbar.stop().animate({
scrollLeft: activeTab[0].offsetLeft - tabbar[0].offsetWidth / 2 + activeTab[0].offsetWidth / 2 - 15
});
$('.cm-modal__wrapper-bodyer .lists').removeClass('active');
$(".cm-modal__wrapper-bodyer .lists[data-show='" + show + "']").addClass('active');
});
const _this = this;
$('.cm-modal__wrapper-bodyer .lists-item').on('click', function () {
const text = $(this).attr('data-text');
_this._replaceSelection(cm, ` ${text} `);
$('.cm-modal').removeClass('active');
cm.focus();
});
}
});
}
});
}
handleFullScreen(el) {
el.toggleClass('active');
$('body').toggleClass('fullscreen');
$('.cm-container').toggleClass('fullscreen');
$('.cm-preview').width(0);
}
handlePublish() {
$('#btn-submit').click();
}
handleUndo(cm) {
undo(cm);
cm.focus();
}
handleRedo(cm) {
redo(cm);
cm.focus();
}
handleIndent(cm) {
this._replaceSelection(cm, ' ');
cm.focus();
}
handleTime(cm) {
const time = new Date();
const _Year = time.getFullYear();
const _Month = String(time.getMonth() + 1).padStart(2, 0);
const _Date = String(time.getDate()).padStart(2, 0);
const _Hours = String(time.getHours()).padStart(2, 0);
const _Minutes = String(time.getMinutes()).padStart(2, 0);
const _Seconds = String(time.getSeconds()).padStart(2, 0);
const _Day = ['星期日', '星期一', '星期二', '星期三', '星期四', '星期五', '星期六'][time.getDay()];
const _time = `${this._getLineCh(cm) ? '\n' : ''}${_Year}-${_Month}-${_Date} ${_Hours}:${_Minutes}:${_Seconds} ${_Day}\n`;
this._replaceSelection(cm, _time);
cm.focus();
}
handleHr(cm) {
const str = `${this._getLineCh(cm) ? '\n' : ''}\n------------\n\n`;
this._replaceSelection(cm, str);
cm.focus();
}
handleClean(cm) {
cm.dispatch({ changes: { from: 0, to: cm.state.doc.length, insert: '' } });
cm.focus();
}
handleOrdered(cm) {
const selection = this._getSelection(cm);
if (selection === '') {
const str = (this._getLineCh(cm) ? '\n\n' : '') + '1. ';
this._replaceSelection(cm, str);
} else {
const selectionText = selection.split('\n');
for (let i = 0, len = selectionText.length; i < len; i++) {
selectionText[i] = selectionText[i] === '' ? '' : i + 1 + '. ' + selectionText[i];
}
const str = (this._getLineCh(cm) ? '\n' : '') + selectionText.join('\n');
this._replaceSelection(cm, str);
}
cm.focus();
}
handleUnordered(cm) {
const selection = this._getSelection(cm);
if (selection === '') {
const str = (this._getLineCh(cm) ? '\n' : '') + '- ';
this._replaceSelection(cm, str);
} else {
const selectionText = selection.split('\n');
for (let i = 0, len = selectionText.length; i < len; i++) {
selectionText[i] = selectionText[i] === '' ? '' : '- ' + selectionText[i];
}
const str = (this._getLineCh(cm) ? '\n' : '') + selectionText.join('\n');
this._replaceSelection(cm, str);
}
cm.focus();
}
handleQuote(cm) {
const selection = this._getSelection(cm);
if (selection === '') {
this._replaceSelection(cm, `${this._getLineCh(cm) ? '\n' : ''}> `);
} else {
const selectionText = selection.split('\n');
for (let i = 0, len = selectionText.length; i < len; i++) {
selectionText[i] = selectionText[i] === '' ? '' : '> ' + selectionText[i];
}
const str = (this._getLineCh(cm) ? '\n' : '') + selectionText.join('\n');
this._replaceSelection(cm, str);
}
cm.focus();
}
handleDownload(cm) {
const title = $('#title').val() || '新文章';
const aTag = document.createElement('a');
let blob = new Blob([cm.state.doc.toString()]);
aTag.download = title + '.md';
aTag.href = URL.createObjectURL(blob);
aTag.click();
URL.revokeObjectURL(blob);
}
handleTitle(cm, tool) {
const item = $(`
$('.cm-modal__wrapper-footer--cancle, .cm-modal__wrapper-header--close').on('click', () => $('.cm-modal').removeClass('active'));
$('.cm-modal__wrapper-footer--confirm').on('click', () => {
this.options.confirm();
$('.cm-modal').removeClass('active');
});
}
_openModal(options = {}) {
const _options = {
title: '提示',
innerHtml: '内容',
hasFooter: true,
confirm: () => {},
handler: () => {}
};
this.options = Object.assign(_options, options);
$('.cm-modal__wrapper-header--text').html(this.options.title);
$('.cm-modal__wrapper-bodyer').html(this.options.innerHtml);
this.options.hasFooter ? $('.cm-modal__wrapper-footer').show() : $('.cm-modal__wrapper-footer').hide();
$('.cm-modal').addClass('active');
this.options.handler();
}
_getLineCh(cm) {
const head = cm.state.selection.main.head;
const line = cm.state.doc.lineAt(head);
return head - line.from;
}
_replaceSelection(cm, str) {
cm.dispatch(cm.state.replaceSelection(str));
}
_setCursor(cm, pos) {
cm.dispatch({ selection: { anchor: pos } });
}
_getSelection(cm) {
return cm.state.sliceDoc(cm.state.selection.main.from, cm.state.selection.main.to);
}
_insetAmboText(cm, str) {
const cursor = cm.state.selection.main.head;
const selection = this._getSelection(cm);
this._replaceSelection(cm, ` ${str + selection + str} `);
if (selection === '') this._setCursor(cm, cursor + str.length + 1);
cm.focus();
}
_createTableLists(cm, url, activeTab = '', modalTitle) {
$.ajax({
url,
dataType: 'json',
success: res => {
let tabbarStr = '';
let listsStr = '';
for (let key in res) {
const arr = res[key].split(' ');
tabbarStr += `<div class="tabbar-item ${key === activeTab ? 'active' : ''}" data-show="${key}">${key}</div>`;
listsStr += `<div class="lists ${key === activeTab ? 'active' : ''}" data-show="${key}">${arr.map(item => `<div class="lists-item" data-text="${item}">${item}</div>`).join(' ')}</div>`;
}
this._openModal({
title: modalTitle,
hasFooter: false,
innerHtml: `<div class="tabbar">${tabbarStr}</div>${listsStr}`,
handler: () => {
$('.cm-modal__wrapper-bodyer .tabbar-item').on('click', function () {
const activeTab = $(this);
const show = activeTab.attr('data-show');
const tabbar = $('.cm-modal__wrapper-bodyer .tabbar');
activeTab.addClass('active').siblings().removeClass('active');
tabbar.stop().animate({
scrollLeft: activeTab[0].offsetLeft - tabbar[0].offsetWidth / 2 + activeTab[0].offsetWidth / 2 - 15
});
$('.cm-modal__wrapper-bodyer .lists').removeClass('active');
$(".cm-modal__wrapper-bodyer .lists[data-show='" + show + "']").addClass('active');
});
const _this = this;
$('.cm-modal__wrapper-bodyer .lists-item').on('click', function () {
const text = $(this).attr('data-text');
_this._replaceSelection(cm, ` ${text} `);
$('.cm-modal').removeClass('active');
cm.focus();
});
}
});
}
});
}
handleFullScreen(el) {
el.toggleClass('active');
$('body').toggleClass('fullscreen');
$('.cm-container').toggleClass('fullscreen');
$('.cm-preview').width(0);
}
handlePublish() {
$('#btn-submit').click();
}
handleUndo(cm) {
undo(cm);
cm.focus();
}
handleRedo(cm) {
redo(cm);
cm.focus();
}
handleIndent(cm) {
this._replaceSelection(cm, ' ');
cm.focus();
}
handleTime(cm) {
const time = new Date();
const _Year = time.getFullYear();
const _Month = String(time.getMonth() + 1).padStart(2, 0);
const _Date = String(time.getDate()).padStart(2, 0);
const _Hours = String(time.getHours()).padStart(2, 0);
const _Minutes = String(time.getMinutes()).padStart(2, 0);
const _Seconds = String(time.getSeconds()).padStart(2, 0);
const _Day = ['星期日', '星期一', '星期二', '星期三', '星期四', '星期五', '星期六'][time.getDay()];
const _time = `${this._getLineCh(cm) ? '\n' : ''}${_Year}-${_Month}-${_Date} ${_Hours}:${_Minutes}:${_Seconds} ${_Day}\n`;
this._replaceSelection(cm, _time);
cm.focus();
}
handleHr(cm) {
const str = `${this._getLineCh(cm) ? '\n' : ''}\n------------\n\n`;
this._replaceSelection(cm, str);
cm.focus();
}
handleClean(cm) {
cm.dispatch({ changes: { from: 0, to: cm.state.doc.length, insert: '' } });
cm.focus();
}
handleOrdered(cm) {
const selection = this._getSelection(cm);
if (selection === '') {
const str = (this._getLineCh(cm) ? '\n\n' : '') + '1. ';
this._replaceSelection(cm, str);
} else {
const selectionText = selection.split('\n');
for (let i = 0, len = selectionText.length; i < len; i++) {
selectionText[i] = selectionText[i] === '' ? '' : i + 1 + '. ' + selectionText[i];
}
const str = (this._getLineCh(cm) ? '\n' : '') + selectionText.join('\n');
this._replaceSelection(cm, str);
}
cm.focus();
}
handleUnordered(cm) {
const selection = this._getSelection(cm);
if (selection === '') {
const str = (this._getLineCh(cm) ? '\n' : '') + '- ';
this._replaceSelection(cm, str);
} else {
const selectionText = selection.split('\n');
for (let i = 0, len = selectionText.length; i < len; i++) {
selectionText[i] = selectionText[i] === '' ? '' : '- ' + selectionText[i];
}
const str = (this._getLineCh(cm) ? '\n' : '') + selectionText.join('\n');
this._replaceSelection(cm, str);
}
cm.focus();
}
handleQuote(cm) {
const selection = this._getSelection(cm);
if (selection === '') {
this._replaceSelection(cm, `${this._getLineCh(cm) ? '\n' : ''}> `);
} else {
const selectionText = selection.split('\n');
for (let i = 0, len = selectionText.length; i < len; i++) {
selectionText[i] = selectionText[i] === '' ? '' : '> ' + selectionText[i];
}
const str = (this._getLineCh(cm) ? '\n' : '') + selectionText.join('\n');
this._replaceSelection(cm, str);
}
cm.focus();
}
handleDownload(cm) {
const title = $('#title').val() || '新文章';
const aTag = document.createElement('a');
let blob = new Blob([cm.state.doc.toString()]);
aTag.download = title + '.md';
aTag.href = URL.createObjectURL(blob);
aTag.click();
URL.revokeObjectURL(blob);
}
handleTitle(cm, tool) {
const item = $(`
<div class="cm-tools-item" title="${tool.title}">
${tool.innerHTML}
<div class="cm-tools__dropdown">
@ -208,26 +208,26 @@ export default class JoeAction {
</div>
</div>
`);
item.on('click', function (e) {
e.stopPropagation();
$(this).toggleClass('active');
});
const _this = this;
item.on('click', '.cm-tools__dropdown-item', function (e) {
e.stopPropagation();
const text = $(this).attr('data-text');
if (_this._getLineCh(cm)) _this._replaceSelection(cm, '\n\n' + text);
else _this._replaceSelection(cm, text);
item.removeClass('active');
cm.focus();
});
$(document).on('click', () => item.removeClass('active'));
$('.cm-tools').append(item);
}
handleLink(cm) {
this._openModal({
title: '插入链接',
innerHtml: `
item.on('click', function (e) {
e.stopPropagation();
$(this).toggleClass('active');
});
const _this = this;
item.on('click', '.cm-tools__dropdown-item', function (e) {
e.stopPropagation();
const text = $(this).attr('data-text');
if (_this._getLineCh(cm)) _this._replaceSelection(cm, '\n\n' + text);
else _this._replaceSelection(cm, text);
item.removeClass('active');
cm.focus();
});
$(document).on('click', () => item.removeClass('active'));
$('.cm-tools').append(item);
}
handleLink(cm) {
this._openModal({
title: '插入链接',
innerHtml: `
<div class="fitem">
<label>链接标题</label>
<input autocomplete="off" name="title" placeholder="请输入链接标题"/>
@ -237,18 +237,18 @@ export default class JoeAction {
<input autocomplete="off" name="url" placeholder="请输入链接地址"/>
</div>
`,
confirm: () => {
const title = $(".cm-modal input[name='title']").val() || 'Test';
const url = $(".cm-modal input[name='url']").val() || 'http://';
this._replaceSelection(cm, ` [${title}](${url}) `);
cm.focus();
}
});
}
handleImage(cm) {
this._openModal({
title: '插入图片',
innerHtml: `
confirm: () => {
const title = $(".cm-modal input[name='title']").val() || 'Test';
const url = $(".cm-modal input[name='url']").val() || 'http://';
this._replaceSelection(cm, ` [${title}](${url}) `);
cm.focus();
}
});
}
handleImage(cm) {
this._openModal({
title: '插入图片',
innerHtml: `
<div class="fitem">
<label>图片名称</label>
<input autocomplete="off" name="title" placeholder="请输入图片名称"/>
@ -258,18 +258,18 @@ export default class JoeAction {
<input autocomplete="off" name="url" placeholder="请输入图片地址"/>
</div>
`,
confirm: () => {
const title = $(".cm-modal input[name='title']").val() || 'Test';
const url = $(".cm-modal input[name='url']").val() || 'http://';
this._replaceSelection(cm, ` ![${title}](${url}) `);
cm.focus();
}
});
}
handleTable(cm) {
this._openModal({
title: '插入表格',
innerHtml: `
confirm: () => {
const title = $(".cm-modal input[name='title']").val() || 'Test';
const url = $(".cm-modal input[name='url']").val() || 'http://';
this._replaceSelection(cm, ` ![${title}](${url}) `);
cm.focus();
}
});
}
handleTable(cm) {
this._openModal({
title: '插入表格',
innerHtml: `
<div class="fitem">
<label>表格行</label>
<input style="width: 50px; flex: none; margin-right: 10px;" value="3" autocomplete="off" name="row"/>
@ -277,33 +277,33 @@ export default class JoeAction {
<input style="width: 50px; flex: none;" value="3" autocomplete="off" name="column"/>
</div>
`,
confirm: () => {
let row = $(".cm-modal input[name='row']").val();
let column = $(".cm-modal input[name='column']").val();
if (isNaN(row)) row = 3;
if (isNaN(column)) column = 3;
let rowStr = '';
let rangeStr = '';
let columnlStr = '';
for (let i = 0; i < column; i++) {
rowStr += '| 表头 ';
rangeStr += '| :--: ';
}
for (let i = 0; i < row; i++) {
for (let j = 0; j < column; j++) columnlStr += '| 表格 ';
columnlStr += '|\n';
}
const htmlStr = `${rowStr}|\n${rangeStr}|\n${columnlStr}\n`;
if (this._getLineCh(cm)) this._replaceSelection(cm, '\n\n' + htmlStr);
else this._replaceSelection(cm, htmlStr);
cm.focus();
}
});
}
handleCodeBlock(cm) {
this._openModal({
title: '插入代码块',
innerHtml: `
confirm: () => {
let row = $(".cm-modal input[name='row']").val();
let column = $(".cm-modal input[name='column']").val();
if (isNaN(row)) row = 3;
if (isNaN(column)) column = 3;
let rowStr = '';
let rangeStr = '';
let columnlStr = '';
for (let i = 0; i < column; i++) {
rowStr += '| 表头 ';
rangeStr += '| :--: ';
}
for (let i = 0; i < row; i++) {
for (let j = 0; j < column; j++) columnlStr += '| 表格 ';
columnlStr += '|\n';
}
const htmlStr = `${rowStr}|\n${rangeStr}|\n${columnlStr}\n`;
if (this._getLineCh(cm)) this._replaceSelection(cm, '\n\n' + htmlStr);
else this._replaceSelection(cm, htmlStr);
cm.focus();
}
});
}
handleCodeBlock(cm) {
this._openModal({
title: '插入代码块',
innerHtml: `
<div class="fitem">
<label>语言类型</label>
<select name="type">
@ -347,25 +347,30 @@ export default class JoeAction {
</select>
</div>
`,
confirm: () => {
const type = $(".cm-modal select[name='type']").val() || 'html';
const htmlStr = `\`\`\`${type}\ncode here...\n\`\`\``;
if (this._getLineCh(cm)) this._replaceSelection(cm, '\n\n' + htmlStr);
else this._replaceSelection(cm, htmlStr);
cm.focus();
}
});
}
handleAbout() {
this._openModal({
title: '关于',
innerHtml: `
confirm: () => {
const type = $(".cm-modal select[name='type']").val() || 'html';
const htmlStr = `\`\`\`${type}\ncode here...\n\`\`\``;
if (this._getLineCh(cm)) this._replaceSelection(cm, '\n\n' + htmlStr);
else this._replaceSelection(cm, htmlStr);
cm.focus();
}
});
}
handleAbout() {
this._openModal({
title: '关于',
innerHtml: `
<ul>
<li>短代码功能正在开发中...</li>
<li>仅支持网络图片粘贴上传截图等</li>
<li>本编辑器仅供Joe主题使用未经允许不得移植至其他主题</li>
</ul>
`
});
}
});
}
handleTask(cm, type) {
const str = type ? '{x}' : '{ }';
this._replaceSelection(cm, ` ${str} `);
cm.focus();
}
}

View File

@ -0,0 +1,10 @@
const parser = new HyperDown();
export default function createPreviewHtml(str) {
str = parser.makeHtml(str);
str = str.replace(/{x}/g, '<input type="checkbox" class="task" checked disabled></input>')
str = str.replace(/{ }/g, '<input type="checkbox" class="task" disabled></input>')
$('.cm-preview-content').html(str);
$('.cm-preview-content pre code').each((i, el) => Prism.highlightElement(el));
}

View File

@ -84,6 +84,16 @@ export default [
title: '符号表情',
innerHTML: '<svg viewBox="0 0 1024 1024" xmlns="http://www.w3.org/2000/svg" width="19" height="19"><path d="M512 56.889A455.111 455.111 0 0 0 56.889 512 455.111 455.111 0 0 0 512 967.111 455.111 455.111 0 0 0 967.111 512 455.111 455.111 0 0 0 512 56.889zm111.047 270.336A69.086 69.086 0 0 1 671.29 307.2c17.863 0 35.67 7.396 48.242 20.025 12.629 12.572 20.025 30.379 20.025 48.242 0 17.863-7.396 35.669-20.025 48.241-12.8 12.744-30.151 19.912-48.242 20.025a68.95 68.95 0 0 1-48.242-20.025 68.95 68.95 0 0 1-20.025-48.241c0-17.863 7.396-35.67 20.025-48.242zm-318.578 0a69.086 69.086 0 0 1 48.242-20.025c17.863 0 35.67 7.396 48.242 20.025 12.63 12.572 20.025 30.379 20.025 48.242 0 17.863-7.396 35.669-20.025 48.241-12.8 12.744-30.151 19.912-48.242 20.025a68.95 68.95 0 0 1-48.242-20.025 68.95 68.95 0 0 1-20.025-48.241c0-17.863 7.396-35.67 20.025-48.242zM786.375 566.67c-10.24 132.893-118.556 236.544-270.563 235.975-156.331 1.707-264.704-107.178-270.507-235.975a23.324 23.324 0 0 1-2.446-10.41c0-13.597 11.605-24.633 26.282-24.52h493.796c14.336 0 26.055 11.037 26.055 24.52a24.292 24.292 0 0 1-2.617 10.41z"/></svg>'
},
{
type: 'task-no',
title: '任务 - 未完成',
innerHTML: '<svg viewBox="0 0 1024 1024" xmlns="http://www.w3.org/2000/svg" width="20" height="20"><path d="M831.55 128.531c38.35 0 63.911 25.568 63.911 63.91v639.104c0 38.355-25.561 63.916-63.91 63.916H192.44c-38.34 0-63.908-25.56-63.908-63.916V192.442c0-38.343 25.567-63.91 63.908-63.91h639.11m0-63.91H192.44c-70.3 0-127.816 57.518-127.816 127.82v639.103c0 70.308 57.515 127.833 127.816 127.833h639.11c70.294 0 127.822-57.525 127.822-127.833V192.442c0-70.302-57.527-127.82-127.823-127.82zm0 0"/></svg>'
},
{
type: 'task-yes',
title: '任务 - 已完成',
innerHTML: '<svg viewBox="0 0 1024 1024" xmlns="http://www.w3.org/2000/svg" width="20" height="20"><path d="M831.551 64.623h-639.11c-70.3 0-127.816 57.517-127.816 127.819v639.103c0 70.308 57.515 127.833 127.816 127.833h639.11c70.294 0 127.822-57.525 127.822-127.833V192.442c0-70.302-57.527-127.82-127.822-127.82zM646.217 486.44c-108.652 159.779-204.52 345.115-204.52 345.115L192.443 550.351l63.916-70.303 153.385 146.994s76.695-127.822 178.95-236.469c102.261-108.652 223.689-198.127 223.689-198.127l19.17 63.916c0-.001-102.255 108.646-185.337 230.078z"/></svg>'
},
{
type: 'code-block',
title: '代码块',

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

View File

@ -7,36 +7,29 @@ import { history, historyKeymap } from '@codemirror/history';
import { classHighlightStyle } from '@codemirror/highlight';
import tools from './_tools';
import JoeAction from './_actions';
import createPreviewHtml from './_create';
class Joe extends JoeAction {
constructor() {
super();
this.plugins = [history(), classHighlightStyle, bracketMatching(), closeBrackets()];
this.parser = new HyperDown();
this._isPasting = false;
constructor() {
super();
this.plugins = [history(), classHighlightStyle, bracketMatching(), closeBrackets()];
this._isPasting = false;
this.init_ViewPort();
this.init_Editor();
this.init_Preview();
this.init_Tools();
this.init_Insert();
}
this.init_ViewPort();
this.init_Editor();
this.init_Preview();
this.init_Tools();
this.init_Insert();
}
/* 已测 √ */
init_ViewPort() {
if ($('meta[name="viewport"]').length > 0) $('meta[name="viewport"]').attr('content', 'width=device-width, user-scalable=no, initial-scale=1.0, shrink-to-fit=no, viewport-fit=cover');
else $('head').append('<meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1.0, shrink-to-fit=no, viewport-fit=cover">');
}
_createPreviewHtml(str) {
str = this.parser.makeHtml(str);
$('.cm-preview-content').html(str);
$('.cm-preview-content pre code').each((i, el) => Prism.highlightElement(el));
}
/* 已测 √ */
init_ViewPort() {
if ($('meta[name="viewport"]').length > 0) $('meta[name="viewport"]').attr('content', 'width=device-width, user-scalable=no, initial-scale=1.0, shrink-to-fit=no, viewport-fit=cover');
else $('head').append('<meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1.0, shrink-to-fit=no, viewport-fit=cover">');
}
/* 已测 √ */
init_Editor() {
$('#text').before(`
/* 已测 √ */
init_Editor() {
$('#text').before(`
<div class="cm-container">
<div class="cm-tools"></div>
<div class="cm-mainer">
@ -47,225 +40,231 @@ class Joe extends JoeAction {
<div class="cm-progress-right"></div>
</div>
`);
this._createPreviewHtml($('#text').val());
const cm = new EditorView({
state: EditorState.create({
doc: $('#text').val(),
extensions: [
...this.plugins,
keymap.of([defaultTabBinding, ...defaultKeymap, ...historyKeymap, ...closeBracketsKeymap]),
EditorView.updateListener.of(update => {
if (!update.docChanged) return;
this._createPreviewHtml(update.state.doc.toString());
}),
EditorView.domEventHandlers({
paste: e => {
const clipboardData = e.clipboardData;
if (!clipboardData || !clipboardData.items) return;
const items = clipboardData.items;
if (!items.length) return;
let blob = null;
for (let i = 0; i < items.length; i++) {
if (items[i].type.indexOf('image') !== -1) {
e.preventDefault();
blob = items[i].getAsFile();
break;
}
}
if (!blob) return;
let api = window.JoeConfig.uploadAPI;
if (!api) return;
const cid = $('input[name="cid"]').val();
cid && (api = api + '&cid=' + cid);
if (this._isPasting) return;
this._isPasting = true;
const fileName = Date.now().toString(36) + '.png';
let formData = new FormData();
formData.append('name', fileName);
formData.append('file', blob, fileName);
$.ajax({
url: api,
method: 'post',
data: formData,
contentType: false,
processData: false,
dataType: 'json',
xhr: () => {
const xhr = $.ajaxSettings.xhr();
if (!xhr.upload) return;
xhr.upload.addEventListener(
'progress',
e => {
let percent = (e.loaded / e.total) * 100;
$('.cm-progress-left').width(percent / 2 + '%');
$('.cm-progress-right').width(percent / 2 + '%');
},
false
);
return xhr;
},
success: res => {
$('.cm-progress-left').width(0);
$('.cm-progress-right').width(0);
this._isPasting = false;
const str = `${super._getLineCh(cm) ? '\n' : ''}![${res[1].title}](${res[0]})\n`;
super._replaceSelection(cm, str);
cm.focus();
},
error: () => {
$('.cm-progress-left').width(0);
$('.cm-progress-right').width(0);
this._isPasting = false;
}
});
}
})
],
tabSize: 4
})
});
$('.cm-mainer').prepend(cm.dom);
$('#text')[0].form && $('#text')[0].form.addEventListener('submit', () => $('#text').val(cm.state.doc.toString()));
this.cm = cm;
}
createPreviewHtml($('#text').val());
const cm = new EditorView({
state: EditorState.create({
doc: $('#text').val(),
extensions: [
...this.plugins,
keymap.of([defaultTabBinding, ...defaultKeymap, ...historyKeymap, ...closeBracketsKeymap]),
EditorView.updateListener.of(update => {
if (!update.docChanged) return;
createPreviewHtml(update.state.doc.toString());
}),
EditorView.domEventHandlers({
paste: e => {
const clipboardData = e.clipboardData;
if (!clipboardData || !clipboardData.items) return;
const items = clipboardData.items;
if (!items.length) return;
let blob = null;
for (let i = 0; i < items.length; i++) {
if (items[i].type.indexOf('image') !== -1) {
e.preventDefault();
blob = items[i].getAsFile();
break;
}
}
if (!blob) return;
let api = window.JoeConfig.uploadAPI;
if (!api) return;
const cid = $('input[name="cid"]').val();
cid && (api = api + '&cid=' + cid);
if (this._isPasting) return;
this._isPasting = true;
const fileName = Date.now().toString(36) + '.png';
let formData = new FormData();
formData.append('name', fileName);
formData.append('file', blob, fileName);
$.ajax({
url: api,
method: 'post',
data: formData,
contentType: false,
processData: false,
dataType: 'json',
xhr: () => {
const xhr = $.ajaxSettings.xhr();
if (!xhr.upload) return;
xhr.upload.addEventListener(
'progress',
e => {
let percent = (e.loaded / e.total) * 100;
$('.cm-progress-left').width(percent / 2 + '%');
$('.cm-progress-right').width(percent / 2 + '%');
},
false
);
return xhr;
},
success: res => {
$('.cm-progress-left').width(0);
$('.cm-progress-right').width(0);
this._isPasting = false;
const str = `${super._getLineCh(cm) ? '\n' : ''}![${res[1].title}](${res[0]})\n`;
super._replaceSelection(cm, str);
cm.focus();
},
error: () => {
$('.cm-progress-left').width(0);
$('.cm-progress-right').width(0);
this._isPasting = false;
}
});
}
})
],
tabSize: 4
})
});
$('.cm-mainer').prepend(cm.dom);
$('#text')[0].form && $('#text')[0].form.addEventListener('submit', () => $('#text').val(cm.state.doc.toString()));
this.cm = cm;
}
/* 已测 √ */
init_Preview() {
const move = (nowClientX, nowWidth, clientX) => {
let moveX = nowClientX - clientX;
let moveWidth = nowWidth + moveX;
if (moveWidth <= 0) moveWidth = 0;
if (moveWidth >= $('.cm-mainer').outerWidth() - 16) moveWidth = $('.cm-mainer').outerWidth() - 16;
$('.cm-preview').width(moveWidth);
};
$('.cm-resize').on({
mousedown: e => {
e.preventDefault();
e.stopPropagation();
const nowWidth = $('.cm-preview').outerWidth();
const nowClientX = e.clientX;
document.onmousemove = _e => {
if (window.requestAnimationFrame) requestAnimationFrame(() => move(nowClientX, nowWidth, _e.clientX));
else move(nowClientX, nowWidth, _e.clientX);
};
document.onmouseup = () => {
document.onmousemove = null;
document.onmouseup = null;
};
return false;
},
touchstart: e => {
e.preventDefault();
e.stopPropagation();
const nowWidth = $('.cm-preview').outerWidth();
const nowClientX = e.originalEvent.targetTouches[0].clientX;
document.ontouchmove = _e => {
if (window.requestAnimationFrame) requestAnimationFrame(() => move(nowClientX, nowWidth, _e.targetTouches[0].clientX));
else move(nowClientX, nowWidth, _e.targetTouches[0].clientX);
};
document.ontouchend = () => {
document.ontouchmove = null;
document.ontouchend = null;
};
return false;
}
});
}
/* 已测 √ */
init_Preview() {
const move = (nowClientX, nowWidth, clientX) => {
let moveX = nowClientX - clientX;
let moveWidth = nowWidth + moveX;
if (moveWidth <= 0) moveWidth = 0;
if (moveWidth >= $('.cm-mainer').outerWidth() - 16) moveWidth = $('.cm-mainer').outerWidth() - 16;
$('.cm-preview').width(moveWidth);
};
$('.cm-resize').on({
mousedown: e => {
e.preventDefault();
e.stopPropagation();
const nowWidth = $('.cm-preview').outerWidth();
const nowClientX = e.clientX;
document.onmousemove = _e => {
if (window.requestAnimationFrame) requestAnimationFrame(() => move(nowClientX, nowWidth, _e.clientX));
else move(nowClientX, nowWidth, _e.clientX);
};
document.onmouseup = () => {
document.onmousemove = null;
document.onmouseup = null;
};
return false;
},
touchstart: e => {
e.preventDefault();
e.stopPropagation();
const nowWidth = $('.cm-preview').outerWidth();
const nowClientX = e.originalEvent.targetTouches[0].clientX;
document.ontouchmove = _e => {
if (window.requestAnimationFrame) requestAnimationFrame(() => move(nowClientX, nowWidth, _e.targetTouches[0].clientX));
else move(nowClientX, nowWidth, _e.targetTouches[0].clientX);
};
document.ontouchend = () => {
document.ontouchmove = null;
document.ontouchend = null;
};
return false;
}
});
}
/* 已测 √ */
init_Tools() {
tools.forEach(item => {
if (item.type === 'title') {
super.handleTitle(this.cm, item);
} else {
const el = $(`<div class="cm-tools-item" title="${item.title}">${item.innerHTML}</div>`);
el.on('click', e => {
e.preventDefault();
switch (item.type) {
case 'fullScreen':
super.handleFullScreen(el);
break;
case 'publish':
super.handlePublish();
break;
case 'undo':
super.handleUndo(this.cm);
break;
case 'redo':
super.handleRedo(this.cm);
break;
case 'time':
super.handleTime(this.cm);
break;
case 'bold':
super._insetAmboText(this.cm, '**');
break;
case 'italic':
super._insetAmboText(this.cm, '*');
break;
case 'delete':
super._insetAmboText(this.cm, '~~');
break;
case 'code-inline':
super._insetAmboText(this.cm, '`');
break;
case 'indent':
super.handleIndent(this.cm);
break;
case 'hr':
super.handleHr(this.cm);
break;
case 'clean':
super.handleClean(this.cm);
break;
case 'ordered-list':
super.handleOrdered(this.cm);
break;
case 'unordered-list':
super.handleUnordered(this.cm);
break;
case 'quote':
super.handleQuote(this.cm);
break;
case 'download':
super.handleDownload(this.cm);
break;
case 'link':
super.handleLink(this.cm);
break;
case 'image':
super.handleImage(this.cm);
break;
case 'table':
super.handleTable(this.cm);
break;
case 'code-block':
super.handleCodeBlock(this.cm);
break;
case 'about':
super.handleAbout();
break;
case 'character':
super._createTableLists(this.cm, JoeConfig.characterAPI, '星星符号', '字符大全');
break;
case 'emoji':
super._createTableLists(this.cm, JoeConfig.emojiAPI, '表情', '符号表情(需数据库支持)');
break;
}
});
$('.cm-tools').append(el);
}
});
}
/* 已测 √ */
init_Tools() {
tools.forEach(item => {
if (item.type === 'title') {
super.handleTitle(this.cm, item);
} else {
const el = $(`<div class="cm-tools-item" title="${item.title}">${item.innerHTML}</div>`);
el.on('click', e => {
e.preventDefault();
switch (item.type) {
case 'fullScreen':
super.handleFullScreen(el);
break;
case 'publish':
super.handlePublish();
break;
case 'undo':
super.handleUndo(this.cm);
break;
case 'redo':
super.handleRedo(this.cm);
break;
case 'time':
super.handleTime(this.cm);
break;
case 'bold':
super._insetAmboText(this.cm, '**');
break;
case 'italic':
super._insetAmboText(this.cm, '*');
break;
case 'delete':
super._insetAmboText(this.cm, '~~');
break;
case 'code-inline':
super._insetAmboText(this.cm, '`');
break;
case 'indent':
super.handleIndent(this.cm);
break;
case 'hr':
super.handleHr(this.cm);
break;
case 'clean':
super.handleClean(this.cm);
break;
case 'ordered-list':
super.handleOrdered(this.cm);
break;
case 'unordered-list':
super.handleUnordered(this.cm);
break;
case 'quote':
super.handleQuote(this.cm);
break;
case 'download':
super.handleDownload(this.cm);
break;
case 'link':
super.handleLink(this.cm);
break;
case 'image':
super.handleImage(this.cm);
break;
case 'table':
super.handleTable(this.cm);
break;
case 'code-block':
super.handleCodeBlock(this.cm);
break;
case 'about':
super.handleAbout();
break;
case 'character':
super._createTableLists(this.cm, JoeConfig.characterAPI, '星星符号', '字符大全');
break;
case 'emoji':
super._createTableLists(this.cm, JoeConfig.emojiAPI, '表情', '符号表情(需数据库支持)');
break;
case 'task-no':
super.handleTask(this.cm, false);
break;
case 'task-yes':
super.handleTask(this.cm, true);
break;
}
});
$('.cm-tools').append(el);
}
});
}
/* 已测 √ */
init_Insert() {
Typecho.insertFileToEditor = (file, url, isImage) => {
const str = `${super._getLineCh(this.cm) ? '\n' : ''}${isImage ? '!' : ''}[${file}](${url})\n`;
super._replaceSelection(this.cm, str);
this.cm.focus();
};
}
/* 已测 √ */
init_Insert() {
Typecho.insertFileToEditor = (file, url, isImage) => {
const str = `${super._getLineCh(this.cm) ? '\n' : ''}${isImage ? '!' : ''}[${file}](${url})\n`;
super._replaceSelection(this.cm, str);
this.cm.focus();
};
}
}
document.addEventListener('DOMContentLoaded', () => new Joe());

View File

@ -1,10 +1,10 @@
import { nodeResolve } from '@rollup/plugin-node-resolve';
import { uglify } from 'rollup-plugin-uglify';
export default {
input: './js/joe.write.js',
output: {
file: './js/joe.write.chunk.js',
format: 'iife'
},
plugins: [nodeResolve(), uglify()]
input: './js/joe.write.js',
output: {
file: './js/joe.write.chunk.js',
format: 'iife'
},
plugins: [nodeResolve(), uglify()]
};