tinyMCEのリアルタイムプレビュー

show.blade.php

<textarea id="myTextArea" name="body" class="mceEditor">{{ old('body') }}</textarea>
	@if($errors->has('body'))
	<span class="error">{{ $errors->first('body') }}</span>
	@endif
	<p>
		<input type="submit" value="登録">
	</p>
	</form>
	<div style="border:1px solid; width:300px; height:100px;" id="preview_area"></div>
	<script src="/js/main.js"></script>
	<script src="/js/tinymce/tinymce.min.js"></script>
	<script>
  	tinymce.init({
    mode : "specific_textareas",
    editor_selector : "mceEditor",
    init_instance_callback: function (editor) {
      editor.on('change', function (e) {
          $('#preview_area').html(editor.getContent());
      });
    }
  });
</script>

これはマジ凄い。

tinyMCEをlaravelに配置する

@extends('layouts.default')

@section('title', $article->login_id)

@section('content')
<h1>
	<a href="{{ action('ArticlesController@edit', $article->id)}}" class="register">編集</a>
	<a href="/">登録情報</a>
</h1>
	<h3>{{ $article->name}}</h3>
		<p>{{$article->login_id}}</p>
		<p>{{$article->name}}</p>
		<p>{{$article->role}}</p>
		<p>{{$article->password}}</p>

<h2>Documents</h2>
<ul>
		@forelse ($article->documents as $document)
		<li>{{ $document->body }} <a href="#" class="del" data-id="{{ $document->id }}">[x]</a>
		<form method="post" action="{{ action('DocumentsController@destroy', &#91;$article, $document&#93;) }}" id="form_{{ $document->id }}">
      {{ csrf_field() }}
      {{ method_field('delete') }}
    </form></li>
		@empty
		<li>No document yet</li>
		@endforelse
</ul>
<form method="post" action="{{ action('DocumentsController@store', $article) }}">
		{{ csrf_field()}}
	<p>
		<input type="text" name="mobile" placeholder="iphone/android" value="{{ old('mobile') }}">
		@if($errors->has('mobile'))
		<span class="error">{{ $errors->first('mobile') }}</span>
		@endif
	</p>
	<p>
		<input type="text" name="published_at" placeholder="配信日" value="{{ old('published_at') }}">
		@if($errors->has('published_at'))
		<span class="error">{{ $errors->first('published_at') }}</span>
		@endif
	</p>
	<!-- <p>
		<input type="text" name="body" placeholder="原稿" value="{{ old('body') }}">
		@if($errors->has('body'))
		<span class="error">{{ $errors->first('body') }}</span>
		@endif
	</p> -->
	<textarea id="myTextArea" name="body">{{ old('body') }}</textarea>
	@if($errors->has('body'))
	<span class="error">{{ $errors->first('body') }}</span>
	@endif
	<p>
		<input type="submit" value="登録">
	</p>
	</form>
	<script src="/js/main.js"></script>
	<script src="/js/tinymce/tinymce.min.js"></script>
	<script>
		tinymce.init({
		selector: "textarea",  
	});
	</script>
@endsection

tinyMCE

登録後

なるほど~

tinyMCEをpost

<?php
// function eh($s) { echo htmlspecialchars($s, ENT_QUOTES, "UTF-8"); }
$data = filter_input(INPUT_POST, "name");
?>
<!DOCTYPE html>
<html>
<head>
	<script src="tinymce/js/tinymce/tinymce.min.js"></script>
	<script>
		tinymce.init({
		selector: "textarea",  
	});
	</script>
</head>
<body>
	<h1>テキストを入力してください</h1>
	<form method="post">
		<textarea id="myTextArea" name="name"></textarea>
		<input type="submit" value="送信">
	</form>
	<h2>送信データ</h2>
	<?php if($data){ ?>
		<pre><?php echo $data; ?></pre>
	<?php } ?>
</body>
</html>

ok

これをlaravelに実装する

TinyMCEで入力した値を表示

<!DOCTYPE html>
<html>
<head>
	<script src="tinymce/js/tinymce/tinymce.min.js"></script>
	<script>tinymce.init({
		selector: 'textarea'
	});
	</script>
</head>
<body>
	<h1>テキストを入力してください</h1>
	<textarea>TinyMCE Editor</textarea>
