TinyMCEにファイルアップロード機能を実装する

2016年12月8日

動作環境

Bootstrap: 3.3.7
Dropzone.js:4.3.0
TinyMCE:?

 

追記(18/06/15)

この記事は古い記事です。

下記URLが最新の記事となっています。

Vue.jsを使って、もっとWordPressっぽいTinyMCE用メディアアップロードを実装する

 

TinyMCEにアップロード機能を欲しい!

WordPressのリッチテキストエディタでお馴染みのTinyMCEですが、デフォルトだとローカル環境のファイルをサーバーにアップロードする機能がありません。

なので、作ってみました。

TinyMCE

 

まずアップロード機能

最初は、ローカルにあるファイルをサーバーにアップロードする機能が欲しいです。

どうせならドッラグ&ドロップでアップできると楽ですよね!

というわけで、DropZoneさんの出番です。

Dropzone.js

 

使い方は上記URLをみてもらうとわかると思います(英語読めぬ)

 

次にUI

普段Wordpressを使っているせいか、WordpressライクなUIがお気にい入りです。

なので、今回はWordpressっぽく、Modelに搭載して、アップロードしたいと思います。

Bootstrap

 

導入

■bower

まずbowerを使って、必要なファイルを取得します。

※ bowerが無ければ、各ファイルを本家からダウンロードしてくればOK

{
"name": "bower",
"authors": [
"kabanoki"
],
"description": "",
"main": "",
"license": "MIT",
"homepage": "",
"ignore": [
"**/.*",
"node_modules",
"bower_components",
"test",
"tests"
],
"dependencies": {
"dropzone": "^4.3.0",
"tinymce": "^4.5.0",
"bootstrap": "^3.3.7"
}
}

 

■フロントエンド(HTML・CSS・javascript)

CSSもJavascriptもHTMLに直書きしてしまうような雑なソースですが、「index.html」として以下を

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<link rel="stylesheet" href="bower_components/bootstrap/dist/css/bootstrap.min.css" type="text/css" />
<link rel="stylesheet" href="bower_components/bootstrap/dist/css/bootstrap-theme.min.css" type="text/css" />
<link rel="stylesheet" href="bower_components/dropzone/dist/dropzone.css">
<link rel="stylesheet" href="bower_components/dropzone/dist/basic.css">
<style>
/* メイン */
#main {
padding:20px;
}

/* mce */
.mytextarea{
width: 100%;
height: 700px;
}

/* model */
.tab-content .tab-pane{
min-height: 600px;
}

/* model-dropzoon */
.tab-content .tab-pane form{
min-height: 600px;
border: 2px dashed #2e6da4;
}
div#uplode form#my-dropzone div.dz-message {
margin-top: 30%;
}
.dz-message span{
font-weight: bold;
}
.dz-drag-hover{
background-color: #7acaff;
}

/* modal-mediabox */
.media-box{
max-height: 615px;
min-height: 615px;
overflow: auto;
}
.media-box h3.media-datatime{
clear: both;
padding-top: 20px;
padding-bottom: 5px;
border-bottom: 1px solid #000;
}
.media-box .media-list {
margin: 10px;
padding: 5px;
text-align: center;
cursor: pointer;
min-width: 140px;
}
.media-box .media-list .img-box{
border: 1px solid #ddd;
height: 120px;
padding: 10px;
}
.media-box .media-list.media-checked .img-box{
border: 2px solid #0000ff;
}
.media-list .img-box img{
max-width: 100%;
max-height: 100%;
}

.media-detail{
border-left: 1px solid #ddd;
max-height: 615px;
min-height: 615px;
overflow: auto;
background-color: #f3f3f3;
display: none;
}
</style>
</head>
<body>

<!-- メインコンテンツ -->
<div id="main">
<button class="btn btn-default" type="button" data-toggle="modal" data-target="#myModal">
<span class="glyphicon glyphicon-picture"></span>ファイルアップロード
</button>
<textarea class="mytextarea">アップロードするとDEMO画像が挿入されます。</textarea>
</div>


<!-- Modal -->
<div class="modal fade" id="myModal" tabindex="-1" role="dialog" aria-labelledby="myModalLabel">
<div class="modal-dialog modal-lg" role="document">
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">&times;</span></button>
<h4 class="modal-title" id="myModalLabel">ファイルアップロード</h4>
</div>
<div class="modal-body">
<!-- Nav tabs -->
<ul class="nav nav-tabs" role="tablist">
<li role="presentation"class="active"><a href="#uplode" aria-controls="profile" role="tab" data-toggle="tab">アップロード</a></li>
<li role="presentation" >
<a href="#home" aria-controls="home" role="tab" data-toggle="tab">ファイル一覧</a>
</li>
</ul>

<!-- Tab panes -->
<div class="tab-content">
<div role="tabpanel" class="tab-pane active" id="uplode">
<form id="my-dropzone" action="./upload.php" class="dropzone">
<div class="fallback">
<input name="file" type="file" multiple />
</div>
</form>
</div>
<div role="tabpanel" class="tab-pane" id="home">
<div class="media-box col-lg-12 col-md-12 col-sm-12 col-xs-12">
</div>
<div class="media-detail col-lg-4 col-md-4 col-sm-4 col-xs-4">

</div>
</div>
</div>
</div>
<div class="modal-footer">
<div class="media-title-checked text-left col-lg-5 col-md-5 col-sm-5 col-xs-5"></div>
<div class=" col-lg-7 col-md-7 col-sm-7 col-xs-7">
<button type="button" class="media-insert btn btn-primary " disabled="disabled">記事に挿入</button>
<button type="button" class="media-delete btn btn-danger" disabled="disabled">画像を削除</button>
</div>
</div>
</div>
</div>
</div>
<!-- Modal -->



