mirror of
https://github.com/typecho/typecho.git
synced 2025-03-18 08:59:40 +01:00
Implement Ctrl+S or Command+S for save draft (#1628)
* Implement Ctrl+S or Command+S for save draft * rename * add Typecho.savePost * fix upload file size * add new uploader * replace new uploader * fix textarea change * fix preview * refactor post edit * fix issue * fix page edit --------- Co-authored-by: joyqi <joyqi@segmentfault.com> Co-authored-by: joyqi <magike.net@gmail.com>
This commit is contained in:
parent
ff1fde5c4b
commit
438ac35487
@ -84,6 +84,7 @@ $backupFiles = \Widget\Backup::alloc()->listFiles();
|
||||
<?php
|
||||
include 'copyright.php';
|
||||
include 'common-js.php';
|
||||
include 'form-js.php';
|
||||
?>
|
||||
<script>
|
||||
$('#backup-secondary .typecho-option-tabs li').click(function() {
|
||||
|
@ -133,10 +133,6 @@
|
||||
.attr('rel', 'noopener noreferrer');
|
||||
});
|
||||
}
|
||||
|
||||
$('.main form').submit(function () {
|
||||
$('button[type=submit]', this).attr('disabled', 'disabled');
|
||||
});
|
||||
});
|
||||
})();
|
||||
</script>
|
||||
|
@ -20,7 +20,7 @@ $(document).ready(function () {
|
||||
$(this).remove();
|
||||
});
|
||||
|
||||
$(this).parents('form').trigger('field');
|
||||
$(this).parents('form').trigger('change');
|
||||
}
|
||||
});
|
||||
}
|
||||
@ -40,10 +40,6 @@ $(document).ready(function () {
|
||||
+ '<td><button type="button" class="btn btn-xs"><?php _e('删除'); ?></button></td></tr>',
|
||||
el = $(html).hide().appendTo('#custom-field table tbody').fadeIn();
|
||||
|
||||
$(':input', el).bind('input change', function () {
|
||||
$(this).parents('form').trigger('field');
|
||||
});
|
||||
|
||||
attachDeleteEvent(el);
|
||||
});
|
||||
});
|
||||
|
@ -1,17 +1,44 @@
|
||||
<?php if(!defined('__TYPECHO_ADMIN__')) exit; ?>
|
||||
<?php $content = !empty($post) ? $post : $page; if ($options->markdown): ?>
|
||||
<?php $content = !empty($post) ? $post : $page; ?>
|
||||
<script>
|
||||
(function () {
|
||||
$('#text').on('change', function (e) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
}).on('input', function () {
|
||||
$(this).parents('form').trigger('write');
|
||||
});
|
||||
})();
|
||||
</script>
|
||||
<?php if (!$options->markdown): ?>
|
||||
<script>
|
||||
(function () {
|
||||
const textarea = $('#text');
|
||||
|
||||
// 原始的插入图片和文件
|
||||
Typecho.insertFileToEditor = function (file, url, isImage) {
|
||||
const sel = textarea.getSelection(),
|
||||
html = isImage ? '<img src="' + url + '" alt="' + file + '" />'
|
||||
: '<a href="' + url + '">' + file + '</a>',
|
||||
offset = (sel ? sel.start : 0) + html.length;
|
||||
|
||||
textarea.replaceSelection(html);
|
||||
textarea.setSelection(offset, offset);
|
||||
};
|
||||
})();
|
||||
</script>
|
||||
<?php else: ?>
|
||||
<script src="<?php $options->adminStaticUrl('js', 'hyperdown.js'); ?>"></script>
|
||||
<script src="<?php $options->adminStaticUrl('js', 'pagedown.js'); ?>"></script>
|
||||
<script src="<?php $options->adminStaticUrl('js', 'paste.js'); ?>"></script>
|
||||
<script src="<?php $options->adminStaticUrl('js', 'purify.js'); ?>"></script>
|
||||
<script>
|
||||
$(document).ready(function () {
|
||||
var textarea = $('#text'),
|
||||
isFullScreen = false,
|
||||
const textarea = $('#text'),
|
||||
toolbar = $('<div class="editor" id="wmd-button-bar" />').insertBefore(textarea.parent()),
|
||||
preview = $('<div id="wmd-preview" class="wmd-hidetab" />').insertAfter('.editor');
|
||||
let isFullScreen = false;
|
||||
|
||||
var options = {}, isMarkdown = <?php echo intval($content->isMarkdown || !$content->have()); ?>;
|
||||
const options = {}, isMarkdown = <?php echo intval($content->isMarkdown || !$content->have()); ?>;
|
||||
|
||||
options.strings = {
|
||||
bold: '<?php _e('加粗'); ?> <strong> Ctrl+B',
|
||||
@ -59,13 +86,13 @@ $(document).ready(function () {
|
||||
help: '<?php _e('Markdown语法帮助'); ?>'
|
||||
};
|
||||
|
||||
var converter = new HyperDown(),
|
||||
const converter = new HyperDown(),
|
||||
editor = new Markdown.Editor(converter, '', options);
|
||||
|
||||
// 自动跟随
|
||||
converter.enableHtml(true);
|
||||
converter.enableLine(true);
|
||||
reloadScroll = scrollableEditor(textarea, preview);
|
||||
const reloadScroll = scrollableEditor(textarea, preview);
|
||||
|
||||
// 修正白名单
|
||||
converter.hook('makeHtml', function (html) {
|
||||
@ -82,7 +109,7 @@ $(document).ready(function () {
|
||||
|
||||
// 替换block
|
||||
html = html.replace(/<(iframe|embed)\s+([^>]*)>/ig, function (all, tag, src) {
|
||||
if (src[src.length - 1] == '/') {
|
||||
if (src[src.length - 1] === '/') {
|
||||
src = src.substring(0, src.length - 1);
|
||||
}
|
||||
|
||||
@ -94,15 +121,16 @@ $(document).ready(function () {
|
||||
});
|
||||
|
||||
editor.hooks.chain('onPreviewRefresh', function () {
|
||||
var images = $('img', preview), count = images.length;
|
||||
const images = $('img', preview);
|
||||
let count = images.length;
|
||||
|
||||
if (count == 0) {
|
||||
if (count === 0) {
|
||||
reloadScroll(true);
|
||||
} else {
|
||||
images.bind('load error', function () {
|
||||
count --;
|
||||
|
||||
if (count == 0) {
|
||||
if (count === 0) {
|
||||
reloadScroll(true);
|
||||
}
|
||||
});
|
||||
@ -111,8 +139,8 @@ $(document).ready(function () {
|
||||
|
||||
<?php \Typecho\Plugin::factory('admin/editor-js.php')->call('markdownEditor', $content); ?>
|
||||
|
||||
var th = textarea.height(), ph = preview.height(),
|
||||
uploadBtn = $('<button type="button" id="btn-fullscreen-upload" class="btn btn-link">'
|
||||
let th = textarea.height(), ph = preview.height();
|
||||
const uploadBtn = $('<button type="button" id="btn-fullscreen-upload" class="btn btn-link">'
|
||||
+ '<i class="i-upload"><?php _e('附件'); ?></i></button>')
|
||||
.prependTo('.submit .right')
|
||||
.click(function() {
|
||||
@ -129,7 +157,7 @@ $(document).ready(function () {
|
||||
th = textarea.height();
|
||||
ph = preview.height();
|
||||
$(document.body).addClass('fullscreen');
|
||||
var h = $(window).height() - toolbar.outerHeight();
|
||||
const h = $(window).height() - toolbar.outerHeight();
|
||||
|
||||
textarea.css('height', h);
|
||||
preview.css('height', h);
|
||||
@ -139,7 +167,7 @@ $(document).ready(function () {
|
||||
editor.hooks.chain('enterFullScreen', function () {
|
||||
$(document.body).addClass('fullscreen');
|
||||
|
||||
var h = window.screen.height - toolbar.outerHeight();
|
||||
const h = window.screen.height - toolbar.outerHeight();
|
||||
textarea.css('height', h);
|
||||
preview.css('height', h);
|
||||
isFullScreen = true;
|
||||
@ -156,19 +184,23 @@ $(document).ready(function () {
|
||||
textarea.trigger('input');
|
||||
});
|
||||
|
||||
editor.hooks.chain('save', function () {
|
||||
Typecho.savePost();
|
||||
});
|
||||
|
||||
function initMarkdown() {
|
||||
editor.run();
|
||||
|
||||
var imageButton = $('#wmd-image-button'),
|
||||
const imageButton = $('#wmd-image-button'),
|
||||
linkButton = $('#wmd-link-button');
|
||||
|
||||
Typecho.insertFileToEditor = function (file, url, isImage) {
|
||||
var button = isImage ? imageButton : linkButton;
|
||||
const button = isImage ? imageButton : linkButton;
|
||||
|
||||
options.strings[isImage ? 'imagename' : 'linkname'] = file;
|
||||
button.trigger('click');
|
||||
|
||||
var checkDialog = setInterval(function () {
|
||||
let checkDialog = setInterval(function () {
|
||||
if ($('.wmd-prompt-dialog').length > 0) {
|
||||
$('.wmd-prompt-dialog input').val(url).select();
|
||||
clearInterval(checkDialog);
|
||||
@ -177,12 +209,12 @@ $(document).ready(function () {
|
||||
}, 10);
|
||||
};
|
||||
|
||||
Typecho.uploadComplete = function (file) {
|
||||
Typecho.insertFileToEditor(file.title, file.url, file.isImage);
|
||||
Typecho.uploadComplete = function (attachment) {
|
||||
Typecho.insertFileToEditor(attachment.title, attachment.url, attachment.isImage);
|
||||
};
|
||||
|
||||
// 编辑预览切换
|
||||
var edittab = $('.editor').prepend('<div class="wmd-edittab"><a href="#wmd-editarea" class="active"><?php _e('撰写'); ?></a><a href="#wmd-preview"><?php _e('预览'); ?></a></div>'),
|
||||
const edittab = $('.editor').prepend('<div class="wmd-edittab"><a href="#wmd-editarea" class="active"><?php _e('撰写'); ?></a><a href="#wmd-preview"><?php _e('预览'); ?></a></div>'),
|
||||
editarea = $(textarea.parent()).attr("id", "wmd-editarea");
|
||||
|
||||
$(".wmd-edittab a").click(function() {
|
||||
@ -190,11 +222,11 @@ $(document).ready(function () {
|
||||
$(this).addClass("active");
|
||||
$("#wmd-editarea, #wmd-preview").addClass("wmd-hidetab");
|
||||
|
||||
var selected_tab = $(this).attr("href"),
|
||||
const selected_tab = $(this).attr("href"),
|
||||
selected_el = $(selected_tab).removeClass("wmd-hidetab");
|
||||
|
||||
// 预览时隐藏编辑器按钮
|
||||
if (selected_tab == "#wmd-preview") {
|
||||
if (selected_tab === "#wmd-preview") {
|
||||
$("#wmd-button-row").addClass("wmd-visualhide");
|
||||
} else {
|
||||
$("#wmd-button-row").removeClass("wmd-visualhide");
|
||||
@ -207,21 +239,30 @@ $(document).ready(function () {
|
||||
});
|
||||
|
||||
// 剪贴板复制图片
|
||||
textarea.pastableTextarea().on('pasteImage', function (e, data) {
|
||||
var name = data.name ? data.name.replace(/[\(\)\[\]\*#!]/g, '') : (new Date()).toISOString().replace(/\..+$/, '');
|
||||
if (!name.match(/\.[a-z0-9]{2,}$/i)) {
|
||||
var ext = data.blob.type.split('/').pop();
|
||||
name += '.' + ext;
|
||||
}
|
||||
textarea.bind('paste', function (e) {
|
||||
const items = (e.clipboardData || e.originalEvent.clipboardData).items;
|
||||
|
||||
Typecho.uploadFile(new File([data.blob], name), name);
|
||||
for (const item of items) {
|
||||
if (item.kind === 'file') {
|
||||
const file = item.getAsFile();
|
||||
|
||||
if (file.size > 0) {
|
||||
if (!file.name) {
|
||||
file.name = (new Date()).toISOString().replace(/\..+$/, '')
|
||||
+ '.' + file.type.split('/').pop();
|
||||
}
|
||||
|
||||
Typecho.uploadFile(file);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (isMarkdown) {
|
||||
initMarkdown();
|
||||
} else {
|
||||
var notice = $('<div class="message notice"><?php _e('这篇文章不是由Markdown语法创建的, 继续使用Markdown编辑它吗?'); ?> '
|
||||
const notice = $('<div class="message notice"><?php _e('这篇文章不是由Markdown语法创建的, 继续使用Markdown编辑它吗?'); ?> '
|
||||
+ '<button class="btn btn-xs primary yes"><?php _e('是'); ?></button> '
|
||||
+ '<button class="btn btn-xs no"><?php _e('否'); ?></button></div>')
|
||||
.hide().insertBefore(textarea).slideDown();
|
||||
|
@ -1,20 +1,15 @@
|
||||
<?php if(!defined('__TYPECHO_ADMIN__')) exit; ?>
|
||||
<?php
|
||||
if (isset($post) && $post instanceof \Typecho\Widget && $post->have()) {
|
||||
$fileParentContent = $post;
|
||||
} elseif (isset($page) && $page instanceof \Typecho\Widget && $page->have()) {
|
||||
$fileParentContent = $page;
|
||||
}
|
||||
$phpMaxFilesize = function_exists('ini_get') ? trim(ini_get('upload_max_filesize')) : '0';
|
||||
|
||||
$phpMaxFilesize = function_exists('ini_get') ? trim(ini_get('upload_max_filesize')) : 0;
|
||||
if (preg_match("/^([0-9]+)([a-z]{1,2})?$/i", $phpMaxFilesize, $matches)) {
|
||||
$size = intval($matches[1]);
|
||||
$unit = $matches[2] ?? 'b';
|
||||
|
||||
if (preg_match("/^([0-9]+)([a-z]{1,2})$/i", $phpMaxFilesize, $matches)) {
|
||||
$phpMaxFilesize = strtolower($matches[1] . $matches[2] . (1 == strlen($matches[2]) ? 'b' : ''));
|
||||
$phpMaxFilesize = round($size * pow(1024, stripos('bkmgtpezy', $unit[0])));
|
||||
}
|
||||
?>
|
||||
|
||||
<script src="<?php $options->adminStaticUrl('js', 'moxie.js'); ?>"></script>
|
||||
<script src="<?php $options->adminStaticUrl('js', 'plupload.js'); ?>"></script>
|
||||
<script>
|
||||
$(document).ready(function() {
|
||||
function updateAttachmentNumber () {
|
||||
@ -29,24 +24,40 @@ $(document).ready(function() {
|
||||
}
|
||||
|
||||
balloon.html(count);
|
||||
} else if (0 == count && balloon.length > 0) {
|
||||
} else if (0 === count && balloon.length > 0) {
|
||||
balloon.remove();
|
||||
}
|
||||
}
|
||||
|
||||
$('.upload-area').bind({
|
||||
dragenter : function () {
|
||||
updateAttachmentNumber();
|
||||
|
||||
const uploadUrl = $('.upload-area').bind({
|
||||
dragenter : function (e) {
|
||||
$(this).parent().addClass('drag');
|
||||
},
|
||||
|
||||
dragover : function (e) {
|
||||
e.stopPropagation();
|
||||
e.preventDefault();
|
||||
$(this).parent().addClass('drag');
|
||||
},
|
||||
|
||||
drop : function () {
|
||||
drop : function (e) {
|
||||
e.stopPropagation();
|
||||
e.preventDefault();
|
||||
$(this).parent().removeClass('drag');
|
||||
|
||||
const files = e.originalEvent.dataTransfer.files;
|
||||
|
||||
if (files.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (const file of files) {
|
||||
Typecho.uploadFile(file);
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
dragend : function () {
|
||||
$(this).parent().removeClass('drag');
|
||||
},
|
||||
@ -54,31 +65,44 @@ $(document).ready(function() {
|
||||
dragleave : function () {
|
||||
$(this).parent().removeClass('drag');
|
||||
}
|
||||
}).data('url');
|
||||
|
||||
const btn = $('.upload-file');
|
||||
const fileInput = $('<input type="file" name="file" />').hide().insertAfter(btn);
|
||||
|
||||
btn.click(function () {
|
||||
fileInput.click();
|
||||
return false;
|
||||
});
|
||||
|
||||
updateAttachmentNumber();
|
||||
fileInput.change(function () {
|
||||
if (this.files.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
Typecho.uploadFile(this.files[0]);
|
||||
});
|
||||
|
||||
function fileUploadStart (file) {
|
||||
$('<li id="' + file.id + '" class="loading">'
|
||||
+ file.name + '</li>').appendTo('#file-list');
|
||||
}
|
||||
|
||||
function fileUploadError (error) {
|
||||
var file = error.file, code = error.code, word;
|
||||
function fileUploadError (type, file) {
|
||||
let word = '<?php _e('上传出现错误'); ?>';
|
||||
|
||||
switch (code) {
|
||||
case plupload.FILE_SIZE_ERROR:
|
||||
switch (type) {
|
||||
case 'size':
|
||||
word = '<?php _e('文件大小超过限制'); ?>';
|
||||
break;
|
||||
case plupload.FILE_EXTENSION_ERROR:
|
||||
case 'type':
|
||||
word = '<?php _e('文件扩展名不被支持'); ?>';
|
||||
break;
|
||||
case plupload.FILE_DUPLICATE_ERROR:
|
||||
case 'duplicate':
|
||||
word = '<?php _e('文件已经上传过'); ?>';
|
||||
break;
|
||||
case plupload.HTTP_ERROR:
|
||||
case 'network':
|
||||
default:
|
||||
word = '<?php _e('上传出现错误'); ?>';
|
||||
break;
|
||||
}
|
||||
|
||||
@ -94,104 +118,91 @@ $(document).ready(function() {
|
||||
li.effect('highlight', {color : '#FBC2C4'}, 2000, function () {
|
||||
$(this).remove();
|
||||
});
|
||||
|
||||
// fix issue #341
|
||||
this.removeFile(file);
|
||||
}
|
||||
|
||||
var completeFile = null;
|
||||
function fileUploadComplete (id, url, data) {
|
||||
var li = $('#' + id).removeClass('loading').data('cid', data.cid)
|
||||
.data('url', data.url)
|
||||
.data('image', data.isImage)
|
||||
.html('<input type="hidden" name="attachment[]" value="' + data.cid + '" />'
|
||||
+ '<a class="insert" target="_blank" href="###" title="<?php _e('点击插入文件'); ?>">' + data.title + '</a><div class="info">' + data.bytes
|
||||
function fileUploadComplete (file, attachment) {
|
||||
const li = $('#' + file.id).removeClass('loading').data('cid', attachment.cid)
|
||||
.data('url', attachment.url)
|
||||
.data('image', attachment.isImage)
|
||||
.html('<input type="hidden" name="attachment[]" value="' + attachment.cid + '" />'
|
||||
+ '<a class="insert" target="_blank" href="###" title="<?php _e('点击插入文件'); ?>">'
|
||||
+ attachment.title + '</a><div class="info">' + attachment.bytes
|
||||
+ ' <a class="file" target="_blank" href="<?php $options->adminUrl('media.php'); ?>?cid='
|
||||
+ data.cid + '" title="<?php _e('编辑'); ?>"><i class="i-edit"></i></a>'
|
||||
+ attachment.cid + '" title="<?php _e('编辑'); ?>"><i class="i-edit"></i></a>'
|
||||
+ ' <a class="delete" href="###" title="<?php _e('删除'); ?>"><i class="i-delete"></i></a></div>')
|
||||
.effect('highlight', 1000);
|
||||
|
||||
|
||||
attachInsertEvent(li);
|
||||
attachDeleteEvent(li);
|
||||
updateAttachmentNumber();
|
||||
|
||||
if (!completeFile) {
|
||||
completeFile = data;
|
||||
}
|
||||
Typecho.uploadComplete(attachment);
|
||||
}
|
||||
|
||||
var uploader = null, tabFilesEl = $('#tab-files').bind('init', function () {
|
||||
uploader = new plupload.Uploader({
|
||||
browse_button : $('.upload-file').get(0),
|
||||
url : '<?php $security->index('/action/upload'
|
||||
. (isset($fileParentContent) ? '?cid=' . $fileParentContent->cid : '')); ?>',
|
||||
runtimes : 'html5,flash,html4',
|
||||
flash_swf_url : '<?php $options->adminStaticUrl('js', 'Moxie.swf'); ?>',
|
||||
drop_element : $('.upload-area').get(0),
|
||||
filters : {
|
||||
max_file_size : '<?php echo $phpMaxFilesize ?>',
|
||||
mime_types : [{'title' : '<?php _e('允许上传的文件'); ?>', 'extensions' : '<?php echo implode(',', $options->allowedAttachmentTypes); ?>'}],
|
||||
prevent_duplicates : true
|
||||
},
|
||||
Typecho.uploadFile = (function () {
|
||||
const types = '<?php echo json_encode($options->allowedAttachmentTypes); ?>';
|
||||
const maxSize = <?php echo $phpMaxFilesize ?>;
|
||||
const queue = [];
|
||||
let index = 0;
|
||||
|
||||
init : {
|
||||
FilesAdded : function (up, files) {
|
||||
for (var i = 0; i < files.length; i ++) {
|
||||
fileUploadStart(files[i]);
|
||||
}
|
||||
const getUrl = function () {
|
||||
const url = new URL(uploadUrl);
|
||||
const cid = $('input[name=cid]').val();
|
||||
|
||||
completeFile = null;
|
||||
uploader.start();
|
||||
},
|
||||
url.searchParams.append('cid', cid);
|
||||
return url.toString();
|
||||
};
|
||||
|
||||
UploadComplete : function () {
|
||||
if (completeFile) {
|
||||
Typecho.uploadComplete(completeFile);
|
||||
}
|
||||
},
|
||||
const upload = function () {
|
||||
const file = queue.shift();
|
||||
|
||||
FileUploaded : function (up, file, result) {
|
||||
if (200 == result.status) {
|
||||
var data = $.parseJSON(result.response);
|
||||
|
||||
if (data) {
|
||||
fileUploadComplete(file.id, data[0], data[1]);
|
||||
uploader.removeFile(file);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
fileUploadError.call(uploader, {
|
||||
code : plupload.HTTP_ERROR,
|
||||
file : file
|
||||
});
|
||||
},
|
||||
|
||||
Error : function (up, error) {
|
||||
fileUploadError.call(uploader, error);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
uploader.init();
|
||||
});
|
||||
|
||||
Typecho.uploadFile = function (file, name) {
|
||||
if (!uploader) {
|
||||
$('#tab-files-btn').parent().trigger('click');
|
||||
}
|
||||
|
||||
var timer = setInterval(function () {
|
||||
if (!uploader) {
|
||||
if (!file) {
|
||||
return;
|
||||
}
|
||||
|
||||
clearInterval(timer);
|
||||
timer = null;
|
||||
const data = new FormData();
|
||||
data.append('file', file);
|
||||
|
||||
uploader.addFile(file, name);
|
||||
}, 50);
|
||||
};
|
||||
fetch(getUrl(), {
|
||||
method: 'POST',
|
||||
body: data
|
||||
}).then(function (response) {
|
||||
if (response.ok) {
|
||||
return response.json();
|
||||
} else {
|
||||
throw new Error(response.statusText);
|
||||
}
|
||||
}).then(function (data) {
|
||||
if (data) {
|
||||
const [_, attachment] = data;
|
||||
fileUploadComplete(file, attachment);
|
||||
upload();
|
||||
} else {
|
||||
throw new Error('no data');
|
||||
}
|
||||
}).catch(function (error) {
|
||||
fileUploadError('network', file);
|
||||
upload();
|
||||
});
|
||||
};
|
||||
|
||||
return function (file) {
|
||||
file.id = 'upload-' + (index++);
|
||||
|
||||
if (file.size > maxSize) {
|
||||
return fileUploadError('size', file);
|
||||
}
|
||||
|
||||
const match = file.name.match(/\.([a-z0-9]+)$/i);
|
||||
if (!match || types.indexOf(match[1].toLowerCase()) < 0) {
|
||||
return fileUploadError('type', file);
|
||||
}
|
||||
|
||||
queue.push(file);
|
||||
fileUploadStart(file);
|
||||
upload();
|
||||
};
|
||||
})();
|
||||
|
||||
function attachInsertEvent (el) {
|
||||
$('.insert', el).click(function () {
|
||||
|
@ -13,7 +13,9 @@ if (isset($post) || isset($page)) {
|
||||
?>
|
||||
|
||||
<div id="upload-panel" class="p">
|
||||
<div class="upload-area" draggable="true"><?php _e('拖放文件到这里<br>或者 %s选择文件上传%s', '<a href="###" class="upload-file">', '</a>'); ?></div>
|
||||
<div class="upload-area" data-url="<?php $security->index('/action/upload'); ?>">
|
||||
<?php _e('拖放文件到这里<br>或者 %s选择文件上传%s', '<a href="###" class="upload-file">', '</a>'); ?>
|
||||
</div>
|
||||
<ul id="file-list">
|
||||
<?php while ($attachment->next()): ?>
|
||||
<li data-cid="<?php $attachment->cid(); ?>" data-url="<?php echo $attachment->attachment->url; ?>" data-image="<?php echo $attachment->attachment->isImage ? 1 : 0; ?>"><input type="hidden" name="attachment[]" value="<?php $attachment->cid(); ?>" />
|
||||
|
@ -8,12 +8,18 @@
|
||||
$('html,body').scrollTop(error.parents('.typecho-option').offset().top);
|
||||
}
|
||||
|
||||
$('form').submit(function () {
|
||||
if (this.submitted) {
|
||||
$('.main form').submit(function () {
|
||||
const self = $(this);
|
||||
|
||||
if (self.hasClass('submitting')) {
|
||||
return false;
|
||||
} else {
|
||||
this.submitted = true;
|
||||
$('button[type=submit]', this).attr('disabled', 'disabled');
|
||||
self.addClass('submitting');
|
||||
}
|
||||
}).on('submitted', function () {
|
||||
$('button[type=submit]', this).removeAttr('disabled');
|
||||
$(this).removeClass('submitting');
|
||||
});
|
||||
|
||||
$('label input[type=text]').click(function (e) {
|
||||
|
Binary file not shown.
File diff suppressed because one or more lines are too long
2
admin/js/jquery-ui.js
vendored
2
admin/js/jquery-ui.js
vendored
File diff suppressed because one or more lines are too long
2
admin/js/jquery.js
vendored
2
admin/js/jquery.js
vendored
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
140
admin/media.php
140
admin/media.php
@ -3,13 +3,7 @@ include 'common.php';
|
||||
include 'header.php';
|
||||
include 'menu.php';
|
||||
|
||||
$phpMaxFilesize = function_exists('ini_get') ? trim(ini_get('upload_max_filesize')) : 0;
|
||||
|
||||
if (preg_match("/^([0-9]+)([a-z]{1,2})$/i", $phpMaxFilesize, $matches)) {
|
||||
$phpMaxFilesize = strtolower($matches[1] . $matches[2] . (1 == strlen($matches[2]) ? 'b' : ''));
|
||||
}
|
||||
|
||||
$attachment = \Widget\Contents\Attachment\Edit::alloc();
|
||||
\Widget\Contents\Attachment\Edit::alloc()->prepare()->to($attachment);
|
||||
?>
|
||||
|
||||
<div class="main">
|
||||
@ -35,8 +29,9 @@ $attachment = \Widget\Contents\Attachment\Edit::alloc();
|
||||
</p>
|
||||
|
||||
<div id="upload-panel" class="p">
|
||||
<div class="upload-area"
|
||||
draggable="true"><?php _e('拖放文件到这里<br>或者 %s选择文件上传%s', '<a href="###" class="upload-file">', '</a>'); ?></div>
|
||||
<div class="upload-area" data-url="<?php $security->index('/action/upload?do=modify'); ?>">
|
||||
<?php _e('拖放文件到这里<br>或者 %s选择文件上传%s', '<a href="###" class="upload-file">', '</a>'); ?>
|
||||
</div>
|
||||
<ul id="file-list"></ul>
|
||||
</div>
|
||||
</div>
|
||||
@ -50,9 +45,8 @@ $attachment = \Widget\Contents\Attachment\Edit::alloc();
|
||||
<?php
|
||||
include 'copyright.php';
|
||||
include 'common-js.php';
|
||||
include 'file-upload-js.php';
|
||||
?>
|
||||
<script src="<?php $options->adminStaticUrl('js', 'moxie.js'); ?>"></script>
|
||||
<script src="<?php $options->adminStaticUrl('js', 'plupload.js'); ?>"></script>
|
||||
<script type="text/javascript">
|
||||
$(document).ready(function () {
|
||||
$('#attachment-url').click(function () {
|
||||
@ -69,130 +63,16 @@ include 'common-js.php';
|
||||
return false;
|
||||
});
|
||||
|
||||
$('.upload-area').bind({
|
||||
dragenter: function () {
|
||||
$(this).parent().addClass('drag');
|
||||
},
|
||||
|
||||
dragover: function (e) {
|
||||
$(this).parent().addClass('drag');
|
||||
},
|
||||
|
||||
drop: function () {
|
||||
$(this).parent().removeClass('drag');
|
||||
},
|
||||
|
||||
dragend: function () {
|
||||
$(this).parent().removeClass('drag');
|
||||
},
|
||||
|
||||
dragleave: function () {
|
||||
$(this).parent().removeClass('drag');
|
||||
}
|
||||
});
|
||||
|
||||
function fileUploadStart(file) {
|
||||
$('<ul id="file-list"></ul>').appendTo('#upload-panel');
|
||||
$('<li id="' + file.id + '" class="loading">'
|
||||
+ file.name + '</li>').prependTo('#file-list');
|
||||
}
|
||||
|
||||
function fileUploadError(error) {
|
||||
var file = error.file, code = error.code, word;
|
||||
|
||||
switch (code) {
|
||||
case plupload.FILE_SIZE_ERROR:
|
||||
word = '<?php _e('文件大小超过限制'); ?>';
|
||||
break;
|
||||
case plupload.FILE_EXTENSION_ERROR:
|
||||
word = '<?php _e('文件扩展名不被支持'); ?>';
|
||||
break;
|
||||
case plupload.FILE_DUPLICATE_ERROR:
|
||||
word = '<?php _e('文件已经上传过'); ?>';
|
||||
break;
|
||||
case plupload.HTTP_ERROR:
|
||||
default:
|
||||
word = '<?php _e('上传出现错误'); ?>';
|
||||
break;
|
||||
Typecho.uploadComplete = function (attachment) {
|
||||
if (attachment.isImage) {
|
||||
$('.typecho-attachment-photo').attr('src', attachment.url + '?' + Math.random());
|
||||
}
|
||||
|
||||
var fileError = '<?php _e('%s 上传失败'); ?>'.replace('%s', file.name),
|
||||
li, exist = $('#' + file.id);
|
||||
|
||||
if (exist.length > 0) {
|
||||
li = exist.removeClass('loading').html(fileError);
|
||||
} else {
|
||||
$('<ul id="file-list"></ul>').appendTo('#upload-panel');
|
||||
li = $('<li>' + fileError + '<br />' + word + '</li>').prependTo('#file-list');
|
||||
}
|
||||
|
||||
li.effect('highlight', {color: '#FBC2C4'}, 2000, function () {
|
||||
$(this).remove();
|
||||
});
|
||||
}
|
||||
|
||||
function fileUploadComplete(id, url, data) {
|
||||
var img = $('.typecho-attachment-photo');
|
||||
|
||||
if (img.length > 0) {
|
||||
img.get(0).src = '<?php $attachment->attachment->url(); ?>?' + Math.random();
|
||||
}
|
||||
|
||||
$('#' + id).text('<?php _e('文件 %s 已经替换'); ?>'.replace('%s', data.title))
|
||||
$('#file-list li').text('<?php _e('文件 %s 已经替换'); ?>'.replace('%s', attachment.title))
|
||||
.effect('highlight', 1000, function () {
|
||||
$(this).remove();
|
||||
$('#file-list').remove();
|
||||
});
|
||||
}
|
||||
|
||||
var uploader = new plupload.Uploader({
|
||||
browse_button: $('.upload-file').get(0),
|
||||
url: '<?php $security->index('/action/upload?do=modify&cid=' . $attachment->cid); ?>',
|
||||
runtimes: 'html5,flash,html4',
|
||||
flash_swf_url: '<?php $options->adminStaticUrl('js', 'Moxie.swf'); ?>',
|
||||
drop_element: $('.upload-area').get(0),
|
||||
filters: {
|
||||
max_file_size: '<?php echo $phpMaxFilesize ?>',
|
||||
mime_types: [{
|
||||
'title': '<?php _e('允许上传的文件'); ?>',
|
||||
'extensions': '<?php $attachment->attachment->type(); ?>'
|
||||
}],
|
||||
prevent_duplicates: true
|
||||
},
|
||||
multi_selection: false,
|
||||
|
||||
init: {
|
||||
FilesAdded: function (up, files) {
|
||||
plupload.each(files, function (file) {
|
||||
fileUploadStart(file);
|
||||
});
|
||||
|
||||
uploader.start();
|
||||
},
|
||||
|
||||
FileUploaded: function (up, file, result) {
|
||||
if (200 == result.status) {
|
||||
var data = $.parseJSON(result.response);
|
||||
|
||||
if (data) {
|
||||
fileUploadComplete(file.id, data[0], data[1]);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
fileUploadError({
|
||||
code: plupload.HTTP_ERROR,
|
||||
file: file
|
||||
});
|
||||
},
|
||||
|
||||
Error: function (up, error) {
|
||||
fileUploadError(error);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
uploader.init();
|
||||
};
|
||||
});
|
||||
</script>
|
||||
<?php
|
||||
|
11714
admin/src/js/moxie.js
11714
admin/src/js/moxie.js
File diff suppressed because it is too large
Load Diff
@ -1767,6 +1767,8 @@ else
|
||||
hooks.addNoop("enterFakeFullScreen");
|
||||
hooks.addNoop("exitFullScreen");
|
||||
|
||||
hooks.addNoop("save");
|
||||
|
||||
this.getConverter = function () { return markdownConverter; }
|
||||
|
||||
var that = this,
|
||||
@ -3082,11 +3084,13 @@ else
|
||||
doClick(buttons.undo);
|
||||
}
|
||||
break;
|
||||
case "s":
|
||||
hooks.save();
|
||||
break;
|
||||
default:
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
if (key.preventDefault) {
|
||||
key.preventDefault();
|
||||
}
|
||||
|
@ -1,443 +0,0 @@
|
||||
// Generated by CoffeeScript 1.12.7
|
||||
|
||||
/*
|
||||
paste.js is an interface to read data ( text / image ) from clipboard in different browsers. It also contains several hacks.
|
||||
|
||||
https://github.com/layerssss/paste.js
|
||||
*/
|
||||
|
||||
(function() {
|
||||
var $, Paste, createHiddenEditable, dataURLtoBlob, isFocusable;
|
||||
|
||||
$ = window.jQuery;
|
||||
|
||||
$.paste = function(pasteContainer) {
|
||||
var pm;
|
||||
if (typeof console !== "undefined" && console !== null) {
|
||||
console.log("DEPRECATED: This method is deprecated. Please use $.fn.pastableNonInputable() instead.");
|
||||
}
|
||||
pm = Paste.mountNonInputable(pasteContainer);
|
||||
return pm._container;
|
||||
};
|
||||
|
||||
$.fn.pastableNonInputable = function() {
|
||||
var el, j, len, ref;
|
||||
ref = this;
|
||||
for (j = 0, len = ref.length; j < len; j++) {
|
||||
el = ref[j];
|
||||
if (el._pastable || $(el).is('textarea, input:text, [contenteditable]')) {
|
||||
continue;
|
||||
}
|
||||
Paste.mountNonInputable(el);
|
||||
el._pastable = true;
|
||||
}
|
||||
return this;
|
||||
};
|
||||
|
||||
$.fn.pastableTextarea = function() {
|
||||
var el, j, len, ref;
|
||||
ref = this;
|
||||
for (j = 0, len = ref.length; j < len; j++) {
|
||||
el = ref[j];
|
||||
if (el._pastable || $(el).is(':not(textarea, input:text)')) {
|
||||
continue;
|
||||
}
|
||||
Paste.mountTextarea(el);
|
||||
el._pastable = true;
|
||||
}
|
||||
return this;
|
||||
};
|
||||
|
||||
$.fn.pastableContenteditable = function() {
|
||||
var el, j, len, ref;
|
||||
ref = this;
|
||||
for (j = 0, len = ref.length; j < len; j++) {
|
||||
el = ref[j];
|
||||
if (el._pastable || $(el).is(':not([contenteditable])')) {
|
||||
continue;
|
||||
}
|
||||
Paste.mountContenteditable(el);
|
||||
el._pastable = true;
|
||||
}
|
||||
return this;
|
||||
};
|
||||
|
||||
dataURLtoBlob = function(dataURL, sliceSize) {
|
||||
var b64Data, byteArray, byteArrays, byteCharacters, byteNumbers, contentType, i, m, offset, ref, slice;
|
||||
if (sliceSize == null) {
|
||||
sliceSize = 512;
|
||||
}
|
||||
if (!(m = dataURL.match(/^data\:([^\;]+)\;base64\,(.+)$/))) {
|
||||
return null;
|
||||
}
|
||||
ref = m, m = ref[0], contentType = ref[1], b64Data = ref[2];
|
||||
byteCharacters = atob(b64Data);
|
||||
byteArrays = [];
|
||||
offset = 0;
|
||||
while (offset < byteCharacters.length) {
|
||||
slice = byteCharacters.slice(offset, offset + sliceSize);
|
||||
byteNumbers = new Array(slice.length);
|
||||
i = 0;
|
||||
while (i < slice.length) {
|
||||
byteNumbers[i] = slice.charCodeAt(i);
|
||||
i++;
|
||||
}
|
||||
byteArray = new Uint8Array(byteNumbers);
|
||||
byteArrays.push(byteArray);
|
||||
offset += sliceSize;
|
||||
}
|
||||
return new Blob(byteArrays, {
|
||||
type: contentType
|
||||
});
|
||||
};
|
||||
|
||||
createHiddenEditable = function() {
|
||||
return $(document.createElement('div')).attr('contenteditable', true).attr('aria-hidden', true).attr('tabindex', -1).css({
|
||||
width: 1,
|
||||
height: 1,
|
||||
position: 'fixed',
|
||||
left: -100,
|
||||
overflow: 'hidden',
|
||||
opacity: 1e-17
|
||||
});
|
||||
};
|
||||
|
||||
isFocusable = function(element, hasTabindex) {
|
||||
var fieldset, focusableIfVisible, img, map, mapName, nodeName;
|
||||
map = void 0;
|
||||
mapName = void 0;
|
||||
img = void 0;
|
||||
focusableIfVisible = void 0;
|
||||
fieldset = void 0;
|
||||
nodeName = element.nodeName.toLowerCase();
|
||||
if ('area' === nodeName) {
|
||||
map = element.parentNode;
|
||||
mapName = map.name;
|
||||
if (!element.href || !mapName || map.nodeName.toLowerCase() !== 'map') {
|
||||
return false;
|
||||
}
|
||||
img = $('img[usemap=\'#' + mapName + '\']');
|
||||
return img.length > 0 && img.is(':visible');
|
||||
}
|
||||
if (/^(input|select|textarea|button|object)$/.test(nodeName)) {
|
||||
focusableIfVisible = !element.disabled;
|
||||
if (focusableIfVisible) {
|
||||
fieldset = $(element).closest('fieldset')[0];
|
||||
if (fieldset) {
|
||||
focusableIfVisible = !fieldset.disabled;
|
||||
}
|
||||
}
|
||||
} else if ('a' === nodeName) {
|
||||
focusableIfVisible = element.href || hasTabindex;
|
||||
} else {
|
||||
focusableIfVisible = hasTabindex;
|
||||
}
|
||||
focusableIfVisible = focusableIfVisible || $(element).is('[contenteditable]');
|
||||
return focusableIfVisible && $(element).is(':visible');
|
||||
};
|
||||
|
||||
Paste = (function() {
|
||||
Paste.prototype._target = null;
|
||||
|
||||
Paste.prototype._container = null;
|
||||
|
||||
Paste.mountNonInputable = function(nonInputable) {
|
||||
var paste;
|
||||
paste = new Paste(createHiddenEditable().appendTo(nonInputable), nonInputable);
|
||||
$(nonInputable).on('click', (function(_this) {
|
||||
return function(ev) {
|
||||
if (!(isFocusable(ev.target, false) || window.getSelection().toString())) {
|
||||
return paste._container.focus();
|
||||
}
|
||||
};
|
||||
})(this));
|
||||
paste._container.on('focus', (function(_this) {
|
||||
return function() {
|
||||
return $(nonInputable).addClass('pastable-focus');
|
||||
};
|
||||
})(this));
|
||||
return paste._container.on('blur', (function(_this) {
|
||||
return function() {
|
||||
return $(nonInputable).removeClass('pastable-focus');
|
||||
};
|
||||
})(this));
|
||||
};
|
||||
|
||||
Paste.mountTextarea = function(textarea) {
|
||||
var ctlDown, paste, ref, ref1;
|
||||
if ((typeof DataTransfer !== "undefined" && DataTransfer !== null ? DataTransfer.prototype : void 0) && ((ref = Object.getOwnPropertyDescriptor) != null ? (ref1 = ref.call(Object, DataTransfer.prototype, 'items')) != null ? ref1.get : void 0 : void 0)) {
|
||||
return this.mountContenteditable(textarea);
|
||||
}
|
||||
paste = new Paste(createHiddenEditable().insertBefore(textarea), textarea);
|
||||
ctlDown = false;
|
||||
$(textarea).on('keyup', function(ev) {
|
||||
var ref2;
|
||||
if ((ref2 = ev.keyCode) === 17 || ref2 === 224) {
|
||||
ctlDown = false;
|
||||
}
|
||||
return null;
|
||||
});
|
||||
$(textarea).on('keydown', function(ev) {
|
||||
var ref2;
|
||||
if ((ref2 = ev.keyCode) === 17 || ref2 === 224) {
|
||||
ctlDown = true;
|
||||
}
|
||||
if ((ev.ctrlKey != null) && (ev.metaKey != null)) {
|
||||
ctlDown = ev.ctrlKey || ev.metaKey;
|
||||
}
|
||||
if (ctlDown && ev.keyCode === 86) {
|
||||
paste._textarea_focus_stolen = true;
|
||||
paste._container.focus();
|
||||
paste._paste_event_fired = false;
|
||||
setTimeout((function(_this) {
|
||||
return function() {
|
||||
if (!paste._paste_event_fired) {
|
||||
$(textarea).focus();
|
||||
return paste._textarea_focus_stolen = false;
|
||||
}
|
||||
};
|
||||
})(this), 1);
|
||||
}
|
||||
return null;
|
||||
});
|
||||
$(textarea).on('paste', (function(_this) {
|
||||
return function() {};
|
||||
})(this));
|
||||
$(textarea).on('focus', (function(_this) {
|
||||
return function() {
|
||||
if (!paste._textarea_focus_stolen) {
|
||||
return $(textarea).addClass('pastable-focus');
|
||||
}
|
||||
};
|
||||
})(this));
|
||||
$(textarea).on('blur', (function(_this) {
|
||||
return function() {
|
||||
if (!paste._textarea_focus_stolen) {
|
||||
return $(textarea).removeClass('pastable-focus');
|
||||
}
|
||||
};
|
||||
})(this));
|
||||
$(paste._target).on('_pasteCheckContainerDone', (function(_this) {
|
||||
return function() {
|
||||
$(textarea).focus();
|
||||
return paste._textarea_focus_stolen = false;
|
||||
};
|
||||
})(this));
|
||||
return $(paste._target).on('pasteText', (function(_this) {
|
||||
return function(ev, data) {
|
||||
var content, curEnd, curStart;
|
||||
curStart = $(textarea).prop('selectionStart');
|
||||
curEnd = $(textarea).prop('selectionEnd');
|
||||
content = $(textarea).val();
|
||||
$(textarea).val("" + content.slice(0, curStart) + data.text + content.slice(curEnd));
|
||||
$(textarea)[0].setSelectionRange(curStart + data.text.length, curStart + data.text.length);
|
||||
return $(textarea).trigger('change');
|
||||
};
|
||||
})(this));
|
||||
};
|
||||
|
||||
Paste.mountContenteditable = function(contenteditable) {
|
||||
var paste;
|
||||
paste = new Paste(contenteditable, contenteditable);
|
||||
$(contenteditable).on('focus', (function(_this) {
|
||||
return function() {
|
||||
return $(contenteditable).addClass('pastable-focus');
|
||||
};
|
||||
})(this));
|
||||
return $(contenteditable).on('blur', (function(_this) {
|
||||
return function() {
|
||||
return $(contenteditable).removeClass('pastable-focus');
|
||||
};
|
||||
})(this));
|
||||
};
|
||||
|
||||
function Paste(_container, _target) {
|
||||
this._container = _container;
|
||||
this._target = _target;
|
||||
this._container = $(this._container);
|
||||
this._target = $(this._target).addClass('pastable');
|
||||
this._container.on('paste', (function(_this) {
|
||||
return function(ev) {
|
||||
var _i, clipboardData, file, fileType, item, j, k, l, len, len1, len2, pastedFilename, reader, ref, ref1, ref2, ref3, ref4, stringIsFilename, text;
|
||||
_this.originalEvent = (ev.originalEvent !== null ? ev.originalEvent : null);
|
||||
_this._paste_event_fired = true;
|
||||
if (((ref = ev.originalEvent) != null ? ref.clipboardData : void 0) != null) {
|
||||
clipboardData = ev.originalEvent.clipboardData;
|
||||
if (clipboardData.items) {
|
||||
pastedFilename = null;
|
||||
_this.originalEvent.pastedTypes = [];
|
||||
ref1 = clipboardData.items;
|
||||
for (j = 0, len = ref1.length; j < len; j++) {
|
||||
item = ref1[j];
|
||||
if (item.type.match(/^text\/(plain|rtf|html)/)) {
|
||||
_this.originalEvent.pastedTypes.push(item.type);
|
||||
}
|
||||
}
|
||||
ref2 = clipboardData.items;
|
||||
for (_i = k = 0, len1 = ref2.length; k < len1; _i = ++k) {
|
||||
item = ref2[_i];
|
||||
if (item.type.match(/^image\//)) {
|
||||
reader = new FileReader();
|
||||
reader.onload = function(event) {
|
||||
return _this._handleImage(event.target.result, _this.originalEvent, pastedFilename);
|
||||
};
|
||||
try {
|
||||
reader.readAsDataURL(item.getAsFile());
|
||||
} catch (error) {}
|
||||
ev.preventDefault();
|
||||
break;
|
||||
}
|
||||
if (item.type === 'text/plain') {
|
||||
if (_i === 0 && clipboardData.items.length > 1 && clipboardData.items[1].type.match(/^image\//)) {
|
||||
stringIsFilename = true;
|
||||
fileType = clipboardData.items[1].type;
|
||||
}
|
||||
item.getAsString(function(string) {
|
||||
if (stringIsFilename) {
|
||||
pastedFilename = string;
|
||||
return _this._target.trigger('pasteText', {
|
||||
text: string,
|
||||
isFilename: true,
|
||||
fileType: fileType,
|
||||
originalEvent: _this.originalEvent
|
||||
});
|
||||
} else {
|
||||
return _this._target.trigger('pasteText', {
|
||||
text: string,
|
||||
originalEvent: _this.originalEvent
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
if (item.type === 'text/rtf') {
|
||||
item.getAsString(function(string) {
|
||||
return _this._target.trigger('pasteTextRich', {
|
||||
text: string,
|
||||
originalEvent: _this.originalEvent
|
||||
});
|
||||
});
|
||||
}
|
||||
if (item.type === 'text/html') {
|
||||
item.getAsString(function(string) {
|
||||
return _this._target.trigger('pasteTextHtml', {
|
||||
text: string,
|
||||
originalEvent: _this.originalEvent
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (-1 !== Array.prototype.indexOf.call(clipboardData.types, 'text/plain')) {
|
||||
text = clipboardData.getData('Text');
|
||||
setTimeout(function() {
|
||||
return _this._target.trigger('pasteText', {
|
||||
text: text,
|
||||
originalEvent: _this.originalEvent
|
||||
});
|
||||
}, 1);
|
||||
}
|
||||
_this._checkImagesInContainer(function(src) {
|
||||
return _this._handleImage(src, _this.originalEvent);
|
||||
});
|
||||
}
|
||||
}
|
||||
if (clipboardData = window.clipboardData) {
|
||||
if ((ref3 = (text = clipboardData.getData('Text'))) != null ? ref3.length : void 0) {
|
||||
setTimeout(function() {
|
||||
_this._target.trigger('pasteText', {
|
||||
text: text,
|
||||
originalEvent: _this.originalEvent
|
||||
});
|
||||
return _this._target.trigger('_pasteCheckContainerDone');
|
||||
}, 1);
|
||||
} else {
|
||||
ref4 = clipboardData.files;
|
||||
for (l = 0, len2 = ref4.length; l < len2; l++) {
|
||||
file = ref4[l];
|
||||
_this._handleImage(URL.createObjectURL(file), _this.originalEvent);
|
||||
}
|
||||
_this._checkImagesInContainer(function(src) {});
|
||||
}
|
||||
}
|
||||
return null;
|
||||
};
|
||||
})(this));
|
||||
}
|
||||
|
||||
Paste.prototype._handleImage = function(src, e, name) {
|
||||
var loader;
|
||||
if (src.match(/^webkit\-fake\-url\:\/\//)) {
|
||||
return this._target.trigger('pasteImageError', {
|
||||
message: "You are trying to paste an image in Safari, however we are unable to retieve its data."
|
||||
});
|
||||
}
|
||||
this._target.trigger('pasteImageStart');
|
||||
loader = new Image();
|
||||
loader.crossOrigin = "anonymous";
|
||||
loader.onload = (function(_this) {
|
||||
return function() {
|
||||
var blob, canvas, ctx, dataURL;
|
||||
canvas = document.createElement('canvas');
|
||||
canvas.width = loader.width;
|
||||
canvas.height = loader.height;
|
||||
ctx = canvas.getContext('2d');
|
||||
ctx.drawImage(loader, 0, 0, canvas.width, canvas.height);
|
||||
dataURL = null;
|
||||
try {
|
||||
dataURL = canvas.toDataURL('image/png');
|
||||
blob = dataURLtoBlob(dataURL);
|
||||
} catch (error) {}
|
||||
if (dataURL) {
|
||||
_this._target.trigger('pasteImage', {
|
||||
blob: blob,
|
||||
dataURL: dataURL,
|
||||
width: loader.width,
|
||||
height: loader.height,
|
||||
originalEvent: e,
|
||||
name: name
|
||||
});
|
||||
}
|
||||
return _this._target.trigger('pasteImageEnd');
|
||||
};
|
||||
})(this);
|
||||
loader.onerror = (function(_this) {
|
||||
return function() {
|
||||
_this._target.trigger('pasteImageError', {
|
||||
message: "Failed to get image from: " + src,
|
||||
url: src
|
||||
});
|
||||
return _this._target.trigger('pasteImageEnd');
|
||||
};
|
||||
})(this);
|
||||
return loader.src = src;
|
||||
};
|
||||
|
||||
Paste.prototype._checkImagesInContainer = function(cb) {
|
||||
var img, j, len, ref, timespan;
|
||||
timespan = Math.floor(1000 * Math.random());
|
||||
ref = this._container.find('img');
|
||||
for (j = 0, len = ref.length; j < len; j++) {
|
||||
img = ref[j];
|
||||
img["_paste_marked_" + timespan] = true;
|
||||
}
|
||||
return setTimeout((function(_this) {
|
||||
return function() {
|
||||
var k, len1, ref1;
|
||||
ref1 = _this._container.find('img');
|
||||
for (k = 0, len1 = ref1.length; k < len1; k++) {
|
||||
img = ref1[k];
|
||||
if (!img["_paste_marked_" + timespan]) {
|
||||
cb(img.src);
|
||||
$(img).remove();
|
||||
}
|
||||
}
|
||||
return _this._target.trigger('_pasteCheckContainerDone');
|
||||
};
|
||||
})(this), 1);
|
||||
};
|
||||
|
||||
return Paste;
|
||||
|
||||
})();
|
||||
|
||||
}).call(this);
|
File diff suppressed because it is too large
Load Diff
@ -10,7 +10,8 @@
|
||||
}
|
||||
})
|
||||
},
|
||||
uploadComplete : function (file) {}
|
||||
uploadComplete : function (attachment) {},
|
||||
savePost : function (cb) {},
|
||||
};
|
||||
})(window);
|
||||
|
||||
|
@ -42,12 +42,12 @@ $(document).ready(function() {
|
||||
Typecho.editorResize('text', '<?php $security->index('/action/ajax?do=editorResize'); ?>');
|
||||
|
||||
// tag autocomplete 提示
|
||||
var tags = $('#tags'), tagsPre = [];
|
||||
const tags = $('#tags'), tagsPre = [];
|
||||
|
||||
if (tags.length > 0) {
|
||||
var items = tags.val().split(','), result = [];
|
||||
for (var i = 0; i < items.length; i ++) {
|
||||
var tag = items[i];
|
||||
const items = tags.val().split(',');
|
||||
for (let i = 0; i < items.length; i ++) {
|
||||
const tag = items[i];
|
||||
|
||||
if (!tag) {
|
||||
continue;
|
||||
@ -87,7 +87,7 @@ $(document).ready(function() {
|
||||
result = [];
|
||||
}
|
||||
|
||||
if (!result[0] || result[0]['id'] != query) {
|
||||
if (!result[0] || result[0]['id'] !== query) {
|
||||
result.unshift({
|
||||
id : val,
|
||||
tags : val
|
||||
@ -100,17 +100,17 @@ $(document).ready(function() {
|
||||
|
||||
// tag autocomplete 提示宽度设置
|
||||
$('#token-input-tags').focus(function() {
|
||||
var t = $('.token-input-dropdown'),
|
||||
const t = $('.token-input-dropdown'),
|
||||
offset = t.outerWidth() - t.width();
|
||||
t.width($('.token-input-list').outerWidth() - offset);
|
||||
});
|
||||
}
|
||||
|
||||
// 缩略名自适应宽度
|
||||
var slug = $('#slug');
|
||||
const slug = $('#slug');
|
||||
|
||||
if (slug.length > 0) {
|
||||
var wrap = $('<div />').css({
|
||||
const wrap = $('<div />').css({
|
||||
'position' : 'relative',
|
||||
'display' : 'inline-block'
|
||||
}),
|
||||
@ -126,10 +126,10 @@ $(document).ready(function() {
|
||||
'minWidth' : '5px',
|
||||
'position' : 'absolute',
|
||||
'width' : '100%'
|
||||
})), originalWidth = slug.width();
|
||||
}));
|
||||
|
||||
function justifySlugWidth() {
|
||||
var val = slug.val();
|
||||
const val = slug.val();
|
||||
justifySlug.text(val.length > 0 ? val : ' ');
|
||||
}
|
||||
|
||||
@ -137,91 +137,106 @@ $(document).ready(function() {
|
||||
justifySlugWidth();
|
||||
}
|
||||
|
||||
// 原始的插入图片和文件
|
||||
Typecho.insertFileToEditor = function (file, url, isImage) {
|
||||
var textarea = $('#text'), sel = textarea.getSelection(),
|
||||
html = isImage ? '<img src="' + url + '" alt="' + file + '" />'
|
||||
: '<a href="' + url + '">' + file + '</a>',
|
||||
offset = (sel ? sel.start : 0) + html.length;
|
||||
|
||||
textarea.replaceSelection(html);
|
||||
textarea.setSelection(offset, offset);
|
||||
};
|
||||
|
||||
var submitted = false, form = $('form[name=write_post],form[name=write_page]').submit(function () {
|
||||
submitted = true;
|
||||
}), formAction = form.attr('action'),
|
||||
// 处理保存文章的逻辑
|
||||
const form = $('form[name=write_post],form[name=write_page]'),
|
||||
idInput = $('input[name=cid]'),
|
||||
cid = idInput.val(),
|
||||
draft = $('input[name=draft]'),
|
||||
draftId = draft.length > 0 ? draft.val() : 0,
|
||||
btnSave = $('#btn-save').removeAttr('name').removeAttr('value'),
|
||||
btnSubmit = $('#btn-submit').removeAttr('name').removeAttr('value'),
|
||||
btnPreview = $('#btn-preview'),
|
||||
doAction = $('<input type="hidden" name="do" value="publish" />').appendTo(form),
|
||||
locked = false,
|
||||
autoSave = $('<span id="auto-save-message" class="left"></span>').prependTo('.submit');
|
||||
|
||||
let cid = idInput.val(),
|
||||
draftId = draft.length > 0 ? draft.val() : 0,
|
||||
changed = false,
|
||||
autoSave = $('<span id="auto-save-message" class="left"></span>').prependTo('.submit'),
|
||||
written = false,
|
||||
lastSaveTime = null;
|
||||
|
||||
$(':input', form).bind('input change', function (e) {
|
||||
var tagName = $(this).prop('tagName');
|
||||
|
||||
if (tagName.match(/(input|textarea)/i) && e.type == 'change') {
|
||||
return;
|
||||
}
|
||||
|
||||
changed = true;
|
||||
form.on('write', function () {
|
||||
written = true;
|
||||
form.trigger('datachange');
|
||||
});
|
||||
|
||||
form.bind('field', function () {
|
||||
changed = true;
|
||||
form.on('change', function () {
|
||||
if (written) {
|
||||
form.trigger('datachange');
|
||||
}
|
||||
});
|
||||
|
||||
$('button[name=do]').click(function () {
|
||||
$('input[name=do]').val($(this).val());
|
||||
});
|
||||
|
||||
// 自动检测离开页
|
||||
$(window).bind('beforeunload', function () {
|
||||
if (changed && !form.hasClass('submitting')) {
|
||||
return '<?php _e('内容已经改变尚未保存, 您确认要离开此页面吗?'); ?>';
|
||||
}
|
||||
});
|
||||
|
||||
// 发送保存请求
|
||||
function saveData(cb) {
|
||||
function callback(o) {
|
||||
Typecho.savePost = function(cb) {
|
||||
if (!changed) {
|
||||
cb && cb();
|
||||
return;
|
||||
}
|
||||
|
||||
const callback = function (o) {
|
||||
lastSaveTime = o.time;
|
||||
cid = o.cid;
|
||||
draftId = o.draftId;
|
||||
idInput.val(cid);
|
||||
autoSave.text('<?php _e('已保存'); ?>' + ' (' + o.time + ')').effect('highlight', 1000);
|
||||
locked = false;
|
||||
|
||||
btnSave.removeAttr('disabled');
|
||||
btnPreview.removeAttr('disabled');
|
||||
|
||||
if (!!cb) {
|
||||
cb(o)
|
||||
}
|
||||
}
|
||||
cb && cb();
|
||||
};
|
||||
|
||||
changed = false;
|
||||
btnSave.attr('disabled', 'disabled');
|
||||
btnPreview.attr('disabled', 'disabled');
|
||||
autoSave.text('<?php _e('正在保存'); ?>');
|
||||
|
||||
if (typeof FormData !== 'undefined') {
|
||||
var data = new FormData(form.get(0));
|
||||
data.append('do', 'save');
|
||||
const data = new FormData(form.get(0));
|
||||
data.append('do', 'save');
|
||||
form.triggerHandler('submit');
|
||||
|
||||
$.ajax({
|
||||
url: formAction,
|
||||
processData: false,
|
||||
contentType: false,
|
||||
type: 'POST',
|
||||
data: data,
|
||||
success: callback
|
||||
});
|
||||
} else {
|
||||
var data = form.serialize() + '&do=save';
|
||||
$.post(formAction, data, callback, 'json');
|
||||
$.ajax({
|
||||
url: form.attr('action'),
|
||||
processData: false,
|
||||
contentType: false,
|
||||
type: 'POST',
|
||||
data: data,
|
||||
success: callback,
|
||||
error: function () {
|
||||
autoSave.text('<?php _e('保存失败, 请重试'); ?>');
|
||||
},
|
||||
complete: function () {
|
||||
form.trigger('submitted');
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
<?php if ($options->autoSave): ?>
|
||||
// 自动保存
|
||||
let saveTimer = null;
|
||||
|
||||
form.on('datachange', function () {
|
||||
changed = true;
|
||||
autoSave.text('<?php _e('尚未保存'); ?>' + (lastSaveTime ? ' (<?php _e('上次保存时间'); ?>: ' + lastSaveTime + ')' : ''));
|
||||
|
||||
if (saveTimer) {
|
||||
clearTimeout(saveTimer);
|
||||
}
|
||||
}
|
||||
|
||||
saveTimer = setTimeout(function () {
|
||||
Typecho.savePost();
|
||||
}, 3000);
|
||||
});
|
||||
<?php else: ?>
|
||||
form.on('datachange', function () {
|
||||
changed = true;
|
||||
});
|
||||
<?php endif; ?>
|
||||
|
||||
// 计算夏令时偏移
|
||||
var dstOffset = (function () {
|
||||
var d = new Date(),
|
||||
const dstOffset = (function () {
|
||||
const d = new Date(),
|
||||
jan = new Date(d.getFullYear(), 0, 1),
|
||||
jul = new Date(d.getFullYear(), 6, 1),
|
||||
stdOffset = Math.max(jan.getTimezoneOffset(), jul.getTimezoneOffset());
|
||||
@ -236,50 +251,14 @@ $(document).ready(function() {
|
||||
// 时区
|
||||
$('<input name="timezone" type="hidden" />').appendTo(form).val(- (new Date).getTimezoneOffset() * 60);
|
||||
|
||||
// 自动保存
|
||||
<?php if ($options->autoSave): ?>
|
||||
var autoSaveOnce = !!cid;
|
||||
|
||||
function autoSaveListener () {
|
||||
setInterval(function () {
|
||||
if (changed && !locked) {
|
||||
locked = true;
|
||||
saveData();
|
||||
}
|
||||
}, 10000);
|
||||
}
|
||||
|
||||
if (autoSaveOnce) {
|
||||
autoSaveListener();
|
||||
}
|
||||
|
||||
$('#text').bind('input propertychange', function () {
|
||||
if (!locked) {
|
||||
autoSave.text('<?php _e('尚未保存'); ?>' + (lastSaveTime ? ' (<?php _e('上次保存时间'); ?>: ' + lastSaveTime + ')' : ''));
|
||||
}
|
||||
|
||||
if (!autoSaveOnce) {
|
||||
autoSaveOnce = true;
|
||||
autoSaveListener();
|
||||
}
|
||||
});
|
||||
<?php endif; ?>
|
||||
|
||||
// 自动检测离开页
|
||||
$(window).bind('beforeunload', function () {
|
||||
if (changed && !submitted) {
|
||||
return '<?php _e('内容已经改变尚未保存, 您确认要离开此页面吗?'); ?>';
|
||||
}
|
||||
});
|
||||
|
||||
// 预览功能
|
||||
var isFullScreen = false;
|
||||
let isFullScreen = false;
|
||||
|
||||
function previewData(cid) {
|
||||
isFullScreen = $(document.body).hasClass('fullscreen');
|
||||
$(document.body).addClass('fullscreen preview');
|
||||
|
||||
var frame = $('<iframe frameborder="0" class="preview-frame preview-loading"></iframe>')
|
||||
const frame = $('<iframe frameborder="0" class="preview-frame preview-loading"></iframe>')
|
||||
.attr('src', './preview.php?cid=' + cid)
|
||||
.attr('sandbox', 'allow-same-origin allow-scripts')
|
||||
.appendTo(document.body);
|
||||
@ -292,36 +271,28 @@ $(document).ready(function() {
|
||||
}
|
||||
|
||||
function cancelPreview() {
|
||||
if (submitted) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!isFullScreen) {
|
||||
$(document.body).removeClass('fullscreen');
|
||||
}
|
||||
|
||||
$(document.body).removeClass('preview');
|
||||
$('.preview-frame').remove();
|
||||
};
|
||||
}
|
||||
|
||||
$('#btn-cancel-preview').click(cancelPreview);
|
||||
|
||||
$(window).bind('message', function (e) {
|
||||
if (e.originalEvent.data == 'cancelPreview') {
|
||||
if (e.originalEvent.data === 'cancelPreview') {
|
||||
cancelPreview();
|
||||
}
|
||||
});
|
||||
|
||||
btnPreview.click(function () {
|
||||
if (changed) {
|
||||
locked = true;
|
||||
|
||||
if (confirm('<?php _e('修改后的内容需要保存后才能预览, 是否保存?'); ?>')) {
|
||||
saveData(function (o) {
|
||||
previewData(o.draftId);
|
||||
Typecho.savePost(function () {
|
||||
previewData(draftId);
|
||||
});
|
||||
} else {
|
||||
locked = false;
|
||||
}
|
||||
} else if (!!draftId) {
|
||||
previewData(draftId);
|
||||
@ -330,28 +301,13 @@ $(document).ready(function() {
|
||||
}
|
||||
});
|
||||
|
||||
btnSave.click(function () {
|
||||
doAction.attr('value', 'save');
|
||||
});
|
||||
|
||||
btnSubmit.click(function () {
|
||||
doAction.attr('value', 'publish');
|
||||
});
|
||||
|
||||
// 控制选项和附件的切换
|
||||
var fileUploadInit = false;
|
||||
$('#edit-secondary .typecho-option-tabs li').click(function() {
|
||||
$('#edit-secondary .typecho-option-tabs li').removeClass('active');
|
||||
$(this).addClass('active');
|
||||
$(this).parents('#edit-secondary').find('.tab-content').addClass('hidden');
|
||||
|
||||
var selected_tab = $(this).find('a').attr('href'),
|
||||
selected_el = $(selected_tab).removeClass('hidden');
|
||||
$('#edit-secondary .typecho-option-tabs li.active').removeClass('active');
|
||||
$('#edit-secondary .tab-content').addClass('hidden');
|
||||
|
||||
if (!fileUploadInit) {
|
||||
selected_el.trigger('init');
|
||||
fileUploadInit = true;
|
||||
}
|
||||
const activeTab = $(this).addClass('active').find('a').attr('href');
|
||||
$(activeTab).removeClass('hidden');
|
||||
|
||||
return false;
|
||||
});
|
||||
@ -364,9 +320,9 @@ $(document).ready(function() {
|
||||
|
||||
// 自动隐藏密码框
|
||||
$('#visibility').change(function () {
|
||||
var val = $(this).val(), password = $('#post-password');
|
||||
const val = $(this).val(), password = $('#post-password');
|
||||
|
||||
if ('password' == val) {
|
||||
if ('password' === val) {
|
||||
password.removeClass('hidden');
|
||||
} else {
|
||||
password.addClass('hidden');
|
||||
|
@ -2,7 +2,7 @@
|
||||
include 'common.php';
|
||||
include 'header.php';
|
||||
include 'menu.php';
|
||||
\Widget\Contents\Page\Edit::alloc()->to($page);
|
||||
\Widget\Contents\Page\Edit::alloc()->prepare()->to($page);
|
||||
?>
|
||||
<div class="main">
|
||||
<div class="body container">
|
||||
@ -53,6 +53,7 @@ include 'menu.php';
|
||||
class="i-caret-left"></i> <?php _e('取消预览'); ?></button>
|
||||
</span>
|
||||
<span class="right">
|
||||
<input type="hidden" name="do" value="publish" />
|
||||
<input type="hidden" name="cid" value="<?php $page->cid(); ?>"/>
|
||||
<button type="button" id="btn-preview" class="btn"><i
|
||||
class="i-exlink"></i> <?php _e('预览页面'); ?></button>
|
||||
|
@ -2,7 +2,7 @@
|
||||
include 'common.php';
|
||||
include 'header.php';
|
||||
include 'menu.php';
|
||||
\Widget\Contents\Post\Edit::alloc()->to($post);
|
||||
\Widget\Contents\Post\Edit::alloc()->prepare()->to($post);
|
||||
?>
|
||||
<div class="main">
|
||||
<div class="body container">
|
||||
@ -58,6 +58,7 @@ include 'menu.php';
|
||||
class="i-caret-left"></i> <?php _e('取消预览'); ?></button>
|
||||
</span>
|
||||
<span class="right">
|
||||
<input type="hidden" name="do" value="publish" />
|
||||
<input type="hidden" name="cid" value="<?php $post->cid(); ?>"/>
|
||||
<button type="button" id="btn-preview" class="btn"><i
|
||||
class="i-exlink"></i> <?php _e('预览文章'); ?></button>
|
||||
@ -114,7 +115,7 @@ include 'menu.php';
|
||||
|
||||
<section class="typecho-post-option">
|
||||
<label for="token-input-tags" class="typecho-label"><?php _e('标签'); ?></label>
|
||||
<p><input id="tags" name="tags" type="text" value="<?php $post->tags(',', false); ?>"
|
||||
<p><input id="tags" name="tags" type="text" value="<?php $post->have() ? $post->tags(',', false) : ''; ?>"
|
||||
class="w-100 text"/></p>
|
||||
</section>
|
||||
|
||||
|
7288
tools/package-lock.json
generated
7288
tools/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -19,7 +19,7 @@
|
||||
"dependencies": {
|
||||
"@picocss/pico": "^1.5.0",
|
||||
"chalk": "^4.0.0",
|
||||
"node-sass": "^6.0.1",
|
||||
"node-sass": "^9.0.0",
|
||||
"sprite-magic-importer": "^1.6.2",
|
||||
"uglify-js": "^3.11.6"
|
||||
}
|
||||
|
@ -183,7 +183,7 @@ class Request
|
||||
$exists = is_array($value) || (is_string($value) && strlen($value) > 0);
|
||||
return $value;
|
||||
} else {
|
||||
$exists = false;
|
||||
$exists = true;
|
||||
return $default;
|
||||
}
|
||||
} else {
|
||||
|
@ -41,9 +41,10 @@ class EmptyClass
|
||||
* @access public
|
||||
* @param string $name 方法名
|
||||
* @param array $args 参数列表
|
||||
* @return void
|
||||
* @return $this
|
||||
*/
|
||||
public function __call(string $name, array $args)
|
||||
{
|
||||
return $this;
|
||||
}
|
||||
}
|
||||
|
@ -7,7 +7,8 @@ use Typecho\Widget\Exception;
|
||||
use Typecho\Widget\Helper\Form;
|
||||
use Typecho\Widget\Helper\Layout;
|
||||
use Widget\ActionInterface;
|
||||
use Widget\Contents\Post\Edit as PostEdit;
|
||||
use Widget\Base\Contents;
|
||||
use Widget\Contents\PrepareEditTrait;
|
||||
use Widget\Notice;
|
||||
use Widget\Upload;
|
||||
|
||||
@ -24,8 +25,10 @@ if (!defined('__TYPECHO_ROOT_DIR__')) {
|
||||
* @copyright Copyright (c) 2008 Typecho team (http://www.typecho.org)
|
||||
* @license GNU General Public License 2.0
|
||||
*/
|
||||
class Edit extends PostEdit implements ActionInterface
|
||||
class Edit extends Contents implements ActionInterface
|
||||
{
|
||||
use PrepareEditTrait;
|
||||
|
||||
/**
|
||||
* 执行函数
|
||||
*
|
||||
@ -35,20 +38,6 @@ class Edit extends PostEdit implements ActionInterface
|
||||
{
|
||||
/** 必须为贡献者以上权限 */
|
||||
$this->user->pass('contributor');
|
||||
|
||||
/** 获取文章内容 */
|
||||
if (!empty($this->request->cid)) {
|
||||
$this->db->fetchRow($this->select()
|
||||
->where('table.contents.type = ?', 'attachment')
|
||||
->where('table.contents.cid = ?', $this->request->filter('int')->get('cid'))
|
||||
->limit(1), [$this, 'push']);
|
||||
|
||||
if (!$this->have()) {
|
||||
throw new Exception(_t('文件不存在'), 404);
|
||||
} elseif (!$this->allow('edit')) {
|
||||
throw new Exception(_t('没有编辑权限'), 403);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@ -229,32 +218,7 @@ class Edit extends PostEdit implements ActionInterface
|
||||
$posts = $this->request->filter('int')->getArray('cid');
|
||||
$deleteCount = 0;
|
||||
|
||||
foreach ($posts as $post) {
|
||||
// 删除插件接口
|
||||
self::pluginHandle()->call('delete', $post, $this);
|
||||
|
||||
$condition = $this->db->sql()->where('cid = ?', $post);
|
||||
$row = $this->db->fetchRow($this->select()
|
||||
->where('table.contents.type = ?', 'attachment')
|
||||
->where('table.contents.cid = ?', $post)
|
||||
->limit(1), [$this, 'push']);
|
||||
|
||||
if ($this->isWriteable(clone $condition) && $this->delete($condition)) {
|
||||
/** 删除文件 */
|
||||
Upload::deleteHandle($row);
|
||||
|
||||
/** 删除评论 */
|
||||
$this->db->query($this->db->delete('table.comments')
|
||||
->where('cid = ?', $post));
|
||||
|
||||
// 完成删除插件接口
|
||||
self::pluginHandle()->call('finishDelete', $post, $this);
|
||||
|
||||
$deleteCount++;
|
||||
}
|
||||
|
||||
unset($condition);
|
||||
}
|
||||
$this->deleteByIds($posts, $deleteCount);
|
||||
|
||||
if ($this->request->isAjax()) {
|
||||
$this->response->throwJson($deleteCount > 0 ? ['code' => 200, 'message' => _t('文件已经被删除')]
|
||||
@ -291,32 +255,7 @@ class Edit extends PostEdit implements ActionInterface
|
||||
->page($page, 100)), 'cid');
|
||||
$page++;
|
||||
|
||||
foreach ($posts as $post) {
|
||||
// 删除插件接口
|
||||
self::pluginHandle()->call('delete', $post, $this);
|
||||
|
||||
$condition = $this->db->sql()->where('cid = ?', $post);
|
||||
$row = $this->db->fetchRow($this->select()
|
||||
->where('table.contents.type = ?', 'attachment')
|
||||
->where('table.contents.cid = ?', $post)
|
||||
->limit(1), [$this, 'push']);
|
||||
|
||||
if ($this->isWriteable(clone $condition) && $this->delete($condition)) {
|
||||
/** 删除文件 */
|
||||
Upload::deleteHandle($row);
|
||||
|
||||
/** 删除评论 */
|
||||
$this->db->query($this->db->delete('table.comments')
|
||||
->where('cid = ?', $post));
|
||||
|
||||
// 完成删除插件接口
|
||||
self::pluginHandle()->call('finishDelete', $post, $this);
|
||||
|
||||
$deleteCount++;
|
||||
}
|
||||
|
||||
unset($condition);
|
||||
}
|
||||
$this->deleteByIds($posts, $deleteCount);
|
||||
} while (count($posts) == 100);
|
||||
|
||||
/** 设置提示信息 */
|
||||
@ -329,6 +268,16 @@ class Edit extends PostEdit implements ActionInterface
|
||||
$this->response->redirect(Common::url('manage-medias.php', $this->options->adminUrl));
|
||||
}
|
||||
|
||||
/**
|
||||
* @return $this
|
||||
* @throws Exception
|
||||
* @throws \Typecho\Db\Exception
|
||||
*/
|
||||
public function prepare(): self
|
||||
{
|
||||
return $this->prepareEdit('attachment', false, _t('文件不存在'));
|
||||
}
|
||||
|
||||
/**
|
||||
* 绑定动作
|
||||
*
|
||||
@ -339,8 +288,44 @@ class Edit extends PostEdit implements ActionInterface
|
||||
{
|
||||
$this->security->protect();
|
||||
$this->on($this->request->is('do=delete'))->deleteAttachment();
|
||||
$this->on($this->have() && $this->request->is('do=update'))->updateAttachment();
|
||||
$this->on($this->request->is('do=update'))
|
||||
->prepare()->updateAttachment();
|
||||
$this->on($this->request->is('do=clear'))->clearAttachment();
|
||||
$this->response->redirect($this->options->adminUrl);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $posts
|
||||
* @param int $deleteCount
|
||||
* @return void
|
||||
*/
|
||||
protected function deleteByIds(array $posts, int &$deleteCount): void
|
||||
{
|
||||
foreach ($posts as $post) {
|
||||
// 删除插件接口
|
||||
self::pluginHandle()->call('delete', $post, $this);
|
||||
|
||||
$condition = $this->db->sql()->where('cid = ?', $post);
|
||||
$row = $this->db->fetchRow($this->select()
|
||||
->where('table.contents.type = ?', 'attachment')
|
||||
->where('table.contents.cid = ?', $post)
|
||||
->limit(1), [$this, 'push']);
|
||||
|
||||
if ($this->isWriteable(clone $condition) && $this->delete($condition)) {
|
||||
/** 删除文件 */
|
||||
Upload::deleteHandle($row);
|
||||
|
||||
/** 删除评论 */
|
||||
$this->db->query($this->db->delete('table.comments')
|
||||
->where('cid = ?', $post));
|
||||
|
||||
// 完成删除插件接口
|
||||
self::pluginHandle()->call('finishDelete', $post, $this);
|
||||
|
||||
$deleteCount++;
|
||||
}
|
||||
|
||||
unset($condition);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
566
var/Widget/Contents/EditTrait.php
Normal file
566
var/Widget/Contents/EditTrait.php
Normal file
@ -0,0 +1,566 @@
|
||||
<?php
|
||||
|
||||
namespace Widget\Contents;
|
||||
|
||||
use Typecho\Common;
|
||||
use Typecho\Config;
|
||||
use Typecho\Db\Exception as DbException;
|
||||
use Typecho\Validate;
|
||||
use Typecho\Widget\Exception;
|
||||
use Typecho\Widget\Helper\Form\Element;
|
||||
use Typecho\Widget\Helper\Layout;
|
||||
use Widget\Base\Contents;
|
||||
use Widget\Base\Metas;
|
||||
|
||||
trait EditTrait
|
||||
{
|
||||
/**
|
||||
* getFieldItems
|
||||
*
|
||||
* @throws DbException
|
||||
*/
|
||||
public function getFieldItems(): array
|
||||
{
|
||||
$fields = [];
|
||||
|
||||
if ($this->have()) {
|
||||
$defaultFields = $this->getDefaultFieldItems();
|
||||
$rows = $this->db->fetchAll($this->db->select()->from('table.fields')
|
||||
->where('cid = ?', isset($this->draft) ? $this->draft['cid'] : $this->cid));
|
||||
|
||||
foreach ($rows as $row) {
|
||||
$isFieldReadOnly = Contents::pluginHandle()
|
||||
->trigger($plugged)->call('isFieldReadOnly', $row['name']);
|
||||
|
||||
if ($plugged && $isFieldReadOnly) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$isFieldReadOnly = static::pluginHandle()
|
||||
->trigger($plugged)->call('isFieldReadOnly', $row['name']);
|
||||
|
||||
if ($plugged && $isFieldReadOnly) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!isset($defaultFields[$row['name']])) {
|
||||
$fields[] = $row;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $fields;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
public function getDefaultFieldItems(): array
|
||||
{
|
||||
$defaultFields = [];
|
||||
$configFile = $this->options->themeFile($this->options->theme, 'functions.php');
|
||||
$layout = new Layout();
|
||||
$fields = new Config();
|
||||
|
||||
if ($this->have()) {
|
||||
$fields = $this->fields;
|
||||
}
|
||||
|
||||
Contents::pluginHandle()->call('getDefaultFieldItems', $layout);
|
||||
static::pluginHandle()->call('getDefaultFieldItems', $layout);
|
||||
|
||||
if (file_exists($configFile)) {
|
||||
require_once $configFile;
|
||||
|
||||
if (function_exists('themeFields')) {
|
||||
themeFields($layout);
|
||||
}
|
||||
|
||||
$func = $this->getThemeFieldsHook();
|
||||
if (function_exists($func)) {
|
||||
call_user_func($func, $layout);
|
||||
}
|
||||
}
|
||||
|
||||
$items = $layout->getItems();
|
||||
foreach ($items as $item) {
|
||||
if ($item instanceof Element) {
|
||||
$name = $item->input->getAttribute('name');
|
||||
|
||||
$isFieldReadOnly = Contents::pluginHandle()
|
||||
->trigger($plugged)->call('isFieldReadOnly', $name);
|
||||
if ($plugged && $isFieldReadOnly) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (preg_match("/^fields\[(.+)\]$/", $name, $matches)) {
|
||||
$name = $matches[1];
|
||||
} else {
|
||||
$inputName = 'fields[' . $name . ']';
|
||||
if (preg_match("/^(.+)\[\]$/", $name, $matches)) {
|
||||
$name = $matches[1];
|
||||
$inputName = 'fields[' . $name . '][]';
|
||||
}
|
||||
|
||||
foreach ($item->inputs as $input) {
|
||||
$input->setAttribute('name', $inputName);
|
||||
}
|
||||
}
|
||||
|
||||
if (isset($fields->{$name})) {
|
||||
$item->value($fields->{$name});
|
||||
}
|
||||
|
||||
$elements = $item->container->getItems();
|
||||
array_shift($elements);
|
||||
$div = new Layout('div');
|
||||
|
||||
foreach ($elements as $el) {
|
||||
$div->addItem($el);
|
||||
}
|
||||
|
||||
$defaultFields[$name] = [$item->label, $div];
|
||||
}
|
||||
}
|
||||
|
||||
return $defaultFields;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取自定义字段的hook名称
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
abstract protected function getThemeFieldsHook(): string;
|
||||
|
||||
/**
|
||||
* getFields
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected function getFields(): array
|
||||
{
|
||||
$fields = [];
|
||||
$fieldNames = $this->request->getArray('fieldNames');
|
||||
|
||||
if (!empty($fieldNames)) {
|
||||
$data = [
|
||||
'fieldNames' => $this->request->getArray('fieldNames'),
|
||||
'fieldTypes' => $this->request->getArray('fieldTypes'),
|
||||
'fieldValues' => $this->request->getArray('fieldValues')
|
||||
];
|
||||
foreach ($data['fieldNames'] as $key => $val) {
|
||||
$val = trim($val);
|
||||
|
||||
if (0 == strlen($val)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$fields[$val] = [$data['fieldTypes'][$key], $data['fieldValues'][$key]];
|
||||
}
|
||||
}
|
||||
|
||||
$customFields = $this->request->getArray('fields');
|
||||
foreach ($customFields as $key => $val) {
|
||||
$fields[$key] = [is_array($val) ? 'json' : 'str', $val];
|
||||
}
|
||||
|
||||
return $fields;
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除草稿
|
||||
*
|
||||
* @param integer $cid 草稿id
|
||||
* @throws DbException
|
||||
*/
|
||||
protected function deleteDraft(int $cid, bool $hasMetas = true)
|
||||
{
|
||||
$this->delete($this->db->sql()->where('cid = ?', $cid));
|
||||
|
||||
if ($hasMetas) {
|
||||
/** 删除草稿分类 */
|
||||
$this->setCategories($cid, [], false, false);
|
||||
|
||||
/** 删除标签 */
|
||||
$this->setTags($cid, null, false, false);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据提交值获取created字段值
|
||||
*
|
||||
* @return integer
|
||||
*/
|
||||
protected function getCreated(): int
|
||||
{
|
||||
$created = $this->options->time;
|
||||
if ($this->request->is('created')) {
|
||||
$created = $this->request->get('created');
|
||||
} elseif ($this->request->is('date')) {
|
||||
$dstOffset = $this->request->get('dst', 0);
|
||||
$timezoneSymbol = $this->options->timezone >= 0 ? '+' : '-';
|
||||
$timezoneOffset = abs($this->options->timezone);
|
||||
$timezone = $timezoneSymbol . str_pad($timezoneOffset / 3600, 2, '0', STR_PAD_LEFT) . ':00';
|
||||
[$date, $time] = explode(' ', $this->request->get('date'));
|
||||
|
||||
$created = strtotime("{$date}T{$time}{$timezone}") - $dstOffset;
|
||||
} elseif ($this->request->is('year&month&day')) {
|
||||
$second = $this->request->filter('int')->get('sec', date('s'));
|
||||
$min = $this->request->filter('int')->get('min', date('i'));
|
||||
$hour = $this->request->filter('int')->get('hour', date('H'));
|
||||
|
||||
$year = $this->request->filter('int')->get('year');
|
||||
$month = $this->request->filter('int')->get('month');
|
||||
$day = $this->request->filter('int')->get('day');
|
||||
|
||||
$created = mktime($hour, $min, $second, $month, $day, $year)
|
||||
- $this->options->timezone + $this->options->serverTimezone;
|
||||
} elseif ($this->have() && $this->created > 0) {
|
||||
//如果是修改文章
|
||||
$created = $this->created;
|
||||
} elseif ($this->request->is('do=save')) {
|
||||
// 如果是草稿而且没有任何输入则保持原状
|
||||
$created = 0;
|
||||
}
|
||||
|
||||
return $created;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置分类
|
||||
*
|
||||
* @param integer $cid 内容id
|
||||
* @param array $categories 分类id的集合数组
|
||||
* @param boolean $beforeCount 是否参与计数
|
||||
* @param boolean $afterCount 是否参与计数
|
||||
* @throws DbException
|
||||
*/
|
||||
protected function setCategories(int $cid, array $categories, bool $beforeCount = true, bool $afterCount = true)
|
||||
{
|
||||
$categories = array_unique(array_map('trim', $categories));
|
||||
|
||||
/** 取出已有category */
|
||||
$existCategories = array_column(
|
||||
$this->db->fetchAll(
|
||||
$this->db->select('table.metas.mid')
|
||||
->from('table.metas')
|
||||
->join('table.relationships', 'table.relationships.mid = table.metas.mid')
|
||||
->where('table.relationships.cid = ?', $cid)
|
||||
->where('table.metas.type = ?', 'category')
|
||||
),
|
||||
'mid'
|
||||
);
|
||||
|
||||
/** 删除已有category */
|
||||
if ($existCategories) {
|
||||
foreach ($existCategories as $category) {
|
||||
$this->db->query($this->db->delete('table.relationships')
|
||||
->where('cid = ?', $cid)
|
||||
->where('mid = ?', $category));
|
||||
|
||||
if ($beforeCount) {
|
||||
$this->db->query($this->db->update('table.metas')
|
||||
->expression('count', 'count - 1')
|
||||
->where('mid = ?', $category));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** 插入category */
|
||||
if ($categories) {
|
||||
foreach ($categories as $category) {
|
||||
/** 如果分类不存在 */
|
||||
if (
|
||||
!$this->db->fetchRow(
|
||||
$this->db->select('mid')
|
||||
->from('table.metas')
|
||||
->where('mid = ?', $category)
|
||||
->limit(1)
|
||||
)
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$this->db->query($this->db->insert('table.relationships')
|
||||
->rows([
|
||||
'mid' => $category,
|
||||
'cid' => $cid
|
||||
]));
|
||||
|
||||
if ($afterCount) {
|
||||
$this->db->query($this->db->update('table.metas')
|
||||
->expression('count', 'count + 1')
|
||||
->where('mid = ?', $category));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置内容标签
|
||||
*
|
||||
* @param integer $cid
|
||||
* @param string|null $tags
|
||||
* @param boolean $beforeCount 是否参与计数
|
||||
* @param boolean $afterCount 是否参与计数
|
||||
* @throws DbException
|
||||
*/
|
||||
protected function setTags(int $cid, ?string $tags, bool $beforeCount = true, bool $afterCount = true)
|
||||
{
|
||||
$tags = str_replace(',', ',', $tags);
|
||||
$tags = array_unique(array_map('trim', explode(',', $tags)));
|
||||
$tags = array_filter($tags, [Validate::class, 'xssCheck']);
|
||||
|
||||
/** 取出已有tag */
|
||||
$existTags = array_column(
|
||||
$this->db->fetchAll(
|
||||
$this->db->select('table.metas.mid')
|
||||
->from('table.metas')
|
||||
->join('table.relationships', 'table.relationships.mid = table.metas.mid')
|
||||
->where('table.relationships.cid = ?', $cid)
|
||||
->where('table.metas.type = ?', 'tag')
|
||||
),
|
||||
'mid'
|
||||
);
|
||||
|
||||
/** 删除已有tag */
|
||||
if ($existTags) {
|
||||
foreach ($existTags as $tag) {
|
||||
if (0 == strlen($tag)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$this->db->query($this->db->delete('table.relationships')
|
||||
->where('cid = ?', $cid)
|
||||
->where('mid = ?', $tag));
|
||||
|
||||
if ($beforeCount) {
|
||||
$this->db->query($this->db->update('table.metas')
|
||||
->expression('count', 'count - 1')
|
||||
->where('mid = ?', $tag));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** 取出插入tag */
|
||||
$insertTags = Metas::alloc()->scanTags($tags);
|
||||
|
||||
/** 插入tag */
|
||||
if ($insertTags) {
|
||||
foreach ($insertTags as $tag) {
|
||||
if (0 == strlen($tag)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$this->db->query($this->db->insert('table.relationships')
|
||||
->rows([
|
||||
'mid' => $tag,
|
||||
'cid' => $cid
|
||||
]));
|
||||
|
||||
if ($afterCount) {
|
||||
$this->db->query($this->db->update('table.metas')
|
||||
->expression('count', 'count + 1')
|
||||
->where('mid = ?', $tag));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 同步附件
|
||||
*
|
||||
* @param integer $cid 内容id
|
||||
* @throws DbException
|
||||
*/
|
||||
protected function attach(int $cid)
|
||||
{
|
||||
$attachments = $this->request->getArray('attachment');
|
||||
if (!empty($attachments)) {
|
||||
foreach ($attachments as $key => $attachment) {
|
||||
$this->db->query($this->db->update('table.contents')->rows([
|
||||
'parent' => $cid,
|
||||
'status' => 'publish',
|
||||
'order' => $key + 1
|
||||
])->where('cid = ? AND type = ?', $attachment, 'attachment'));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 取消附件关联
|
||||
*
|
||||
* @param integer $cid 内容id
|
||||
* @throws DbException
|
||||
*/
|
||||
protected function unAttach(int $cid)
|
||||
{
|
||||
$this->db->query($this->db->update('table.contents')->rows(['parent' => 0, 'status' => 'publish'])
|
||||
->where('parent = ? AND type = ?', $cid, 'attachment'));
|
||||
}
|
||||
|
||||
/**
|
||||
* 发布内容
|
||||
*
|
||||
* @param array $contents 内容结构
|
||||
* @param boolean $hasMetas 是否有metas
|
||||
* @throws DbException|Exception
|
||||
*/
|
||||
protected function publish(array $contents, bool $hasMetas = true)
|
||||
{
|
||||
/** 发布内容, 检查是否具有直接发布的权限 */
|
||||
$this->checkStatus($contents);
|
||||
|
||||
/** 真实的内容id */
|
||||
$realId = 0;
|
||||
|
||||
/** 是否是从草稿状态发布 */
|
||||
$isDraftToPublish = preg_match("/_draft$/", $this->type);
|
||||
|
||||
$isBeforePublish = ('publish' == $this->status);
|
||||
$isAfterPublish = ('publish' == $contents['status']);
|
||||
|
||||
/** 重新发布现有内容 */
|
||||
if ($this->have()) {
|
||||
|
||||
/** 如果它本身不是草稿, 需要删除其草稿 */
|
||||
if (!$isDraftToPublish && $this->draft) {
|
||||
$cid = $this->draft['cid'];
|
||||
$this->deleteDraft($cid);
|
||||
$this->deleteFields($cid);
|
||||
}
|
||||
|
||||
/** 直接将草稿状态更改 */
|
||||
if ($this->update($contents, $this->db->sql()->where('cid = ?', $this->cid))) {
|
||||
$realId = $this->cid;
|
||||
}
|
||||
} else {
|
||||
/** 发布一个新内容 */
|
||||
$realId = $this->insert($contents);
|
||||
}
|
||||
|
||||
if ($realId > 0) {
|
||||
if ($hasMetas) {
|
||||
/** 插入分类 */
|
||||
if (array_key_exists('category', $contents)) {
|
||||
$this->setCategories(
|
||||
$realId,
|
||||
!empty($contents['category']) && is_array($contents['category'])
|
||||
? $contents['category'] : [$this->options->defaultCategory],
|
||||
!$isDraftToPublish && $isBeforePublish,
|
||||
$isAfterPublish
|
||||
);
|
||||
}
|
||||
|
||||
/** 插入标签 */
|
||||
if (array_key_exists('tags', $contents)) {
|
||||
$this->setTags($realId, $contents['tags'], !$isDraftToPublish && $isBeforePublish, $isAfterPublish);
|
||||
}
|
||||
}
|
||||
|
||||
/** 同步附件 */
|
||||
$this->attach($realId);
|
||||
|
||||
/** 保存自定义字段 */
|
||||
$this->applyFields($this->getFields(), $realId);
|
||||
|
||||
$this->db->fetchRow($this->select()
|
||||
->where('table.contents.cid = ?', $realId)->limit(1), [$this, 'push']);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 保存内容
|
||||
*
|
||||
* @param array $contents 内容结构
|
||||
* @param boolean $hasMetas 是否有metas
|
||||
* @return integer
|
||||
* @throws DbException|Exception
|
||||
*/
|
||||
protected function save(array $contents, bool $hasMetas = true): int
|
||||
{
|
||||
/** 发布内容, 检查是否具有直接发布的权限 */
|
||||
$this->checkStatus($contents);
|
||||
|
||||
/** 真实的内容id */
|
||||
$realId = 0;
|
||||
|
||||
/** 如果草稿已经存在 */
|
||||
if ($this->draft) {
|
||||
|
||||
/** 直接将草稿状态更改 */
|
||||
if ($this->update($contents, $this->db->sql()->where('cid = ?', $this->draft['cid']))) {
|
||||
$realId = $this->draft['cid'];
|
||||
}
|
||||
} else {
|
||||
if ($this->have()) {
|
||||
$contents['parent'] = $this->cid;
|
||||
}
|
||||
|
||||
/** 发布一个新内容 */
|
||||
$realId = $this->insert($contents);
|
||||
|
||||
if (!$this->have()) {
|
||||
$this->db->fetchRow(
|
||||
$this->select()->where('table.contents.cid = ?', $realId)->limit(1),
|
||||
[$this, 'push']
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if ($realId > 0) {
|
||||
if ($hasMetas) {
|
||||
/** 插入分类 */
|
||||
if (array_key_exists('category', $contents)) {
|
||||
$this->setCategories($realId, !empty($contents['category']) && is_array($contents['category']) ?
|
||||
$contents['category'] : [$this->options->defaultCategory], false, false);
|
||||
}
|
||||
|
||||
/** 插入标签 */
|
||||
if (array_key_exists('tags', $contents)) {
|
||||
$this->setTags($realId, $contents['tags'], false, false);
|
||||
}
|
||||
}
|
||||
|
||||
/** 同步附件 */
|
||||
$this->attach($this->cid);
|
||||
|
||||
/** 保存自定义字段 */
|
||||
$this->applyFields($this->getFields(), $realId);
|
||||
|
||||
return $realId;
|
||||
}
|
||||
|
||||
return $this->draft['cid'];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $contents
|
||||
* @return void
|
||||
* @throws DbException
|
||||
* @throws Exception
|
||||
*/
|
||||
private function checkStatus(array &$contents)
|
||||
{
|
||||
if ($this->user->pass('editor', true)) {
|
||||
if (empty($contents['visibility'])) {
|
||||
$contents['status'] = 'publish';
|
||||
} elseif (
|
||||
!in_array($contents['visibility'], ['private', 'waiting', 'publish', 'hidden'])
|
||||
) {
|
||||
if (empty($contents['password']) || 'password' != $contents['visibility']) {
|
||||
$contents['password'] = '';
|
||||
}
|
||||
$contents['status'] = 'publish';
|
||||
} else {
|
||||
$contents['status'] = $contents['visibility'];
|
||||
$contents['password'] = '';
|
||||
}
|
||||
} else {
|
||||
$contents['status'] = 'waiting';
|
||||
$contents['password'] = '';
|
||||
}
|
||||
}
|
||||
}
|
@ -4,9 +4,12 @@ namespace Widget\Contents\Page;
|
||||
|
||||
use Typecho\Common;
|
||||
use Typecho\Date;
|
||||
use Typecho\Db\Exception as DbException;
|
||||
use Typecho\Widget\Exception;
|
||||
use Widget\Contents\Post\Edit as PostEdit;
|
||||
use Widget\Base\Contents;
|
||||
use Widget\Contents\EditTrait;
|
||||
use Widget\ActionInterface;
|
||||
use Widget\Contents\PrepareEditTrait;
|
||||
use Widget\Notice;
|
||||
use Widget\Service;
|
||||
|
||||
@ -23,15 +26,10 @@ if (!defined('__TYPECHO_ROOT_DIR__')) {
|
||||
* @copyright Copyright (c) 2008 Typecho team (http://www.typecho.org)
|
||||
* @license GNU General Public License 2.0
|
||||
*/
|
||||
class Edit extends PostEdit implements ActionInterface
|
||||
class Edit extends Contents implements ActionInterface
|
||||
{
|
||||
/**
|
||||
* 自定义字段的hook名称
|
||||
*
|
||||
* @var string
|
||||
* @access protected
|
||||
*/
|
||||
protected string $themeCustomFieldsHook = 'themePageFields';
|
||||
use PrepareEditTrait;
|
||||
use EditTrait;
|
||||
|
||||
/**
|
||||
* 执行函数
|
||||
@ -39,30 +37,12 @@ class Edit extends PostEdit implements ActionInterface
|
||||
* @access public
|
||||
* @return void
|
||||
* @throws Exception
|
||||
* @throws \Typecho\Db\Exception
|
||||
* @throws DbException
|
||||
*/
|
||||
public function execute()
|
||||
{
|
||||
/** 必须为编辑以上权限 */
|
||||
$this->user->pass('editor');
|
||||
|
||||
/** 获取文章内容 */
|
||||
if ($this->request->is('cid')) {
|
||||
$this->db->fetchRow($this->select()
|
||||
->where('table.contents.type = ? OR table.contents.type = ?', 'page', 'page_draft')
|
||||
->where('table.contents.cid = ?', $this->request->filter('int')->get('cid'))
|
||||
->limit(1), [$this, 'push']);
|
||||
|
||||
if ('page_draft' == $this->status && $this->parent) {
|
||||
$this->response->redirect(Common::url('write-page.php?cid=' . $this->parent, $this->options->adminUrl));
|
||||
}
|
||||
|
||||
if (!$this->have()) {
|
||||
throw new Exception(_t('页面不存在'), 404);
|
||||
} elseif (!$this->allow('edit')) {
|
||||
throw new Exception(_t('没有编辑权限'), 403);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@ -94,7 +74,7 @@ class Edit extends PostEdit implements ActionInterface
|
||||
if ($this->request->is('do=publish')) {
|
||||
/** 重新发布已经存在的文章 */
|
||||
$contents['type'] = 'page';
|
||||
$this->publish($contents);
|
||||
$this->publish($contents, false);
|
||||
|
||||
// 完成发布插件接口
|
||||
self::pluginHandle()->call('finishPublish', $contents, $this);
|
||||
@ -116,7 +96,7 @@ class Edit extends PostEdit implements ActionInterface
|
||||
} else {
|
||||
/** 保存文章 */
|
||||
$contents['type'] = 'page_draft';
|
||||
$this->save($contents);
|
||||
$draftId = $this->save($contents, false);
|
||||
|
||||
// 完成发布插件接口
|
||||
self::pluginHandle()->call('finishSave', $contents, $this);
|
||||
@ -130,7 +110,7 @@ class Edit extends PostEdit implements ActionInterface
|
||||
'success' => 1,
|
||||
'time' => $created->format('H:i:s A'),
|
||||
'cid' => $this->cid,
|
||||
'draftId' => $this->draft['cid']
|
||||
'draftId' => $draftId
|
||||
]);
|
||||
} else {
|
||||
/** 设置提示信息 */
|
||||
@ -145,7 +125,7 @@ class Edit extends PostEdit implements ActionInterface
|
||||
/**
|
||||
* 标记页面
|
||||
*
|
||||
* @throws \Typecho\Db\Exception
|
||||
* @throws DbException
|
||||
*/
|
||||
public function markPage()
|
||||
{
|
||||
@ -202,7 +182,7 @@ class Edit extends PostEdit implements ActionInterface
|
||||
/**
|
||||
* 删除页面
|
||||
*
|
||||
* @throws \Typecho\Db\Exception
|
||||
* @throws DbException
|
||||
*/
|
||||
public function deletePage()
|
||||
{
|
||||
@ -238,7 +218,7 @@ class Edit extends PostEdit implements ActionInterface
|
||||
$this->deleteFields($page);
|
||||
|
||||
if ($draft) {
|
||||
$this->deleteDraft($draft['cid']);
|
||||
$this->deleteDraft($draft['cid'], false);
|
||||
$this->deleteFields($draft['cid']);
|
||||
}
|
||||
|
||||
@ -263,7 +243,7 @@ class Edit extends PostEdit implements ActionInterface
|
||||
/**
|
||||
* 删除页面所属草稿
|
||||
*
|
||||
* @throws \Typecho\Db\Exception
|
||||
* @throws DbException
|
||||
*/
|
||||
public function deletePageDraft()
|
||||
{
|
||||
@ -298,7 +278,7 @@ class Edit extends PostEdit implements ActionInterface
|
||||
/**
|
||||
* 页面排序
|
||||
*
|
||||
* @throws \Typecho\Db\Exception
|
||||
* @throws DbException
|
||||
*/
|
||||
public function sortPage()
|
||||
{
|
||||
@ -319,20 +299,40 @@ class Edit extends PostEdit implements ActionInterface
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return $this
|
||||
* @throws DbException
|
||||
* @throws Exception
|
||||
*/
|
||||
public function prepare(): self
|
||||
{
|
||||
return $this->prepareEdit('page', true, _t('页面不存在'));
|
||||
}
|
||||
|
||||
/**
|
||||
* 绑定动作
|
||||
*
|
||||
* @access public
|
||||
* @return void
|
||||
* @throws DbException
|
||||
* @throws Exception
|
||||
*/
|
||||
public function action()
|
||||
{
|
||||
$this->security->protect();
|
||||
$this->on($this->request->is('do=publish') || $this->request->is('do=save'))->writePage();
|
||||
$this->on($this->request->is('do=publish') || $this->request->is('do=save'))
|
||||
->prepare()->writePage();
|
||||
$this->on($this->request->is('do=delete'))->deletePage();
|
||||
$this->on($this->request->is('do=mark'))->markPage();
|
||||
$this->on($this->request->is('do=deleteDraft'))->deletePageDraft();
|
||||
$this->on($this->request->is('do=sort'))->sortPage();
|
||||
$this->response->redirect($this->options->adminUrl);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
protected function getThemeFieldsHook(): string
|
||||
{
|
||||
return 'themePageFields';
|
||||
}
|
||||
}
|
||||
|
@ -3,16 +3,14 @@
|
||||
namespace Widget\Contents\Post;
|
||||
|
||||
use Typecho\Common;
|
||||
use Typecho\Config;
|
||||
use Typecho\Validate;
|
||||
use Typecho\Widget\Exception;
|
||||
use Typecho\Widget\Helper\Form\Element;
|
||||
use Typecho\Widget\Helper\Layout;
|
||||
use Widget\Base\Contents;
|
||||
use Widget\Base\Metas;
|
||||
use Widget\ActionInterface;
|
||||
use Typecho\Db\Exception as DbException;
|
||||
use Typecho\Date as TypechoDate;
|
||||
use Widget\Contents\EditTrait;
|
||||
use Widget\Contents\PrepareEditTrait;
|
||||
use Widget\Notice;
|
||||
use Widget\Service;
|
||||
|
||||
@ -22,17 +20,11 @@ if (!defined('__TYPECHO_ROOT_DIR__')) {
|
||||
|
||||
/**
|
||||
* 编辑文章组件
|
||||
*
|
||||
* @property-read array|null $draft
|
||||
*/
|
||||
class Edit extends Contents implements ActionInterface
|
||||
{
|
||||
/**
|
||||
* 自定义字段的hook名称
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected string $themeCustomFieldsHook = 'themePostFields';
|
||||
use PrepareEditTrait;
|
||||
use EditTrait;
|
||||
|
||||
/**
|
||||
* 执行函数
|
||||
@ -43,220 +35,6 @@ class Edit extends Contents implements ActionInterface
|
||||
{
|
||||
/** 必须为贡献者以上权限 */
|
||||
$this->user->pass('contributor');
|
||||
|
||||
/** 获取文章内容 */
|
||||
if ($this->request->is('cid')) {
|
||||
$this->db->fetchRow($this->select()
|
||||
->where('table.contents.type = ? OR table.contents.type = ?', 'post', 'post_draft')
|
||||
->where('table.contents.cid = ?', $this->request->filter('int')->get('cid'))
|
||||
->limit(1), [$this, 'push']);
|
||||
|
||||
if ('post_draft' == $this->type && $this->parent) {
|
||||
$this->response->redirect(
|
||||
Common::url('write-post.php?cid=' . $this->parent, $this->options->adminUrl)
|
||||
);
|
||||
}
|
||||
|
||||
if (!$this->have()) {
|
||||
throw new Exception(_t('文章不存在'), 404);
|
||||
} elseif (!$this->allow('edit')) {
|
||||
throw new Exception(_t('没有编辑权限'), 403);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取文章权限
|
||||
*
|
||||
* @param mixed ...$permissions
|
||||
* @return bool
|
||||
* @throws Exception|DbException
|
||||
*/
|
||||
public function allow(...$permissions): bool
|
||||
{
|
||||
$allow = true;
|
||||
|
||||
foreach ($permissions as $permission) {
|
||||
$permission = strtolower($permission);
|
||||
|
||||
if ('edit' == $permission) {
|
||||
$allow &= ($this->user->pass('editor', true) || $this->authorId == $this->user->uid);
|
||||
} else {
|
||||
$permission = 'allow' . ucfirst(strtolower($permission));
|
||||
$optionPermission = 'default' . ucfirst($permission);
|
||||
$allow &= ($this->{$permission} ?? $this->options->{$optionPermission});
|
||||
}
|
||||
}
|
||||
|
||||
return $allow;
|
||||
}
|
||||
|
||||
/**
|
||||
* 过滤堆栈
|
||||
*
|
||||
* @param array $value 每行的值
|
||||
* @return array
|
||||
* @throws DbException
|
||||
*/
|
||||
public function filter(array $value): array
|
||||
{
|
||||
if ('post' == $value['type'] || 'page' == $value['type']) {
|
||||
$draft = $this->db->fetchRow(Contents::alloc()->select()
|
||||
->where(
|
||||
'table.contents.parent = ? AND table.contents.type = ?',
|
||||
$value['cid'],
|
||||
$value['type'] . '_draft'
|
||||
)
|
||||
->limit(1));
|
||||
|
||||
if (!empty($draft)) {
|
||||
$draft['slug'] = ltrim($draft['slug'], '@');
|
||||
$draft['type'] = $value['type'];
|
||||
|
||||
$draft = parent::filter($draft);
|
||||
|
||||
$draft['tags'] = $this->db->fetchAll($this->db
|
||||
->select()->from('table.metas')
|
||||
->join('table.relationships', 'table.relationships.mid = table.metas.mid')
|
||||
->where('table.relationships.cid = ?', $draft['cid'])
|
||||
->where('table.metas.type = ?', 'tag'), [Metas::alloc(), 'filter']);
|
||||
$draft['cid'] = $value['cid'];
|
||||
|
||||
return $draft;
|
||||
}
|
||||
}
|
||||
|
||||
return parent::filter($value);
|
||||
}
|
||||
|
||||
/**
|
||||
* 输出文章发布日期
|
||||
*
|
||||
* @param string $format 日期格式
|
||||
* @return void
|
||||
*/
|
||||
public function date($format = null)
|
||||
{
|
||||
if (isset($this->created)) {
|
||||
parent::date($format);
|
||||
} else {
|
||||
echo date($format, $this->options->time + $this->options->timezone - $this->options->serverTimezone);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取网页标题
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getMenuTitle(): string
|
||||
{
|
||||
return _t('编辑 %s', $this->title);
|
||||
}
|
||||
|
||||
/**
|
||||
* getFieldItems
|
||||
*
|
||||
* @throws DbException
|
||||
*/
|
||||
public function getFieldItems(): array
|
||||
{
|
||||
$fields = [];
|
||||
|
||||
if ($this->have()) {
|
||||
$defaultFields = $this->getDefaultFieldItems();
|
||||
$rows = $this->db->fetchAll($this->db->select()->from('table.fields')
|
||||
->where('cid = ?', $this->cid));
|
||||
|
||||
foreach ($rows as $row) {
|
||||
$isFieldReadOnly = Contents::pluginHandle()
|
||||
->trigger($plugged)->call('isFieldReadOnly', $row['name']);
|
||||
|
||||
if ($plugged && $isFieldReadOnly) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!isset($defaultFields[$row['name']])) {
|
||||
$fields[] = $row;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $fields;
|
||||
}
|
||||
|
||||
/**
|
||||
* getDefaultFieldItems
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getDefaultFieldItems(): array
|
||||
{
|
||||
$defaultFields = [];
|
||||
$configFile = $this->options->themeFile($this->options->theme, 'functions.php');
|
||||
$layout = new Layout();
|
||||
$fields = new Config();
|
||||
|
||||
if ($this->have()) {
|
||||
$fields = $this->fields;
|
||||
}
|
||||
|
||||
self::pluginHandle()->call('getDefaultFieldItems', $layout);
|
||||
|
||||
if (file_exists($configFile)) {
|
||||
require_once $configFile;
|
||||
|
||||
if (function_exists('themeFields')) {
|
||||
themeFields($layout);
|
||||
}
|
||||
|
||||
if (function_exists($this->themeCustomFieldsHook)) {
|
||||
call_user_func($this->themeCustomFieldsHook, $layout);
|
||||
}
|
||||
}
|
||||
|
||||
$items = $layout->getItems();
|
||||
foreach ($items as $item) {
|
||||
if ($item instanceof Element) {
|
||||
$name = $item->input->getAttribute('name');
|
||||
|
||||
$isFieldReadOnly = Contents::pluginHandle()
|
||||
->trigger($plugged)->call('isFieldReadOnly', $name);
|
||||
if ($plugged && $isFieldReadOnly) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (preg_match("/^fields\[(.+)\]$/", $name, $matches)) {
|
||||
$name = $matches[1];
|
||||
} else {
|
||||
$inputName = 'fields[' . $name . ']';
|
||||
if (preg_match("/^(.+)\[\]$/", $name, $matches)) {
|
||||
$name = $matches[1];
|
||||
$inputName = 'fields[' . $name . '][]';
|
||||
}
|
||||
|
||||
foreach ($item->inputs as $input) {
|
||||
$input->setAttribute('name', $inputName);
|
||||
}
|
||||
}
|
||||
|
||||
if (isset($fields->{$name})) {
|
||||
$item->value($fields->{$name});
|
||||
}
|
||||
|
||||
$elements = $item->container->getItems();
|
||||
array_shift($elements);
|
||||
$div = new Layout('div');
|
||||
|
||||
foreach ($elements as $el) {
|
||||
$div->addItem($el);
|
||||
}
|
||||
|
||||
$defaultFields[$name] = [$item->label, $div];
|
||||
}
|
||||
}
|
||||
|
||||
return $defaultFields;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -294,7 +72,9 @@ class Edit extends Contents implements ActionInterface
|
||||
self::pluginHandle()->call('finishPublish', $contents, $this);
|
||||
|
||||
/** 发送ping */
|
||||
$trackback = array_filter(array_unique(preg_split("/(\r|\n|\r\n)/", trim($this->request->get('trackback')))));
|
||||
$trackback = array_filter(
|
||||
array_unique(preg_split("/(\r|\n|\r\n)/", trim($this->request->get('trackback'))))
|
||||
);
|
||||
Service::alloc()->sendPing($this, $trackback);
|
||||
|
||||
/** 设置提示信息 */
|
||||
@ -313,7 +93,7 @@ class Edit extends Contents implements ActionInterface
|
||||
} else {
|
||||
/** 保存文章 */
|
||||
$contents['type'] = 'post_draft';
|
||||
$this->save($contents);
|
||||
$draftId = $this->save($contents);
|
||||
|
||||
// 完成保存插件接口
|
||||
self::pluginHandle()->call('finishSave', $contents, $this);
|
||||
@ -327,7 +107,7 @@ class Edit extends Contents implements ActionInterface
|
||||
'success' => 1,
|
||||
'time' => $created->format('H:i:s A'),
|
||||
'cid' => $this->cid,
|
||||
'draftId' => $this->draft['cid']
|
||||
'draftId' => $draftId
|
||||
]);
|
||||
} else {
|
||||
/** 设置提示信息 */
|
||||
@ -339,325 +119,6 @@ class Edit extends Contents implements ActionInterface
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据提交值获取created字段值
|
||||
*
|
||||
* @return integer
|
||||
*/
|
||||
protected function getCreated(): int
|
||||
{
|
||||
$created = $this->options->time;
|
||||
if ($this->request->is('created')) {
|
||||
$created = $this->request->get('created');
|
||||
} elseif ($this->request->is('date')) {
|
||||
$dstOffset = $this->request->get('dst', 0);
|
||||
$timezoneSymbol = $this->options->timezone >= 0 ? '+' : '-';
|
||||
$timezoneOffset = abs($this->options->timezone);
|
||||
$timezone = $timezoneSymbol . str_pad($timezoneOffset / 3600, 2, '0', STR_PAD_LEFT) . ':00';
|
||||
[$date, $time] = explode(' ', $this->request->get('date'));
|
||||
|
||||
$created = strtotime("{$date}T{$time}{$timezone}") - $dstOffset;
|
||||
} elseif ($this->request->is('year&month&day')) {
|
||||
$second = $this->request->filter('int')->get('sec', date('s'));
|
||||
$min = $this->request->filter('int')->get('min', date('i'));
|
||||
$hour = $this->request->filter('int')->get('hour', date('H'));
|
||||
|
||||
$year = $this->request->filter('int')->get('year');
|
||||
$month = $this->request->filter('int')->get('month');
|
||||
$day = $this->request->filter('int')->get('day');
|
||||
|
||||
$created = mktime($hour, $min, $second, $month, $day, $year)
|
||||
- $this->options->timezone + $this->options->serverTimezone;
|
||||
} elseif ($this->have() && $this->created > 0) {
|
||||
//如果是修改文章
|
||||
$created = $this->created;
|
||||
} elseif ($this->request->is('do=save')) {
|
||||
// 如果是草稿而且没有任何输入则保持原状
|
||||
$created = 0;
|
||||
}
|
||||
|
||||
return $created;
|
||||
}
|
||||
|
||||
/**
|
||||
* 发布内容
|
||||
*
|
||||
* @param array $contents 内容结构
|
||||
* @throws DbException|Exception
|
||||
*/
|
||||
protected function publish(array $contents)
|
||||
{
|
||||
/** 发布内容, 检查是否具有直接发布的权限 */
|
||||
$this->checkStatus($contents);
|
||||
|
||||
/** 真实的内容id */
|
||||
$realId = 0;
|
||||
|
||||
/** 是否是从草稿状态发布 */
|
||||
$isDraftToPublish = ('post_draft' == $this->type || 'page_draft' == $this->type);
|
||||
|
||||
$isBeforePublish = ('publish' == $this->status);
|
||||
$isAfterPublish = ('publish' == $contents['status']);
|
||||
|
||||
/** 重新发布现有内容 */
|
||||
if ($this->have()) {
|
||||
|
||||
/** 如果它本身不是草稿, 需要删除其草稿 */
|
||||
if (!$isDraftToPublish && $this->draft) {
|
||||
$cid = $this->draft['cid'];
|
||||
$this->deleteDraft($cid);
|
||||
$this->deleteFields($cid);
|
||||
}
|
||||
|
||||
/** 直接将草稿状态更改 */
|
||||
if ($this->update($contents, $this->db->sql()->where('cid = ?', $this->cid))) {
|
||||
$realId = $this->cid;
|
||||
}
|
||||
} else {
|
||||
/** 发布一个新内容 */
|
||||
$realId = $this->insert($contents);
|
||||
}
|
||||
|
||||
if ($realId > 0) {
|
||||
/** 插入分类 */
|
||||
if (array_key_exists('category', $contents)) {
|
||||
$this->setCategories(
|
||||
$realId,
|
||||
!empty($contents['category']) && is_array($contents['category'])
|
||||
? $contents['category'] : [$this->options->defaultCategory],
|
||||
!$isDraftToPublish && $isBeforePublish,
|
||||
$isAfterPublish
|
||||
);
|
||||
}
|
||||
|
||||
/** 插入标签 */
|
||||
if (array_key_exists('tags', $contents)) {
|
||||
$this->setTags($realId, $contents['tags'], !$isDraftToPublish && $isBeforePublish, $isAfterPublish);
|
||||
}
|
||||
|
||||
/** 同步附件 */
|
||||
$this->attach($realId);
|
||||
|
||||
/** 保存自定义字段 */
|
||||
$this->applyFields($this->getFields(), $realId);
|
||||
|
||||
$this->db->fetchRow($this->select()->where('table.contents.cid = ?', $realId)->limit(1), [$this, 'push']);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除草稿
|
||||
*
|
||||
* @param integer $cid 草稿id
|
||||
* @throws DbException
|
||||
*/
|
||||
protected function deleteDraft(int $cid)
|
||||
{
|
||||
$this->delete($this->db->sql()->where('cid = ?', $cid));
|
||||
|
||||
/** 删除草稿分类 */
|
||||
$this->setCategories($cid, [], false, false);
|
||||
|
||||
/** 删除标签 */
|
||||
$this->setTags($cid, null, false, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置分类
|
||||
*
|
||||
* @param integer $cid 内容id
|
||||
* @param array $categories 分类id的集合数组
|
||||
* @param boolean $beforeCount 是否参与计数
|
||||
* @param boolean $afterCount 是否参与计数
|
||||
* @throws DbException
|
||||
*/
|
||||
public function setCategories(int $cid, array $categories, bool $beforeCount = true, bool $afterCount = true)
|
||||
{
|
||||
$categories = array_unique(array_map('trim', $categories));
|
||||
|
||||
/** 取出已有category */
|
||||
$existCategories = array_column(
|
||||
$this->db->fetchAll(
|
||||
$this->db->select('table.metas.mid')
|
||||
->from('table.metas')
|
||||
->join('table.relationships', 'table.relationships.mid = table.metas.mid')
|
||||
->where('table.relationships.cid = ?', $cid)
|
||||
->where('table.metas.type = ?', 'category')
|
||||
),
|
||||
'mid'
|
||||
);
|
||||
|
||||
/** 删除已有category */
|
||||
if ($existCategories) {
|
||||
foreach ($existCategories as $category) {
|
||||
$this->db->query($this->db->delete('table.relationships')
|
||||
->where('cid = ?', $cid)
|
||||
->where('mid = ?', $category));
|
||||
|
||||
if ($beforeCount) {
|
||||
$this->db->query($this->db->update('table.metas')
|
||||
->expression('count', 'count - 1')
|
||||
->where('mid = ?', $category));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** 插入category */
|
||||
if ($categories) {
|
||||
foreach ($categories as $category) {
|
||||
/** 如果分类不存在 */
|
||||
if (
|
||||
!$this->db->fetchRow(
|
||||
$this->db->select('mid')
|
||||
->from('table.metas')
|
||||
->where('mid = ?', $category)
|
||||
->limit(1)
|
||||
)
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$this->db->query($this->db->insert('table.relationships')
|
||||
->rows([
|
||||
'mid' => $category,
|
||||
'cid' => $cid
|
||||
]));
|
||||
|
||||
if ($afterCount) {
|
||||
$this->db->query($this->db->update('table.metas')
|
||||
->expression('count', 'count + 1')
|
||||
->where('mid = ?', $category));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置内容标签
|
||||
*
|
||||
* @param integer $cid
|
||||
* @param string|null $tags
|
||||
* @param boolean $beforeCount 是否参与计数
|
||||
* @param boolean $afterCount 是否参与计数
|
||||
* @throws DbException
|
||||
*/
|
||||
public function setTags(int $cid, ?string $tags, bool $beforeCount = true, bool $afterCount = true)
|
||||
{
|
||||
$tags = str_replace(',', ',', $tags);
|
||||
$tags = array_unique(array_map('trim', explode(',', $tags)));
|
||||
$tags = array_filter($tags, [Validate::class, 'xssCheck']);
|
||||
|
||||
/** 取出已有tag */
|
||||
$existTags = array_column(
|
||||
$this->db->fetchAll(
|
||||
$this->db->select('table.metas.mid')
|
||||
->from('table.metas')
|
||||
->join('table.relationships', 'table.relationships.mid = table.metas.mid')
|
||||
->where('table.relationships.cid = ?', $cid)
|
||||
->where('table.metas.type = ?', 'tag')
|
||||
),
|
||||
'mid'
|
||||
);
|
||||
|
||||
/** 删除已有tag */
|
||||
if ($existTags) {
|
||||
foreach ($existTags as $tag) {
|
||||
if (0 == strlen($tag)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$this->db->query($this->db->delete('table.relationships')
|
||||
->where('cid = ?', $cid)
|
||||
->where('mid = ?', $tag));
|
||||
|
||||
if ($beforeCount) {
|
||||
$this->db->query($this->db->update('table.metas')
|
||||
->expression('count', 'count - 1')
|
||||
->where('mid = ?', $tag));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** 取出插入tag */
|
||||
$insertTags = Metas::alloc()->scanTags($tags);
|
||||
|
||||
/** 插入tag */
|
||||
if ($insertTags) {
|
||||
foreach ($insertTags as $tag) {
|
||||
if (0 == strlen($tag)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$this->db->query($this->db->insert('table.relationships')
|
||||
->rows([
|
||||
'mid' => $tag,
|
||||
'cid' => $cid
|
||||
]));
|
||||
|
||||
if ($afterCount) {
|
||||
$this->db->query($this->db->update('table.metas')
|
||||
->expression('count', 'count + 1')
|
||||
->where('mid = ?', $tag));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 同步附件
|
||||
*
|
||||
* @param integer $cid 内容id
|
||||
* @throws DbException
|
||||
*/
|
||||
protected function attach(int $cid)
|
||||
{
|
||||
$attachments = $this->request->getArray('attachment');
|
||||
if (!empty($attachments)) {
|
||||
foreach ($attachments as $key => $attachment) {
|
||||
$this->db->query($this->db->update('table.contents')->rows([
|
||||
'parent' => $cid,
|
||||
'status' => 'publish',
|
||||
'order' => $key + 1
|
||||
])->where('cid = ? AND type = ?', $attachment, 'attachment'));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* getFields
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected function getFields(): array
|
||||
{
|
||||
$fields = [];
|
||||
$fieldNames = $this->request->getArray('fieldNames');
|
||||
|
||||
if (!empty($fieldNames)) {
|
||||
$data = [
|
||||
'fieldNames' => $this->request->getArray('fieldNames'),
|
||||
'fieldTypes' => $this->request->getArray('fieldTypes'),
|
||||
'fieldValues' => $this->request->getArray('fieldValues')
|
||||
];
|
||||
foreach ($data['fieldNames'] as $key => $val) {
|
||||
$val = trim($val);
|
||||
|
||||
if (0 == strlen($val)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$fields[$val] = [$data['fieldTypes'][$key], $data['fieldValues'][$key]];
|
||||
}
|
||||
}
|
||||
|
||||
$customFields = $this->request->getArray('fields');
|
||||
foreach ($customFields as $key => $val) {
|
||||
$fields[$key] = [is_array($val) ? 'json' : 'str', $val];
|
||||
}
|
||||
|
||||
return $fields;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取页面偏移的URL Query
|
||||
*
|
||||
@ -677,63 +138,6 @@ class Edit extends Contents implements ActionInterface
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* 保存内容
|
||||
*
|
||||
* @param array $contents 内容结构
|
||||
* @throws DbException|Exception
|
||||
*/
|
||||
protected function save(array $contents)
|
||||
{
|
||||
/** 发布内容, 检查是否具有直接发布的权限 */
|
||||
$this->checkStatus($contents);
|
||||
|
||||
/** 真实的内容id */
|
||||
$realId = 0;
|
||||
|
||||
/** 如果草稿已经存在 */
|
||||
if ($this->draft) {
|
||||
|
||||
/** 直接将草稿状态更改 */
|
||||
if ($this->update($contents, $this->db->sql()->where('cid = ?', $this->draft['cid']))) {
|
||||
$realId = $this->draft['cid'];
|
||||
}
|
||||
} else {
|
||||
if ($this->have()) {
|
||||
$contents['parent'] = $this->cid;
|
||||
}
|
||||
|
||||
/** 发布一个新内容 */
|
||||
$realId = $this->insert($contents);
|
||||
|
||||
if (!$this->have()) {
|
||||
$this->db->fetchRow(
|
||||
$this->select()->where('table.contents.cid = ?', $realId)->limit(1),
|
||||
[$this, 'push']
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if ($realId > 0) {
|
||||
/** 插入分类 */
|
||||
if (array_key_exists('category', $contents)) {
|
||||
$this->setCategories($realId, !empty($contents['category']) && is_array($contents['category']) ?
|
||||
$contents['category'] : [$this->options->defaultCategory], false, false);
|
||||
}
|
||||
|
||||
/** 插入标签 */
|
||||
if (array_key_exists('tags', $contents)) {
|
||||
$this->setTags($realId, $contents['tags'], false, false);
|
||||
}
|
||||
|
||||
/** 同步附件 */
|
||||
$this->attach($this->cid);
|
||||
|
||||
/** 保存自定义字段 */
|
||||
$this->applyFields($this->getFields(), $realId);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 标记文章
|
||||
*
|
||||
@ -895,18 +299,6 @@ class Edit extends Contents implements ActionInterface
|
||||
$this->response->goBack();
|
||||
}
|
||||
|
||||
/**
|
||||
* 取消附件关联
|
||||
*
|
||||
* @param integer $cid 内容id
|
||||
* @throws DbException
|
||||
*/
|
||||
protected function unAttach(int $cid)
|
||||
{
|
||||
$this->db->query($this->db->update('table.contents')->rows(['parent' => 0, 'status' => 'publish'])
|
||||
->where('parent = ? AND type = ?', $cid, 'attachment'));
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除文章所属草稿
|
||||
*
|
||||
@ -942,13 +334,26 @@ class Edit extends Contents implements ActionInterface
|
||||
$this->response->goBack();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return $this
|
||||
* @throws DbException
|
||||
* @throws Exception
|
||||
*/
|
||||
public function prepare(): self
|
||||
{
|
||||
return $this->prepareEdit('post', true, _t('文章不存在'));
|
||||
}
|
||||
|
||||
/**
|
||||
* 绑定动作
|
||||
*
|
||||
* @throws Exception|DbException
|
||||
*/
|
||||
public function action()
|
||||
{
|
||||
$this->security->protect();
|
||||
$this->on($this->request->is('do=publish') || $this->request->is('do=save'))->writePost();
|
||||
$this->on($this->request->is('do=publish') || $this->request->is('do=save'))
|
||||
->prepare()->writePost();
|
||||
$this->on($this->request->is('do=delete'))->deletePost();
|
||||
$this->on($this->request->is('do=mark'))->markPost();
|
||||
$this->on($this->request->is('do=deleteDraft'))->deletePostDraft();
|
||||
@ -957,83 +362,10 @@ class Edit extends Contents implements ActionInterface
|
||||
}
|
||||
|
||||
/**
|
||||
* 将tags取出
|
||||
*
|
||||
* @return array
|
||||
* @throws DbException
|
||||
* @return string
|
||||
*/
|
||||
protected function ___tags(): array
|
||||
protected function getThemeFieldsHook(): string
|
||||
{
|
||||
if ($this->have()) {
|
||||
return $this->db->fetchAll($this->db
|
||||
->select()->from('table.metas')
|
||||
->join('table.relationships', 'table.relationships.mid = table.metas.mid')
|
||||
->where('table.relationships.cid = ?', $this->cid)
|
||||
->where('table.metas.type = ?', 'tag'), [Metas::alloc(), 'filter']);
|
||||
}
|
||||
|
||||
return [];
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前时间
|
||||
*
|
||||
* @return TypechoDate
|
||||
*/
|
||||
protected function ___date(): TypechoDate
|
||||
{
|
||||
return new TypechoDate();
|
||||
}
|
||||
|
||||
/**
|
||||
* 当前文章的草稿
|
||||
*
|
||||
* @return array|null
|
||||
* @throws DbException
|
||||
*/
|
||||
protected function ___draft(): ?array
|
||||
{
|
||||
if ($this->have()) {
|
||||
if ('post_draft' == $this->type || 'page_draft' == $this->type) {
|
||||
return $this->row;
|
||||
} else {
|
||||
return $this->db->fetchRow(Contents::alloc()->select()
|
||||
->where(
|
||||
'table.contents.parent = ? AND (table.contents.type = ? OR table.contents.type = ?)',
|
||||
$this->cid,
|
||||
'post_draft',
|
||||
'page_draft'
|
||||
)
|
||||
->limit(1), [Contents::alloc(), 'filter']);
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $contents
|
||||
* @return void
|
||||
*/
|
||||
private function checkStatus(array &$contents)
|
||||
{
|
||||
if ($this->user->pass('editor', true)) {
|
||||
if (empty($contents['visibility'])) {
|
||||
$contents['status'] = 'publish';
|
||||
} elseif (
|
||||
!in_array($contents['visibility'], ['private', 'waiting', 'publish', 'hidden'])
|
||||
) {
|
||||
if (empty($contents['password']) || 'password' != $contents['visibility']) {
|
||||
$contents['password'] = '';
|
||||
}
|
||||
$contents['status'] = 'publish';
|
||||
} else {
|
||||
$contents['status'] = $contents['visibility'];
|
||||
$contents['password'] = '';
|
||||
}
|
||||
} else {
|
||||
$contents['status'] = 'waiting';
|
||||
$contents['password'] = '';
|
||||
}
|
||||
return 'themePostFields';
|
||||
}
|
||||
}
|
||||
|
114
var/Widget/Contents/PrepareEditTrait.php
Normal file
114
var/Widget/Contents/PrepareEditTrait.php
Normal file
@ -0,0 +1,114 @@
|
||||
<?php
|
||||
|
||||
namespace Widget\Contents;
|
||||
|
||||
use Typecho\Common;
|
||||
use Typecho\Db\Exception as DbException;
|
||||
use Typecho\Widget\Exception;
|
||||
use Widget\Base\Metas;
|
||||
|
||||
trait PrepareEditTrait
|
||||
{
|
||||
|
||||
/**
|
||||
* 准备编辑
|
||||
*
|
||||
* @param string $type
|
||||
* @param bool $hasDraft
|
||||
* @param string $notFoundMessage
|
||||
* @return $this
|
||||
* @throws Exception|DbException
|
||||
*/
|
||||
protected function prepareEdit(string $type, bool $hasDraft, string $notFoundMessage): self
|
||||
{
|
||||
if ($this->request->is('cid')) {
|
||||
$contentTypes = [$type];
|
||||
if ($hasDraft) {
|
||||
$contentTypes[] = $type . '_draft';
|
||||
}
|
||||
|
||||
$this->db->fetchRow($this->select()
|
||||
->where('table.contents.type IN ?', $contentTypes)
|
||||
->where('table.contents.cid = ?', $this->request->filter('int')->get('cid'))
|
||||
->limit(1), [$this, 'push']);
|
||||
|
||||
if (!$this->have()) {
|
||||
throw new Exception($notFoundMessage, 404);
|
||||
}
|
||||
|
||||
if ($hasDraft) {
|
||||
if ($type . '_draft' === $this->type && $this->parent) {
|
||||
$this->response->redirect(
|
||||
Common::url('write-' . $type . '.php?cid=' . $this->parent, $this->options->adminUrl)
|
||||
);
|
||||
}
|
||||
|
||||
$draft = $this->type === $type . '_draft' ? $this->row : $this->db->fetchRow($this->select()
|
||||
->where('table.contents.parent = ? AND table.contents.type = ?', $this->cid, $type . '_draft')
|
||||
->limit(1), [$this, 'filter']);
|
||||
|
||||
if (isset($draft)) {
|
||||
$draft['slug'] = ltrim($draft['slug'], '@');
|
||||
$draft['type'] = $type;
|
||||
$draft['draft'] = $draft;
|
||||
$draft['cid'] = $this->cid;
|
||||
$draft['tags'] = $this->db->fetchAll($this->db
|
||||
->select()->from('table.metas')
|
||||
->join('table.relationships', 'table.relationships.mid = table.metas.mid')
|
||||
->where('table.relationships.cid = ?', $draft['cid'])
|
||||
->where('table.metas.type = ?', 'tag'), [Metas::alloc(), 'filter']);
|
||||
|
||||
$this->row = $draft;
|
||||
}
|
||||
}
|
||||
|
||||
if (!$this->allow('edit')) {
|
||||
throw new Exception(_t('没有编辑权限'), 403);
|
||||
}
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return $this
|
||||
*/
|
||||
abstract public function prepare(): self;
|
||||
|
||||
/**
|
||||
* 获取网页标题
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getMenuTitle(): string
|
||||
{
|
||||
return _t('编辑 %s', $this->prepare()->title);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 获取权限
|
||||
*
|
||||
* @param mixed ...$permissions
|
||||
* @return bool
|
||||
* @throws Exception|DbException
|
||||
*/
|
||||
public function allow(...$permissions): bool
|
||||
{
|
||||
$allow = true;
|
||||
|
||||
foreach ($permissions as $permission) {
|
||||
$permission = strtolower($permission);
|
||||
|
||||
if ('edit' == $permission) {
|
||||
$allow &= ($this->user->pass('editor', true) || $this->authorId == $this->user->uid);
|
||||
} else {
|
||||
$permission = 'allow' . ucfirst(strtolower($permission));
|
||||
$optionPermission = 'default' . ucfirst($permission);
|
||||
$allow &= ($this->{$permission} ?? $this->options->{$optionPermission});
|
||||
}
|
||||
}
|
||||
|
||||
return $allow;
|
||||
}
|
||||
}
|
@ -47,7 +47,7 @@ class Edit extends Metas implements ActionInterface
|
||||
->where('type = ?', 'category')
|
||||
->where('mid = ?', $mid)->limit(1));
|
||||
|
||||
return (bool)$category;
|
||||
return isset($category);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -47,7 +47,7 @@ class Edit extends Metas implements ActionInterface
|
||||
->where('type = ?', 'tag')
|
||||
->where('mid = ?', $mid)->limit(1));
|
||||
|
||||
return (bool)$tag;
|
||||
return isset($tag);
|
||||
}
|
||||
|
||||
/**
|
||||
|
Loading…
x
Reference in New Issue
Block a user