</body>
</html>

普通に実装します。

vagrantでajaxでpythonにpostする

index.php

<!DOCTYPE html>
<html lang="ja">
<head>
  <title>Ajax</title>
</head>

<body>
  <h1>Ajax</h1>
  <form id="form">
    <div><label>送信する数字</label><input type="number" id="number" value="0"></div>
    <div>
      <label>送信するテキスト</label>
      <textarea id="text"></textarea>
    </div>
    <button type="submit" class="btn btn-primary">Submit</button>
  </form>
  <div id="result"></div>

  <script src="https://code.jquery.com/jquery-3.2.1.min.js"></script>
  <script type="text/javascript">
    $(document).ready(function(){
      $('#form').submit(function(){
        event.preventDefault();
        var $form = $(this);
        $.ajax({
          url:'http://localhost:8000/cgi-bin/index.py',
          type: 'post',
          dataType: 'text',
          data: {
            number: $('#number').val(),
            text: $('#text').val()
          },
        })
        .done(function(response){
          $('#result').html(response);
        })
        .fail(function(){
          $('#result').html('Failed.');
        });
      });
    });
    </script>
  </body>
</html>

index.py

#!/usr/bin/env python
# -*- coding: utf-8 -*-

import cgi, cgitb

cgitb.enable()

form = cgi.FieldStorage()
text = form.getFirst("text")
n = form.getFirst("number")
sequence_list = []

print('Content-type: text/html\nAccess-Control-Allow-Origin: *\n')
print("<p>送信された数字: {}</p>".format("None" if n is None else int(n)))
print("<p>送信されたテキスト: {}</p>".format(text))
python -m http.server --cgi

何故だ? 問題はHTML側ではないと思うので、AWSもしくはsakuraでやってみるか。

index.phpをindex.htmlに変更します。

192.168.35.1 – – [26/Aug/2018 10:19:03] code 403, message CGI script is not executable (‘/cgi-bin/index.py’)
192.168.35.1 – – [26/Aug/2018 10:19:03] “POST /cgi-bin/index.py HTTP/1.1” 403 –

なに?
[vagrant@localhost app]$ cd cgi-bin
[vagrant@localhost cgi-bin]$ chmod 755 index.py

192.168.35.1 – – [26/Aug/2018 10:23:35] “GET / HTTP/1.1” 200 –
192.168.35.1 – – [26/Aug/2018 10:23:43] “POST /cgi-bin/index.py HTTP/1.1” 200 –
: そのようなファイルやディレクトリはありません
192.168.35.1 – – [26/Aug/2018 10:23:43] CGI script exit status 0x7f00

う~ん、なんでだろう。
jsのdocument.titleで取得してphpファイルに送ることもできるが、後々のことを考えるとpythonでやりたいですね。

adsenseが広告を表示している時のscriptタグを見てみよう

