Skip to content

Commit cae428f

Browse files
NHPTNHPT
andauthored
修复:兼容 Typecho 1.3 并加固 EditorMD (#216)
Co-authored-by: NHPT <yxiye@qq.com>
1 parent 3adcb99 commit cae428f

6 files changed

Lines changed: 491 additions & 83 deletions

File tree

EditorMD/Plugin.php

Lines changed: 193 additions & 81 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,8 @@
55
*
66
* @package EditorMD
77
* @author DT27
8-
* @version 1.4.0
8+
* @version 1.5.0
9+
* @dependence 1.2.0-*
910
* @link https://dt27.cn/php/editormd-for-typecho/
1011
*/
1112
class EditorMD_Plugin implements Typecho_Plugin_Interface
@@ -111,16 +112,28 @@ public static function Editor()
111112
$options = Helper::options();
112113
$cssUrl = $options->pluginUrl.'/EditorMD/css/editormd.min.css';
113114
$jsUrl = $options->pluginUrl.'/EditorMD/js/editormd.min.js';
115+
$markedUrl = $options->pluginUrl.'/EditorMD/lib/marked.min.js';
116+
$purifyUrl = $options->pluginUrl.'/EditorMD/lib/purify.min.js';
117+
$safeMarkedUrl = $options->pluginUrl.'/EditorMD/js/safe-marked.js';
114118
$editormd = Typecho_Widget::widget('Widget_Options')->plugin('EditorMD');
115119
?>
116120
<link rel="stylesheet" href="<?php echo $cssUrl; ?>" />
117121
<script>
118122
var emojiPath = '<?php echo $options->pluginUrl; ?>';
119123
var uploadURL = '<?php Helper::security()->index('/action/upload?cid=CID'); ?>';
120124
</script>
125+
<script type="text/javascript" src="<?php echo $markedUrl; ?>"></script>
126+
<script type="text/javascript" src="<?php echo $purifyUrl; ?>"></script>
121127
<script type="text/javascript" src="<?php echo $jsUrl; ?>"></script>
128+
<script type="text/javascript" src="<?php echo $safeMarkedUrl; ?>"></script>
122129
<script>
123130
$(document).ready(function() {
131+
if (!window.EditorMDSanitizeMarked
132+
|| !window.EditorMDSanitizeMarked()
133+
|| !window.EditorMDGuardMarkedLoader
134+
|| !window.EditorMDGuardMarkedLoader()) {
135+
return;
136+
}
124137

125138
var textarea = $('#text').parent("p");
126139
var isMarkdown = $('[name=markdown]').val()?1:0;
@@ -145,7 +158,7 @@ public static function Editor()
145158
height: 640,
146159
path: '<?php echo $options->pluginUrl ?>/EditorMD/lib/',
147160
toolbarAutoFixed: false,
148-
htmlDecode: true,
161+
htmlDecode: false,
149162
emoji: <?php echo $editormd->emoji ? 'true' : 'false'; ?>,
150163
tex: <?php echo $editormd->isTex ? 'true' : 'false'; ?>,
151164
toc: <?php echo $editormd->isToc ? 'true' : 'false'; ?>,
@@ -221,87 +234,109 @@ public static function Editor()
221234

222235
// 优化图片及文件附件插入 Thanks to Markxuxiao
223236
Typecho.insertFileToEditor = function (file, url, isImage) {
224-
html = isImage ? '![' + file + '](' + url + ')'
237+
var html = isImage ? '![' + file + '](' + url + ')'
225238
: '[' + file + '](' + url + ')';
226239
postEditormd.insertValue(html);
227240
};
228241

229242
// 支持黏贴图片直接上传
230-
$(document).on('paste', function(event) {
231-
event = event.originalEvent;
232-
var cbd = event.clipboardData;
233-
var ua = window.navigator.userAgent;
234-
if (!(event.clipboardData && event.clipboardData.items)) {
243+
var pasteUploadIndex = 0;
244+
var uploadClipboardImage = function(blob) {
245+
if (!blob) {
235246
return;
236247
}
237248

238-
if (cbd.items && cbd.items.length === 2 && cbd.items[0].kind === "string" && cbd.items[1].kind === "file" &&
239-
cbd.types && cbd.types.length === 2 && cbd.types[0] === "text/plain" && cbd.types[1] === "Files" &&
240-
ua.match(/Macintosh/i) && Number(ua.match(/Chrome\/(\d{2})/i)[1]) < 49){
249+
var extensions = {
250+
'image/jpeg': 'jpg',
251+
'image/pjpeg': 'jpg',
252+
'image/png': 'png',
253+
'image/gif': 'gif',
254+
'image/webp': 'webp'
255+
};
256+
var ext = extensions[blob.type];
257+
if (!ext || !blob.size) {
241258
return;
242259
}
243260

244-
var itemLength = cbd.items.length;
261+
var uploadId = pasteUploadIndex++;
262+
var formData = new FormData();
263+
formData.append(
264+
'blob',
265+
blob,
266+
new Date().getTime() + '-' + uploadId + '.' + ext
267+
);
245268

246-
if (itemLength == 0) {
247-
return;
248-
}
269+
var uploadingText = '![图片上传中(' + uploadId + ')...]';
270+
var uploadFailText = '![图片上传失败(' + uploadId + ')]';
271+
postEditormd.insertValue(uploadingText);
249272

250-
if (itemLength == 1 && cbd.items[0].kind == 'string') {
251-
return;
252-
}
273+
$.ajax({
274+
method: 'post',
275+
url: uploadURL.replace(
276+
'CID',
277+
encodeURIComponent($('input[name="cid"]').val() || '')
278+
),
279+
data: formData,
280+
dataType: 'json',
281+
contentType: false,
282+
processData: false,
283+
success: function(data) {
284+
if (data && data[0]) {
285+
var attachment = data[1] || {};
286+
var attachmentId = parseInt(attachment.cid, 10);
287+
var imageUrl = attachment.url || data[0];
253288

254-
if ((itemLength == 1 && cbd.items[0].kind == 'file')
255-
|| itemLength > 1
256-
) {
257-
for (var i = 0; i < cbd.items.length; i++) {
258-
var item = cbd.items[i];
289+
postEditormd.setValue(
290+
postEditormd.getValue().replace(
291+
uploadingText,
292+
'![](' + imageUrl + ')'
293+
)
294+
);
259295

260-
if(item.kind == "file") {
261-
var blob = item.getAsFile();
262-
if (blob.size === 0) {
263-
return;
296+
if (
297+
attachmentId > 0
298+
&& !$('input[name="attachment[]"][value="'
299+
+ attachmentId + '"]').length
300+
) {
301+
$('<input>', {
302+
type: 'hidden',
303+
name: 'attachment[]',
304+
value: attachmentId
305+
}).appendTo($('#text').closest('form'));
264306
}
265-
var ext = 'jpg';
266-
switch(blob.type) {
267-
case 'image/jpeg':
268-
case 'image/pjpeg':
269-
ext = 'jpg';
270-
break;
271-
case 'image/png':
272-
ext = 'png';
273-
break;
274-
case 'image/gif':
275-
ext = 'gif';
276-
break;
277-
}
278-
var formData = new FormData();
279-
formData.append('blob', blob, Math.floor(new Date().getTime() / 1000) + '.' + ext);
280-
var uploadingText = '![图片上传中(' + i + ')...]';
281-
var uploadFailText = '![图片上传失败(' + i + ')]'
282-
postEditormd.insertValue(uploadingText);
283-
$.ajax({
284-
method: 'post',
285-
url: uploadURL.replace('CID', $('input[name="cid"]').val()),
286-
data: formData,
287-
contentType: false,
288-
processData: false,
289-
success: function(data) {
290-
if (data[0]) {
291-
postEditormd.setValue(postEditormd.getValue().replace(uploadingText, '![](' + data[0] + ')'));
292-
} else {
293-
postEditormd.setValue(postEditormd.getValue().replace(uploadingText, uploadFailText));
294-
}
295-
},
296-
error: function() {
297-
postEditormd.setValue(postEditormd.getValue().replace(uploadingText, uploadFailText));
298-
}
299-
});
307+
} else {
308+
postEditormd.setValue(
309+
postEditormd.getValue().replace(
310+
uploadingText,
311+
uploadFailText
312+
)
313+
);
300314
}
315+
},
316+
error: function() {
317+
postEditormd.setValue(
318+
postEditormd.getValue().replace(
319+
uploadingText,
320+
uploadFailText
321+
)
322+
);
301323
}
324+
});
325+
};
302326

327+
$(document).on('paste', function(event) {
328+
event = event.originalEvent;
329+
var cbd = event.clipboardData;
330+
if (!(event.clipboardData && event.clipboardData.items)) {
331+
return;
303332
}
304333

334+
for (var i = 0; i < cbd.items.length; i++) {
335+
var item = cbd.items[i];
336+
if (item.kind === 'file') {
337+
uploadClipboardImage(item.getAsFile());
338+
}
339+
}
305340
});
306341

307342
});
@@ -311,20 +346,23 @@ public static function Editor()
311346
/**
312347
* emoji 解析器
313348
*/
314-
public static function footerJS($conent)
349+
public static function footerJS($content)
315350
{
316351
$options = Helper::options();
317352
$pluginUrl = $options->pluginUrl.'/EditorMD';
318353
$editormd = Typecho_Widget::widget('Widget_Options')->plugin('EditorMD');
354+
$needsMarkdown = $editormd->isActive == 1 && self::$count > 0;
319355
if($editormd->emoji){
320356
?>
321357
<link rel="stylesheet" href="<?php echo $pluginUrl; ?>/css/emojify.min.css" />
322-
<?php }if($editormd->emoji || ($editormd->isActive == 1 && $conent->isMarkdown)){ ?>
358+
<?php }if($editormd->emoji || $needsMarkdown){ ?>
323359
<script type="text/javascript">
324360
window.jQuery || document.write(unescape('%3Cscript%20type%3D%22text/javascript%22%20src%3D%22<?php echo $pluginUrl; ?>/lib/jquery.min.js%22%3E%3C/script%3E'));
325361
</script>
326-
<?php }if($editormd->isActive == 1 && $conent->isMarkdown){ ?>
362+
<?php }if($needsMarkdown){ ?>
327363
<script src="<?php echo $pluginUrl; ?>/lib/marked.min.js"></script>
364+
<script src="<?php echo $pluginUrl; ?>/lib/purify.min.js"></script>
365+
<script src="<?php echo $pluginUrl; ?>/js/safe-marked.js"></script>
328366
<script src="<?php echo $pluginUrl; ?>/js/editormd.min.js"></script>
329367
<?php if($editormd->isSeq == 1||$editormd->isFlow == 1){ ?>
330368
<script src="<?php echo $pluginUrl; ?>/lib/raphael.min.js"></script>
@@ -336,20 +374,31 @@ public static function footerJS($conent)
336374
<script src="<?php echo $pluginUrl; ?>/lib/sequence-diagram.min.js"></script>
337375
<?php }}if($editormd->emoji){ ?>
338376
<script src="<?php echo $pluginUrl; ?>/js/emojify.min.js"></script>
339-
<?php }if($editormd->emoji||($editormd->isActive == 1 && $conent->isMarkdown)){?>
377+
<?php }if($editormd->emoji||$needsMarkdown){?>
340378
<script type="text/javascript">
341379
$(function() {
342-
<?php if($editormd->isActive == 1 && $conent->isMarkdown){ ?>
380+
<?php if($needsMarkdown){ ?>
381+
if (!window.EditorMDSanitizeMarked
382+
|| !window.EditorMDSanitizeMarked()) {
383+
return;
384+
}
385+
343386
var parseMarkdown = function () {
344387
var markdowns = document.getElementsByClassName("md_content");
345388
$(markdowns).each(function () {
346-
var markdown = $(this).children("#append-test").text();
389+
var source = $(this).children(".editormd-source");
390+
if (!source.length) {
391+
return;
392+
}
393+
var markdown = source.map(function () {
394+
return $(this).val();
395+
}).get().join('\n\n');
347396
//$('#md_content_'+i).text('');
348397
var editormdView;
349398
editormdView = editormd.markdownToHTML($(this).attr("id"), {
350399
markdown: markdown,//+ "\r\n" + $("#append-test").text(),
351400
toolbarAutoFixed: false,
352-
htmlDecode: true,
401+
htmlDecode: false,
353402
emoji: <?php echo $editormd->emoji ? 'true' : 'false'; ?>,
354403
tex: <?php echo $editormd->isTex ? 'true' : 'false'; ?>,
355404
toc: <?php echo $editormd->isToc ? 'true' : 'false'; ?>,
@@ -362,7 +411,7 @@ public static function footerJS($conent)
362411
};
363412
parseMarkdown();
364413
$(document).on('pjax:complete', function () {
365-
parseMarkdown()
414+
parseMarkdown();
366415
});
367416
<?php }if($editormd->emoji){ ?>
368417
emojify.setConfig({
@@ -397,20 +446,83 @@ public static function footerJS($conent)
397446
<?php
398447
}
399448
}
400-
public static function content($text, $conent){
401-
self::$count++;
449+
public static function content($text, $content){
402450
$editormd = Typecho_Widget::widget('Widget_Options')->plugin('EditorMD');
403-
$text = $conent->isMarkdown ? ($editormd->isActive == 1?$text:$conent->markdown($text))
404-
: $conent->autoP($text);
405-
if($editormd->isActive == 1 && $conent->isMarkdown)
406-
return '<div id="md_content_'.self::$count.'" class="md_content" style="min-height: 50px;"><textarea id="append-test" style="display:none;">'.$text.'</textarea></div>';
407-
else
451+
if ($content->isMarkdown) {
452+
return $editormd->isActive == 1
453+
? self::renderMarkdown($text)
454+
: self::markdown($text);
455+
}
456+
457+
return self::autoP($text);
458+
}
459+
460+
public static function excerpt($text, $content){
461+
if (self::isTypecho13()) {
408462
return $text;
463+
}
464+
465+
return self::content($text, $content);
409466
}
410-
public static function excerpt($text, $conent){
467+
468+
private static function renderMarkdown($text)
469+
{
470+
$segments = explode('<!--more-->', (string) $text);
471+
foreach ($segments as &$segment) {
472+
$segment = '<textarea class="editormd-source" style="display:none;">'
473+
. htmlspecialchars($segment, ENT_NOQUOTES | ENT_SUBSTITUTE, 'UTF-8')
474+
. '</textarea>';
475+
}
476+
unset($segment);
477+
411478
self::$count++;
412-
$text = $conent->isMarkdown ? $conent->markdown($text)
413-
: $conent->autoP($text);
414-
return $text;
479+
return '<div id="md_content_' . self::$count
480+
. '" class="md_content" style="min-height: 50px;">'
481+
. implode('<!--more-->', $segments)
482+
. '</div>';
483+
}
484+
485+
private static function markdown($text)
486+
{
487+
$handle = Typecho_Plugin::factory('Widget_Abstract_Contents');
488+
if (self::isTypecho13()) {
489+
$result = $handle->trigger($parsed)->filter('markdown', (string) $text);
490+
} else {
491+
$result = $handle->trigger($parsed)->markdown((string) $text);
492+
}
493+
494+
return $parsed ? $result : Markdown::convert((string) $text);
495+
}
496+
497+
private static function autoP($text)
498+
{
499+
if (!$text) {
500+
return $text;
501+
}
502+
503+
$handle = Typecho_Plugin::factory('Widget_Abstract_Contents');
504+
if (self::isTypecho13()) {
505+
$result = $handle->trigger($parsed)->filter('autoP', (string) $text);
506+
} else {
507+
$result = $handle->trigger($parsed)->autoP((string) $text);
508+
}
509+
if ($parsed) {
510+
return $result;
511+
}
512+
513+
static $parser;
514+
if (!$parser) {
515+
$parser = new AutoP();
516+
}
517+
518+
return $parser->parse((string) $text);
519+
}
520+
521+
private static function isTypecho13()
522+
{
523+
return method_exists(
524+
Typecho_Plugin::factory('Widget_Abstract_Contents'),
525+
'filter'
526+
);
415527
}
416528
}

0 commit comments

Comments
 (0)