<script src="bower_components/jquery/dist/jquery.min.js"></script>
<script src="bower_components/bootstrap/dist/js/bootstrap.js"></script>
<script src="bower_components/tinymce/tinymce.min.js"></script>
<script src="bower_components/dropzone/dist/dropzone.js"></script>


<script>
Dropzone.options.myAwesomeDropzone = false;
Dropzone.autoDiscover = false;

var media;

$(function(){
/**
* tinymce
*/
tinymce.init({
selector: '.mytextarea',
body_id: 'mce-blog',
});

/**
* tab
*/
$('#myTabs a').click(function (e) {
e.preventDefault()
$(this).tab('show')
})


/**
* dropzone
*/
var myDropzone = new Dropzone("#my-dropzone",{
dictDefaultMessage : 'ファイルをドロップでアップロードします。',
renameFilename: function(file_name){
return file_name;
},
acceptedFiles : 'image/*' +
', application/pdf' +
', application/excel' +
', application/vnd.ms-excel' +
', application/msexcel' +
', application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' +
', application/msword' +
', application/vnd.openxmlformats-officedocument.wordprocessingml.document',
dictInvalidFileType : '選択されたファイルは許可されていない形式です。'
});

myDropzone.on("success", function(file, res) {
console.log(file);
console.log(res);
console.log('success');

var ele = document.getElementsByClassName('data-datetime-' + res.datatime);

// アップロードメディアを一覧に追加
if(ele.length === 0)
{
// 日付ラベルが無い時
split_datatime = res.datatime.split('-');

var h3tag = document.createElement('h3');
h3tag.textContent = split_datatime[0] + "年" + split_datatime[1] + "月";
h3tag.classList.add('media-datatime', 'data-datetime-' + res.datatime);
$(".media-box").prepend(h3tag, res.html);
}
else
{
// 日付ラベルが有る時
$(".media-box .data-datetime-" + res.datatime).after(res.html);
}

//
if(/^image/.test(file.type))
media = tinymce.activeEditor.dom.createHTML('img', {src: res.path, style:'max-width:100%;', class:'mce-img'}, '');
else
media = tinymce.activeEditor.dom.createHTML('a', {href: res.path, class:'mce-media'}, '<img src="./images/media/document.png">');


});

myDropzone.on("complete", function(file, res) {
console.log('complete');
tinymce.activeEditor.selection.setContent(media);
});

/**
* model
*/
$('#myModal').on('hidden.bs.modal', function () {
console.log('hidden.bs.modal');
var getAcceptedFiles = myDropzone.getAcceptedFiles();

jQuery.each(getAcceptedFiles, function() {
myDropzone.removeFile(this);
});
});


/**
* media event
*
*/
// リストのメディアを選択
$(document).on('click', '.media-list',function(){
$(".media-list").removeClass("media-checked");
$(this).addClass("media-checked");

$(".media-title-checked").text($(this).find("img").attr('alt'));
$(".media-insert, .media-delete").prop("disabled", false);
});

// リストからエディタにメディアをアップ
$(".media-insert").click(function(){
var src = $(".media-checked .img-box img").attr('src');
var media = tinymce.activeEditor.dom.createHTML('img', {src: src, style:'max-width:100%;', class:'mce-img'}, '');

tinymce.activeEditor.selection.setContent(media);

$(".media-insert, .media-delete").prop("disabled", true);

$('#myModal').modal('hide');
});

// リストからメディアを削除
$(".media-delete").click(function(){
if(confirm('本当に削除しますか?')){
// 日付グループのitemが全て無くなったら、日付ラベルを削除
var ele = document.getElementsByClassName('media-checked');
var prevEle = ele[0].previousElementSibling;
var nextEle = ele[0].nextElementSibling;

if((prevEle == null || prevEle.tagName == 'H3') && (nextEle == null || nextEle.tagName == 'H3'))
{
var h3tag = ele[0].previousElementSibling;
h3tag.parentNode.removeChild(h3tag);
}

// itemを削除
$(".media-checked").remove();
$(".media-insert, .media-delete").prop("disabled", true);
$(".media-title-checked").text('');
}
});
});
</script>
</body>
</html>

 

■サーバーサイド(PHP)

次に、ファイルをサーバーにアップロードする為のシステムをPHPで書きました。
以下のシステムはセキュリティ系の事を一切考えずに書いてあるので、そのまま本番に使用しないでください!

foreach ($_FILES AS $file)
{
  if (is_uploaded_file($file["tmp_name"]))
  {
    if (move_uploaded_file($file["tmp_name"], "./uploads/" . $file["name"]))
    {
      chmod("uploads/" . $file["name"], 0644);
      header("Content-Type: application/json; charset=utf-8");
      echo json_encode(array(
          'path' => "./uploads/" . $file["name"]
         ,'datatime' => date('Y-m')
         ,'html' => sprintf('<div class="media-list col-lg-2 col-md-2 col-sm-2 col-xs-2"><div class="img-box"><img src="%s" /></div></div>', "./uploads/" . $file["name"])
      ));
    }
    else
    {
       echo "ファイルをアップロードできません。";
    }
  }
  else
  {
     echo "ファイルが選択されていません。";
   }
}
die;

これでOKなはずです。
ファイルは全て同一階層です。

 

完成版イメージ

 

http://aqueous-beyond-18288.herokuapp.com/upload_modal/full.html

 

  • この記事を書いた人

カバノキ

印刷会社のWEB部隊に所属してます。 WEB制作に携わってから、もう時期10年になります。 普段の業務では、PHPをメインにサーバーサイドの言語を扱っています。 最近のお気に入りはJavascriptです。 Vue.jsを狂喜乱舞しながら、社内に布教中です。

-jQuery
-, , ,