<html>
 <head>
 <script>var google_casm=[];</script></head>
  <body leftmargin="0" topmargin="0" marginwidth="0" marginheight="0">
   <script>var viewReq = new Array();function vu(u) {var i=new Image();i.src=u.replace("&amp;","&");viewReq.push(i);}</script>
    <script>
     vu("https://googleads.g.doubleclick.net/pagead/adview?ai\x3dCNObGSnmAW8HkMcy8qAGF6pToCea_s-9Nt8nw14wBwI23ARABIABgifvEhPQTggEXY2EtcHViLTU5MDk5MDY5MDMwMDE1NDPIAQmpAia1Lc4RUEM-qAMBqgSpAU_QQ_Rj93S8kVbtJKXBn61POdqV-PJKkq6oiRUHR8R3xXVht0iQFjq2o58McqzSOPki4ViMccrfpDxizihR5gxjXu1-qq_MV0AttoMAoplvpeafMKLu6MqIgOgSPwwDQYRIMg950_-ErWJUJtnkTTwEZDgLU0yKtgbmZ_L3nN2wnRHsyjF2sS2rI1ghh34IjMCyQPx-WEie9FZEpMgMbcfxEEtQVxM9IiSABu3ThJ-Hh8_FV6AGIagHpr4bqAfZyxuoB8_MG9gHANIIBQiAYRAB\x26sigh\x3d7lNTaw-j1Lg\x26tpd\x3dAGWhJmvGJR22zlwGWMoBY7QldpBR54WOoVfhFqBTgNjTnIeH4Q")</script><iframe id="a4fcaf81" name="a4fcaf81" src="https://ads.as.criteo.com/delivery/r/afr.php?did=5b80794a3ecfb6aec32279adbb7dc500&amp;z=W4B5SgAMckEKKh5MAAU1BeHMADN-sy159r8etg&amp;u=%7Cbqp5UtXU%2FozCsXrUntRD9aG8mBTS%2F4cMmUHa%2B7P%2FHbI%3D%7C&amp;c1=R15UMXQIVgOy1yFtMk-OzIVKTl7RHrqjv9VEgFn2s5CInTakLjEscWAT_QgGCs-DVac46ma0roVknAcVeiz79qA2jvRycM3wXHRt_ZC6ggqQ7Z8L8VBfDIlxb-1KofGAcbIdMvKaVCvd8efl34JCyEjMZxDTgFk_N0MyDRJuY1LLIrBq-p4utMNjs8LwhgBCqJwMYbJazdskLfTa2B_s6EavaaQFpSC8mfGIpS6WwfMZ-H6B0zAnYFMg5Y3_cNZ3ReFwFysqbLPncHVSha5lNVpM6gv1VwpGthJmOiC2HGBPtHfgld82edQj06OnpmsFFgExvKn1Xy9Sf4Rocj4-xoo9dE9mi_dAE-f2Px4BVMk67ET0YpkcRNq6DoXmez5UNfI0nF2OonkbauC-lIvdHnVnbkCeXh3J&amp;ct0=https://adclick.g.doubleclick.net/aclk%3Fsa%3DL%26ai%3DCNObGSnmAW8HkMcy8qAGF6pToCea_s-9Nt8nw14wBwI23ARABIABgifvEhPQTggEXY2EtcHViLTU5MDk5MDY5MDMwMDE1NDPIAQmpAia1Lc4RUEM-qAMBqgSpAU_QQ_Rj93S8kVbtJKXBn61POdqV-PJKkq6oiRUHR8R3xXVht0iQFjq2o58McqzSOPki4ViMccrfpDxizihR5gxjXu1-qq_MV0AttoMAoplvpeafMKLu6MqIgOgSPwwDQYRIMg950_-ErWJUJtnkTTwEZDgLU0yKtgbmZ_L3nN2wnRHsyjF2sS2rI1ghh34IjMCyQPx-WEie9FZEpMgMbcfxEEtQVxM9IiSABu3ThJ-Hh8_FV6AGIagHpr4bqAfZyxuoB8_MG9gHANIIBQiAYRAB%26num%3D1%26sig%3DAOD64_3_vbq_qdR_XCHngAs7zLB1xkG4oA%26client%3Dca-pub-5909906903001543%26adurl%3D" framespacing="0" frameborder="no" scrolling="no" width="160" height="600"></iframe><script src="https://tpc.googlesyndication.com/pagead/js/r20180820/r20110914/client/ext/m_window_focus_non_hydra.js" async="">
</script>

<script>function initWindowFocus() {window['window_focus_for_click'] =wfocusnhinit("https://googleads.g.doubleclick.net/pagead/conversion/?ai\x3dCNObGSnmAW8HkMcy8qAGF6pToCea_s-9Nt8nw14wBwI23ARABIABgifvEhPQTggEXY2EtcHViLTU5MDk5MDY5MDMwMDE1NDPIAQmpAia1Lc4RUEM-qAMBqgSpAU_QQ_Rj93S8kVbtJKXBn61POdqV-PJKkq6oiRUHR8R3xXVht0iQFjq2o58McqzSOPki4ViMccrfpDxizihR5gxjXu1-qq_MV0AttoMAoplvpeafMKLu6MqIgOgSPwwDQYRIMg950_-ErWJUJtnkTTwEZDgLU0yKtgbmZ_L3nN2wnRHsyjF2sS2rI1ghh34IjMCyQPx-WEie9FZEpMgMbcfxEEtQVxM9IiSABu3ThJ-Hh8_FV6AGIagHpr4bqAfZyxuoB8_MG9gHANIIBQiAYRAB\x26sigh\x3dAqdxowVUmv4","SnmAW6alMYaUigbcyq-gDw","CMGxl9bRht0CFUweKgodBTUFnQ",true,false);}if (window.wfocusnhinit) {initWindowFocus();} else {window['google_wf_async'] = initWindowFocus;}</script><script src="https://tpc.googlesyndication.com/pagead/js/r20180820/r20110914/activeview/osd_listener.js"></script><script type="text/javascript">osdlfm(-1,'','B669ySnmAW8HkMcy8qAGF6pToCQC3yfDXjAEAABABOAHIAQmgBiHSCAUIgGEQAQ','',1373412883,true,'xza\x3d1\x26mza\x3d1\x26ud\x3d1\x26la\x3d0\x26alp\x3dai\x26alh\x3d1235097137\x26',3,'CAASFeRodzIus_dBieBQD7HnIWF19lQiig','//pagead2.googlesyndication.com/activeview?avi\x3dB669ySnmAW8HkMcy8qAGF6pToCQC3yfDXjAEAABABOAHIAQmgBiHSCAUIgGEQAQ\x26cid\x3dCAASFeRodzIus_dBieBQD7HnIWF19lQiig','');</script><script src="https://tpc.googlesyndication.com/pagead/js/r20180820/r20110914/client/ext/m_qs_click_protection.js"></script><script>googqscp.init([[[[null,500,99,2,8,null,null,null,1]]],null,null,null,null,null,null,null,0]);</script><div style="display: none; position: absolute; z-index: 2147483647; width: 100%; height: 100%; top: 0px; left: 0px;"></div>

<img src="//www.google.com/ads/measurement/l?ebcid=ALh7CaRtfTX0i5pM-7NJdmInBZxQR_2XDJrER0DNwCrJ2CCr6n_HdcvqI2qqpMqL9NX0xgsG7QDylH6rGfso5mpyifDJm2tnlA" style="display:none;"><img src="https://googleads.g.doubleclick.net/pagead/ide_cookie" style="display:none;"><script>if (window.top && window.top.postMessage) {window.top.postMessage('{"googMsgType":"adpnt"}','*');}</script><div style="display:none" data-google-query-id="CMGxl9bRht0CFUweKgodBTUFnQ"></div><div style="bottom:0;right:0;width:86px;height:250px;background:initial !important;position:absolute !important;max-width:100% !important;max-height:100% !important;pointer-events:none !important;image-rendering:pixelated !important;z-index:2147483647;background-image:url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACsAAAAWBAMAAACrl3iAAAAABlBMVEUAAAD+AciWmZzWAAAAAnRSTlMAApidrBQAAAB8SURBVBjTbZEBDsAgCAOPH/T/r11EKGrGiBtZwbPCf8QOYn27AuVfrdRUqSCfwFWKpFypt061e1qN12O2SeIicUgPN00SzOy9eYqdJG5NtbTUA98HJrx/tcFJom5eMaTRbt7nsoXJ7+0H+3YQGYvXu+DxrP3eedxlga/4ACLHBaIDKh93AAAAAElFTkSuQmCC') !important">
  </div>
 </body>
</html>

まず、var google_casm=[];で google_casmを宣言しています。
leftmargin=”0″ topmargin=”0″ marginwidth=”0″ marginheight=”0″ なので、marginは0です。
var viewReq = new Array();function vu(u) {var i=new Image();i.src=u.replace(“&”,”&”);viewReq.push(i); で、viewReqの配列を宣言し、vu(u)でuの&ampを&に変換、viewReqに new Imageをpushしています。

でました、doubleclick.net ここでもかよ!
double clickはGAのcookieやsessionを保存しているDBなので、恐らくdouble clickにデータを読みに行っていると考えられます。
https://googleads.g.doubleclick.net/pagead/adview?ai

続いてiframe ここで広告を標示していますね。criteo.com?? 初めて聞きました。phpファイルですね。
iframe id=”a4fcaf81″ name=”a4fcaf81″ src=”https://ads.as.criteo.com/delivery/r/afr.php

criteoのサイトを見ると広告系のように見える。
https://www.criteo.com/

次はclick時の処理か?
function initWindowFocus() {window[‘window_focus_for_click’] =wfocusnhinit(“https://googleads.g.doubleclick.net/pagead/conversion/?

https://tpc.googlesyndication.com/pagead/js/r20180820/r20110914/activeview/osd_listener.js

pagead2.googlesyndication.com/activeview
//www.google.com/ads/measurement/l

まとめると、行動履歴のデータを読み込んで、iframeで広告を表示して、さらにどの広告をクリックしたか計測してDBに入ている、ということか。

簡易的に作るとすれば、
1. ユーザーのリファラーを取得する
2. リファラーから、ユーザーが見たページのコンテンツを言語解析する
3. 解析したコンテンツと近い広告をiframeで表示する

-> ユーザーのリファラーの取得は、cookieやsessionを埋め込んでればokだが、そうでない場合にどうするか?
-> DOM構造からどうやって言語解析をするか。形態素解析で頻出単語を抽出し、ページのカテゴリーを推測するか
-> adsenseの出稿管理画面ではどうなっているか?
-> DBで登録した広告をどうやってiframeで表示させるか?

仮説ではこんなところ。

jsで位置情報を取得する

<div id="result"></div>

<script>
if(navigator.geolocation)
{
	navigator.geolocation.getCurrentPosition(

		function(position){

			var data = position.coords;

		var lat = data.latitude;
		var lng = data.longitude;
		var alt = data.altitude;
		var accLatlng = data.accuracy;
		var accAlt = data.altitudeAccuracy;
		var heading = data.heading;
		var speed = data.speed;

		document.getElementById('result').innerHTML = '<dl><dt>緯度</dt><dd>' + lat + '</dd><dt>経度</dt><dd>' + lng + '</dd><dt>高度</dt><dd>' + alt + '</dd><dt>緯度、経度の精度</dt><dd>' + accLatlng + '</dd><dt>高度の精度</dt><dd>' + accAlt + '</dd><dt>方角</dt><dd>' + heading + '</dd><dt>速度</dt><dd>' + speed + '</dd></dl>' ;

		var latlng = new google.maps.LatLng(lat, lng);

		var map = new google.maps.Map(document.getElementById('map-canvas'), {
			zoom: 15,
			center: latlng,
		});
			new google.maps.Marker({
				map: map,
				position: latlng,
			});
		},
		
		function(error)
		{
			var errorInfo = [
				"原因不明のエラーが発生しました。",
				"位置情報の取得が許可されませんでした。",
				"電波状況などで位置情報が取得できませんでした。",
				"位置情報の取得に時間がかかりすぎてタイムアウトしました。"
			];

			var errorNo = error.code;

			var errorMessage = "[エラー番号: " + errorNo + "]\n" + errorInfo[ errorNo ];

			alert(errorMessage);
			document.getElementById("result").innerHTML = errorMessage;

		},
		{
			"enableHighAccuracy": false,
			"timeout": 8000,
			"maximumAge": 2000,
		}

		);

}

else
{
	var errorMessage = "お使いの端末は、Geolocation APIに対応していません。";

	alert(errorMessage);

	document.getElementById('result').innerHTML = errorMessage;
}

</script>

なに!? vagrantだから?

Vue.jsを触ってみよう

CDNで読み込みます。

<script src="https://cdn.jsdelivr.net/npm/vue@2.5.17/dist/vue.js"></script>
<div id="demo">
	<p>{{message}}</p>
	<input v-model="message">
</div>

<script>
	var demo = new Vue({
		el: "#demo",
		data: {
			message: "hello Vue.js!"
		}
	})
</script>

Ajaxのような動きをする。
CDNのvue.jsの中を見てみる。
https://cdn.jsdelivr.net/npm/vue@2.5.17/dist/vue.js

XMLHttpRequestがないのでAjaxではないのか。。これだけではちょっとわかりません。

v-modelは双方向データバインディング
v-ifはifでレンダリング
v-onはv-on:click=”doSomething”など、イベントリスナ
v-forはfor文

<script src="https://cdn.jsdelivr.net/npm/vue@2.5.17/dist/vue.js"></script>
<div id="demo">
	<li v-for="item in items">
		{{item.message}}
	</li>
</div>

<script>
	var demo = new Vue({
		el: "#demo",
		data: {
			items: [
				{message: 'Foo'},
				{message: 'Bar'}
			]
		}
	})
</script>

なるほど。書きやすそうな印象はありますね。