[{"data":1,"prerenderedAt":26579},["ShallowReactive",2],{"category-devstack":3},{"count":4,"content":5},43,[6,1437,2025,6117,7784,9576,13236,15446,20317,23917],{"id":7,"title":8,"body":9,"category":1423,"createdAt":1425,"description":1426,"extension":1427,"index":1428,"meta":1429,"navigation":340,"path":1430,"publish":340,"seo":1431,"series":1428,"seriesTitle":1428,"stem":1432,"tag":1433,"thumbnail":1435,"updatedAt":1425,"__hash__":1436},"articles\u002Farticles\u002Fmultiple-select-vue.md","multiple属性での複数選択セレクトボックスで選択した値をemitする方法。",{"type":10,"value":11,"toc":1417},"minimark",[12,16,20,23,41,176,197,200,576,583,590,594,597,605,717,728,733,736,740,744,747,1227,1233,1239,1248,1315,1332,1389,1398,1401,1404,1407,1410,1413],[13,14,15],"p",{},"こんにちはjunです。vue.jsを用いてフォームUIを作成していたところmultipule属性を付与したセレクトボックスで選択した値を$emitする方法についてメモ＆共有しようと思います。",[17,18,19],"h2",{"id":19},"vueを用いてselectの値を受け取る",[13,21,22],{},"セレクトボックスは基本的には以下の様に、プルダウン式のメニューから一つの値を選ぶ要素です。",[24,25,27,28,27,33,27,37],"select",{"name":26},"sample","\n ",[29,30,32],"option",{"value":31},"1","選択肢１",[29,34,36],{"value":35},"2","選択肢2",[29,38,40],{"value":39},"3","選択肢3",[42,43,48],"pre",{"className":44,"code":45,"language":46,"meta":47,"style":47},"language-html shiki shiki-themes material-theme-ocean","\u003Cselect name=\"sample\">\n \u003Coption value=\"1\">選択肢１\u003C\u002Foption>\n \u003Coption value=\"2\">選択肢2\u003C\u002Foption>\n \u003Coption value=\"3\">選択肢3\u003C\u002Foption>\n\u003C\u002Fselect>\n","html","",[49,50,51,81,113,140,167],"code",{"__ignoreMap":47},[52,53,56,60,63,67,70,73,76,78],"span",{"class":54,"line":55},"line",1,[52,57,59],{"class":58},"sAklC","\u003C",[52,61,24],{"class":62},"s-wAU",[52,64,66],{"class":65},"sJ14y"," name",[52,68,69],{"class":58},"=",[52,71,72],{"class":58},"\"",[52,74,26],{"class":75},"sfyAc",[52,77,72],{"class":58},[52,79,80],{"class":58},">\n",[52,82,84,87,89,92,94,96,98,100,103,106,109,111],{"class":54,"line":83},2,[52,85,86],{"class":58}," \u003C",[52,88,29],{"class":62},[52,90,91],{"class":65}," value",[52,93,69],{"class":58},[52,95,72],{"class":58},[52,97,31],{"class":75},[52,99,72],{"class":58},[52,101,102],{"class":58},">",[52,104,32],{"class":105},"s0W1g",[52,107,108],{"class":58},"\u003C\u002F",[52,110,29],{"class":62},[52,112,80],{"class":58},[52,114,116,118,120,122,124,126,128,130,132,134,136,138],{"class":54,"line":115},3,[52,117,86],{"class":58},[52,119,29],{"class":62},[52,121,91],{"class":65},[52,123,69],{"class":58},[52,125,72],{"class":58},[52,127,35],{"class":75},[52,129,72],{"class":58},[52,131,102],{"class":58},[52,133,36],{"class":105},[52,135,108],{"class":58},[52,137,29],{"class":62},[52,139,80],{"class":58},[52,141,143,145,147,149,151,153,155,157,159,161,163,165],{"class":54,"line":142},4,[52,144,86],{"class":58},[52,146,29],{"class":62},[52,148,91],{"class":65},[52,150,69],{"class":58},[52,152,72],{"class":58},[52,154,39],{"class":75},[52,156,72],{"class":58},[52,158,102],{"class":58},[52,160,40],{"class":105},[52,162,108],{"class":58},[52,164,29],{"class":62},[52,166,80],{"class":58},[52,168,170,172,174],{"class":54,"line":169},5,[52,171,108],{"class":58},[52,173,24],{"class":62},[52,175,80],{"class":58},[13,177,178,179,182,183,185,186,188,189,192,193,196],{},"基本的にinput系の値を取得するときは",[49,180,181],{},"v-model","を使用するのが定石ですが、",[49,184,181],{},"では解決できないコンポーネントの状況もあるでしょう。私も",[49,187,181],{},"という糖衣構文ではなく",[49,190,191],{},"v-bind=\"~~~\"",", ",[49,194,195],{},"@input=\"$emit(~~~~)\"","という形で入力された値を親コンポーネントに返す様にしていました。",[13,198,199],{},"一択のセレクトボックスであれば以下の様に記述できます。",[42,201,205],{"className":202,"code":203,"language":204,"meta":47,"style":47},"language-vue shiki shiki-themes material-theme-ocean","\u003Ctemplate>\n \u003Cselect name=\"postName\" @input=\"$emit('newInput', $event.target.value)\">\n   \u003Coption v-for=\"val in selectBox\"\n           :value=\"val.value\" \n           :selected=\"inputValue === val.value\">\n           {{val.text}}\n   \u003C\u002Foption>\n \u003C\u002Fselect>\n\u003C\u002Ftemplate>\n\n\u003Cscript>\nexport default{\n name:'childSelect',\n props:['inputed'],  \u002F\u002F親コンポーネントから初期値のvalueを受け取るためのprops\n data(){\n   return{\n     selector:[\n          {text:'選択肢1',value:'1'},\n          {text:'選択肢2',value:'2'},\n          {text:'選択肢3',value:'3'}\n     ],\n   }\n },\n \n}\n\u003C\u002Fscript>\n","vue",[49,206,207,216,247,267,284,300,306,316,326,335,342,352,365,385,413,425,433,444,478,507,537,545,551,557,562,567],{"__ignoreMap":47},[52,208,209,211,214],{"class":54,"line":55},[52,210,59],{"class":58},[52,212,213],{"class":62},"template",[52,215,80],{"class":58},[52,217,218,220,222,224,226,228,231,233,236,238,240,243,245],{"class":54,"line":83},[52,219,86],{"class":58},[52,221,24],{"class":62},[52,223,66],{"class":65},[52,225,69],{"class":58},[52,227,72],{"class":58},[52,229,230],{"class":75},"postName",[52,232,72],{"class":58},[52,234,235],{"class":65}," @input",[52,237,69],{"class":58},[52,239,72],{"class":58},[52,241,242],{"class":75},"$emit('newInput', $event.target.value)",[52,244,72],{"class":58},[52,246,80],{"class":58},[52,248,249,252,254,257,259,261,264],{"class":54,"line":115},[52,250,251],{"class":58},"   \u003C",[52,253,29],{"class":62},[52,255,256],{"class":65}," v-for",[52,258,69],{"class":58},[52,260,72],{"class":58},[52,262,263],{"class":75},"val in selectBox",[52,265,266],{"class":58},"\"\n",[52,268,269,272,274,276,279,281],{"class":54,"line":142},[52,270,271],{"class":65},"           :value",[52,273,69],{"class":58},[52,275,72],{"class":58},[52,277,278],{"class":75},"val.value",[52,280,72],{"class":58},[52,282,283],{"class":58}," \n",[52,285,286,289,291,293,296,298],{"class":54,"line":169},[52,287,288],{"class":65},"           :selected",[52,290,69],{"class":58},[52,292,72],{"class":58},[52,294,295],{"class":75},"inputValue === val.value",[52,297,72],{"class":58},[52,299,80],{"class":58},[52,301,303],{"class":54,"line":302},6,[52,304,305],{"class":105},"           {{val.text}}\n",[52,307,309,312,314],{"class":54,"line":308},7,[52,310,311],{"class":58},"   \u003C\u002F",[52,313,29],{"class":62},[52,315,80],{"class":58},[52,317,319,322,324],{"class":54,"line":318},8,[52,320,321],{"class":58}," \u003C\u002F",[52,323,24],{"class":62},[52,325,80],{"class":58},[52,327,329,331,333],{"class":54,"line":328},9,[52,330,108],{"class":58},[52,332,213],{"class":62},[52,334,80],{"class":58},[52,336,338],{"class":54,"line":337},10,[52,339,341],{"emptyLinePlaceholder":340},true,"\n",[52,343,345,347,350],{"class":54,"line":344},11,[52,346,59],{"class":58},[52,348,349],{"class":62},"script",[52,351,80],{"class":58},[52,353,355,358,362],{"class":54,"line":354},12,[52,356,357],{"class":65},"export",[52,359,361],{"class":360},"s6cf3"," default",[52,363,364],{"class":58},"{\n",[52,366,368,371,374,377,380,382],{"class":54,"line":367},13,[52,369,66],{"class":370},"s5Dmg",[52,372,373],{"class":58},":",[52,375,376],{"class":58},"'",[52,378,379],{"class":75},"childSelect",[52,381,376],{"class":58},[52,383,384],{"class":58},",\n",[52,386,388,391,393,396,398,401,403,406,409],{"class":54,"line":387},14,[52,389,390],{"class":370}," props",[52,392,373],{"class":58},[52,394,395],{"class":62},"[",[52,397,376],{"class":58},[52,399,400],{"class":75},"inputed",[52,402,376],{"class":58},[52,404,405],{"class":62},"]",[52,407,408],{"class":58},",",[52,410,412],{"class":411},"sC9rS","  \u002F\u002F親コンポーネントから初期値のvalueを受け取るためのprops\n",[52,414,416,420,423],{"class":54,"line":415},15,[52,417,419],{"class":418},"sdLwU"," data",[52,421,422],{"class":62},"()",[52,424,364],{"class":58},[52,426,428,431],{"class":54,"line":427},16,[52,429,430],{"class":360},"   return",[52,432,364],{"class":58},[52,434,436,439,441],{"class":54,"line":435},17,[52,437,438],{"class":62},"     selector",[52,440,373],{"class":58},[52,442,443],{"class":62},"[\n",[52,445,447,450,453,455,457,460,462,464,467,469,471,473,475],{"class":54,"line":446},18,[52,448,449],{"class":58},"          {",[52,451,452],{"class":62},"text",[52,454,373],{"class":58},[52,456,376],{"class":58},[52,458,459],{"class":75},"選択肢1",[52,461,376],{"class":58},[52,463,408],{"class":58},[52,465,466],{"class":62},"value",[52,468,373],{"class":58},[52,470,376],{"class":58},[52,472,31],{"class":75},[52,474,376],{"class":58},[52,476,477],{"class":58},"},\n",[52,479,481,483,485,487,489,491,493,495,497,499,501,503,505],{"class":54,"line":480},19,[52,482,449],{"class":58},[52,484,452],{"class":62},[52,486,373],{"class":58},[52,488,376],{"class":58},[52,490,36],{"class":75},[52,492,376],{"class":58},[52,494,408],{"class":58},[52,496,466],{"class":62},[52,498,373],{"class":58},[52,500,376],{"class":58},[52,502,35],{"class":75},[52,504,376],{"class":58},[52,506,477],{"class":58},[52,508,510,512,514,516,518,520,522,524,526,528,530,532,534],{"class":54,"line":509},20,[52,511,449],{"class":58},[52,513,452],{"class":62},[52,515,373],{"class":58},[52,517,376],{"class":58},[52,519,40],{"class":75},[52,521,376],{"class":58},[52,523,408],{"class":58},[52,525,466],{"class":62},[52,527,373],{"class":58},[52,529,376],{"class":58},[52,531,39],{"class":75},[52,533,376],{"class":58},[52,535,536],{"class":58},"}\n",[52,538,540,543],{"class":54,"line":539},21,[52,541,542],{"class":62},"     ]",[52,544,384],{"class":58},[52,546,548],{"class":54,"line":547},22,[52,549,550],{"class":58},"   }\n",[52,552,554],{"class":54,"line":553},23,[52,555,556],{"class":58}," },\n",[52,558,560],{"class":54,"line":559},24,[52,561,283],{"class":62},[52,563,565],{"class":54,"line":564},25,[52,566,536],{"class":58},[52,568,570,572,574],{"class":54,"line":569},26,[52,571,108],{"class":58},[52,573,349],{"class":62},[52,575,80],{"class":58},[13,577,578,579,582],{},"この様にすることで親コンポーネントでは",[49,580,581],{},"newInput","を用いてこのセレクトボックスからの値を受け取ることができます。",[13,584,585,586,589],{},"もし編集画面など、セレクトボックスに予め選択済みの設定を行う場合はpropsにvalueの値を入れておけば、一致する選択肢に",[49,587,588],{},"selected","が付与されます。",[17,591,593],{"id":592},"multipuleで複数選択を可能にする","Multipuleで複数選択を可能にする",[13,595,596],{},"チェックボックスにしては値が多く、リストの方が見やすいのでセレクトボックスから複数選択ができる様にするためには、multipleという属性を付与するだけで実装できます。",[24,598,27,599,27,601,27,603],{"name":26,"multiple":340},[29,600,32],{"value":31},[29,602,36],{"value":35},[29,604,40],{"value":39},[42,606,608],{"className":44,"code":607,"language":46,"meta":47,"style":47},"\u003Cselect name=\"sample\" multiple>\n \u003Coption value=\"1\">選択肢１\u003C\u002Foption>\n \u003Coption value=\"2\">選択肢2\u003C\u002Foption>\n \u003Coption value=\"3\">選択肢3\u003C\u002Foption>\n\u003C\u002Fselect>\n",[49,609,610,631,657,683,709],{"__ignoreMap":47},[52,611,612,614,616,618,620,622,624,626,629],{"class":54,"line":55},[52,613,59],{"class":58},[52,615,24],{"class":62},[52,617,66],{"class":65},[52,619,69],{"class":58},[52,621,72],{"class":58},[52,623,26],{"class":75},[52,625,72],{"class":58},[52,627,628],{"class":65}," multiple",[52,630,80],{"class":58},[52,632,633,635,637,639,641,643,645,647,649,651,653,655],{"class":54,"line":83},[52,634,86],{"class":58},[52,636,29],{"class":62},[52,638,91],{"class":65},[52,640,69],{"class":58},[52,642,72],{"class":58},[52,644,31],{"class":75},[52,646,72],{"class":58},[52,648,102],{"class":58},[52,650,32],{"class":105},[52,652,108],{"class":58},[52,654,29],{"class":62},[52,656,80],{"class":58},[52,658,659,661,663,665,667,669,671,673,675,677,679,681],{"class":54,"line":115},[52,660,86],{"class":58},[52,662,29],{"class":62},[52,664,91],{"class":65},[52,666,69],{"class":58},[52,668,72],{"class":58},[52,670,35],{"class":75},[52,672,72],{"class":58},[52,674,102],{"class":58},[52,676,36],{"class":105},[52,678,108],{"class":58},[52,680,29],{"class":62},[52,682,80],{"class":58},[52,684,685,687,689,691,693,695,697,699,701,703,705,707],{"class":54,"line":142},[52,686,86],{"class":58},[52,688,29],{"class":62},[52,690,91],{"class":65},[52,692,69],{"class":58},[52,694,72],{"class":58},[52,696,39],{"class":75},[52,698,72],{"class":58},[52,700,102],{"class":58},[52,702,40],{"class":105},[52,704,108],{"class":58},[52,706,29],{"class":62},[52,708,80],{"class":58},[52,710,711,713,715],{"class":54,"line":169},[52,712,108],{"class":58},[52,714,24],{"class":62},[52,716,80],{"class":58},[13,718,719,720,723,724,727],{},"実際に",[49,721,722],{},"$event.target.value","を",[49,725,726],{},"console.log()","で出力して見てみると、選択肢１をクリックしてから他の選択肢も含めましたが帰って来た結果は全て「１」でした。",[729,730],"image-render",{":src":731,":width":732},"'_mix\u002Fvue-select03-768x197.png'","'100%'",[13,734,735],{},"格納する親コンポーネントのdataもこの様に１から変化ありませんでした。",[729,737],{":src":738,":width":739},"'_mix\u002Fvue-select04.png'","'400px'",[17,741,743],{"id":742},"multipleの場合はeventtargetoptions","multipleの場合は$event.target.options",[13,745,746],{},"選択された値は全て配列で帰ってくる様に調整をします。inputされた時、以下の様に値を取得する様に変更します。",[42,748,750],{"className":202,"code":749,"language":204,"meta":47,"style":47},"\u003Ctemplate>\n \u003Cselect name=\"postName\" @input=\"returnValeu($event)\" multiple>\n   \u003Coption v-for=\"val in selectBox\"\n           :value=\"val.value\" \n           :selected=\"inputValue === val.value\">\n           {{val.text}}\n   \u003C\u002Foption>\n \u003C\u002Fselect>\n\u003C\u002Ftemplate>\n\n\u003Cscript>\nexport default{\n name:'childSelect',\n props:['inputed'],  \u002F\u002F親コンポーネントから初期値のvalueを受け取るためのprops\n methods:{\n  returnValeu($event){\n    let opt = Object.values($event.target.options).filter(ele=>{\n                  return ele.selected;\n              })\n    let values = opt.map(ele=>ele.value);\n    this.$emit('newInput', values);\n  }\n }\n data(){\n   return{\n     selector:[\n          {text:'選択肢1',value:'1'},\n          {text:'選択肢2',value:'2'},\n          {text:'選択肢3',value:'3'}\n     ],\n   }\n },\n \n}\n\u003C\u002Fscript>\n",[49,751,752,760,791,807,821,835,839,847,855,863,867,875,883,897,917,925,941,993,1008,1016,1048,1072,1077,1082,1090,1096,1104,1133,1162,1191,1198,1203,1208,1213,1218],{"__ignoreMap":47},[52,753,754,756,758],{"class":54,"line":55},[52,755,59],{"class":58},[52,757,213],{"class":62},[52,759,80],{"class":58},[52,761,762,764,766,768,770,772,774,776,778,780,782,785,787,789],{"class":54,"line":83},[52,763,86],{"class":58},[52,765,24],{"class":62},[52,767,66],{"class":65},[52,769,69],{"class":58},[52,771,72],{"class":58},[52,773,230],{"class":75},[52,775,72],{"class":58},[52,777,235],{"class":65},[52,779,69],{"class":58},[52,781,72],{"class":58},[52,783,784],{"class":75},"returnValeu($event)",[52,786,72],{"class":58},[52,788,628],{"class":65},[52,790,80],{"class":58},[52,792,793,795,797,799,801,803,805],{"class":54,"line":115},[52,794,251],{"class":58},[52,796,29],{"class":62},[52,798,256],{"class":65},[52,800,69],{"class":58},[52,802,72],{"class":58},[52,804,263],{"class":75},[52,806,266],{"class":58},[52,808,809,811,813,815,817,819],{"class":54,"line":142},[52,810,271],{"class":65},[52,812,69],{"class":58},[52,814,72],{"class":58},[52,816,278],{"class":75},[52,818,72],{"class":58},[52,820,283],{"class":58},[52,822,823,825,827,829,831,833],{"class":54,"line":169},[52,824,288],{"class":65},[52,826,69],{"class":58},[52,828,72],{"class":58},[52,830,295],{"class":75},[52,832,72],{"class":58},[52,834,80],{"class":58},[52,836,837],{"class":54,"line":302},[52,838,305],{"class":105},[52,840,841,843,845],{"class":54,"line":308},[52,842,311],{"class":58},[52,844,29],{"class":62},[52,846,80],{"class":58},[52,848,849,851,853],{"class":54,"line":318},[52,850,321],{"class":58},[52,852,24],{"class":62},[52,854,80],{"class":58},[52,856,857,859,861],{"class":54,"line":328},[52,858,108],{"class":58},[52,860,213],{"class":62},[52,862,80],{"class":58},[52,864,865],{"class":54,"line":337},[52,866,341],{"emptyLinePlaceholder":340},[52,868,869,871,873],{"class":54,"line":344},[52,870,59],{"class":58},[52,872,349],{"class":62},[52,874,80],{"class":58},[52,876,877,879,881],{"class":54,"line":354},[52,878,357],{"class":65},[52,880,361],{"class":360},[52,882,364],{"class":58},[52,884,885,887,889,891,893,895],{"class":54,"line":367},[52,886,66],{"class":370},[52,888,373],{"class":58},[52,890,376],{"class":58},[52,892,379],{"class":75},[52,894,376],{"class":58},[52,896,384],{"class":58},[52,898,899,901,903,905,907,909,911,913,915],{"class":54,"line":387},[52,900,390],{"class":370},[52,902,373],{"class":58},[52,904,395],{"class":62},[52,906,376],{"class":58},[52,908,400],{"class":75},[52,910,376],{"class":58},[52,912,405],{"class":62},[52,914,408],{"class":58},[52,916,412],{"class":411},[52,918,919,922],{"class":54,"line":415},[52,920,921],{"class":370}," methods",[52,923,924],{"class":58},":{\n",[52,926,927,930,933,936,939],{"class":54,"line":427},[52,928,929],{"class":418},"  returnValeu",[52,931,932],{"class":62},"(",[52,934,935],{"class":105},"$event",[52,937,938],{"class":62},")",[52,940,364],{"class":58},[52,942,943,946,949,952,955,958,961,963,965,967,970,972,975,977,979,982,984,988,991],{"class":54,"line":435},[52,944,945],{"class":65},"    let",[52,947,948],{"class":105}," opt",[52,950,951],{"class":58}," =",[52,953,954],{"class":105}," Object",[52,956,957],{"class":58},".",[52,959,960],{"class":418},"values",[52,962,932],{"class":62},[52,964,935],{"class":105},[52,966,957],{"class":58},[52,968,969],{"class":105},"target",[52,971,957],{"class":58},[52,973,974],{"class":105},"options",[52,976,938],{"class":62},[52,978,957],{"class":58},[52,980,981],{"class":418},"filter",[52,983,932],{"class":62},[52,985,987],{"class":986},"s7ZW3","ele",[52,989,990],{"class":65},"=>",[52,992,364],{"class":58},[52,994,995,998,1001,1003,1005],{"class":54,"line":446},[52,996,997],{"class":360},"                  return",[52,999,1000],{"class":105}," ele",[52,1002,957],{"class":58},[52,1004,588],{"class":105},[52,1006,1007],{"class":58},";\n",[52,1009,1010,1013],{"class":54,"line":480},[52,1011,1012],{"class":58},"              }",[52,1014,1015],{"class":62},")\n",[52,1017,1018,1020,1023,1025,1027,1029,1032,1034,1036,1038,1040,1042,1044,1046],{"class":54,"line":509},[52,1019,945],{"class":65},[52,1021,1022],{"class":105}," values",[52,1024,951],{"class":58},[52,1026,948],{"class":105},[52,1028,957],{"class":58},[52,1030,1031],{"class":418},"map",[52,1033,932],{"class":62},[52,1035,987],{"class":986},[52,1037,990],{"class":65},[52,1039,987],{"class":105},[52,1041,957],{"class":58},[52,1043,466],{"class":105},[52,1045,938],{"class":62},[52,1047,1007],{"class":58},[52,1049,1050,1053,1056,1058,1060,1062,1064,1066,1068,1070],{"class":54,"line":539},[52,1051,1052],{"class":58},"    this.",[52,1054,1055],{"class":418},"$emit",[52,1057,932],{"class":62},[52,1059,376],{"class":58},[52,1061,581],{"class":75},[52,1063,376],{"class":58},[52,1065,408],{"class":58},[52,1067,1022],{"class":105},[52,1069,938],{"class":62},[52,1071,1007],{"class":58},[52,1073,1074],{"class":54,"line":547},[52,1075,1076],{"class":58},"  }\n",[52,1078,1079],{"class":54,"line":553},[52,1080,1081],{"class":58}," }\n",[52,1083,1084,1086,1088],{"class":54,"line":559},[52,1085,419],{"class":418},[52,1087,422],{"class":62},[52,1089,364],{"class":58},[52,1091,1092,1094],{"class":54,"line":564},[52,1093,430],{"class":360},[52,1095,364],{"class":58},[52,1097,1098,1100,1102],{"class":54,"line":569},[52,1099,438],{"class":62},[52,1101,373],{"class":58},[52,1103,443],{"class":62},[52,1105,1107,1109,1111,1113,1115,1117,1119,1121,1123,1125,1127,1129,1131],{"class":54,"line":1106},27,[52,1108,449],{"class":58},[52,1110,452],{"class":62},[52,1112,373],{"class":58},[52,1114,376],{"class":58},[52,1116,459],{"class":75},[52,1118,376],{"class":58},[52,1120,408],{"class":58},[52,1122,466],{"class":62},[52,1124,373],{"class":58},[52,1126,376],{"class":58},[52,1128,31],{"class":75},[52,1130,376],{"class":58},[52,1132,477],{"class":58},[52,1134,1136,1138,1140,1142,1144,1146,1148,1150,1152,1154,1156,1158,1160],{"class":54,"line":1135},28,[52,1137,449],{"class":58},[52,1139,452],{"class":62},[52,1141,373],{"class":58},[52,1143,376],{"class":58},[52,1145,36],{"class":75},[52,1147,376],{"class":58},[52,1149,408],{"class":58},[52,1151,466],{"class":62},[52,1153,373],{"class":58},[52,1155,376],{"class":58},[52,1157,35],{"class":75},[52,1159,376],{"class":58},[52,1161,477],{"class":58},[52,1163,1165,1167,1169,1171,1173,1175,1177,1179,1181,1183,1185,1187,1189],{"class":54,"line":1164},29,[52,1166,449],{"class":58},[52,1168,452],{"class":62},[52,1170,373],{"class":58},[52,1172,376],{"class":58},[52,1174,40],{"class":75},[52,1176,376],{"class":58},[52,1178,408],{"class":58},[52,1180,466],{"class":62},[52,1182,373],{"class":58},[52,1184,376],{"class":58},[52,1186,39],{"class":75},[52,1188,376],{"class":58},[52,1190,536],{"class":58},[52,1192,1194,1196],{"class":54,"line":1193},30,[52,1195,542],{"class":62},[52,1197,384],{"class":58},[52,1199,1201],{"class":54,"line":1200},31,[52,1202,550],{"class":58},[52,1204,1206],{"class":54,"line":1205},32,[52,1207,556],{"class":58},[52,1209,1211],{"class":54,"line":1210},33,[52,1212,283],{"class":62},[52,1214,1216],{"class":54,"line":1215},34,[52,1217,536],{"class":58},[52,1219,1221,1223,1225],{"class":54,"line":1220},35,[52,1222,108],{"class":58},[52,1224,349],{"class":62},[52,1226,80],{"class":58},[13,1228,1229,1230,1232],{},"処理が少し長めなのでmethodに切り分けました。",[49,1231,784],{},"の中身で、イベント（選択）が発生したターゲットの要素を捉えるまでは同じですが、その中にvalueではなくてoptionsというプロパティがあります。",[13,1234,1235,1238],{},[49,1236,1237],{},"$event.target.options","は配列ですがHTMLCollectionなのでObject.valuesをもちいて、filter()が使用できる様にしています。",[13,1240,1241,1243,1244,1247],{},[49,1242,1237],{},"には選択肢の",[49,1245,1246],{},"\u003Coption>\u003C\u002Foption>","部分に関する情報が入っており、選択されたか（selected）とその値（value）というプロパティがあります。",[42,1249,1253],{"className":1250,"code":1251,"language":1252,"meta":47,"style":47},"language-javascript shiki shiki-themes material-theme-ocean","let opt = Object.values($event.target.options).filter(ele=>{\n　　return ele.selected;\n})\n","javascript",[49,1254,1255,1295,1308],{"__ignoreMap":47},[52,1256,1257,1260,1263,1265,1267,1269,1271,1274,1276,1278,1280,1283,1285,1287,1289,1291,1293],{"class":54,"line":55},[52,1258,1259],{"class":65},"let",[52,1261,1262],{"class":105}," opt ",[52,1264,69],{"class":58},[52,1266,954],{"class":105},[52,1268,957],{"class":58},[52,1270,960],{"class":418},[52,1272,1273],{"class":105},"($event",[52,1275,957],{"class":58},[52,1277,969],{"class":105},[52,1279,957],{"class":58},[52,1281,1282],{"class":105},"options)",[52,1284,957],{"class":58},[52,1286,981],{"class":418},[52,1288,932],{"class":105},[52,1290,987],{"class":986},[52,1292,990],{"class":65},[52,1294,364],{"class":58},[52,1296,1297,1300,1302,1304,1306],{"class":54,"line":83},[52,1298,1299],{"class":360},"　　return",[52,1301,1000],{"class":105},[52,1303,957],{"class":58},[52,1305,588],{"class":105},[52,1307,1007],{"class":58},[52,1309,1310,1313],{"class":54,"line":115},[52,1311,1312],{"class":58},"}",[52,1314,1015],{"class":105},[13,1316,1317,1318,1320,1321,1324,1325,1328,1329,1331],{},"ここで",[49,1319,588],{},"プロパティが",[49,1322,1323],{},"true","か",[49,1326,1327],{},"false","で選択されたかがわかるので、",[49,1330,1323],{},"の要素のみ取得して新しい配列を手に入れます。その配列に対して",[42,1333,1335],{"className":1250,"code":1334,"language":1252,"meta":47,"style":47},"let values = opt.map(ele=>ele.value);\nthis.$emit('newInput', values);\n",[49,1336,1337,1367],{"__ignoreMap":47},[52,1338,1339,1341,1344,1346,1348,1350,1352,1354,1356,1358,1360,1362,1365],{"class":54,"line":55},[52,1340,1259],{"class":65},[52,1342,1343],{"class":105}," values ",[52,1345,69],{"class":58},[52,1347,948],{"class":105},[52,1349,957],{"class":58},[52,1351,1031],{"class":418},[52,1353,932],{"class":105},[52,1355,987],{"class":986},[52,1357,990],{"class":65},[52,1359,987],{"class":105},[52,1361,957],{"class":58},[52,1363,1364],{"class":105},"value)",[52,1366,1007],{"class":58},[52,1368,1369,1372,1374,1376,1378,1380,1382,1384,1387],{"class":54,"line":83},[52,1370,1371],{"class":58},"this.",[52,1373,1055],{"class":418},[52,1375,932],{"class":105},[52,1377,376],{"class":58},[52,1379,581],{"class":75},[52,1381,376],{"class":58},[52,1383,408],{"class":58},[52,1385,1386],{"class":105}," values)",[52,1388,1007],{"class":58},[13,1390,1391,1394,1395,1397],{},[49,1392,1393],{},"map()","を用いて",[49,1396,466],{},"プロパティのみを抽出したものを新しい配列として返し、それを親コンポーネントに渡します。これで選択したものを配列として渡すことが可能になります。",[17,1399,1400],{"id":1400},"値を出力してみる",[13,1402,1403],{},"先ほどの様に選択肢１から順に選択して値の変化を見てみましょう。",[729,1405],{":src":1406,":width":739},"'_mix\u002Fvue-select06-768x203-2.png'",[729,1408],{":src":1409,":width":739},"'_mix\u002Fvue-select05-1.png'",[13,1411,1412],{},"取得された値が順番順番に配列で取得できてますね。親コンポーネントにもちゃんと入っています。これでmultipule属性を持ったセレクトボックスの値を$emitすることができます。",[1414,1415,1416],"style",{},"html pre.shiki code .sAklC, html code.shiki .sAklC{--shiki-default:#89DDFF}html pre.shiki code .s-wAU, html code.shiki .s-wAU{--shiki-default:#F07178}html pre.shiki code .sJ14y, html code.shiki .sJ14y{--shiki-default:#C792EA}html pre.shiki code .sfyAc, html code.shiki .sfyAc{--shiki-default:#C3E88D}html pre.shiki code .s0W1g, html code.shiki .s0W1g{--shiki-default:#BABED8}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html pre.shiki code .s6cf3, html code.shiki .s6cf3{--shiki-default:#89DDFF;--shiki-default-font-style:italic}html pre.shiki code .s5Dmg, html code.shiki .s5Dmg{--shiki-default:#FFCB6B}html pre.shiki code .sC9rS, html code.shiki .sC9rS{--shiki-default:#464B5D;--shiki-default-font-style:italic}html pre.shiki code .sdLwU, html code.shiki .sdLwU{--shiki-default:#82AAFF}html pre.shiki code .s7ZW3, html code.shiki .s7ZW3{--shiki-default:#BABED8;--shiki-default-font-style:italic}",{"title":47,"searchDepth":115,"depth":115,"links":1418},[1419,1420,1421,1422],{"id":19,"depth":83,"text":19},{"id":592,"depth":83,"text":593},{"id":742,"depth":83,"text":743},{"id":1400,"depth":83,"text":1400},[1424],"devstack","2026-04-15","multiple属性での複数選択セレクトボックスで選択した値をemitする方法","md",null,{},"\u002Farticles\u002Fmultiple-select-vue",{"title":8,"description":1426},"articles\u002Fmultiple-select-vue",[1434,204],"js","_mix\u002Fvue-select06-768x203.png","VP5RFAbgJFA3otr_esQFmbFyOCNxilN184p7o6oAQZo",{"id":1438,"title":1439,"body":1440,"category":2015,"createdAt":2016,"description":1439,"extension":1427,"index":1428,"meta":2017,"navigation":340,"path":2018,"publish":340,"seo":2019,"series":1428,"seriesTitle":1428,"stem":2020,"tag":2021,"thumbnail":1428,"updatedAt":2016,"__hash__":2024},"articles\u002Farticles\u002Fconnect-with-scp-ssh.md","FTPクライアントでなくターミナルでsshやscpによる通信を行う方法",{"type":10,"value":1441,"toc":1978},[1442,1445,1448,1451,1454,1457,1460,1466,1476,1481,1492,1495,1498,1503,1513,1522,1526,1529,1532,1535,1538,1541,1544,1548,1551,1554,1562,1565,1568,1571,1574,1576,1579,1582,1585,1588,1591,1594,1597,1600,1603,1606,1609,1612,1619,1625,1628,1634,1641,1644,1647,1653,1656,1659,1665,1668,1671,1677,1680,1683,1686,1698,1701,1704,1707,1710,1716,1719,1725,1728,1734,1737,1741,1744,1747,1750,1753,1759,1762,1765,1771,1778,1784,1787,1791,1794,1800,1803,1806,1809,1812,1818,1821,1825,1828,1831,1837,1840,1846,1849,1855,1858,1861,1864,1867,1870,1876,1879,1885,1888,1892,1898,1902,1908,1912,1915,1921,1927,1930,1933,1939,1942,1945,1951,1957,1960,1963,1969,1972,1975],[13,1443,1444],{},"こんにちはjunです。あなたはサーバー上にファイルをアップロードする際にどうやっていますか？大体がFTPクライアント（windowsだとwinscp、MacだとFileZillaなど）を使用していると思います。",[13,1446,1447],{},"GUIで操作できるソフトは扱いやすく、簡単にアップロード・ダウンロードを行ったり、複数ファイルを移動する際などは便利です。しかしある程度エンジニアレベルが上がると、サーバー構築やサーバー上での操作、例えば権限のいるファイルの編集とか機械的なファイル操作が必要になってくるので、FTPクライアントでの操作では限界が生じます。",[13,1449,1450],{},"特に大量のファイルを扱う際にはGUIクライアントソフトを用いると時間がかかったりすることも多かったり、権限によってファイルの操作ができない場合はsudoを打てる環境を用意しないといけません。",[13,1452,1453],{},"今回の記事ではSSHでサーバーとのやりとりをするための設定、そしてローカルとリモート間のscpを用いたファイル転送などGUIからCLI操作へのステップアップできるように解説していきたいと思います",[17,1455,1456],{"id":1456},"前提条件",[13,1458,1459],{},"細かい説明に入る前に以下の条件での説明とします。",[13,1461,1462],{},[1463,1464,1465],"strong",{},"クライアント",[1467,1468,1469,1473],"ul",{},[1470,1471,1472],"li",{},"macOS Catalina 10.15.5",[1470,1474,1475],{},"ターミナルを使用",[13,1477,1478],{},[1463,1479,1480],{},"サーバー（GMO VPSとします）",[1467,1482,1483,1486,1489],{},[1470,1484,1485],{},"centOS7",[1470,1487,1488],{},"22番ポート開放済み",[1470,1490,1491],{},"sshdインストール済み",[17,1493,1494],{"id":1494},"用語や基礎知識の確認",[13,1496,1497],{},"まず以降の説明を行う前に基礎的な用語と知識の解説を行います。知っている人はフンフンと頷きながら飛ばしてください。",[1499,1500,1502],"h3",{"id":1501},"ssh","SSH",[13,1504,1505,1508,1509,1512],{},[1463,1506,1507],{},"S","ecure ",[1463,1510,1511],{},"Sh","ellのこと。直訳すると「安全なシェル」。シェルというのはターミナルとかコマンドプロンプトのことだと思ってください。そのシェルとネットワークを用いて遠隔にあるサーバーを操作します。昔はTelnetというものを用いてシェルで遠隔操作していましたが、Telnetで送受信するデータが平文という脆弱性がありました。",[13,1514,1515,1516,1518,1519,1521],{},"それに対する形で通信内容が暗号化される様になっているのがSSH。だから",[1463,1517,1507],{},"ecure な ",[1463,1520,1511],{},"ellと言われる。HttpにHttpsがついた様なニュアンス。",[1499,1523,1525],{"id":1524},"ipアドレス","IPアドレス",[13,1527,1528],{},"ネットワーク通信を行うえで必要な、各ネットワーク機器を一意に区別する数字のこと。マイナンバーとか車のナンバーみたいなものです。IPを用いることで接続するサーバーを指定することができます。SSHでは接続先のIPを知る必要があります。",[1499,1530,1531],{"id":1531},"ユーザー",[13,1533,1534],{},"サーバー上で操作するための名前というか、操作者の名前です。ユーザーを複数作り権限を割り振ることでサーバー上の操作を制限することができます。個々の名前にしておけば、誰が何を実行したのかも分かります。SSHでは「どのユーザー（権限を持った人）で実行するか？」が大切になります。",[13,1536,1537],{},"linuxでは「root」という名前のユーザーが最初にいます。このrootは他人のパスワードも変えられるし、サーバー上全てのファイルを消すことなんて可能な、なんでもできる「スーパーユーザー」です。",[1499,1539,1540],{"id":1540},"グループ",[13,1542,1543],{},"ユーザーをくくる為の「組」のこと。そのままの意味です。同じグループ内であれば所有者が異なるファイルであっても操作ができます。（そのような権限が付与されている場合）",[1499,1545,1547],{"id":1546},"ssh認証方式","SSH認証方式",[13,1549,1550],{},"SSHはリモートでサーバーに接続しますが、サーバーからしてみれば「どこのよく分からないIP（あなたのPC）から自分をいじってもいいか？」と聞かれているので必ず認証をします。つまりこのサーバーをいじっても良い正規の人間かをチェックします。",[13,1552,1553],{},"その認証方式としてSSHでは主に",[1467,1555,1556,1559],{},[1470,1557,1558],{},"パスワード認証方式",[1470,1560,1561],{},"鍵交換方式",[13,1563,1564],{},"の２つがあります。",[1566,1567,1558],"h4",{"id":1558},[13,1569,1570],{},"接続する際のユーザー名と、そのユーザーに紐づいたパスワードで接続することができます。Webサービスでログインがあるものと同じですね。分かりやすいですが、接続の度にパスワードを求められてちょっと面倒です。",[13,1572,1573],{},"また総当たり攻撃で突破される可能性もあるので、運用とセキュリティ面では次の鍵交換がおすすめです。",[1566,1575,1561],{"id":1561},[13,1577,1578],{},"秘密鍵と公開鍵というものを用いて認証を行います。鍵交換による認証原理の説明は今回は省きます。秘密鍵をクライアント（ローカル）において、公開鍵をサーバーにおいておきます。",[13,1580,1581],{},"ユーザーに紐づいた秘密鍵・公開鍵で認証を行います。秘密鍵は後述する通りにコマンドのオプションで指定できるので自動化やSSHの簡略化の際に便利になります。また秘密鍵のファイルが外部に漏れなければパスワード方式より安全です。",[13,1583,1584],{},"今回の解説では鍵交換方式でのSSH実行方法の設定も行います。",[17,1586,1587],{"id":1587},"接続準備",[13,1589,1590],{},"今回は自分でVPSを借りて接続をするという状況として操作をします。ConohaやGMOではブラウザから最初にサーバーの設定を行います。",[13,1592,1593],{},"OSやrootユーザーのパスワードを設定してまず、サーバーが作られます。IPアドレスなども管理画面などに表示されていると思いますので控えておきます。",[13,1595,1596],{},"IPアドレスは「123.456.789.012」の様なピリオド４つで分けられた数字列のことです。",[729,1598],{":src":1599,":width":732},"'_mix\u002Fconoha-768x714.png'",[729,1601],{":src":1602,":width":732},"'_mix\u002Fxserver-768x432.png'",[1499,1604,1605],{"id":1605},"接続自体は簡単",[13,1607,1608],{},"IPアドレスとSSHユーザー名とパスワードがわかればあとは簡単です。とりあえず今回はVPSを借りているという想定なので、そのVPSのIPとrootユーザーパスワードがわかっているとします。",[13,1610,1611],{},"macならばターミナルを開きます。macにはsshを行うsshdが入っているので以下のコマンドを唱えます。",[42,1613,1617],{"className":1614,"code":1616,"language":452},[1615],"language-text","~ % ssh root@IP_ADRESS\n",[49,1618,1616],{"__ignoreMap":47},[13,1620,1621,1624],{},[49,1622,1623],{},"ssh ユーザー名＠IPアドレス","を入力します。これは「指定したIPアドレスにrootというユーザー名でssh接続します！」という意味です。特にオプションを指定せず、パスワード認証方式が許可されている場合はパスワードを聞いてくるので入力します。",[13,1626,1627],{},"正しくパスワードを入力すると次の様な文章が出てきます。",[42,1629,1632],{"className":1630,"code":1631,"language":452},[1615],"The authenticity of host '*****************' can't be established.\nRSA key fingerprint is *****************.\nAre you sure you want to continue connecting (yes\u002Fno)? \n>yes #問題なければ\n",[49,1633,1631],{"__ignoreMap":47},[13,1635,1636,1637,1640],{},"これはSSHキーフィンガースプリントと言われ日本語では「鍵指紋」と言われています。簡単に説明すると指定したIPで接続しても「本当に自分が繋ごうとしてるサーバーであっていますか？」と尋ねてくる。基本的にVPSなどは信頼できるので",[49,1638,1639],{},"yes","を選択。",[13,1642,1643],{},"この時yesを押すとクライアント側にサーバーから送られた公開鍵のハッシュ が保存されます。次回以降の接続ではこの公開鍵のハッシュが一緒に認証に使用されます。なぜ一緒に使われるのかというと、サーバーのなりすまし防止です。",[13,1645,1646],{},"IPなどの情報に加えてさらにsshdサーバーを区別するためにこの情報を保存して、接続先を担保します。もし次回に保存した公開鍵とサーバーから提示された公開鍵ハッシュ が異なると以下の様な警告が出ます。",[42,1648,1651],{"className":1649,"code":1650,"language":452},[1615],"@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@\n@    WARNING: REMOTE HOST IDENTIFICATION HAS CHANGED!     @\n@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@\nIT IS POSSIBLE THAT SOMEONE IS DOING SOMETHING NASTY!\n",[49,1652,1650],{"__ignoreMap":47},[17,1654,1655],{"id":1655},"接続の確認",[13,1657,1658],{},"認証が終えるとこれでssh完了です。意外と簡単ですよね。変わったかどうかを確かめるには、とりあえずターミナルの表示を確認してみてください。",[42,1660,1663],{"className":1661,"code":1662,"language":452},[1615],"Last login: Wed Jul 15 18:11:06 2020 from xxxxxxxxxxxxx\n[root@HOST_NAME ~]$\n",[49,1664,1662],{"__ignoreMap":47},[13,1666,1667],{},"この様にログイン時間、そして左側の表記が「ユーザー名＠IPなどのホスト名　カレントディレクトリ名」になっています。この状態であればログインできています。",[13,1669,1670],{},"sshを止める場合には",[42,1672,1675],{"className":1673,"code":1674,"language":452},[1615],"[root@HOST_NAME ~]$ exit\n",[49,1676,1674],{"__ignoreMap":47},[13,1678,1679],{},"と入力するとsshを終了(ログアウト)できます。",[17,1681,1682],{"id":1682},"鍵交換の実装",[13,1684,1685],{},"前述した通りパスワード認証はわかりやすいですが、逐一パスワードを入力したりするのは面倒ですし、自動化ができません。流出や総当たりの危険性があるので出来たら鍵交換方式に変えた方がスムーズです。",[1687,1688,1689,1692,1695],"ol",{},[1470,1690,1691],{},"ここではrootでパスワード認証して初めてssh接続をした。",[1470,1693,1694],{},"このサーバーにはsshの公開鍵も秘密鍵もねえ！",[1470,1696,1697],{},"root用の鍵交換認証を実装",[13,1699,1700],{},"という状況で鍵交換を実装したいと思います。",[1499,1702,1703],{"id":1703},"秘密鍵と公開鍵を作成する",[13,1705,1706],{},"サーバーにログインした状態で秘密鍵と公開鍵を作成します。ユーザーのホーム ディレクトリには「.ssh」という隠しフォルダがあり、その中にssh用の鍵を格納します。",[13,1708,1709],{},"もしホームディレクトリに.sshディレクトリがない場合は作成します。今回は無かったとしましょう。",[42,1711,1714],{"className":1712,"code":1713,"language":452},[1615],"[root@HOST_NAME ~]$ mkdir .ssh\n[root@HOST_NAME ~]$ chmod 700 ~\u002F.ssh \n",[49,1715,1713],{"__ignoreMap":47},[13,1717,1718],{},".sshディレクトリは権限を700にします。でないと鍵による接続ができません。その.sshに移動して以下のコマンドを唱えて両鍵を作成します。今回はパスフレーズ（追加のパスワード）はなしとします。パスフレーズを入れるとパスワード認証の様に逐一入力を求められてしまいます。",[42,1720,1723],{"className":1721,"code":1722,"language":452},[1615],"[root@HOST_NAME .ssh]$ ssh-keygen -t rsa -f id_rsa #-fはファイル名を指定できる。\nEnter passphrase (empty for no passphrase): #←パスフレーズを入力。基本なしでもOK、その際はEnterをおす\nEnter same passphrase again: #←パスフレーズを再入力。基本なしの時はEnterをおす\nYour identification has been saved in \u002Froot\u002F.ssh\u002Fid_rsa.\nYour public key has been saved in \u002Froot\u002F.ssh\u002Fid_rsa.pub.\nThe key fingerprint is:\nxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx root@xxxxx\nThe key's randomart image is:\n+--[ RSA 2048]----+\n|                 |\n|       .         |\n|        o        |\n|oo     . .       |\n|*.. .   S .      |\n|+E o       .     |\n|=.o . .   .      |\n|.+ . o ...       |\n|*+.   ..+o       |\n+-----------------+\n[root@HOST_NAME .ssh]$ ls\n[root@HOST_NAME .ssh]$ id_rsa id_rsa.pub\n",[49,1724,1722],{"__ignoreMap":47},[13,1726,1727],{},"鍵を作成してディレクトリ内をみると２つのファイルが見つかります。pubがついている方が公開鍵で、もう片方が秘密鍵です。次に公開鍵をサーバーに登録し、600権限を割り振ります。以下のコマンドを唱えます。",[42,1729,1732],{"className":1730,"code":1731,"language":452},[1615],"[root@HOST_NAME .ssh]$ cat id_rsa.pub >> authorized_keys\n[root@HOST_NAME .ssh]$ chmod 600 authorized_keys\n[root@HOST_NAME .ssh]$ rm -fv id_rsa.pub #id_rsa.pubは捨てる\n",[49,1733,1731],{"__ignoreMap":47},[13,1735,1736],{},"権限の設定を忘れて接続ができないミスが多いので注意。",[17,1738,1740],{"id":1739},"秘密鍵をクライアント自分のpcへ送信","秘密鍵をクライアント（自分のPC）へ送信",[13,1742,1743],{},"では次に接続元となる自分のPCへサーバーで作成した秘密鍵id_rsaを送信します。.sshフォルダ以外の場所に移動して、クラアントツールでひっぱてもいいですが、せっかくなのでscpコマンドを用いてやってみましょう。",[1566,1745,1746],{"id":1746},"scpコマンドでリモートからコピーする",[13,1748,1749],{},"scpはGUIのソフトでの送受信の正体です。リモートからローカルへのファイルのコピーをします。ファイルをコピーするコマンドにcpがありますが、あれがネットワークを通じている物だと思ってください。",[13,1751,1752],{},"リモートからローカルへコピーする時はローカル側で以下のような入力をします。",[42,1754,1757],{"className":1755,"code":1756,"language":452},[1615],"jun@MacBook-Pro% scp \u003Cユーザー名>@\u003CIPアドレス>:\u003Cファイルまでのパス> \u003Cファイルを置くローカルのパス>\n",[49,1758,1756],{"__ignoreMap":47},[13,1760,1761],{},"scpの後に１つ目に「コピー元のファイルまでのパス」、２つ目に「コピーしたファイルの置き場所」を入力します。注意して欲しいのはscpコマンドはサーバーにログインして唱えるのではないということです。",[13,1763,1764],{},"よくわからないと思うので、同じようにリモートからあるファイルを取得する操作をFileZillaで行うと以下のようにメッセージが表示されます。やっていることはscpと変わりません。",[42,1766,1769],{"className":1767,"code":1768,"language":452},[1615],"状態:     \"\u002Fvar\u002Fwww\u002Fhtml\" のディレクトリ リストの表示成功\n状態:     xxx.xx.xxx.xxx に接続中...\n状態:     Using username \"sample\". \n状態:     Connected to    xxx.xx.xxx.xxx\n状態:     \u002Fvar\u002Fwww\u002Fhtml\u002Findex.php のダウンロードを開始しています\n状態:     42 バイト (1 秒) のファイル転送に成功しました\n",[49,1770,1768],{"__ignoreMap":47},[13,1772,1773,1774,1777],{},"ローカルからリモートへ接続してファイルを取得しているのがわかります。scpによるファイルの送受信は ",[1463,1775,1776],{},"サーバーにログインして行うのではなくて"," 、ローカル側から接続先を指定してファイルの送受信を行います。今回の秘密鍵を持ってくる場合は以下のように打ちます。",[42,1779,1782],{"className":1780,"code":1781,"language":452},[1615],"jun@MacBook-Pro% scp root@IP_ADRESS:~\u002F.ssh\u002Fid_rsa ~\u002F\n",[49,1783,1781],{"__ignoreMap":47},[13,1785,1786],{},"「~\u002F」というのは「ホームディレクトリ」配下という意味です。「rootの ~\u002F.ssh\u002Fid_rsaにあるファイル」を「ローカルのホームディレクトリ 」にコピーしろ！という命令です。",[1499,1788,1790],{"id":1789},"秘密鍵を適当な箇所に置いて権限を付与","秘密鍵を適当な箇所に置いて、権限を付与",[13,1792,1793],{},"秘密鍵は接続の際にパスで参照できればどこに置いてもいいのですが、わかりやすいようにローカルの.sshディレクトリ配下に入れることにします。Macにはホームディレクトリに.sshというssh用の隠しフォルダがあります。以下はローカルでの作業です。",[42,1795,1798],{"className":1796,"code":1797,"language":452},[1615],"jun@MacBook-Pro % cd ~ #ホームディレクトリに移動\njun@MacBook-Pro ~ % mv id_rsa .ssh\u002F && cd .ssh\njun@MacBook-Pro .ssh % ls -a\nconfig  id_rsa  known_hosts\n\njun@MacBook-Pro .ssh % chmod 600 id_rsa\n",[49,1799,1797],{"__ignoreMap":47},[13,1801,1802],{},"ここでも秘密鍵は600権限を付与しておきます。ここも忘れてハマりやすいので注意。ssh接続先が多くなり、鍵が増えたら「keys」などの適当な鍵収納用のフォルダを作るといいです。",[17,1804,1805],{"id":1805},"鍵交換で接続する",[13,1807,1808],{},"以上で鍵交換の実装は完了です。実務的な運用の場合はサーバーでパスワードログインを禁止するなどが必要ですが今回は省略します。ではローカルから鍵を使ってログインしましょう。",[13,1810,1811],{},".ssh配下に鍵があるので、鍵のパスを指定しする様に以下の様に唱えます。",[42,1813,1816],{"className":1814,"code":1815,"language":452},[1615],"jun@MacBook-Pro ~ % ssh -i ~\u002F.ssh\u002Fid_rsa root@IP_ADRESS\n",[49,1817,1815],{"__ignoreMap":47},[13,1819,1820],{},"パスワード式の接続に「-i」オプションを指定すると、「鍵交換で接続」という意味になります。-iの後にはローカルに保存した秘密鍵までのパスを指定します。あとはユーザー名とIPアドレスを入力すれば完了です。",[17,1822,1824],{"id":1823},"パス指定が面倒","パス指定が面倒〜〜",[13,1826,1827],{},"鍵交換で実装を行うとパスワードを入力しなくていいので、メモをみたり自動化の際には便利です。しかしパスの指定が面倒という欠点がありますが、簡単に解決できます。エイリアスを設定してとても簡単に接続できる様になります。",[13,1829,1830],{},"早速設定してみましょう。.sshディレクトリに移動して「config」というファイルを編集します。なければ作成してください。",[42,1832,1835],{"className":1833,"code":1834,"language":452},[1615],"jun@MacBook-Pro ~ % cd ~\u002F.ssh\njun@MacBook-Pro .ssh % vi config\n",[49,1836,1834],{"__ignoreMap":47},[13,1838,1839],{},"このconfigファイルに以下の情報を登録します。",[42,1841,1844],{"className":1842,"code":1843,"language":452},[1615],"Host sshconnect #好きな名前\n HostName IP_ADRESS\n User root\n Port 22\n IdentityFile ~\u002F.ssh\u002Fid_rsa\n",[49,1845,1843],{"__ignoreMap":47},[13,1847,1848],{},"configファイルに上記の様に記述すると以下のコマンドを唱えるだけで、HostNameで指定したサーバーへsshができます。",[42,1850,1853],{"className":1851,"code":1852,"language":452},[1615],"jun@MacBook-Pro ~ % ssh sshconnect\n",[49,1854,1852],{"__ignoreMap":47},[13,1856,1857],{},"とっても短くになりました。「sshconnect」と入力しただけです。さらに鍵交換式なのでパスワードの入力も入りません。ちなみにユーザー名だけだと、パスワードが求められ入力しないといけません。セキュリティの関係上configファイルにはパスワードを記録できません。（FileZillaなどのソフトはソフト自身が記憶している）",[13,1859,1860],{},"完全にストレスフリーにHostのエイリアスを打つだけで接続するには鍵交換式しかできません。",[17,1862,1863],{"id":1863},"scpでファイルを送ってみよう",[13,1865,1866],{},"scpファイルの送受信をまとめておきます。",[1499,1868,1869],{"id":1869},"エイリアスを用いた送受信",[42,1871,1874],{"className":1872,"code":1873,"language":452},[1615],"jun@MacBook-Pro ~ %　scp ~\u002Ftest.txt sshconnect:~\u002F\n",[49,1875,1873],{"__ignoreMap":47},[13,1877,1878],{},"sshの接続先は上記の様にエイリアス化するとリモート先をこんなに簡単に接続できます。エイリアスを使わない場合は以下の様に唱えます。",[42,1880,1883],{"className":1881,"code":1882,"language":452},[1615],"jun@MacBook-Pro ~ %　scp -i ~\u002F.ssh\u002Fid_rsa ~\u002Ftest.txt root@IP_ADRESS:~\u002F\n",[49,1884,1882],{"__ignoreMap":47},[13,1886,1887],{},"以下はエイリアスを用いた鍵交換となっています。",[17,1889,1891],{"id":1890},"ローカルリモート","ローカル→リモート",[42,1893,1896],{"className":1894,"code":1895,"language":452},[1615],"jun@MacBook-Pro ~ %　scp ~\u002Ftest.txt sshconnect:~\u002F\n\n# scp \u003Cローカルのファイルまでのパス>　\u003Cエイリアス>:\u003Cリモートのコピー先のパス>\n",[49,1897,1895],{"__ignoreMap":47},[17,1899,1901],{"id":1900},"リモートローカル","リモート→ローカル",[42,1903,1906],{"className":1904,"code":1905,"language":452},[1615],"jun@MacBook-Pro ~ %　scp sshconnect:~\u002Ftest.txt　~\u002F \n\n# scp \u003Cエイリアス>:\u003Cリモートのファイルまでのパス>　\u003Cローカルのコピー先のパス>\n",[49,1907,1905],{"__ignoreMap":47},[17,1909,1911],{"id":1910},"ディレクトリ-ごと移動ローカルから","ディレクトリ ごと移動（ローカルから）",[13,1913,1914],{},"膨大なファイルを輸送する時に早くて便利です。",[42,1916,1919],{"className":1917,"code":1918,"language":452},[1615],"jun@MacBook-Pro ~ %　scp -r ~\u002Fheavy\u002F sshconnect:~\u002F\n",[49,1920,1918],{"__ignoreMap":47},[13,1922,1923,1926],{},[49,1924,1925],{},"-r","を指定しないと「送信するものが普通のファイルじゃないよ！（not a regular file）」と怒られます。再起的にコピーされるので、ディレクトリ配下全てがコピーされます。",[17,1928,1929],{"id":1929},"リモートからリモート",[13,1931,1932],{},"リモートサーバーその１をsshconnect01、その２をsshconnect02として01から02へ移動する場合です。ローカルを経由する方法と01で02に対するscpを行う方法があります。図示すると以下の様な感じです。",[42,1934,1937],{"className":1935,"code":1936,"language":452},[1615],"ローカル経由\n\n  |-------sshconnect01\n  |↓   ←\nlocal\n  |↓　　→\n  |------sshconnect02\n\nリモートから直接\n  |-------sshconnect01\n  |↑   →       |↓\nlocal          |↓\n               |↓\n          sshconnect02\n",[49,1938,1936],{"__ignoreMap":47},[1566,1940,1941],{"id":1941},"リモートで直接",[13,1943,1944],{},"リモート01から02に直接送る場合はローカルで以下の様に打ちます",[42,1946,1949],{"className":1947,"code":1948,"language":452},[1615],"リモート01から02に直接送る場合はローカルで以下の様に打ちます\n",[49,1950,1948],{"__ignoreMap":47},[13,1952,1953,1954,1956],{},"ディレクトリを写す場合は ",[49,1955,1925],{}," オプションを付けます。ただし、この方法は01は02への接続情報を持っていなければなりません。エリアスの場合も01の.ssh\u002Fconfigに sshconnect02についての記述を書く必要があります。02が鍵認証を行っている場合は01に02の鍵をおいておく必要があります。",[1566,1958,1959],{"id":1959},"ローカル経由",[13,1961,1962],{},"01から02に接続できない場合はローカルを経由する様にします。以下の様に打ちます。",[42,1964,1967],{"className":1965,"code":1966,"language":452},[1615],"jun@MacBook-Pro ~ %　scp -3 sshconnect01:~\u002Ftest.txt　sshconnect02:~\u002F \n",[49,1968,1966],{"__ignoreMap":47},[13,1970,1971],{},"-3 オプションを使用します。ディレクトリコピーならばさらに -r を付けましょう。01からローカルに引っ張ってきてから、02へ送信します。自身の転送量は多くなりますが手軽にできます。",[17,1973,1974],{"id":1974},"あとがき",[13,1976,1977],{},"以上がsshによるリモートログインとscpによるファイル送受信方法です。いつもはGUIソフトで操作しつつ、大量のファイル移動や権限が伴う移動などはコマンドを用いた方が便利です。まずはtest.txtなど影響のないファイルを作って、練習するといいでしょう。",{"title":47,"searchDepth":115,"depth":115,"links":1979},[1980,1981,1991,1994,1995,1998,2002,2003,2004,2007,2008,2009,2010,2014],{"id":1456,"depth":83,"text":1456},{"id":1494,"depth":83,"text":1494,"children":1982},[1983,1984,1985,1986,1987],{"id":1501,"depth":115,"text":1502},{"id":1524,"depth":115,"text":1525},{"id":1531,"depth":115,"text":1531},{"id":1540,"depth":115,"text":1540},{"id":1546,"depth":115,"text":1547,"children":1988},[1989,1990],{"id":1558,"depth":142,"text":1558},{"id":1561,"depth":142,"text":1561},{"id":1587,"depth":83,"text":1587,"children":1992},[1993],{"id":1605,"depth":115,"text":1605},{"id":1655,"depth":83,"text":1655},{"id":1682,"depth":83,"text":1682,"children":1996},[1997],{"id":1703,"depth":115,"text":1703},{"id":1739,"depth":83,"text":1740,"children":1999},[2000,2001],{"id":1746,"depth":142,"text":1746},{"id":1789,"depth":115,"text":1790},{"id":1805,"depth":83,"text":1805},{"id":1823,"depth":83,"text":1824},{"id":1863,"depth":83,"text":1863,"children":2005},[2006],{"id":1869,"depth":115,"text":1869},{"id":1890,"depth":83,"text":1891},{"id":1900,"depth":83,"text":1901},{"id":1910,"depth":83,"text":1911},{"id":1929,"depth":83,"text":1929,"children":2011},[2012,2013],{"id":1941,"depth":142,"text":1941},{"id":1959,"depth":142,"text":1959},{"id":1974,"depth":83,"text":1974},[1424],"2026-03-25",{},"\u002Farticles\u002Fconnect-with-scp-ssh",{"title":1439,"description":1439},"articles\u002Fconnect-with-scp-ssh",[2022,2023],"infrastructure","network","vb0ciWoDxv7wsXMgPxZsPl_2qDz-iNYzIyU8cFy07oc",{"id":2026,"title":2027,"body":2028,"category":6108,"createdAt":6109,"description":2027,"extension":1427,"index":1428,"meta":6110,"navigation":340,"path":6111,"publish":340,"seo":6112,"series":1428,"seriesTitle":1428,"stem":6113,"tag":6114,"thumbnail":6115,"updatedAt":6109,"__hash__":6116},"articles\u002Farticles\u002Fjs-canvas-wave.md","画像下部が波でうねるアニメーションをcanvasで実装する",{"type":10,"value":2029,"toc":6085},[2030,2033,2036,2046,2049,2052,2066,2069,2072,2075,2078,2081,2084,2101,2104,2108,2111,2114,2117,2128,2131,2400,2403,2406,2409,2701,2704,2764,2767,2963,2974,2987,2990,2994,2998,3006,3067,3070,3073,3098,3101,3168,3174,3181,3187,3190,3193,3199,3882,3885,3896,3899,3903,4014,4020,4024,4030,4346,4349,4369,4372,4375,4379,4447,4459,4465,4467,4470,4473,4476,4676,4679,4682,4696,4699,4713,4716,4719,4722,4760,4769,4779,4782,4784,4787,4797,4906,4916,4926,4966,4969,4972,5984,5987,5990,6059,6076,6079,6082],[13,2031,2032],{},"こんにちはjunです。今日は以下のようなcanvasを用いたアニメーションを作っていきます。\nレスポンシブをとりあえず画像の一部をうねらせるだけであれば70行ほどのjsコードですみます。",[729,2034],{":src":2035,":width":732},"'_mix\u002Fex01-768x299.png'",[13,2037,2038,2045],{},[2039,2040,2044],"a",{"href":2041,"rel":2042},"https:\u002F\u002Fapps.jun-app.com\u002Fwave\u002F",[2043],"nofollow","こちら","にて実際に動かしています。",[17,2047,2048],{"id":2048},"必要な知識",[13,2050,2051],{},"細かい実装の説明にうつる前に今回用いる必要な知識と原理について確認します。以下の知識にある程度知見がある人はすっ飛ばしてください。",[1467,2053,2054,2057,2060,2063],{},[1470,2055,2056],{},"canvas要素",[1470,2058,2059],{},"jsでcanvasを読み込む方法",[1470,2061,2062],{},"jsでcanvasに画像を設定する方法",[1470,2064,2065],{},"三角関数（超基礎）",[17,2067,2068],{"id":2068},"canvasでのアニメーションの原理",[13,2070,2071],{},"実装サイトでも見ていただいと思いますが、きちんとアニメーションをしておりまた、動画を流しているわけではありません。このアニメーションはcanvas要素というものをjsで操作することで実装ができます。",[13,2073,2074],{},"画像を波打たせる方法は普通のimgタグやdivでは難しいです。柔軟に簡単に実装するためにcanvasを用います。",[13,2076,2077],{},"このcanvasでのアニメーションは実は見えないスピードで「画像を消しては、再描画、消して、再描画…」というのを行っています。また描画する画像は下部を透明な波線で消してから描画しています。また波が連なり、流れるように見せるために三角関数を用いて波型に消す箇所を計算しています。",[13,2079,2080],{},"ちょっとわかりにくいにので図にしてみます。",[729,2082],{":src":2083,":width":732},"'_mix\u002Fwave01.jpeg'",[1467,2085,2086,2089,2092,2095,2098],{},[1470,2087,2088],{},"canvasでは上図のオレンジ点で示した様な描画地点を座標で指定します。（１）",[1470,2090,2091],{},"描画地点は数式を用いて指定できるので、波の部分は三角関数で座標を指定します。（２）",[1470,2093,2094],{},"そして画像の端にたどり着いたら元の描画いちに戻る様にします。（３、４）",[1470,2096,2097],{},"囲まれた部分の色などを指定できるので、透明化を行います。（５）",[1470,2099,2100],{},"そしてすぐに画像を元に戻して、１〜５を再度行う。",[13,2102,2103],{},"また１〜６を繰り返すたびにsni(θ)のθを増やしていけば毎回異なる波がうねる様に見えます。この様にして波のアニメーションを実装します。",[1499,2105,2107],{"id":2106},"canvasって何","canvasって何？",[13,2109,2110],{},"canvasというのはHTML5で扱われる要素の一つであり、2次元図形・グラフィック・アニメーションをjavaScriptを用いて描画することができます。cssでは解決できない図形やアニメーションを実装することができます。数十年前だとflashが担っていたことをHTMLで行うような感じです。",[17,2112,2113],{"id":2113},"canvasに画像を表示させる",[13,2115,2116],{},"ではまずはうねらせる画像をcanvas要素に表示させるところまで行います。今回は同階層に",[1467,2118,2119,2122,2125],{},[1470,2120,2121],{},"index.html",[1470,2123,2124],{},"app.js",[1470,2126,2127],{},"sample.jpg",[13,2129,2130],{},"を用意しておきます。sample.jpgは縦横比１：２にトリミングをしておきます。では作っていきましょう。まずは以下のように適当にHTMLを作っておきます。",[42,2132,2134],{"className":44,"code":2133,"language":46,"meta":47,"style":47},"\u003C!DOCTYPE html>\n\u003Chtml>\n    \u003Chead>\n        \u003Cmeta charset=\"utf-8\">\n        \u003Ctitle>waving image\u003C\u002Ftitle>\n        \u003Cstyle>\n            *{\n                margin:0;\n                padding:0;\n            }\n            main{\n                background:rgb(240, 255, 255);;\n            }\n        \u003C\u002Fstyle>\n    \u003C\u002Fhead>\n    \u003Cbody>\n        \u003Cmain>\n            \u003Ccanvas id=\"canvas\">\u003C\u002Fcanvas>\n        \u003C\u002Fmain>\n        \u003Cscript src=\".\u002Fapp.js\">\u003C\u002Fscript>\n    \u003C\u002Fbody>\n\u003C\u002Fhtml>\n",[49,2135,2136,2149,2157,2167,2189,2207,2215,2222,2236,2247,2252,2259,2286,2290,2299,2308,2317,2326,2352,2360,2384,2392],{"__ignoreMap":47},[52,2137,2138,2141,2144,2147],{"class":54,"line":55},[52,2139,2140],{"class":58},"\u003C!",[52,2142,2143],{"class":62},"DOCTYPE",[52,2145,2146],{"class":65}," html",[52,2148,80],{"class":58},[52,2150,2151,2153,2155],{"class":54,"line":83},[52,2152,59],{"class":58},[52,2154,46],{"class":62},[52,2156,80],{"class":58},[52,2158,2159,2162,2165],{"class":54,"line":115},[52,2160,2161],{"class":58},"    \u003C",[52,2163,2164],{"class":62},"head",[52,2166,80],{"class":58},[52,2168,2169,2172,2175,2178,2180,2182,2185,2187],{"class":54,"line":142},[52,2170,2171],{"class":58},"        \u003C",[52,2173,2174],{"class":62},"meta",[52,2176,2177],{"class":65}," charset",[52,2179,69],{"class":58},[52,2181,72],{"class":58},[52,2183,2184],{"class":75},"utf-8",[52,2186,72],{"class":58},[52,2188,80],{"class":58},[52,2190,2191,2193,2196,2198,2201,2203,2205],{"class":54,"line":169},[52,2192,2171],{"class":58},[52,2194,2195],{"class":62},"title",[52,2197,102],{"class":58},[52,2199,2200],{"class":105},"waving image",[52,2202,108],{"class":58},[52,2204,2195],{"class":62},[52,2206,80],{"class":58},[52,2208,2209,2211,2213],{"class":54,"line":302},[52,2210,2171],{"class":58},[52,2212,1414],{"class":62},[52,2214,80],{"class":58},[52,2216,2217,2220],{"class":54,"line":308},[52,2218,2219],{"class":370},"            *",[52,2221,364],{"class":58},[52,2223,2224,2228,2230,2234],{"class":54,"line":318},[52,2225,2227],{"class":2226},"s6YsC","                margin",[52,2229,373],{"class":58},[52,2231,2233],{"class":2232},"sx098","0",[52,2235,1007],{"class":58},[52,2237,2238,2241,2243,2245],{"class":54,"line":328},[52,2239,2240],{"class":2226},"                padding",[52,2242,373],{"class":58},[52,2244,2233],{"class":2232},[52,2246,1007],{"class":58},[52,2248,2249],{"class":54,"line":337},[52,2250,2251],{"class":58},"            }\n",[52,2253,2254,2257],{"class":54,"line":344},[52,2255,2256],{"class":370},"            main",[52,2258,364],{"class":58},[52,2260,2261,2264,2266,2269,2271,2274,2276,2279,2281,2283],{"class":54,"line":354},[52,2262,2263],{"class":2226},"                background",[52,2265,373],{"class":58},[52,2267,2268],{"class":418},"rgb",[52,2270,932],{"class":58},[52,2272,2273],{"class":2232},"240",[52,2275,408],{"class":58},[52,2277,2278],{"class":2232}," 255",[52,2280,408],{"class":58},[52,2282,2278],{"class":2232},[52,2284,2285],{"class":58},");;\n",[52,2287,2288],{"class":54,"line":367},[52,2289,2251],{"class":58},[52,2291,2292,2295,2297],{"class":54,"line":387},[52,2293,2294],{"class":58},"        \u003C\u002F",[52,2296,1414],{"class":62},[52,2298,80],{"class":58},[52,2300,2301,2304,2306],{"class":54,"line":415},[52,2302,2303],{"class":58},"    \u003C\u002F",[52,2305,2164],{"class":62},[52,2307,80],{"class":58},[52,2309,2310,2312,2315],{"class":54,"line":427},[52,2311,2161],{"class":58},[52,2313,2314],{"class":62},"body",[52,2316,80],{"class":58},[52,2318,2319,2321,2324],{"class":54,"line":435},[52,2320,2171],{"class":58},[52,2322,2323],{"class":62},"main",[52,2325,80],{"class":58},[52,2327,2328,2331,2334,2337,2339,2341,2343,2345,2348,2350],{"class":54,"line":446},[52,2329,2330],{"class":58},"            \u003C",[52,2332,2333],{"class":62},"canvas",[52,2335,2336],{"class":65}," id",[52,2338,69],{"class":58},[52,2340,72],{"class":58},[52,2342,2333],{"class":75},[52,2344,72],{"class":58},[52,2346,2347],{"class":58},">\u003C\u002F",[52,2349,2333],{"class":62},[52,2351,80],{"class":58},[52,2353,2354,2356,2358],{"class":54,"line":480},[52,2355,2294],{"class":58},[52,2357,2323],{"class":62},[52,2359,80],{"class":58},[52,2361,2362,2364,2366,2369,2371,2373,2376,2378,2380,2382],{"class":54,"line":509},[52,2363,2171],{"class":58},[52,2365,349],{"class":62},[52,2367,2368],{"class":65}," src",[52,2370,69],{"class":58},[52,2372,72],{"class":58},[52,2374,2375],{"class":75},".\u002Fapp.js",[52,2377,72],{"class":58},[52,2379,2347],{"class":58},[52,2381,349],{"class":62},[52,2383,80],{"class":58},[52,2385,2386,2388,2390],{"class":54,"line":539},[52,2387,2303],{"class":58},[52,2389,2314],{"class":62},[52,2391,80],{"class":58},[52,2393,2394,2396,2398],{"class":54,"line":547},[52,2395,108],{"class":58},[52,2397,46],{"class":62},[52,2399,80],{"class":58},[13,2401,2402],{},"描画がされるcanvasを用意して、javascriptで拾えるようにidをつけておきましょう。あとは描画を実行するjsファイルをcanvasより後に書いておきます。",[1499,2404,2405],{"id":2405},"javascriptで描画対象のcanvasを設定",[13,2407,2408],{},"app.jsに以下のようにコードを書きます。",[42,2410,2412],{"className":1250,"code":2411,"language":1252,"meta":47,"style":47},"function initAnimation(){\n    var canvas = document.getElementById('canvas');\n    var ctx = canvas.getContext('2d');\n\n    var imagePath = ('.\u002Fsample.jpg');\n    var image = new Image();\n    image.src = imagePath;\n\n    canvas.width = Number(window.innerWidth);\n    canvas.height = Number(canvas.width\u002F2);\n    image.onload = function(){\n          ctx.drawImage(image,0,0,image.width,image.height,0,0,canvas.width,canvas.height);\n    }\n}\n",[49,2413,2414,2425,2455,2484,2488,2511,2530,2546,2550,2579,2609,2625,2692,2697],{"__ignoreMap":47},[52,2415,2416,2419,2422],{"class":54,"line":55},[52,2417,2418],{"class":65},"function",[52,2420,2421],{"class":418}," initAnimation",[52,2423,2424],{"class":58},"(){\n",[52,2426,2427,2430,2433,2435,2438,2440,2443,2445,2447,2449,2451,2453],{"class":54,"line":83},[52,2428,2429],{"class":65},"    var",[52,2431,2432],{"class":105}," canvas",[52,2434,951],{"class":58},[52,2436,2437],{"class":105}," document",[52,2439,957],{"class":58},[52,2441,2442],{"class":418},"getElementById",[52,2444,932],{"class":62},[52,2446,376],{"class":58},[52,2448,2333],{"class":75},[52,2450,376],{"class":58},[52,2452,938],{"class":62},[52,2454,1007],{"class":58},[52,2456,2457,2459,2462,2464,2466,2468,2471,2473,2475,2478,2480,2482],{"class":54,"line":115},[52,2458,2429],{"class":65},[52,2460,2461],{"class":105}," ctx",[52,2463,951],{"class":58},[52,2465,2432],{"class":105},[52,2467,957],{"class":58},[52,2469,2470],{"class":418},"getContext",[52,2472,932],{"class":62},[52,2474,376],{"class":58},[52,2476,2477],{"class":75},"2d",[52,2479,376],{"class":58},[52,2481,938],{"class":62},[52,2483,1007],{"class":58},[52,2485,2486],{"class":54,"line":142},[52,2487,341],{"emptyLinePlaceholder":340},[52,2489,2490,2492,2495,2497,2500,2502,2505,2507,2509],{"class":54,"line":169},[52,2491,2429],{"class":65},[52,2493,2494],{"class":105}," imagePath",[52,2496,951],{"class":58},[52,2498,2499],{"class":62}," (",[52,2501,376],{"class":58},[52,2503,2504],{"class":75},".\u002Fsample.jpg",[52,2506,376],{"class":58},[52,2508,938],{"class":62},[52,2510,1007],{"class":58},[52,2512,2513,2515,2518,2520,2523,2526,2528],{"class":54,"line":302},[52,2514,2429],{"class":65},[52,2516,2517],{"class":105}," image",[52,2519,951],{"class":58},[52,2521,2522],{"class":58}," new",[52,2524,2525],{"class":418}," Image",[52,2527,422],{"class":62},[52,2529,1007],{"class":58},[52,2531,2532,2535,2537,2540,2542,2544],{"class":54,"line":308},[52,2533,2534],{"class":105},"    image",[52,2536,957],{"class":58},[52,2538,2539],{"class":105},"src",[52,2541,951],{"class":58},[52,2543,2494],{"class":105},[52,2545,1007],{"class":58},[52,2547,2548],{"class":54,"line":318},[52,2549,341],{"emptyLinePlaceholder":340},[52,2551,2552,2555,2557,2560,2562,2565,2567,2570,2572,2575,2577],{"class":54,"line":328},[52,2553,2554],{"class":105},"    canvas",[52,2556,957],{"class":58},[52,2558,2559],{"class":105},"width",[52,2561,951],{"class":58},[52,2563,2564],{"class":418}," Number",[52,2566,932],{"class":62},[52,2568,2569],{"class":105},"window",[52,2571,957],{"class":58},[52,2573,2574],{"class":105},"innerWidth",[52,2576,938],{"class":62},[52,2578,1007],{"class":58},[52,2580,2581,2583,2585,2588,2590,2592,2594,2596,2598,2600,2603,2605,2607],{"class":54,"line":337},[52,2582,2554],{"class":105},[52,2584,957],{"class":58},[52,2586,2587],{"class":105},"height",[52,2589,951],{"class":58},[52,2591,2564],{"class":418},[52,2593,932],{"class":62},[52,2595,2333],{"class":105},[52,2597,957],{"class":58},[52,2599,2559],{"class":105},[52,2601,2602],{"class":58},"\u002F",[52,2604,35],{"class":2232},[52,2606,938],{"class":62},[52,2608,1007],{"class":58},[52,2610,2611,2613,2615,2618,2620,2623],{"class":54,"line":344},[52,2612,2534],{"class":105},[52,2614,957],{"class":58},[52,2616,2617],{"class":418},"onload",[52,2619,951],{"class":58},[52,2621,2622],{"class":65}," function",[52,2624,2424],{"class":58},[52,2626,2627,2630,2632,2635,2637,2640,2642,2644,2646,2648,2650,2652,2654,2656,2658,2660,2662,2664,2666,2668,2670,2672,2674,2676,2678,2680,2682,2684,2686,2688,2690],{"class":54,"line":354},[52,2628,2629],{"class":105},"          ctx",[52,2631,957],{"class":58},[52,2633,2634],{"class":418},"drawImage",[52,2636,932],{"class":62},[52,2638,2639],{"class":105},"image",[52,2641,408],{"class":58},[52,2643,2233],{"class":2232},[52,2645,408],{"class":58},[52,2647,2233],{"class":2232},[52,2649,408],{"class":58},[52,2651,2639],{"class":105},[52,2653,957],{"class":58},[52,2655,2559],{"class":105},[52,2657,408],{"class":58},[52,2659,2639],{"class":105},[52,2661,957],{"class":58},[52,2663,2587],{"class":105},[52,2665,408],{"class":58},[52,2667,2233],{"class":2232},[52,2669,408],{"class":58},[52,2671,2233],{"class":2232},[52,2673,408],{"class":58},[52,2675,2333],{"class":105},[52,2677,957],{"class":58},[52,2679,2559],{"class":105},[52,2681,408],{"class":58},[52,2683,2333],{"class":105},[52,2685,957],{"class":58},[52,2687,2587],{"class":105},[52,2689,938],{"class":62},[52,2691,1007],{"class":58},[52,2693,2694],{"class":54,"line":367},[52,2695,2696],{"class":58},"    }\n",[52,2698,2699],{"class":54,"line":387},[52,2700,536],{"class":58},[13,2702,2703],{},"この箇所ではHTMLからcanvas要素を指定して、jsを用いてcanvasの操作を行える様にするおまじないです。以降はこのctx(描画コンテキストインスタンス)に様々な指定を行います。",[42,2705,2707],{"className":1250,"code":2706,"language":1252,"meta":47,"style":47},"var canvas = document.getElementById('canvas');\nvar ctx = canvas.getContext('2d');\n",[49,2708,2709,2737],{"__ignoreMap":47},[52,2710,2711,2714,2717,2719,2721,2723,2725,2727,2729,2731,2733,2735],{"class":54,"line":55},[52,2712,2713],{"class":65},"var",[52,2715,2716],{"class":105}," canvas ",[52,2718,69],{"class":58},[52,2720,2437],{"class":105},[52,2722,957],{"class":58},[52,2724,2442],{"class":418},[52,2726,932],{"class":105},[52,2728,376],{"class":58},[52,2730,2333],{"class":75},[52,2732,376],{"class":58},[52,2734,938],{"class":105},[52,2736,1007],{"class":58},[52,2738,2739,2741,2744,2746,2748,2750,2752,2754,2756,2758,2760,2762],{"class":54,"line":83},[52,2740,2713],{"class":65},[52,2742,2743],{"class":105}," ctx ",[52,2745,69],{"class":58},[52,2747,2432],{"class":105},[52,2749,957],{"class":58},[52,2751,2470],{"class":418},[52,2753,932],{"class":105},[52,2755,376],{"class":58},[52,2757,2477],{"class":75},[52,2759,376],{"class":58},[52,2761,938],{"class":105},[52,2763,1007],{"class":58},[13,2765,2766],{},"そして",[42,2768,2770],{"className":1250,"code":2769,"language":1252,"meta":47,"style":47},"var imagePath = ('.\u002Fsample.jpg');\nvar image = new Image();\nimage.src = imagePath;\n\ncanvas.width = Number(window.innerWidth);\ncanvas.height = Number(canvas.width\u002F2);\nimage.onload = function(){\n        ctx.drawImage(image,0,0,image.width,image.height,0,0,canvas.width,canvas.height);\n}\n",[49,2771,2772,2793,2810,2825,2829,2852,2880,2894,2959],{"__ignoreMap":47},[52,2773,2774,2776,2779,2781,2783,2785,2787,2789,2791],{"class":54,"line":55},[52,2775,2713],{"class":65},[52,2777,2778],{"class":105}," imagePath ",[52,2780,69],{"class":58},[52,2782,2499],{"class":105},[52,2784,376],{"class":58},[52,2786,2504],{"class":75},[52,2788,376],{"class":58},[52,2790,938],{"class":105},[52,2792,1007],{"class":58},[52,2794,2795,2797,2800,2802,2804,2806,2808],{"class":54,"line":83},[52,2796,2713],{"class":65},[52,2798,2799],{"class":105}," image ",[52,2801,69],{"class":58},[52,2803,2522],{"class":58},[52,2805,2525],{"class":418},[52,2807,422],{"class":105},[52,2809,1007],{"class":58},[52,2811,2812,2814,2816,2819,2821,2823],{"class":54,"line":115},[52,2813,2639],{"class":105},[52,2815,957],{"class":58},[52,2817,2818],{"class":105},"src ",[52,2820,69],{"class":58},[52,2822,2494],{"class":105},[52,2824,1007],{"class":58},[52,2826,2827],{"class":54,"line":142},[52,2828,341],{"emptyLinePlaceholder":340},[52,2830,2831,2833,2835,2838,2840,2842,2845,2847,2850],{"class":54,"line":169},[52,2832,2333],{"class":105},[52,2834,957],{"class":58},[52,2836,2837],{"class":105},"width ",[52,2839,69],{"class":58},[52,2841,2564],{"class":418},[52,2843,2844],{"class":105},"(window",[52,2846,957],{"class":58},[52,2848,2849],{"class":105},"innerWidth)",[52,2851,1007],{"class":58},[52,2853,2854,2856,2858,2861,2863,2865,2868,2870,2872,2874,2876,2878],{"class":54,"line":302},[52,2855,2333],{"class":105},[52,2857,957],{"class":58},[52,2859,2860],{"class":105},"height ",[52,2862,69],{"class":58},[52,2864,2564],{"class":418},[52,2866,2867],{"class":105},"(canvas",[52,2869,957],{"class":58},[52,2871,2559],{"class":105},[52,2873,2602],{"class":58},[52,2875,35],{"class":2232},[52,2877,938],{"class":105},[52,2879,1007],{"class":58},[52,2881,2882,2884,2886,2888,2890,2892],{"class":54,"line":308},[52,2883,2639],{"class":105},[52,2885,957],{"class":58},[52,2887,2617],{"class":418},[52,2889,951],{"class":58},[52,2891,2622],{"class":65},[52,2893,2424],{"class":58},[52,2895,2896,2899,2901,2903,2905,2907,2909,2911,2913,2915,2917,2919,2921,2923,2925,2927,2929,2931,2933,2935,2937,2939,2941,2943,2945,2947,2949,2951,2953,2955,2957],{"class":54,"line":318},[52,2897,2898],{"class":105},"        ctx",[52,2900,957],{"class":58},[52,2902,2634],{"class":418},[52,2904,932],{"class":62},[52,2906,2639],{"class":105},[52,2908,408],{"class":58},[52,2910,2233],{"class":2232},[52,2912,408],{"class":58},[52,2914,2233],{"class":2232},[52,2916,408],{"class":58},[52,2918,2639],{"class":105},[52,2920,957],{"class":58},[52,2922,2559],{"class":105},[52,2924,408],{"class":58},[52,2926,2639],{"class":105},[52,2928,957],{"class":58},[52,2930,2587],{"class":105},[52,2932,408],{"class":58},[52,2934,2233],{"class":2232},[52,2936,408],{"class":58},[52,2938,2233],{"class":2232},[52,2940,408],{"class":58},[52,2942,2333],{"class":105},[52,2944,957],{"class":58},[52,2946,2559],{"class":105},[52,2948,408],{"class":58},[52,2950,2333],{"class":105},[52,2952,957],{"class":58},[52,2954,2587],{"class":105},[52,2956,938],{"class":62},[52,2958,1007],{"class":58},[52,2960,2961],{"class":54,"line":328},[52,2962,536],{"class":58},[13,2964,2965,2966,2969,2970,2973],{},"jsのImageオブジェクトを用いてsrcプロパティに映す画像のパスを入れます。",[49,2967,2968],{},"canvas.width","、",[49,2971,2972],{},"canvas.height","でcanvasの大きさを指定します。画像が縦横１：２なので canvasもその比率に沿う様にしました。",[13,2975,2976,2979,2980,2983,2984,2986],{},[49,2977,2978],{},"image.onload"," を用いてsrcで指定した画像の読み込みが終わったら、\n",[49,2981,2982],{},"drawImage()"," メソッドを用いてcanvasに画像を描画する様にします。\n",[49,2985,2978],{}," を使わないと画像が読み込まれる前に描画しようとするので、映されません。",[13,2988,2989],{},"とりあえずここまでくると、canvas要素しかないHTMLにもかかわらず、以下の様に画像が描画されているはずです。",[729,2991],{":src":2992,":width":2993,":center":1323},"'_mix\u002Fwave02.png'","'500px'",[1566,2995,2997],{"id":2996},"drawimageの使い方","drawImage()の使い方",[13,2999,3000,3001],{},"drawImage()はcanvasに範囲を指定して画像を描画します。canvas全体に画像を描画するならば必ず、最後の引数まで入力したほうがいいです。",[2039,3002,3005],{"href":3003,"rel":3004},"https:\u002F\u002Fdeveloper.mozilla.org\u002Fen-US\u002Fdocs\u002FWeb\u002FAPI\u002FCanvasRenderingContext2D\u002FdrawImage",[2043],"（MDNの解説（英語））",[42,3007,3009],{"className":1250,"code":3008,"language":1252,"meta":47,"style":47},"void ctx.drawImage(image, sx, sy, sWidth, sHeight, dx, dy, dWidth, dHeight);\n",[49,3010,3011],{"__ignoreMap":47},[52,3012,3013,3016,3018,3020,3022,3025,3027,3030,3032,3035,3037,3040,3042,3045,3047,3050,3052,3055,3057,3060,3062,3065],{"class":54,"line":55},[52,3014,3015],{"class":58},"void",[52,3017,2461],{"class":105},[52,3019,957],{"class":58},[52,3021,2634],{"class":418},[52,3023,3024],{"class":105},"(image",[52,3026,408],{"class":58},[52,3028,3029],{"class":105}," sx",[52,3031,408],{"class":58},[52,3033,3034],{"class":105}," sy",[52,3036,408],{"class":58},[52,3038,3039],{"class":105}," sWidth",[52,3041,408],{"class":58},[52,3043,3044],{"class":105}," sHeight",[52,3046,408],{"class":58},[52,3048,3049],{"class":105}," dx",[52,3051,408],{"class":58},[52,3053,3054],{"class":105}," dy",[52,3056,408],{"class":58},[52,3058,3059],{"class":105}," dWidth",[52,3061,408],{"class":58},[52,3063,3064],{"class":105}," dHeight)",[52,3066,1007],{"class":58},[13,3068,3069],{},"それぞれの引数を説明すると",[13,3071,3072],{},"Imageインスタンス。つまり描画する対象の画像",[1687,3074,3075,3078,3081,3084,3087,3090,3092,3095],{},[1470,3076,3077],{},"画像の切り抜き開始地点のX座標",[1470,3079,3080],{},"画像の切り抜き開始地点のY座標",[1470,3082,3083],{},"第２引数で指定したX座標から切り抜くX方向の距離（幅）",[1470,3085,3086],{},"第３引数で指定したY座標から切り抜くY方向の距離（高さ）",[1470,3088,3089],{},"canvasに描画する開始地点のX座標",[1470,3091,3089],{},[1470,3093,3094],{},"第６引数で指定したX座標から描画するX方向の距離（幅）",[1470,3096,3097],{},"第７引数で指定したY座標から描画するY方向の距離（高さ）",[13,3099,3100],{},"という感じです！私の今回の例でいくと",[42,3102,3104],{"className":1250,"code":3103,"language":1252,"meta":47,"style":47},"ctx.drawImage(image,0,0,image.width,image.height,0,0,canvas.width,canvas.height);\n",[49,3105,3106],{"__ignoreMap":47},[52,3107,3108,3111,3113,3115,3117,3119,3121,3123,3125,3127,3129,3131,3133,3135,3137,3139,3141,3143,3145,3147,3149,3151,3153,3155,3157,3159,3161,3163,3166],{"class":54,"line":55},[52,3109,3110],{"class":105},"ctx",[52,3112,957],{"class":58},[52,3114,2634],{"class":418},[52,3116,3024],{"class":105},[52,3118,408],{"class":58},[52,3120,2233],{"class":2232},[52,3122,408],{"class":58},[52,3124,2233],{"class":2232},[52,3126,408],{"class":58},[52,3128,2639],{"class":105},[52,3130,957],{"class":58},[52,3132,2559],{"class":105},[52,3134,408],{"class":58},[52,3136,2639],{"class":105},[52,3138,957],{"class":58},[52,3140,2587],{"class":105},[52,3142,408],{"class":58},[52,3144,2233],{"class":2232},[52,3146,408],{"class":58},[52,3148,2233],{"class":2232},[52,3150,408],{"class":58},[52,3152,2333],{"class":105},[52,3154,957],{"class":58},[52,3156,2559],{"class":105},[52,3158,408],{"class":58},[52,3160,2333],{"class":105},[52,3162,957],{"class":58},[52,3164,3165],{"class":105},"height)",[52,3167,1007],{"class":58},[13,3169,3170,3173],{},[49,3171,3172],{},"0,0,image.width,image.height",", で画像の（０,０）座標地点から画像の幅と高さ分、画像を切り取るという意味です。つまり画像全体を読み取っているのと同じです。",[13,3175,3176,3177,3180],{},"もし",[49,3178,3179],{},"0,0,image.width\u002F2,image.height\u002F2",", としたら元画像の1\u002F4だけの部分切り取られた画像が映し出されます。",[13,3182,3183,3186],{},[49,3184,3185],{},"0,0,canvas.width,canvas.height"," ここもcanvasの（０,０）座標地点からcanvasの幅と高さ分、画像を貼り付けるという意味です。つまりcanvas全体に画像を貼り付けるのと同じです。",[13,3188,3189],{},"ここは実際にコードを描いて比率をいじって見てください。そうすればここの意味がわかる様になります。",[17,3191,3192],{"id":3192},"波線に画像を切り抜く",[13,3194,3195,3196,3198],{},"それでは次にこの画像を波線に切り抜きます。切り抜きのイメージとしては上で説明した図の様に、一辺が波線の四角形を描いて、透明に塗り潰します。",[49,3197,2978],{},"以降に以下のコードを加えます。",[42,3200,3202],{"className":1250,"code":3201,"language":1252,"meta":47,"style":47},"image.onload = function(){\n    initDraw();\n}\n    \nvar canvasEndX = canvas.width;\nvar canvasEndY = canvas.height;\nvar waveStartPoint = canvasEndY-150;\n\nvar amplitude = 30;\nvar period = 1000;\nvar degree = 0;\n\nfunction initDraw(){\n    imageSet(image,canvasEndX,canvasEndY);\n    waveDrawing(waveStartPoint,canvasEndX,canvasEndY,degree,amplitude,period);\n}\n\nfunction imageSet(imageObj,canvasEndX,canvasEndY){\n    var imgWidth = imageObj.width;\n    var imgHeight = imageObj.height;\n    ctx.drawImage(image,0,0,imgWidth,imgHeight,0,0,canvasEndX,canvasEndY);\n}\n\nfunction waveDrawing(waveStartPoint,canvasEndX,canvasEndY,deg,am,tp){\n    var waveStartY = waveStartPoint;\n    ctx.globalCompositeOperation = \"destination-out\";\n    ctx.beginPath();\n    ctx.moveTo(0, waveStartY);\n\n    for (var x=0; x \u003C= canvasEndX; x+= 1) {\n        var y = -am*Math.sin((Math.PI\u002Ftp)*(deg+x));\n        ctx.lineTo(x, y+waveStartY);\n    }\n\n    ctx.lineTo(canvasEndX,canvasEndY);\n    ctx.lineTo(0,canvasEndY);\n    ctx.closePath();\n\n    　ctx.fillStyle = \"rgba(255,255,255,1)\"; \u002F\u002Fopacity 1\n    ctx.fill();\n}\n",[49,3203,3204,3218,3227,3231,3236,3253,3270,3290,3294,3308,3322,3336,3340,3349,3372,3409,3413,3417,3440,3458,3475,3526,3530,3534,3570,3584,3605,3618,3639,3643,3685,3744,3770,3774,3778,3798,3819,3833,3838,3863,3877],{"__ignoreMap":47},[52,3205,3206,3208,3210,3212,3214,3216],{"class":54,"line":55},[52,3207,2639],{"class":105},[52,3209,957],{"class":58},[52,3211,2617],{"class":418},[52,3213,951],{"class":58},[52,3215,2622],{"class":65},[52,3217,2424],{"class":58},[52,3219,3220,3223,3225],{"class":54,"line":83},[52,3221,3222],{"class":418},"    initDraw",[52,3224,422],{"class":62},[52,3226,1007],{"class":58},[52,3228,3229],{"class":54,"line":115},[52,3230,536],{"class":58},[52,3232,3233],{"class":54,"line":142},[52,3234,3235],{"class":105},"    \n",[52,3237,3238,3240,3243,3245,3247,3249,3251],{"class":54,"line":169},[52,3239,2713],{"class":65},[52,3241,3242],{"class":105}," canvasEndX ",[52,3244,69],{"class":58},[52,3246,2432],{"class":105},[52,3248,957],{"class":58},[52,3250,2559],{"class":105},[52,3252,1007],{"class":58},[52,3254,3255,3257,3260,3262,3264,3266,3268],{"class":54,"line":302},[52,3256,2713],{"class":65},[52,3258,3259],{"class":105}," canvasEndY ",[52,3261,69],{"class":58},[52,3263,2432],{"class":105},[52,3265,957],{"class":58},[52,3267,2587],{"class":105},[52,3269,1007],{"class":58},[52,3271,3272,3274,3277,3279,3282,3285,3288],{"class":54,"line":308},[52,3273,2713],{"class":65},[52,3275,3276],{"class":105}," waveStartPoint ",[52,3278,69],{"class":58},[52,3280,3281],{"class":105}," canvasEndY",[52,3283,3284],{"class":58},"-",[52,3286,3287],{"class":2232},"150",[52,3289,1007],{"class":58},[52,3291,3292],{"class":54,"line":318},[52,3293,341],{"emptyLinePlaceholder":340},[52,3295,3296,3298,3301,3303,3306],{"class":54,"line":328},[52,3297,2713],{"class":65},[52,3299,3300],{"class":105}," amplitude ",[52,3302,69],{"class":58},[52,3304,3305],{"class":2232}," 30",[52,3307,1007],{"class":58},[52,3309,3310,3312,3315,3317,3320],{"class":54,"line":337},[52,3311,2713],{"class":65},[52,3313,3314],{"class":105}," period ",[52,3316,69],{"class":58},[52,3318,3319],{"class":2232}," 1000",[52,3321,1007],{"class":58},[52,3323,3324,3326,3329,3331,3334],{"class":54,"line":344},[52,3325,2713],{"class":65},[52,3327,3328],{"class":105}," degree ",[52,3330,69],{"class":58},[52,3332,3333],{"class":2232}," 0",[52,3335,1007],{"class":58},[52,3337,3338],{"class":54,"line":354},[52,3339,341],{"emptyLinePlaceholder":340},[52,3341,3342,3344,3347],{"class":54,"line":367},[52,3343,2418],{"class":65},[52,3345,3346],{"class":418}," initDraw",[52,3348,2424],{"class":58},[52,3350,3351,3354,3356,3358,3360,3363,3365,3368,3370],{"class":54,"line":387},[52,3352,3353],{"class":418},"    imageSet",[52,3355,932],{"class":62},[52,3357,2639],{"class":105},[52,3359,408],{"class":58},[52,3361,3362],{"class":105},"canvasEndX",[52,3364,408],{"class":58},[52,3366,3367],{"class":105},"canvasEndY",[52,3369,938],{"class":62},[52,3371,1007],{"class":58},[52,3373,3374,3377,3379,3382,3384,3386,3388,3390,3392,3395,3397,3400,3402,3405,3407],{"class":54,"line":415},[52,3375,3376],{"class":418},"    waveDrawing",[52,3378,932],{"class":62},[52,3380,3381],{"class":105},"waveStartPoint",[52,3383,408],{"class":58},[52,3385,3362],{"class":105},[52,3387,408],{"class":58},[52,3389,3367],{"class":105},[52,3391,408],{"class":58},[52,3393,3394],{"class":105},"degree",[52,3396,408],{"class":58},[52,3398,3399],{"class":105},"amplitude",[52,3401,408],{"class":58},[52,3403,3404],{"class":105},"period",[52,3406,938],{"class":62},[52,3408,1007],{"class":58},[52,3410,3411],{"class":54,"line":427},[52,3412,536],{"class":58},[52,3414,3415],{"class":54,"line":435},[52,3416,341],{"emptyLinePlaceholder":340},[52,3418,3419,3421,3424,3426,3429,3431,3433,3435,3437],{"class":54,"line":446},[52,3420,2418],{"class":65},[52,3422,3423],{"class":418}," imageSet",[52,3425,932],{"class":58},[52,3427,3428],{"class":986},"imageObj",[52,3430,408],{"class":58},[52,3432,3362],{"class":986},[52,3434,408],{"class":58},[52,3436,3367],{"class":986},[52,3438,3439],{"class":58},"){\n",[52,3441,3442,3444,3447,3449,3452,3454,3456],{"class":54,"line":480},[52,3443,2429],{"class":65},[52,3445,3446],{"class":105}," imgWidth",[52,3448,951],{"class":58},[52,3450,3451],{"class":105}," imageObj",[52,3453,957],{"class":58},[52,3455,2559],{"class":105},[52,3457,1007],{"class":58},[52,3459,3460,3462,3465,3467,3469,3471,3473],{"class":54,"line":509},[52,3461,2429],{"class":65},[52,3463,3464],{"class":105}," imgHeight",[52,3466,951],{"class":58},[52,3468,3451],{"class":105},[52,3470,957],{"class":58},[52,3472,2587],{"class":105},[52,3474,1007],{"class":58},[52,3476,3477,3480,3482,3484,3486,3488,3490,3492,3494,3496,3498,3501,3503,3506,3508,3510,3512,3514,3516,3518,3520,3522,3524],{"class":54,"line":539},[52,3478,3479],{"class":105},"    ctx",[52,3481,957],{"class":58},[52,3483,2634],{"class":418},[52,3485,932],{"class":62},[52,3487,2639],{"class":105},[52,3489,408],{"class":58},[52,3491,2233],{"class":2232},[52,3493,408],{"class":58},[52,3495,2233],{"class":2232},[52,3497,408],{"class":58},[52,3499,3500],{"class":105},"imgWidth",[52,3502,408],{"class":58},[52,3504,3505],{"class":105},"imgHeight",[52,3507,408],{"class":58},[52,3509,2233],{"class":2232},[52,3511,408],{"class":58},[52,3513,2233],{"class":2232},[52,3515,408],{"class":58},[52,3517,3362],{"class":105},[52,3519,408],{"class":58},[52,3521,3367],{"class":105},[52,3523,938],{"class":62},[52,3525,1007],{"class":58},[52,3527,3528],{"class":54,"line":547},[52,3529,536],{"class":58},[52,3531,3532],{"class":54,"line":553},[52,3533,341],{"emptyLinePlaceholder":340},[52,3535,3536,3538,3541,3543,3545,3547,3549,3551,3553,3555,3558,3560,3563,3565,3568],{"class":54,"line":559},[52,3537,2418],{"class":65},[52,3539,3540],{"class":418}," waveDrawing",[52,3542,932],{"class":58},[52,3544,3381],{"class":986},[52,3546,408],{"class":58},[52,3548,3362],{"class":986},[52,3550,408],{"class":58},[52,3552,3367],{"class":986},[52,3554,408],{"class":58},[52,3556,3557],{"class":986},"deg",[52,3559,408],{"class":58},[52,3561,3562],{"class":986},"am",[52,3564,408],{"class":58},[52,3566,3567],{"class":986},"tp",[52,3569,3439],{"class":58},[52,3571,3572,3574,3577,3579,3582],{"class":54,"line":564},[52,3573,2429],{"class":65},[52,3575,3576],{"class":105}," waveStartY",[52,3578,951],{"class":58},[52,3580,3581],{"class":105}," waveStartPoint",[52,3583,1007],{"class":58},[52,3585,3586,3588,3590,3593,3595,3598,3601,3603],{"class":54,"line":569},[52,3587,3479],{"class":105},[52,3589,957],{"class":58},[52,3591,3592],{"class":105},"globalCompositeOperation",[52,3594,951],{"class":58},[52,3596,3597],{"class":58}," \"",[52,3599,3600],{"class":75},"destination-out",[52,3602,72],{"class":58},[52,3604,1007],{"class":58},[52,3606,3607,3609,3611,3614,3616],{"class":54,"line":1106},[52,3608,3479],{"class":105},[52,3610,957],{"class":58},[52,3612,3613],{"class":418},"beginPath",[52,3615,422],{"class":62},[52,3617,1007],{"class":58},[52,3619,3620,3622,3624,3627,3629,3631,3633,3635,3637],{"class":54,"line":1135},[52,3621,3479],{"class":105},[52,3623,957],{"class":58},[52,3625,3626],{"class":418},"moveTo",[52,3628,932],{"class":62},[52,3630,2233],{"class":2232},[52,3632,408],{"class":58},[52,3634,3576],{"class":105},[52,3636,938],{"class":62},[52,3638,1007],{"class":58},[52,3640,3641],{"class":54,"line":1164},[52,3642,341],{"emptyLinePlaceholder":340},[52,3644,3645,3648,3650,3652,3655,3657,3659,3662,3664,3667,3670,3672,3674,3677,3680,3683],{"class":54,"line":1193},[52,3646,3647],{"class":360},"    for",[52,3649,2499],{"class":62},[52,3651,2713],{"class":65},[52,3653,3654],{"class":105}," x",[52,3656,69],{"class":58},[52,3658,2233],{"class":2232},[52,3660,3661],{"class":58},";",[52,3663,3654],{"class":105},[52,3665,3666],{"class":58}," \u003C=",[52,3668,3669],{"class":105}," canvasEndX",[52,3671,3661],{"class":58},[52,3673,3654],{"class":105},[52,3675,3676],{"class":58},"+=",[52,3678,3679],{"class":2232}," 1",[52,3681,3682],{"class":62},") ",[52,3684,364],{"class":58},[52,3686,3687,3690,3693,3695,3698,3700,3703,3706,3708,3711,3714,3716,3718,3721,3723,3725,3727,3729,3731,3733,3736,3739,3742],{"class":54,"line":1200},[52,3688,3689],{"class":65},"        var",[52,3691,3692],{"class":105}," y",[52,3694,951],{"class":58},[52,3696,3697],{"class":58}," -",[52,3699,3562],{"class":105},[52,3701,3702],{"class":58},"*",[52,3704,3705],{"class":105},"Math",[52,3707,957],{"class":58},[52,3709,3710],{"class":418},"sin",[52,3712,3713],{"class":62},"((",[52,3715,3705],{"class":105},[52,3717,957],{"class":58},[52,3719,3720],{"class":105},"PI",[52,3722,2602],{"class":58},[52,3724,3567],{"class":105},[52,3726,938],{"class":62},[52,3728,3702],{"class":58},[52,3730,932],{"class":62},[52,3732,3557],{"class":105},[52,3734,3735],{"class":58},"+",[52,3737,3738],{"class":105},"x",[52,3740,3741],{"class":62},"))",[52,3743,1007],{"class":58},[52,3745,3746,3748,3750,3753,3755,3757,3759,3761,3763,3766,3768],{"class":54,"line":1205},[52,3747,2898],{"class":105},[52,3749,957],{"class":58},[52,3751,3752],{"class":418},"lineTo",[52,3754,932],{"class":62},[52,3756,3738],{"class":105},[52,3758,408],{"class":58},[52,3760,3692],{"class":105},[52,3762,3735],{"class":58},[52,3764,3765],{"class":105},"waveStartY",[52,3767,938],{"class":62},[52,3769,1007],{"class":58},[52,3771,3772],{"class":54,"line":1210},[52,3773,2696],{"class":58},[52,3775,3776],{"class":54,"line":1215},[52,3777,341],{"emptyLinePlaceholder":340},[52,3779,3780,3782,3784,3786,3788,3790,3792,3794,3796],{"class":54,"line":1220},[52,3781,3479],{"class":105},[52,3783,957],{"class":58},[52,3785,3752],{"class":418},[52,3787,932],{"class":62},[52,3789,3362],{"class":105},[52,3791,408],{"class":58},[52,3793,3367],{"class":105},[52,3795,938],{"class":62},[52,3797,1007],{"class":58},[52,3799,3801,3803,3805,3807,3809,3811,3813,3815,3817],{"class":54,"line":3800},36,[52,3802,3479],{"class":105},[52,3804,957],{"class":58},[52,3806,3752],{"class":418},[52,3808,932],{"class":62},[52,3810,2233],{"class":2232},[52,3812,408],{"class":58},[52,3814,3367],{"class":105},[52,3816,938],{"class":62},[52,3818,1007],{"class":58},[52,3820,3822,3824,3826,3829,3831],{"class":54,"line":3821},37,[52,3823,3479],{"class":105},[52,3825,957],{"class":58},[52,3827,3828],{"class":418},"closePath",[52,3830,422],{"class":62},[52,3832,1007],{"class":58},[52,3834,3836],{"class":54,"line":3835},38,[52,3837,341],{"emptyLinePlaceholder":340},[52,3839,3841,3844,3846,3849,3851,3853,3856,3858,3860],{"class":54,"line":3840},39,[52,3842,3843],{"class":105},"    　ctx",[52,3845,957],{"class":58},[52,3847,3848],{"class":105},"fillStyle",[52,3850,951],{"class":58},[52,3852,3597],{"class":58},[52,3854,3855],{"class":75},"rgba(255,255,255,1)",[52,3857,72],{"class":58},[52,3859,3661],{"class":58},[52,3861,3862],{"class":411}," \u002F\u002Fopacity 1\n",[52,3864,3866,3868,3870,3873,3875],{"class":54,"line":3865},40,[52,3867,3479],{"class":105},[52,3869,957],{"class":58},[52,3871,3872],{"class":418},"fill",[52,3874,422],{"class":62},[52,3876,1007],{"class":58},[52,3878,3880],{"class":54,"line":3879},41,[52,3881,536],{"class":58},[13,3883,3884],{},"ここでは３つの関数を作成します。",[1467,3886,3887,3890,3893],{},[1470,3888,3889],{},"initDraw()：初期の波線くりぬき画像を描画する。",[1470,3891,3892],{},"imageSet()：画像をキャンバスに描画する。またすでに画像がある場合はそれをクリアする。",[1470,3894,3895],{},"waveDrawing()：画像を波線にくり抜く。",[13,3897,3898],{},"それぞれを解説していきます。",[1499,3900,3902],{"id":3901},"imageset-で画像を描画","imageSet() で画像を描画",[42,3904,3906],{"className":1250,"code":3905,"language":1252,"meta":47,"style":47},"function imageSet(imageObj,canvasEndX,canvasEndY){\n     var imgWidth = imageObj.width;\n     var imgHeight = imageObj.height;\n     ctx.drawImage(image,0,0,imgWidth,imgHeight,0,0,canvasEndX,canvasEndY);\n}\n",[49,3907,3908,3928,3945,3961,4010],{"__ignoreMap":47},[52,3909,3910,3912,3914,3916,3918,3920,3922,3924,3926],{"class":54,"line":55},[52,3911,2418],{"class":65},[52,3913,3423],{"class":418},[52,3915,932],{"class":58},[52,3917,3428],{"class":986},[52,3919,408],{"class":58},[52,3921,3362],{"class":986},[52,3923,408],{"class":58},[52,3925,3367],{"class":986},[52,3927,3439],{"class":58},[52,3929,3930,3933,3935,3937,3939,3941,3943],{"class":54,"line":83},[52,3931,3932],{"class":65},"     var",[52,3934,3446],{"class":105},[52,3936,951],{"class":58},[52,3938,3451],{"class":105},[52,3940,957],{"class":58},[52,3942,2559],{"class":105},[52,3944,1007],{"class":58},[52,3946,3947,3949,3951,3953,3955,3957,3959],{"class":54,"line":115},[52,3948,3932],{"class":65},[52,3950,3464],{"class":105},[52,3952,951],{"class":58},[52,3954,3451],{"class":105},[52,3956,957],{"class":58},[52,3958,2587],{"class":105},[52,3960,1007],{"class":58},[52,3962,3963,3966,3968,3970,3972,3974,3976,3978,3980,3982,3984,3986,3988,3990,3992,3994,3996,3998,4000,4002,4004,4006,4008],{"class":54,"line":142},[52,3964,3965],{"class":105},"     ctx",[52,3967,957],{"class":58},[52,3969,2634],{"class":418},[52,3971,932],{"class":62},[52,3973,2639],{"class":105},[52,3975,408],{"class":58},[52,3977,2233],{"class":2232},[52,3979,408],{"class":58},[52,3981,2233],{"class":2232},[52,3983,408],{"class":58},[52,3985,3500],{"class":105},[52,3987,408],{"class":58},[52,3989,3505],{"class":105},[52,3991,408],{"class":58},[52,3993,2233],{"class":2232},[52,3995,408],{"class":58},[52,3997,2233],{"class":2232},[52,3999,408],{"class":58},[52,4001,3362],{"class":105},[52,4003,408],{"class":58},[52,4005,3367],{"class":105},[52,4007,938],{"class":62},[52,4009,1007],{"class":58},[52,4011,4012],{"class":54,"line":169},[52,4013,536],{"class":58},[13,4015,4016,4017,4019],{},"この関数は単に画像を描画するだけです。引数に画像オブジェクトとキャンバスの幅・高さ情報をとり、先ほども説明した",[49,4018,2982],{},"を用いてキャンバスに画像を描画します。",[1499,4021,4023],{"id":4022},"wavedrawingで波線くりぬきをする","waveDrawing()で波線くりぬきをする",[13,4025,4026,4029],{},[49,4027,4028],{},"waveDrawing()","という関数で波線のくりぬき処理をかいていきます。",[42,4031,4033],{"className":1250,"code":4032,"language":1252,"meta":47,"style":47},"function waveDrawing(waveStartPoint,canvasEndX,canvasEndY,deg,am,tp){\n      var waveStartY = waveStartPoint;\n      ctx.globalCompositeOperation = \"destination-out\";\n      ctx.beginPath();\n      ctx.moveTo(0, waveStartY);\n\n      for (var x=0; x \u003C= canvasEndX; x+= 1) {\n           var y = -am*Math.sin((Math.PI\u002Ftp)*(deg+x));;\n           ctx.lineTo(x, y+waveStartY);\n      }\n\n      ctx.lineTo(canvasEndX,canvasEndY);\n      ctx.lineTo(0,canvasEndY);\n      ctx.closePath();\n\n      ctx.fillStyle = \"rgba(255,255,255,1)\"; \u002F\u002Fopacity 1\n      ctx.fill();\n}\n",[49,4034,4035,4067,4080,4099,4111,4131,4135,4170,4220,4245,4250,4254,4274,4294,4306,4310,4330,4342],{"__ignoreMap":47},[52,4036,4037,4039,4041,4043,4045,4047,4049,4051,4053,4055,4057,4059,4061,4063,4065],{"class":54,"line":55},[52,4038,2418],{"class":65},[52,4040,3540],{"class":418},[52,4042,932],{"class":58},[52,4044,3381],{"class":986},[52,4046,408],{"class":58},[52,4048,3362],{"class":986},[52,4050,408],{"class":58},[52,4052,3367],{"class":986},[52,4054,408],{"class":58},[52,4056,3557],{"class":986},[52,4058,408],{"class":58},[52,4060,3562],{"class":986},[52,4062,408],{"class":58},[52,4064,3567],{"class":986},[52,4066,3439],{"class":58},[52,4068,4069,4072,4074,4076,4078],{"class":54,"line":83},[52,4070,4071],{"class":65},"      var",[52,4073,3576],{"class":105},[52,4075,951],{"class":58},[52,4077,3581],{"class":105},[52,4079,1007],{"class":58},[52,4081,4082,4085,4087,4089,4091,4093,4095,4097],{"class":54,"line":115},[52,4083,4084],{"class":105},"      ctx",[52,4086,957],{"class":58},[52,4088,3592],{"class":105},[52,4090,951],{"class":58},[52,4092,3597],{"class":58},[52,4094,3600],{"class":75},[52,4096,72],{"class":58},[52,4098,1007],{"class":58},[52,4100,4101,4103,4105,4107,4109],{"class":54,"line":142},[52,4102,4084],{"class":105},[52,4104,957],{"class":58},[52,4106,3613],{"class":418},[52,4108,422],{"class":62},[52,4110,1007],{"class":58},[52,4112,4113,4115,4117,4119,4121,4123,4125,4127,4129],{"class":54,"line":169},[52,4114,4084],{"class":105},[52,4116,957],{"class":58},[52,4118,3626],{"class":418},[52,4120,932],{"class":62},[52,4122,2233],{"class":2232},[52,4124,408],{"class":58},[52,4126,3576],{"class":105},[52,4128,938],{"class":62},[52,4130,1007],{"class":58},[52,4132,4133],{"class":54,"line":302},[52,4134,341],{"emptyLinePlaceholder":340},[52,4136,4137,4140,4142,4144,4146,4148,4150,4152,4154,4156,4158,4160,4162,4164,4166,4168],{"class":54,"line":308},[52,4138,4139],{"class":360},"      for",[52,4141,2499],{"class":62},[52,4143,2713],{"class":65},[52,4145,3654],{"class":105},[52,4147,69],{"class":58},[52,4149,2233],{"class":2232},[52,4151,3661],{"class":58},[52,4153,3654],{"class":105},[52,4155,3666],{"class":58},[52,4157,3669],{"class":105},[52,4159,3661],{"class":58},[52,4161,3654],{"class":105},[52,4163,3676],{"class":58},[52,4165,3679],{"class":2232},[52,4167,3682],{"class":62},[52,4169,364],{"class":58},[52,4171,4172,4175,4177,4179,4181,4183,4185,4187,4189,4191,4193,4195,4197,4199,4201,4203,4205,4207,4209,4211,4213,4215,4217],{"class":54,"line":318},[52,4173,4174],{"class":65},"           var",[52,4176,3692],{"class":105},[52,4178,951],{"class":58},[52,4180,3697],{"class":58},[52,4182,3562],{"class":105},[52,4184,3702],{"class":58},[52,4186,3705],{"class":105},[52,4188,957],{"class":58},[52,4190,3710],{"class":418},[52,4192,3713],{"class":62},[52,4194,3705],{"class":105},[52,4196,957],{"class":58},[52,4198,3720],{"class":105},[52,4200,2602],{"class":58},[52,4202,3567],{"class":105},[52,4204,938],{"class":62},[52,4206,3702],{"class":58},[52,4208,932],{"class":62},[52,4210,3557],{"class":105},[52,4212,3735],{"class":58},[52,4214,3738],{"class":105},[52,4216,3741],{"class":62},[52,4218,4219],{"class":58},";;\n",[52,4221,4222,4225,4227,4229,4231,4233,4235,4237,4239,4241,4243],{"class":54,"line":328},[52,4223,4224],{"class":105},"           ctx",[52,4226,957],{"class":58},[52,4228,3752],{"class":418},[52,4230,932],{"class":62},[52,4232,3738],{"class":105},[52,4234,408],{"class":58},[52,4236,3692],{"class":105},[52,4238,3735],{"class":58},[52,4240,3765],{"class":105},[52,4242,938],{"class":62},[52,4244,1007],{"class":58},[52,4246,4247],{"class":54,"line":337},[52,4248,4249],{"class":58},"      }\n",[52,4251,4252],{"class":54,"line":344},[52,4253,341],{"emptyLinePlaceholder":340},[52,4255,4256,4258,4260,4262,4264,4266,4268,4270,4272],{"class":54,"line":354},[52,4257,4084],{"class":105},[52,4259,957],{"class":58},[52,4261,3752],{"class":418},[52,4263,932],{"class":62},[52,4265,3362],{"class":105},[52,4267,408],{"class":58},[52,4269,3367],{"class":105},[52,4271,938],{"class":62},[52,4273,1007],{"class":58},[52,4275,4276,4278,4280,4282,4284,4286,4288,4290,4292],{"class":54,"line":367},[52,4277,4084],{"class":105},[52,4279,957],{"class":58},[52,4281,3752],{"class":418},[52,4283,932],{"class":62},[52,4285,2233],{"class":2232},[52,4287,408],{"class":58},[52,4289,3367],{"class":105},[52,4291,938],{"class":62},[52,4293,1007],{"class":58},[52,4295,4296,4298,4300,4302,4304],{"class":54,"line":387},[52,4297,4084],{"class":105},[52,4299,957],{"class":58},[52,4301,3828],{"class":418},[52,4303,422],{"class":62},[52,4305,1007],{"class":58},[52,4307,4308],{"class":54,"line":415},[52,4309,341],{"emptyLinePlaceholder":340},[52,4311,4312,4314,4316,4318,4320,4322,4324,4326,4328],{"class":54,"line":427},[52,4313,4084],{"class":105},[52,4315,957],{"class":58},[52,4317,3848],{"class":105},[52,4319,951],{"class":58},[52,4321,3597],{"class":58},[52,4323,3855],{"class":75},[52,4325,72],{"class":58},[52,4327,3661],{"class":58},[52,4329,3862],{"class":411},[52,4331,4332,4334,4336,4338,4340],{"class":54,"line":435},[52,4333,4084],{"class":105},[52,4335,957],{"class":58},[52,4337,3872],{"class":418},[52,4339,422],{"class":62},[52,4341,1007],{"class":58},[52,4343,4344],{"class":54,"line":446},[52,4345,536],{"class":58},[13,4347,4348],{},"各パラメーターは",[1467,4350,4351,4354,4357,4360,4363,4366],{},[1470,4352,4353],{},"waveStartPoint：波を書き始めるY軸の開始位置",[1470,4355,4356],{},"canvasEndX：canvasの右端のX座標（最大のcanvasX座標）",[1470,4358,4359],{},"canvasEndY：canvasの下端のY座標（最大のcanvasXY座標）",[1470,4361,4362],{},"deg：角度の初期値",[1470,4364,4365],{},"am：振幅（波の最大の高さを変化させる）",[1470,4367,4368],{},"tp：周期（１波の幅を変化させる）",[13,4370,4371],{},"最後の３つは高校の三角関数を思い出してください。それほど難しく考えず、amを大きくすれば波が大きくなり、tpの場合は大きいほどなだらかな波になります。",[13,4373,4374],{},"またctxは上部で定義したcanvasインスタンスです。今回はグローバルにしてます。",[1566,4376,4378],{"id":4377},"描画位置を定義し重ね合わせの設定をする","描画位置を定義し、重ね合わせの設定をする",[42,4380,4382],{"className":1250,"code":4381,"language":1252,"meta":47,"style":47},"var waveStartY = waveStartPoint;\nctx.globalCompositeOperation = \"destination-out\";\nctx.beginPath();\nctx.moveTo(0, waveStartY);\n",[49,4383,4384,4397,4416,4428],{"__ignoreMap":47},[52,4385,4386,4388,4391,4393,4395],{"class":54,"line":55},[52,4387,2713],{"class":65},[52,4389,4390],{"class":105}," waveStartY ",[52,4392,69],{"class":58},[52,4394,3581],{"class":105},[52,4396,1007],{"class":58},[52,4398,4399,4401,4403,4406,4408,4410,4412,4414],{"class":54,"line":83},[52,4400,3110],{"class":105},[52,4402,957],{"class":58},[52,4404,4405],{"class":105},"globalCompositeOperation ",[52,4407,69],{"class":58},[52,4409,3597],{"class":58},[52,4411,3600],{"class":75},[52,4413,72],{"class":58},[52,4415,1007],{"class":58},[52,4417,4418,4420,4422,4424,4426],{"class":54,"line":115},[52,4419,3110],{"class":105},[52,4421,957],{"class":58},[52,4423,3613],{"class":418},[52,4425,422],{"class":105},[52,4427,1007],{"class":58},[52,4429,4430,4432,4434,4436,4438,4440,4442,4445],{"class":54,"line":142},[52,4431,3110],{"class":105},[52,4433,957],{"class":58},[52,4435,3626],{"class":418},[52,4437,932],{"class":105},[52,4439,2233],{"class":2232},[52,4441,408],{"class":58},[52,4443,4444],{"class":105}," waveStartY)",[52,4446,1007],{"class":58},[13,4448,4449,4450,4453,4454],{},"画像を波線に透過させる場合にこの設定 ",[49,4451,4452],{},"ctx.globalCompositeOperation = \"destination-out\"; ","が重要です。",[2039,4455,4458],{"href":4456,"rel":4457},"https:\u002F\u002Fdeveloper.mozilla.org\u002Fja\u002Fdocs\u002FWeb\u002FAPI\u002FCanvasRenderingContext2D\u002FglobalCompositeOperation",[2043],"MDNの解説",[13,4460,4461,4462,4464],{},"この",[49,4463,3592],{},"はcanvasにおける図形どうしが重ね合わさった際にどう描画するのかを定義します。冒頭で出したこの図を見てみてください。",[729,4466],{":src":2083,":width":732},[13,4468,4469],{},"透明のくりぬきは画像の上に、赤線で範囲を指定してその中を透明色に塗りつぶすということをしています。その時に「すでに描画された画像」と「透明色に塗り潰された図形」が重ね合わさっています。",[13,4471,4472],{},"その時にを設定していると、後で描画した図形と重なり合わない部分だけが残る様に描画されます。つまり上図の５の様に一部分だけ透明になります。",[1566,4474,4475],{"id":4475},"くりぬき範囲を指定する",[42,4477,4479],{"className":1250,"code":4478,"language":1252,"meta":47,"style":47}," ctx.beginPath();\n ctx.moveTo(0, waveStartY);\n for (var x=0; x \u003C= canvasEndX; x+= 1) {\n     var y = -am*Math.sin((Math.PI\u002Ftp)*(deg+x));;\n     ctx.lineTo(x, y+waveStartY);\n }\n\n ctx.lineTo(canvasEndX,canvasEndY);\n ctx.lineTo(0,canvasEndY);\n ctx.closePath();\n",[49,4480,4481,4493,4511,4548,4596,4620,4624,4628,4646,4664],{"__ignoreMap":47},[52,4482,4483,4485,4487,4489,4491],{"class":54,"line":55},[52,4484,2461],{"class":105},[52,4486,957],{"class":58},[52,4488,3613],{"class":418},[52,4490,422],{"class":105},[52,4492,1007],{"class":58},[52,4494,4495,4497,4499,4501,4503,4505,4507,4509],{"class":54,"line":83},[52,4496,2461],{"class":105},[52,4498,957],{"class":58},[52,4500,3626],{"class":418},[52,4502,932],{"class":105},[52,4504,2233],{"class":2232},[52,4506,408],{"class":58},[52,4508,4444],{"class":105},[52,4510,1007],{"class":58},[52,4512,4513,4516,4518,4520,4522,4524,4526,4528,4531,4534,4536,4538,4540,4542,4544,4546],{"class":54,"line":115},[52,4514,4515],{"class":360}," for",[52,4517,2499],{"class":105},[52,4519,2713],{"class":65},[52,4521,3654],{"class":105},[52,4523,69],{"class":58},[52,4525,2233],{"class":2232},[52,4527,3661],{"class":58},[52,4529,4530],{"class":105}," x ",[52,4532,4533],{"class":58},"\u003C=",[52,4535,3669],{"class":105},[52,4537,3661],{"class":58},[52,4539,3654],{"class":105},[52,4541,3676],{"class":58},[52,4543,3679],{"class":2232},[52,4545,3682],{"class":105},[52,4547,364],{"class":58},[52,4549,4550,4552,4554,4556,4558,4560,4562,4564,4566,4568,4570,4572,4574,4576,4578,4580,4582,4584,4586,4588,4590,4592,4594],{"class":54,"line":142},[52,4551,3932],{"class":65},[52,4553,3692],{"class":105},[52,4555,951],{"class":58},[52,4557,3697],{"class":58},[52,4559,3562],{"class":105},[52,4561,3702],{"class":58},[52,4563,3705],{"class":105},[52,4565,957],{"class":58},[52,4567,3710],{"class":418},[52,4569,3713],{"class":62},[52,4571,3705],{"class":105},[52,4573,957],{"class":58},[52,4575,3720],{"class":105},[52,4577,2602],{"class":58},[52,4579,3567],{"class":105},[52,4581,938],{"class":62},[52,4583,3702],{"class":58},[52,4585,932],{"class":62},[52,4587,3557],{"class":105},[52,4589,3735],{"class":58},[52,4591,3738],{"class":105},[52,4593,3741],{"class":62},[52,4595,4219],{"class":58},[52,4597,4598,4600,4602,4604,4606,4608,4610,4612,4614,4616,4618],{"class":54,"line":169},[52,4599,3965],{"class":105},[52,4601,957],{"class":58},[52,4603,3752],{"class":418},[52,4605,932],{"class":62},[52,4607,3738],{"class":105},[52,4609,408],{"class":58},[52,4611,3692],{"class":105},[52,4613,3735],{"class":58},[52,4615,3765],{"class":105},[52,4617,938],{"class":62},[52,4619,1007],{"class":58},[52,4621,4622],{"class":54,"line":302},[52,4623,1081],{"class":58},[52,4625,4626],{"class":54,"line":308},[52,4627,341],{"emptyLinePlaceholder":340},[52,4629,4630,4632,4634,4636,4639,4641,4644],{"class":54,"line":318},[52,4631,2461],{"class":105},[52,4633,957],{"class":58},[52,4635,3752],{"class":418},[52,4637,4638],{"class":105},"(canvasEndX",[52,4640,408],{"class":58},[52,4642,4643],{"class":105},"canvasEndY)",[52,4645,1007],{"class":58},[52,4647,4648,4650,4652,4654,4656,4658,4660,4662],{"class":54,"line":328},[52,4649,2461],{"class":105},[52,4651,957],{"class":58},[52,4653,3752],{"class":418},[52,4655,932],{"class":105},[52,4657,2233],{"class":2232},[52,4659,408],{"class":58},[52,4661,4643],{"class":105},[52,4663,1007],{"class":58},[52,4665,4666,4668,4670,4672,4674],{"class":54,"line":337},[52,4667,2461],{"class":105},[52,4669,957],{"class":58},[52,4671,3828],{"class":418},[52,4673,422],{"class":105},[52,4675,1007],{"class":58},[13,4677,4678],{},"次にくりぬきの範囲を指定します。上図でいうと１〜４を指します。フォトショップやイラストレーターを使っている人なら「パスで選択範囲を指定」という意味がわかると思います。それをここでjsを用いて行っています。",[13,4680,4681],{},"念のために解説すると、パスというのは図形を構成する点（座標）みたいなものです。そして図形はその点を結ぶことで描画できます。四角形であれば点（頂点）は４つあって、それを一筆書きすると四角ができますよね。その一筆書きの順路と位置をこのコードで定義しています。",[1687,4683,4684,4687,4690,4693],{},[1470,4685,4686],{},"ctx.beginPath()でパスの指定を開始します。",[1470,4688,4689],{},"ctx.moveTo(X,Y)で指定した座標にパスを移動させます。",[1470,4691,4692],{},"ctx.lineTo(nextX,nextY)で次の座標を指定しパスを移動させつつ線をひきます。",[1470,4694,4695],{},"ctx.closePath()でパスの指定を終了します。",[13,4697,4698],{},"今回はこのパスの指定を",[1687,4700,4701,4704,4707,4710],{},[1470,4702,4703],{},"画像の左端（X=０）、指定した波の開始地点（waveStartY）より（0, waveStartY）からパスを開始。",[1470,4705,4706],{},"画像の右端（X＝canvasEndX）までfor文を用いて、さらに三角関数の式にx座標を入れて、波線を描く様にパスを指定していく。",[1470,4708,4709],{},"画像の右端までついたら、画像の右下端、左下端を通って、開始地点に戻る。",[1470,4711,4712],{},"パスを閉じる。",[13,4714,4715],{},"この様にしています。",[1566,4717,4718],{"id":4718},"選択範囲を透明色で塗りつぶす",[13,4720,4721],{},"上記の方法で選択範囲を指定すれば、あとは塗りつぶすだけです。",[42,4723,4725],{"className":1250,"code":4724,"language":1252,"meta":47,"style":47},"ctx.fillStyle = \"rgba(255,255,255,1)\"; \u002F\u002Fopacity 1\nctx.fill();\n",[49,4726,4727,4748],{"__ignoreMap":47},[52,4728,4729,4731,4733,4736,4738,4740,4742,4744,4746],{"class":54,"line":55},[52,4730,3110],{"class":105},[52,4732,957],{"class":58},[52,4734,4735],{"class":105},"fillStyle ",[52,4737,69],{"class":58},[52,4739,3597],{"class":58},[52,4741,3855],{"class":75},[52,4743,72],{"class":58},[52,4745,3661],{"class":58},[52,4747,3862],{"class":411},[52,4749,4750,4752,4754,4756,4758],{"class":54,"line":83},[52,4751,3110],{"class":105},[52,4753,957],{"class":58},[52,4755,3872],{"class":418},[52,4757,422],{"class":105},[52,4759,1007],{"class":58},[13,4761,4762,4764,4765,4768],{},[49,4763,3848],{},"で塗り潰しの色を設定できます。ここは実際、",[49,4766,4767],{},"globalCompositeOperation = \"destination-out\";","を設定していれば何色でも大丈夫です。ですが念のため透明色を設定。",[13,4770,2766,4771,4774,4775,4778],{},[49,4772,4773],{},"fill()","で指定したスタイル、パスの範囲で塗り潰しを行います。g",[49,4776,4777],{},"lobalCompositeOperation = \"destination-out\";","が設定されているので塗り潰された部分と画像の重なり合う部分以外が残り、重なり部分は透明になります。",[13,4780,4781],{},"ここまで来れば以下の様に波線にくり抜かれた画像が得られます。",[729,4783],{":src":2035,":width":732},[17,4785,4786],{"id":4786},"ループでアニメーションを実装",[13,4788,4789,4790,4793,4794,4796],{},"定義した",[49,4791,4792],{},"imageSet()","と",[49,4795,4028],{},"を用いて以下のループを設定します。",[42,4798,4800],{"className":1250,"code":4799,"language":1252,"meta":47,"style":47},"function loop(){\n      setInterval(function(){\n      imageSet(image,canvasEndX,canvasEndY);\n      waveDrawing(waveStartPoint,canvasEndX,canvasEndY,degree,amplitude,period);\n      degree += 12; \u002F\u002F12はなんとなく\n    },30)\n}\n",[49,4801,4802,4811,4822,4843,4876,4892,4902],{"__ignoreMap":47},[52,4803,4804,4806,4809],{"class":54,"line":55},[52,4805,2418],{"class":65},[52,4807,4808],{"class":418}," loop",[52,4810,2424],{"class":58},[52,4812,4813,4816,4818,4820],{"class":54,"line":83},[52,4814,4815],{"class":418},"      setInterval",[52,4817,932],{"class":62},[52,4819,2418],{"class":65},[52,4821,2424],{"class":58},[52,4823,4824,4827,4829,4831,4833,4835,4837,4839,4841],{"class":54,"line":115},[52,4825,4826],{"class":418},"      imageSet",[52,4828,932],{"class":62},[52,4830,2639],{"class":105},[52,4832,408],{"class":58},[52,4834,3362],{"class":105},[52,4836,408],{"class":58},[52,4838,3367],{"class":105},[52,4840,938],{"class":62},[52,4842,1007],{"class":58},[52,4844,4845,4848,4850,4852,4854,4856,4858,4860,4862,4864,4866,4868,4870,4872,4874],{"class":54,"line":142},[52,4846,4847],{"class":418},"      waveDrawing",[52,4849,932],{"class":62},[52,4851,3381],{"class":105},[52,4853,408],{"class":58},[52,4855,3362],{"class":105},[52,4857,408],{"class":58},[52,4859,3367],{"class":105},[52,4861,408],{"class":58},[52,4863,3394],{"class":105},[52,4865,408],{"class":58},[52,4867,3399],{"class":105},[52,4869,408],{"class":58},[52,4871,3404],{"class":105},[52,4873,938],{"class":62},[52,4875,1007],{"class":58},[52,4877,4878,4881,4884,4887,4889],{"class":54,"line":169},[52,4879,4880],{"class":105},"      degree",[52,4882,4883],{"class":58}," +=",[52,4885,4886],{"class":2232}," 12",[52,4888,3661],{"class":58},[52,4890,4891],{"class":411}," \u002F\u002F12はなんとなく\n",[52,4893,4894,4897,4900],{"class":54,"line":302},[52,4895,4896],{"class":58},"    },",[52,4898,4899],{"class":2232},"30",[52,4901,1015],{"class":62},[52,4903,4904],{"class":54,"line":308},[52,4905,536],{"class":58},[13,4907,4908,4909,4912,4913,4915],{},"ここで一番大切なのは",[49,4910,4911],{},"deggre +=4","の様に",[49,4914,4028],{},"で用いる初期角度を足していくことです。こうすることで波がウネウネします。degreeの加算が多いほど波が早くなります。50以上にすると荒波になります笑",[13,4917,4918,4919,4922,4923,4925],{},"そしてこの",[49,4920,4921],{},"loop()","の関数を",[49,4924,2978],{},"で呼び出して発火させます。",[42,4927,4929],{"className":1250,"code":4928,"language":1252,"meta":47,"style":47},"image.onload = function(){\n    initDraw();\n    loop();\n}\n",[49,4930,4931,4945,4953,4962],{"__ignoreMap":47},[52,4932,4933,4935,4937,4939,4941,4943],{"class":54,"line":55},[52,4934,2639],{"class":105},[52,4936,957],{"class":58},[52,4938,2617],{"class":418},[52,4940,951],{"class":58},[52,4942,2622],{"class":65},[52,4944,2424],{"class":58},[52,4946,4947,4949,4951],{"class":54,"line":83},[52,4948,3222],{"class":418},[52,4950,422],{"class":62},[52,4952,1007],{"class":58},[52,4954,4955,4958,4960],{"class":54,"line":115},[52,4956,4957],{"class":418},"    loop",[52,4959,422],{"class":62},[52,4961,1007],{"class":58},[52,4963,4964],{"class":54,"line":142},[52,4965,536],{"class":58},[17,4967,4968],{"id":4968},"コード全体",[13,4970,4971],{},"上記をまとめたコードがこちらです。",[42,4973,4975],{"className":1250,"code":4974,"language":1252,"meta":47,"style":47},"window.onload = init();\nfunction init(){\n    initAnimation();\n\n    function initAnimation(){\n        var canvas = document.getElementById('canvas');\n        var ctx = canvas.getContext('2d');\n\n        var imagePath = ('.\u002Fsample.jpg');\n        var image = new Image();\n        image.src = imagePath;\n\n        \u002F\u002Fset canvas width and height\n        canvas.width = Number(window.innerWidth);\n        canvas.height = Number(canvas.width\u002F2);\n        image.onload = function(){\n                initDraw();\n                loop();\n            }\n            \n        var canvasEndX = canvas.width;\n        var canvasEndY = canvas.height;\n        var waveStartPoint = canvasEndY-150;\n\n        var amplitude = 30;\n        var period = 600;\n        var degree = 0;\n\n        function initDraw(){\n            imageSet(image,canvasEndX,canvasEndY);\n            waveDrawing(waveStartPoint,canvasEndX,canvasEndY,degree,amplitude,period);\n        }\n\n        function loop(){\n            setInterval(function(){\n                imageSet(image,canvasEndX,canvasEndY);\n                waveDrawing(waveStartPoint,canvasEndX,canvasEndY,degree,amplitude,period);\n                degree += 12;\n            },30)\n        }\n\n        function imageSet(imageObj,canvasEndX,canvasEndY){\n            var imgWidth = imageObj.width;\n            var imgHeight = imageObj.height;\n\n            ctx.globalCompositeOperation = \"destination-over\";\n            ctx.drawImage(image,0,0,imgWidth,imgHeight,0,0,canvasEndX,canvasEndY);\n        }\n\n        function waveDrawing(waveStartPoint,canvasEndX,canvasEndY,deg,am,tp){\n            var waveStartY = waveStartPoint;\n            ctx.globalCompositeOperation = \"destination-out\";\n            ctx.beginPath();\n            ctx.moveTo(0, waveStartY);\n\n            for (var x=0; x \u003C= canvasEndX; x+= 1) {\n                var y = -am*Math.sin((Math.PI\u002Ftp)*(deg+x));\n                ctx.lineTo(x, y+waveStartY);\n            }\n\n            ctx.lineTo(canvasEndX,canvasEndY);\n            ctx.lineTo(0,canvasEndY);\n            ctx.closePath();\n\n            ctx.fillStyle = \"rgba(255,255,255,1)\"; \u002F\u002Fopacity 1\n            ctx.fill();\n        }\n\n    }\n}\n",[49,4976,4977,4995,5003,5012,5016,5025,5051,5077,5081,5101,5117,5132,5136,5141,5166,5194,5208,5217,5226,5230,5235,5251,5267,5283,5287,5300,5314,5327,5331,5340,5361,5394,5399,5403,5411,5422,5443,5476,5487,5496,5500,5504,5525,5542,5559,5564,5585,5634,5639,5644,5677,5690,5709,5722,5743,5748,5784,5834,5860,5865,5870,5891,5912,5925,5930,5951,5964,5969,5974,5979],{"__ignoreMap":47},[52,4978,4979,4981,4983,4986,4988,4991,4993],{"class":54,"line":55},[52,4980,2569],{"class":105},[52,4982,957],{"class":58},[52,4984,4985],{"class":105},"onload ",[52,4987,69],{"class":58},[52,4989,4990],{"class":418}," init",[52,4992,422],{"class":105},[52,4994,1007],{"class":58},[52,4996,4997,4999,5001],{"class":54,"line":83},[52,4998,2418],{"class":65},[52,5000,4990],{"class":418},[52,5002,2424],{"class":58},[52,5004,5005,5008,5010],{"class":54,"line":115},[52,5006,5007],{"class":418},"    initAnimation",[52,5009,422],{"class":62},[52,5011,1007],{"class":58},[52,5013,5014],{"class":54,"line":142},[52,5015,341],{"emptyLinePlaceholder":340},[52,5017,5018,5021,5023],{"class":54,"line":169},[52,5019,5020],{"class":65},"    function",[52,5022,2421],{"class":418},[52,5024,2424],{"class":58},[52,5026,5027,5029,5031,5033,5035,5037,5039,5041,5043,5045,5047,5049],{"class":54,"line":302},[52,5028,3689],{"class":65},[52,5030,2432],{"class":105},[52,5032,951],{"class":58},[52,5034,2437],{"class":105},[52,5036,957],{"class":58},[52,5038,2442],{"class":418},[52,5040,932],{"class":62},[52,5042,376],{"class":58},[52,5044,2333],{"class":75},[52,5046,376],{"class":58},[52,5048,938],{"class":62},[52,5050,1007],{"class":58},[52,5052,5053,5055,5057,5059,5061,5063,5065,5067,5069,5071,5073,5075],{"class":54,"line":308},[52,5054,3689],{"class":65},[52,5056,2461],{"class":105},[52,5058,951],{"class":58},[52,5060,2432],{"class":105},[52,5062,957],{"class":58},[52,5064,2470],{"class":418},[52,5066,932],{"class":62},[52,5068,376],{"class":58},[52,5070,2477],{"class":75},[52,5072,376],{"class":58},[52,5074,938],{"class":62},[52,5076,1007],{"class":58},[52,5078,5079],{"class":54,"line":318},[52,5080,341],{"emptyLinePlaceholder":340},[52,5082,5083,5085,5087,5089,5091,5093,5095,5097,5099],{"class":54,"line":328},[52,5084,3689],{"class":65},[52,5086,2494],{"class":105},[52,5088,951],{"class":58},[52,5090,2499],{"class":62},[52,5092,376],{"class":58},[52,5094,2504],{"class":75},[52,5096,376],{"class":58},[52,5098,938],{"class":62},[52,5100,1007],{"class":58},[52,5102,5103,5105,5107,5109,5111,5113,5115],{"class":54,"line":337},[52,5104,3689],{"class":65},[52,5106,2517],{"class":105},[52,5108,951],{"class":58},[52,5110,2522],{"class":58},[52,5112,2525],{"class":418},[52,5114,422],{"class":62},[52,5116,1007],{"class":58},[52,5118,5119,5122,5124,5126,5128,5130],{"class":54,"line":344},[52,5120,5121],{"class":105},"        image",[52,5123,957],{"class":58},[52,5125,2539],{"class":105},[52,5127,951],{"class":58},[52,5129,2494],{"class":105},[52,5131,1007],{"class":58},[52,5133,5134],{"class":54,"line":354},[52,5135,341],{"emptyLinePlaceholder":340},[52,5137,5138],{"class":54,"line":367},[52,5139,5140],{"class":411},"        \u002F\u002Fset canvas width and height\n",[52,5142,5143,5146,5148,5150,5152,5154,5156,5158,5160,5162,5164],{"class":54,"line":387},[52,5144,5145],{"class":105},"        canvas",[52,5147,957],{"class":58},[52,5149,2559],{"class":105},[52,5151,951],{"class":58},[52,5153,2564],{"class":418},[52,5155,932],{"class":62},[52,5157,2569],{"class":105},[52,5159,957],{"class":58},[52,5161,2574],{"class":105},[52,5163,938],{"class":62},[52,5165,1007],{"class":58},[52,5167,5168,5170,5172,5174,5176,5178,5180,5182,5184,5186,5188,5190,5192],{"class":54,"line":415},[52,5169,5145],{"class":105},[52,5171,957],{"class":58},[52,5173,2587],{"class":105},[52,5175,951],{"class":58},[52,5177,2564],{"class":418},[52,5179,932],{"class":62},[52,5181,2333],{"class":105},[52,5183,957],{"class":58},[52,5185,2559],{"class":105},[52,5187,2602],{"class":58},[52,5189,35],{"class":2232},[52,5191,938],{"class":62},[52,5193,1007],{"class":58},[52,5195,5196,5198,5200,5202,5204,5206],{"class":54,"line":427},[52,5197,5121],{"class":105},[52,5199,957],{"class":58},[52,5201,2617],{"class":418},[52,5203,951],{"class":58},[52,5205,2622],{"class":65},[52,5207,2424],{"class":58},[52,5209,5210,5213,5215],{"class":54,"line":435},[52,5211,5212],{"class":418},"                initDraw",[52,5214,422],{"class":62},[52,5216,1007],{"class":58},[52,5218,5219,5222,5224],{"class":54,"line":446},[52,5220,5221],{"class":418},"                loop",[52,5223,422],{"class":62},[52,5225,1007],{"class":58},[52,5227,5228],{"class":54,"line":480},[52,5229,2251],{"class":58},[52,5231,5232],{"class":54,"line":509},[52,5233,5234],{"class":62},"            \n",[52,5236,5237,5239,5241,5243,5245,5247,5249],{"class":54,"line":539},[52,5238,3689],{"class":65},[52,5240,3669],{"class":105},[52,5242,951],{"class":58},[52,5244,2432],{"class":105},[52,5246,957],{"class":58},[52,5248,2559],{"class":105},[52,5250,1007],{"class":58},[52,5252,5253,5255,5257,5259,5261,5263,5265],{"class":54,"line":547},[52,5254,3689],{"class":65},[52,5256,3281],{"class":105},[52,5258,951],{"class":58},[52,5260,2432],{"class":105},[52,5262,957],{"class":58},[52,5264,2587],{"class":105},[52,5266,1007],{"class":58},[52,5268,5269,5271,5273,5275,5277,5279,5281],{"class":54,"line":553},[52,5270,3689],{"class":65},[52,5272,3581],{"class":105},[52,5274,951],{"class":58},[52,5276,3281],{"class":105},[52,5278,3284],{"class":58},[52,5280,3287],{"class":2232},[52,5282,1007],{"class":58},[52,5284,5285],{"class":54,"line":559},[52,5286,341],{"emptyLinePlaceholder":340},[52,5288,5289,5291,5294,5296,5298],{"class":54,"line":564},[52,5290,3689],{"class":65},[52,5292,5293],{"class":105}," amplitude",[52,5295,951],{"class":58},[52,5297,3305],{"class":2232},[52,5299,1007],{"class":58},[52,5301,5302,5304,5307,5309,5312],{"class":54,"line":569},[52,5303,3689],{"class":65},[52,5305,5306],{"class":105}," period",[52,5308,951],{"class":58},[52,5310,5311],{"class":2232}," 600",[52,5313,1007],{"class":58},[52,5315,5316,5318,5321,5323,5325],{"class":54,"line":1106},[52,5317,3689],{"class":65},[52,5319,5320],{"class":105}," degree",[52,5322,951],{"class":58},[52,5324,3333],{"class":2232},[52,5326,1007],{"class":58},[52,5328,5329],{"class":54,"line":1135},[52,5330,341],{"emptyLinePlaceholder":340},[52,5332,5333,5336,5338],{"class":54,"line":1164},[52,5334,5335],{"class":65},"        function",[52,5337,3346],{"class":418},[52,5339,2424],{"class":58},[52,5341,5342,5345,5347,5349,5351,5353,5355,5357,5359],{"class":54,"line":1193},[52,5343,5344],{"class":418},"            imageSet",[52,5346,932],{"class":62},[52,5348,2639],{"class":105},[52,5350,408],{"class":58},[52,5352,3362],{"class":105},[52,5354,408],{"class":58},[52,5356,3367],{"class":105},[52,5358,938],{"class":62},[52,5360,1007],{"class":58},[52,5362,5363,5366,5368,5370,5372,5374,5376,5378,5380,5382,5384,5386,5388,5390,5392],{"class":54,"line":1200},[52,5364,5365],{"class":418},"            waveDrawing",[52,5367,932],{"class":62},[52,5369,3381],{"class":105},[52,5371,408],{"class":58},[52,5373,3362],{"class":105},[52,5375,408],{"class":58},[52,5377,3367],{"class":105},[52,5379,408],{"class":58},[52,5381,3394],{"class":105},[52,5383,408],{"class":58},[52,5385,3399],{"class":105},[52,5387,408],{"class":58},[52,5389,3404],{"class":105},[52,5391,938],{"class":62},[52,5393,1007],{"class":58},[52,5395,5396],{"class":54,"line":1205},[52,5397,5398],{"class":58},"        }\n",[52,5400,5401],{"class":54,"line":1210},[52,5402,341],{"emptyLinePlaceholder":340},[52,5404,5405,5407,5409],{"class":54,"line":1215},[52,5406,5335],{"class":65},[52,5408,4808],{"class":418},[52,5410,2424],{"class":58},[52,5412,5413,5416,5418,5420],{"class":54,"line":1220},[52,5414,5415],{"class":418},"            setInterval",[52,5417,932],{"class":62},[52,5419,2418],{"class":65},[52,5421,2424],{"class":58},[52,5423,5424,5427,5429,5431,5433,5435,5437,5439,5441],{"class":54,"line":3800},[52,5425,5426],{"class":418},"                imageSet",[52,5428,932],{"class":62},[52,5430,2639],{"class":105},[52,5432,408],{"class":58},[52,5434,3362],{"class":105},[52,5436,408],{"class":58},[52,5438,3367],{"class":105},[52,5440,938],{"class":62},[52,5442,1007],{"class":58},[52,5444,5445,5448,5450,5452,5454,5456,5458,5460,5462,5464,5466,5468,5470,5472,5474],{"class":54,"line":3821},[52,5446,5447],{"class":418},"                waveDrawing",[52,5449,932],{"class":62},[52,5451,3381],{"class":105},[52,5453,408],{"class":58},[52,5455,3362],{"class":105},[52,5457,408],{"class":58},[52,5459,3367],{"class":105},[52,5461,408],{"class":58},[52,5463,3394],{"class":105},[52,5465,408],{"class":58},[52,5467,3399],{"class":105},[52,5469,408],{"class":58},[52,5471,3404],{"class":105},[52,5473,938],{"class":62},[52,5475,1007],{"class":58},[52,5477,5478,5481,5483,5485],{"class":54,"line":3835},[52,5479,5480],{"class":105},"                degree",[52,5482,4883],{"class":58},[52,5484,4886],{"class":2232},[52,5486,1007],{"class":58},[52,5488,5489,5492,5494],{"class":54,"line":3840},[52,5490,5491],{"class":58},"            },",[52,5493,4899],{"class":2232},[52,5495,1015],{"class":62},[52,5497,5498],{"class":54,"line":3865},[52,5499,5398],{"class":58},[52,5501,5502],{"class":54,"line":3879},[52,5503,341],{"emptyLinePlaceholder":340},[52,5505,5507,5509,5511,5513,5515,5517,5519,5521,5523],{"class":54,"line":5506},42,[52,5508,5335],{"class":65},[52,5510,3423],{"class":418},[52,5512,932],{"class":58},[52,5514,3428],{"class":986},[52,5516,408],{"class":58},[52,5518,3362],{"class":986},[52,5520,408],{"class":58},[52,5522,3367],{"class":986},[52,5524,3439],{"class":58},[52,5526,5527,5530,5532,5534,5536,5538,5540],{"class":54,"line":4},[52,5528,5529],{"class":65},"            var",[52,5531,3446],{"class":105},[52,5533,951],{"class":58},[52,5535,3451],{"class":105},[52,5537,957],{"class":58},[52,5539,2559],{"class":105},[52,5541,1007],{"class":58},[52,5543,5545,5547,5549,5551,5553,5555,5557],{"class":54,"line":5544},44,[52,5546,5529],{"class":65},[52,5548,3464],{"class":105},[52,5550,951],{"class":58},[52,5552,3451],{"class":105},[52,5554,957],{"class":58},[52,5556,2587],{"class":105},[52,5558,1007],{"class":58},[52,5560,5562],{"class":54,"line":5561},45,[52,5563,341],{"emptyLinePlaceholder":340},[52,5565,5567,5570,5572,5574,5576,5578,5581,5583],{"class":54,"line":5566},46,[52,5568,5569],{"class":105},"            ctx",[52,5571,957],{"class":58},[52,5573,3592],{"class":105},[52,5575,951],{"class":58},[52,5577,3597],{"class":58},[52,5579,5580],{"class":75},"destination-over",[52,5582,72],{"class":58},[52,5584,1007],{"class":58},[52,5586,5588,5590,5592,5594,5596,5598,5600,5602,5604,5606,5608,5610,5612,5614,5616,5618,5620,5622,5624,5626,5628,5630,5632],{"class":54,"line":5587},47,[52,5589,5569],{"class":105},[52,5591,957],{"class":58},[52,5593,2634],{"class":418},[52,5595,932],{"class":62},[52,5597,2639],{"class":105},[52,5599,408],{"class":58},[52,5601,2233],{"class":2232},[52,5603,408],{"class":58},[52,5605,2233],{"class":2232},[52,5607,408],{"class":58},[52,5609,3500],{"class":105},[52,5611,408],{"class":58},[52,5613,3505],{"class":105},[52,5615,408],{"class":58},[52,5617,2233],{"class":2232},[52,5619,408],{"class":58},[52,5621,2233],{"class":2232},[52,5623,408],{"class":58},[52,5625,3362],{"class":105},[52,5627,408],{"class":58},[52,5629,3367],{"class":105},[52,5631,938],{"class":62},[52,5633,1007],{"class":58},[52,5635,5637],{"class":54,"line":5636},48,[52,5638,5398],{"class":58},[52,5640,5642],{"class":54,"line":5641},49,[52,5643,341],{"emptyLinePlaceholder":340},[52,5645,5647,5649,5651,5653,5655,5657,5659,5661,5663,5665,5667,5669,5671,5673,5675],{"class":54,"line":5646},50,[52,5648,5335],{"class":65},[52,5650,3540],{"class":418},[52,5652,932],{"class":58},[52,5654,3381],{"class":986},[52,5656,408],{"class":58},[52,5658,3362],{"class":986},[52,5660,408],{"class":58},[52,5662,3367],{"class":986},[52,5664,408],{"class":58},[52,5666,3557],{"class":986},[52,5668,408],{"class":58},[52,5670,3562],{"class":986},[52,5672,408],{"class":58},[52,5674,3567],{"class":986},[52,5676,3439],{"class":58},[52,5678,5680,5682,5684,5686,5688],{"class":54,"line":5679},51,[52,5681,5529],{"class":65},[52,5683,3576],{"class":105},[52,5685,951],{"class":58},[52,5687,3581],{"class":105},[52,5689,1007],{"class":58},[52,5691,5693,5695,5697,5699,5701,5703,5705,5707],{"class":54,"line":5692},52,[52,5694,5569],{"class":105},[52,5696,957],{"class":58},[52,5698,3592],{"class":105},[52,5700,951],{"class":58},[52,5702,3597],{"class":58},[52,5704,3600],{"class":75},[52,5706,72],{"class":58},[52,5708,1007],{"class":58},[52,5710,5712,5714,5716,5718,5720],{"class":54,"line":5711},53,[52,5713,5569],{"class":105},[52,5715,957],{"class":58},[52,5717,3613],{"class":418},[52,5719,422],{"class":62},[52,5721,1007],{"class":58},[52,5723,5725,5727,5729,5731,5733,5735,5737,5739,5741],{"class":54,"line":5724},54,[52,5726,5569],{"class":105},[52,5728,957],{"class":58},[52,5730,3626],{"class":418},[52,5732,932],{"class":62},[52,5734,2233],{"class":2232},[52,5736,408],{"class":58},[52,5738,3576],{"class":105},[52,5740,938],{"class":62},[52,5742,1007],{"class":58},[52,5744,5746],{"class":54,"line":5745},55,[52,5747,341],{"emptyLinePlaceholder":340},[52,5749,5751,5754,5756,5758,5760,5762,5764,5766,5768,5770,5772,5774,5776,5778,5780,5782],{"class":54,"line":5750},56,[52,5752,5753],{"class":360},"            for",[52,5755,2499],{"class":62},[52,5757,2713],{"class":65},[52,5759,3654],{"class":105},[52,5761,69],{"class":58},[52,5763,2233],{"class":2232},[52,5765,3661],{"class":58},[52,5767,3654],{"class":105},[52,5769,3666],{"class":58},[52,5771,3669],{"class":105},[52,5773,3661],{"class":58},[52,5775,3654],{"class":105},[52,5777,3676],{"class":58},[52,5779,3679],{"class":2232},[52,5781,3682],{"class":62},[52,5783,364],{"class":58},[52,5785,5787,5790,5792,5794,5796,5798,5800,5802,5804,5806,5808,5810,5812,5814,5816,5818,5820,5822,5824,5826,5828,5830,5832],{"class":54,"line":5786},57,[52,5788,5789],{"class":65},"                var",[52,5791,3692],{"class":105},[52,5793,951],{"class":58},[52,5795,3697],{"class":58},[52,5797,3562],{"class":105},[52,5799,3702],{"class":58},[52,5801,3705],{"class":105},[52,5803,957],{"class":58},[52,5805,3710],{"class":418},[52,5807,3713],{"class":62},[52,5809,3705],{"class":105},[52,5811,957],{"class":58},[52,5813,3720],{"class":105},[52,5815,2602],{"class":58},[52,5817,3567],{"class":105},[52,5819,938],{"class":62},[52,5821,3702],{"class":58},[52,5823,932],{"class":62},[52,5825,3557],{"class":105},[52,5827,3735],{"class":58},[52,5829,3738],{"class":105},[52,5831,3741],{"class":62},[52,5833,1007],{"class":58},[52,5835,5837,5840,5842,5844,5846,5848,5850,5852,5854,5856,5858],{"class":54,"line":5836},58,[52,5838,5839],{"class":105},"                ctx",[52,5841,957],{"class":58},[52,5843,3752],{"class":418},[52,5845,932],{"class":62},[52,5847,3738],{"class":105},[52,5849,408],{"class":58},[52,5851,3692],{"class":105},[52,5853,3735],{"class":58},[52,5855,3765],{"class":105},[52,5857,938],{"class":62},[52,5859,1007],{"class":58},[52,5861,5863],{"class":54,"line":5862},59,[52,5864,2251],{"class":58},[52,5866,5868],{"class":54,"line":5867},60,[52,5869,341],{"emptyLinePlaceholder":340},[52,5871,5873,5875,5877,5879,5881,5883,5885,5887,5889],{"class":54,"line":5872},61,[52,5874,5569],{"class":105},[52,5876,957],{"class":58},[52,5878,3752],{"class":418},[52,5880,932],{"class":62},[52,5882,3362],{"class":105},[52,5884,408],{"class":58},[52,5886,3367],{"class":105},[52,5888,938],{"class":62},[52,5890,1007],{"class":58},[52,5892,5894,5896,5898,5900,5902,5904,5906,5908,5910],{"class":54,"line":5893},62,[52,5895,5569],{"class":105},[52,5897,957],{"class":58},[52,5899,3752],{"class":418},[52,5901,932],{"class":62},[52,5903,2233],{"class":2232},[52,5905,408],{"class":58},[52,5907,3367],{"class":105},[52,5909,938],{"class":62},[52,5911,1007],{"class":58},[52,5913,5915,5917,5919,5921,5923],{"class":54,"line":5914},63,[52,5916,5569],{"class":105},[52,5918,957],{"class":58},[52,5920,3828],{"class":418},[52,5922,422],{"class":62},[52,5924,1007],{"class":58},[52,5926,5928],{"class":54,"line":5927},64,[52,5929,341],{"emptyLinePlaceholder":340},[52,5931,5933,5935,5937,5939,5941,5943,5945,5947,5949],{"class":54,"line":5932},65,[52,5934,5569],{"class":105},[52,5936,957],{"class":58},[52,5938,3848],{"class":105},[52,5940,951],{"class":58},[52,5942,3597],{"class":58},[52,5944,3855],{"class":75},[52,5946,72],{"class":58},[52,5948,3661],{"class":58},[52,5950,3862],{"class":411},[52,5952,5954,5956,5958,5960,5962],{"class":54,"line":5953},66,[52,5955,5569],{"class":105},[52,5957,957],{"class":58},[52,5959,3872],{"class":418},[52,5961,422],{"class":62},[52,5963,1007],{"class":58},[52,5965,5967],{"class":54,"line":5966},67,[52,5968,5398],{"class":58},[52,5970,5972],{"class":54,"line":5971},68,[52,5973,341],{"emptyLinePlaceholder":340},[52,5975,5977],{"class":54,"line":5976},69,[52,5978,2696],{"class":58},[52,5980,5982],{"class":54,"line":5981},70,[52,5983,536],{"class":58},[17,5985,5986],{"id":5986},"波の様子を変えたい場合",[13,5988,5989],{},"このコードの場合は",[42,5991,5993],{"className":1250,"code":5992,"language":1252,"meta":47,"style":47},"var amplitude = 30;\nvar period = 600;\n\nfunction loop(){\n      ...\n      degree += 12;\n      },30)\n}\n",[49,5994,5995,6007,6019,6023,6031,6036,6046,6055],{"__ignoreMap":47},[52,5996,5997,5999,6001,6003,6005],{"class":54,"line":55},[52,5998,2713],{"class":65},[52,6000,3300],{"class":105},[52,6002,69],{"class":58},[52,6004,3305],{"class":2232},[52,6006,1007],{"class":58},[52,6008,6009,6011,6013,6015,6017],{"class":54,"line":83},[52,6010,2713],{"class":65},[52,6012,3314],{"class":105},[52,6014,69],{"class":58},[52,6016,5311],{"class":2232},[52,6018,1007],{"class":58},[52,6020,6021],{"class":54,"line":115},[52,6022,341],{"emptyLinePlaceholder":340},[52,6024,6025,6027,6029],{"class":54,"line":142},[52,6026,2418],{"class":65},[52,6028,4808],{"class":418},[52,6030,2424],{"class":58},[52,6032,6033],{"class":54,"line":169},[52,6034,6035],{"class":58},"      ...\n",[52,6037,6038,6040,6042,6044],{"class":54,"line":302},[52,6039,4880],{"class":105},[52,6041,4883],{"class":58},[52,6043,4886],{"class":2232},[52,6045,1007],{"class":58},[52,6047,6048,6051,6053],{"class":54,"line":308},[52,6049,6050],{"class":58},"      },",[52,6052,4899],{"class":2232},[52,6054,1015],{"class":105},[52,6056,6057],{"class":54,"line":318},[52,6058,536],{"class":105},[13,6060,6061,6064,6065,6068,6069,6072,6073,6075],{},[49,6062,6063],{},"amplitude（振幅）","で波の最大の高さ、",[49,6066,6067],{},"period（周期）","で１波の周期、",[49,6070,6071],{},"degree +=","で波の速さを変化させることができます。他にも",[49,6074,4028],{},"で定義した三角関数の式を変えることで単純なサイン波でなく、複雑な波を描画できます。",[17,6077,6078],{"id":6078},"最後に",[13,6080,6081],{},"以上がcanvasを用いて画像をウネウネさせる方法です。canvasではこの様に複雑なアニメーションを用いた描画が可能です。jsで記述するのでFlashのActionScriptとかやっていた人は馴染みがあるかもしれません。",[1414,6083,6084],{},"html pre.shiki code .sAklC, html code.shiki .sAklC{--shiki-default:#89DDFF}html pre.shiki code .s-wAU, html code.shiki .s-wAU{--shiki-default:#F07178}html pre.shiki code .sJ14y, html code.shiki .sJ14y{--shiki-default:#C792EA}html pre.shiki code .sfyAc, html code.shiki .sfyAc{--shiki-default:#C3E88D}html pre.shiki code .s0W1g, html code.shiki .s0W1g{--shiki-default:#BABED8}html pre.shiki code .s5Dmg, html code.shiki .s5Dmg{--shiki-default:#FFCB6B}html pre.shiki code .s6YsC, html code.shiki .s6YsC{--shiki-default:#B2CCD6}html pre.shiki code .sx098, html code.shiki .sx098{--shiki-default:#F78C6C}html pre.shiki code .sdLwU, html code.shiki .sdLwU{--shiki-default:#82AAFF}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html pre.shiki code .s7ZW3, html code.shiki .s7ZW3{--shiki-default:#BABED8;--shiki-default-font-style:italic}html pre.shiki code .s6cf3, html code.shiki .s6cf3{--shiki-default:#89DDFF;--shiki-default-font-style:italic}html pre.shiki code .sC9rS, html code.shiki .sC9rS{--shiki-default:#464B5D;--shiki-default-font-style:italic}",{"title":47,"searchDepth":115,"depth":115,"links":6086},[6087,6088,6091,6096,6104,6105,6106,6107],{"id":2048,"depth":83,"text":2048},{"id":2068,"depth":83,"text":2068,"children":6089},[6090],{"id":2106,"depth":115,"text":2107},{"id":2113,"depth":83,"text":2113,"children":6092},[6093],{"id":2405,"depth":115,"text":2405,"children":6094},[6095],{"id":2996,"depth":142,"text":2997},{"id":3192,"depth":83,"text":3192,"children":6097},[6098,6099],{"id":3901,"depth":115,"text":3902},{"id":4022,"depth":115,"text":4023,"children":6100},[6101,6102,6103],{"id":4377,"depth":142,"text":4378},{"id":4475,"depth":142,"text":4475},{"id":4718,"depth":142,"text":4718},{"id":4786,"depth":83,"text":4786},{"id":4968,"depth":83,"text":4968},{"id":5986,"depth":83,"text":5986},{"id":6078,"depth":83,"text":6078},[1424],"2026-03-04",{},"\u002Farticles\u002Fjs-canvas-wave",{"title":2027,"description":2027},"articles\u002Fjs-canvas-wave",[1434],"_mix\u002Fex01-768x299.png","M7BMAJ4ad0W1ocZaATiSTEMNK1g6YP1t_HrYUpkyahQ",{"id":6118,"title":6119,"body":6120,"category":7774,"createdAt":7775,"description":7776,"extension":1427,"index":1428,"meta":7777,"navigation":340,"path":7778,"publish":340,"seo":7779,"series":1428,"seriesTitle":1428,"stem":7780,"tag":7781,"thumbnail":1428,"updatedAt":7775,"__hash__":7783},"articles\u002Farticles\u002Fgithub-actions-node-js.md","Github Actionsを用いてVueファイルをビルド・rsyncで本番環境にデプロイする。",{"type":10,"value":6121,"toc":7753},[6122,6125,6128,6131,6134,6142,6146,6156,6159,6165,6168,6171,6400,6403,6406,6409,6412,6415,6421,6424,6427,6430,6449,6455,6470,6486,6489,6492,6499,6508,6511,6514,6527,6533,6539,6545,6551,6557,6563,6566,6572,6578,6581,6837,6843,6845,6853,6856,6859,6888,6902,7005,7019,7048,7051,7096,7103,7106,7113,7120,7155,7158,7197,7201,7204,7335,7346,7353,7383,7399,7444,7458,7469,7473,7476,7546,7549,7556,7560,7566,7633,7637,7647,7698,7708,7715,7718,7725,7729,7732,7735,7738,7741,7744,7747,7750],[13,6123,6124],{},"こんにちはjunです。皆さんはVueやReact、Nuxtなどなどフロントエンドフレームワークやライブラリを使っていますでしょうか？これらのライブラリはフロントの構築が楽になりますが、一度Node.jsを用いてビルドする必要があります。そしてビルドしたファイルを読み込ませることで動作します。",[13,6126,6127],{},"ローカルの環境ではNode.jsがあるので問題ないですが、デプロイする本番環境になくて困ったということはありませんでしょうか？サーバー要件などで本番環境にNode.jsを入れられない、無い場合は予めどこかでビルドして転送する必要があります。",[13,6129,6130],{},"一番手っ取り早いのはローカルでビルドしてFTPなりrsyncすることです。しかし何人もプロジェクトに関わっていたり、属人化したり、環境が統一されていないなど色々とデメリットがあります。",[13,6132,6133],{},"私はLaravel+Nuxt.jsなプロジェクトを開発し、Xserver（レンタルサーバー）に配置したことがあります。Laravelのアセットファイルと、Nuxt.jsはNode.jsでビルドする必要がありますが、レンタルサーバー にはNode.jsがありません。（Xserverには気合で入れらるらしいですが、、パフォーマンスなどの都合でやめました。）",[13,6135,6136,6137,6141],{},"そこで前から気になっていたGithub Actionsを用いてGithub上でビルドを行い、本番環境でrsyncすることでなんとかアセットファイル系がデプロイできました。今回はこの方法について解説します。前提やGithub Actionsの説明から行いますで、さっさと内部コードを知りたい方は「",[2039,6138,6140],{"href":6139},"#%E3%83%AF%E3%83%BC%E3%82%AF%E3%83%95%E3%83%AD%E3%83%BC%E3%82%92%E6%9B%B8%E3%81%84%E3%81%A6%E3%81%84%E3%81%8F","ワークフローを書いていく","」へ移動してください。",[17,6143,6145],{"id":6144},"github-actionsとは","Github Actionsとは",[6147,6148,6149],"blockquote",{},[13,6150,6151,6152,938],{},"GitHub Actionsを使用すると、ワールドクラスのCI \u002F CDですべてのソフトウェアワークフローを簡単に自動化できます。 GitHubから直接コードをビルド、テスト、デプロイでき、コードレビュー、ブランチ管理、問題のトリアージを希望どおりに機能させます。(",[2039,6153,6154],{"href":6154,"rel":6155},"https:\u002F\u002Fgithub.co.jp\u002Ffeatures\u002Factions",[2043],[13,6157,6158],{},"Github ActionsはGithubでビルド、テスト、デプロイ作業ができる環境です。Github上のコードを用いてすぐにビルドなどができます。例えばLaravelのVueファイルをビルドする際にはLaravelのプロジェクトルートで",[42,6160,6163],{"className":6161,"code":6162,"language":452},[1615],"npm run prod\n",[49,6164,6162],{"__ignoreMap":47},[13,6166,6167],{},"とnode.jsのコマンドを打って、node.jsを動かしてビルドファイルを作成します。そしてビルド後にはcss,jsディレクトリが作成されます。このビルド操作をGithubの環境でも行えるようにします。",[13,6169,6170],{},"以下のようなymlファイルをGithub上で作成して、ビルド・やりたいコマンドを記述します。",[42,6172,6176],{"className":6173,"code":6174,"language":6175,"meta":47,"style":47},"language-yml shiki shiki-themes material-theme-ocean","name: Deploy\n\non: [ workflow_dispatch ]\n\njobs:\n  build:\n\n    runs-on: ubuntu-latest\n    \n    steps:\n    - uses: actions\u002Fcheckout@v2\n      with:\n        ref: release\n    - uses: actions\u002Fsetup-node@v2\n      with:\n        node-version: '14'\n        cache: 'npm'\n     - name: public build\n      run: |\n        npm install\n        npm run prod\n        rsync -av --delete \\\n        .\u002Fpublic\u002Fcss .\u002Fpublic\u002Fjs .\u002Fpublic\u002Fimages \\\n        deploy:$LARAVEL_PATH\u002Fpublic\u002F\n        scp .\u002Fpublic\u002Fmix-manifest.json deploy:$LARAVEL_PATH\u002Fpublic\u002Fmix-manifest.json\n      env:\n        LARAVEL_PATH : ${{ secrets.LARAVEL_PATH }}\n","yml",[49,6177,6178,6188,6192,6209,6213,6221,6228,6232,6242,6246,6253,6266,6273,6283,6294,6300,6316,6330,6342,6352,6357,6362,6367,6372,6377,6382,6389],{"__ignoreMap":47},[52,6179,6180,6183,6185],{"class":54,"line":55},[52,6181,6182],{"class":62},"name",[52,6184,373],{"class":58},[52,6186,6187],{"class":75}," Deploy\n",[52,6189,6190],{"class":54,"line":83},[52,6191,341],{"emptyLinePlaceholder":340},[52,6193,6194,6198,6200,6203,6206],{"class":54,"line":115},[52,6195,6197],{"class":6196},"sbqyR","on",[52,6199,373],{"class":58},[52,6201,6202],{"class":58}," [",[52,6204,6205],{"class":75}," workflow_dispatch",[52,6207,6208],{"class":58}," ]\n",[52,6210,6211],{"class":54,"line":142},[52,6212,341],{"emptyLinePlaceholder":340},[52,6214,6215,6218],{"class":54,"line":169},[52,6216,6217],{"class":62},"jobs",[52,6219,6220],{"class":58},":\n",[52,6222,6223,6226],{"class":54,"line":302},[52,6224,6225],{"class":62},"  build",[52,6227,6220],{"class":58},[52,6229,6230],{"class":54,"line":308},[52,6231,341],{"emptyLinePlaceholder":340},[52,6233,6234,6237,6239],{"class":54,"line":318},[52,6235,6236],{"class":62},"    runs-on",[52,6238,373],{"class":58},[52,6240,6241],{"class":75}," ubuntu-latest\n",[52,6243,6244],{"class":54,"line":328},[52,6245,3235],{"class":105},[52,6247,6248,6251],{"class":54,"line":337},[52,6249,6250],{"class":62},"    steps",[52,6252,6220],{"class":58},[52,6254,6255,6258,6261,6263],{"class":54,"line":344},[52,6256,6257],{"class":58},"    -",[52,6259,6260],{"class":62}," uses",[52,6262,373],{"class":58},[52,6264,6265],{"class":75}," actions\u002Fcheckout@v2\n",[52,6267,6268,6271],{"class":54,"line":354},[52,6269,6270],{"class":62},"      with",[52,6272,6220],{"class":58},[52,6274,6275,6278,6280],{"class":54,"line":367},[52,6276,6277],{"class":62},"        ref",[52,6279,373],{"class":58},[52,6281,6282],{"class":75}," release\n",[52,6284,6285,6287,6289,6291],{"class":54,"line":387},[52,6286,6257],{"class":58},[52,6288,6260],{"class":62},[52,6290,373],{"class":58},[52,6292,6293],{"class":75}," actions\u002Fsetup-node@v2\n",[52,6295,6296,6298],{"class":54,"line":415},[52,6297,6270],{"class":62},[52,6299,6220],{"class":58},[52,6301,6302,6305,6307,6310,6313],{"class":54,"line":427},[52,6303,6304],{"class":62},"        node-version",[52,6306,373],{"class":58},[52,6308,6309],{"class":58}," '",[52,6311,6312],{"class":75},"14",[52,6314,6315],{"class":58},"'\n",[52,6317,6318,6321,6323,6325,6328],{"class":54,"line":435},[52,6319,6320],{"class":62},"        cache",[52,6322,373],{"class":58},[52,6324,6309],{"class":58},[52,6326,6327],{"class":75},"npm",[52,6329,6315],{"class":58},[52,6331,6332,6335,6337,6339],{"class":54,"line":446},[52,6333,6334],{"class":58},"     -",[52,6336,66],{"class":62},[52,6338,373],{"class":58},[52,6340,6341],{"class":75}," public build\n",[52,6343,6344,6347,6349],{"class":54,"line":480},[52,6345,6346],{"class":62},"      run",[52,6348,373],{"class":58},[52,6350,6351],{"class":360}," |\n",[52,6353,6354],{"class":54,"line":509},[52,6355,6356],{"class":75},"        npm install\n",[52,6358,6359],{"class":54,"line":539},[52,6360,6361],{"class":75},"        npm run prod\n",[52,6363,6364],{"class":54,"line":547},[52,6365,6366],{"class":75},"        rsync -av --delete \\\n",[52,6368,6369],{"class":54,"line":553},[52,6370,6371],{"class":75},"        .\u002Fpublic\u002Fcss .\u002Fpublic\u002Fjs .\u002Fpublic\u002Fimages \\\n",[52,6373,6374],{"class":54,"line":559},[52,6375,6376],{"class":75},"        deploy:$LARAVEL_PATH\u002Fpublic\u002F\n",[52,6378,6379],{"class":54,"line":564},[52,6380,6381],{"class":75},"        scp .\u002Fpublic\u002Fmix-manifest.json deploy:$LARAVEL_PATH\u002Fpublic\u002Fmix-manifest.json\n",[52,6383,6384,6387],{"class":54,"line":569},[52,6385,6386],{"class":62},"      env",[52,6388,6220],{"class":58},[52,6390,6391,6394,6397],{"class":54,"line":1106},[52,6392,6393],{"class":62},"        LARAVEL_PATH",[52,6395,6396],{"class":58}," :",[52,6398,6399],{"class":75}," ${{ secrets.LARAVEL_PATH }}\n",[13,6401,6402],{},"Dokcerファイルやバッチファイルと似た感じです。Node.jsの環境などはGithubが用意してくれます。",[1499,6404,6405],{"id":6405},"使える言語など",[13,6407,6408],{},"結構なんでもいけます。Node.jsはしかり、PHP\u002FPython\u002FRuby\u002FJava\u002FPower Shellなど色々あります。",[1499,6410,6411],{"id":6411},"料金",[13,6413,6414],{},"パブリックリポジトリの場合は何分使おうが無料です。しかしプライベートの場合は無料枠があり、フリーは毎月2000分までとなっています。(2022年1月時点)",[13,6416,6417,6418],{},"参照：",[2039,6419,6154],{"href":6154,"rel":6420},[2043],[13,6422,6423],{},"私のプロジェクトの場合、大体3分ぐらいで終わりますし、頻繁にビルドしないので基本無料で使えそうです。",[17,6425,6426],{"id":6426},"全体処理の概要とビルド対象",[13,6428,6429],{},"今回ビルドするプロジェクトはLaravelとNuxtが連携されたプロジェクトです。",[1467,6431,6432,6442],{},[1470,6433,6434,6435,408,6438,6441],{},"Laravelのresources配下に作ったsassとVueファイル。公開ディレクトリの",[49,6436,6437],{},"css\u002F",[49,6439,6440],{},"js\u002F","に吐き出される",[1470,6443,6444,6445,6448],{},"Nuxtの静的書き出しファイル。",[49,6446,6447],{},"dist\u002F","というディレクトリが生成される。",[42,6450,6453],{"className":6451,"code":6452,"language":452},[1615],".\n├── README.md\n├── app\n├── artisan\n├── bootstrap\n├── composer.json\n├── composer.lock\n├── config\n├── database\n├── nuxt # nuxtはここ\n├── package-lock.json\n├── package.json\n├── phpunit.xml\n├── public # ここにLaravelのcss,jsが吐き出される\n├── resources\n├── routes\n├── server.php\n├── storage\n├── tests\n├── tinker_test.php\n└── webpack.mix.js\n",[49,6454,6452],{"__ignoreMap":47},[13,6456,6457,6458,6461,6462,6465,6466,6469],{},"まあとりあえず、Laravelルートで",[49,6459,6460],{},"npm run prod","そして、",[49,6463,6464],{},"nuxt\u002F","にて",[49,6467,6468],{},"npm run generate","してnode.jsを動かす必要があります。",[13,6471,6472,6473,408,6475,408,6477,408,6480,408,6483,6485],{},"そして生成されてた",[49,6474,6437],{},[49,6476,6440],{},[49,6478,6479],{},"images\u002F",[49,6481,6482],{},"mix-manifest.json",[49,6484,6447],{},"ディレクトリ・ファイルを本番サーバーに転送します。",[13,6487,6488],{},"転送の際にはrsyncを使用し、SSHの鍵認証で接続します。またこのアクションは手動で行うようにします。一応、masterブランチにプッシュされた時に自動で実行なんてこともできます。ただし今回はビルドする環境が欲しいだけなので、処理の実行は手動で行います。以上がActionの概要です。",[17,6490,6491],{"id":6491},"環境変数を定義しておく",[13,6493,6494,6495,6498],{},"まずSSHやビルドで使用する",[49,6496,6497],{},".env","など環境変数を定義しておきます。Github Actionsでも環境変数を定義できます。",[13,6500,6501,6502,6507],{},"リポジトリのSettingsから",[2039,6503,6506],{"href":6504,"rel":6505},"https:\u002F\u002Fgithub.com\u002Fmedia-ch\u002Fsetagaya_foodshare\u002Fsettings\u002Fsecrets\u002Factions",[2043],"Secrets","の画面で定義できます。",[729,6509],{":src":6510,":width":732},"'github-actions-node-js\u002Fsecrets-setting.png'",[13,6512,6513],{},"「New repository secret」をクリックしてキー名と値を入力します。",[13,6515,6516,6519,6520,2969,6523,6526],{},[1463,6517,6518],{},"SSH_KEY","：SSHに使用する鍵ファイルの中身です。",[49,6521,6522],{},".pem",[49,6524,6525],{},".key","などを開いて記述された文字をすべてコピペしてください。",[13,6528,6529,6532],{},[1463,6530,6531],{},"SSH_HOST","：接続先のホスト名です。",[13,6534,6535,6538],{},[1463,6536,6537],{},"SSH_PORT","：接続先のポートです。",[13,6540,6541,6544],{},[1463,6542,6543],{},"SSH_USER","：接続先のユーザーです。",[13,6546,6547,6550],{},[1463,6548,6549],{},"LARAEL_PATH","：本番サーバーでのLaravelが置かれている絶対パスです。",[13,6552,6553,6556],{},[1463,6554,6555],{},"GOOGLE_MAP_API_KEY","：NuxtでGoogleMapのAPI（フロントに出してもOKなやつ）を使っているので定義。あとで使います。",[13,6558,6559,6560,6562],{},"以上のSSHに関連する値と、",[49,6561,6497],{},"の記述で必要であれば書いておきます。それではアクションの内容を書いていきましょう。",[17,6564,6565],{"id":6565},"ワークフローを作成しテンプレートを選択する",[13,6567,6568,6569],{},"メニューの「Actions」をクリックしたのち、「New Workflow」をクリックします。\n",[729,6570],{":src":6571},"'github-actions-node-js\u002Fmenu.png'",[13,6573,6574,6575],{},"するとworkflowを選択する画面にて、テンプレートをいくつか用意してくれています。\n",[729,6576],{":src":6577},"'github-actions-node-js\u002Fworkflows.png'",[13,6579,6580],{},"今回はNode.jsのビルドしかないので「Node.js」を選択します。するとある程度の記述が書かれた状態で、yamlが表示されます。",[42,6582,6586],{"className":6583,"code":6584,"language":6585,"meta":47,"style":47},"language-yaml shiki shiki-themes material-theme-ocean","# This workflow will do a clean installation of node dependencies, cache\u002Frestore them, build the source code and run tests across different versions of node\n# For more information see: https:\u002F\u002Fhelp.github.com\u002Factions\u002Flanguage-and-framework-guides\u002Fusing-nodejs-with-github-actions\n\nname: Node.js CI\n\non:\n  push:\n    branches: [ master ]\n  pull_request:\n    branches: [ master ]\n\njobs:\n  build:\n\n    runs-on: ubuntu-latest\n\n    strategy:\n      matrix:\n        node-version: [12.x, 14.x, 16.x]\n        # See supported Node.js release schedule at https:\u002F\u002Fnodejs.org\u002Fen\u002Fabout\u002Freleases\u002F\n\n    steps:\n    - uses: actions\u002Fcheckout@v2\n    - name: Use Node.js ${{ matrix.node-version }}\n      uses: actions\u002Fsetup-node@v2\n      with:\n        node-version: ${{ matrix.node-version }}\n        cache: 'npm'\n    - run: npm ci\n    - run: npm run build --if-present\n    - run: npm test\n","yaml",[49,6587,6588,6593,6598,6602,6611,6615,6621,6628,6642,6649,6661,6665,6671,6677,6681,6689,6693,6700,6707,6731,6736,6740,6746,6756,6767,6776,6782,6791,6803,6815,6826],{"__ignoreMap":47},[52,6589,6590],{"class":54,"line":55},[52,6591,6592],{"class":411},"# This workflow will do a clean installation of node dependencies, cache\u002Frestore them, build the source code and run tests across different versions of node\n",[52,6594,6595],{"class":54,"line":83},[52,6596,6597],{"class":411},"# For more information see: https:\u002F\u002Fhelp.github.com\u002Factions\u002Flanguage-and-framework-guides\u002Fusing-nodejs-with-github-actions\n",[52,6599,6600],{"class":54,"line":115},[52,6601,341],{"emptyLinePlaceholder":340},[52,6603,6604,6606,6608],{"class":54,"line":142},[52,6605,6182],{"class":62},[52,6607,373],{"class":58},[52,6609,6610],{"class":75}," Node.js CI\n",[52,6612,6613],{"class":54,"line":169},[52,6614,341],{"emptyLinePlaceholder":340},[52,6616,6617,6619],{"class":54,"line":302},[52,6618,6197],{"class":6196},[52,6620,6220],{"class":58},[52,6622,6623,6626],{"class":54,"line":308},[52,6624,6625],{"class":62},"  push",[52,6627,6220],{"class":58},[52,6629,6630,6633,6635,6637,6640],{"class":54,"line":318},[52,6631,6632],{"class":62},"    branches",[52,6634,373],{"class":58},[52,6636,6202],{"class":58},[52,6638,6639],{"class":75}," master",[52,6641,6208],{"class":58},[52,6643,6644,6647],{"class":54,"line":328},[52,6645,6646],{"class":62},"  pull_request",[52,6648,6220],{"class":58},[52,6650,6651,6653,6655,6657,6659],{"class":54,"line":337},[52,6652,6632],{"class":62},[52,6654,373],{"class":58},[52,6656,6202],{"class":58},[52,6658,6639],{"class":75},[52,6660,6208],{"class":58},[52,6662,6663],{"class":54,"line":344},[52,6664,341],{"emptyLinePlaceholder":340},[52,6666,6667,6669],{"class":54,"line":354},[52,6668,6217],{"class":62},[52,6670,6220],{"class":58},[52,6672,6673,6675],{"class":54,"line":367},[52,6674,6225],{"class":62},[52,6676,6220],{"class":58},[52,6678,6679],{"class":54,"line":387},[52,6680,341],{"emptyLinePlaceholder":340},[52,6682,6683,6685,6687],{"class":54,"line":415},[52,6684,6236],{"class":62},[52,6686,373],{"class":58},[52,6688,6241],{"class":75},[52,6690,6691],{"class":54,"line":427},[52,6692,341],{"emptyLinePlaceholder":340},[52,6694,6695,6698],{"class":54,"line":435},[52,6696,6697],{"class":62},"    strategy",[52,6699,6220],{"class":58},[52,6701,6702,6705],{"class":54,"line":446},[52,6703,6704],{"class":62},"      matrix",[52,6706,6220],{"class":58},[52,6708,6709,6711,6713,6715,6718,6720,6723,6725,6728],{"class":54,"line":480},[52,6710,6304],{"class":62},[52,6712,373],{"class":58},[52,6714,6202],{"class":58},[52,6716,6717],{"class":75},"12.x",[52,6719,408],{"class":58},[52,6721,6722],{"class":75}," 14.x",[52,6724,408],{"class":58},[52,6726,6727],{"class":75}," 16.x",[52,6729,6730],{"class":58},"]\n",[52,6732,6733],{"class":54,"line":509},[52,6734,6735],{"class":411},"        # See supported Node.js release schedule at https:\u002F\u002Fnodejs.org\u002Fen\u002Fabout\u002Freleases\u002F\n",[52,6737,6738],{"class":54,"line":539},[52,6739,341],{"emptyLinePlaceholder":340},[52,6741,6742,6744],{"class":54,"line":547},[52,6743,6250],{"class":62},[52,6745,6220],{"class":58},[52,6747,6748,6750,6752,6754],{"class":54,"line":553},[52,6749,6257],{"class":58},[52,6751,6260],{"class":62},[52,6753,373],{"class":58},[52,6755,6265],{"class":75},[52,6757,6758,6760,6762,6764],{"class":54,"line":559},[52,6759,6257],{"class":58},[52,6761,66],{"class":62},[52,6763,373],{"class":58},[52,6765,6766],{"class":75}," Use Node.js ${{ matrix.node-version }}\n",[52,6768,6769,6772,6774],{"class":54,"line":564},[52,6770,6771],{"class":62},"      uses",[52,6773,373],{"class":58},[52,6775,6293],{"class":75},[52,6777,6778,6780],{"class":54,"line":569},[52,6779,6270],{"class":62},[52,6781,6220],{"class":58},[52,6783,6784,6786,6788],{"class":54,"line":1106},[52,6785,6304],{"class":62},[52,6787,373],{"class":58},[52,6789,6790],{"class":75}," ${{ matrix.node-version }}\n",[52,6792,6793,6795,6797,6799,6801],{"class":54,"line":1135},[52,6794,6320],{"class":62},[52,6796,373],{"class":58},[52,6798,6309],{"class":58},[52,6800,6327],{"class":75},[52,6802,6315],{"class":58},[52,6804,6805,6807,6810,6812],{"class":54,"line":1164},[52,6806,6257],{"class":58},[52,6808,6809],{"class":62}," run",[52,6811,373],{"class":58},[52,6813,6814],{"class":75}," npm ci\n",[52,6816,6817,6819,6821,6823],{"class":54,"line":1193},[52,6818,6257],{"class":58},[52,6820,6809],{"class":62},[52,6822,373],{"class":58},[52,6824,6825],{"class":75}," npm run build --if-present\n",[52,6827,6828,6830,6832,6834],{"class":54,"line":1200},[52,6829,6257],{"class":58},[52,6831,6809],{"class":62},[52,6833,373],{"class":58},[52,6835,6836],{"class":75}," npm test\n",[13,6838,6839,6840,6842],{},"最初は上記の構成となっています。",[49,6841,6182],{},"はワークフローの名前になります。わかりやすいものに変えておきましょう。",[17,6844,6140],{"id":6140},[13,6846,6847,6848],{},"まず最初にLaravelのアセットファイルを作成してrsyncするとこまで記述します。なお登場する記述に関してはこちらの公式ドキュメントを参考にしてください。",[2039,6849,6852],{"href":6850,"rel":6851},"https:\u002F\u002Fdocs.github.com\u002Fja\u002Factions\u002Fusing-workflows\u002Fworkflow-syntax-for-github-actions",[2043],"ワークフロー構文",[1499,6854,6855],{"id":6855},"実行条件などの変更",[13,6857,6858],{},"最初は実行条件などを変更します。",[42,6860,6862],{"className":6583,"code":6861,"language":6585,"meta":47,"style":47},"name: Deploy\n\non: [ workflow_dispatch ]\n",[49,6863,6864,6872,6876],{"__ignoreMap":47},[52,6865,6866,6868,6870],{"class":54,"line":55},[52,6867,6182],{"class":62},[52,6869,373],{"class":58},[52,6871,6187],{"class":75},[52,6873,6874],{"class":54,"line":83},[52,6875,341],{"emptyLinePlaceholder":340},[52,6877,6878,6880,6882,6884,6886],{"class":54,"line":115},[52,6879,6197],{"class":6196},[52,6881,373],{"class":58},[52,6883,6202],{"class":58},[52,6885,6205],{"class":75},[52,6887,6208],{"class":58},[13,6889,6890,6893,6894,6897,6898,6901],{},[49,6891,6892],{},"on:","はこのワークフローの実行条件です。デフォルトでは",[49,6895,6896],{},"master","ブランチにpushされることが条件ですが、ここでは",[49,6899,6900],{},"workflow_dispatch","として、手動で実行できるようにします。",[42,6903,6905],{"className":6583,"code":6904,"language":6585,"meta":47,"style":47},"jobs:\n  build:\n\n    runs-on: ubuntu-latest\n    \n    steps:\n    - uses: actions\u002Fcheckout@v2\n      with:\n        ref: release\n    - uses: actions\u002Fsetup-node@v2\n      with:\n        node-version: '14'\n        cache: 'npm'\n",[49,6906,6907,6913,6919,6923,6931,6935,6941,6951,6957,6965,6975,6981,6993],{"__ignoreMap":47},[52,6908,6909,6911],{"class":54,"line":55},[52,6910,6217],{"class":62},[52,6912,6220],{"class":58},[52,6914,6915,6917],{"class":54,"line":83},[52,6916,6225],{"class":62},[52,6918,6220],{"class":58},[52,6920,6921],{"class":54,"line":115},[52,6922,341],{"emptyLinePlaceholder":340},[52,6924,6925,6927,6929],{"class":54,"line":142},[52,6926,6236],{"class":62},[52,6928,373],{"class":58},[52,6930,6241],{"class":75},[52,6932,6933],{"class":54,"line":169},[52,6934,3235],{"class":105},[52,6936,6937,6939],{"class":54,"line":302},[52,6938,6250],{"class":62},[52,6940,6220],{"class":58},[52,6942,6943,6945,6947,6949],{"class":54,"line":308},[52,6944,6257],{"class":58},[52,6946,6260],{"class":62},[52,6948,373],{"class":58},[52,6950,6265],{"class":75},[52,6952,6953,6955],{"class":54,"line":318},[52,6954,6270],{"class":62},[52,6956,6220],{"class":58},[52,6958,6959,6961,6963],{"class":54,"line":328},[52,6960,6277],{"class":62},[52,6962,373],{"class":58},[52,6964,6282],{"class":75},[52,6966,6967,6969,6971,6973],{"class":54,"line":337},[52,6968,6257],{"class":58},[52,6970,6260],{"class":62},[52,6972,373],{"class":58},[52,6974,6293],{"class":75},[52,6976,6977,6979],{"class":54,"line":344},[52,6978,6270],{"class":62},[52,6980,6220],{"class":58},[52,6982,6983,6985,6987,6989,6991],{"class":54,"line":354},[52,6984,6304],{"class":62},[52,6986,373],{"class":58},[52,6988,6309],{"class":58},[52,6990,6312],{"class":75},[52,6992,6315],{"class":58},[52,6994,6995,6997,6999,7001,7003],{"class":54,"line":367},[52,6996,6320],{"class":62},[52,6998,373],{"class":58},[52,7000,6309],{"class":58},[52,7002,6327],{"class":75},[52,7004,6315],{"class":58},[13,7006,7007,7008,7011,7012,7014,7015,7018],{},"このリポジトリの場合、本番環境は常に",[49,7009,7010],{},"release","ブランチをプルするのでビルド対象ファイルも",[49,7013,7010],{},"のものを使用します。そのため",[49,7016,7017],{},"- uses: actions\u002Fcheckout@v2","というブランチのチェックアウトが処理を行う記述をします。",[42,7020,7022],{"className":6583,"code":7021,"language":6585,"meta":47,"style":47},"    - uses: actions\u002Fcheckout@v2\n      with:\n        ref: release\n",[49,7023,7024,7034,7040],{"__ignoreMap":47},[52,7025,7026,7028,7030,7032],{"class":54,"line":55},[52,7027,6257],{"class":58},[52,7029,6260],{"class":62},[52,7031,373],{"class":58},[52,7033,6265],{"class":75},[52,7035,7036,7038],{"class":54,"line":83},[52,7037,6270],{"class":62},[52,7039,6220],{"class":58},[52,7041,7042,7044,7046],{"class":54,"line":115},[52,7043,6277],{"class":62},[52,7045,373],{"class":58},[52,7047,6282],{"class":75},[13,7049,7050],{},"これでリリースブランチにチェックアウトしてビルドできるようになります。",[42,7052,7054],{"className":6583,"code":7053,"language":6585,"meta":47,"style":47},"- uses: actions\u002Fsetup-node@v2\n      with:\n        node-version: '14'\n        cache: 'npm'\n",[49,7055,7056,7066,7072,7084],{"__ignoreMap":47},[52,7057,7058,7060,7062,7064],{"class":54,"line":55},[52,7059,3284],{"class":58},[52,7061,6260],{"class":62},[52,7063,373],{"class":58},[52,7065,6293],{"class":75},[52,7067,7068,7070],{"class":54,"line":83},[52,7069,6270],{"class":62},[52,7071,6220],{"class":58},[52,7073,7074,7076,7078,7080,7082],{"class":54,"line":115},[52,7075,6304],{"class":62},[52,7077,373],{"class":58},[52,7079,6309],{"class":58},[52,7081,6312],{"class":75},[52,7083,6315],{"class":58},[52,7085,7086,7088,7090,7092,7094],{"class":54,"line":142},[52,7087,6320],{"class":62},[52,7089,373],{"class":58},[52,7091,6309],{"class":58},[52,7093,6327],{"class":75},[52,7095,6315],{"class":58},[13,7097,7098,7099,7102],{},"これでNode.js14の環境を使用できるようになり、npmコマンドも使用できるようになります。それでは",[49,7100,7101],{},"step:","配下に実行するコマンドを書いていきましょう。",[1499,7104,7105],{"id":7105},"実行コードの書き方",[13,7107,7108,7109,7112],{},"実行コードは",[49,7110,7111],{},"name:","を用いて区分けできます。長いと何がなんだか分からなくなるので、書いておくといいです。",[13,7114,7115,7116,7119],{},"そして実行内容は",[49,7117,7118],{},"run:","に記述します。",[42,7121,7123],{"className":6583,"code":7122,"language":6585,"meta":47,"style":47},"    - run: npm ci\n    - run: npm run build --if-present\n    - run: npm test\n",[49,7124,7125,7135,7145],{"__ignoreMap":47},[52,7126,7127,7129,7131,7133],{"class":54,"line":55},[52,7128,6257],{"class":58},[52,7130,6809],{"class":62},[52,7132,373],{"class":58},[52,7134,6814],{"class":75},[52,7136,7137,7139,7141,7143],{"class":54,"line":83},[52,7138,6257],{"class":58},[52,7140,6809],{"class":62},[52,7142,373],{"class":58},[52,7144,6825],{"class":75},[52,7146,7147,7149,7151,7153],{"class":54,"line":115},[52,7148,6257],{"class":58},[52,7150,6809],{"class":62},[52,7152,373],{"class":58},[52,7154,6836],{"class":75},[13,7156,7157],{},"のように１つづつ書いてもいいですが、ここでは以下のようにまとめて書くことにします。",[42,7159,7161],{"className":6583,"code":7160,"language":6585,"meta":47,"style":47},"    - name: ssh key generate\n      run: |\n        echo \"$SSH_KEY\" > id_rsa\n        mkdir ~\u002F.ssh\n        chmod 700 ~\u002F.ssh\n",[49,7162,7163,7174,7182,7187,7192],{"__ignoreMap":47},[52,7164,7165,7167,7169,7171],{"class":54,"line":55},[52,7166,6257],{"class":58},[52,7168,66],{"class":62},[52,7170,373],{"class":58},[52,7172,7173],{"class":75}," ssh key generate\n",[52,7175,7176,7178,7180],{"class":54,"line":83},[52,7177,6346],{"class":62},[52,7179,373],{"class":58},[52,7181,6351],{"class":360},[52,7183,7184],{"class":54,"line":115},[52,7185,7186],{"class":75},"        echo \"$SSH_KEY\" > id_rsa\n",[52,7188,7189],{"class":54,"line":142},[52,7190,7191],{"class":75},"        mkdir ~\u002F.ssh\n",[52,7193,7194],{"class":54,"line":169},[52,7195,7196],{"class":75},"        chmod 700 ~\u002F.ssh\n",[1499,7198,7200],{"id":7199},"sshの設定","SSHの設定",[13,7202,7203],{},"そんでは最初にSSHの設定を行います。毎回、鍵のパスとかを記述するのは面倒なのでSSHのconfigファイルを作ってエイリアスで呼べるようにしましょう。",[42,7205,7207],{"className":6583,"code":7206,"language":6585,"meta":47,"style":47},"    - name: ssh key generate\n      run: |\n        echo \"$SSH_KEY\" > id_rsa\n        mkdir ~\u002F.ssh\n        chmod 700 ~\u002F.ssh\n        mv id_rsa ~\u002F.ssh\u002F\n        chmod 600 ~\u002F.ssh\u002Fid_rsa\n        echo \"HOST deploy\" >> ~\u002F.ssh\u002Fconfig\n        echo \"HostName $SSH_HOST\" >> ~\u002F.ssh\u002Fconfig\n        echo \"user $SSH_USER\" >> ~\u002F.ssh\u002Fconfig\n        echo \"Port $SSH_PORT\" >> ~\u002F.ssh\u002Fconfig\n        echo \"IdentityFile $HOME\u002F.ssh\u002Fid_rsa\" >> ~\u002F.ssh\u002Fconfig\n        echo \"StrictHostKeyChecking no\" >> ~\u002F.ssh\u002Fconfig\n        echo \"UserKnownHostsFile=\u002Fdev\u002Fnull\" >> ~\u002F.ssh\u002Fconfig\n        chmod 644 ~\u002F.ssh\u002Fconfig\n      env:\n        SSH_KEY: ${{ secrets.SSH_KEY }}\n        SSH_HOST: ${{ secrets.SSH_HOST }}\n        SSH_USER: ${{ secrets.SSH_USER }}\n        SSH_PORT: ${{ secrets.SSH_PORT }}\n",[49,7208,7209,7219,7227,7231,7235,7239,7244,7249,7254,7259,7264,7269,7274,7279,7284,7289,7295,7305,7315,7325],{"__ignoreMap":47},[52,7210,7211,7213,7215,7217],{"class":54,"line":55},[52,7212,6257],{"class":58},[52,7214,66],{"class":62},[52,7216,373],{"class":58},[52,7218,7173],{"class":75},[52,7220,7221,7223,7225],{"class":54,"line":83},[52,7222,6346],{"class":62},[52,7224,373],{"class":58},[52,7226,6351],{"class":360},[52,7228,7229],{"class":54,"line":115},[52,7230,7186],{"class":75},[52,7232,7233],{"class":54,"line":142},[52,7234,7191],{"class":75},[52,7236,7237],{"class":54,"line":169},[52,7238,7196],{"class":75},[52,7240,7241],{"class":54,"line":302},[52,7242,7243],{"class":75},"        mv id_rsa ~\u002F.ssh\u002F\n",[52,7245,7246],{"class":54,"line":308},[52,7247,7248],{"class":75},"        chmod 600 ~\u002F.ssh\u002Fid_rsa\n",[52,7250,7251],{"class":54,"line":318},[52,7252,7253],{"class":75},"        echo \"HOST deploy\" >> ~\u002F.ssh\u002Fconfig\n",[52,7255,7256],{"class":54,"line":328},[52,7257,7258],{"class":75},"        echo \"HostName $SSH_HOST\" >> ~\u002F.ssh\u002Fconfig\n",[52,7260,7261],{"class":54,"line":337},[52,7262,7263],{"class":75},"        echo \"user $SSH_USER\" >> ~\u002F.ssh\u002Fconfig\n",[52,7265,7266],{"class":54,"line":344},[52,7267,7268],{"class":75},"        echo \"Port $SSH_PORT\" >> ~\u002F.ssh\u002Fconfig\n",[52,7270,7271],{"class":54,"line":354},[52,7272,7273],{"class":75},"        echo \"IdentityFile $HOME\u002F.ssh\u002Fid_rsa\" >> ~\u002F.ssh\u002Fconfig\n",[52,7275,7276],{"class":54,"line":367},[52,7277,7278],{"class":75},"        echo \"StrictHostKeyChecking no\" >> ~\u002F.ssh\u002Fconfig\n",[52,7280,7281],{"class":54,"line":387},[52,7282,7283],{"class":75},"        echo \"UserKnownHostsFile=\u002Fdev\u002Fnull\" >> ~\u002F.ssh\u002Fconfig\n",[52,7285,7286],{"class":54,"line":415},[52,7287,7288],{"class":75},"        chmod 644 ~\u002F.ssh\u002Fconfig\n",[52,7290,7291,7293],{"class":54,"line":427},[52,7292,6386],{"class":62},[52,7294,6220],{"class":58},[52,7296,7297,7300,7302],{"class":54,"line":435},[52,7298,7299],{"class":62},"        SSH_KEY",[52,7301,373],{"class":58},[52,7303,7304],{"class":75}," ${{ secrets.SSH_KEY }}\n",[52,7306,7307,7310,7312],{"class":54,"line":446},[52,7308,7309],{"class":62},"        SSH_HOST",[52,7311,373],{"class":58},[52,7313,7314],{"class":75}," ${{ secrets.SSH_HOST }}\n",[52,7316,7317,7320,7322],{"class":54,"line":480},[52,7318,7319],{"class":62},"        SSH_USER",[52,7321,373],{"class":58},[52,7323,7324],{"class":75}," ${{ secrets.SSH_USER }}\n",[52,7326,7327,7330,7332],{"class":54,"line":509},[52,7328,7329],{"class":62},"        SSH_PORT",[52,7331,373],{"class":58},[52,7333,7334],{"class":75}," ${{ secrets.SSH_PORT }}\n",[13,7336,7337,7338,7341,7342,7345],{},"まず",[49,7339,7340],{},"env:","にて使用する環境変数をコマンド変数に渡します。こうすることでコマンド内で",[49,7343,7344],{},"$SSH_KEY","として値を使用できます。キー、ホスト、ユーザー、ポートを出します。",[13,7347,7348,7349,7352],{},"ちなみに",[49,7350,7351],{},"${{ secrets.SSH_KEY }}","の記述はコンテキストと呼ばれています。ワークフローに関する情報やsecretsに保存した値にアクセスできます。",[42,7354,7356],{"className":6583,"code":7355,"language":6585,"meta":47,"style":47},"echo \"$SSH_KEY\" > id_rsa\nmkdir ~\u002F.ssh\nchmod 700 ~\u002F.ssh\nmv id_rsa ~\u002F.ssh\u002F\nchmod 600 ~\u002F.ssh\u002Fid_rsa\n",[49,7357,7358,7363,7368,7373,7378],{"__ignoreMap":47},[52,7359,7360],{"class":54,"line":55},[52,7361,7362],{"class":75},"echo \"$SSH_KEY\" > id_rsa\n",[52,7364,7365],{"class":54,"line":83},[52,7366,7367],{"class":75},"mkdir ~\u002F.ssh\n",[52,7369,7370],{"class":54,"line":115},[52,7371,7372],{"class":75},"chmod 700 ~\u002F.ssh\n",[52,7374,7375],{"class":54,"line":142},[52,7376,7377],{"class":75},"mv id_rsa ~\u002F.ssh\u002F\n",[52,7379,7380],{"class":54,"line":169},[52,7381,7382],{"class":75},"chmod 600 ~\u002F.ssh\u002Fid_rsa\n",[13,7384,7337,7385,723,7387,7390,7391,7394,7395,7398],{},[49,7386,7344],{},[49,7388,7389],{},"id_rsa","としてファイルに出力。そして",[49,7392,7393],{},"~\u002F.ssh","ディレクトリを作成してその配下に置いておきます。鍵と",[49,7396,7397],{},".ssh","ディレクトリの権限変更を忘れないようにしてください。",[42,7400,7402],{"className":6583,"code":7401,"language":6585,"meta":47,"style":47},"echo \"HOST deploy\" >> ~\u002F.ssh\u002Fconfig\necho \"HostName $SSH_HOST\" >> ~\u002F.ssh\u002Fconfig\necho \"user $SSH_USER\" >> ~\u002F.ssh\u002Fconfig\necho \"Port $SSH_PORT\" >> ~\u002F.ssh\u002Fconfig\necho \"IdentityFile $HOME\u002F.ssh\u002Fid_rsa\" >> ~\u002F.ssh\u002Fconfig\necho \"StrictHostKeyChecking no\" >> ~\u002F.ssh\u002Fconfig\necho \"UserKnownHostsFile=\u002Fdev\u002Fnull\" >> ~\u002F.ssh\u002Fconfig\nchmod 644 ~\u002F.ssh\u002Fconfig\n",[49,7403,7404,7409,7414,7419,7424,7429,7434,7439],{"__ignoreMap":47},[52,7405,7406],{"class":54,"line":55},[52,7407,7408],{"class":75},"echo \"HOST deploy\" >> ~\u002F.ssh\u002Fconfig\n",[52,7410,7411],{"class":54,"line":83},[52,7412,7413],{"class":75},"echo \"HostName $SSH_HOST\" >> ~\u002F.ssh\u002Fconfig\n",[52,7415,7416],{"class":54,"line":115},[52,7417,7418],{"class":75},"echo \"user $SSH_USER\" >> ~\u002F.ssh\u002Fconfig\n",[52,7420,7421],{"class":54,"line":142},[52,7422,7423],{"class":75},"echo \"Port $SSH_PORT\" >> ~\u002F.ssh\u002Fconfig\n",[52,7425,7426],{"class":54,"line":169},[52,7427,7428],{"class":75},"echo \"IdentityFile $HOME\u002F.ssh\u002Fid_rsa\" >> ~\u002F.ssh\u002Fconfig\n",[52,7430,7431],{"class":54,"line":302},[52,7432,7433],{"class":75},"echo \"StrictHostKeyChecking no\" >> ~\u002F.ssh\u002Fconfig\n",[52,7435,7436],{"class":54,"line":308},[52,7437,7438],{"class":75},"echo \"UserKnownHostsFile=\u002Fdev\u002Fnull\" >> ~\u002F.ssh\u002Fconfig\n",[52,7440,7441],{"class":54,"line":318},[52,7442,7443],{"class":75},"chmod 644 ~\u002F.ssh\u002Fconfig\n",[13,7445,7446,7449,7450,7453,7454,7457],{},[49,7447,7448],{},"~\u002F.ssh\u002Fconfig","ファイルにエイリアスの記述を書いておきます。SSHは初回接続時に警告をだすので",[49,7451,7452],{},"StrictHostKeyChecking no","を設定し、known_hostsを出さないように",[49,7455,7456],{},"UserKnownHostsFile=\u002Fdev\u002Fnull","としておきます。",[13,7459,7460,7461,7464,7465,7468],{},"最後に権限をきちんと設定すれば、",[49,7462,7463],{},"deploy","というSSHエイリアスが使える用意なります。手動で",[49,7466,7467],{},".ssh\u002Fconfig","の設定をしていたのを自動化した感じです。",[1499,7470,7472],{"id":7471},"ビルド処理rsync処理を記述","ビルド処理・rsync処理を記述",[13,7474,7475],{},"SSHの設定はできたのでLaravelのアセットビルドをするコードを書きます。",[42,7477,7479],{"className":6583,"code":7478,"language":6585,"meta":47,"style":47},"- name: public build\n  run: |\n    npm install\n    npm run prod\n    rsync -av --delete \\\n    .\u002Fpublic\u002Fcss .\u002Fpublic\u002Fjs .\u002Fpublic\u002Fimages \\\n    deploy:$LARAVEL_PATH\u002Fpublic\u002F\n    scp .\u002Fpublic\u002Fmix-manifest.json deploy:$LARAVEL_PATH\u002Fpublic\u002Fmix-manifest.json\n  env:\n    LARAVEL_PATH : ${{ secrets.LARAVEL_PATH }}\n",[49,7480,7481,7491,7500,7505,7510,7515,7520,7525,7530,7537],{"__ignoreMap":47},[52,7482,7483,7485,7487,7489],{"class":54,"line":55},[52,7484,3284],{"class":58},[52,7486,66],{"class":62},[52,7488,373],{"class":58},[52,7490,6341],{"class":75},[52,7492,7493,7496,7498],{"class":54,"line":83},[52,7494,7495],{"class":62},"  run",[52,7497,373],{"class":58},[52,7499,6351],{"class":360},[52,7501,7502],{"class":54,"line":115},[52,7503,7504],{"class":75},"    npm install\n",[52,7506,7507],{"class":54,"line":142},[52,7508,7509],{"class":75},"    npm run prod\n",[52,7511,7512],{"class":54,"line":169},[52,7513,7514],{"class":75},"    rsync -av --delete \\\n",[52,7516,7517],{"class":54,"line":302},[52,7518,7519],{"class":75},"    .\u002Fpublic\u002Fcss .\u002Fpublic\u002Fjs .\u002Fpublic\u002Fimages \\\n",[52,7521,7522],{"class":54,"line":308},[52,7523,7524],{"class":75},"    deploy:$LARAVEL_PATH\u002Fpublic\u002F\n",[52,7526,7527],{"class":54,"line":318},[52,7528,7529],{"class":75},"    scp .\u002Fpublic\u002Fmix-manifest.json deploy:$LARAVEL_PATH\u002Fpublic\u002Fmix-manifest.json\n",[52,7531,7532,7535],{"class":54,"line":328},[52,7533,7534],{"class":62},"  env",[52,7536,6220],{"class":58},[52,7538,7539,7542,7544],{"class":54,"line":337},[52,7540,7541],{"class":62},"    LARAVEL_PATH",[52,7543,6396],{"class":58},[52,7545,6399],{"class":75},[13,7547,7548],{},"ワークフローの初期位置はリポジトリルートと対応しています。最初にライブラリなどをインストールしてからビルドします。",[13,7550,7551,7552,7555],{},"終わったらrsyncを行います。エイリアスを定義しておいたので",[49,7553,7554],{},"deploy:$LARAVEL_PATH\u002Fpublic\u002F","と簡単な記述で済みます。",[1499,7557,7559],{"id":7558},"nuxtjsのビルド処理を記述する","Nuxt.jsのビルド処理を記述する",[13,7561,7562,7563,7565],{},"次に",[49,7564,6464],{},"に移動してnuxt.jsのビルド処理を記述します。",[42,7567,7569],{"className":6583,"code":7568,"language":6585,"meta":47,"style":47},"    - name: nuxt build\n      run: |\n        cd nuxt\n        touch .env\n        echo \"GOOGLE_MAP_API_KEY=${{ secrets.GOOGLE_MAP_API_KEY }}\" >> .env\n        npm install\n        npm run generate\n        rsync -av --delete .\u002Fdist deploy:$LARAVEL_PATH\u002Fnuxt\u002F\n      env:\n        LARAVEL_PATH : ${{ secrets.LARAVEL_PATH }}\n",[49,7570,7571,7582,7590,7595,7600,7605,7609,7614,7619,7625],{"__ignoreMap":47},[52,7572,7573,7575,7577,7579],{"class":54,"line":55},[52,7574,6257],{"class":58},[52,7576,66],{"class":62},[52,7578,373],{"class":58},[52,7580,7581],{"class":75}," nuxt build\n",[52,7583,7584,7586,7588],{"class":54,"line":83},[52,7585,6346],{"class":62},[52,7587,373],{"class":58},[52,7589,6351],{"class":360},[52,7591,7592],{"class":54,"line":115},[52,7593,7594],{"class":75},"        cd nuxt\n",[52,7596,7597],{"class":54,"line":142},[52,7598,7599],{"class":75},"        touch .env\n",[52,7601,7602],{"class":54,"line":169},[52,7603,7604],{"class":75},"        echo \"GOOGLE_MAP_API_KEY=${{ secrets.GOOGLE_MAP_API_KEY }}\" >> .env\n",[52,7606,7607],{"class":54,"line":302},[52,7608,6356],{"class":75},[52,7610,7611],{"class":54,"line":308},[52,7612,7613],{"class":75},"        npm run generate\n",[52,7615,7616],{"class":54,"line":318},[52,7617,7618],{"class":75},"        rsync -av --delete .\u002Fdist deploy:$LARAVEL_PATH\u002Fnuxt\u002F\n",[52,7620,7621,7623],{"class":54,"line":328},[52,7622,6386],{"class":62},[52,7624,6220],{"class":58},[52,7626,7627,7629,7631],{"class":54,"line":337},[52,7628,6393],{"class":62},[52,7630,6396],{"class":58},[52,7632,6399],{"class":75},[1566,7634,7636],{"id":7635},"envファイルを使うには",".envファイルを使うには",[13,7638,7639,7640,7642,7643,7646],{},"プロジェクトによっては",[49,7641,6497],{},"ファイルを使用して端末ごとに環境変数を定義していると思います。.envは大体",[49,7644,7645],{},"gitignore","されてバージョン管理されていないので、ワークフローないで作成します。",[42,7648,7650],{"className":6583,"code":7649,"language":6585,"meta":47,"style":47},"      run: |\n        cd nuxt\n        touch .env\n        echo \"GOOGLE_MAP_API_KEY=${{ secrets.GOOGLE_MAP_API_KEY }}\" >> .env\n        npm install\n        npm run generate\n        rsync -av --delete .\u002Fdist deploy:$LARAVEL_PATH\u002Fnuxt\u002F\n      env:\n        LARAVEL_PATH : ${{ secrets.LARAVEL_PATH }}\n",[49,7651,7652,7660,7664,7668,7672,7676,7680,7684,7690],{"__ignoreMap":47},[52,7653,7654,7656,7658],{"class":54,"line":55},[52,7655,6346],{"class":62},[52,7657,373],{"class":58},[52,7659,6351],{"class":360},[52,7661,7662],{"class":54,"line":83},[52,7663,7594],{"class":75},[52,7665,7666],{"class":54,"line":115},[52,7667,7599],{"class":75},[52,7669,7670],{"class":54,"line":142},[52,7671,7604],{"class":75},[52,7673,7674],{"class":54,"line":169},[52,7675,6356],{"class":75},[52,7677,7678],{"class":54,"line":302},[52,7679,7613],{"class":75},[52,7681,7682],{"class":54,"line":308},[52,7683,7618],{"class":75},[52,7685,7686,7688],{"class":54,"line":318},[52,7687,6386],{"class":62},[52,7689,6220],{"class":58},[52,7691,7692,7694,7696],{"class":54,"line":328},[52,7693,6393],{"class":62},[52,7695,6396],{"class":58},[52,7697,6399],{"class":75},[13,7699,7700,7701,7703,7704,7707],{},"と必要な箇所で",[49,7702,6497],{},"ファイルを作って、",[49,7705,7706],{},"echo \"GOOGLE_MAP_API_KEY=${{ secrets.GOOGLE_MAP_API_KEY }}\" >> .env","とGithubの環境変数内容を出力します。これでOKです。",[13,7709,7710,7711,7714],{},"nuxtは静的書出すると",[49,7712,7713],{},"dist","が作成されるので、rsyncで転送します。",[1499,7716,7717],{"id":7717},"保存する",[13,7719,7720,7721,7724],{},"記述が終わったら画面右上の「Start Commit」をおして内容をコミットします。ちなみにワークフローファイルはリポジトリの",[49,7722,7723],{},".github\u002Fworkflows","というディレクトリが作られ、そこに保存されます。",[17,7726,7728],{"id":7727},"全ビルドを実行手動","全ビルドを実行（手動）",[13,7730,7731],{},"ではビルドしましょう。Actionsで対象のデプロイ名を選択して「Run Workflow」を押して、対象ブランチを選択して実行します。",[729,7733],{":src":7734},"'github-actions-node-js\u002Fdispatch-job.png'",[13,7736,7737],{},"ビルド中の様子やログは随時確認することができます。ビルドが無事に終了すると以下のように全てチェックマークとなり、処理が終了します。どこかエラーが起きた場合はそこで処理が中止されます。",[729,7739],{":src":7740},"'github-actions-node-js\u002Fsuccess.png'",[13,7742,7743],{},"終わったら本番サーバーで対象のファイルが作られているかなどを確認してみましょう。",[17,7745,7746],{"id":7746},"使ってみた感想",[13,7748,7749],{},"少人数でこれぐらいであればローカルPCで誰でもできそうですが、ボタンひとつでビルドできるようになることや手違いもなくなるのでとてもおすすめです。色々自動化できるようにGithub Actionsを色々探ってみます。",[1414,7751,7752],{},"html pre.shiki code .s-wAU, html code.shiki .s-wAU{--shiki-default:#F07178}html pre.shiki code .sAklC, html code.shiki .sAklC{--shiki-default:#89DDFF}html pre.shiki code .sfyAc, html code.shiki .sfyAc{--shiki-default:#C3E88D}html pre.shiki code .sbqyR, html code.shiki .sbqyR{--shiki-default:#FF9CAC}html pre.shiki code .s0W1g, html code.shiki .s0W1g{--shiki-default:#BABED8}html pre.shiki code .s6cf3, html code.shiki .s6cf3{--shiki-default:#89DDFF;--shiki-default-font-style:italic}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html pre.shiki code .sC9rS, html code.shiki .sC9rS{--shiki-default:#464B5D;--shiki-default-font-style:italic}",{"title":47,"searchDepth":115,"depth":115,"links":7754},[7755,7759,7760,7761,7762,7772,7773],{"id":6144,"depth":83,"text":6145,"children":7756},[7757,7758],{"id":6405,"depth":115,"text":6405},{"id":6411,"depth":115,"text":6411},{"id":6426,"depth":83,"text":6426},{"id":6491,"depth":83,"text":6491},{"id":6565,"depth":83,"text":6565},{"id":6140,"depth":83,"text":6140,"children":7763},[7764,7765,7766,7767,7768,7771],{"id":6855,"depth":115,"text":6855},{"id":7105,"depth":115,"text":7105},{"id":7199,"depth":115,"text":7200},{"id":7471,"depth":115,"text":7472},{"id":7558,"depth":115,"text":7559,"children":7769},[7770],{"id":7635,"depth":142,"text":7636},{"id":7717,"depth":115,"text":7717},{"id":7727,"depth":83,"text":7728},{"id":7746,"depth":83,"text":7746},[1424],"2026-02-11","not node.jsな環境でアセットをビルドして転送する。",{},"\u002Farticles\u002Fgithub-actions-node-js",{"title":6119,"description":7776},"articles\u002Fgithub-actions-node-js",[2022,7782],"node","tRNfJ9LFek0TNwphGk2rKIZmRCAhyvmVgSvx-MGlLXE",{"id":7785,"title":7786,"body":7787,"category":9566,"createdAt":9567,"description":7786,"extension":1427,"index":1428,"meta":9568,"navigation":340,"path":9569,"publish":340,"seo":9570,"series":1428,"seriesTitle":1428,"stem":9571,"tag":9572,"thumbnail":9574,"updatedAt":9567,"__hash__":9575},"articles\u002Farticles\u002Fbuild-lamp-with-docker.md","Dockerでosから作るcentos8+apache2.4+laravel 6 開発環境構築",{"type":10,"value":7788,"toc":9540},[7789,7792,7795,7798,7804,7807,8058,8061,8072,8075,8078,8088,8102,8105,8108,8111,8114,8342,8345,8349,8418,8430,8437,8455,8458,8462,8468,8485,8502,8509,8512,8518,8524,8528,8561,8579,8587,8590,8623,8626,8630,8633,8650,8653,8656,8662,8676,8682,8685,8691,8912,8915,8918,8921,9020,9031,9034,9037,9074,9090,9093,9127,9143,9147,9254,9263,9266,9269,9276,9282,9291,9297,9309,9313,9327,9333,9340,9343,9347,9357,9363,9370,9376,9381,9384,9387,9393,9400,9406,9409,9413,9416,9422,9425,9431,9442,9448,9457,9463,9472,9482,9488,9496,9528,9531,9534,9537],[13,7790,7791],{},"こんにちはjunです。laravelの開発環境をDockerで構築した機会がありました。よくlaravelぐらいならば apcheイメージを入れたりして構築すると思います。その場合大体イメージのosがUbuntuになったりしますが、本番環境のosはcentosだったりと「osから構築したいなー」と思ったので今回はDockerfileとdocker-composeを用いてosからlaravelまで構築しようと思います。",[13,7793,7794],{},"この記事で使用したOSとdockerのバージョン\ndocker：19.03.13\nmaxOS Catalina 10.15.5",[17,7796,7797],{"id":7797},"構成の全体像",[42,7799,7802],{"className":7800,"code":7801,"language":452},[1615],"centos_laravel\n├── docker-compose.yml\n├── laravel\n│\n├── web\n    ├── Dockerfile\n    ├── httpd.conf\n    └── php.ini\n",[49,7803,7801],{"__ignoreMap":47},[13,7805,7806],{},"ディレクトリは上記の様な感じです。docker-compose.ymlは以下の様になっています。",[42,7808,7811],{"className":6173,"code":7809,"filename":7810,"language":6175,"meta":47,"style":47},"version: '3'\nservices: \n  web_1:\n    image: centos8_apache:1.0\n    depends_on: \n      - db\n    volumes: \n      - .\u002Flaravel\u002F:\u002Fvar\u002Fwww\u002Fhtml\u002F\n      - \u002Fsys\u002Ffs\u002Fcgroup:\u002Fsys\u002Ffs\u002Fcgroup:ro\n    ports: \n      - \"9000:80\"\n      - \"3000:3000\"\n    privileged: true\n    command: \u002Fsbin\u002Finit\n  db:\n    image: mysql:5.7\n    environment:\n      MYSQL_DATABASE: larvel_docker\n      MYSQL_USER: test\n      MYSQL_PASSWORD: testtest\n      MYSQL_ROOT_PASSWORD: rootroot\n    ports: \n      - \"3306:3306\"\n    volumes: \n      - laravel_data:\u002Fvar\u002Flib\u002Fmysql\nvolumes: \n  laravel_data: {}\n","docker-compose.yml",[49,7812,7813,7826,7835,7842,7851,7860,7868,7877,7884,7891,7900,7911,7922,7932,7942,7949,7958,7965,7975,7985,7995,8005,8013,8024,8032,8039,8048],{"__ignoreMap":47},[52,7814,7815,7818,7820,7822,7824],{"class":54,"line":55},[52,7816,7817],{"class":62},"version",[52,7819,373],{"class":58},[52,7821,6309],{"class":58},[52,7823,39],{"class":75},[52,7825,6315],{"class":58},[52,7827,7828,7831,7833],{"class":54,"line":83},[52,7829,7830],{"class":62},"services",[52,7832,373],{"class":58},[52,7834,283],{"class":105},[52,7836,7837,7840],{"class":54,"line":115},[52,7838,7839],{"class":62},"  web_1",[52,7841,6220],{"class":58},[52,7843,7844,7846,7848],{"class":54,"line":142},[52,7845,2534],{"class":62},[52,7847,373],{"class":58},[52,7849,7850],{"class":75}," centos8_apache:1.0\n",[52,7852,7853,7856,7858],{"class":54,"line":169},[52,7854,7855],{"class":62},"    depends_on",[52,7857,373],{"class":58},[52,7859,283],{"class":105},[52,7861,7862,7865],{"class":54,"line":302},[52,7863,7864],{"class":58},"      -",[52,7866,7867],{"class":75}," db\n",[52,7869,7870,7873,7875],{"class":54,"line":308},[52,7871,7872],{"class":62},"    volumes",[52,7874,373],{"class":58},[52,7876,283],{"class":105},[52,7878,7879,7881],{"class":54,"line":318},[52,7880,7864],{"class":58},[52,7882,7883],{"class":75}," .\u002Flaravel\u002F:\u002Fvar\u002Fwww\u002Fhtml\u002F\n",[52,7885,7886,7888],{"class":54,"line":328},[52,7887,7864],{"class":58},[52,7889,7890],{"class":75}," \u002Fsys\u002Ffs\u002Fcgroup:\u002Fsys\u002Ffs\u002Fcgroup:ro\n",[52,7892,7893,7896,7898],{"class":54,"line":337},[52,7894,7895],{"class":62},"    ports",[52,7897,373],{"class":58},[52,7899,283],{"class":105},[52,7901,7902,7904,7906,7909],{"class":54,"line":344},[52,7903,7864],{"class":58},[52,7905,3597],{"class":58},[52,7907,7908],{"class":75},"9000:80",[52,7910,266],{"class":58},[52,7912,7913,7915,7917,7920],{"class":54,"line":354},[52,7914,7864],{"class":58},[52,7916,3597],{"class":58},[52,7918,7919],{"class":75},"3000:3000",[52,7921,266],{"class":58},[52,7923,7924,7927,7929],{"class":54,"line":367},[52,7925,7926],{"class":62},"    privileged",[52,7928,373],{"class":58},[52,7930,7931],{"class":6196}," true\n",[52,7933,7934,7937,7939],{"class":54,"line":387},[52,7935,7936],{"class":62},"    command",[52,7938,373],{"class":58},[52,7940,7941],{"class":75}," \u002Fsbin\u002Finit\n",[52,7943,7944,7947],{"class":54,"line":415},[52,7945,7946],{"class":62},"  db",[52,7948,6220],{"class":58},[52,7950,7951,7953,7955],{"class":54,"line":427},[52,7952,2534],{"class":62},[52,7954,373],{"class":58},[52,7956,7957],{"class":75}," mysql:5.7\n",[52,7959,7960,7963],{"class":54,"line":435},[52,7961,7962],{"class":62},"    environment",[52,7964,6220],{"class":58},[52,7966,7967,7970,7972],{"class":54,"line":446},[52,7968,7969],{"class":62},"      MYSQL_DATABASE",[52,7971,373],{"class":58},[52,7973,7974],{"class":75}," larvel_docker\n",[52,7976,7977,7980,7982],{"class":54,"line":480},[52,7978,7979],{"class":62},"      MYSQL_USER",[52,7981,373],{"class":58},[52,7983,7984],{"class":75}," test\n",[52,7986,7987,7990,7992],{"class":54,"line":509},[52,7988,7989],{"class":62},"      MYSQL_PASSWORD",[52,7991,373],{"class":58},[52,7993,7994],{"class":75}," testtest\n",[52,7996,7997,8000,8002],{"class":54,"line":539},[52,7998,7999],{"class":62},"      MYSQL_ROOT_PASSWORD",[52,8001,373],{"class":58},[52,8003,8004],{"class":75}," rootroot\n",[52,8006,8007,8009,8011],{"class":54,"line":547},[52,8008,7895],{"class":62},[52,8010,373],{"class":58},[52,8012,283],{"class":105},[52,8014,8015,8017,8019,8022],{"class":54,"line":553},[52,8016,7864],{"class":58},[52,8018,3597],{"class":58},[52,8020,8021],{"class":75},"3306:3306",[52,8023,266],{"class":58},[52,8025,8026,8028,8030],{"class":54,"line":559},[52,8027,7872],{"class":62},[52,8029,373],{"class":58},[52,8031,283],{"class":105},[52,8033,8034,8036],{"class":54,"line":564},[52,8035,7864],{"class":58},[52,8037,8038],{"class":75}," laravel_data:\u002Fvar\u002Flib\u002Fmysql\n",[52,8040,8041,8044,8046],{"class":54,"line":569},[52,8042,8043],{"class":62},"volumes",[52,8045,373],{"class":58},[52,8047,283],{"class":105},[52,8049,8050,8053,8055],{"class":54,"line":1106},[52,8051,8052],{"class":62},"  laravel_data",[52,8054,373],{"class":58},[52,8056,8057],{"class":58}," {}\n",[13,8059,8060],{},"構築の流れとしては",[1467,8062,8063,8066,8069],{},[1470,8064,8065],{},"centos8、apache2.4、php7.4、nodejsが入ったwebサーバーイメージを作成",[1470,8067,8068],{},"webサーバーイメージとmysqlイメージで作られた2つのコンテナをdocker-composeで連携する",[1470,8070,8071],{},"laraevelのマイグレーションを行う。",[13,8073,8074],{},"と行った流れで行います。",[1499,8076,8077],{"id":8077},"webサーバーイメージ",[13,8079,8080,8083,8084,8087],{},[49,8081,8082],{},"web","ディレクトリ配下にはcentos8、apache、php、nodeがインストールされたイメージを作成する",[49,8085,8086],{},"Dockerfile","とapacheの設定ファイル、phpの設定ファイルがあります。",[13,8089,8090,8091,8094,8095,8097,8098,8101],{},"laravelでは実運用の際にドキュメントルート を変更する必要があるので",[49,8092,8093],{},"httpd.conf","を編集して、それをコンテナの",[49,8096,8093],{},"にコピーしています。",[49,8099,8100],{},"php.ini","はタイムゾーン を書き足すぐらいですけど同じ様にエディタ上で設定ファイルを変更できる様にしています。",[1499,8103,8104],{"id":8104},"laravelディレクトリ",[13,8106,8107],{},"larvelディレクトリはlaravelソースが予めインストールされています。そのソースをコンテナのドキュメントルート 配下にボリュームすることでローカルのエディタで編集してコンテナ環境でレビューすることができます。",[17,8109,8110],{"id":8110},"webサーバーイメージを作成",[13,8112,8113],{},"それではまずwebサーバーイメージを作成していきます。と言っても以下のDockerfileの内容で事足ります。",[42,8115,8119],{"className":8116,"code":8117,"language":8118,"meta":47,"style":47},"language-dockerfile shiki shiki-themes material-theme-ocean","FROM centos:8\n\nENV container docker\nRUN (cd \u002Flib\u002Fsystemd\u002Fsystem\u002Fsysinit.target.wants\u002F; for i in *; do [ $i == \\\nsystemd-tmpfiles-setup.service ] || rm -f $i; done); \\\nrm -f \u002Flib\u002Fsystemd\u002Fsystem\u002Fmulti-user.target.wants\u002F*;\\\nrm -f \u002Fetc\u002Fsystemd\u002Fsystem\u002F*.wants\u002F*;\\\nrm -f \u002Flib\u002Fsystemd\u002Fsystem\u002Flocal-fs.target.wants\u002F*; \\\nrm -f \u002Flib\u002Fsystemd\u002Fsystem\u002Fsockets.target.wants\u002F*udev*; \\\nrm -f \u002Flib\u002Fsystemd\u002Fsystem\u002Fsockets.target.wants\u002F*initctl*; \\\nrm -f \u002Flib\u002Fsystemd\u002Fsystem\u002Fbasic.target.wants\u002F*;\\\nrm -f \u002Flib\u002Fsystemd\u002Fsystem\u002Fanaconda.target.wants\u002F*;\nVOLUME [ \"\u002Fsys\u002Ffs\u002Fcgroup\" ]\nCMD [\"\u002Fusr\u002Fsbin\u002Finit\"]\n\nRUN \u002Fbin\u002Fcp \u002Fusr\u002Fshare\u002Fzoneinfo\u002FAsia\u002FTokyo \u002Fetc\u002Flocaltime\n\nRUN yum install -y epel-release && yum clean all\n\nRUN rpm -ivh http:\u002F\u002Fftp.riken.jp\u002FLinux\u002Fremi\u002Fenterprise\u002Fremi-release-8.rpm\n\nRUN yum -y update && yum clean all\n\nRUN yum -y install httpd && yum clean all\n\nCOPY .\u002Fhttpd.conf \u002Fetc\u002Fhttpd\u002Fconf\u002Fhttpd.conf\n\nRUN yum -y install php74-php php74-php-mysqli php74-php-gd php74-php-mbstring php74-php-opcache php74-php-xml php74-php-pear php74-php-devel php74-php-pecl-imagick php74-php-pecl-imagick-devel php74-php-pecl-zip\n\nRUN ln \u002Fusr\u002Fbin\u002Fphp74 \u002Fusr\u002Fbin\u002Fphp\n\nCOPY .\u002Fphp.ini \u002Fetc\u002Fopt\u002Fremi\u002Fphp74\u002Fphp.ini\n\nRUN chown -R apache:apache \u002Fvar\u002Fwww\u002Fhtml\n\nRUN systemctl enable php74-php-fpm\n\nRUN systemctl enable httpd \n\nVOLUME [ \"\u002Fvar\u002Fwww\u002Fhtml\" ]\n\nRUN chown -R apache:apache \u002Fvar\u002Fwww\u002Fhtml\n\nRUN dnf -y module enable nodejs:12\n\nRUN dnf -y install nodejs\n\nEXPOSE 80\n","dockerfile",[49,8120,8121,8126,8130,8135,8140,8145,8150,8155,8160,8165,8170,8175,8180,8185,8190,8194,8199,8203,8208,8212,8217,8221,8226,8230,8235,8239,8244,8248,8253,8257,8262,8266,8271,8275,8280,8284,8289,8293,8298,8302,8307,8311,8315,8319,8324,8328,8333,8337],{"__ignoreMap":47},[52,8122,8123],{"class":54,"line":55},[52,8124,8125],{},"FROM centos:8\n",[52,8127,8128],{"class":54,"line":83},[52,8129,341],{"emptyLinePlaceholder":340},[52,8131,8132],{"class":54,"line":115},[52,8133,8134],{},"ENV container docker\n",[52,8136,8137],{"class":54,"line":142},[52,8138,8139],{},"RUN (cd \u002Flib\u002Fsystemd\u002Fsystem\u002Fsysinit.target.wants\u002F; for i in *; do [ $i == \\\n",[52,8141,8142],{"class":54,"line":169},[52,8143,8144],{},"systemd-tmpfiles-setup.service ] || rm -f $i; done); \\\n",[52,8146,8147],{"class":54,"line":302},[52,8148,8149],{},"rm -f \u002Flib\u002Fsystemd\u002Fsystem\u002Fmulti-user.target.wants\u002F*;\\\n",[52,8151,8152],{"class":54,"line":308},[52,8153,8154],{},"rm -f \u002Fetc\u002Fsystemd\u002Fsystem\u002F*.wants\u002F*;\\\n",[52,8156,8157],{"class":54,"line":318},[52,8158,8159],{},"rm -f \u002Flib\u002Fsystemd\u002Fsystem\u002Flocal-fs.target.wants\u002F*; \\\n",[52,8161,8162],{"class":54,"line":328},[52,8163,8164],{},"rm -f \u002Flib\u002Fsystemd\u002Fsystem\u002Fsockets.target.wants\u002F*udev*; \\\n",[52,8166,8167],{"class":54,"line":337},[52,8168,8169],{},"rm -f \u002Flib\u002Fsystemd\u002Fsystem\u002Fsockets.target.wants\u002F*initctl*; \\\n",[52,8171,8172],{"class":54,"line":344},[52,8173,8174],{},"rm -f \u002Flib\u002Fsystemd\u002Fsystem\u002Fbasic.target.wants\u002F*;\\\n",[52,8176,8177],{"class":54,"line":354},[52,8178,8179],{},"rm -f \u002Flib\u002Fsystemd\u002Fsystem\u002Fanaconda.target.wants\u002F*;\n",[52,8181,8182],{"class":54,"line":367},[52,8183,8184],{},"VOLUME [ \"\u002Fsys\u002Ffs\u002Fcgroup\" ]\n",[52,8186,8187],{"class":54,"line":387},[52,8188,8189],{},"CMD [\"\u002Fusr\u002Fsbin\u002Finit\"]\n",[52,8191,8192],{"class":54,"line":415},[52,8193,341],{"emptyLinePlaceholder":340},[52,8195,8196],{"class":54,"line":427},[52,8197,8198],{},"RUN \u002Fbin\u002Fcp \u002Fusr\u002Fshare\u002Fzoneinfo\u002FAsia\u002FTokyo \u002Fetc\u002Flocaltime\n",[52,8200,8201],{"class":54,"line":435},[52,8202,341],{"emptyLinePlaceholder":340},[52,8204,8205],{"class":54,"line":446},[52,8206,8207],{},"RUN yum install -y epel-release && yum clean all\n",[52,8209,8210],{"class":54,"line":480},[52,8211,341],{"emptyLinePlaceholder":340},[52,8213,8214],{"class":54,"line":509},[52,8215,8216],{},"RUN rpm -ivh http:\u002F\u002Fftp.riken.jp\u002FLinux\u002Fremi\u002Fenterprise\u002Fremi-release-8.rpm\n",[52,8218,8219],{"class":54,"line":539},[52,8220,341],{"emptyLinePlaceholder":340},[52,8222,8223],{"class":54,"line":547},[52,8224,8225],{},"RUN yum -y update && yum clean all\n",[52,8227,8228],{"class":54,"line":553},[52,8229,341],{"emptyLinePlaceholder":340},[52,8231,8232],{"class":54,"line":559},[52,8233,8234],{},"RUN yum -y install httpd && yum clean all\n",[52,8236,8237],{"class":54,"line":564},[52,8238,341],{"emptyLinePlaceholder":340},[52,8240,8241],{"class":54,"line":569},[52,8242,8243],{},"COPY .\u002Fhttpd.conf \u002Fetc\u002Fhttpd\u002Fconf\u002Fhttpd.conf\n",[52,8245,8246],{"class":54,"line":1106},[52,8247,341],{"emptyLinePlaceholder":340},[52,8249,8250],{"class":54,"line":1135},[52,8251,8252],{},"RUN yum -y install php74-php php74-php-mysqli php74-php-gd php74-php-mbstring php74-php-opcache php74-php-xml php74-php-pear php74-php-devel php74-php-pecl-imagick php74-php-pecl-imagick-devel php74-php-pecl-zip\n",[52,8254,8255],{"class":54,"line":1164},[52,8256,341],{"emptyLinePlaceholder":340},[52,8258,8259],{"class":54,"line":1193},[52,8260,8261],{},"RUN ln \u002Fusr\u002Fbin\u002Fphp74 \u002Fusr\u002Fbin\u002Fphp\n",[52,8263,8264],{"class":54,"line":1200},[52,8265,341],{"emptyLinePlaceholder":340},[52,8267,8268],{"class":54,"line":1205},[52,8269,8270],{},"COPY .\u002Fphp.ini \u002Fetc\u002Fopt\u002Fremi\u002Fphp74\u002Fphp.ini\n",[52,8272,8273],{"class":54,"line":1210},[52,8274,341],{"emptyLinePlaceholder":340},[52,8276,8277],{"class":54,"line":1215},[52,8278,8279],{},"RUN chown -R apache:apache \u002Fvar\u002Fwww\u002Fhtml\n",[52,8281,8282],{"class":54,"line":1220},[52,8283,341],{"emptyLinePlaceholder":340},[52,8285,8286],{"class":54,"line":3800},[52,8287,8288],{},"RUN systemctl enable php74-php-fpm\n",[52,8290,8291],{"class":54,"line":3821},[52,8292,341],{"emptyLinePlaceholder":340},[52,8294,8295],{"class":54,"line":3835},[52,8296,8297],{},"RUN systemctl enable httpd \n",[52,8299,8300],{"class":54,"line":3840},[52,8301,341],{"emptyLinePlaceholder":340},[52,8303,8304],{"class":54,"line":3865},[52,8305,8306],{},"VOLUME [ \"\u002Fvar\u002Fwww\u002Fhtml\" ]\n",[52,8308,8309],{"class":54,"line":3879},[52,8310,341],{"emptyLinePlaceholder":340},[52,8312,8313],{"class":54,"line":5506},[52,8314,8279],{},[52,8316,8317],{"class":54,"line":4},[52,8318,341],{"emptyLinePlaceholder":340},[52,8320,8321],{"class":54,"line":5544},[52,8322,8323],{},"RUN dnf -y module enable nodejs:12\n",[52,8325,8326],{"class":54,"line":5561},[52,8327,341],{"emptyLinePlaceholder":340},[52,8329,8330],{"class":54,"line":5566},[52,8331,8332],{},"RUN dnf -y install nodejs\n",[52,8334,8335],{"class":54,"line":5587},[52,8336,341],{"emptyLinePlaceholder":340},[52,8338,8339],{"class":54,"line":5636},[52,8340,8341],{},"EXPOSE 80\n",[13,8343,8344],{},"もう少し細かく解説していきます。",[1499,8346,8348],{"id":8347},"centos8をいれ設定","centos8をいれ、設定",[42,8350,8352],{"className":8116,"code":8351,"language":8118,"meta":47,"style":47},"FROM centos:8\n\nENV container docker\nRUN (cd \u002Flib\u002Fsystemd\u002Fsystem\u002Fsysinit.target.wants\u002F; for i in *; do [ $i == \\\nsystemd-tmpfiles-setup.service ] || rm -f $i; done); \\\nrm -f \u002Flib\u002Fsystemd\u002Fsystem\u002Fmulti-user.target.wants\u002F*;\\\nrm -f \u002Fetc\u002Fsystemd\u002Fsystem\u002F*.wants\u002F*;\\\nrm -f \u002Flib\u002Fsystemd\u002Fsystem\u002Flocal-fs.target.wants\u002F*; \\\nrm -f \u002Flib\u002Fsystemd\u002Fsystem\u002Fsockets.target.wants\u002F*udev*; \\\nrm -f \u002Flib\u002Fsystemd\u002Fsystem\u002Fsockets.target.wants\u002F*initctl*; \\\nrm -f \u002Flib\u002Fsystemd\u002Fsystem\u002Fbasic.target.wants\u002F*;\\\nrm -f \u002Flib\u002Fsystemd\u002Fsystem\u002Fanaconda.target.wants\u002F*;\nVOLUME [ \"\u002Fsys\u002Ffs\u002Fcgroup\" ]\nCMD [\"\u002Fusr\u002Fsbin\u002Finit\"]\n\nRUN \u002Fbin\u002Fcp \u002Fusr\u002Fshare\u002Fzoneinfo\u002FAsia\u002FTokyo \u002Fetc\u002Flocaltime\n",[49,8353,8354,8358,8362,8366,8370,8374,8378,8382,8386,8390,8394,8398,8402,8406,8410,8414],{"__ignoreMap":47},[52,8355,8356],{"class":54,"line":55},[52,8357,8125],{},[52,8359,8360],{"class":54,"line":83},[52,8361,341],{"emptyLinePlaceholder":340},[52,8363,8364],{"class":54,"line":115},[52,8365,8134],{},[52,8367,8368],{"class":54,"line":142},[52,8369,8139],{},[52,8371,8372],{"class":54,"line":169},[52,8373,8144],{},[52,8375,8376],{"class":54,"line":302},[52,8377,8149],{},[52,8379,8380],{"class":54,"line":308},[52,8381,8154],{},[52,8383,8384],{"class":54,"line":318},[52,8385,8159],{},[52,8387,8388],{"class":54,"line":328},[52,8389,8164],{},[52,8391,8392],{"class":54,"line":337},[52,8393,8169],{},[52,8395,8396],{"class":54,"line":344},[52,8397,8174],{},[52,8399,8400],{"class":54,"line":354},[52,8401,8179],{},[52,8403,8404],{"class":54,"line":367},[52,8405,8184],{},[52,8407,8408],{"class":54,"line":387},[52,8409,8189],{},[52,8411,8412],{"class":54,"line":415},[52,8413,341],{"emptyLinePlaceholder":340},[52,8415,8416],{"class":54,"line":427},[52,8417,8198],{},[13,8419,8420,8423,8424,8429],{},[49,8421,8422],{},"FROM centos:8","で書かれている様に、centos8自身のイメージは公式の",[2039,8425,8428],{"href":8426,"rel":8427},"https:\u002F\u002Fhub.docker.com\u002F_\u002Fcentos",[2043],"dockerHub","からpullします。そしてこれらの記述は公式の手順のソースを貼り付けた感じです。",[13,8431,8432,8433,8436],{},"以前dockerにcentos8の環境を作ろうとして詰まった時がありました（",[2039,8434,2044],{"href":8435},"\u002Farticles\u002Fstuck-on-docker-centos8","の記事）。その際はこの以下の部分だけを書いていました。",[42,8438,8440],{"className":8116,"code":8439,"language":8118,"meta":47,"style":47},"FROM centos:8\nRUN \u002Fbin\u002Fcp \u002Fusr\u002Fshare\u002Fzoneinfo\u002FAsia\u002FTokyo \u002Fetc\u002Flocaltime\n#..以下同じ\n",[49,8441,8442,8446,8450],{"__ignoreMap":47},[52,8443,8444],{"class":54,"line":55},[52,8445,8125],{},[52,8447,8448],{"class":54,"line":83},[52,8449,8198],{},[52,8451,8452],{"class":54,"line":115},[52,8453,8454],{},"#..以下同じ\n",[13,8456,8457],{},"純粋に公式のcentosイメージはデフォルトでsystemdがアクティブにならず、コンテナを立てても応答しません。docker-composeの際にも一工夫必要になりますが、まずは上の方の記述でcenotsのイメージ設定をしましょう。",[1499,8459,8461],{"id":8460},"apache24をいれる","apache2.4をいれる",[42,8463,8466],{"className":8464,"code":8465,"language":452},[1615],"RUN yum -y install httpd && yum clean all\n\nCOPY .\u002Fhttpd.conf \u002Fetc\u002Fhttpd\u002Fconf\u002Fhttpd.conf\n",[49,8467,8465],{"__ignoreMap":47},[13,8469,8470,8471,723,8474,8477,8478,8481,8482,8484],{},"こんだけです。",[49,8472,8473],{},"yum -y install httpd",[49,8475,8476],{},"RUN","してhttpdを入れているだけですね。ちなみにdockerコンテナ内でyumなどを用いてインストールする際は",[49,8479,8480],{},"-y","オプションをつけましょう。",[49,8483,8480],{},"オプションは全てyesで答えるというオプションです。これがないとインストール時に「本当にインストールしますか？（Yes\u002FNo）」と聞かれ、ビルドがとまります。",[13,8486,2766,8487,8489,8490,8492,8493,8495,8496,8498,8499,8501],{},[49,8488,8086],{},"と同じディレクトリにいる",[49,8491,8093],{},"をコンテナ内の",[49,8494,8093],{},"にCOPYします。",[49,8497,8093],{},"は別途にapache2.4コンテナを立ててコピーするか、最初はこのCOPY部分をコメントアウトして一度この",[49,8500,8086],{},"をビルドしてコピーしても大丈夫です。",[8503,8504,8508],"div",{"className":8505},[8506,8507],"alert","alert-info","\nコンテナ内からソースを手元にコピーするためにはdocker cp コマンドを用います。\n",[13,8510,8511],{},"例えば別途にapache2.4という名前でコンテナを立ち上げている場合、以下の様にしてhttpd.confをコピーします。",[42,8513,8516],{"className":8514,"code":8515,"language":452},[1615],"docker container cp apache2.4:\u002Fetc\u002Fhttpd\u002Fconf\u002Fhttpd.conf .\u002F\n",[49,8517,8515],{"__ignoreMap":47},[13,8519,8520,8521,8523],{},"こうするとコンテナ内のソースが手元にコピーされます。インストールする環境によって",[49,8522,8093],{},"の置き場所が変わったりもするのでまずはCOPY無しでビルドして、パスを確認してから手元にコピーするといいです。",[1499,8525,8527],{"id":8526},"php-をいれる","php をいれる",[42,8529,8531],{"className":8116,"code":8530,"language":8118,"meta":47,"style":47},"RUN rpm -ivh http:\u002F\u002Fftp.riken.jp\u002FLinux\u002Fremi\u002Fenterprise\u002Fremi-release-8.rpm\n\nRUN yum -y install php74-php php74-php-mysqli php74-php-gd php74-php-mbstring php74-php-opcache php74-php-xml php74-php-pear php74-php-devel php74-php-pecl-imagick php74-php-pecl-imagick-devel php74-php-pecl-zip\n\nRUN ln \u002Fusr\u002Fbin\u002Fphp74 \u002Fusr\u002Fbin\u002Fphp\n\nCOPY .\u002Fphp.ini \u002Fetc\u002Fopt\u002Fremi\u002Fphp74\u002Fphp.ini\n",[49,8532,8533,8537,8541,8545,8549,8553,8557],{"__ignoreMap":47},[52,8534,8535],{"class":54,"line":55},[52,8536,8216],{},[52,8538,8539],{"class":54,"line":83},[52,8540,341],{"emptyLinePlaceholder":340},[52,8542,8543],{"class":54,"line":115},[52,8544,8252],{},[52,8546,8547],{"class":54,"line":142},[52,8548,341],{"emptyLinePlaceholder":340},[52,8550,8551],{"class":54,"line":169},[52,8552,8261],{},[52,8554,8555],{"class":54,"line":302},[52,8556,341],{"emptyLinePlaceholder":340},[52,8558,8559],{"class":54,"line":308},[52,8560,8270],{},[13,8562,8563,8566,8567,8570,8571,8574,8575,8578],{},[49,8564,8565],{},"remi","リポジトリをインストールして",[49,8568,8569],{},"yum","を通じてphp7.4を入れます。",[49,8572,8573],{},"RUN ln \u002Fusr\u002Fbin\u002Fphp74 \u002Fusr\u002Fbin\u002Fphp"," で ",[49,8576,8577],{},"php","コマンドを使用できる様にします。",[13,8580,2766,8581,8583,8584,8586],{},[49,8582,8093],{},"と同じ様に",[49,8585,8100],{},"を手元からコピーします。",[1499,8588,8589],{"id":8589},"権限の設定と永続化",[42,8591,8593],{"className":8116,"code":8592,"language":8118,"meta":47,"style":47},"RUN chown -R apache:apache \u002Fvar\u002Fwww\u002Fhtml\n\nRUN systemctl enable php74-php-fpm\n\nRUN systemctl enable httpd \n\nVOLUME [ \"\u002Fvar\u002Fwww\u002Fhtml\" ]\n",[49,8594,8595,8599,8603,8607,8611,8615,8619],{"__ignoreMap":47},[52,8596,8597],{"class":54,"line":55},[52,8598,8279],{},[52,8600,8601],{"class":54,"line":83},[52,8602,341],{"emptyLinePlaceholder":340},[52,8604,8605],{"class":54,"line":115},[52,8606,8288],{},[52,8608,8609],{"class":54,"line":142},[52,8610,341],{"emptyLinePlaceholder":340},[52,8612,8613],{"class":54,"line":169},[52,8614,8297],{},[52,8616,8617],{"class":54,"line":302},[52,8618,341],{"emptyLinePlaceholder":340},[52,8620,8621],{"class":54,"line":308},[52,8622,8306],{},[13,8624,8625],{},"webサーバーとphpを入れたのでドキュメントルート をapacheが触れる様に権限を変更します。そしてwebサーバーとphp-fpmが自動で起動する様にします。最後にlaravelプロジェクトをドキュメントルート配下にボリュームできる様にボリュームの設定をします。",[1499,8627,8629],{"id":8628},"nodejsをいれるおまけ","node.jsをいれる（おまけ）",[13,8631,8632],{},"node.jsをいれるのはフロントの開発でnuxt.jsを用いるためです。ここは飛ばしても構いません。",[42,8634,8636],{"className":8116,"code":8635,"language":8118,"meta":47,"style":47},"RUN dnf -y module enable nodejs:12\n\nRUN dnf -y install nodejs\n",[49,8637,8638,8642,8646],{"__ignoreMap":47},[52,8639,8640],{"class":54,"line":55},[52,8641,8323],{},[52,8643,8644],{"class":54,"line":83},[52,8645,341],{"emptyLinePlaceholder":340},[52,8647,8648],{"class":54,"line":115},[52,8649,8332],{},[17,8651,8652],{"id":8652},"イメージをビルドする",[13,8654,8655],{},"webサーバーのDockerfileが書き終わったのでまずビルドしてイメージを作りましょう。Dockerfileがあるディレクトリで以下のコマンドを唱えます。",[42,8657,8660],{"className":8658,"code":8659,"language":452},[1615],"$ docker build -t centos_apache:1.0 . \n",[49,8661,8659],{"__ignoreMap":47},[13,8663,8664,8667,8668,8671,8672,8675],{},[49,8665,8666],{},". ","は「現在のディレクトリ」という意味です。そして -t でタグをつけを有効にします。今回作成したイメージは ",[49,8669,8670],{},"centos_apache","で",[49,8673,8674],{},"1.0","というタグをつけておきます。ビルドは少し時間がかかりますが、完了すればローカルにこのイメージが登録されます。dockerhubなどに置いておけば、他の人にがpullできる様になります。",[42,8677,8680],{"className":8678,"code":8679,"language":452},[1615],"$ docker images                                           \nREPOSITORY                     TAG                 IMAGE ID            CREATED             SIZE\ncentos_apache                   1.0                54784hkjdfjk        17 hours ago        941MB\n",[49,8681,8679],{"__ignoreMap":47},[17,8683,8684],{"id":8684},"docker-composeを作成",[13,8686,8687,8688,8690],{},"それではosから調整したwebサーバーイメージは作成できたので、次はdocker-composeを使用してDBコンテナとの連携などをしていきます。",[49,8689,7810],{},"は以下の通りです。",[42,8692,8694],{"className":6173,"code":8693,"filename":7810,"language":6175,"meta":47,"style":47},"version: '3'\nservices: \n  web_1:\n    image: centos_apache:1.0\n    depends_on: \n      - db\n    volumes: \n      - .\u002Flaravel\u002F:\u002Fvar\u002Fwww\u002Fhtml\u002F\n      - \u002Fsys\u002Ffs\u002Fcgroup:\u002Fsys\u002Ffs\u002Fcgroup:ro\n    ports: \n      - \"9000:80\"\n      - \"3000:3000\"\n    privileged: true\n    command: \u002Fsbin\u002Finit\n  db:\n    image: mysql:5.7\n    environment:\n      MYSQL_DATABASE: trend_system\n      MYSQL_USER: trend\n      MYSQL_PASSWORD: trendtrend\n      MYSQL_ROOT_PASSWORD: rootroot\n    ports: \n      - \"3306:3306\"\n    volumes: \n      - laravel_data:\u002Fvar\u002Flib\u002Fmysql\nvolumes: \n  laravel_data: {}\n",[49,8695,8696,8708,8716,8722,8731,8739,8745,8753,8759,8765,8773,8783,8793,8801,8809,8815,8823,8829,8838,8847,8856,8864,8872,8882,8890,8896,8904],{"__ignoreMap":47},[52,8697,8698,8700,8702,8704,8706],{"class":54,"line":55},[52,8699,7817],{"class":62},[52,8701,373],{"class":58},[52,8703,6309],{"class":58},[52,8705,39],{"class":75},[52,8707,6315],{"class":58},[52,8709,8710,8712,8714],{"class":54,"line":83},[52,8711,7830],{"class":62},[52,8713,373],{"class":58},[52,8715,283],{"class":105},[52,8717,8718,8720],{"class":54,"line":115},[52,8719,7839],{"class":62},[52,8721,6220],{"class":58},[52,8723,8724,8726,8728],{"class":54,"line":142},[52,8725,2534],{"class":62},[52,8727,373],{"class":58},[52,8729,8730],{"class":75}," centos_apache:1.0\n",[52,8732,8733,8735,8737],{"class":54,"line":169},[52,8734,7855],{"class":62},[52,8736,373],{"class":58},[52,8738,283],{"class":105},[52,8740,8741,8743],{"class":54,"line":302},[52,8742,7864],{"class":58},[52,8744,7867],{"class":75},[52,8746,8747,8749,8751],{"class":54,"line":308},[52,8748,7872],{"class":62},[52,8750,373],{"class":58},[52,8752,283],{"class":105},[52,8754,8755,8757],{"class":54,"line":318},[52,8756,7864],{"class":58},[52,8758,7883],{"class":75},[52,8760,8761,8763],{"class":54,"line":328},[52,8762,7864],{"class":58},[52,8764,7890],{"class":75},[52,8766,8767,8769,8771],{"class":54,"line":337},[52,8768,7895],{"class":62},[52,8770,373],{"class":58},[52,8772,283],{"class":105},[52,8774,8775,8777,8779,8781],{"class":54,"line":344},[52,8776,7864],{"class":58},[52,8778,3597],{"class":58},[52,8780,7908],{"class":75},[52,8782,266],{"class":58},[52,8784,8785,8787,8789,8791],{"class":54,"line":354},[52,8786,7864],{"class":58},[52,8788,3597],{"class":58},[52,8790,7919],{"class":75},[52,8792,266],{"class":58},[52,8794,8795,8797,8799],{"class":54,"line":367},[52,8796,7926],{"class":62},[52,8798,373],{"class":58},[52,8800,7931],{"class":6196},[52,8802,8803,8805,8807],{"class":54,"line":387},[52,8804,7936],{"class":62},[52,8806,373],{"class":58},[52,8808,7941],{"class":75},[52,8810,8811,8813],{"class":54,"line":415},[52,8812,7946],{"class":62},[52,8814,6220],{"class":58},[52,8816,8817,8819,8821],{"class":54,"line":427},[52,8818,2534],{"class":62},[52,8820,373],{"class":58},[52,8822,7957],{"class":75},[52,8824,8825,8827],{"class":54,"line":435},[52,8826,7962],{"class":62},[52,8828,6220],{"class":58},[52,8830,8831,8833,8835],{"class":54,"line":446},[52,8832,7969],{"class":62},[52,8834,373],{"class":58},[52,8836,8837],{"class":75}," trend_system\n",[52,8839,8840,8842,8844],{"class":54,"line":480},[52,8841,7979],{"class":62},[52,8843,373],{"class":58},[52,8845,8846],{"class":75}," trend\n",[52,8848,8849,8851,8853],{"class":54,"line":509},[52,8850,7989],{"class":62},[52,8852,373],{"class":58},[52,8854,8855],{"class":75}," trendtrend\n",[52,8857,8858,8860,8862],{"class":54,"line":539},[52,8859,7999],{"class":62},[52,8861,373],{"class":58},[52,8863,8004],{"class":75},[52,8865,8866,8868,8870],{"class":54,"line":547},[52,8867,7895],{"class":62},[52,8869,373],{"class":58},[52,8871,283],{"class":105},[52,8873,8874,8876,8878,8880],{"class":54,"line":553},[52,8875,7864],{"class":58},[52,8877,3597],{"class":58},[52,8879,8021],{"class":75},[52,8881,266],{"class":58},[52,8883,8884,8886,8888],{"class":54,"line":559},[52,8885,7872],{"class":62},[52,8887,373],{"class":58},[52,8889,283],{"class":105},[52,8891,8892,8894],{"class":54,"line":564},[52,8893,7864],{"class":58},[52,8895,8038],{"class":75},[52,8897,8898,8900,8902],{"class":54,"line":569},[52,8899,8043],{"class":62},[52,8901,373],{"class":58},[52,8903,283],{"class":105},[52,8905,8906,8908,8910],{"class":54,"line":1106},[52,8907,8052],{"class":62},[52,8909,373],{"class":58},[52,8911,8057],{"class":58},[13,8913,8914],{},"コンテナはwebサーバーとDBサーバーの二つをたて、それらをdepends_onを通じて連携します。そして手元にはlaravelとnuxtをインストールしたプロジェクトディレクトリを置いておき、それをコンテナのドキュメントルートに置くと行った手順です。",[13,8916,8917],{},"それでは詳細を解説していきます。",[1499,8919,8920],{"id":8920},"webサーバーコンテナの記述",[42,8922,8924],{"className":6173,"code":8923,"filename":7810,"language":6175,"meta":47,"style":47},"web_1:\n    image: centos_apache:1.0\n    depends_on: \n        - db\n    volumes: \n        - .\u002Flaravel\u002F:\u002Fvar\u002Fwww\u002Fhtml\u002F\n        - \u002Fsys\u002Ffs\u002Fcgroup:\u002Fsys\u002Ffs\u002Fcgroup:ro\n    ports: \n        - \"9000:80\"\n        - \"3000:3000\"\n    privileged: true\n    command: \u002Fsbin\u002Finit\n",[49,8925,8926,8933,8941,8949,8956,8964,8970,8976,8984,8994,9004,9012],{"__ignoreMap":47},[52,8927,8928,8931],{"class":54,"line":55},[52,8929,8930],{"class":62},"web_1",[52,8932,6220],{"class":58},[52,8934,8935,8937,8939],{"class":54,"line":83},[52,8936,2534],{"class":62},[52,8938,373],{"class":58},[52,8940,8730],{"class":75},[52,8942,8943,8945,8947],{"class":54,"line":115},[52,8944,7855],{"class":62},[52,8946,373],{"class":58},[52,8948,283],{"class":105},[52,8950,8951,8954],{"class":54,"line":142},[52,8952,8953],{"class":58},"        -",[52,8955,7867],{"class":75},[52,8957,8958,8960,8962],{"class":54,"line":169},[52,8959,7872],{"class":62},[52,8961,373],{"class":58},[52,8963,283],{"class":105},[52,8965,8966,8968],{"class":54,"line":302},[52,8967,8953],{"class":58},[52,8969,7883],{"class":75},[52,8971,8972,8974],{"class":54,"line":308},[52,8973,8953],{"class":58},[52,8975,7890],{"class":75},[52,8977,8978,8980,8982],{"class":54,"line":318},[52,8979,7895],{"class":62},[52,8981,373],{"class":58},[52,8983,283],{"class":105},[52,8985,8986,8988,8990,8992],{"class":54,"line":328},[52,8987,8953],{"class":58},[52,8989,3597],{"class":58},[52,8991,7908],{"class":75},[52,8993,266],{"class":58},[52,8995,8996,8998,9000,9002],{"class":54,"line":337},[52,8997,8953],{"class":58},[52,8999,3597],{"class":58},[52,9001,7919],{"class":75},[52,9003,266],{"class":58},[52,9005,9006,9008,9010],{"class":54,"line":344},[52,9007,7926],{"class":62},[52,9009,373],{"class":58},[52,9011,7931],{"class":6196},[52,9013,9014,9016,9018],{"class":54,"line":354},[52,9015,7936],{"class":62},[52,9017,373],{"class":58},[52,9019,7941],{"class":75},[13,9021,9022,9023,9026,9027,9030],{},"この箇所は先ほどビルドしたwebサーバーイメージを用いたコンテナに関する記述です。",[49,9024,9025],{},"depends_on: -db","とすることでDBと連携ができる様になります。そして",[49,9028,9029],{},".\u002Flaravel\u002F:\u002Fvar\u002Fwww\u002Fhtml\u002F","でプロジェクトソースをコンテナに入れ込んでいます。",[1566,9032,9033],{"id":9033},"centosのための記述",[13,9035,9036],{},"そして先述の通りcenots8のイメージを使用している場合はコンテナがきちんと起動するために以下のコードが必要です。",[42,9038,9040],{"className":6173,"code":9039,"filename":7810,"language":6175,"meta":47,"style":47},"volumes: \n    - \u002Fsys\u002Ffs\u002Fcgroup:\u002Fsys\u002Ffs\u002Fcgroup:ro\nprivileged: true\ncommand: \u002Fsbin\u002Finit\n",[49,9041,9042,9050,9056,9065],{"__ignoreMap":47},[52,9043,9044,9046,9048],{"class":54,"line":55},[52,9045,8043],{"class":62},[52,9047,373],{"class":58},[52,9049,283],{"class":105},[52,9051,9052,9054],{"class":54,"line":83},[52,9053,6257],{"class":58},[52,9055,7890],{"class":75},[52,9057,9058,9061,9063],{"class":54,"line":115},[52,9059,9060],{"class":62},"privileged",[52,9062,373],{"class":58},[52,9064,7931],{"class":6196},[52,9066,9067,9070,9072],{"class":54,"line":142},[52,9068,9069],{"class":62},"command",[52,9071,373],{"class":58},[52,9073,7941],{"class":75},[13,9075,9076,9077,9081,9082,9085,9086,9089],{},"詳細は",[2039,9078,2044],{"href":9079,"rel":9080},"https:\u002F\u002Fjun-app.com\u002Fdocker-centos8\u002F",[2043],"の記事にて解説します。centos8はデフォルトでコンテナ内でアクティブにならずwebサーバーも起動しません。ホストマシンの",[49,9083,9084],{},"systemd","をマウントし、そして",[49,9087,9088],{},"privileged: true","にてコンテナにroot権限を与えることで、コンテナのcentos8が動きます。",[1566,9091,9092],{"id":9092},"ポートを通す",[42,9094,9096],{"className":6173,"code":9095,"filename":7810,"language":6175,"meta":47,"style":47},"ports: \n    - \"9000:80\"\n    - \"3000:3000\"\n",[49,9097,9098,9107,9117],{"__ignoreMap":47},[52,9099,9100,9103,9105],{"class":54,"line":55},[52,9101,9102],{"class":62},"ports",[52,9104,373],{"class":58},[52,9106,283],{"class":105},[52,9108,9109,9111,9113,9115],{"class":54,"line":83},[52,9110,6257],{"class":58},[52,9112,3597],{"class":58},[52,9114,7908],{"class":75},[52,9116,266],{"class":58},[52,9118,9119,9121,9123,9125],{"class":54,"line":115},[52,9120,6257],{"class":58},[52,9122,3597],{"class":58},[52,9124,7919],{"class":75},[52,9126,266],{"class":58},[13,9128,9129,9130,4793,9133,9136,9137,2969,9140,9142],{},"ここでホストマシンの",[49,9131,9132],{},"localhost:9000",[49,9134,9135],{},"localhost:3000","をコンテナの",[49,9138,9139],{},"localhost",[49,9141,9135],{},"につなげる様にします。ホストマシンを9000にしたのは気分です。干渉しなければ他のポートでも大丈夫です。",[1499,9144,9146],{"id":9145},"dbの記述","DBの記述",[42,9148,9150],{"className":6173,"code":9149,"filename":7810,"language":6175,"meta":47,"style":47},"db:\n    image: mysql:5.7\n    environment:\n      MYSQL_DATABASE: laravel_test\n      MYSQL_USER: test\n      MYSQL_PASSWORD: testtest\n      MYSQL_ROOT_PASSWORD: rootroot\n    ports: \n      - \"3306:3306\"\n    volumes: \n      - laravel_data:\u002Fvar\u002Flib\u002Fmysql\nvolumes: \n  laravel_data: {}\n",[49,9151,9152,9159,9167,9173,9182,9190,9198,9206,9214,9224,9232,9238,9246],{"__ignoreMap":47},[52,9153,9154,9157],{"class":54,"line":55},[52,9155,9156],{"class":62},"db",[52,9158,6220],{"class":58},[52,9160,9161,9163,9165],{"class":54,"line":83},[52,9162,2534],{"class":62},[52,9164,373],{"class":58},[52,9166,7957],{"class":75},[52,9168,9169,9171],{"class":54,"line":115},[52,9170,7962],{"class":62},[52,9172,6220],{"class":58},[52,9174,9175,9177,9179],{"class":54,"line":142},[52,9176,7969],{"class":62},[52,9178,373],{"class":58},[52,9180,9181],{"class":75}," laravel_test\n",[52,9183,9184,9186,9188],{"class":54,"line":169},[52,9185,7979],{"class":62},[52,9187,373],{"class":58},[52,9189,7984],{"class":75},[52,9191,9192,9194,9196],{"class":54,"line":302},[52,9193,7989],{"class":62},[52,9195,373],{"class":58},[52,9197,7994],{"class":75},[52,9199,9200,9202,9204],{"class":54,"line":308},[52,9201,7999],{"class":62},[52,9203,373],{"class":58},[52,9205,8004],{"class":75},[52,9207,9208,9210,9212],{"class":54,"line":318},[52,9209,7895],{"class":62},[52,9211,373],{"class":58},[52,9213,283],{"class":105},[52,9215,9216,9218,9220,9222],{"class":54,"line":328},[52,9217,7864],{"class":58},[52,9219,3597],{"class":58},[52,9221,8021],{"class":75},[52,9223,266],{"class":58},[52,9225,9226,9228,9230],{"class":54,"line":337},[52,9227,7872],{"class":62},[52,9229,373],{"class":58},[52,9231,283],{"class":105},[52,9233,9234,9236],{"class":54,"line":344},[52,9235,7864],{"class":58},[52,9237,8038],{"class":75},[52,9239,9240,9242,9244],{"class":54,"line":354},[52,9241,8043],{"class":62},[52,9243,373],{"class":58},[52,9245,283],{"class":105},[52,9247,9248,9250,9252],{"class":54,"line":367},[52,9249,8052],{"class":62},[52,9251,373],{"class":58},[52,9253,8057],{"class":58},[13,9255,9256,9257,9262],{},"DBはmysql:5.7の",[2039,9258,9261],{"href":9259,"rel":9260},"https:\u002F\u002Fhub.docker.com\u002F_\u002Fmysql",[2043],"公式イメージ","を使用します。パスワードの設定なども簡単に行ってくれます。そしてデータ内容は永続化したいのでlaravel_data:{}をボリュームしておきます。",[13,9264,9265],{},"DBはこれでOKです。もしDBにあるデータをまっさらにしたい場合はコンテナを止めて、ボリュームを削除すれば大丈夫です。",[17,9267,9268],{"id":9268},"laravelのプロジェクトソースの調整",[13,9270,9271,9272,9275],{},"以上でwebサーバーとDBサーバーの準備は整いました。次は",[49,9273,9274],{},"laravel","ソースと細かい調整を行います。まずはlaravelディレクトリにプロジェクトを入れます。今回はバージョン６を入れます。ローカルにはcomposerが入っているものとします。",[42,9277,9280],{"className":9278,"code":9279,"language":452},[1615],"$ cd laravel\n$ composer create-project \"laravel\u002Flaravel=6.*\" .\n",[49,9281,9279],{"__ignoreMap":47},[13,9283,9284,9285,9287,9288,9290],{},"laravelのソースが並んだら",[49,9286,6497],{},"を以下の様にDBの設定を書き込みます。",[49,9289,7810],{},"で設定した通りのパスワード、ユーザーです。",[42,9292,9295],{"className":9293,"code":9294,"filename":6497,"language":452,"meta":47},[1615],"...\nDB_CONNECTION=mysql\nDB_HOST=db\nDB_PORT=3306\nDB_DATABASE=laravel_test\nDB_USERNAME=root\nDB_PASSWORD=rootroot\n...\n",[49,9296,9294],{"__ignoreMap":47},[13,9298,9299,9302,9303,9305,9306,9308],{},[49,9300,9301],{},"DB_HOST","は",[49,9304,7810],{},"のserviceで定義した名前",[49,9307,9156],{},"で大丈夫です。こうすればマイグレーションができる様になります。",[1499,9310,9312],{"id":9311},"httpdconfとphpiniの微調整","httpd.confとphp.iniの微調整",[13,9314,9315,9316,9319,9320,9322,9323,9326],{},"Laravelではドキュメントルート を ",[49,9317,9318],{},"\u002Fpublic"," 配下にする様に言われています。",[49,9321,8093],{},"のデフォルトのドキュメントルートは ",[49,9324,9325],{},"\u002Fvar\u002Fwww\u002Fhtml","なので以下の様に変更しておきます",[42,9328,9331],{"className":9329,"code":9330,"filename":8093,"language":452,"meta":47},[1615],"DocumentRoot \"\u002Fvar\u002Fwww\u002Fhtml\u002Fpublic\"\n\n\u003CDirectory \"\u002Fvar\u002Fwww\u002Fhtml\u002Fpublic\">\n    Options Indexes FollowSymLinks\n    AllowOverride All\n    Require all granted\n\u003C\u002FDirectory>\n",[49,9332,9330],{"__ignoreMap":47},[13,9334,9335,9336,9339],{},"こうするとapacheは",[49,9337,9338],{},"\u002Fvar\u002Fwww\u002Fhtml\u002Fpublic","をドキュメントルートとして見てくれます。",[13,9341,9342],{},"php.iniはタイムゾーン をAsia\u002FTokyoにするだけです。",[17,9344,9346],{"id":9345},"composeを起動","composeを起動！",[13,9348,9349,9350,9353,9354,9356],{},"それでは",[49,9351,9352],{},"docker-compose","で起動します。",[49,9355,7810],{},"がある場所にて以下の様に唱えます。",[42,9358,9361],{"className":9359,"code":9360,"language":452},[1615],"$ docker-compose up -d\n",[49,9362,9360],{"__ignoreMap":47},[13,9364,9365,9366,9369],{},"ビルドがうまくいけば",[49,9367,9368],{},"docker ps","で2つのコンテナが起動しているのが確認できます。",[42,9371,9374],{"className":9372,"code":9373,"language":452},[1615],"CONTAINER ID        IMAGE               COMMAND                  CREATED             STATUS              PORTS                                          NAMES\n3e121d905e5e        centos_apache:1.0   \"\u002Fsbin\u002Finit\"             5 seconds ago       Up 3 seconds        0.0.0.0:3000->3000\u002Ftcp, 0.0.0.0:9000->80\u002Ftcp   centos_apache_web_1_1\nea5076e310e9        mysql:5.7           \"docker-entrypoint.s…\"   5 seconds ago       Up 4 seconds        0.0.0.0:3306->3306\u002Ftcp, 33060\u002Ftcp              centos_apache_db_1\n",[49,9375,9373],{"__ignoreMap":47},[13,9377,9378,9380],{},[49,9379,9132],{},"をブラウザでアクセスすると",[729,9382],{":src":9383,":width":732},"'_mix\u002Fsch-2020-11-28-17.33.36-768x407.png'",[13,9385,9386],{},"はい。おなじみのLaravelの画面が表示されました。マイグレーションをするためにコンテナに入ります。",[42,9388,9391],{"className":9389,"code":9390,"language":452},[1615],"$ docker exec -it centos_apache_web_1_1 \u002Fbin\u002Fbash\n",[49,9392,9390],{"__ignoreMap":47},[13,9394,9395,9396,9399],{},"プロジェクトルートに移動して",[49,9397,9398],{},"php artisan migrate","を唱えるとDBコンテナへマイグレーションされます。",[42,9401,9404],{"className":9402,"code":9403,"language":452},[1615],"$ cd \u002Fvar\u002Fwww\u002Fhtml\n$ ls\nREADME.md  artisan    client         composer.lock  database      nuxt.config.js     package.json  public     routes      storage  vendor\napp        bootstrap  composer.json  config         node_modules  package-lock.json  phpunit.xml   resources  server.php  tests    webpack.mix.js\n\n$ php artisan migrate\nMigration table created successfully.\nMigrating: 2014_10_12_000000_create_users_table\nMigrated:  2014_10_12_000000_create_users_table (0.03 seconds)\nMigrating: 2014_10_12_100000_create_password_resets_table\nMigrated:  2014_10_12_100000_create_password_resets_table (0.02 seconds)\nMigrating: 2019_08_19_000000_create_failed_jobs_table\nMigrated:  2019_08_19_000000_create_failed_jobs_table (0.01 seconds)\n",[49,9405,9403],{"__ignoreMap":47},[13,9407,9408],{},"本当に入ったかはDBコンテナに入ってmysqlコマンドを通じて中身を見ると確認できます。ここまでくればLaravelの環境構築は完了です。phpのバージョンを変えてみたりで、OS環境による検証もある程度楽になるでしょう。",[17,9410,9412],{"id":9411},"おまけnuxtjsを起動させる","（おまけ）Nuxt.jsを起動させる",[13,9414,9415],{},"一応私のプロジェクトではnuxt.jsを入れていたので、せっかくなので解説します。larvelディレクトリにてnuxt.jsのプロジェクトを作成します。（ローカルでも、コンテナ内でもどちらでも。私はローカルで作成）",[42,9417,9420],{"className":9418,"code":9419,"language":452},[1615],"$ cd laravel\n$ npx create-nuxt-app client\n",[49,9421,9419],{"__ignoreMap":47},[13,9423,9424],{},"以下の様なディレクトリになりました。",[42,9426,9429],{"className":9427,"code":9428,"language":452},[1615],".\n├── README.md\n├── app\n├── artisan\n├── bootstrap\n├── client # nuxt ソース\n├── composer.json\n├── composer.lock\n├── config\n├── database\n├── phpunit.xml\n├── public\n├── resources\n├── routes\n├── server.php\n├── storage\n├── tests\n├── vendor\n└── webpack.mix.js\n",[49,9430,9428],{"__ignoreMap":47},[13,9432,9433,9434,9437,9438,9441],{},"ちなみにLaravelに初期にあった",[49,9435,9436],{},"package.json","は削除しています。",[49,9439,9440],{},"client","ディレクトリ配下にnuxtのソースがありますが、これだと管理がしにくいので以下の様に変更します。",[42,9443,9446],{"className":9444,"code":9445,"language":452},[1615],".\n├── README.md\n├── app\n├── artisan\n├── bootstrap\n├── client\n├── composer.json\n├── composer.lock\n├── config\n├── database\n├── node_modules #clientから\n├── nuxt.config.js #clientから\n├── package-lock.json #clientから\n├── package.json #clientから\n├── phpunit.xml\n├── public\n├── resources\n├── routes\n├── server.php\n├── storage\n├── tests\n├── vendor\n└── webpack.mix.js\n",[49,9447,9445],{"__ignoreMap":47},[13,9449,9450,9452,9453,9456],{},[49,9451,9440],{},"から4つのファイルを引っ張り出し、そして",[49,9454,9455],{},"nuxt.config.js","に以下を追記します。",[42,9458,9461],{"className":9459,"code":9460,"filename":9455,"language":452,"meta":47},[1615],"srcDir: 'client\u002F',\n",[49,9462,9460],{"__ignoreMap":47},[13,9464,9465,9466,9468,9469,9471],{},"こうすることで",[49,9467,9440],{},"配下はnuxtのソースだけを置いて設定ファイルや",[49,9470,7713],{},"はLaravelのプロジェクトルートに出される様にします。この辺はチームの好みによるので自由に変更してください。",[13,9473,9474,9475,9478,9479,9481],{},"そしてコンテナ内に入って ",[49,9476,9477],{},"npm run dev","をすればnuxtの開発サーバーが立ち上がるのですが、その前にまた",[49,9480,9455],{},"に以下の内容を記述します。",[42,9483,9486],{"className":9484,"code":9485,"filename":9455,"language":452,"meta":47},[1615],"server: {\n    host: '0.0.0.0'\n},\n",[49,9487,9485],{"__ignoreMap":47},[13,9489,9490,9491,9493,9494,8671],{},"dockerコンテナを使用しない場合はこの設定は要らないのですが、コンテナ内で",[49,9492,9477],{},"をしてそれに接続するには上記の設定が必要です。コンテナ内のlocalhost:3000に開発サーバーが立ち上がります。",[49,9495,7810],{},[42,9497,9499],{"className":6173,"code":9498,"filename":7810,"language":6175,"meta":47,"style":47},"ports: \n    - \"9000:80\"\n    - \"3000:3000\n",[49,9500,9501,9509,9519],{"__ignoreMap":47},[52,9502,9503,9505,9507],{"class":54,"line":55},[52,9504,9102],{"class":62},[52,9506,373],{"class":58},[52,9508,283],{"class":105},[52,9510,9511,9513,9515,9517],{"class":54,"line":83},[52,9512,6257],{"class":58},[52,9514,3597],{"class":58},[52,9516,7908],{"class":75},[52,9518,266],{"class":58},[52,9520,9521,9523,9525],{"class":54,"line":115},[52,9522,6257],{"class":58},[52,9524,3597],{"class":58},[52,9526,9527],{"class":75},"3000:3000\n",[13,9529,9530],{},"この様にホストの3000とコンテナの3000ポートをつないでいたので、localhost:3000にアクセスすると",[729,9532],{":src":9533,":width":732},"'_mix\u002Fsch-2020-11-28-18.02.48-768x599.png'",[13,9535,9536],{},"nuxtの画面が現れました。（UIフレームワークにvuetifyを選択するとこの画面になります。）これでnuxtの開発もコンテナ内で実行できます。",[1414,9538,9539],{},"html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html pre.shiki code .s-wAU, html code.shiki .s-wAU{--shiki-default:#F07178}html pre.shiki code .sAklC, html code.shiki .sAklC{--shiki-default:#89DDFF}html pre.shiki code .sfyAc, html code.shiki .sfyAc{--shiki-default:#C3E88D}html pre.shiki code .s0W1g, html code.shiki .s0W1g{--shiki-default:#BABED8}html pre.shiki code .sbqyR, html code.shiki .sbqyR{--shiki-default:#FF9CAC}",{"title":47,"searchDepth":115,"depth":115,"links":9541},[9542,9546,9553,9554,9561,9564,9565],{"id":7797,"depth":83,"text":7797,"children":9543},[9544,9545],{"id":8077,"depth":115,"text":8077},{"id":8104,"depth":115,"text":8104},{"id":8110,"depth":83,"text":8110,"children":9547},[9548,9549,9550,9551,9552],{"id":8347,"depth":115,"text":8348},{"id":8460,"depth":115,"text":8461},{"id":8526,"depth":115,"text":8527},{"id":8589,"depth":115,"text":8589},{"id":8628,"depth":115,"text":8629},{"id":8652,"depth":83,"text":8652},{"id":8684,"depth":83,"text":8684,"children":9555},[9556,9560],{"id":8920,"depth":115,"text":8920,"children":9557},[9558,9559],{"id":9033,"depth":142,"text":9033},{"id":9092,"depth":142,"text":9092},{"id":9145,"depth":115,"text":9146},{"id":9268,"depth":83,"text":9268,"children":9562},[9563],{"id":9311,"depth":115,"text":9312},{"id":9345,"depth":83,"text":9346},{"id":9411,"depth":83,"text":9412},[1424],"2026-01-16",{},"\u002Farticles\u002Fbuild-lamp-with-docker",{"title":7786,"description":7786},"articles\u002Fbuild-lamp-with-docker",[9573,9274],"docker","_mix\u002Fdokcer_laravel.jpeg","V2jA9dknjBNGZQzsV_h65rI9ap5VBJ9l1skOpFFoD8c",{"id":9577,"title":9578,"body":9579,"category":13226,"createdAt":13227,"description":13228,"extension":1427,"index":1428,"meta":13229,"navigation":340,"path":13230,"publish":340,"seo":13231,"series":1428,"seriesTitle":1428,"stem":13232,"tag":13233,"thumbnail":13234,"updatedAt":13227,"__hash__":13235},"articles\u002Farticles\u002Ffilepond-crop-vue.md","Vue filepondとCropper.jsの連携とファイルアップロード",{"type":10,"value":9580,"toc":13206},[9581,9590,9593,9597,9606,9829,9832,9846,9849,9852,9855,9858,9864,9875,9880,9887,9891,9898,9971,9985,10005,10009,10016,10459,10462,10465,10468,10483,10505,10753,10763,10767,10770,10776,10782,10951,10955,10958,11429,11452,11457,11460,11474,11477,11480,11854,11866,11869,11872,11886,11889,11899,12031,12050,12053,12056,12059,12062,12071,12472,12478,12484,12500,12511,12514,12527,13175,13178,13181,13184,13191,13194,13203],[13,9582,9583,9584,9589],{},"こんにちはjunです。皆さんはファイルアップロードをJSで実装したことありますか？最近のwebサービスではアバターだったり、記事に画像のせるなど画像をアップロードするのが当たり前になっています。そのため仕事でもファイルのアップロード機能を実装しますが、Ajaxを用いてのFileインターフェースの扱いなどUIの構築が面倒なので、ライブラリを使っています。私は",[2039,9585,9588],{"href":9586,"rel":9587},"https:\u002F\u002Fpqina.nl\u002Ffilepond\u002F",[2043],"filepond","と言うものを使用して、複数アップロードやファイルのフロントバリデーションを実装します。VanillaだけでなくVue.jsやReactとの連携がしやすいので重宝しています。",[13,9591,9592],{},"今回の記事は「Filepondのアップロードを行う前にフロントでトリミングしたものをアップロードする」という単純にアップロードするだけでなく、画像を加工してからUPする機能を実装します。Line,Facebook、Twitterなどではプロフィール画像をアップロードする際に、どんな写真でも任意の範囲で１：１でトリミングできる機能があります。意外とユーザーは自身のスマホでトリミングする機能があるのを知らなかったり、機器によっては提供されていないor面倒だったりします。UX的な観点からもフロントでのトリミング機能があると便利です。",[729,9594],{":src":9595,":width":9596,":center":1323},"'filepond-crop-vue\u002Ftwitter.png'","'50%'",[13,9598,9599,9600,9605],{},"この機能を実装するにあたり、",[2039,9601,9604],{"href":9602,"rel":9603},"https:\u002F\u002Ffengyuanchen.github.io\u002Fcropperjs\u002F",[2043],"Cropper.js","のVue版を使用します。アップロードはfilepondを使用するので両者ライブラリの連携を行います。意外と苦労したので記事にしたいと思います。フロントエンド はVue CLIを用います。バックエンド側の解説はしません。バージョン情報は以下の通りです。",[42,9607,9611],{"className":9608,"code":9609,"language":9610,"meta":47,"style":47},"language-json shiki shiki-themes material-theme-ocean","\"devDependencies\": {\n    \"vue\": \"^2.6.12\",\n    \"vue-filepond\": \"^6.0.3\",\n},\n\"dependencies\": {\n    \"filepond\": \"^4.30.3\",\n    \"filepond-plugin-file-validate-type\": \"^1.2.6\",\n    \"filepond-plugin-image-crop\": \"^2.0.6\",\n    \"filepond-plugin-image-edit\": \"^1.6.3\",\n    \"filepond-plugin-image-preview\": \"^4.6.10\",\n    \"filepond-plugin-image-transform\": \"^3.8.7\",\n    \"vue-cropperjs\": \"^4.2.0\",\n}\n","json",[49,9612,9613,9627,9647,9667,9673,9686,9705,9725,9745,9765,9785,9805,9825],{"__ignoreMap":47},[52,9614,9615,9617,9620,9622,9625],{"class":54,"line":55},[52,9616,72],{"class":58},[52,9618,9619],{"class":75},"devDependencies",[52,9621,72],{"class":58},[52,9623,9624],{"class":105},": ",[52,9626,364],{"class":58},[52,9628,9629,9632,9634,9636,9638,9640,9643,9645],{"class":54,"line":83},[52,9630,9631],{"class":58},"    \"",[52,9633,204],{"class":65},[52,9635,72],{"class":58},[52,9637,373],{"class":58},[52,9639,3597],{"class":58},[52,9641,9642],{"class":75},"^2.6.12",[52,9644,72],{"class":58},[52,9646,384],{"class":58},[52,9648,9649,9651,9654,9656,9658,9660,9663,9665],{"class":54,"line":115},[52,9650,9631],{"class":58},[52,9652,9653],{"class":65},"vue-filepond",[52,9655,72],{"class":58},[52,9657,373],{"class":58},[52,9659,3597],{"class":58},[52,9661,9662],{"class":75},"^6.0.3",[52,9664,72],{"class":58},[52,9666,384],{"class":58},[52,9668,9669,9671],{"class":54,"line":142},[52,9670,1312],{"class":58},[52,9672,384],{"class":105},[52,9674,9675,9677,9680,9682,9684],{"class":54,"line":169},[52,9676,72],{"class":58},[52,9678,9679],{"class":75},"dependencies",[52,9681,72],{"class":58},[52,9683,9624],{"class":105},[52,9685,364],{"class":58},[52,9687,9688,9690,9692,9694,9696,9698,9701,9703],{"class":54,"line":302},[52,9689,9631],{"class":58},[52,9691,9588],{"class":65},[52,9693,72],{"class":58},[52,9695,373],{"class":58},[52,9697,3597],{"class":58},[52,9699,9700],{"class":75},"^4.30.3",[52,9702,72],{"class":58},[52,9704,384],{"class":58},[52,9706,9707,9709,9712,9714,9716,9718,9721,9723],{"class":54,"line":308},[52,9708,9631],{"class":58},[52,9710,9711],{"class":65},"filepond-plugin-file-validate-type",[52,9713,72],{"class":58},[52,9715,373],{"class":58},[52,9717,3597],{"class":58},[52,9719,9720],{"class":75},"^1.2.6",[52,9722,72],{"class":58},[52,9724,384],{"class":58},[52,9726,9727,9729,9732,9734,9736,9738,9741,9743],{"class":54,"line":318},[52,9728,9631],{"class":58},[52,9730,9731],{"class":65},"filepond-plugin-image-crop",[52,9733,72],{"class":58},[52,9735,373],{"class":58},[52,9737,3597],{"class":58},[52,9739,9740],{"class":75},"^2.0.6",[52,9742,72],{"class":58},[52,9744,384],{"class":58},[52,9746,9747,9749,9752,9754,9756,9758,9761,9763],{"class":54,"line":328},[52,9748,9631],{"class":58},[52,9750,9751],{"class":65},"filepond-plugin-image-edit",[52,9753,72],{"class":58},[52,9755,373],{"class":58},[52,9757,3597],{"class":58},[52,9759,9760],{"class":75},"^1.6.3",[52,9762,72],{"class":58},[52,9764,384],{"class":58},[52,9766,9767,9769,9772,9774,9776,9778,9781,9783],{"class":54,"line":337},[52,9768,9631],{"class":58},[52,9770,9771],{"class":65},"filepond-plugin-image-preview",[52,9773,72],{"class":58},[52,9775,373],{"class":58},[52,9777,3597],{"class":58},[52,9779,9780],{"class":75},"^4.6.10",[52,9782,72],{"class":58},[52,9784,384],{"class":58},[52,9786,9787,9789,9792,9794,9796,9798,9801,9803],{"class":54,"line":344},[52,9788,9631],{"class":58},[52,9790,9791],{"class":65},"filepond-plugin-image-transform",[52,9793,72],{"class":58},[52,9795,373],{"class":58},[52,9797,3597],{"class":58},[52,9799,9800],{"class":75},"^3.8.7",[52,9802,72],{"class":58},[52,9804,384],{"class":58},[52,9806,9807,9809,9812,9814,9816,9818,9821,9823],{"class":54,"line":354},[52,9808,9631],{"class":58},[52,9810,9811],{"class":65},"vue-cropperjs",[52,9813,72],{"class":58},[52,9815,373],{"class":58},[52,9817,3597],{"class":58},[52,9819,9820],{"class":75},"^4.2.0",[52,9822,72],{"class":58},[52,9824,384],{"class":58},[52,9826,9827],{"class":54,"line":367},[52,9828,536],{"class":58},[17,9830,9831],{"id":9831},"実装目標",[1687,9833,9834,9837,9840,9843],{},[1470,9835,9836],{},"任意の画像をブラウザにアップロード",[1470,9838,9839],{},"画像を編集できるUIを用意し、任意でトリミングできる様にする",[1470,9841,9842],{},"任意の位置、倍率でオリジナルの画像を１：１でトリミング",[1470,9844,9845],{},"トリミングした画像をサーバーにアップロードする。",[13,9847,9848],{},"以上の機能を持ったアップローダーが実装目標です。",[17,9850,9851],{"id":9851},"初期設定",[13,9853,9854],{},"ではまず最初に必要なライブラリをインストールしていきましょう。とりあえず今はVue CLIの設定とfilepondだけインストールしましょう。Vue CLIのセットアップ解説は省きます。",[1499,9856,9857],{"id":9857},"ライブラリインストール",[42,9859,9862],{"className":9860,"code":9861,"language":452},[1615],"vue create filepond_cropper\ncd filepond_cropper\n\nnpm install filepond filepond-plugin-file-validate-type filepond-plugin-image-crop filepond-plugin-image-preview --save\nnpm install vue-filepond@6.0.3 --save-dev\n",[49,9863,9861],{"__ignoreMap":47},[13,9865,9866,9867,9869,9870],{},"このインストールで気をつけたいのが",[49,9868,9653],{},"のバージョンです。そのままインストールするとVue3に対応した最新版がインストールされ、Vue2系では動作しません。",[2039,9871,9874],{"href":9872,"rel":9873},"https:\u002F\u002Fgithub.com\u002Fpqina\u002Fvue-filepond",[2043],"本家Githubでも注意書きがあります。",[6147,9876,9877],{},[13,9878,9879],{},"If you want to use Vue FilePond with Vue 2, please use v6 of this plugin.",[13,9881,9882,9883,9886],{},"そのため ",[49,9884,9885],{},"npm install vue-filepond@6.0.3 --save-dev","としてバージョン６を入れる様にしてください。",[1499,9888,9890],{"id":9889},"vueレンダリング準備","Vueレンダリング準備",[13,9892,9893,9894,9897],{},"ライブラリのインストールが終わりましたら、最初にある",[49,9895,9896],{},"src\u002Fmain.js","でvueでfilepondを使用できる様にします。",[42,9899,9902],{"className":1250,"code":9900,"filename":9901,"language":1252,"meta":47,"style":47},"import Vue from 'vue'\n\nimport vueFilePond from 'vue-filepond';\nimport FilePondPluginFileValidateType from 'filepond-plugin-file-validate-type\u002Fdist\u002Ffilepond-plugin-file-validate-type.esm.js';\nimport FilePondPluginImagePreview from 'filepond-plugin-image-preview\u002Fdist\u002Ffilepond-plugin-image-preview.esm.js';\nimport FilePondPluginImageCrop from 'filepond-plugin-image-crop\u002Fdist\u002Ffilepond-plugin-image-crop.esm';\nconst FilePond = vueFilePond(FilePondPluginFileValidateType, FilePondPluginImagePreview,FilePondPluginImageCrop);\nVue.component(FilePond);\n\nVue.config.productionTip = false\n\nnew Vue({\n  render: h => h(App),\n}).$mount('#app')\n","main.js",[49,9903,9904,9909,9913,9918,9923,9928,9933,9938,9943,9947,9952,9956,9961,9966],{"__ignoreMap":47},[52,9905,9906],{"class":54,"line":55},[52,9907,9908],{},"import Vue from 'vue'\n",[52,9910,9911],{"class":54,"line":83},[52,9912,341],{"emptyLinePlaceholder":340},[52,9914,9915],{"class":54,"line":115},[52,9916,9917],{},"import vueFilePond from 'vue-filepond';\n",[52,9919,9920],{"class":54,"line":142},[52,9921,9922],{},"import FilePondPluginFileValidateType from 'filepond-plugin-file-validate-type\u002Fdist\u002Ffilepond-plugin-file-validate-type.esm.js';\n",[52,9924,9925],{"class":54,"line":169},[52,9926,9927],{},"import FilePondPluginImagePreview from 'filepond-plugin-image-preview\u002Fdist\u002Ffilepond-plugin-image-preview.esm.js';\n",[52,9929,9930],{"class":54,"line":302},[52,9931,9932],{},"import FilePondPluginImageCrop from 'filepond-plugin-image-crop\u002Fdist\u002Ffilepond-plugin-image-crop.esm';\n",[52,9934,9935],{"class":54,"line":308},[52,9936,9937],{},"const FilePond = vueFilePond(FilePondPluginFileValidateType, FilePondPluginImagePreview,FilePondPluginImageCrop);\n",[52,9939,9940],{"class":54,"line":318},[52,9941,9942],{},"Vue.component(FilePond);\n",[52,9944,9945],{"class":54,"line":328},[52,9946,341],{"emptyLinePlaceholder":340},[52,9948,9949],{"class":54,"line":337},[52,9950,9951],{},"Vue.config.productionTip = false\n",[52,9953,9954],{"class":54,"line":344},[52,9955,341],{"emptyLinePlaceholder":340},[52,9957,9958],{"class":54,"line":354},[52,9959,9960],{},"new Vue({\n",[52,9962,9963],{"class":54,"line":367},[52,9964,9965],{},"  render: h => h(App),\n",[52,9967,9968],{"class":54,"line":387},[52,9969,9970],{},"}).$mount('#app')\n",[13,9972,9973,9976,9977,9980,9981,9984],{},[49,9974,9975],{},"vueFilePond","があれば一応すぐに使用できますがここではfilepondのプラグインを使用します。プラグインを",[49,9978,9979],{},"import","して ",[49,9982,9983],{},"vueFilePond()","の引数に当てていきます。",[1467,9986,9987,9993,9999],{},[1470,9988,9989,9992],{},[49,9990,9991],{},"FilePondPluginFileValidateType","はファイルの拡張子を検証します。ここでは画像（jpg,png,gif）以外を許可しない様にします。",[1470,9994,9995,9998],{},[49,9996,9997],{},"FilePondPluginImagePreview","はブラウザにアップロードしたファイルをプレビューできる様にします。",[1470,10000,10001,10004],{},[49,10002,10003],{},"FilePondPluginImageCrop","はブラウザにアップロードしたファイルを任意のアスペクト比でトリミングしたプレビューを表示します。（実際にトリミングはしてくれない。現状はあくまでトリミング情報を提供してプレビュー表示を変えるだけ）",[17,10006,10008],{"id":10007},"まずはfilepondにアップロードできるようにする","まずはFilepondにアップロードできるようにする",[13,10010,10011,10012,10015],{},"プラグインを読み込み、",[49,10013,10014],{},"src\u002FApp.vue","を編集します。",[42,10017,10019],{"className":202,"code":10018,"filename":10014,"language":204,"meta":47,"style":47},"\u003Ctemplate>\n    \u003Cdiv>\n        \u003Cfile-pond ref=\"filepond\" v-bind=\"attrs\"\u002F>\n    \u003C\u002Fdiv>\n\u003C\u002Ftemplate>\n\u003Cscript>\nimport ja from 'filepond\u002Flocale\u002Fja-ja';\nimport VueCropper from 'vue-cropperjs';\nexport default {\n    name:'fileuploader',\n    data(){\n        return{\n            files:[],\n        }\n    },\n    methods:{\n        onError(res){\n            console.error(res)\n        },\n    },\n    computed:{\n        attrs(){\n            let apiserver = '\u002Fapi\u002Fv1\u002Ffile\u002Favater';\n            return {\n                allowMultiple:false,\n                'accepted-file-types':'image\u002Fjpeg,image\u002Fpng,image\u002Fgif',\n                instantUpload:false,\n                imageCropAspectRatio:'1:1',\n                server:{\n                    process: {\n                        url:apiserver,\n                        method: 'POST',\n                        withCredentials: true,\n                        onerror:(response) => this.onError(response)\n                    },\n                },\n                ...ja\n            }\n        }\n    },\n}\n\u003C\u002Fscript>\n",[49,10020,10021,10029,10037,10070,10078,10086,10094,10113,10130,10139,10155,10162,10169,10181,10185,10190,10197,10209,10225,10230,10234,10241,10248,10267,10274,10285,10306,10317,10333,10340,10349,10361,10377,10389,10417,10422,10427,10435,10439,10443,10447,10451],{"__ignoreMap":47},[52,10022,10023,10025,10027],{"class":54,"line":55},[52,10024,59],{"class":58},[52,10026,213],{"class":62},[52,10028,80],{"class":58},[52,10030,10031,10033,10035],{"class":54,"line":83},[52,10032,2161],{"class":58},[52,10034,8503],{"class":62},[52,10036,80],{"class":58},[52,10038,10039,10041,10044,10047,10049,10051,10053,10055,10058,10060,10062,10065,10067],{"class":54,"line":115},[52,10040,2171],{"class":58},[52,10042,10043],{"class":62},"file-pond",[52,10045,10046],{"class":65}," ref",[52,10048,69],{"class":58},[52,10050,72],{"class":58},[52,10052,9588],{"class":75},[52,10054,72],{"class":58},[52,10056,10057],{"class":65}," v-bind",[52,10059,69],{"class":58},[52,10061,72],{"class":58},[52,10063,10064],{"class":75},"attrs",[52,10066,72],{"class":58},[52,10068,10069],{"class":58},"\u002F>\n",[52,10071,10072,10074,10076],{"class":54,"line":142},[52,10073,2303],{"class":58},[52,10075,8503],{"class":62},[52,10077,80],{"class":58},[52,10079,10080,10082,10084],{"class":54,"line":169},[52,10081,108],{"class":58},[52,10083,213],{"class":62},[52,10085,80],{"class":58},[52,10087,10088,10090,10092],{"class":54,"line":302},[52,10089,59],{"class":58},[52,10091,349],{"class":62},[52,10093,80],{"class":58},[52,10095,10096,10098,10101,10104,10106,10109,10111],{"class":54,"line":308},[52,10097,9979],{"class":360},[52,10099,10100],{"class":105}," ja ",[52,10102,10103],{"class":360},"from",[52,10105,6309],{"class":58},[52,10107,10108],{"class":75},"filepond\u002Flocale\u002Fja-ja",[52,10110,376],{"class":58},[52,10112,1007],{"class":58},[52,10114,10115,10117,10120,10122,10124,10126,10128],{"class":54,"line":318},[52,10116,9979],{"class":360},[52,10118,10119],{"class":105}," VueCropper ",[52,10121,10103],{"class":360},[52,10123,6309],{"class":58},[52,10125,9811],{"class":75},[52,10127,376],{"class":58},[52,10129,1007],{"class":58},[52,10131,10132,10134,10136],{"class":54,"line":328},[52,10133,357],{"class":360},[52,10135,361],{"class":360},[52,10137,10138],{"class":58}," {\n",[52,10140,10141,10144,10146,10148,10151,10153],{"class":54,"line":337},[52,10142,10143],{"class":62},"    name",[52,10145,373],{"class":58},[52,10147,376],{"class":58},[52,10149,10150],{"class":75},"fileuploader",[52,10152,376],{"class":58},[52,10154,384],{"class":58},[52,10156,10157,10160],{"class":54,"line":344},[52,10158,10159],{"class":62},"    data",[52,10161,2424],{"class":58},[52,10163,10164,10167],{"class":54,"line":354},[52,10165,10166],{"class":360},"        return",[52,10168,364],{"class":58},[52,10170,10171,10174,10176,10179],{"class":54,"line":367},[52,10172,10173],{"class":62},"            files",[52,10175,373],{"class":58},[52,10177,10178],{"class":62},"[]",[52,10180,384],{"class":58},[52,10182,10183],{"class":54,"line":387},[52,10184,5398],{"class":58},[52,10186,10187],{"class":54,"line":415},[52,10188,10189],{"class":58},"    },\n",[52,10191,10192,10195],{"class":54,"line":427},[52,10193,10194],{"class":62},"    methods",[52,10196,924],{"class":58},[52,10198,10199,10202,10204,10207],{"class":54,"line":435},[52,10200,10201],{"class":62},"        onError",[52,10203,932],{"class":58},[52,10205,10206],{"class":986},"res",[52,10208,3439],{"class":58},[52,10210,10211,10214,10216,10219,10221,10223],{"class":54,"line":446},[52,10212,10213],{"class":105},"            console",[52,10215,957],{"class":58},[52,10217,10218],{"class":418},"error",[52,10220,932],{"class":62},[52,10222,10206],{"class":105},[52,10224,1015],{"class":62},[52,10226,10227],{"class":54,"line":480},[52,10228,10229],{"class":58},"        },\n",[52,10231,10232],{"class":54,"line":509},[52,10233,10189],{"class":58},[52,10235,10236,10239],{"class":54,"line":539},[52,10237,10238],{"class":62},"    computed",[52,10240,924],{"class":58},[52,10242,10243,10246],{"class":54,"line":547},[52,10244,10245],{"class":62},"        attrs",[52,10247,2424],{"class":58},[52,10249,10250,10253,10256,10258,10260,10263,10265],{"class":54,"line":553},[52,10251,10252],{"class":65},"            let",[52,10254,10255],{"class":105}," apiserver",[52,10257,951],{"class":58},[52,10259,6309],{"class":58},[52,10261,10262],{"class":75},"\u002Fapi\u002Fv1\u002Ffile\u002Favater",[52,10264,376],{"class":58},[52,10266,1007],{"class":58},[52,10268,10269,10272],{"class":54,"line":559},[52,10270,10271],{"class":360},"            return",[52,10273,10138],{"class":58},[52,10275,10276,10279,10281,10283],{"class":54,"line":564},[52,10277,10278],{"class":62},"                allowMultiple",[52,10280,373],{"class":58},[52,10282,1327],{"class":6196},[52,10284,384],{"class":58},[52,10286,10287,10290,10293,10295,10297,10299,10302,10304],{"class":54,"line":569},[52,10288,10289],{"class":58},"                '",[52,10291,10292],{"class":62},"accepted-file-types",[52,10294,376],{"class":58},[52,10296,373],{"class":58},[52,10298,376],{"class":58},[52,10300,10301],{"class":75},"image\u002Fjpeg,image\u002Fpng,image\u002Fgif",[52,10303,376],{"class":58},[52,10305,384],{"class":58},[52,10307,10308,10311,10313,10315],{"class":54,"line":1106},[52,10309,10310],{"class":62},"                instantUpload",[52,10312,373],{"class":58},[52,10314,1327],{"class":6196},[52,10316,384],{"class":58},[52,10318,10319,10322,10324,10326,10329,10331],{"class":54,"line":1135},[52,10320,10321],{"class":62},"                imageCropAspectRatio",[52,10323,373],{"class":58},[52,10325,376],{"class":58},[52,10327,10328],{"class":75},"1:1",[52,10330,376],{"class":58},[52,10332,384],{"class":58},[52,10334,10335,10338],{"class":54,"line":1164},[52,10336,10337],{"class":62},"                server",[52,10339,924],{"class":58},[52,10341,10342,10345,10347],{"class":54,"line":1193},[52,10343,10344],{"class":62},"                    process",[52,10346,373],{"class":58},[52,10348,10138],{"class":58},[52,10350,10351,10354,10356,10359],{"class":54,"line":1200},[52,10352,10353],{"class":62},"                        url",[52,10355,373],{"class":58},[52,10357,10358],{"class":105},"apiserver",[52,10360,384],{"class":58},[52,10362,10363,10366,10368,10370,10373,10375],{"class":54,"line":1205},[52,10364,10365],{"class":62},"                        method",[52,10367,373],{"class":58},[52,10369,6309],{"class":58},[52,10371,10372],{"class":75},"POST",[52,10374,376],{"class":58},[52,10376,384],{"class":58},[52,10378,10379,10382,10384,10387],{"class":54,"line":1210},[52,10380,10381],{"class":62},"                        withCredentials",[52,10383,373],{"class":58},[52,10385,10386],{"class":6196}," true",[52,10388,384],{"class":58},[52,10390,10391,10394,10397,10400,10402,10405,10408,10411,10413,10415],{"class":54,"line":1215},[52,10392,10393],{"class":418},"                        onerror",[52,10395,10396],{"class":58},":(",[52,10398,10399],{"class":986},"response",[52,10401,938],{"class":58},[52,10403,10404],{"class":65}," =>",[52,10406,10407],{"class":58}," this.",[52,10409,10410],{"class":418},"onError",[52,10412,932],{"class":62},[52,10414,10399],{"class":105},[52,10416,1015],{"class":62},[52,10418,10419],{"class":54,"line":1220},[52,10420,10421],{"class":58},"                    },\n",[52,10423,10424],{"class":54,"line":3800},[52,10425,10426],{"class":58},"                },\n",[52,10428,10429,10432],{"class":54,"line":3821},[52,10430,10431],{"class":58},"                ...",[52,10433,10434],{"class":105},"ja\n",[52,10436,10437],{"class":54,"line":3835},[52,10438,2251],{"class":58},[52,10440,10441],{"class":54,"line":3840},[52,10442,5398],{"class":58},[52,10444,10445],{"class":54,"line":3865},[52,10446,10189],{"class":58},[52,10448,10449],{"class":54,"line":3879},[52,10450,536],{"class":58},[52,10452,10453,10455,10457],{"class":54,"line":5506},[52,10454,108],{"class":58},[52,10456,349],{"class":62},[52,10458,80],{"class":58},[13,10460,10461],{},"上記の通りでfilepondのコンポーネント表示されます。（スタイルなどは適宜調整してください）",[729,10463],{":src":10464,":width":9596,":center":1323},"'filepond-crop-vue\u002Ffilepond.png'",[1499,10466,10467],{"id":10467},"設定内容",[13,10469,10470,10471,10474,10475,10478,10479,10482],{},"filepondの公式ではテンプレートに",[49,10472,10473],{},"props","を設定していますが日本語化や色々設定が多いので、",[49,10476,10477],{},"computed","に記載した方がいいです。また日本語化する場合は",[49,10480,10481],{},"import ja from 'filepond\u002Flocale\u002Fja-ja';"," とインポートして展開します。",[13,10484,10485,10486,9302,10488,10493,10494,10497,10498,4793,10501,10504],{},"そしてこれらの",[49,10487,10473],{},[2039,10489,10492],{"href":10490,"rel":10491},"https:\u002F\u002Fpqina.nl\u002Ffilepond\u002Fdocs\u002Fapi\u002Finstance\u002Fproperties\u002F",[2043],"本家vanilla版ドキュメント","で記載されている",[49,10495,10496],{},"properties","と同じ項目です。またfilepondのメソッドにアクセスする場合は",[49,10499,10500],{},"this.$refs.pond",[49,10502,10503],{},"ref","の値を使用します。今回設定した項目の解説は以下の通りです。",[42,10506,10509],{"className":10507,"code":10508,"language":1434,"meta":47,"style":47},"language-js shiki shiki-themes material-theme-ocean","return {\n    \u002F\u002F 複数ファイルのアップロードを許可するか\n    allowMultiple:false,\n\n    \u002F\u002F 許可するmime\n    'accepted-file-types':'image\u002Fjpeg,image\u002Fpng,image\u002Fgif',\n\n    \u002F\u002F ブラウザにアップしたとき、すぐにサーバーにアップロードするか。デフォルトはTrueなので注意\n    instantUpload:false,\n\n    \u002F\u002F FilePondPluginImageCrop有効時のアスペクト比。\n    imageCropAspectRatio:'1:1',\n\n    \u002F\u002F アップロードする際のajaxの設定\n    server:{\n        \u002F\u002F アップロードするときの設定\n        process: {\n            \u002F\u002F アップロード先URL\n            url:apiserver,\n\n            \u002F\u002F アップロードのメソッド（processはデフォルトでPOST）\n            method: 'POST',\n\n            \u002F\u002F withCredentialsを有効化\n            withCredentials: true,\n\n            \u002F\u002F エラー時のコールバック\n            onerror:(response) => this.onError(response)\n        },\n    },\n\n    \u002F\u002F 日本語化\n    ...ja\n}\n",[49,10510,10511,10518,10523,10534,10538,10543,10562,10566,10571,10582,10586,10591,10606,10610,10615,10622,10627,10636,10641,10652,10656,10661,10676,10680,10685,10696,10700,10705,10725,10729,10733,10737,10742,10749],{"__ignoreMap":47},[52,10512,10513,10516],{"class":54,"line":55},[52,10514,10515],{"class":360},"return",[52,10517,10138],{"class":58},[52,10519,10520],{"class":54,"line":83},[52,10521,10522],{"class":411},"    \u002F\u002F 複数ファイルのアップロードを許可するか\n",[52,10524,10525,10528,10530,10532],{"class":54,"line":115},[52,10526,10527],{"class":62},"    allowMultiple",[52,10529,373],{"class":58},[52,10531,1327],{"class":6196},[52,10533,384],{"class":58},[52,10535,10536],{"class":54,"line":142},[52,10537,341],{"emptyLinePlaceholder":340},[52,10539,10540],{"class":54,"line":169},[52,10541,10542],{"class":411},"    \u002F\u002F 許可するmime\n",[52,10544,10545,10548,10550,10552,10554,10556,10558,10560],{"class":54,"line":302},[52,10546,10547],{"class":58},"    '",[52,10549,10292],{"class":62},[52,10551,376],{"class":58},[52,10553,373],{"class":58},[52,10555,376],{"class":58},[52,10557,10301],{"class":75},[52,10559,376],{"class":58},[52,10561,384],{"class":58},[52,10563,10564],{"class":54,"line":308},[52,10565,341],{"emptyLinePlaceholder":340},[52,10567,10568],{"class":54,"line":318},[52,10569,10570],{"class":411},"    \u002F\u002F ブラウザにアップしたとき、すぐにサーバーにアップロードするか。デフォルトはTrueなので注意\n",[52,10572,10573,10576,10578,10580],{"class":54,"line":328},[52,10574,10575],{"class":62},"    instantUpload",[52,10577,373],{"class":58},[52,10579,1327],{"class":6196},[52,10581,384],{"class":58},[52,10583,10584],{"class":54,"line":337},[52,10585,341],{"emptyLinePlaceholder":340},[52,10587,10588],{"class":54,"line":344},[52,10589,10590],{"class":411},"    \u002F\u002F FilePondPluginImageCrop有効時のアスペクト比。\n",[52,10592,10593,10596,10598,10600,10602,10604],{"class":54,"line":354},[52,10594,10595],{"class":62},"    imageCropAspectRatio",[52,10597,373],{"class":58},[52,10599,376],{"class":58},[52,10601,10328],{"class":75},[52,10603,376],{"class":58},[52,10605,384],{"class":58},[52,10607,10608],{"class":54,"line":367},[52,10609,341],{"emptyLinePlaceholder":340},[52,10611,10612],{"class":54,"line":387},[52,10613,10614],{"class":411},"    \u002F\u002F アップロードする際のajaxの設定\n",[52,10616,10617,10620],{"class":54,"line":415},[52,10618,10619],{"class":62},"    server",[52,10621,924],{"class":58},[52,10623,10624],{"class":54,"line":427},[52,10625,10626],{"class":411},"        \u002F\u002F アップロードするときの設定\n",[52,10628,10629,10632,10634],{"class":54,"line":435},[52,10630,10631],{"class":62},"        process",[52,10633,373],{"class":58},[52,10635,10138],{"class":58},[52,10637,10638],{"class":54,"line":446},[52,10639,10640],{"class":411},"            \u002F\u002F アップロード先URL\n",[52,10642,10643,10646,10648,10650],{"class":54,"line":480},[52,10644,10645],{"class":62},"            url",[52,10647,373],{"class":58},[52,10649,10358],{"class":105},[52,10651,384],{"class":58},[52,10653,10654],{"class":54,"line":509},[52,10655,341],{"emptyLinePlaceholder":340},[52,10657,10658],{"class":54,"line":539},[52,10659,10660],{"class":411},"            \u002F\u002F アップロードのメソッド（processはデフォルトでPOST）\n",[52,10662,10663,10666,10668,10670,10672,10674],{"class":54,"line":547},[52,10664,10665],{"class":62},"            method",[52,10667,373],{"class":58},[52,10669,6309],{"class":58},[52,10671,10372],{"class":75},[52,10673,376],{"class":58},[52,10675,384],{"class":58},[52,10677,10678],{"class":54,"line":553},[52,10679,341],{"emptyLinePlaceholder":340},[52,10681,10682],{"class":54,"line":559},[52,10683,10684],{"class":411},"            \u002F\u002F withCredentialsを有効化\n",[52,10686,10687,10690,10692,10694],{"class":54,"line":564},[52,10688,10689],{"class":62},"            withCredentials",[52,10691,373],{"class":58},[52,10693,10386],{"class":6196},[52,10695,384],{"class":58},[52,10697,10698],{"class":54,"line":569},[52,10699,341],{"emptyLinePlaceholder":340},[52,10701,10702],{"class":54,"line":1106},[52,10703,10704],{"class":411},"            \u002F\u002F エラー時のコールバック\n",[52,10706,10707,10710,10712,10714,10716,10718,10720,10722],{"class":54,"line":1135},[52,10708,10709],{"class":418},"            onerror",[52,10711,10396],{"class":58},[52,10713,10399],{"class":986},[52,10715,938],{"class":58},[52,10717,10404],{"class":65},[52,10719,10407],{"class":58},[52,10721,10410],{"class":418},[52,10723,10724],{"class":105},"(response)\n",[52,10726,10727],{"class":54,"line":1164},[52,10728,10229],{"class":58},[52,10730,10731],{"class":54,"line":1193},[52,10732,10189],{"class":58},[52,10734,10735],{"class":54,"line":1200},[52,10736,341],{"emptyLinePlaceholder":340},[52,10738,10739],{"class":54,"line":1205},[52,10740,10741],{"class":411},"    \u002F\u002F 日本語化\n",[52,10743,10744,10747],{"class":54,"line":1210},[52,10745,10746],{"class":58},"    ...",[52,10748,10434],{"class":105},[52,10750,10751],{"class":54,"line":1215},[52,10752,536],{"class":58},[13,10754,10755,10756,10759,10760,10762],{},"ひとまず上記の設定があり、サーバー側でURLが利用可能であれば一応アップロードが可能です。ただしこれだけではトリミングされません。（プレビューはトリミングされて表示されますが）\n",[49,10757,10758],{},"instantUpload","はデフォルトで",[49,10761,1323],{},"であり、トリミング前にアップロードされますので注意が必要です。",[17,10764,10766],{"id":10765},"filepondとvue-cropperへの連携","Filepondとvue-cropperへの連携",[13,10768,10769],{},"ではアップロードはできたのでcropperと連携しましょう。新しくライブラリをインストールします。",[42,10771,10774],{"className":10772,"code":10773,"language":452},[1615],"npm install filepond-plugin-image-edit filepond-plugin-image-transform vue-cropperjs --save\n",[49,10775,10773],{"__ignoreMap":47},[13,10777,10778,10779,10781],{},"cropperのVue版とfilepondの編集APIとトリミングしてくれるプラグインをインストールします。",[49,10780,9901],{},"も変更します。",[42,10783,10785],{"className":10507,"code":10784,"filename":9901,"language":1434,"meta":47,"style":47},"import vueFilePond from 'vue-filepond';\nimport FilePondPluginFileValidateType from 'filepond-plugin-file-validate-type\u002Fdist\u002Ffilepond-plugin-file-validate-type.esm.js';\nimport FilePondPluginImagePreview from 'filepond-plugin-image-preview\u002Fdist\u002Ffilepond-plugin-image-preview.esm.js';\nimport FilePondPluginImageCrop from 'filepond-plugin-image-crop\u002Fdist\u002Ffilepond-plugin-image-crop.esm';\nimport FilePondPluginImageEdit from 'filepond-plugin-image-edit\u002Fdist\u002Ffilepond-plugin-image-edit.esm';　\u002F\u002F 追加\nimport FilePondPluginImageTransform from 'filepond-plugin-image-transform\u002Fdist\u002Ffilepond-plugin-image-transform.esm';　\u002F\u002F 追加\nconst FilePond = vueFilePond(FilePondPluginFileValidateType, FilePondPluginImagePreview,FilePondPluginImageCrop,FilePondPluginImageEdit,FilePondPluginImageTransform);\nVue.component(FilePond);\n",[49,10786,10787,10804,10822,10840,10858,10879,10899,10936],{"__ignoreMap":47},[52,10788,10789,10791,10794,10796,10798,10800,10802],{"class":54,"line":55},[52,10790,9979],{"class":360},[52,10792,10793],{"class":105}," vueFilePond ",[52,10795,10103],{"class":360},[52,10797,6309],{"class":58},[52,10799,9653],{"class":75},[52,10801,376],{"class":58},[52,10803,1007],{"class":58},[52,10805,10806,10808,10811,10813,10815,10818,10820],{"class":54,"line":83},[52,10807,9979],{"class":360},[52,10809,10810],{"class":105}," FilePondPluginFileValidateType ",[52,10812,10103],{"class":360},[52,10814,6309],{"class":58},[52,10816,10817],{"class":75},"filepond-plugin-file-validate-type\u002Fdist\u002Ffilepond-plugin-file-validate-type.esm.js",[52,10819,376],{"class":58},[52,10821,1007],{"class":58},[52,10823,10824,10826,10829,10831,10833,10836,10838],{"class":54,"line":115},[52,10825,9979],{"class":360},[52,10827,10828],{"class":105}," FilePondPluginImagePreview ",[52,10830,10103],{"class":360},[52,10832,6309],{"class":58},[52,10834,10835],{"class":75},"filepond-plugin-image-preview\u002Fdist\u002Ffilepond-plugin-image-preview.esm.js",[52,10837,376],{"class":58},[52,10839,1007],{"class":58},[52,10841,10842,10844,10847,10849,10851,10854,10856],{"class":54,"line":142},[52,10843,9979],{"class":360},[52,10845,10846],{"class":105}," FilePondPluginImageCrop ",[52,10848,10103],{"class":360},[52,10850,6309],{"class":58},[52,10852,10853],{"class":75},"filepond-plugin-image-crop\u002Fdist\u002Ffilepond-plugin-image-crop.esm",[52,10855,376],{"class":58},[52,10857,1007],{"class":58},[52,10859,10860,10862,10865,10867,10869,10872,10874,10876],{"class":54,"line":169},[52,10861,9979],{"class":360},[52,10863,10864],{"class":105}," FilePondPluginImageEdit ",[52,10866,10103],{"class":360},[52,10868,6309],{"class":58},[52,10870,10871],{"class":75},"filepond-plugin-image-edit\u002Fdist\u002Ffilepond-plugin-image-edit.esm",[52,10873,376],{"class":58},[52,10875,3661],{"class":58},[52,10877,10878],{"class":411},"　\u002F\u002F 追加\n",[52,10880,10881,10883,10886,10888,10890,10893,10895,10897],{"class":54,"line":302},[52,10882,9979],{"class":360},[52,10884,10885],{"class":105}," FilePondPluginImageTransform ",[52,10887,10103],{"class":360},[52,10889,6309],{"class":58},[52,10891,10892],{"class":75},"filepond-plugin-image-transform\u002Fdist\u002Ffilepond-plugin-image-transform.esm",[52,10894,376],{"class":58},[52,10896,3661],{"class":58},[52,10898,10878],{"class":411},[52,10900,10901,10904,10907,10909,10912,10915,10917,10920,10922,10924,10926,10929,10931,10934],{"class":54,"line":308},[52,10902,10903],{"class":65},"const",[52,10905,10906],{"class":105}," FilePond ",[52,10908,69],{"class":58},[52,10910,10911],{"class":418}," vueFilePond",[52,10913,10914],{"class":105},"(FilePondPluginFileValidateType",[52,10916,408],{"class":58},[52,10918,10919],{"class":105}," FilePondPluginImagePreview",[52,10921,408],{"class":58},[52,10923,10003],{"class":105},[52,10925,408],{"class":58},[52,10927,10928],{"class":105},"FilePondPluginImageEdit",[52,10930,408],{"class":58},[52,10932,10933],{"class":105},"FilePondPluginImageTransform)",[52,10935,1007],{"class":58},[52,10937,10938,10941,10943,10946,10949],{"class":54,"line":318},[52,10939,10940],{"class":105},"Vue",[52,10942,957],{"class":58},[52,10944,10945],{"class":418},"component",[52,10947,10948],{"class":105},"(FilePond)",[52,10950,1007],{"class":58},[1499,10952,10954],{"id":10953},"編集uiの表示","編集UIの表示",[13,10956,10957],{},"まず編集ボタンを表示できる様にします。先程のfilepondの設定に編集用コールバックの設定を追加します。",[42,10959,10961],{"className":202,"code":10960,"filename":10014,"language":204,"meta":47,"style":47},"\u003Cscript>\n\u002F\u002F 部分省略\nexport default {\n    name:'fileuploader',\n    data(){\n        return{\n            files:[],\n        }\n    },\n    methods:{\n        \u002F\u002F ...\n    },\n    computed:{\n        \u002F\u002F 追加\n        editor(){\n            return {\n                \u002F\u002F Called by FilePond to edit the image\n                \u002F\u002F - should open your image editor\n                \u002F\u002F - receives file object and image edit instructions\n                open: (file, instructions) => {\n                \u002F\u002F open editor here\n\n                },\n\n                \u002F\u002F Callback set by FilePond\n                \u002F\u002F - should be called by the editor when user confirms editing\n                \u002F\u002F - should receive output object, resulting edit information\n                onconfirm: (data) => {},\n\n                \u002F\u002F Callback set by FilePond\n                \u002F\u002F - should be called by the editor when user cancels editing\n                oncancel: () => {},\n\n                \u002F\u002F Callback set by FilePond\n                \u002F\u002F - should be called by the editor when user closes the editor\n                onclose: () => {\n                },\n            }\n        },\n        attrs(){\n            let apiserver = '\u002Fapi\u002Fv1\u002Ffile\u002Favater';\n            return {\n                imageEditEditor:this.editor, \u002F\u002F 追加\n                allowMultiple:false,\n                'accepted-file-types':'image\u002Fjpeg,image\u002Fpng,image\u002Fgif',\n                instantUpload:false,\n                imageCropAspectRatio:'1:1',\n                server:{\n                    process: {\n                        url:apiserver,\n                        method: 'POST',\n                        withCredentials: true,\n                        onerror:(response) => this.onError(response)\n                    },\n                },\n                ...ja\n            }\n        },\n        \u002F\u002F ...\n    },\n}\n\u003C\u002Fscript>\n",[49,10962,10963,10971,10976,10984,10998,11004,11010,11020,11024,11028,11034,11039,11043,11049,11054,11061,11067,11072,11077,11082,11105,11110,11114,11118,11122,11127,11132,11137,11156,11160,11164,11169,11183,11187,11191,11196,11209,11213,11217,11221,11227,11243,11249,11265,11275,11293,11303,11317,11323,11331,11341,11355,11365,11387,11391,11395,11401,11405,11409,11413,11417,11421],{"__ignoreMap":47},[52,10964,10965,10967,10969],{"class":54,"line":55},[52,10966,59],{"class":58},[52,10968,349],{"class":62},[52,10970,80],{"class":58},[52,10972,10973],{"class":54,"line":83},[52,10974,10975],{"class":411},"\u002F\u002F 部分省略\n",[52,10977,10978,10980,10982],{"class":54,"line":115},[52,10979,357],{"class":360},[52,10981,361],{"class":360},[52,10983,10138],{"class":58},[52,10985,10986,10988,10990,10992,10994,10996],{"class":54,"line":142},[52,10987,10143],{"class":62},[52,10989,373],{"class":58},[52,10991,376],{"class":58},[52,10993,10150],{"class":75},[52,10995,376],{"class":58},[52,10997,384],{"class":58},[52,10999,11000,11002],{"class":54,"line":169},[52,11001,10159],{"class":62},[52,11003,2424],{"class":58},[52,11005,11006,11008],{"class":54,"line":302},[52,11007,10166],{"class":360},[52,11009,364],{"class":58},[52,11011,11012,11014,11016,11018],{"class":54,"line":308},[52,11013,10173],{"class":62},[52,11015,373],{"class":58},[52,11017,10178],{"class":62},[52,11019,384],{"class":58},[52,11021,11022],{"class":54,"line":318},[52,11023,5398],{"class":58},[52,11025,11026],{"class":54,"line":328},[52,11027,10189],{"class":58},[52,11029,11030,11032],{"class":54,"line":337},[52,11031,10194],{"class":62},[52,11033,924],{"class":58},[52,11035,11036],{"class":54,"line":344},[52,11037,11038],{"class":411},"        \u002F\u002F ...\n",[52,11040,11041],{"class":54,"line":354},[52,11042,10189],{"class":58},[52,11044,11045,11047],{"class":54,"line":367},[52,11046,10238],{"class":62},[52,11048,924],{"class":58},[52,11050,11051],{"class":54,"line":387},[52,11052,11053],{"class":411},"        \u002F\u002F 追加\n",[52,11055,11056,11059],{"class":54,"line":415},[52,11057,11058],{"class":62},"        editor",[52,11060,2424],{"class":58},[52,11062,11063,11065],{"class":54,"line":427},[52,11064,10271],{"class":360},[52,11066,10138],{"class":58},[52,11068,11069],{"class":54,"line":435},[52,11070,11071],{"class":411},"                \u002F\u002F Called by FilePond to edit the image\n",[52,11073,11074],{"class":54,"line":446},[52,11075,11076],{"class":411},"                \u002F\u002F - should open your image editor\n",[52,11078,11079],{"class":54,"line":480},[52,11080,11081],{"class":411},"                \u002F\u002F - receives file object and image edit instructions\n",[52,11083,11084,11087,11089,11091,11094,11096,11099,11101,11103],{"class":54,"line":509},[52,11085,11086],{"class":418},"                open",[52,11088,373],{"class":58},[52,11090,2499],{"class":58},[52,11092,11093],{"class":986},"file",[52,11095,408],{"class":58},[52,11097,11098],{"class":986}," instructions",[52,11100,938],{"class":58},[52,11102,10404],{"class":65},[52,11104,10138],{"class":58},[52,11106,11107],{"class":54,"line":539},[52,11108,11109],{"class":411},"                \u002F\u002F open editor here\n",[52,11111,11112],{"class":54,"line":547},[52,11113,341],{"emptyLinePlaceholder":340},[52,11115,11116],{"class":54,"line":553},[52,11117,10426],{"class":58},[52,11119,11120],{"class":54,"line":559},[52,11121,341],{"emptyLinePlaceholder":340},[52,11123,11124],{"class":54,"line":564},[52,11125,11126],{"class":411},"                \u002F\u002F Callback set by FilePond\n",[52,11128,11129],{"class":54,"line":569},[52,11130,11131],{"class":411},"                \u002F\u002F - should be called by the editor when user confirms editing\n",[52,11133,11134],{"class":54,"line":1106},[52,11135,11136],{"class":411},"                \u002F\u002F - should receive output object, resulting edit information\n",[52,11138,11139,11142,11144,11146,11149,11151,11153],{"class":54,"line":1135},[52,11140,11141],{"class":418},"                onconfirm",[52,11143,373],{"class":58},[52,11145,2499],{"class":58},[52,11147,11148],{"class":986},"data",[52,11150,938],{"class":58},[52,11152,10404],{"class":65},[52,11154,11155],{"class":58}," {},\n",[52,11157,11158],{"class":54,"line":1164},[52,11159,341],{"emptyLinePlaceholder":340},[52,11161,11162],{"class":54,"line":1193},[52,11163,11126],{"class":411},[52,11165,11166],{"class":54,"line":1200},[52,11167,11168],{"class":411},"                \u002F\u002F - should be called by the editor when user cancels editing\n",[52,11170,11171,11174,11176,11179,11181],{"class":54,"line":1205},[52,11172,11173],{"class":418},"                oncancel",[52,11175,373],{"class":58},[52,11177,11178],{"class":58}," ()",[52,11180,10404],{"class":65},[52,11182,11155],{"class":58},[52,11184,11185],{"class":54,"line":1210},[52,11186,341],{"emptyLinePlaceholder":340},[52,11188,11189],{"class":54,"line":1215},[52,11190,11126],{"class":411},[52,11192,11193],{"class":54,"line":1220},[52,11194,11195],{"class":411},"                \u002F\u002F - should be called by the editor when user closes the editor\n",[52,11197,11198,11201,11203,11205,11207],{"class":54,"line":3800},[52,11199,11200],{"class":418},"                onclose",[52,11202,373],{"class":58},[52,11204,11178],{"class":58},[52,11206,10404],{"class":65},[52,11208,10138],{"class":58},[52,11210,11211],{"class":54,"line":3821},[52,11212,10426],{"class":58},[52,11214,11215],{"class":54,"line":3835},[52,11216,2251],{"class":58},[52,11218,11219],{"class":54,"line":3840},[52,11220,10229],{"class":58},[52,11222,11223,11225],{"class":54,"line":3865},[52,11224,10245],{"class":62},[52,11226,2424],{"class":58},[52,11228,11229,11231,11233,11235,11237,11239,11241],{"class":54,"line":3879},[52,11230,10252],{"class":65},[52,11232,10255],{"class":105},[52,11234,951],{"class":58},[52,11236,6309],{"class":58},[52,11238,10262],{"class":75},[52,11240,376],{"class":58},[52,11242,1007],{"class":58},[52,11244,11245,11247],{"class":54,"line":5506},[52,11246,10271],{"class":360},[52,11248,10138],{"class":58},[52,11250,11251,11254,11257,11260,11262],{"class":54,"line":4},[52,11252,11253],{"class":62},"                imageEditEditor",[52,11255,11256],{"class":58},":this.",[52,11258,11259],{"class":105},"editor",[52,11261,408],{"class":58},[52,11263,11264],{"class":411}," \u002F\u002F 追加\n",[52,11266,11267,11269,11271,11273],{"class":54,"line":5544},[52,11268,10278],{"class":62},[52,11270,373],{"class":58},[52,11272,1327],{"class":6196},[52,11274,384],{"class":58},[52,11276,11277,11279,11281,11283,11285,11287,11289,11291],{"class":54,"line":5561},[52,11278,10289],{"class":58},[52,11280,10292],{"class":62},[52,11282,376],{"class":58},[52,11284,373],{"class":58},[52,11286,376],{"class":58},[52,11288,10301],{"class":75},[52,11290,376],{"class":58},[52,11292,384],{"class":58},[52,11294,11295,11297,11299,11301],{"class":54,"line":5566},[52,11296,10310],{"class":62},[52,11298,373],{"class":58},[52,11300,1327],{"class":6196},[52,11302,384],{"class":58},[52,11304,11305,11307,11309,11311,11313,11315],{"class":54,"line":5587},[52,11306,10321],{"class":62},[52,11308,373],{"class":58},[52,11310,376],{"class":58},[52,11312,10328],{"class":75},[52,11314,376],{"class":58},[52,11316,384],{"class":58},[52,11318,11319,11321],{"class":54,"line":5636},[52,11320,10337],{"class":62},[52,11322,924],{"class":58},[52,11324,11325,11327,11329],{"class":54,"line":5641},[52,11326,10344],{"class":62},[52,11328,373],{"class":58},[52,11330,10138],{"class":58},[52,11332,11333,11335,11337,11339],{"class":54,"line":5646},[52,11334,10353],{"class":62},[52,11336,373],{"class":58},[52,11338,10358],{"class":105},[52,11340,384],{"class":58},[52,11342,11343,11345,11347,11349,11351,11353],{"class":54,"line":5679},[52,11344,10365],{"class":62},[52,11346,373],{"class":58},[52,11348,6309],{"class":58},[52,11350,10372],{"class":75},[52,11352,376],{"class":58},[52,11354,384],{"class":58},[52,11356,11357,11359,11361,11363],{"class":54,"line":5692},[52,11358,10381],{"class":62},[52,11360,373],{"class":58},[52,11362,10386],{"class":6196},[52,11364,384],{"class":58},[52,11366,11367,11369,11371,11373,11375,11377,11379,11381,11383,11385],{"class":54,"line":5711},[52,11368,10393],{"class":418},[52,11370,10396],{"class":58},[52,11372,10399],{"class":986},[52,11374,938],{"class":58},[52,11376,10404],{"class":65},[52,11378,10407],{"class":58},[52,11380,10410],{"class":418},[52,11382,932],{"class":62},[52,11384,10399],{"class":105},[52,11386,1015],{"class":62},[52,11388,11389],{"class":54,"line":5724},[52,11390,10421],{"class":58},[52,11392,11393],{"class":54,"line":5745},[52,11394,10426],{"class":58},[52,11396,11397,11399],{"class":54,"line":5750},[52,11398,10431],{"class":58},[52,11400,10434],{"class":105},[52,11402,11403],{"class":54,"line":5786},[52,11404,2251],{"class":58},[52,11406,11407],{"class":54,"line":5836},[52,11408,10229],{"class":58},[52,11410,11411],{"class":54,"line":5862},[52,11412,11038],{"class":411},[52,11414,11415],{"class":54,"line":5867},[52,11416,10189],{"class":58},[52,11418,11419],{"class":54,"line":5872},[52,11420,536],{"class":58},[52,11422,11423,11425,11427],{"class":54,"line":5893},[52,11424,108],{"class":58},[52,11426,349],{"class":62},[52,11428,80],{"class":58},[13,11430,11431,11433,11434,11437,11438,11440,11441,11443,11444,11446,11447],{},[49,11432,11259],{},"というプロパティーを追加しました。そしてfilepondの設定に",[49,11435,11436],{},"imageEditEditor","を追加して、",[49,11439,11259],{},"を参照する様にします。この",[49,11442,11436],{},"がないと編集UIが出現しません。",[49,11445,11259],{},"の雛形は",[2039,11448,11451],{"href":11449,"rel":11450},"https:\u002F\u002Fpqina.nl\u002Ffilepond\u002Fdocs\u002Fapi\u002Fplugins\u002Fimage-edit#integrating-other-editors",[2043],"本家ドキュメントから参照できます。",[13,11453,11454,11456],{},[49,11455,11436],{},"をしますと下図の赤矢印の様に編集ボタンが出現します。",[729,11458],{":src":11459,":width":9596,":center":1323},"'filepond-crop-vue\u002Feditui.png'",[13,11461,11462,11463,11465,11466,11469,11470,11473],{},"この編集ボタンをクリックした際は",[49,11464,11259],{},"の",[49,11467,11468],{},"open()=>{}","コールバックが実行されます。この際に",[49,11471,11472],{},"cropper","に画像データを渡し、起動する様にすればトリミングUIを表示できる様になります。",[1499,11475,11476],{"id":11476},"cropperのレンダリング",[13,11478,11479],{},"設定の前にfilepondのコンポーネントを表示する様にします。",[42,11481,11483],{"className":202,"code":11482,"filename":10014,"language":204,"meta":47,"style":47},"\u003Ctemplate>\n\u003Cdiv>\n    \u003Cfile-pond v-bind=\"attrs\"\u002F>\n    \u003Cvue-cropper\n        ref=\"cropper\"\n        :src=\"imgSrc\"\n        :aspect-ratio=\"1 \u002F 1\"\n        :view-mode=\"1\"\n    >\n    \u003C\u002Fvue-cropper>\n    \u003Cbutton variant=\"primary\" @click=\"crop\">crop\u003C\u002Fbutton>\n\u003C\u002Fdiv>\n\u003C\u002Ftemplate>\n\u003Cscript>\nimport ja from 'filepond\u002Flocale\u002Fja-ja';\nimport VueCropper from 'vue-cropperjs';　 \u002F\u002F 追加\nexport default {\n    name:'fileuploader',\n    data(){\n        return{\n            files:[],\n            imgSrc:'', \u002F\u002F 追加\n        }\n    },\n    methods:{\n        onError(res){\n            \u002F\u002F ...\n        },\n        crop(){\n            \u002F\u002F 追加\n            \u002F\u002F とりあえずこのままにしておいてください。\n        }\n    },\n    computed:{\n        \u002F\u002F ...\n    },\n    components:{\n        VueCropper \u002F\u002F 追加\n    }\n}\n\u003C\u002Fscript>\n",[49,11484,11485,11493,11501,11519,11526,11538,11552,11566,11579,11584,11593,11634,11642,11650,11658,11674,11693,11701,11715,11721,11727,11737,11751,11755,11759,11765,11775,11780,11784,11791,11796,11801,11805,11809,11815,11819,11823,11830,11838,11842,11846],{"__ignoreMap":47},[52,11486,11487,11489,11491],{"class":54,"line":55},[52,11488,59],{"class":58},[52,11490,213],{"class":62},[52,11492,80],{"class":58},[52,11494,11495,11497,11499],{"class":54,"line":83},[52,11496,59],{"class":58},[52,11498,8503],{"class":62},[52,11500,80],{"class":58},[52,11502,11503,11505,11507,11509,11511,11513,11515,11517],{"class":54,"line":115},[52,11504,2161],{"class":58},[52,11506,10043],{"class":62},[52,11508,10057],{"class":65},[52,11510,69],{"class":58},[52,11512,72],{"class":58},[52,11514,10064],{"class":75},[52,11516,72],{"class":58},[52,11518,10069],{"class":58},[52,11520,11521,11523],{"class":54,"line":142},[52,11522,2161],{"class":58},[52,11524,11525],{"class":62},"vue-cropper\n",[52,11527,11528,11530,11532,11534,11536],{"class":54,"line":169},[52,11529,6277],{"class":65},[52,11531,69],{"class":58},[52,11533,72],{"class":58},[52,11535,11472],{"class":75},[52,11537,266],{"class":58},[52,11539,11540,11543,11545,11547,11550],{"class":54,"line":302},[52,11541,11542],{"class":65},"        :src",[52,11544,69],{"class":58},[52,11546,72],{"class":58},[52,11548,11549],{"class":75},"imgSrc",[52,11551,266],{"class":58},[52,11553,11554,11557,11559,11561,11564],{"class":54,"line":308},[52,11555,11556],{"class":65},"        :aspect-ratio",[52,11558,69],{"class":58},[52,11560,72],{"class":58},[52,11562,11563],{"class":75},"1 \u002F 1",[52,11565,266],{"class":58},[52,11567,11568,11571,11573,11575,11577],{"class":54,"line":318},[52,11569,11570],{"class":65},"        :view-mode",[52,11572,69],{"class":58},[52,11574,72],{"class":58},[52,11576,31],{"class":75},[52,11578,266],{"class":58},[52,11580,11581],{"class":54,"line":328},[52,11582,11583],{"class":58},"    >\n",[52,11585,11586,11588,11591],{"class":54,"line":337},[52,11587,2303],{"class":58},[52,11589,11590],{"class":62},"vue-cropper",[52,11592,80],{"class":58},[52,11594,11595,11597,11600,11603,11605,11607,11610,11612,11615,11617,11619,11622,11624,11626,11628,11630,11632],{"class":54,"line":344},[52,11596,2161],{"class":58},[52,11598,11599],{"class":62},"button",[52,11601,11602],{"class":65}," variant",[52,11604,69],{"class":58},[52,11606,72],{"class":58},[52,11608,11609],{"class":75},"primary",[52,11611,72],{"class":58},[52,11613,11614],{"class":65}," @click",[52,11616,69],{"class":58},[52,11618,72],{"class":58},[52,11620,11621],{"class":75},"crop",[52,11623,72],{"class":58},[52,11625,102],{"class":58},[52,11627,11621],{"class":105},[52,11629,108],{"class":58},[52,11631,11599],{"class":62},[52,11633,80],{"class":58},[52,11635,11636,11638,11640],{"class":54,"line":354},[52,11637,108],{"class":58},[52,11639,8503],{"class":62},[52,11641,80],{"class":58},[52,11643,11644,11646,11648],{"class":54,"line":367},[52,11645,108],{"class":58},[52,11647,213],{"class":62},[52,11649,80],{"class":58},[52,11651,11652,11654,11656],{"class":54,"line":387},[52,11653,59],{"class":58},[52,11655,349],{"class":62},[52,11657,80],{"class":58},[52,11659,11660,11662,11664,11666,11668,11670,11672],{"class":54,"line":415},[52,11661,9979],{"class":360},[52,11663,10100],{"class":105},[52,11665,10103],{"class":360},[52,11667,6309],{"class":58},[52,11669,10108],{"class":75},[52,11671,376],{"class":58},[52,11673,1007],{"class":58},[52,11675,11676,11678,11680,11682,11684,11686,11688,11690],{"class":54,"line":427},[52,11677,9979],{"class":360},[52,11679,10119],{"class":105},[52,11681,10103],{"class":360},[52,11683,6309],{"class":58},[52,11685,9811],{"class":75},[52,11687,376],{"class":58},[52,11689,3661],{"class":58},[52,11691,11692],{"class":411},"　 \u002F\u002F 追加\n",[52,11694,11695,11697,11699],{"class":54,"line":435},[52,11696,357],{"class":360},[52,11698,361],{"class":360},[52,11700,10138],{"class":58},[52,11702,11703,11705,11707,11709,11711,11713],{"class":54,"line":446},[52,11704,10143],{"class":62},[52,11706,373],{"class":58},[52,11708,376],{"class":58},[52,11710,10150],{"class":75},[52,11712,376],{"class":58},[52,11714,384],{"class":58},[52,11716,11717,11719],{"class":54,"line":480},[52,11718,10159],{"class":62},[52,11720,2424],{"class":58},[52,11722,11723,11725],{"class":54,"line":509},[52,11724,10166],{"class":360},[52,11726,364],{"class":58},[52,11728,11729,11731,11733,11735],{"class":54,"line":539},[52,11730,10173],{"class":62},[52,11732,373],{"class":58},[52,11734,10178],{"class":62},[52,11736,384],{"class":58},[52,11738,11739,11742,11744,11747,11749],{"class":54,"line":547},[52,11740,11741],{"class":62},"            imgSrc",[52,11743,373],{"class":58},[52,11745,11746],{"class":58},"''",[52,11748,408],{"class":58},[52,11750,11264],{"class":411},[52,11752,11753],{"class":54,"line":553},[52,11754,5398],{"class":58},[52,11756,11757],{"class":54,"line":559},[52,11758,10189],{"class":58},[52,11760,11761,11763],{"class":54,"line":564},[52,11762,10194],{"class":62},[52,11764,924],{"class":58},[52,11766,11767,11769,11771,11773],{"class":54,"line":569},[52,11768,10201],{"class":62},[52,11770,932],{"class":58},[52,11772,10206],{"class":986},[52,11774,3439],{"class":58},[52,11776,11777],{"class":54,"line":1106},[52,11778,11779],{"class":411},"            \u002F\u002F ...\n",[52,11781,11782],{"class":54,"line":1135},[52,11783,10229],{"class":58},[52,11785,11786,11789],{"class":54,"line":1164},[52,11787,11788],{"class":62},"        crop",[52,11790,2424],{"class":58},[52,11792,11793],{"class":54,"line":1193},[52,11794,11795],{"class":411},"            \u002F\u002F 追加\n",[52,11797,11798],{"class":54,"line":1200},[52,11799,11800],{"class":411},"            \u002F\u002F とりあえずこのままにしておいてください。\n",[52,11802,11803],{"class":54,"line":1205},[52,11804,5398],{"class":58},[52,11806,11807],{"class":54,"line":1210},[52,11808,10189],{"class":58},[52,11810,11811,11813],{"class":54,"line":1215},[52,11812,10238],{"class":62},[52,11814,924],{"class":58},[52,11816,11817],{"class":54,"line":1220},[52,11818,11038],{"class":411},[52,11820,11821],{"class":54,"line":3800},[52,11822,10189],{"class":58},[52,11824,11825,11828],{"class":54,"line":3821},[52,11826,11827],{"class":62},"    components",[52,11829,924],{"class":58},[52,11831,11832,11835],{"class":54,"line":3835},[52,11833,11834],{"class":105},"        VueCropper ",[52,11836,11837],{"class":411},"\u002F\u002F 追加\n",[52,11839,11840],{"class":54,"line":3840},[52,11841,2696],{"class":58},[52,11843,11844],{"class":54,"line":3865},[52,11845,536],{"class":58},[52,11847,11848,11850,11852],{"class":54,"line":3879},[52,11849,108],{"class":58},[52,11851,349],{"class":62},[52,11853,80],{"class":58},[13,11855,11856,11859,11860,11862,11863,11865],{},[49,11857,11858],{},"import VueCropper from 'vue-cropperjs';","にてvue-cropperのコンポーネントを配置します。",[49,11861,11590],{},"はトリミングのUIを提供し、",[49,11864,11599],{},"はトリミングの実行を行ってfilepondにデータを渡すメソッドに連絡します。\nひとまず以下の様に表示されると思います。",[729,11867],{":src":11868,":width":9596,":center":1323},"'filepond-crop-vue\u002Fcropper-init.png'",[13,11870,11871],{},"なおvue-cropperのコンポーネントに設定した項目は以下の通りです。",[1467,11873,11874,11880],{},[1470,11875,11876,11879],{},[49,11877,11878],{},"aspect-ratio",":トリミングUIのアスペクト比です。",[1470,11881,11882,11885],{},[49,11883,11884],{},"view-mode",":ビューモードと呼ばれるトリミングの選択範囲を制御できます。デフォルトは0であり制限がなく、透明の領域までトリミングできてしまします。詳しくはそれぞれ設定してみて挙動を確認してください。",[1499,11887,11888],{"id":11888},"filepondからcropperへ画像を渡す",[13,11890,11891,11892,11894,11895,11898],{},"それでは編集ボタンを押したらvue-cropperへ画像を渡す様にしましょう。実際のサービスではモーダルなどを出したりした方がいいかもしれませんが、面倒なので今はそのまま表示します。",[49,11893,11259],{},"をの",[49,11896,11897],{},"open","コールバックを編集します。",[42,11900,11902],{"className":10507,"code":11901,"language":1434,"meta":47,"style":47},"editor(){\n    return {\n        \u002F\u002F Called by FilePond to edit the image\n        \u002F\u002F - should open your image editor\n        \u002F\u002F - receives file object and image edit instructions\n        open: (file, instructions) => {\n        \u002F\u002F open editor here\n            const objectURL = URL.createObjectURL(file)\n            this.imgSet =objectURL;\n            this.$refs.cropper.replace(objectURL);\n        },\n},\n",[49,11903,11904,11912,11919,11924,11929,11934,11955,11960,11984,11999,12023,12027],{"__ignoreMap":47},[52,11905,11906,11908,11910],{"class":54,"line":55},[52,11907,11259],{"class":418},[52,11909,422],{"class":105},[52,11911,364],{"class":58},[52,11913,11914,11917],{"class":54,"line":83},[52,11915,11916],{"class":360},"    return",[52,11918,10138],{"class":58},[52,11920,11921],{"class":54,"line":115},[52,11922,11923],{"class":411},"        \u002F\u002F Called by FilePond to edit the image\n",[52,11925,11926],{"class":54,"line":142},[52,11927,11928],{"class":411},"        \u002F\u002F - should open your image editor\n",[52,11930,11931],{"class":54,"line":169},[52,11932,11933],{"class":411},"        \u002F\u002F - receives file object and image edit instructions\n",[52,11935,11936,11939,11941,11943,11945,11947,11949,11951,11953],{"class":54,"line":302},[52,11937,11938],{"class":418},"        open",[52,11940,373],{"class":58},[52,11942,2499],{"class":58},[52,11944,11093],{"class":986},[52,11946,408],{"class":58},[52,11948,11098],{"class":986},[52,11950,938],{"class":58},[52,11952,10404],{"class":65},[52,11954,10138],{"class":58},[52,11956,11957],{"class":54,"line":308},[52,11958,11959],{"class":411},"        \u002F\u002F open editor here\n",[52,11961,11962,11965,11968,11970,11973,11975,11978,11980,11982],{"class":54,"line":318},[52,11963,11964],{"class":65},"            const",[52,11966,11967],{"class":105}," objectURL",[52,11969,951],{"class":58},[52,11971,11972],{"class":105}," URL",[52,11974,957],{"class":58},[52,11976,11977],{"class":418},"createObjectURL",[52,11979,932],{"class":62},[52,11981,11093],{"class":105},[52,11983,1015],{"class":62},[52,11985,11986,11989,11992,11994,11997],{"class":54,"line":328},[52,11987,11988],{"class":58},"            this.",[52,11990,11991],{"class":105},"imgSet",[52,11993,951],{"class":58},[52,11995,11996],{"class":105},"objectURL",[52,11998,1007],{"class":58},[52,12000,12001,12003,12006,12008,12010,12012,12015,12017,12019,12021],{"class":54,"line":337},[52,12002,11988],{"class":58},[52,12004,12005],{"class":105},"$refs",[52,12007,957],{"class":58},[52,12009,11472],{"class":105},[52,12011,957],{"class":58},[52,12013,12014],{"class":418},"replace",[52,12016,932],{"class":62},[52,12018,11996],{"class":105},[52,12020,938],{"class":62},[52,12022,1007],{"class":58},[52,12024,12025],{"class":54,"line":344},[52,12026,10229],{"class":58},[52,12028,12029],{"class":54,"line":354},[52,12030,477],{"class":58},[13,12032,12033,12035,12036,12038,12039,12042,12043,12045,12046,12049],{},[49,12034,11897],{},"コールバックは２つの引数があり、",[49,12037,11093],{},"はアップロードされたFileオブジェクトです。instructionsは編集する前のfilepondの編集情報です。fileオブジェクトから",[49,12040,12041],{},"URL.createObjectURL(file)","を使用することでブラウザアップロード（filepond）された画像をURLで参照できる様になります。そして",[49,12044,11549],{},"に代入し、そして",[49,12047,12048],{},"cropper.replace(objectURL);","を使用してトリミングの画像を差し替えます。",[13,12051,12052],{},"上記の様に編集して、編集ボタンを押すとトリミングUIが表示される様になり、任意のトリミング位置を設定できる様になります。",[729,12054],{":src":12055,":width":9596,":center":1323},"'filepond-crop-vue\u002Fcroppable.png'",[1499,12057,12058],{"id":12058},"cropperからfilepondへ情報を渡す",[13,12060,12061],{},"トリミングUIでは任意の範囲と位置を設定できます。トリミングした画像をfilepondに適用するためには、vue-cropperで選択したトリミングの位置や範囲などのオブジェクトをfilepondに渡すことで実現できます。（cropperで画像を加工するわけではない！）",[13,12063,12064,12065,11465,12067,12070],{},"データを渡すときは",[49,12066,11259],{},[49,12068,12069],{},"onconfirm","コールバックを利用します。vue-cropperでトリミングした際にコールバックを呼び出します。",[42,12072,12074],{"className":202,"code":12073,"filename":10014,"language":204,"meta":47,"style":47},"\u003Ctemplate>\n\u003Cdiv>\n    \u003Cfile-pond v-bind=\"attrs\"\u002F>\n    \u003Cvue-cropper\n        ref=\"cropper\"\n        :src=\"imgSrc\"\n        :aspect-ratio=\"1 \u002F 1\"\n        :view-mode=\"1\"\n    >\n    \u003C\u002Fvue-cropper>\n    \u003Cbutton variant=\"primary\" @click=\"crop\">crop\u003C\u002Fbutton>\n\u003C\u002Fdiv>\n\u003C\u002Ftemplate>\n\u003Cscript>\nimport ja from 'filepond\u002Flocale\u002Fja-ja';\nimport VueCropper from 'vue-cropperjs';　 \u002F\u002F 追加\nexport default {\n    name:'fileuploader',\n    data(){\n        return{\n            files:[],\n            imgSrc:'', \u002F\u002F 追加\n        }\n    },\n    methods:{\n        onError(res){\n            \u002F\u002F ...\n        },\n        crop(){\n            \u002F\u002F 追加\n            this.editor.onconfirm()\n        }\n    },\n    computed:{\n        editor(){\n            return{\n                \u002F\u002F ...\n                onconfirm: (data) => {},\n                \u002F\u002F ...\n            }\n        }\n    },\n    components:{\n        VueCropper \u002F\u002F 追加\n    }\n}\n\u003C\u002Fscript>\n",[49,12075,12076,12084,12092,12110,12116,12128,12140,12152,12164,12168,12176,12212,12220,12228,12236,12252,12270,12278,12292,12298,12304,12314,12326,12330,12334,12340,12350,12354,12358,12364,12368,12381,12385,12389,12395,12401,12407,12412,12428,12432,12436,12440,12444,12450,12456,12460,12464],{"__ignoreMap":47},[52,12077,12078,12080,12082],{"class":54,"line":55},[52,12079,59],{"class":58},[52,12081,213],{"class":62},[52,12083,80],{"class":58},[52,12085,12086,12088,12090],{"class":54,"line":83},[52,12087,59],{"class":58},[52,12089,8503],{"class":62},[52,12091,80],{"class":58},[52,12093,12094,12096,12098,12100,12102,12104,12106,12108],{"class":54,"line":115},[52,12095,2161],{"class":58},[52,12097,10043],{"class":62},[52,12099,10057],{"class":65},[52,12101,69],{"class":58},[52,12103,72],{"class":58},[52,12105,10064],{"class":75},[52,12107,72],{"class":58},[52,12109,10069],{"class":58},[52,12111,12112,12114],{"class":54,"line":142},[52,12113,2161],{"class":58},[52,12115,11525],{"class":62},[52,12117,12118,12120,12122,12124,12126],{"class":54,"line":169},[52,12119,6277],{"class":65},[52,12121,69],{"class":58},[52,12123,72],{"class":58},[52,12125,11472],{"class":75},[52,12127,266],{"class":58},[52,12129,12130,12132,12134,12136,12138],{"class":54,"line":302},[52,12131,11542],{"class":65},[52,12133,69],{"class":58},[52,12135,72],{"class":58},[52,12137,11549],{"class":75},[52,12139,266],{"class":58},[52,12141,12142,12144,12146,12148,12150],{"class":54,"line":308},[52,12143,11556],{"class":65},[52,12145,69],{"class":58},[52,12147,72],{"class":58},[52,12149,11563],{"class":75},[52,12151,266],{"class":58},[52,12153,12154,12156,12158,12160,12162],{"class":54,"line":318},[52,12155,11570],{"class":65},[52,12157,69],{"class":58},[52,12159,72],{"class":58},[52,12161,31],{"class":75},[52,12163,266],{"class":58},[52,12165,12166],{"class":54,"line":328},[52,12167,11583],{"class":58},[52,12169,12170,12172,12174],{"class":54,"line":337},[52,12171,2303],{"class":58},[52,12173,11590],{"class":62},[52,12175,80],{"class":58},[52,12177,12178,12180,12182,12184,12186,12188,12190,12192,12194,12196,12198,12200,12202,12204,12206,12208,12210],{"class":54,"line":344},[52,12179,2161],{"class":58},[52,12181,11599],{"class":62},[52,12183,11602],{"class":65},[52,12185,69],{"class":58},[52,12187,72],{"class":58},[52,12189,11609],{"class":75},[52,12191,72],{"class":58},[52,12193,11614],{"class":65},[52,12195,69],{"class":58},[52,12197,72],{"class":58},[52,12199,11621],{"class":75},[52,12201,72],{"class":58},[52,12203,102],{"class":58},[52,12205,11621],{"class":105},[52,12207,108],{"class":58},[52,12209,11599],{"class":62},[52,12211,80],{"class":58},[52,12213,12214,12216,12218],{"class":54,"line":354},[52,12215,108],{"class":58},[52,12217,8503],{"class":62},[52,12219,80],{"class":58},[52,12221,12222,12224,12226],{"class":54,"line":367},[52,12223,108],{"class":58},[52,12225,213],{"class":62},[52,12227,80],{"class":58},[52,12229,12230,12232,12234],{"class":54,"line":387},[52,12231,59],{"class":58},[52,12233,349],{"class":62},[52,12235,80],{"class":58},[52,12237,12238,12240,12242,12244,12246,12248,12250],{"class":54,"line":415},[52,12239,9979],{"class":360},[52,12241,10100],{"class":105},[52,12243,10103],{"class":360},[52,12245,6309],{"class":58},[52,12247,10108],{"class":75},[52,12249,376],{"class":58},[52,12251,1007],{"class":58},[52,12253,12254,12256,12258,12260,12262,12264,12266,12268],{"class":54,"line":427},[52,12255,9979],{"class":360},[52,12257,10119],{"class":105},[52,12259,10103],{"class":360},[52,12261,6309],{"class":58},[52,12263,9811],{"class":75},[52,12265,376],{"class":58},[52,12267,3661],{"class":58},[52,12269,11692],{"class":411},[52,12271,12272,12274,12276],{"class":54,"line":435},[52,12273,357],{"class":360},[52,12275,361],{"class":360},[52,12277,10138],{"class":58},[52,12279,12280,12282,12284,12286,12288,12290],{"class":54,"line":446},[52,12281,10143],{"class":62},[52,12283,373],{"class":58},[52,12285,376],{"class":58},[52,12287,10150],{"class":75},[52,12289,376],{"class":58},[52,12291,384],{"class":58},[52,12293,12294,12296],{"class":54,"line":480},[52,12295,10159],{"class":62},[52,12297,2424],{"class":58},[52,12299,12300,12302],{"class":54,"line":509},[52,12301,10166],{"class":360},[52,12303,364],{"class":58},[52,12305,12306,12308,12310,12312],{"class":54,"line":539},[52,12307,10173],{"class":62},[52,12309,373],{"class":58},[52,12311,10178],{"class":62},[52,12313,384],{"class":58},[52,12315,12316,12318,12320,12322,12324],{"class":54,"line":547},[52,12317,11741],{"class":62},[52,12319,373],{"class":58},[52,12321,11746],{"class":58},[52,12323,408],{"class":58},[52,12325,11264],{"class":411},[52,12327,12328],{"class":54,"line":553},[52,12329,5398],{"class":58},[52,12331,12332],{"class":54,"line":559},[52,12333,10189],{"class":58},[52,12335,12336,12338],{"class":54,"line":564},[52,12337,10194],{"class":62},[52,12339,924],{"class":58},[52,12341,12342,12344,12346,12348],{"class":54,"line":569},[52,12343,10201],{"class":62},[52,12345,932],{"class":58},[52,12347,10206],{"class":986},[52,12349,3439],{"class":58},[52,12351,12352],{"class":54,"line":1106},[52,12353,11779],{"class":411},[52,12355,12356],{"class":54,"line":1135},[52,12357,10229],{"class":58},[52,12359,12360,12362],{"class":54,"line":1164},[52,12361,11788],{"class":62},[52,12363,2424],{"class":58},[52,12365,12366],{"class":54,"line":1193},[52,12367,11795],{"class":411},[52,12369,12370,12372,12374,12376,12378],{"class":54,"line":1200},[52,12371,11988],{"class":58},[52,12373,11259],{"class":105},[52,12375,957],{"class":58},[52,12377,12069],{"class":418},[52,12379,12380],{"class":62},"()\n",[52,12382,12383],{"class":54,"line":1205},[52,12384,5398],{"class":58},[52,12386,12387],{"class":54,"line":1210},[52,12388,10189],{"class":58},[52,12390,12391,12393],{"class":54,"line":1215},[52,12392,10238],{"class":62},[52,12394,924],{"class":58},[52,12396,12397,12399],{"class":54,"line":1220},[52,12398,11058],{"class":62},[52,12400,2424],{"class":58},[52,12402,12403,12405],{"class":54,"line":3800},[52,12404,10271],{"class":360},[52,12406,364],{"class":58},[52,12408,12409],{"class":54,"line":3821},[52,12410,12411],{"class":411},"                \u002F\u002F ...\n",[52,12413,12414,12416,12418,12420,12422,12424,12426],{"class":54,"line":3835},[52,12415,11141],{"class":418},[52,12417,373],{"class":58},[52,12419,2499],{"class":58},[52,12421,11148],{"class":986},[52,12423,938],{"class":58},[52,12425,10404],{"class":65},[52,12427,11155],{"class":58},[52,12429,12430],{"class":54,"line":3840},[52,12431,12411],{"class":411},[52,12433,12434],{"class":54,"line":3865},[52,12435,2251],{"class":58},[52,12437,12438],{"class":54,"line":3879},[52,12439,5398],{"class":58},[52,12441,12442],{"class":54,"line":5506},[52,12443,10189],{"class":58},[52,12445,12446,12448],{"class":54,"line":4},[52,12447,11827],{"class":62},[52,12449,924],{"class":58},[52,12451,12452,12454],{"class":54,"line":5544},[52,12453,11834],{"class":105},[52,12455,11837],{"class":411},[52,12457,12458],{"class":54,"line":5561},[52,12459,2696],{"class":58},[52,12461,12462],{"class":54,"line":5566},[52,12463,536],{"class":58},[52,12465,12466,12468,12470],{"class":54,"line":5587},[52,12467,108],{"class":58},[52,12469,349],{"class":62},[52,12471,80],{"class":58},[13,12473,12474,12475,12477],{},"この時",[49,12476,12069],{},"に渡すデータは以下の様に定義する必要があります。",[42,12479,12482],{"className":12480,"code":12481,"language":452},[1615],"{\n    data: {\n        \u002F\u002F This is the same as the instructions object\n        crop: {\n            center: {\n                x: .5,\n                y: .5\n            },\n            flip: {\n                horizontal: false,\n                vertical: false\n            },\n            zoom: 1,\n            rotation: 0,\n            aspectRatio: null\n        }\n    }\n}\n",[49,12483,12481],{"__ignoreMap":47},[13,12485,12486,12487,11465,12489,12492,12493,12496,12497,12499],{},"このデータは",[49,12488,11259],{},[49,12490,12491],{},"open(file, instructions)","コールバックの",[49,12494,12495],{},"instructions","と同じ構造です。filepondはこの",[49,12498,12495],{},"の値を変更することで、プレビューの画像表示や実際にアップロードする画像のトリミングなどが実行されます。",[13,12501,12502,12503,12506,12507,12510],{},"そのためvue-cropperのトリミングによる座標位置や、トリミング範囲上記のデータに合わせて設定する必要があります。vue-cropperには",[49,12504,12505],{},"getCanvasData()","や",[49,12508,12509],{},"getData()","などのメソッドがあるため、それらを用いてvue-cropperのデータをコールバックを通してfilepondへ渡します。",[1566,12512,12513],{"id":12513},"計算を行う",[13,12515,12516,12517,12522,12523,12526],{},"しかしfilepondに渡すデータは結構クセがあり、cropperから取得できる情報の何をどこに当てはめればいいのかわかりません。",[2039,12518,12521],{"href":12519,"rel":12520},"https:\u002F\u002Fgithub.com\u002Fpqina\u002Ffilepond-plugin-image-edit\u002Fissues\u002F1",[2043],"github issueにてcropper.jsとの連携に関するissue","を発見し、今回はその値を用いることにします。",[49,12524,12525],{},"crop()","を以下の様に変更します。",[42,12528,12530],{"className":10507,"code":12529,"language":1434,"meta":47,"style":47},"crop(){\n    \u002F\u002F https:\u002F\u002Fgithub.com\u002Fpqina\u002Ffilepond-plugin-image-edit\u002Fissues\u002F1\n    \u002F* Constants. *\u002F\n    const canvasData = this.$refs.cropper.getCanvasData() \u002F\u002F Cropperjs method getCanvasData()\n    const cropData = this.$refs.cropper.getData() \u002F\u002F Cropperjs method getData()\n\n    \u002F* Ratio of selected crop area. *\u002F\n    const cropAreaRatio = cropData.height \u002F cropData.width\n\n    \u002F* Center point of crop area in percent. *\u002F\n    const percentX = (cropData.x + cropData.width \u002F 2) \u002F canvasData.naturalWidth\n    const percentY = (cropData.y + cropData.height \u002F 2) \u002F canvasData.naturalHeight\n\n    \u002F* Calculate available space round image center position. *\u002F\n    const cx = percentX > 0.5 ? 1 - percentX : percentX\n    const cy = percentY > 0.5 ? 1 - percentY : percentY\n\n    \u002F* Calculate image rectangle respecting space round image from crop area. *\u002F\n    let width = canvasData.naturalWidth\n    let height = width * cropAreaRatio\n    if (height > canvasData.naturalHeight) {\n    height = canvasData.naturalHeight\n    width = height \u002F cropAreaRatio\n    }\n    const rectWidth = cx * 2 * width\n    const rectHeight = cy * 2 * height\n\n    \u002F* Calculate zoom. *\u002F\n    const zoom = Math.max(rectWidth \u002F cropData.width, rectHeight \u002F cropData.height)\n    this.editor.onconfirm({\n    data: {\n        crop: {\n            center: {\n                x: percentX,\n                y: percentY\n            },\n            flip: {\n                horizontal: cropData.scaleX \u003C 0,\n                vertical: cropData.scaleY \u003C 0\n            },\n            zoom: zoom,\n            rotation: (Math.PI \u002F 180) * cropData.rotate,\n            aspectRatio: cropAreaRatio\n        }\n    }\n    })\n}\n",[49,12531,12532,12540,12545,12550,12579,12606,12610,12615,12640,12644,12649,12692,12733,12737,12742,12773,12801,12805,12810,12825,12842,12864,12877,12890,12894,12914,12934,12938,12943,12987,13001,13009,13017,13026,13037,13046,13051,13060,13080,13099,13103,13114,13147,13156,13160,13164,13171],{"__ignoreMap":47},[52,12533,12534,12536,12538],{"class":54,"line":55},[52,12535,11621],{"class":418},[52,12537,422],{"class":105},[52,12539,364],{"class":58},[52,12541,12542],{"class":54,"line":83},[52,12543,12544],{"class":411},"    \u002F\u002F https:\u002F\u002Fgithub.com\u002Fpqina\u002Ffilepond-plugin-image-edit\u002Fissues\u002F1\n",[52,12546,12547],{"class":54,"line":115},[52,12548,12549],{"class":411},"    \u002F* Constants. *\u002F\n",[52,12551,12552,12555,12558,12560,12562,12564,12566,12568,12570,12573,12576],{"class":54,"line":142},[52,12553,12554],{"class":65},"    const",[52,12556,12557],{"class":105}," canvasData",[52,12559,951],{"class":58},[52,12561,10407],{"class":58},[52,12563,12005],{"class":105},[52,12565,957],{"class":58},[52,12567,11472],{"class":105},[52,12569,957],{"class":58},[52,12571,12572],{"class":418},"getCanvasData",[52,12574,12575],{"class":62},"() ",[52,12577,12578],{"class":411},"\u002F\u002F Cropperjs method getCanvasData()\n",[52,12580,12581,12583,12586,12588,12590,12592,12594,12596,12598,12601,12603],{"class":54,"line":169},[52,12582,12554],{"class":65},[52,12584,12585],{"class":105}," cropData",[52,12587,951],{"class":58},[52,12589,10407],{"class":58},[52,12591,12005],{"class":105},[52,12593,957],{"class":58},[52,12595,11472],{"class":105},[52,12597,957],{"class":58},[52,12599,12600],{"class":418},"getData",[52,12602,12575],{"class":62},[52,12604,12605],{"class":411},"\u002F\u002F Cropperjs method getData()\n",[52,12607,12608],{"class":54,"line":302},[52,12609,341],{"emptyLinePlaceholder":340},[52,12611,12612],{"class":54,"line":308},[52,12613,12614],{"class":411},"    \u002F* Ratio of selected crop area. *\u002F\n",[52,12616,12617,12619,12622,12624,12626,12628,12630,12633,12635,12637],{"class":54,"line":318},[52,12618,12554],{"class":65},[52,12620,12621],{"class":105}," cropAreaRatio",[52,12623,951],{"class":58},[52,12625,12585],{"class":105},[52,12627,957],{"class":58},[52,12629,2587],{"class":105},[52,12631,12632],{"class":58}," \u002F",[52,12634,12585],{"class":105},[52,12636,957],{"class":58},[52,12638,12639],{"class":105},"width\n",[52,12641,12642],{"class":54,"line":328},[52,12643,341],{"emptyLinePlaceholder":340},[52,12645,12646],{"class":54,"line":337},[52,12647,12648],{"class":411},"    \u002F* Center point of crop area in percent. *\u002F\n",[52,12650,12651,12653,12656,12658,12660,12663,12665,12667,12670,12672,12674,12676,12678,12681,12683,12685,12687,12689],{"class":54,"line":344},[52,12652,12554],{"class":65},[52,12654,12655],{"class":105}," percentX",[52,12657,951],{"class":58},[52,12659,2499],{"class":62},[52,12661,12662],{"class":105},"cropData",[52,12664,957],{"class":58},[52,12666,3738],{"class":105},[52,12668,12669],{"class":58}," +",[52,12671,12585],{"class":105},[52,12673,957],{"class":58},[52,12675,2559],{"class":105},[52,12677,12632],{"class":58},[52,12679,12680],{"class":2232}," 2",[52,12682,3682],{"class":62},[52,12684,2602],{"class":58},[52,12686,12557],{"class":105},[52,12688,957],{"class":58},[52,12690,12691],{"class":105},"naturalWidth\n",[52,12693,12694,12696,12699,12701,12703,12705,12707,12710,12712,12714,12716,12718,12720,12722,12724,12726,12728,12730],{"class":54,"line":354},[52,12695,12554],{"class":65},[52,12697,12698],{"class":105}," percentY",[52,12700,951],{"class":58},[52,12702,2499],{"class":62},[52,12704,12662],{"class":105},[52,12706,957],{"class":58},[52,12708,12709],{"class":105},"y",[52,12711,12669],{"class":58},[52,12713,12585],{"class":105},[52,12715,957],{"class":58},[52,12717,2587],{"class":105},[52,12719,12632],{"class":58},[52,12721,12680],{"class":2232},[52,12723,3682],{"class":62},[52,12725,2602],{"class":58},[52,12727,12557],{"class":105},[52,12729,957],{"class":58},[52,12731,12732],{"class":105},"naturalHeight\n",[52,12734,12735],{"class":54,"line":367},[52,12736,341],{"emptyLinePlaceholder":340},[52,12738,12739],{"class":54,"line":387},[52,12740,12741],{"class":411},"    \u002F* Calculate available space round image center position. *\u002F\n",[52,12743,12744,12746,12749,12751,12753,12756,12759,12762,12764,12766,12768,12770],{"class":54,"line":415},[52,12745,12554],{"class":65},[52,12747,12748],{"class":105}," cx",[52,12750,951],{"class":58},[52,12752,12655],{"class":105},[52,12754,12755],{"class":58}," >",[52,12757,12758],{"class":2232}," 0.5",[52,12760,12761],{"class":58}," ?",[52,12763,3679],{"class":2232},[52,12765,3697],{"class":58},[52,12767,12655],{"class":105},[52,12769,6396],{"class":58},[52,12771,12772],{"class":105}," percentX\n",[52,12774,12775,12777,12780,12782,12784,12786,12788,12790,12792,12794,12796,12798],{"class":54,"line":427},[52,12776,12554],{"class":65},[52,12778,12779],{"class":105}," cy",[52,12781,951],{"class":58},[52,12783,12698],{"class":105},[52,12785,12755],{"class":58},[52,12787,12758],{"class":2232},[52,12789,12761],{"class":58},[52,12791,3679],{"class":2232},[52,12793,3697],{"class":58},[52,12795,12698],{"class":105},[52,12797,6396],{"class":58},[52,12799,12800],{"class":105}," percentY\n",[52,12802,12803],{"class":54,"line":435},[52,12804,341],{"emptyLinePlaceholder":340},[52,12806,12807],{"class":54,"line":446},[52,12808,12809],{"class":411},"    \u002F* Calculate image rectangle respecting space round image from crop area. *\u002F\n",[52,12811,12812,12814,12817,12819,12821,12823],{"class":54,"line":480},[52,12813,945],{"class":65},[52,12815,12816],{"class":105}," width",[52,12818,951],{"class":58},[52,12820,12557],{"class":105},[52,12822,957],{"class":58},[52,12824,12691],{"class":105},[52,12826,12827,12829,12832,12834,12836,12839],{"class":54,"line":509},[52,12828,945],{"class":65},[52,12830,12831],{"class":105}," height",[52,12833,951],{"class":58},[52,12835,12816],{"class":105},[52,12837,12838],{"class":58}," *",[52,12840,12841],{"class":105}," cropAreaRatio\n",[52,12843,12844,12847,12849,12851,12853,12855,12857,12860,12862],{"class":54,"line":539},[52,12845,12846],{"class":360},"    if",[52,12848,2499],{"class":62},[52,12850,2587],{"class":105},[52,12852,12755],{"class":58},[52,12854,12557],{"class":105},[52,12856,957],{"class":58},[52,12858,12859],{"class":105},"naturalHeight",[52,12861,3682],{"class":62},[52,12863,364],{"class":58},[52,12865,12866,12869,12871,12873,12875],{"class":54,"line":547},[52,12867,12868],{"class":105},"    height",[52,12870,951],{"class":58},[52,12872,12557],{"class":105},[52,12874,957],{"class":58},[52,12876,12732],{"class":105},[52,12878,12879,12882,12884,12886,12888],{"class":54,"line":553},[52,12880,12881],{"class":105},"    width",[52,12883,951],{"class":58},[52,12885,12831],{"class":105},[52,12887,12632],{"class":58},[52,12889,12841],{"class":105},[52,12891,12892],{"class":54,"line":559},[52,12893,2696],{"class":58},[52,12895,12896,12898,12901,12903,12905,12907,12909,12911],{"class":54,"line":564},[52,12897,12554],{"class":65},[52,12899,12900],{"class":105}," rectWidth",[52,12902,951],{"class":58},[52,12904,12748],{"class":105},[52,12906,12838],{"class":58},[52,12908,12680],{"class":2232},[52,12910,12838],{"class":58},[52,12912,12913],{"class":105}," width\n",[52,12915,12916,12918,12921,12923,12925,12927,12929,12931],{"class":54,"line":569},[52,12917,12554],{"class":65},[52,12919,12920],{"class":105}," rectHeight",[52,12922,951],{"class":58},[52,12924,12779],{"class":105},[52,12926,12838],{"class":58},[52,12928,12680],{"class":2232},[52,12930,12838],{"class":58},[52,12932,12933],{"class":105}," height\n",[52,12935,12936],{"class":54,"line":1106},[52,12937,341],{"emptyLinePlaceholder":340},[52,12939,12940],{"class":54,"line":1135},[52,12941,12942],{"class":411},"    \u002F* Calculate zoom. *\u002F\n",[52,12944,12945,12947,12950,12952,12955,12957,12960,12962,12965,12967,12969,12971,12973,12975,12977,12979,12981,12983,12985],{"class":54,"line":1164},[52,12946,12554],{"class":65},[52,12948,12949],{"class":105}," zoom",[52,12951,951],{"class":58},[52,12953,12954],{"class":105}," Math",[52,12956,957],{"class":58},[52,12958,12959],{"class":418},"max",[52,12961,932],{"class":62},[52,12963,12964],{"class":105},"rectWidth",[52,12966,12632],{"class":58},[52,12968,12585],{"class":105},[52,12970,957],{"class":58},[52,12972,2559],{"class":105},[52,12974,408],{"class":58},[52,12976,12920],{"class":105},[52,12978,12632],{"class":58},[52,12980,12585],{"class":105},[52,12982,957],{"class":58},[52,12984,2587],{"class":105},[52,12986,1015],{"class":62},[52,12988,12989,12991,12993,12995,12997,12999],{"class":54,"line":1193},[52,12990,1052],{"class":58},[52,12992,11259],{"class":105},[52,12994,957],{"class":58},[52,12996,12069],{"class":418},[52,12998,932],{"class":62},[52,13000,364],{"class":58},[52,13002,13003,13005,13007],{"class":54,"line":1200},[52,13004,10159],{"class":62},[52,13006,373],{"class":58},[52,13008,10138],{"class":58},[52,13010,13011,13013,13015],{"class":54,"line":1205},[52,13012,11788],{"class":62},[52,13014,373],{"class":58},[52,13016,10138],{"class":58},[52,13018,13019,13022,13024],{"class":54,"line":1210},[52,13020,13021],{"class":62},"            center",[52,13023,373],{"class":58},[52,13025,10138],{"class":58},[52,13027,13028,13031,13033,13035],{"class":54,"line":1215},[52,13029,13030],{"class":62},"                x",[52,13032,373],{"class":58},[52,13034,12655],{"class":105},[52,13036,384],{"class":58},[52,13038,13039,13042,13044],{"class":54,"line":1220},[52,13040,13041],{"class":62},"                y",[52,13043,373],{"class":58},[52,13045,12800],{"class":105},[52,13047,13048],{"class":54,"line":3800},[52,13049,13050],{"class":58},"            },\n",[52,13052,13053,13056,13058],{"class":54,"line":3821},[52,13054,13055],{"class":62},"            flip",[52,13057,373],{"class":58},[52,13059,10138],{"class":58},[52,13061,13062,13065,13067,13069,13071,13074,13076,13078],{"class":54,"line":3835},[52,13063,13064],{"class":62},"                horizontal",[52,13066,373],{"class":58},[52,13068,12585],{"class":105},[52,13070,957],{"class":58},[52,13072,13073],{"class":105},"scaleX",[52,13075,86],{"class":58},[52,13077,3333],{"class":2232},[52,13079,384],{"class":58},[52,13081,13082,13085,13087,13089,13091,13094,13096],{"class":54,"line":3840},[52,13083,13084],{"class":62},"                vertical",[52,13086,373],{"class":58},[52,13088,12585],{"class":105},[52,13090,957],{"class":58},[52,13092,13093],{"class":105},"scaleY",[52,13095,86],{"class":58},[52,13097,13098],{"class":2232}," 0\n",[52,13100,13101],{"class":54,"line":3865},[52,13102,13050],{"class":58},[52,13104,13105,13108,13110,13112],{"class":54,"line":3879},[52,13106,13107],{"class":62},"            zoom",[52,13109,373],{"class":58},[52,13111,12949],{"class":105},[52,13113,384],{"class":58},[52,13115,13116,13119,13121,13123,13125,13127,13129,13131,13134,13136,13138,13140,13142,13145],{"class":54,"line":5506},[52,13117,13118],{"class":62},"            rotation",[52,13120,373],{"class":58},[52,13122,2499],{"class":62},[52,13124,3705],{"class":105},[52,13126,957],{"class":58},[52,13128,3720],{"class":105},[52,13130,12632],{"class":58},[52,13132,13133],{"class":2232}," 180",[52,13135,3682],{"class":62},[52,13137,3702],{"class":58},[52,13139,12585],{"class":105},[52,13141,957],{"class":58},[52,13143,13144],{"class":105},"rotate",[52,13146,384],{"class":58},[52,13148,13149,13152,13154],{"class":54,"line":4},[52,13150,13151],{"class":62},"            aspectRatio",[52,13153,373],{"class":58},[52,13155,12841],{"class":105},[52,13157,13158],{"class":54,"line":5544},[52,13159,5398],{"class":58},[52,13161,13162],{"class":54,"line":5561},[52,13163,2696],{"class":58},[52,13165,13166,13169],{"class":54,"line":5566},[52,13167,13168],{"class":58},"    }",[52,13170,1015],{"class":62},[52,13172,13173],{"class":54,"line":5587},[52,13174,536],{"class":58},[17,13176,13177],{"id":13177},"アップロードを行う",[13,13179,13180],{},"これでvue-cropperのトリミング情報をfilepondに渡すことができる様になりました。実際にトリミング範囲を指定してcropボタンをクリックしますと、その位置と範囲にしたがってトリミングされた範囲がプレビューされます。",[729,13182],{":src":13183,":width":9596,":center":1323},"'filepond-crop-vue\u002Fcropped.png'",[13,13185,13186,13187,13190],{},"そして最後にアップロードボタンをクリックすると渡されたデータを元に、filepondが元画像のトリミングを行いアップロードします。トリミングしてもらうためには",[49,13188,13189],{},"FilePondPluginImageTransform","がインストールされ、有効になっている必要があります。サーバー側では１：１にトリミングされた画像を確認することができます。",[17,13192,13193],{"id":13193},"まとめ",[13,13195,13196,13197,13202],{},"filepondは主にブラウザアップロードと実際のトリミング処理を行います。そしてトリミング箇所の指定は別のライブラリを用いて実装する必要があります。filepondは",[2039,13198,13201],{"href":13199,"rel":13200},"https:\u002F\u002Fpqina.nl\u002Fpintura\u002F",[2043],"Doka Image Editor","という有償ライブラリとの互換性を第一にしているそうで、cropper.jsとの連携と特に計算が方法がわかりませんでした。ひとまず上記の方法でトリミングとアップロード機能が実装できました。後はUIを整えてあげれば完成です。",[1414,13204,13205],{},"html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html pre.shiki code .sAklC, html code.shiki .sAklC{--shiki-default:#89DDFF}html pre.shiki code .sfyAc, html code.shiki .sfyAc{--shiki-default:#C3E88D}html pre.shiki code .s0W1g, html code.shiki .s0W1g{--shiki-default:#BABED8}html pre.shiki code .sJ14y, html code.shiki .sJ14y{--shiki-default:#C792EA}html pre.shiki code .s-wAU, html code.shiki .s-wAU{--shiki-default:#F07178}html pre.shiki code .s6cf3, html code.shiki .s6cf3{--shiki-default:#89DDFF;--shiki-default-font-style:italic}html pre.shiki code .s7ZW3, html code.shiki .s7ZW3{--shiki-default:#BABED8;--shiki-default-font-style:italic}html pre.shiki code .sdLwU, html code.shiki .sdLwU{--shiki-default:#82AAFF}html pre.shiki code .sbqyR, html code.shiki .sbqyR{--shiki-default:#FF9CAC}html pre.shiki code .sC9rS, html code.shiki .sC9rS{--shiki-default:#464B5D;--shiki-default-font-style:italic}html pre.shiki code .sx098, html code.shiki .sx098{--shiki-default:#F78C6C}",{"title":47,"searchDepth":115,"depth":115,"links":13207},[13208,13209,13213,13216,13224,13225],{"id":9831,"depth":83,"text":9831},{"id":9851,"depth":83,"text":9851,"children":13210},[13211,13212],{"id":9857,"depth":115,"text":9857},{"id":9889,"depth":115,"text":9890},{"id":10007,"depth":83,"text":10008,"children":13214},[13215],{"id":10467,"depth":115,"text":10467},{"id":10765,"depth":83,"text":10766,"children":13217},[13218,13219,13220,13221],{"id":10953,"depth":115,"text":10954},{"id":11476,"depth":115,"text":11476},{"id":11888,"depth":115,"text":11888},{"id":12058,"depth":115,"text":12058,"children":13222},[13223],{"id":12513,"depth":142,"text":12513},{"id":13177,"depth":83,"text":13177},{"id":13193,"depth":83,"text":13193},[1424],"2025-12-09","Vueでブラウザでのトリミング機能とファイルアップロードを実装する",{},"\u002Farticles\u002Ffilepond-crop-vue",{"title":9578,"description":13228},"articles\u002Ffilepond-crop-vue",[1434,204],"filepond-crop-vue\u002Fthumbnail.png","an_V9C83Uuzs33hco6VOdrE9TtIqJVF5teLfyG0kaaE",{"id":13237,"title":13238,"body":13239,"category":15436,"createdAt":15437,"description":13238,"extension":1427,"index":1428,"meta":15438,"navigation":340,"path":15439,"publish":340,"seo":15440,"series":1428,"seriesTitle":1428,"stem":15441,"tag":15442,"thumbnail":15444,"updatedAt":15437,"__hash__":15445},"articles\u002Farticles\u002Fmuch-post-migration-wp.md","別CMSで作成された4万件分の大量投稿をwordpressに引越しする",{"type":10,"value":13240,"toc":15418},[13241,13244,13250,13253,13257,13260,13267,13278,13281,13288,13294,13300,13303,13306,13317,13320,13323,13326,13340,13343,13346,13353,13356,13359,13387,13390,13399,13405,13408,13411,13646,13649,13655,13658,13661,13664,13671,13677,13681,13691,13727,13733,13736,13739,13742,13852,13855,13858,13949,13968,14007,14010,14016,14085,14088,14205,14211,14214,14217,14220,14223,14229,14232,14238,14245,14251,14254,14258,14261,14264,14397,14403,14662,14669,14720,14723,14774,14784,14787,14790,14916,14930,14933,14936,14939,15084,15092,15322,15329,15343,15354,15366,15369,15373,15376,15382,15389,15392,15398,15401,15412,15415],[13,13242,13243],{},"こんにちはjunです。会社でとてつもない量のCMSのデータをwordpressに移行する計画がありました。色々と課題がある中なんとかwordpressにデータをマイグレーションして再構築できたので共有したいと思います。",[13,13245,13246,13247],{},"データが４万件もあるのでプログラム的にwordpressを操作する必要がありました。日本語で検索してもなかなかヒットしなかったので、少し苦労。 ",[1463,13248,13249],{},"しかしCLIがわかって、使う関数を把握すれば意外と簡単です。",[13,13251,13252],{},"引越しの背景から説明するので、「ささっと移行手順を見せろや！」という人は「wordpress側の構築概要」から見てください。",[17,13254,13256],{"id":13255},"背景データ移行の課題","背景＆データ移行の課題",[13,13258,13259],{},"2006年に構築されたサイトを弊社が受け持っており、その環境が古くなってきたので移行することになりました。PHP5、centos6というレガシーな環境で動いており非常に危なっかしい上に、ろくに保守もされてないのでページングとか表示もおかしい部分も出てきました。",[13,13261,13262,13263,13266],{},"移行するサイトはwordpressではない ",[1463,13264,13265],{},"別のCMSで構築されており、移行の際にはDBからデータを一回ダンプして加工してwordpressにマイグレーションをする必要がありました。"," しかしそのデータは",[1467,13268,13269,13272,13275],{},[1470,13270,13271],{},"投稿４万件",[1470,13273,13274],{},"ユーザー8200人",[1470,13276,13277],{},"カテゴリー90件",[13,13279,13280],{},"というデータが膨大であり必然的にプログラム的にデータを移行する必要があります。とりあえず担当の方と移行するデータを精査しました。元々はコミュニティサイトとして使用していたのでユーザーが非常に多く、移行すべきアクティブなユーザーは100人程度だったのでユーザーはかなり減りました。しかし投稿は全部移行でした（泣",[13,13282,13283,13284,13287],{},"旧CMSでの「カテゴリー」はブログの種類に当てはまりました。ブログを管理、投稿する部署が異なっていることが判明し、投稿データにも ",[49,13285,13286],{},"categoruid"," の様に区別するカラムがありました。さらに言えばユーザー情報にも紐づいています笑。",[13,13289,13290,13293],{},[1463,13291,13292],{},"投稿データは旧CMSで結び付けられたユーザーID、カテゴリーIDの関係性を維持しながら移行する必要があります。"," さらに投稿データには",[42,13295,13298],{"className":13296,"code":13297,"language":452},[1615],"[img]http:\u002F\u002F~~~~~[\u002Fimg]\n",[49,13299,13297],{"__ignoreMap":47},[13,13301,13302],{},"という様なそのCMS独自のタグが存在したので、wordpressに移行する前に正規表現で置換する必要がありました。（今回はその解説はしません。別途の記事で）",[13,13304,13305],{},"まとめると",[1467,13307,13308,13311,13314],{},[1470,13309,13310],{},"データ数が膨大。",[1470,13312,13313],{},"記事はカテゴリー、ユーザーとの関係性を維持する。",[1470,13315,13316],{},"記事データの独自記法をwordpress用に置換または削除する。",[13,13318,13319],{},"という課題がありました。",[17,13321,13322],{"id":13322},"wordpress側の構築概要",[13,13324,13325],{},"今回の移行手順としては前準備に",[1687,13327,13328,13331,13334,13337],{},[1470,13329,13330],{},"旧CMSからデータをダンプ(mysql）してローカルに入れておく。",[1470,13332,13333],{},"データ構成をよく観察する。",[1470,13335,13336],{},"必要なデータをSQLを用いて取得、JSONで取得",[1470,13338,13339],{},"JSONを元にPHPスクリプトでデータを加工",[13,13341,13342],{},"この様にデータの加工をしてwordpressに入れ込む準備をしました。加工済みデータJSONとwordpressの関数を用いてこれらのデータをwordpressに移行しました。",[13,13344,13345],{},"移行の特に厄介だったのが旧CMSでは部署ごとにカテゴリーという名前でブログ種が分けられていたことです。wordpressのカテゴリーとは別の概念です。さらに管理ユーザーもそのカテゴリーで区別されていました。",[13,13347,13348,13349,13352],{},"そこで今回は ",[1463,13350,13351],{},"wordpressをマルチサイト構成にして構築しました。"," wordpressには一つのwordpressシステムを用いて複数の異なるブログを構築する機能があります。詳しくはこちらの公式を参照。マルチサイトにすることで複数のブログに分け、さらにそのブログごとにユーザーの割当が可能になります。",[17,13354,13355],{"id":13355},"引越し手順",[13,13357,13358],{},"手順としては以下の通りです。",[1687,13360,13361,13363,13365,13367,13369,13372,13375,13378,13381,13384],{},[1470,13362,13330],{},[1470,13364,13333],{},[1470,13366,13336],{},[1470,13368,13339],{},[1470,13370,13371],{},"wordpressプロジェクト内に上記のデータを移行、挿入用PHPスクリプトを作成",[1470,13373,13374],{},"マルチサイト 構成をONにしてブログネットワークを機械的に作成",[1470,13376,13377],{},"ユーザーを作成して適切なブログネットワークに割り当てる。",[1470,13379,13380],{},"投稿データを対応するユーザーとブログネットワークに割り当てる。",[1470,13382,13383],{},"画像などを移行する。（今回はやりません）",[1470,13385,13386],{},"全てのブログネットワークに共通のテーマを設定する。",[17,13388,13389],{"id":13389},"dockerで検証環境を構築",[13,13391,13392,13393,13398],{},"まずはローカルでの検証環境を作りましょう。失敗するとDBが結構汚れるのですぐにリセットできるdockerを用います。",[2039,13394,13397],{"href":13395,"rel":13396},"https:\u002F\u002Fhub.docker.com\u002F_\u002Fwordpress",[2043],"wordpress公式のdockerHub","の通りにすれば簡単に構築できます。ディレクトリ構成は以下の通りです。",[42,13400,13403],{"className":13401,"code":13402,"language":452},[1615],"docker-wordpress\u002F\n|\n|-scrips\u002F\n|-docker-compose.yml\n",[49,13404,13402],{"__ignoreMap":47},[13,13406,13407],{},"scripts\u002F にはwordpressに挿入するためのPHPスクリプトを入れておくためのディレクトリです。最終的にこのwordpress dockerコンテナの中に入って、このスクリプトをコマンドで実行します。",[13,13409,13410],{},"docker-compose.yml は以下の通りです。（ほとんど公式と同じ。一部改修",[42,13412,13414],{"className":6173,"code":13413,"filename":7810,"language":6175,"meta":47,"style":47},"version: '3.1'\n\nservices:\n\n  wordpress:\n    image: wordpress\n    restart: always\n    ports:\n      - 8080:80\n    environment:\n      WORDPRESS_DB_HOST: db\n      WORDPRESS_DB_USER: exampleuser\n      WORDPRESS_DB_PASSWORD: examplepass\n      WORDPRESS_DB_NAME: exampledb\n    volumes:\n      - .\u002Fscripts:\u002Fvar\u002Fwww\u002Fhtml\u002Fscripts\n\n  db:\n    image: mysql:5.7\n    restart: always\n    environment:\n      MYSQL_DATABASE: exampledb\n      MYSQL_USER: exampleuser\n      MYSQL_PASSWORD: examplepass\n      MYSQL_RANDOM_ROOT_PASSWORD: '1'\n    volumes:\n      - db_wp:\u002Fvar\u002Flib\u002Fmysql\n\nvolumes:\n  .\u002Fscripts:\n  db_wp:\n",[49,13415,13416,13429,13433,13439,13443,13450,13459,13469,13475,13482,13488,13497,13507,13517,13527,13533,13540,13544,13550,13558,13566,13572,13580,13588,13596,13609,13615,13622,13626,13632,13639],{"__ignoreMap":47},[52,13417,13418,13420,13422,13424,13427],{"class":54,"line":55},[52,13419,7817],{"class":62},[52,13421,373],{"class":58},[52,13423,6309],{"class":58},[52,13425,13426],{"class":75},"3.1",[52,13428,6315],{"class":58},[52,13430,13431],{"class":54,"line":83},[52,13432,341],{"emptyLinePlaceholder":340},[52,13434,13435,13437],{"class":54,"line":115},[52,13436,7830],{"class":62},[52,13438,6220],{"class":58},[52,13440,13441],{"class":54,"line":142},[52,13442,341],{"emptyLinePlaceholder":340},[52,13444,13445,13448],{"class":54,"line":169},[52,13446,13447],{"class":62},"  wordpress",[52,13449,6220],{"class":58},[52,13451,13452,13454,13456],{"class":54,"line":302},[52,13453,2534],{"class":62},[52,13455,373],{"class":58},[52,13457,13458],{"class":75}," wordpress\n",[52,13460,13461,13464,13466],{"class":54,"line":308},[52,13462,13463],{"class":62},"    restart",[52,13465,373],{"class":58},[52,13467,13468],{"class":75}," always\n",[52,13470,13471,13473],{"class":54,"line":318},[52,13472,7895],{"class":62},[52,13474,6220],{"class":58},[52,13476,13477,13479],{"class":54,"line":328},[52,13478,7864],{"class":58},[52,13480,13481],{"class":75}," 8080:80\n",[52,13483,13484,13486],{"class":54,"line":337},[52,13485,7962],{"class":62},[52,13487,6220],{"class":58},[52,13489,13490,13493,13495],{"class":54,"line":344},[52,13491,13492],{"class":62},"      WORDPRESS_DB_HOST",[52,13494,373],{"class":58},[52,13496,7867],{"class":75},[52,13498,13499,13502,13504],{"class":54,"line":354},[52,13500,13501],{"class":62},"      WORDPRESS_DB_USER",[52,13503,373],{"class":58},[52,13505,13506],{"class":75}," exampleuser\n",[52,13508,13509,13512,13514],{"class":54,"line":367},[52,13510,13511],{"class":62},"      WORDPRESS_DB_PASSWORD",[52,13513,373],{"class":58},[52,13515,13516],{"class":75}," examplepass\n",[52,13518,13519,13522,13524],{"class":54,"line":387},[52,13520,13521],{"class":62},"      WORDPRESS_DB_NAME",[52,13523,373],{"class":58},[52,13525,13526],{"class":75}," exampledb\n",[52,13528,13529,13531],{"class":54,"line":415},[52,13530,7872],{"class":62},[52,13532,6220],{"class":58},[52,13534,13535,13537],{"class":54,"line":427},[52,13536,7864],{"class":58},[52,13538,13539],{"class":75}," .\u002Fscripts:\u002Fvar\u002Fwww\u002Fhtml\u002Fscripts\n",[52,13541,13542],{"class":54,"line":435},[52,13543,341],{"emptyLinePlaceholder":340},[52,13545,13546,13548],{"class":54,"line":446},[52,13547,7946],{"class":62},[52,13549,6220],{"class":58},[52,13551,13552,13554,13556],{"class":54,"line":480},[52,13553,2534],{"class":62},[52,13555,373],{"class":58},[52,13557,7957],{"class":75},[52,13559,13560,13562,13564],{"class":54,"line":509},[52,13561,13463],{"class":62},[52,13563,373],{"class":58},[52,13565,13468],{"class":75},[52,13567,13568,13570],{"class":54,"line":539},[52,13569,7962],{"class":62},[52,13571,6220],{"class":58},[52,13573,13574,13576,13578],{"class":54,"line":547},[52,13575,7969],{"class":62},[52,13577,373],{"class":58},[52,13579,13526],{"class":75},[52,13581,13582,13584,13586],{"class":54,"line":553},[52,13583,7979],{"class":62},[52,13585,373],{"class":58},[52,13587,13506],{"class":75},[52,13589,13590,13592,13594],{"class":54,"line":559},[52,13591,7989],{"class":62},[52,13593,373],{"class":58},[52,13595,13516],{"class":75},[52,13597,13598,13601,13603,13605,13607],{"class":54,"line":564},[52,13599,13600],{"class":62},"      MYSQL_RANDOM_ROOT_PASSWORD",[52,13602,373],{"class":58},[52,13604,6309],{"class":58},[52,13606,31],{"class":75},[52,13608,6315],{"class":58},[52,13610,13611,13613],{"class":54,"line":569},[52,13612,7872],{"class":62},[52,13614,6220],{"class":58},[52,13616,13617,13619],{"class":54,"line":1106},[52,13618,7864],{"class":58},[52,13620,13621],{"class":75}," db_wp:\u002Fvar\u002Flib\u002Fmysql\n",[52,13623,13624],{"class":54,"line":1135},[52,13625,341],{"emptyLinePlaceholder":340},[52,13627,13628,13630],{"class":54,"line":1164},[52,13629,8043],{"class":62},[52,13631,6220],{"class":58},[52,13633,13634,13637],{"class":54,"line":1193},[52,13635,13636],{"class":62},"  .\u002Fscripts",[52,13638,6220],{"class":58},[52,13640,13641,13644],{"class":54,"line":1200},[52,13642,13643],{"class":62},"  db_wp",[52,13645,6220],{"class":58},[13,13647,13648],{},"DBは永続化して、そしてスクリプトもローカルで編集してすぐに実行できる様にボリュームにマウントしておきます。これで準備完了です。",[42,13650,13653],{"className":13651,"code":13652,"language":452},[1615],"jun@MacBook-Pro docker-wordpress % docker-compose up -d\n",[49,13654,13652],{"__ignoreMap":47},[13,13656,13657],{},"ブラウザを開いてlocalhost:8080にアクセスするとwordpressのインストール画面が開きます。DBの設定などは済んでいるので、初期ユーザーの設定だけで終わります。",[17,13659,13660],{"id":13660},"マルチサイトを機械的に作成",[1499,13662,13663],{"id":13663},"マルチサイトの有効化",[13,13665,13666,13667,13670],{},"それではまず旧CMSのカテゴリーにあたる、マルチサイトを機械的に作成しましょう。その前に",[49,13668,13669],{},"wp-config.php","でやることがあります。以下の様なコードを追記してマルチサイト化を有効にします。",[42,13672,13675],{"className":13673,"code":13674,"language":452},[1615],"define('WP_ALLOW_MULTISITE', true);\n",[49,13676,13674],{"__ignoreMap":47},[729,13678],{":src":13679,":width":13680,":center":1323},"'_mix\u002Fwpml-197x300.png'","'300px'",[13,13682,13683,13684,13686,13687,13690],{},"有効にすると「ツール」から「サイトネットワークの設置」というメニューが出現します。これをクリックしてサイトネットワークの設定を行います。そして新しくコードを追記しろと言われるので以下の様に",[49,13685,13669],{}," と ",[49,13688,13689],{},".htaccess","に追記します。",[42,13692,13695],{"className":13693,"code":13694,"filename":13669,"language":8577,"meta":47,"style":47},"language-php shiki shiki-themes material-theme-ocean","define('MULTISITE', true);\ndefine('SUBDOMAIN_INSTALL', false);\ndefine('DOMAIN_CURRENT_SITE', 'localhost');\ndefine('PATH_CURRENT_SITE', '\u002F');\ndefine('SITE_ID_CURRENT_SITE', 1);\ndefine('BLOG_ID_CURRENT_SITE', 1);\n",[49,13696,13697,13702,13707,13712,13717,13722],{"__ignoreMap":47},[52,13698,13699],{"class":54,"line":55},[52,13700,13701],{},"define('MULTISITE', true);\n",[52,13703,13704],{"class":54,"line":83},[52,13705,13706],{},"define('SUBDOMAIN_INSTALL', false);\n",[52,13708,13709],{"class":54,"line":115},[52,13710,13711],{},"define('DOMAIN_CURRENT_SITE', 'localhost');\n",[52,13713,13714],{"class":54,"line":142},[52,13715,13716],{},"define('PATH_CURRENT_SITE', '\u002F');\n",[52,13718,13719],{"class":54,"line":169},[52,13720,13721],{},"define('SITE_ID_CURRENT_SITE', 1);\n",[52,13723,13724],{"class":54,"line":302},[52,13725,13726],{},"define('BLOG_ID_CURRENT_SITE', 1);\n",[42,13728,13731],{"className":13729,"code":13730,"filename":13689,"language":452,"meta":47},[1615],"RewriteEngine On\nRewriteBase \u002F\nRewriteRule ^index\\.php$ - [L]\n\n# add a trailing slash to \u002Fwp-admin\nRewriteRule ^([_0-9a-zA-Z-]+\u002F)?wp-admin$ $1wp-admin\u002F [R=301,L]\n\nRewriteCond %{REQUEST_FILENAME} -f [OR]\nRewriteCond %{REQUEST_FILENAME} -d\nRewriteRule ^ - [L]\nRewriteRule ^([_0-9a-zA-Z-]+\u002F)?(wp-(content|admin|includes).*) $2 [L]\nRewriteRule ^([_0-9a-zA-Z-]+\u002F)?(.*\\.php)$ $2 [L]\nRewriteRule . index.php [L]\n",[49,13732,13730],{"__ignoreMap":47},[13,13734,13735],{},"なお、今回はドメイン別ではなくサブディレクトリ形式のマルチサイト とします。",[1499,13737,13738],{"id":13738},"サイト作成プログラム",[13,13740,13741],{},"移行元のデータはすでにJSONにしてあります。以下の様な構成とします。",[42,13743,13746],{"className":9608,"code":13744,"filename":13745,"language":9610,"meta":47,"style":47},"[\n  ...\n  {\n    \"id\":\"6\",\n    \"name\":\"サイトの名前\",\n    \"sub_title\":\"サイトのキャッチフレーズ的なもの\",\n    \"mailUser\":\"3::1543\"\n  },\n  ...\n]\n","article_category.json",[49,13747,13748,13752,13757,13762,13782,13801,13821,13839,13844,13848],{"__ignoreMap":47},[52,13749,13750],{"class":54,"line":55},[52,13751,443],{"class":58},[52,13753,13754],{"class":54,"line":83},[52,13755,13756],{"class":105},"  ...\n",[52,13758,13759],{"class":54,"line":115},[52,13760,13761],{"class":58},"  {\n",[52,13763,13764,13766,13769,13771,13773,13775,13778,13780],{"class":54,"line":142},[52,13765,9631],{"class":58},[52,13767,13768],{"class":65},"id",[52,13770,72],{"class":58},[52,13772,373],{"class":58},[52,13774,72],{"class":58},[52,13776,13777],{"class":75},"6",[52,13779,72],{"class":58},[52,13781,384],{"class":58},[52,13783,13784,13786,13788,13790,13792,13794,13797,13799],{"class":54,"line":169},[52,13785,9631],{"class":58},[52,13787,6182],{"class":65},[52,13789,72],{"class":58},[52,13791,373],{"class":58},[52,13793,72],{"class":58},[52,13795,13796],{"class":75},"サイトの名前",[52,13798,72],{"class":58},[52,13800,384],{"class":58},[52,13802,13803,13805,13808,13810,13812,13814,13817,13819],{"class":54,"line":302},[52,13804,9631],{"class":58},[52,13806,13807],{"class":65},"sub_title",[52,13809,72],{"class":58},[52,13811,373],{"class":58},[52,13813,72],{"class":58},[52,13815,13816],{"class":75},"サイトのキャッチフレーズ的なもの",[52,13818,72],{"class":58},[52,13820,384],{"class":58},[52,13822,13823,13825,13828,13830,13832,13834,13837],{"class":54,"line":308},[52,13824,9631],{"class":58},[52,13826,13827],{"class":65},"mailUser",[52,13829,72],{"class":58},[52,13831,373],{"class":58},[52,13833,72],{"class":58},[52,13835,13836],{"class":75},"3::1543",[52,13838,266],{"class":58},[52,13840,13841],{"class":54,"line":318},[52,13842,13843],{"class":58},"  },\n",[52,13845,13846],{"class":54,"line":328},[52,13847,13756],{"class":105},[52,13849,13850],{"class":54,"line":337},[52,13851,6730],{"class":58},[13,13853,13854],{},"旧CMSからはこの様になっており、mailUserがこのカテゴリー（ブログ）を管理するユーザーです。wordpressのマルチサイトを作るには、初期管理ユーザーとサイト名があればとりあえず作れます。",[13,13856,13857],{},"そして追加スクリプトは以下の通りです。",[42,13859,13862],{"className":13693,"code":13860,"filename":13861,"language":8577,"meta":47,"style":47},"\u003C?php\nrequire_once('..\u002F..\u002Fwp-load.php');\n\n$json = file_get_contents('\u002Fvar\u002Fwww\u002Fhtml\u002Fscripts\u002Fcategory\u002Farticle_categpry.json');\n$cats = json_decode($json,true);\n$new_id = 2;\nforeach($cats as $key=>&$val){\n    wpmu_create_blog('localhost','blog'.$key,$val['name'],1,array('blogdescription'=>$val['sub_title']));\n    \n    if( is_wp_error( $return ) ) {\n        print_r($return->get_error_message().\"\\n\");\n        continue;\n    }\n    \n    $val['new_id']=$new_id;\n    $new_id++;\n}\nfile_put_contents(\"\u002Fvar\u002Fwww\u002Fhtml\u002Fscripts\u002Fcategory\u002Fregistered_article_categpry.json\", json_encode($cats,JSON_UNESCAPED_UNICODE));\n","\u002Fscripts\u002Fcreate_site.php",[49,13863,13864,13869,13874,13878,13883,13888,13893,13898,13903,13907,13912,13917,13922,13926,13930,13935,13940,13944],{"__ignoreMap":47},[52,13865,13866],{"class":54,"line":55},[52,13867,13868],{},"\u003C?php\n",[52,13870,13871],{"class":54,"line":83},[52,13872,13873],{},"require_once('..\u002F..\u002Fwp-load.php');\n",[52,13875,13876],{"class":54,"line":115},[52,13877,341],{"emptyLinePlaceholder":340},[52,13879,13880],{"class":54,"line":142},[52,13881,13882],{},"$json = file_get_contents('\u002Fvar\u002Fwww\u002Fhtml\u002Fscripts\u002Fcategory\u002Farticle_categpry.json');\n",[52,13884,13885],{"class":54,"line":169},[52,13886,13887],{},"$cats = json_decode($json,true);\n",[52,13889,13890],{"class":54,"line":302},[52,13891,13892],{},"$new_id = 2;\n",[52,13894,13895],{"class":54,"line":308},[52,13896,13897],{},"foreach($cats as $key=>&$val){\n",[52,13899,13900],{"class":54,"line":318},[52,13901,13902],{},"    wpmu_create_blog('localhost','blog'.$key,$val['name'],1,array('blogdescription'=>$val['sub_title']));\n",[52,13904,13905],{"class":54,"line":328},[52,13906,3235],{},[52,13908,13909],{"class":54,"line":337},[52,13910,13911],{},"    if( is_wp_error( $return ) ) {\n",[52,13913,13914],{"class":54,"line":344},[52,13915,13916],{},"        print_r($return->get_error_message().\"\\n\");\n",[52,13918,13919],{"class":54,"line":354},[52,13920,13921],{},"        continue;\n",[52,13923,13924],{"class":54,"line":367},[52,13925,2696],{},[52,13927,13928],{"class":54,"line":387},[52,13929,3235],{},[52,13931,13932],{"class":54,"line":415},[52,13933,13934],{},"    $val['new_id']=$new_id;\n",[52,13936,13937],{"class":54,"line":427},[52,13938,13939],{},"    $new_id++;\n",[52,13941,13942],{"class":54,"line":435},[52,13943,536],{},[52,13945,13946],{"class":54,"line":446},[52,13947,13948],{},"file_put_contents(\"\u002Fvar\u002Fwww\u002Fhtml\u002Fscripts\u002Fcategory\u002Fregistered_article_categpry.json\", json_encode($cats,JSON_UNESCAPED_UNICODE));\n",[13,13950,13951,13954,13955,13957,13958,13961,13962],{},[49,13952,13953],{},"wpmu_create_blog"," という関数を用いて作成します。",[49,13956,13953],{}," を使用するためには上記のコード二行目にある ",[49,13959,13960],{},"require_once('..\u002F..\u002Fwp-load.php');"," が必要になります。 ",[1463,13963,13964,13967],{},[49,13965,13966],{},"wp-load.php"," を使用することでwordpress関数が使用できる様になります。引数は以下の様に取ります。",[42,13969,13971],{"className":13693,"code":13970,"language":8577,"meta":47,"style":47},"wpmu_create_blog(\n  ブログのドメイン（必須）,\n  ブログのサブディレクトリ名（必須）,\n  サイト名（必須）,\n  管理ユーザーID（必須）,\n  そのほかの情報（配列）,\n)\n",[49,13972,13973,13978,13983,13988,13993,13998,14003],{"__ignoreMap":47},[52,13974,13975],{"class":54,"line":55},[52,13976,13977],{},"wpmu_create_blog(\n",[52,13979,13980],{"class":54,"line":83},[52,13981,13982],{},"  ブログのドメイン（必須）,\n",[52,13984,13985],{"class":54,"line":115},[52,13986,13987],{},"  ブログのサブディレクトリ名（必須）,\n",[52,13989,13990],{"class":54,"line":142},[52,13991,13992],{},"  サイト名（必須）,\n",[52,13994,13995],{"class":54,"line":169},[52,13996,13997],{},"  管理ユーザーID（必須）,\n",[52,13999,14000],{"class":54,"line":302},[52,14001,14002],{},"  そのほかの情報（配列）,\n",[52,14004,14005],{"class":54,"line":308},[52,14006,1015],{},[13,14008,14009],{},"上記スクリプトは非常に単純です。JSONにある旧CMSに登録されたブログカテゴリー分だけforeachで回して関数を実行しているだけです。",[13,14011,14012,14013],{},"しかしブログカテゴリーは後でユーザーと投稿を挿入する際に必要となるので、 ",[1463,14014,14015],{},"「wordpressでのブログIDと以前のブログカテゴリーIDを対応させる」様にしておきます。下記の様に工夫をしておきます。",[42,14017,14019],{"className":13693,"code":14018,"language":8577,"meta":47,"style":47},"$new_id = 2; \u002F\u002F 新しいブログIDの最初の値\nforeach($cats as $key=>&$val){ \u002F\u002F参照渡しをしておく\n    wpmu_create_blog('localhost','blog'.$key,$val['name'],1,array('blogdescription'=>$val['sub_title']));\n    \n    if( is_wp_error( $return ) ) {\n        print_r($return->get_error_message().\"\\n\");\n        continue;\n    }\n    \u002F\u002F エラーが起きなかったら new_id という新しいカラムと共にwordpressのブログIDを記録\n    $val['new_id']=$new_id;\n    $new_id++;\n}\n\n\u002F\u002F wordpressと旧CMSとの関係性を記録したJSONを出力\nfile_put_contents(\"\u002Fvar\u002Fwww\u002Fhtml\u002Fscripts\u002Fcategory\u002Fregistered_article_categpry.json\", json_encode($cats,JSON_UNESCAPED_UNICODE));\n",[49,14020,14021,14026,14031,14035,14039,14043,14047,14051,14055,14060,14064,14068,14072,14076,14081],{"__ignoreMap":47},[52,14022,14023],{"class":54,"line":55},[52,14024,14025],{},"$new_id = 2; \u002F\u002F 新しいブログIDの最初の値\n",[52,14027,14028],{"class":54,"line":83},[52,14029,14030],{},"foreach($cats as $key=>&$val){ \u002F\u002F参照渡しをしておく\n",[52,14032,14033],{"class":54,"line":115},[52,14034,13902],{},[52,14036,14037],{"class":54,"line":142},[52,14038,3235],{},[52,14040,14041],{"class":54,"line":169},[52,14042,13911],{},[52,14044,14045],{"class":54,"line":302},[52,14046,13916],{},[52,14048,14049],{"class":54,"line":308},[52,14050,13921],{},[52,14052,14053],{"class":54,"line":318},[52,14054,2696],{},[52,14056,14057],{"class":54,"line":328},[52,14058,14059],{},"    \u002F\u002F エラーが起きなかったら new_id という新しいカラムと共にwordpressのブログIDを記録\n",[52,14061,14062],{"class":54,"line":337},[52,14063,13934],{},[52,14065,14066],{"class":54,"line":344},[52,14067,13939],{},[52,14069,14070],{"class":54,"line":354},[52,14071,536],{},[52,14073,14074],{"class":54,"line":367},[52,14075,341],{"emptyLinePlaceholder":340},[52,14077,14078],{"class":54,"line":387},[52,14079,14080],{},"\u002F\u002F wordpressと旧CMSとの関係性を記録したJSONを出力\n",[52,14082,14083],{"class":54,"line":415},[52,14084,13948],{},[13,14086,14087],{},"すると新しく作成されたJSONを見ると",[42,14089,14092],{"className":9608,"code":14090,"filename":14091,"language":9610,"meta":47,"style":47},"[\n...\n  {\n    \"id\":\"6\",\n    \"name\":\"サイトの名前\",\n    \"sub_title\":\"サイトのキャッチフレーズ的なもの\",\n    \"mailUser\":\"3::1543\",\n    \"new_id\":3\n  }\n...\n]\n","registered_article_category.json",[49,14093,14094,14098,14103,14107,14125,14143,14161,14179,14193,14197,14201],{"__ignoreMap":47},[52,14095,14096],{"class":54,"line":55},[52,14097,443],{"class":58},[52,14099,14100],{"class":54,"line":83},[52,14101,14102],{"class":105},"...\n",[52,14104,14105],{"class":54,"line":115},[52,14106,13761],{"class":58},[52,14108,14109,14111,14113,14115,14117,14119,14121,14123],{"class":54,"line":142},[52,14110,9631],{"class":58},[52,14112,13768],{"class":65},[52,14114,72],{"class":58},[52,14116,373],{"class":58},[52,14118,72],{"class":58},[52,14120,13777],{"class":75},[52,14122,72],{"class":58},[52,14124,384],{"class":58},[52,14126,14127,14129,14131,14133,14135,14137,14139,14141],{"class":54,"line":169},[52,14128,9631],{"class":58},[52,14130,6182],{"class":65},[52,14132,72],{"class":58},[52,14134,373],{"class":58},[52,14136,72],{"class":58},[52,14138,13796],{"class":75},[52,14140,72],{"class":58},[52,14142,384],{"class":58},[52,14144,14145,14147,14149,14151,14153,14155,14157,14159],{"class":54,"line":302},[52,14146,9631],{"class":58},[52,14148,13807],{"class":65},[52,14150,72],{"class":58},[52,14152,373],{"class":58},[52,14154,72],{"class":58},[52,14156,13816],{"class":75},[52,14158,72],{"class":58},[52,14160,384],{"class":58},[52,14162,14163,14165,14167,14169,14171,14173,14175,14177],{"class":54,"line":308},[52,14164,9631],{"class":58},[52,14166,13827],{"class":65},[52,14168,72],{"class":58},[52,14170,373],{"class":58},[52,14172,72],{"class":58},[52,14174,13836],{"class":75},[52,14176,72],{"class":58},[52,14178,384],{"class":58},[52,14180,14181,14183,14186,14188,14190],{"class":54,"line":318},[52,14182,9631],{"class":58},[52,14184,14185],{"class":65},"new_id",[52,14187,72],{"class":58},[52,14189,373],{"class":58},[52,14191,14192],{"class":2232},"3\n",[52,14194,14195],{"class":54,"line":328},[52,14196,1076],{"class":58},[52,14198,14199],{"class":54,"line":337},[52,14200,14102],{"class":105},[52,14202,14203],{"class":54,"line":344},[52,14204,6730],{"class":58},[13,14206,14207,14208],{},"new_idというカラムとwordpressでのブログIDが入りました。こうすることで ",[1463,14209,14210],{},"「旧CMSでのブログカテゴリーID 6のものはwordpressではブログID 3」という関係性を保存できます。",[13,14212,14213],{},"以上でブログカテゴリーの移行は終了しました。訳70サイトもあるのでこんな感じ↓になります笑。",[729,14215],{":src":14216,":width":13680,":center":1323},"'_mix\u002Fwp-sites-146x300.jpeg'",[1499,14218,14219],{"id":14219},"スクリプトの実行方法",[13,14221,14222],{},"これらスクリプトはコマンドで実行します。dockerで管理しているので",[42,14224,14227],{"className":14225,"code":14226,"language":452},[1615],"docker exec -it {wordpressのコンテナ名} \u002Fbin\u002Fbash\n",[49,14228,14226],{"__ignoreMap":47},[13,14230,14231],{},"この様にしてwordpressを立ち上げているコンテナに入って、コマンドを実行しにいきます。",[42,14233,14236],{"className":14234,"code":14235,"language":452},[1615],"root@0756d76ddde1:\u002Fvar\u002Fwww\u002Fhtml# \n",[49,14237,14235],{"__ignoreMap":47},[13,14239,14240,14241,14244],{},"コンテナに入るとこの様にターミナルが変化します。リモートサーバーにsshでログインしたみたいな感じです。そしてdockerを立ち上げるときに ",[49,14242,14243],{},"scripts\u002F"," ディレクトリをボリュームしているのでそこに移動します。",[42,14246,14249],{"className":14247,"code":14248,"language":452},[1615],"root@0756d76ddde1:\u002Fvar\u002Fwww\u002Fhtml# cd scripts\nroot@0756d76ddde1:\u002Fvar\u002Fwww\u002Fhtml\u002Fscripts# ls\ncreate_site.php\narticle_category.json\n\nroot@0756d76ddde1:\u002Fvar\u002Fwww\u002Fhtml\u002Fscripts# php create_site.php\n",[49,14250,14248],{"__ignoreMap":47},[13,14252,14253],{},"上記の様にphpファイルを指定することでスクリプトが実行されます。",[17,14255,14257],{"id":14256},"ユーザーを機械的に作成割り当て","ユーザーを機械的に作成、割り当て",[1499,14259,14260],{"id":14260},"ユーザーの追加",[13,14262,14263],{},"ではそれぞれのブログを作成したので次はユーザーを作成していきます。ユーザーは以下の様なJSONです。",[42,14265,14268],{"className":9608,"code":14266,"filename":14267,"language":9610,"meta":47,"style":47},"[\n    ...\n    {\n        \"uid\":\"3\",\n        \"loginname\":\"webmaster\",\n        \"name\":\"お名前\",\n        \"email\":\"example@example.com\",\n        \"user_avatar\":\n        \"thumbnail.jpg\",\n    }\n    ...\n]\n","user.json",[49,14269,14270,14274,14279,14284,14304,14324,14343,14363,14374,14385,14389,14393],{"__ignoreMap":47},[52,14271,14272],{"class":54,"line":55},[52,14273,443],{"class":58},[52,14275,14276],{"class":54,"line":83},[52,14277,14278],{"class":105},"    ...\n",[52,14280,14281],{"class":54,"line":115},[52,14282,14283],{"class":58},"    {\n",[52,14285,14286,14289,14292,14294,14296,14298,14300,14302],{"class":54,"line":142},[52,14287,14288],{"class":58},"        \"",[52,14290,14291],{"class":65},"uid",[52,14293,72],{"class":58},[52,14295,373],{"class":58},[52,14297,72],{"class":58},[52,14299,39],{"class":75},[52,14301,72],{"class":58},[52,14303,384],{"class":58},[52,14305,14306,14308,14311,14313,14315,14317,14320,14322],{"class":54,"line":169},[52,14307,14288],{"class":58},[52,14309,14310],{"class":65},"loginname",[52,14312,72],{"class":58},[52,14314,373],{"class":58},[52,14316,72],{"class":58},[52,14318,14319],{"class":75},"webmaster",[52,14321,72],{"class":58},[52,14323,384],{"class":58},[52,14325,14326,14328,14330,14332,14334,14336,14339,14341],{"class":54,"line":302},[52,14327,14288],{"class":58},[52,14329,6182],{"class":65},[52,14331,72],{"class":58},[52,14333,373],{"class":58},[52,14335,72],{"class":58},[52,14337,14338],{"class":75},"お名前",[52,14340,72],{"class":58},[52,14342,384],{"class":58},[52,14344,14345,14347,14350,14352,14354,14356,14359,14361],{"class":54,"line":308},[52,14346,14288],{"class":58},[52,14348,14349],{"class":65},"email",[52,14351,72],{"class":58},[52,14353,373],{"class":58},[52,14355,72],{"class":58},[52,14357,14358],{"class":75},"example@example.com",[52,14360,72],{"class":58},[52,14362,384],{"class":58},[52,14364,14365,14367,14370,14372],{"class":54,"line":318},[52,14366,14288],{"class":58},[52,14368,14369],{"class":65},"user_avatar",[52,14371,72],{"class":58},[52,14373,6220],{"class":58},[52,14375,14376,14378,14381,14383],{"class":54,"line":328},[52,14377,14288],{"class":58},[52,14379,14380],{"class":75},"thumbnail.jpg",[52,14382,72],{"class":58},[52,14384,384],{"class":58},[52,14386,14387],{"class":54,"line":337},[52,14388,2696],{"class":58},[52,14390,14391],{"class":54,"line":344},[52,14392,14278],{"class":105},[52,14394,14395],{"class":54,"line":354},[52,14396,6730],{"class":58},[13,14398,14399,14402],{},[49,14400,14401],{},"groupid"," は旧CMSの権限グループです。そしてユーザー追加スクリプトは以下の様になります。",[42,14404,14406],{"className":13693,"code":14405,"language":8577,"meta":47,"style":47},"\u003C?php\nrequire_once('..\u002F..\u002Fwp-load.php');\n\n$json = file_get_contents('\u002Fvar\u002Fwww\u002Fhtml\u002Fscripts\u002Fuser\u002Fuser.json');\n$uesrs = json_decode($json,true);\n\n$new_id = 2;\nforeach($uesrs as &$val){\n\n    $role;\n    switch($val['groupid']){\n        case \"1\":\n            $role = 'administrator';\n        break;\n       \n        case \"2\":\n            $role = 'contributor';\n        break;\n\n        case \"3\":\n            $role = 'contributor';\n        break;\n\n        case \"4\":\n            $role = 'administrator';\n        break;\n\n        case \"5\":\n            $role = 'contributor';\n        break;\n\n        case \"6\":\n            $role = 'administrator';\n        break;\n\n        case \"7\":\n            $role = 'administrator';\n        break;\n    }\n\n    $user = [\n        'user_pass'=>'PASS_WORD',\n        'user_login'=>$val['loginname'],\n        'user_email'=>$val['email'],\n        'display_name'=>$val['uid'],\n        'role'=>$role,\n    ];\n    $return = wp_insert_user($user);\n    \n    if( is_wp_error( $return ) ) {\n        print_r($return->get_error_message().':'.$val['loginname'].\"\\n\");\n        continue;\n    }\n    $val['new_id']=$new_id;\n    $new_id++;\n}\nfile_put_contents(\"\u002Fvar\u002Fwww\u002Fhtml\u002Fscripts\u002Fuser\u002Fregistered_user.json\", json_encode($uesrs,JSON_UNESCAPED_UNICODE));\n",[49,14407,14408,14412,14416,14420,14425,14430,14434,14438,14443,14447,14452,14457,14462,14467,14472,14477,14482,14487,14491,14495,14500,14504,14508,14512,14517,14521,14525,14529,14534,14538,14542,14546,14551,14555,14559,14563,14568,14572,14576,14580,14584,14589,14594,14599,14604,14609,14614,14619,14624,14628,14632,14637,14641,14645,14649,14653,14657],{"__ignoreMap":47},[52,14409,14410],{"class":54,"line":55},[52,14411,13868],{},[52,14413,14414],{"class":54,"line":83},[52,14415,13873],{},[52,14417,14418],{"class":54,"line":115},[52,14419,341],{"emptyLinePlaceholder":340},[52,14421,14422],{"class":54,"line":142},[52,14423,14424],{},"$json = file_get_contents('\u002Fvar\u002Fwww\u002Fhtml\u002Fscripts\u002Fuser\u002Fuser.json');\n",[52,14426,14427],{"class":54,"line":169},[52,14428,14429],{},"$uesrs = json_decode($json,true);\n",[52,14431,14432],{"class":54,"line":302},[52,14433,341],{"emptyLinePlaceholder":340},[52,14435,14436],{"class":54,"line":308},[52,14437,13892],{},[52,14439,14440],{"class":54,"line":318},[52,14441,14442],{},"foreach($uesrs as &$val){\n",[52,14444,14445],{"class":54,"line":328},[52,14446,341],{"emptyLinePlaceholder":340},[52,14448,14449],{"class":54,"line":337},[52,14450,14451],{},"    $role;\n",[52,14453,14454],{"class":54,"line":344},[52,14455,14456],{},"    switch($val['groupid']){\n",[52,14458,14459],{"class":54,"line":354},[52,14460,14461],{},"        case \"1\":\n",[52,14463,14464],{"class":54,"line":367},[52,14465,14466],{},"            $role = 'administrator';\n",[52,14468,14469],{"class":54,"line":387},[52,14470,14471],{},"        break;\n",[52,14473,14474],{"class":54,"line":415},[52,14475,14476],{},"       \n",[52,14478,14479],{"class":54,"line":427},[52,14480,14481],{},"        case \"2\":\n",[52,14483,14484],{"class":54,"line":435},[52,14485,14486],{},"            $role = 'contributor';\n",[52,14488,14489],{"class":54,"line":446},[52,14490,14471],{},[52,14492,14493],{"class":54,"line":480},[52,14494,341],{"emptyLinePlaceholder":340},[52,14496,14497],{"class":54,"line":509},[52,14498,14499],{},"        case \"3\":\n",[52,14501,14502],{"class":54,"line":539},[52,14503,14486],{},[52,14505,14506],{"class":54,"line":547},[52,14507,14471],{},[52,14509,14510],{"class":54,"line":553},[52,14511,341],{"emptyLinePlaceholder":340},[52,14513,14514],{"class":54,"line":559},[52,14515,14516],{},"        case \"4\":\n",[52,14518,14519],{"class":54,"line":564},[52,14520,14466],{},[52,14522,14523],{"class":54,"line":569},[52,14524,14471],{},[52,14526,14527],{"class":54,"line":1106},[52,14528,341],{"emptyLinePlaceholder":340},[52,14530,14531],{"class":54,"line":1135},[52,14532,14533],{},"        case \"5\":\n",[52,14535,14536],{"class":54,"line":1164},[52,14537,14486],{},[52,14539,14540],{"class":54,"line":1193},[52,14541,14471],{},[52,14543,14544],{"class":54,"line":1200},[52,14545,341],{"emptyLinePlaceholder":340},[52,14547,14548],{"class":54,"line":1205},[52,14549,14550],{},"        case \"6\":\n",[52,14552,14553],{"class":54,"line":1210},[52,14554,14466],{},[52,14556,14557],{"class":54,"line":1215},[52,14558,14471],{},[52,14560,14561],{"class":54,"line":1220},[52,14562,341],{"emptyLinePlaceholder":340},[52,14564,14565],{"class":54,"line":3800},[52,14566,14567],{},"        case \"7\":\n",[52,14569,14570],{"class":54,"line":3821},[52,14571,14466],{},[52,14573,14574],{"class":54,"line":3835},[52,14575,14471],{},[52,14577,14578],{"class":54,"line":3840},[52,14579,2696],{},[52,14581,14582],{"class":54,"line":3865},[52,14583,341],{"emptyLinePlaceholder":340},[52,14585,14586],{"class":54,"line":3879},[52,14587,14588],{},"    $user = [\n",[52,14590,14591],{"class":54,"line":5506},[52,14592,14593],{},"        'user_pass'=>'PASS_WORD',\n",[52,14595,14596],{"class":54,"line":4},[52,14597,14598],{},"        'user_login'=>$val['loginname'],\n",[52,14600,14601],{"class":54,"line":5544},[52,14602,14603],{},"        'user_email'=>$val['email'],\n",[52,14605,14606],{"class":54,"line":5561},[52,14607,14608],{},"        'display_name'=>$val['uid'],\n",[52,14610,14611],{"class":54,"line":5566},[52,14612,14613],{},"        'role'=>$role,\n",[52,14615,14616],{"class":54,"line":5587},[52,14617,14618],{},"    ];\n",[52,14620,14621],{"class":54,"line":5636},[52,14622,14623],{},"    $return = wp_insert_user($user);\n",[52,14625,14626],{"class":54,"line":5641},[52,14627,3235],{},[52,14629,14630],{"class":54,"line":5646},[52,14631,13911],{},[52,14633,14634],{"class":54,"line":5679},[52,14635,14636],{},"        print_r($return->get_error_message().':'.$val['loginname'].\"\\n\");\n",[52,14638,14639],{"class":54,"line":5692},[52,14640,13921],{},[52,14642,14643],{"class":54,"line":5711},[52,14644,2696],{},[52,14646,14647],{"class":54,"line":5724},[52,14648,13934],{},[52,14650,14651],{"class":54,"line":5745},[52,14652,13939],{},[52,14654,14655],{"class":54,"line":5750},[52,14656,536],{},[52,14658,14659],{"class":54,"line":5786},[52,14660,14661],{},"file_put_contents(\"\u002Fvar\u002Fwww\u002Fhtml\u002Fscripts\u002Fuser\u002Fregistered_user.json\", json_encode($uesrs,JSON_UNESCAPED_UNICODE));\n",[13,14663,14664,14665,14668],{},"ちょっと自分のためのコードがありますが、重要なのは ",[49,14666,14667],{},"wp_insert_user"," という関数です。引数は連想配列で入れます。以下のキー名で配列にします。",[42,14670,14672],{"className":13693,"code":14671,"language":8577,"meta":47,"style":47},"$user = [\n        'user_pass'=>'PASS_WORD',       \u002F\u002F パスワード名\n        'user_login'=>$val['loginname'],\u002F\u002F ログイン名（英数字でないといけない）\n        'user_email'=>$val['email'],    \u002F\u002F 登録アドレス\n        'display_name'=>$val['uid'],    \u002F\u002F 表示名（プロフ名）\n        'role'=>$role,                  \u002F\u002F 権限キー\n    ];\n",[49,14673,14674,14679,14687,14692,14700,14708,14716],{"__ignoreMap":47},[52,14675,14676],{"class":54,"line":55},[52,14677,14678],{},"$user = [\n",[52,14680,14681,14684],{"class":54,"line":83},[52,14682,14683],{},"        'user_pass'=>'PASS_WORD',",[52,14685,14686],{},"       \u002F\u002F パスワード名\n",[52,14688,14689],{"class":54,"line":115},[52,14690,14691],{},"        'user_login'=>$val['loginname'],\u002F\u002F ログイン名（英数字でないといけない）\n",[52,14693,14694,14697],{"class":54,"line":142},[52,14695,14696],{},"        'user_email'=>$val['email'],",[52,14698,14699],{},"    \u002F\u002F 登録アドレス\n",[52,14701,14702,14705],{"class":54,"line":169},[52,14703,14704],{},"        'display_name'=>$val['uid'],",[52,14706,14707],{},"    \u002F\u002F 表示名（プロフ名）\n",[52,14709,14710,14713],{"class":54,"line":302},[52,14711,14712],{},"        'role'=>$role,",[52,14714,14715],{},"                  \u002F\u002F 権限キー\n",[52,14717,14718],{"class":54,"line":308},[52,14719,14618],{},[13,14721,14722],{},"権限キーは文字列で指定します。私のコードでは旧CMSのIDを対応させています。そしてブログの時の様に旧データと新データのIDを対応させる様にします。",[42,14724,14726],{"className":13693,"code":14725,"language":8577,"meta":47,"style":47},"...\n   $return = wp_insert_user($user);\n\n　　if( is_wp_error( $return ) ) {\n        print_r($return->get_error_message().':'.$val['loginname'].\"\\n\");\n        continue;\n    }\n    $val['new_id']=$new_id;\n    $new_id++;\n}\nfile_put_contents(\"\u002Fvar\u002Fwww\u002Fhtml\u002Fscripts\u002Fuser\u002Fregistered_user.json\", json_encode($uesrs,JSON_UNESCAPED_UNICODE));\n",[49,14727,14728,14732,14737,14741,14746,14750,14754,14758,14762,14766,14770],{"__ignoreMap":47},[52,14729,14730],{"class":54,"line":55},[52,14731,14102],{},[52,14733,14734],{"class":54,"line":83},[52,14735,14736],{},"   $return = wp_insert_user($user);\n",[52,14738,14739],{"class":54,"line":115},[52,14740,341],{"emptyLinePlaceholder":340},[52,14742,14743],{"class":54,"line":142},[52,14744,14745],{},"　　if( is_wp_error( $return ) ) {\n",[52,14747,14748],{"class":54,"line":169},[52,14749,14636],{},[52,14751,14752],{"class":54,"line":302},[52,14753,13921],{},[52,14755,14756],{"class":54,"line":308},[52,14757,2696],{},[52,14759,14760],{"class":54,"line":318},[52,14761,13934],{},[52,14763,14764],{"class":54,"line":328},[52,14765,13939],{},[52,14767,14768],{"class":54,"line":337},[52,14769,536],{},[52,14771,14772],{"class":54,"line":344},[52,14773,14661],{},[13,14775,14776,14783],{},[1463,14777,14778,14779,14782],{},"ちなみにスクリプトを実行するときは ",[49,14780,14781],{},"is_wp_error( $return ) ","でエラーをキャッチできる様にしましょう。"," なぜか私のデータには同じユーザーのデータがあったりなどで、「ユーザーがすでに登録されています」というエラーでIDがずれてしまうという事件がありました。そのためにキャッチできるスクリプトを入れておきましょう。",[1499,14785,14786],{"id":14786},"ユーザーをブログに当てはめ",[13,14788,14789],{},"ユーザーのスクリプトを実行するとユーザーが作成され、新旧のIDを対応させたユーザーJSONファイルができました。これとブログカテゴリーのデータを用いて各ブログを管理するユーザーを割り当てていきます。",[42,14791,14794],{"className":13693,"code":14792,"filename":14793,"language":8577,"meta":47,"style":47},"\u003C?php\nrequire_once('..\u002F..\u002Fwp-load.php');\n\n\u002F\u002F wordpress user IDが入ったuserのファイル\n$user_json = file_get_contents('\u002Fvar\u002Fwww\u002Fhtml\u002Fscripts\u002Fuser\u002Fregistered_user.json');\n$uesrs = json_decode($user_json,true);\n\n\u002F\u002F wordpress blog IDが入ったブログカテゴリーのファイル\n$cat_json = file_get_contents('\u002Fvar\u002Fwww\u002Fhtml\u002Fscripts\u002Fcategory\u002Fregistered_article_categpry.json');\n$cats = json_decode($cat_json,true);\n\nforeach($cats as $cat_key=> $cat_val){\n    $old_user_ids =explode('::',$cat_val['mailUser']);\n    \n    foreach($old_user_ids as $old_id){\n        $user = array_values(array_filter($uesrs,function($ele) use($old_id) {\n            return $ele['uid'] == $old_id && isset($ele['new_id']);\n        }));\n\n        if(!empty($user)){\n            $new_userid = $user[0]['new_id'];\n            $new_cat_id =intval($cat_val['new_id']);\n            add_user_to_blog($new_cat_id,$new_userid,'administrator');\n        }\n    }\n}\n","add_user_blog.php",[49,14795,14796,14800,14804,14808,14813,14818,14823,14827,14832,14837,14842,14846,14851,14856,14860,14865,14870,14875,14880,14884,14889,14894,14899,14904,14908,14912],{"__ignoreMap":47},[52,14797,14798],{"class":54,"line":55},[52,14799,13868],{},[52,14801,14802],{"class":54,"line":83},[52,14803,13873],{},[52,14805,14806],{"class":54,"line":115},[52,14807,341],{"emptyLinePlaceholder":340},[52,14809,14810],{"class":54,"line":142},[52,14811,14812],{},"\u002F\u002F wordpress user IDが入ったuserのファイル\n",[52,14814,14815],{"class":54,"line":169},[52,14816,14817],{},"$user_json = file_get_contents('\u002Fvar\u002Fwww\u002Fhtml\u002Fscripts\u002Fuser\u002Fregistered_user.json');\n",[52,14819,14820],{"class":54,"line":302},[52,14821,14822],{},"$uesrs = json_decode($user_json,true);\n",[52,14824,14825],{"class":54,"line":308},[52,14826,341],{"emptyLinePlaceholder":340},[52,14828,14829],{"class":54,"line":318},[52,14830,14831],{},"\u002F\u002F wordpress blog IDが入ったブログカテゴリーのファイル\n",[52,14833,14834],{"class":54,"line":328},[52,14835,14836],{},"$cat_json = file_get_contents('\u002Fvar\u002Fwww\u002Fhtml\u002Fscripts\u002Fcategory\u002Fregistered_article_categpry.json');\n",[52,14838,14839],{"class":54,"line":337},[52,14840,14841],{},"$cats = json_decode($cat_json,true);\n",[52,14843,14844],{"class":54,"line":344},[52,14845,341],{"emptyLinePlaceholder":340},[52,14847,14848],{"class":54,"line":354},[52,14849,14850],{},"foreach($cats as $cat_key=> $cat_val){\n",[52,14852,14853],{"class":54,"line":367},[52,14854,14855],{},"    $old_user_ids =explode('::',$cat_val['mailUser']);\n",[52,14857,14858],{"class":54,"line":387},[52,14859,3235],{},[52,14861,14862],{"class":54,"line":415},[52,14863,14864],{},"    foreach($old_user_ids as $old_id){\n",[52,14866,14867],{"class":54,"line":427},[52,14868,14869],{},"        $user = array_values(array_filter($uesrs,function($ele) use($old_id) {\n",[52,14871,14872],{"class":54,"line":435},[52,14873,14874],{},"            return $ele['uid'] == $old_id && isset($ele['new_id']);\n",[52,14876,14877],{"class":54,"line":446},[52,14878,14879],{},"        }));\n",[52,14881,14882],{"class":54,"line":480},[52,14883,341],{"emptyLinePlaceholder":340},[52,14885,14886],{"class":54,"line":509},[52,14887,14888],{},"        if(!empty($user)){\n",[52,14890,14891],{"class":54,"line":539},[52,14892,14893],{},"            $new_userid = $user[0]['new_id'];\n",[52,14895,14896],{"class":54,"line":547},[52,14897,14898],{},"            $new_cat_id =intval($cat_val['new_id']);\n",[52,14900,14901],{"class":54,"line":553},[52,14902,14903],{},"            add_user_to_blog($new_cat_id,$new_userid,'administrator');\n",[52,14905,14906],{"class":54,"line":559},[52,14907,5398],{},[52,14909,14910],{"class":54,"line":564},[52,14911,2696],{},[52,14913,14914],{"class":54,"line":569},[52,14915,536],{},[13,14917,14918,14919,14922,14923,14925,14926,14929],{},"私のデータの場合、ユーザーが複数人いたので",[49,14920,14921],{},"foreach","の中でさらに",[49,14924,14921],{},"しています。マルチサイト の特定のブログに対してユーザーを割り当てるためには ",[49,14927,14928],{},"add_user_to_blog"," を用います。",[13,14931,14932],{},"第一引数にユーザーID、第二引数に対象のブログID、第三には権限グループを指定することで簡単にブログに対してユーザーを割り当てられます。",[17,14934,14935],{"id":14935},"投稿を機械的に流し込み",[13,14937,14938],{},"最後に投稿を流し込みます。私が使用したデータは以下の様なデータになっています。",[42,14940,14943],{"className":9608,"code":14941,"filename":14942,"language":9610,"meta":47,"style":47},"[\n...\n    {\n    　\"id\":\"162\",\n    　\"date\":\"2007-08-02\",\n    　\"category_id\":\"6\",\n    　\"uid\":\"1543\",\n    　\"title\":\"タイトル\",\n    　\"content\":\"ここにブログの内容がプレーンテキスト形式で入っています。\"\n    },\n...\n]\n","article.json",[49,14944,14945,14949,14953,14957,14977,14997,15016,15035,15054,15072,15076,15080],{"__ignoreMap":47},[52,14946,14947],{"class":54,"line":55},[52,14948,443],{"class":58},[52,14950,14951],{"class":54,"line":83},[52,14952,14102],{"class":105},[52,14954,14955],{"class":54,"line":115},[52,14956,14283],{"class":58},[52,14958,14959,14962,14964,14966,14968,14970,14973,14975],{"class":54,"line":142},[52,14960,14961],{"class":58},"    　\"",[52,14963,13768],{"class":65},[52,14965,72],{"class":58},[52,14967,373],{"class":58},[52,14969,72],{"class":58},[52,14971,14972],{"class":75},"162",[52,14974,72],{"class":58},[52,14976,384],{"class":58},[52,14978,14979,14981,14984,14986,14988,14990,14993,14995],{"class":54,"line":169},[52,14980,14961],{"class":58},[52,14982,14983],{"class":65},"date",[52,14985,72],{"class":58},[52,14987,373],{"class":58},[52,14989,72],{"class":58},[52,14991,14992],{"class":75},"2007-08-02",[52,14994,72],{"class":58},[52,14996,384],{"class":58},[52,14998,14999,15001,15004,15006,15008,15010,15012,15014],{"class":54,"line":302},[52,15000,14961],{"class":58},[52,15002,15003],{"class":65},"category_id",[52,15005,72],{"class":58},[52,15007,373],{"class":58},[52,15009,72],{"class":58},[52,15011,13777],{"class":75},[52,15013,72],{"class":58},[52,15015,384],{"class":58},[52,15017,15018,15020,15022,15024,15026,15028,15031,15033],{"class":54,"line":308},[52,15019,14961],{"class":58},[52,15021,14291],{"class":65},[52,15023,72],{"class":58},[52,15025,373],{"class":58},[52,15027,72],{"class":58},[52,15029,15030],{"class":75},"1543",[52,15032,72],{"class":58},[52,15034,384],{"class":58},[52,15036,15037,15039,15041,15043,15045,15047,15050,15052],{"class":54,"line":318},[52,15038,14961],{"class":58},[52,15040,2195],{"class":65},[52,15042,72],{"class":58},[52,15044,373],{"class":58},[52,15046,72],{"class":58},[52,15048,15049],{"class":75},"タイトル",[52,15051,72],{"class":58},[52,15053,384],{"class":58},[52,15055,15056,15058,15061,15063,15065,15067,15070],{"class":54,"line":328},[52,15057,14961],{"class":58},[52,15059,15060],{"class":65},"content",[52,15062,72],{"class":58},[52,15064,373],{"class":58},[52,15066,72],{"class":58},[52,15068,15069],{"class":75},"ここにブログの内容がプレーンテキスト形式で入っています。",[52,15071,266],{"class":58},[52,15073,15074],{"class":54,"line":337},[52,15075,10189],{"class":58},[52,15077,15078],{"class":54,"line":344},[52,15079,14102],{"class":105},[52,15081,15082],{"class":54,"line":354},[52,15083,6730],{"class":58},[13,15085,15086,15088,15089,15091],{},[49,15087,14291],{},"を元にユーザー（著者）と結び付け、",[49,15090,15003],{},"を元にどのブログに投稿するのかを指定します。以下の様なスクリプトを書きました。",[42,15093,15096],{"className":13693,"code":15094,"filename":15095,"language":8577,"meta":47,"style":47},"\u003C?php\nini_set('memory_limit', '1024M');\nrequire_once('..\u002F..\u002Fwp-load.php');\n\n\u002F\u002F wordpress user IDが入ったuserのファイル\n$user_json = file_get_contents('\u002Fvar\u002Fwww\u002Fhtml\u002Fscripts\u002Fuser\u002Fregistered_user.json');\n$uesrs = json_decode($user_json,true);\n\n\u002F\u002F 旧CMSの投稿データ\n$article_json = file_get_contents('\u002Fvar\u002Fwww\u002Fhtml\u002Fscripts\u002Farticles\u002Farticle_replace.json');\n$articles = json_decode($article_json,true);\n\n\u002F\u002F wordpress blog IDが入ったブログカテゴリーのファイル\n$cat_json = file_get_contents('\u002Fvar\u002Fwww\u002Fhtml\u002Fscripts\u002Fcategory\u002Fregistered_article_categpry.json');\n$cats = json_decode($cat_json,true);\n\nforeach($articles as $key => $a_val){\n    $old_cat_id = $a_val['category_id'];\n\n    if(empty($old_cat_id)) continue;\n\n    $cat = array_values(array_filter($cats,function($ele) use($old_cat_id) {\n        return $ele['id'] == $old_cat_id && isset($ele['new_id']);\n    }));\n\n    if(empty($cat)) continue;\n\n    $new_cat_id = $cat[0]['new_id'];\n\n    $old_user_id = $a_val['uid'];\n    $user = array_values(array_filter($uesrs,function($ele) use($old_user_id) {\n        return $ele['uid'] == $old_user_id && isset($ele['new_id']);\n    }));\n\n    $new_user_id = (empty($user))?1:$user[0]['new_id'];\n    if(!get_user_by('id',intval($new_user_id))) continue;\n\n    switch_to_blog($new_cat_id);\n    $new_post = array(\n        'post_title' => $a_val['title'],\n        'post_content' => $a_val['content'],\n        'post_status' => 'publish',\n        'post_date' => date($a_val['date']),\n        'post_author' => $new_user_id,\n        'post_type' =>'post',\n    );\n    wp_insert_post($new_post);\n    restore_current_blog();\n}\n","insert_post.php",[49,15097,15098,15102,15107,15111,15115,15119,15123,15127,15131,15136,15141,15146,15150,15154,15158,15162,15166,15171,15176,15180,15185,15189,15194,15199,15204,15208,15213,15217,15222,15226,15231,15236,15241,15245,15249,15254,15259,15263,15268,15273,15278,15283,15288,15293,15298,15303,15308,15313,15318],{"__ignoreMap":47},[52,15099,15100],{"class":54,"line":55},[52,15101,13868],{},[52,15103,15104],{"class":54,"line":83},[52,15105,15106],{},"ini_set('memory_limit', '1024M');\n",[52,15108,15109],{"class":54,"line":115},[52,15110,13873],{},[52,15112,15113],{"class":54,"line":142},[52,15114,341],{"emptyLinePlaceholder":340},[52,15116,15117],{"class":54,"line":169},[52,15118,14812],{},[52,15120,15121],{"class":54,"line":302},[52,15122,14817],{},[52,15124,15125],{"class":54,"line":308},[52,15126,14822],{},[52,15128,15129],{"class":54,"line":318},[52,15130,341],{"emptyLinePlaceholder":340},[52,15132,15133],{"class":54,"line":328},[52,15134,15135],{},"\u002F\u002F 旧CMSの投稿データ\n",[52,15137,15138],{"class":54,"line":337},[52,15139,15140],{},"$article_json = file_get_contents('\u002Fvar\u002Fwww\u002Fhtml\u002Fscripts\u002Farticles\u002Farticle_replace.json');\n",[52,15142,15143],{"class":54,"line":344},[52,15144,15145],{},"$articles = json_decode($article_json,true);\n",[52,15147,15148],{"class":54,"line":354},[52,15149,341],{"emptyLinePlaceholder":340},[52,15151,15152],{"class":54,"line":367},[52,15153,14831],{},[52,15155,15156],{"class":54,"line":387},[52,15157,14836],{},[52,15159,15160],{"class":54,"line":415},[52,15161,14841],{},[52,15163,15164],{"class":54,"line":427},[52,15165,341],{"emptyLinePlaceholder":340},[52,15167,15168],{"class":54,"line":435},[52,15169,15170],{},"foreach($articles as $key => $a_val){\n",[52,15172,15173],{"class":54,"line":446},[52,15174,15175],{},"    $old_cat_id = $a_val['category_id'];\n",[52,15177,15178],{"class":54,"line":480},[52,15179,341],{"emptyLinePlaceholder":340},[52,15181,15182],{"class":54,"line":509},[52,15183,15184],{},"    if(empty($old_cat_id)) continue;\n",[52,15186,15187],{"class":54,"line":539},[52,15188,341],{"emptyLinePlaceholder":340},[52,15190,15191],{"class":54,"line":547},[52,15192,15193],{},"    $cat = array_values(array_filter($cats,function($ele) use($old_cat_id) {\n",[52,15195,15196],{"class":54,"line":553},[52,15197,15198],{},"        return $ele['id'] == $old_cat_id && isset($ele['new_id']);\n",[52,15200,15201],{"class":54,"line":559},[52,15202,15203],{},"    }));\n",[52,15205,15206],{"class":54,"line":564},[52,15207,341],{"emptyLinePlaceholder":340},[52,15209,15210],{"class":54,"line":569},[52,15211,15212],{},"    if(empty($cat)) continue;\n",[52,15214,15215],{"class":54,"line":1106},[52,15216,341],{"emptyLinePlaceholder":340},[52,15218,15219],{"class":54,"line":1135},[52,15220,15221],{},"    $new_cat_id = $cat[0]['new_id'];\n",[52,15223,15224],{"class":54,"line":1164},[52,15225,341],{"emptyLinePlaceholder":340},[52,15227,15228],{"class":54,"line":1193},[52,15229,15230],{},"    $old_user_id = $a_val['uid'];\n",[52,15232,15233],{"class":54,"line":1200},[52,15234,15235],{},"    $user = array_values(array_filter($uesrs,function($ele) use($old_user_id) {\n",[52,15237,15238],{"class":54,"line":1205},[52,15239,15240],{},"        return $ele['uid'] == $old_user_id && isset($ele['new_id']);\n",[52,15242,15243],{"class":54,"line":1210},[52,15244,15203],{},[52,15246,15247],{"class":54,"line":1215},[52,15248,341],{"emptyLinePlaceholder":340},[52,15250,15251],{"class":54,"line":1220},[52,15252,15253],{},"    $new_user_id = (empty($user))?1:$user[0]['new_id'];\n",[52,15255,15256],{"class":54,"line":3800},[52,15257,15258],{},"    if(!get_user_by('id',intval($new_user_id))) continue;\n",[52,15260,15261],{"class":54,"line":3821},[52,15262,341],{"emptyLinePlaceholder":340},[52,15264,15265],{"class":54,"line":3835},[52,15266,15267],{},"    switch_to_blog($new_cat_id);\n",[52,15269,15270],{"class":54,"line":3840},[52,15271,15272],{},"    $new_post = array(\n",[52,15274,15275],{"class":54,"line":3865},[52,15276,15277],{},"        'post_title' => $a_val['title'],\n",[52,15279,15280],{"class":54,"line":3879},[52,15281,15282],{},"        'post_content' => $a_val['content'],\n",[52,15284,15285],{"class":54,"line":5506},[52,15286,15287],{},"        'post_status' => 'publish',\n",[52,15289,15290],{"class":54,"line":4},[52,15291,15292],{},"        'post_date' => date($a_val['date']),\n",[52,15294,15295],{"class":54,"line":5544},[52,15296,15297],{},"        'post_author' => $new_user_id,\n",[52,15299,15300],{"class":54,"line":5561},[52,15301,15302],{},"        'post_type' =>'post',\n",[52,15304,15305],{"class":54,"line":5566},[52,15306,15307],{},"    );\n",[52,15309,15310],{"class":54,"line":5587},[52,15311,15312],{},"    wp_insert_post($new_post);\n",[52,15314,15315],{"class":54,"line":5636},[52,15316,15317],{},"    restore_current_blog();\n",[52,15319,15320],{"class":54,"line":5641},[52,15321,536],{},[13,15323,15324,15325,15328],{},"４万件分のデータとなると非常にメモリを食うので ",[49,15326,15327],{},"ini_set"," でメモリ上限を上げてあります。",[13,15330,15331,15332,15335,15336,15338,15339,15342],{},"投稿データからブログカテゴリーIDとユーザーIDを新しいwordpresの方と紐づけ、 ",[49,15333,15334],{},"wp_insert_post"," を用いて記事を挿入します。 ",[49,15337,15334],{}," 　の気をつける点はデフォルトでは",[49,15340,15341],{},"blogid=1","のブログに記事を作成するということです。",[13,15344,15345,15346,15349,15350,15353],{},"そのためコードに ",[49,15347,15348],{},"switch_to_blog();"," を追加して挿入対象のブログを切り替えています。この関数を使用するときはセットで ",[49,15351,15352],{},"restore_current_blog();"," を使います。",[13,15355,15356,15357,15359,15360,15362,15363],{},"挿入先のブログを切り替えて、",[49,15358,15334],{}," を用いて投稿を挿入します。",[49,15361,15334],{},"は引数に連想配列を入れます。 ",[1463,15364,15365],{},"対応するキー名が決まっているので間違えない様にしましょう。",[13,15367,15368],{},"そして同じ様にターミナルでこのPHPを実行すればwordpressに投稿データが入ります。ちなみに４万件は15分かかりました。投稿したデータはエディタで普通に編集できますが、クラシックモードでの編集となります。画像などもきちんとタグとパスが生きていればきちんとレンダーされます。",[17,15370,15372],{"id":15371},"ミスってしまったら","ミスってしまったら..",[13,15374,15375],{},"大量のデータを入れたのにミスってしまったらdockerをリセットしましょう。コンテナーを削除してDBのボリュームも削除します。",[42,15377,15380],{"className":15378,"code":15379,"language":452},[1615],"docker volume rm VOLUME_NAME\n",[49,15381,15379],{"__ignoreMap":47},[13,15383,15384,15385,15388],{},"そしてまた ",[49,15386,15387],{},"docker-compose up -d"," を行うことで最初からやり直しができます。",[17,15390,15391],{"id":15391},"意外と簡単でした",[13,15393,15394,15395,15397],{},"以上が旧CMSからwordpressにデータを挿入する方法です。wordpressは ",[49,15396,13966],{}," を読み込めばほとんどの関数を使用でき、ターミナルからも実行できます。 スクリプト自体も100行未満で思いつける簡単なものです。",[13,15399,15400],{},"もしプログラム的にwordpressを操作したい場合は日本語だと上手く出てこないので「wordpress how to ~~~ programmatically」と調べるといいです。私が調べたものですと以下の感じです。",[1467,15402,15403,15406,15409],{},[1470,15404,15405],{},"wordpress how to create post programmatically",[1470,15407,15408],{},"wordpress how to create user programmatically",[1470,15410,15411],{},"wordpress how to set user role programmatically",[13,15413,15414],{},"ぶっちゃけ旧CMSからデータを引っ張ってきたり、適切に加工したり、構造を把握する方が大変でした。機会があればこのデータ移行の時に一番大変だった、正規表現により独自タグの置換も記事にしたいと思います。",[1414,15416,15417],{},"html pre.shiki code .s-wAU, html code.shiki .s-wAU{--shiki-default:#F07178}html pre.shiki code .sAklC, html code.shiki .sAklC{--shiki-default:#89DDFF}html pre.shiki code .sfyAc, html code.shiki .sfyAc{--shiki-default:#C3E88D}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html pre.shiki code .s0W1g, html code.shiki .s0W1g{--shiki-default:#BABED8}html pre.shiki code .sJ14y, html code.shiki .sJ14y{--shiki-default:#C792EA}html pre.shiki code .sx098, html code.shiki .sx098{--shiki-default:#F78C6C}",{"title":47,"searchDepth":115,"depth":115,"links":15419},[15420,15421,15422,15423,15424,15429,15433,15434,15435],{"id":13255,"depth":83,"text":13256},{"id":13322,"depth":83,"text":13322},{"id":13355,"depth":83,"text":13355},{"id":13389,"depth":83,"text":13389},{"id":13660,"depth":83,"text":13660,"children":15425},[15426,15427,15428],{"id":13663,"depth":115,"text":13663},{"id":13738,"depth":115,"text":13738},{"id":14219,"depth":115,"text":14219},{"id":14256,"depth":83,"text":14257,"children":15430},[15431,15432],{"id":14260,"depth":115,"text":14260},{"id":14786,"depth":115,"text":14786},{"id":14935,"depth":83,"text":14935},{"id":15371,"depth":83,"text":15372},{"id":15391,"depth":83,"text":15391},[1424],"2025-11-12",{},"\u002Farticles\u002Fmuch-post-migration-wp",{"title":13238,"description":13238},"articles\u002Fmuch-post-migration-wp",[15443,8577],"wordpress","_mix\u002FWordPress-logotype-wmark-e1606319357320.png","XygCCNsuG0Q3ZTPUKENvFxlijb3Rx1MHJ5r8mRTS0sk",{"id":15447,"title":15448,"body":15449,"category":20309,"createdAt":20310,"description":15448,"extension":1427,"index":1428,"meta":20311,"navigation":340,"path":20312,"publish":340,"seo":20313,"series":1428,"seriesTitle":1428,"stem":20314,"tag":20315,"thumbnail":1428,"updatedAt":20310,"__hash__":20316},"articles\u002Farticles\u002Feditorjs-repeatable.md","Editor.jsで反復入力できるブロックを作る（バニラJS）",{"type":10,"value":15450,"toc":20291},[15451,15454,15457,15460,15463,15466,15469,15483,15486,15490,15498,15501,15504,15507,15510,15513,15517,15525,15539,15542,16065,16073,16076,16079,16082,16094,16097,16100,16103,16106,16109,16113,16124,16554,16561,16607,16639,16642,16645,16648,16657,16660,16805,16812,16821,16827,16841,16943,16946,16949,16952,17167,17181,17184,17194,17197,17212,17369,17375,17538,17541,17544,17547,18020,18030,18036,18039,18056,18708,18711,18714,18720,18726,18729,18732,18735,18816,18819,18822,19318,19522,19548,19766,19792,19895,19901,19904,19907,19914,19917,19920,19923,19926,19936,20051,20063,20231,20243,20245,20248,20266,20275,20281,20288],[13,15452,15453],{},"こんにちはjunです。今回の記事は気になっているブラウザテキストエディターであるeditor.jsについての記事です。WYSINGの一種ですが、シンプルながらもckeditorやtinymceに変わるエディターになるのでは？と思いながら、カスタムブロックを作成しましので記事にしたいと思います。",[13,15455,15456],{},"なぜeditor.jsなのかという経緯や背景から解説しますので、さっさとタイトルの実装内容を知りたい場合は「今回作るもの」からご覧ください。",[17,15458,15459],{"id":15459},"背景",[13,15461,15462],{},"webシステムを作る際にはプレーンテキストのサポートだけでなく、リッチテキストエディタをサポートして欲しいという声があります。リッチテキストがあれば太字、リンク、色、画像などをhtmlを知らずともリッチなコンテンツをワードを使うようにユーザーが実装できるようになります。（こんなやつです↓）",[729,15464],{":src":15465,":width":732},"'editorjs-repeatable\u002Fsample-wysing.png'",[13,15467,15468],{},"ワードで作るような文章ベースの内容であればckeditorやtinymceで問題はありませんが、以下のようなデメリットや要件での難しさがあります。",[1467,15470,15471,15474,15477,15480],{},[1470,15472,15473],{},"生成されるコンテンツがHTMLであるため、webブラウザ意外に表示（レンダリング）が難しい。",[1470,15475,15476],{},"構造化したデータ、特定のフォーマットにしたがった入力やパーツを表現、制御UIを作成することが難しい。",[1470,15478,15479],{},"複雑なビューに対するデータモデルの入力。",[1470,15481,15482],{},"複雑なビューを見たまま編集する（wixみたいな感じ）。",[13,15484,15485],{},"「構造化したデータ、特定のフォーマット」というのは表示したいビューコンポーネントに対する入力項目のことです。例えば以下のbootstrapのカードを見てみてください。",[729,15487],{":src":15488,":width":15489},"'editorjs-repeatable\u002Fbootsrap-sample.png'","'200px'",[13,15491,15492,15493,15497],{},"お客様が「このカードを自分達でテキスト、画像を選択して任意のページ、箇所で表示できるようにしたい」という",[15494,15495,15496],"del",{},"めんどうな","要件があったとします。自由な入力をサポートするので、管理画面での固定的なフォームでは難しそうです。しかし、リッチテキストでは画像の選択や、テキスト部分の制御、カード自体の表示と削除など難しと思います。",[13,15499,15500],{},"さらに以下のようなアコーディオンはどうでしょうか？",[729,15502],{":src":15503,":width":732},"'editorjs-repeatable\u002Fbootsrap-accordion.png'",[13,15505,15506],{},"タイトル、テキスト意外にも「複数個」入力しないといけない、１つのコンポーネントにn個のフォーマットデータを入力できるようにする必要があります。",[13,15508,15509],{},"これが「構造化したデータ、特定のフォーマットにしたがった入力やパーツを表現、制御UI」でしたり、「複雑なビューを見たまま編集する」が難しいという意味です。wordpressでもグーデンベルクというコンポーネントレベルで記事を作成することが主流になり、webコンテンツの編集はワードで作成する様な文章的な内容からリッチなビューをサポートするのが要件になりつつあります。",[13,15511,15512],{},"その場合、従来のWYSINGでは実装が難しいです。そのためWYSINGでHTMLを作成してそれを保存、表示するのではなくて構造化されたデータをベースに表示、編集、出力できるようにしたものがeditor.jsです。",[17,15514,15516],{"id":15515},"editorjsの特徴","editor.jsの特徴",[13,15518,9076,15519,15524],{},[2039,15520,15523],{"href":15521,"rel":15522},"https:\u002F\u002Feditorjs.io\u002F",[2043],"公式サイトを参照","ですがざっとあげるとすれば",[1687,15526,15527,15530,15533,15536],{},[1470,15528,15529],{},"出力されるデータが配列。（永続化はJSONにする）",[1470,15531,15532],{},"特定のプロパティーに沿った要素で出力する。",[1470,15534,15535],{},"カスタマイズがしやすい。",[1470,15537,15538],{},"挿入するブロックを都度選択し、データを入力する。",[13,15540,15541],{},"といった感じです。取得されるデータは以下のようになっています。",[42,15543,15545],{"className":9608,"code":15544,"language":9610,"meta":47,"style":47},"{\n    \"time\" : 1654313680224,\n    \"blocks\" : [\n        {\n            \"id\" : \"gy0oTOBqL2\",\n            \"type\" : \"header\",\n            \"data\" : {\n                \"text\" : \"Editor.js\",\n                \"level\" : 2\n            }\n        },\n        {\n            \"id\" : \"AOOa_CMrPW\",\n            \"type\" : \"list\",\n            \"data\" : {\n                \"style\" : \"unordered\",\n                \"items\" : [\n                    \"It is a block-styled editor\",\n                    \"It returns clean data output in JSON\",\n                    \"Designed to be extendable and pluggable with a simple API\"\n                ]\n            }\n        },\n        {\n            \"id\" : \"6crsAbriK8\",\n            \"type\" : \"header\",\n            \"data\" : {\n                \"text\" : \"What does it mean clean data output\",\n                \"level\" : 3\n            }\n        },\n        {\n            \"id\" : \"GBhduZdsZW\",\n            \"type\" : \"image\",\n            \"data\" : {\n                \"file\" : {\n                    \"url\" : \"https:\u002F\u002Fcodex.so\u002Fpublic\u002Fapp\u002Fimg\u002Fexternal\u002Fcodex2x.png\"\n                },\n                \"caption\" : \"\",\n                \"withBorder\" : false,\n                \"stretched\" : false,\n                \"withBackground\" : false\n            }\n        }\n    ]\n}\n",[49,15546,15547,15551,15567,15581,15586,15606,15626,15638,15658,15672,15676,15680,15684,15703,15722,15734,15753,15766,15778,15789,15798,15803,15807,15811,15815,15834,15852,15864,15883,15896,15900,15904,15908,15927,15945,15957,15969,15987,15991,16007,16021,16034,16048,16052,16056,16061],{"__ignoreMap":47},[52,15548,15549],{"class":54,"line":55},[52,15550,364],{"class":58},[52,15552,15553,15555,15558,15560,15562,15565],{"class":54,"line":83},[52,15554,9631],{"class":58},[52,15556,15557],{"class":65},"time",[52,15559,72],{"class":58},[52,15561,6396],{"class":58},[52,15563,15564],{"class":2232}," 1654313680224",[52,15566,384],{"class":58},[52,15568,15569,15571,15574,15576,15578],{"class":54,"line":115},[52,15570,9631],{"class":58},[52,15572,15573],{"class":65},"blocks",[52,15575,72],{"class":58},[52,15577,6396],{"class":58},[52,15579,15580],{"class":58}," [\n",[52,15582,15583],{"class":54,"line":142},[52,15584,15585],{"class":58},"        {\n",[52,15587,15588,15591,15593,15595,15597,15599,15602,15604],{"class":54,"line":169},[52,15589,15590],{"class":58},"            \"",[52,15592,13768],{"class":370},[52,15594,72],{"class":58},[52,15596,6396],{"class":58},[52,15598,3597],{"class":58},[52,15600,15601],{"class":75},"gy0oTOBqL2",[52,15603,72],{"class":58},[52,15605,384],{"class":58},[52,15607,15608,15610,15613,15615,15617,15619,15622,15624],{"class":54,"line":302},[52,15609,15590],{"class":58},[52,15611,15612],{"class":370},"type",[52,15614,72],{"class":58},[52,15616,6396],{"class":58},[52,15618,3597],{"class":58},[52,15620,15621],{"class":75},"header",[52,15623,72],{"class":58},[52,15625,384],{"class":58},[52,15627,15628,15630,15632,15634,15636],{"class":54,"line":308},[52,15629,15590],{"class":58},[52,15631,11148],{"class":370},[52,15633,72],{"class":58},[52,15635,6396],{"class":58},[52,15637,10138],{"class":58},[52,15639,15640,15643,15645,15647,15649,15651,15654,15656],{"class":54,"line":318},[52,15641,15642],{"class":58},"                \"",[52,15644,452],{"class":2232},[52,15646,72],{"class":58},[52,15648,6396],{"class":58},[52,15650,3597],{"class":58},[52,15652,15653],{"class":75},"Editor.js",[52,15655,72],{"class":58},[52,15657,384],{"class":58},[52,15659,15660,15662,15665,15667,15669],{"class":54,"line":328},[52,15661,15642],{"class":58},[52,15663,15664],{"class":2232},"level",[52,15666,72],{"class":58},[52,15668,6396],{"class":58},[52,15670,15671],{"class":2232}," 2\n",[52,15673,15674],{"class":54,"line":337},[52,15675,2251],{"class":58},[52,15677,15678],{"class":54,"line":344},[52,15679,10229],{"class":58},[52,15681,15682],{"class":54,"line":354},[52,15683,15585],{"class":58},[52,15685,15686,15688,15690,15692,15694,15696,15699,15701],{"class":54,"line":367},[52,15687,15590],{"class":58},[52,15689,13768],{"class":370},[52,15691,72],{"class":58},[52,15693,6396],{"class":58},[52,15695,3597],{"class":58},[52,15697,15698],{"class":75},"AOOa_CMrPW",[52,15700,72],{"class":58},[52,15702,384],{"class":58},[52,15704,15705,15707,15709,15711,15713,15715,15718,15720],{"class":54,"line":387},[52,15706,15590],{"class":58},[52,15708,15612],{"class":370},[52,15710,72],{"class":58},[52,15712,6396],{"class":58},[52,15714,3597],{"class":58},[52,15716,15717],{"class":75},"list",[52,15719,72],{"class":58},[52,15721,384],{"class":58},[52,15723,15724,15726,15728,15730,15732],{"class":54,"line":415},[52,15725,15590],{"class":58},[52,15727,11148],{"class":370},[52,15729,72],{"class":58},[52,15731,6396],{"class":58},[52,15733,10138],{"class":58},[52,15735,15736,15738,15740,15742,15744,15746,15749,15751],{"class":54,"line":427},[52,15737,15642],{"class":58},[52,15739,1414],{"class":2232},[52,15741,72],{"class":58},[52,15743,6396],{"class":58},[52,15745,3597],{"class":58},[52,15747,15748],{"class":75},"unordered",[52,15750,72],{"class":58},[52,15752,384],{"class":58},[52,15754,15755,15757,15760,15762,15764],{"class":54,"line":435},[52,15756,15642],{"class":58},[52,15758,15759],{"class":2232},"items",[52,15761,72],{"class":58},[52,15763,6396],{"class":58},[52,15765,15580],{"class":58},[52,15767,15768,15771,15774,15776],{"class":54,"line":446},[52,15769,15770],{"class":58},"                    \"",[52,15772,15773],{"class":75},"It is a block-styled editor",[52,15775,72],{"class":58},[52,15777,384],{"class":58},[52,15779,15780,15782,15785,15787],{"class":54,"line":480},[52,15781,15770],{"class":58},[52,15783,15784],{"class":75},"It returns clean data output in JSON",[52,15786,72],{"class":58},[52,15788,384],{"class":58},[52,15790,15791,15793,15796],{"class":54,"line":509},[52,15792,15770],{"class":58},[52,15794,15795],{"class":75},"Designed to be extendable and pluggable with a simple API",[52,15797,266],{"class":58},[52,15799,15800],{"class":54,"line":539},[52,15801,15802],{"class":58},"                ]\n",[52,15804,15805],{"class":54,"line":547},[52,15806,2251],{"class":58},[52,15808,15809],{"class":54,"line":553},[52,15810,10229],{"class":58},[52,15812,15813],{"class":54,"line":559},[52,15814,15585],{"class":58},[52,15816,15817,15819,15821,15823,15825,15827,15830,15832],{"class":54,"line":564},[52,15818,15590],{"class":58},[52,15820,13768],{"class":370},[52,15822,72],{"class":58},[52,15824,6396],{"class":58},[52,15826,3597],{"class":58},[52,15828,15829],{"class":75},"6crsAbriK8",[52,15831,72],{"class":58},[52,15833,384],{"class":58},[52,15835,15836,15838,15840,15842,15844,15846,15848,15850],{"class":54,"line":569},[52,15837,15590],{"class":58},[52,15839,15612],{"class":370},[52,15841,72],{"class":58},[52,15843,6396],{"class":58},[52,15845,3597],{"class":58},[52,15847,15621],{"class":75},[52,15849,72],{"class":58},[52,15851,384],{"class":58},[52,15853,15854,15856,15858,15860,15862],{"class":54,"line":1106},[52,15855,15590],{"class":58},[52,15857,11148],{"class":370},[52,15859,72],{"class":58},[52,15861,6396],{"class":58},[52,15863,10138],{"class":58},[52,15865,15866,15868,15870,15872,15874,15876,15879,15881],{"class":54,"line":1135},[52,15867,15642],{"class":58},[52,15869,452],{"class":2232},[52,15871,72],{"class":58},[52,15873,6396],{"class":58},[52,15875,3597],{"class":58},[52,15877,15878],{"class":75},"What does it mean clean data output",[52,15880,72],{"class":58},[52,15882,384],{"class":58},[52,15884,15885,15887,15889,15891,15893],{"class":54,"line":1164},[52,15886,15642],{"class":58},[52,15888,15664],{"class":2232},[52,15890,72],{"class":58},[52,15892,6396],{"class":58},[52,15894,15895],{"class":2232}," 3\n",[52,15897,15898],{"class":54,"line":1193},[52,15899,2251],{"class":58},[52,15901,15902],{"class":54,"line":1200},[52,15903,10229],{"class":58},[52,15905,15906],{"class":54,"line":1205},[52,15907,15585],{"class":58},[52,15909,15910,15912,15914,15916,15918,15920,15923,15925],{"class":54,"line":1210},[52,15911,15590],{"class":58},[52,15913,13768],{"class":370},[52,15915,72],{"class":58},[52,15917,6396],{"class":58},[52,15919,3597],{"class":58},[52,15921,15922],{"class":75},"GBhduZdsZW",[52,15924,72],{"class":58},[52,15926,384],{"class":58},[52,15928,15929,15931,15933,15935,15937,15939,15941,15943],{"class":54,"line":1215},[52,15930,15590],{"class":58},[52,15932,15612],{"class":370},[52,15934,72],{"class":58},[52,15936,6396],{"class":58},[52,15938,3597],{"class":58},[52,15940,2639],{"class":75},[52,15942,72],{"class":58},[52,15944,384],{"class":58},[52,15946,15947,15949,15951,15953,15955],{"class":54,"line":1220},[52,15948,15590],{"class":58},[52,15950,11148],{"class":370},[52,15952,72],{"class":58},[52,15954,6396],{"class":58},[52,15956,10138],{"class":58},[52,15958,15959,15961,15963,15965,15967],{"class":54,"line":3800},[52,15960,15642],{"class":58},[52,15962,11093],{"class":2232},[52,15964,72],{"class":58},[52,15966,6396],{"class":58},[52,15968,10138],{"class":58},[52,15970,15971,15973,15976,15978,15980,15982,15985],{"class":54,"line":3821},[52,15972,15770],{"class":58},[52,15974,15975],{"class":62},"url",[52,15977,72],{"class":58},[52,15979,6396],{"class":58},[52,15981,3597],{"class":58},[52,15983,15984],{"class":75},"https:\u002F\u002Fcodex.so\u002Fpublic\u002Fapp\u002Fimg\u002Fexternal\u002Fcodex2x.png",[52,15986,266],{"class":58},[52,15988,15989],{"class":54,"line":3835},[52,15990,10426],{"class":58},[52,15992,15993,15995,15998,16000,16002,16005],{"class":54,"line":3840},[52,15994,15642],{"class":58},[52,15996,15997],{"class":2232},"caption",[52,15999,72],{"class":58},[52,16001,6396],{"class":58},[52,16003,16004],{"class":58}," \"\"",[52,16006,384],{"class":58},[52,16008,16009,16011,16014,16016,16018],{"class":54,"line":3865},[52,16010,15642],{"class":58},[52,16012,16013],{"class":2232},"withBorder",[52,16015,72],{"class":58},[52,16017,6396],{"class":58},[52,16019,16020],{"class":58}," false,\n",[52,16022,16023,16025,16028,16030,16032],{"class":54,"line":3879},[52,16024,15642],{"class":58},[52,16026,16027],{"class":2232},"stretched",[52,16029,72],{"class":58},[52,16031,6396],{"class":58},[52,16033,16020],{"class":58},[52,16035,16036,16038,16041,16043,16045],{"class":54,"line":5506},[52,16037,15642],{"class":58},[52,16039,16040],{"class":2232},"withBackground",[52,16042,72],{"class":58},[52,16044,6396],{"class":58},[52,16046,16047],{"class":58}," false\n",[52,16049,16050],{"class":54,"line":4},[52,16051,2251],{"class":58},[52,16053,16054],{"class":54,"line":5544},[52,16055,5398],{"class":58},[52,16057,16058],{"class":54,"line":5561},[52,16059,16060],{"class":58},"    ]\n",[52,16062,16063],{"class":54,"line":5566},[52,16064,536],{"class":58},[13,16066,16067,16069,16070,16072],{},[49,16068,15573],{},"という要素はいかにそれぞれのブロックのデータが入り、",[49,16071,15612],{},"によって種類を識別して決まったプロパティーが取得されます。",[1499,16074,16075],{"id":16075},"editorjsのメリット",[13,16077,16078],{},"メリットは従来のwysingで管理できなかったような複雑なビューや構造化したデータをクライアント側で簡単に入力できるようになったこと、そしてそのデータをJSONで管理できることです。スキーマを定義してバリデーションもしやすいですし、JSONなので配布もしやすいです。ブロックのカスタマイズや作成も簡単に行えるので、拡張性も高いです。今回は「作成」のみ行います。",[1499,16080,16081],{"id":16081},"editorjsのデメリット",[13,16083,16084,16085,16090,16091,16093],{},"デメリットは取得されるデータがJSONのため、レンダリングをしたい際は専用のパーサを作成する必要がります。node.jsであれば",[2039,16086,16089],{"href":16087,"rel":16088},"https:\u002F\u002Fgithub.com\u002Fpavittarx\u002Feditorjs-html",[2043],"editorjs-html","というものを使用したりして自前でhtmlに変換する処理が必要です。基本的には",[49,16092,15573],{},"をforeachして、typeをswitchにて分岐させて特定のhtml文字列を作成するといった感じです。",[13,16095,16096],{},"また当たり前ですがバリデーションも実装する必要があります。従来のwysingではphp-purifier,html_sanitizerなどを使用して特定のタグや属性をフィルタして保存するなどを行います。editor.jsは構造化したデータが大切になるので、永続化処理時にあらかじめ定義したプロパティーであるか、構造であるかをチェックするシステムは自前で実装する必要があります。（ここはまた今度書く予定です。）",[13,16098,16099],{},"まだ公式から言語ごとのパーサーやバリデーターはまだ安定したものが出ていない印象です。（2022年6月当時）",[17,16101,16102],{"id":16102},"今回作るもの",[13,16104,16105],{},"上記のような特徴を持ったeditor.jsですが、今回は例に出したアコーディオンを作ってみようかと思います。タイトルと本文（太字、斜体、リンクをサポート）を複数個に増減可能で、順番も変えられるようにします。",[13,16107,16108],{},"使う場面としては「よくある質問」みたいなページです。editor.jsの説明をメインに行うため、デザインや動きは最低限となっています。バニラJSで作成し、サンプルはこちらに置いておきました。",[17,16110,16112],{"id":16111},"足場となるhtmljscssを作成","足場となるhtml,js,cssを作成",[13,16114,16115,16116,408,16118,408,16120,16123],{},"今回は",[49,16117,2121],{},[49,16119,2124],{},[49,16121,16122],{},"style.css","の３つだけを使用します。editor.jsはCDNから引っ張り、webpackなどは使用しません。",[42,16125,16127],{"className":44,"code":16126,"filename":2121,"language":46,"meta":47,"style":47},"\u003C!DOCTYPE html>\n\u003Chtml lang=\"ja\">\n\u003Chead>\n    \u003Cmeta charset=\"UTF-8\">\n    \u003Cmeta http-equiv=\"X-UA-Compatible\" content=\"IE=edge\">\n    \u003Cmeta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n    \u003Clink href=\"https:\u002F\u002Fcdn.jsdelivr.net\u002Fnpm\u002Fbootstrap@5.0.2\u002Fdist\u002Fcss\u002Fbootstrap.min.css\" rel=\"stylesheet\" integrity=\"sha384-EVSTQN3\u002FazprG1Anm3QDgpJLIm9Nao0Yz1ztcQTwFspd3yD65VohhpuuCOmLASjC\" crossorigin=\"anonymous\">\n    \u003Ctitle>editorjs\u003C\u002Ftitle>\n    \u003Clink rel=\"stylesheet\" href=\".\u002Fstyle.css\">\n\u003C\u002Fhead>\n\u003Cbody>\n    \u003Cdiv class=\"container\">\n        \u003Cdiv class=\"mt-5 border bg-light\">\n            \u003Cdiv id=\"editorjs\">\u003C\u002Fdiv>\n        \u003C\u002Fdiv>\n        \u003Cdiv class=\"mt-2\">\n            \u003Cpre id=\"data\">\u003C\u002Fpre>\n        \u003C\u002Fdiv>\n    \u003C\u002Fdiv>\n\u003C\u002Fbody>\n\u003Cscript src=\"https:\u002F\u002Fcdn.jsdelivr.net\u002Fnpm\u002F@editorjs\u002Feditorjs@latest\">\u003C\u002Fscript>\n\u003Cscript src=\".\u002Fapp.js\">\u003C\u002Fscript>\n\u003C\u002Fhtml>\n",[49,16128,16129,16139,16159,16167,16186,16218,16248,16305,16322,16351,16359,16367,16387,16406,16428,16436,16455,16477,16485,16493,16501,16524,16546],{"__ignoreMap":47},[52,16130,16131,16133,16135,16137],{"class":54,"line":55},[52,16132,2140],{"class":58},[52,16134,2143],{"class":62},[52,16136,2146],{"class":65},[52,16138,80],{"class":58},[52,16140,16141,16143,16145,16148,16150,16152,16155,16157],{"class":54,"line":83},[52,16142,59],{"class":58},[52,16144,46],{"class":62},[52,16146,16147],{"class":65}," lang",[52,16149,69],{"class":58},[52,16151,72],{"class":58},[52,16153,16154],{"class":75},"ja",[52,16156,72],{"class":58},[52,16158,80],{"class":58},[52,16160,16161,16163,16165],{"class":54,"line":115},[52,16162,59],{"class":58},[52,16164,2164],{"class":62},[52,16166,80],{"class":58},[52,16168,16169,16171,16173,16175,16177,16179,16182,16184],{"class":54,"line":142},[52,16170,2161],{"class":58},[52,16172,2174],{"class":62},[52,16174,2177],{"class":65},[52,16176,69],{"class":58},[52,16178,72],{"class":58},[52,16180,16181],{"class":75},"UTF-8",[52,16183,72],{"class":58},[52,16185,80],{"class":58},[52,16187,16188,16190,16192,16195,16197,16199,16202,16204,16207,16209,16211,16214,16216],{"class":54,"line":169},[52,16189,2161],{"class":58},[52,16191,2174],{"class":62},[52,16193,16194],{"class":65}," http-equiv",[52,16196,69],{"class":58},[52,16198,72],{"class":58},[52,16200,16201],{"class":75},"X-UA-Compatible",[52,16203,72],{"class":58},[52,16205,16206],{"class":65}," content",[52,16208,69],{"class":58},[52,16210,72],{"class":58},[52,16212,16213],{"class":75},"IE=edge",[52,16215,72],{"class":58},[52,16217,80],{"class":58},[52,16219,16220,16222,16224,16226,16228,16230,16233,16235,16237,16239,16241,16244,16246],{"class":54,"line":302},[52,16221,2161],{"class":58},[52,16223,2174],{"class":62},[52,16225,66],{"class":65},[52,16227,69],{"class":58},[52,16229,72],{"class":58},[52,16231,16232],{"class":75},"viewport",[52,16234,72],{"class":58},[52,16236,16206],{"class":65},[52,16238,69],{"class":58},[52,16240,72],{"class":58},[52,16242,16243],{"class":75},"width=device-width, initial-scale=1.0",[52,16245,72],{"class":58},[52,16247,80],{"class":58},[52,16249,16250,16252,16255,16258,16260,16262,16265,16267,16270,16272,16274,16277,16279,16282,16284,16286,16289,16291,16294,16296,16298,16301,16303],{"class":54,"line":308},[52,16251,2161],{"class":58},[52,16253,16254],{"class":62},"link",[52,16256,16257],{"class":65}," href",[52,16259,69],{"class":58},[52,16261,72],{"class":58},[52,16263,16264],{"class":75},"https:\u002F\u002Fcdn.jsdelivr.net\u002Fnpm\u002Fbootstrap@5.0.2\u002Fdist\u002Fcss\u002Fbootstrap.min.css",[52,16266,72],{"class":58},[52,16268,16269],{"class":65}," rel",[52,16271,69],{"class":58},[52,16273,72],{"class":58},[52,16275,16276],{"class":75},"stylesheet",[52,16278,72],{"class":58},[52,16280,16281],{"class":65}," integrity",[52,16283,69],{"class":58},[52,16285,72],{"class":58},[52,16287,16288],{"class":75},"sha384-EVSTQN3\u002FazprG1Anm3QDgpJLIm9Nao0Yz1ztcQTwFspd3yD65VohhpuuCOmLASjC",[52,16290,72],{"class":58},[52,16292,16293],{"class":65}," crossorigin",[52,16295,69],{"class":58},[52,16297,72],{"class":58},[52,16299,16300],{"class":75},"anonymous",[52,16302,72],{"class":58},[52,16304,80],{"class":58},[52,16306,16307,16309,16311,16313,16316,16318,16320],{"class":54,"line":318},[52,16308,2161],{"class":58},[52,16310,2195],{"class":62},[52,16312,102],{"class":58},[52,16314,16315],{"class":105},"editorjs",[52,16317,108],{"class":58},[52,16319,2195],{"class":62},[52,16321,80],{"class":58},[52,16323,16324,16326,16328,16330,16332,16334,16336,16338,16340,16342,16344,16347,16349],{"class":54,"line":328},[52,16325,2161],{"class":58},[52,16327,16254],{"class":62},[52,16329,16269],{"class":65},[52,16331,69],{"class":58},[52,16333,72],{"class":58},[52,16335,16276],{"class":75},[52,16337,72],{"class":58},[52,16339,16257],{"class":65},[52,16341,69],{"class":58},[52,16343,72],{"class":58},[52,16345,16346],{"class":75},".\u002Fstyle.css",[52,16348,72],{"class":58},[52,16350,80],{"class":58},[52,16352,16353,16355,16357],{"class":54,"line":337},[52,16354,108],{"class":58},[52,16356,2164],{"class":62},[52,16358,80],{"class":58},[52,16360,16361,16363,16365],{"class":54,"line":344},[52,16362,59],{"class":58},[52,16364,2314],{"class":62},[52,16366,80],{"class":58},[52,16368,16369,16371,16373,16376,16378,16380,16383,16385],{"class":54,"line":354},[52,16370,2161],{"class":58},[52,16372,8503],{"class":62},[52,16374,16375],{"class":65}," class",[52,16377,69],{"class":58},[52,16379,72],{"class":58},[52,16381,16382],{"class":75},"container",[52,16384,72],{"class":58},[52,16386,80],{"class":58},[52,16388,16389,16391,16393,16395,16397,16399,16402,16404],{"class":54,"line":367},[52,16390,2171],{"class":58},[52,16392,8503],{"class":62},[52,16394,16375],{"class":65},[52,16396,69],{"class":58},[52,16398,72],{"class":58},[52,16400,16401],{"class":75},"mt-5 border bg-light",[52,16403,72],{"class":58},[52,16405,80],{"class":58},[52,16407,16408,16410,16412,16414,16416,16418,16420,16422,16424,16426],{"class":54,"line":387},[52,16409,2330],{"class":58},[52,16411,8503],{"class":62},[52,16413,2336],{"class":65},[52,16415,69],{"class":58},[52,16417,72],{"class":58},[52,16419,16315],{"class":75},[52,16421,72],{"class":58},[52,16423,2347],{"class":58},[52,16425,8503],{"class":62},[52,16427,80],{"class":58},[52,16429,16430,16432,16434],{"class":54,"line":415},[52,16431,2294],{"class":58},[52,16433,8503],{"class":62},[52,16435,80],{"class":58},[52,16437,16438,16440,16442,16444,16446,16448,16451,16453],{"class":54,"line":427},[52,16439,2171],{"class":58},[52,16441,8503],{"class":62},[52,16443,16375],{"class":65},[52,16445,69],{"class":58},[52,16447,72],{"class":58},[52,16449,16450],{"class":75},"mt-2",[52,16452,72],{"class":58},[52,16454,80],{"class":58},[52,16456,16457,16459,16461,16463,16465,16467,16469,16471,16473,16475],{"class":54,"line":435},[52,16458,2330],{"class":58},[52,16460,42],{"class":62},[52,16462,2336],{"class":65},[52,16464,69],{"class":58},[52,16466,72],{"class":58},[52,16468,11148],{"class":75},[52,16470,72],{"class":58},[52,16472,2347],{"class":58},[52,16474,42],{"class":62},[52,16476,80],{"class":58},[52,16478,16479,16481,16483],{"class":54,"line":446},[52,16480,2294],{"class":58},[52,16482,8503],{"class":62},[52,16484,80],{"class":58},[52,16486,16487,16489,16491],{"class":54,"line":480},[52,16488,2303],{"class":58},[52,16490,8503],{"class":62},[52,16492,80],{"class":58},[52,16494,16495,16497,16499],{"class":54,"line":509},[52,16496,108],{"class":58},[52,16498,2314],{"class":62},[52,16500,80],{"class":58},[52,16502,16503,16505,16507,16509,16511,16513,16516,16518,16520,16522],{"class":54,"line":539},[52,16504,59],{"class":58},[52,16506,349],{"class":62},[52,16508,2368],{"class":65},[52,16510,69],{"class":58},[52,16512,72],{"class":58},[52,16514,16515],{"class":75},"https:\u002F\u002Fcdn.jsdelivr.net\u002Fnpm\u002F@editorjs\u002Feditorjs@latest",[52,16517,72],{"class":58},[52,16519,2347],{"class":58},[52,16521,349],{"class":62},[52,16523,80],{"class":58},[52,16525,16526,16528,16530,16532,16534,16536,16538,16540,16542,16544],{"class":54,"line":547},[52,16527,59],{"class":58},[52,16529,349],{"class":62},[52,16531,2368],{"class":65},[52,16533,69],{"class":58},[52,16535,72],{"class":58},[52,16537,2375],{"class":75},[52,16539,72],{"class":58},[52,16541,2347],{"class":58},[52,16543,349],{"class":62},[52,16545,80],{"class":58},[52,16547,16548,16550,16552],{"class":54,"line":553},[52,16549,108],{"class":58},[52,16551,46],{"class":62},[52,16553,80],{"class":58},[13,16555,16556,16557,16560],{},"editor.js側はひとまず以下のようにして、",[49,16558,16559],{},"\u003Cdiv id=\"editorjs\">\u003C\u002Fdiv>","にエディターがマウントされるようにします。",[42,16562,16564],{"className":10507,"code":16563,"filename":2124,"language":1434,"meta":47,"style":47},"const editor = new EditorJS({\n    holder: 'editorjs',\n});\n",[49,16565,16566,16584,16599],{"__ignoreMap":47},[52,16567,16568,16570,16573,16575,16577,16580,16582],{"class":54,"line":55},[52,16569,10903],{"class":65},[52,16571,16572],{"class":105}," editor ",[52,16574,69],{"class":58},[52,16576,2522],{"class":58},[52,16578,16579],{"class":418}," EditorJS",[52,16581,932],{"class":105},[52,16583,364],{"class":58},[52,16585,16586,16589,16591,16593,16595,16597],{"class":54,"line":83},[52,16587,16588],{"class":62},"    holder",[52,16590,373],{"class":58},[52,16592,6309],{"class":58},[52,16594,16315],{"class":75},[52,16596,376],{"class":58},[52,16598,384],{"class":58},[52,16600,16601,16603,16605],{"class":54,"line":115},[52,16602,1312],{"class":58},[52,16604,938],{"class":105},[52,16606,1007],{"class":58},[42,16608,16612],{"className":16609,"code":16610,"filename":16122,"language":16611,"meta":47,"style":47},"language-css shiki shiki-themes material-theme-ocean",".ce-block__content{\n    background-color: white;\n}\n","css",[49,16613,16614,16623,16635],{"__ignoreMap":47},[52,16615,16616,16618,16621],{"class":54,"line":55},[52,16617,957],{"class":58},[52,16619,16620],{"class":370},"ce-block__content",[52,16622,364],{"class":58},[52,16624,16625,16628,16630,16633],{"class":54,"line":83},[52,16626,16627],{"class":2226},"    background-color",[52,16629,373],{"class":58},[52,16631,16632],{"class":105}," white",[52,16634,1007],{"class":58},[52,16636,16637],{"class":54,"line":115},[52,16638,536],{"class":58},[13,16640,16641],{},"マウントされると以下の画像のように、editor.jsがマウントされて初期状態ではテキストブロックだけが入力できます。",[729,16643],{":src":16644,":width":732},"'editorjs-repeatable\u002Finit-mounted.png'",[17,16646,16647],{"id":16647},"基本のメソッド",[13,16649,16650,16651,16656],{},"実装には",[2039,16652,16655],{"href":16653,"rel":16654},"https:\u002F\u002Feditorjs.io\u002Fthe-first-plugin",[2043],"editor.jsのドキュメント","を参考にして解説します。ドキュメントの方も合わせて作ってみることをお勧めします。",[13,16658,16659],{},"editor.jsにてブロックを実装する場合以下のメソッドを実装します。",[42,16661,16663],{"className":10507,"code":16662,"language":1434,"meta":47,"style":47},"class Accordion{\n    \u002F\u002F editor jsにブロックの情報を渡す\n    static get toolbox() {\n        return{\n            title: 'YourBlockName',\n            icon: '\u003Csvg xmlns=\"http:\u002F\u002Fwww.w3.org\u002F2000\u002Fsvg\" viewBox=\"0 0 576 512\">\u003C!--! Font Awesome Pro 6.1.1 by @fontawesome - https:\u002F\u002Ffontawesome.com License - https:\u002F\u002Ffontawesome.com\u002Flicense (Commercial License) Copyright 2022 Fonticons, Inc. -->\u003Cpath d=\"M528 32H144c-26.51 0-48 21.49-48 48v256c0 26.51 21.49 48 48 48H528c26.51 0 48-21.49 48-48v-256C576 53.49 554.5 32 528 32zM223.1 96c17.68 0 32 14.33 32 32S241.7 160 223.1 160c-17.67 0-32-14.33-32-32S206.3 96 223.1 96zM494.1 311.6C491.3 316.8 485.9 320 480 320H192c-6.023 0-11.53-3.379-14.26-8.75c-2.73-5.367-2.215-11.81 1.332-16.68l70-96C252.1 194.4 256.9 192 262 192c5.111 0 9.916 2.441 12.93 6.574l22.35 30.66l62.74-94.11C362.1 130.7 367.1 128 373.3 128c5.348 0 10.34 2.672 13.31 7.125l106.7 160C496.6 300 496.9 306.3 494.1 311.6zM456 432H120c-39.7 0-72-32.3-72-72v-240C48 106.8 37.25 96 24 96S0 106.8 0 120v240C0 426.2 53.83 480 120 480h336c13.25 0 24-10.75 24-24S469.3 432 456 432z\"\u002F>\u003C\u002Fsvg>'\n        }\n    } \n\n    constructor({ data,api,config }){\n    }\n\n    render(){}\n\n    save(blockContent){}\n}\n",[49,16664,16665,16675,16680,16695,16701,16717,16731,16735,16741,16745,16768,16772,16776,16784,16788,16801],{"__ignoreMap":47},[52,16666,16667,16670,16673],{"class":54,"line":55},[52,16668,16669],{"class":65},"class",[52,16671,16672],{"class":370}," Accordion",[52,16674,364],{"class":58},[52,16676,16677],{"class":54,"line":83},[52,16678,16679],{"class":411},"    \u002F\u002F editor jsにブロックの情報を渡す\n",[52,16681,16682,16685,16688,16691,16693],{"class":54,"line":115},[52,16683,16684],{"class":65},"    static",[52,16686,16687],{"class":65}," get",[52,16689,16690],{"class":62}," toolbox",[52,16692,422],{"class":58},[52,16694,10138],{"class":58},[52,16696,16697,16699],{"class":54,"line":142},[52,16698,10166],{"class":360},[52,16700,364],{"class":58},[52,16702,16703,16706,16708,16710,16713,16715],{"class":54,"line":169},[52,16704,16705],{"class":62},"            title",[52,16707,373],{"class":58},[52,16709,6309],{"class":58},[52,16711,16712],{"class":75},"YourBlockName",[52,16714,376],{"class":58},[52,16716,384],{"class":58},[52,16718,16719,16722,16724,16726,16729],{"class":54,"line":302},[52,16720,16721],{"class":62},"            icon",[52,16723,373],{"class":58},[52,16725,6309],{"class":58},[52,16727,16728],{"class":75},"\u003Csvg xmlns=\"http:\u002F\u002Fwww.w3.org\u002F2000\u002Fsvg\" viewBox=\"0 0 576 512\">\u003C!--! Font Awesome Pro 6.1.1 by @fontawesome - https:\u002F\u002Ffontawesome.com License - https:\u002F\u002Ffontawesome.com\u002Flicense (Commercial License) Copyright 2022 Fonticons, Inc. -->\u003Cpath d=\"M528 32H144c-26.51 0-48 21.49-48 48v256c0 26.51 21.49 48 48 48H528c26.51 0 48-21.49 48-48v-256C576 53.49 554.5 32 528 32zM223.1 96c17.68 0 32 14.33 32 32S241.7 160 223.1 160c-17.67 0-32-14.33-32-32S206.3 96 223.1 96zM494.1 311.6C491.3 316.8 485.9 320 480 320H192c-6.023 0-11.53-3.379-14.26-8.75c-2.73-5.367-2.215-11.81 1.332-16.68l70-96C252.1 194.4 256.9 192 262 192c5.111 0 9.916 2.441 12.93 6.574l22.35 30.66l62.74-94.11C362.1 130.7 367.1 128 373.3 128c5.348 0 10.34 2.672 13.31 7.125l106.7 160C496.6 300 496.9 306.3 494.1 311.6zM456 432H120c-39.7 0-72-32.3-72-72v-240C48 106.8 37.25 96 24 96S0 106.8 0 120v240C0 426.2 53.83 480 120 480h336c13.25 0 24-10.75 24-24S469.3 432 456 432z\"\u002F>\u003C\u002Fsvg>",[52,16730,6315],{"class":58},[52,16732,16733],{"class":54,"line":308},[52,16734,5398],{"class":58},[52,16736,16737,16739],{"class":54,"line":318},[52,16738,13168],{"class":58},[52,16740,283],{"class":105},[52,16742,16743],{"class":54,"line":328},[52,16744,341],{"emptyLinePlaceholder":340},[52,16746,16747,16750,16753,16755,16757,16760,16762,16765],{"class":54,"line":337},[52,16748,16749],{"class":65},"    constructor",[52,16751,16752],{"class":58},"({",[52,16754,419],{"class":986},[52,16756,408],{"class":58},[52,16758,16759],{"class":986},"api",[52,16761,408],{"class":58},[52,16763,16764],{"class":986},"config",[52,16766,16767],{"class":58}," }){\n",[52,16769,16770],{"class":54,"line":344},[52,16771,2696],{"class":58},[52,16773,16774],{"class":54,"line":354},[52,16775,341],{"emptyLinePlaceholder":340},[52,16777,16778,16781],{"class":54,"line":367},[52,16779,16780],{"class":62},"    render",[52,16782,16783],{"class":58},"(){}\n",[52,16785,16786],{"class":54,"line":387},[52,16787,341],{"emptyLinePlaceholder":340},[52,16789,16790,16793,16795,16798],{"class":54,"line":415},[52,16791,16792],{"class":62},"    save",[52,16794,932],{"class":58},[52,16796,16797],{"class":986},"blockContent",[52,16799,16800],{"class":58},"){}\n",[52,16802,16803],{"class":54,"line":427},[52,16804,536],{"class":58},[13,16806,16807,16808,16811],{},"とりあえずこれら３つがあればブロックは実装できます。",[49,16809,16810],{},"static get toolbox()","はeditor.jsが呼び出し、ツールボックスのアイコンやブロック名の表示を行います。アイコンはsvgのタグまたは、画像タグを指定します。widthとheightは50ぐらいが適切です。",[13,16813,16814,16815,16817,16818,16820],{},"コンストラクターにはeditor.jsより、このブロックの初期データ",[49,16816,11148],{},"、とeditor.jsのコアにアクセスできる",[49,16819,16759],{},"が渡されます。",[13,16822,16823,16826],{},[49,16824,16825],{},"render()","はブロックが初期化された時に最初に呼び出され、ブロックのUIビューのhtmlを返すようにします。",[13,16828,16829,16832,16833,9302,16835,16837,16838,16840],{},[49,16830,16831],{},"save(blockContent){}","はエディターの保存時に呼ばれ、このブロッククラスの値を出力します。引数の",[49,16834,16797],{},[49,16836,16825],{},"にて渡されたDOMです。このメソッドはオブジェクトを返すようにします。上記の例でいう、",[49,16839,11148],{},"の箇所を出力します。",[42,16842,16844],{"className":9608,"code":16843,"language":9610,"meta":47,"style":47},"{\n    \"id\" : \"6crsAbriK8\",\n    \"type\" : \"header\",\n    \u002F\u002F save で出力する\n    \"data\" : {\n        \"text\" : \"What does it mean clean data output\",\n        \"level\" : 3\n    }\n},\n",[49,16845,16846,16850,16868,16886,16891,16903,16921,16933,16937],{"__ignoreMap":47},[52,16847,16848],{"class":54,"line":55},[52,16849,364],{"class":58},[52,16851,16852,16854,16856,16858,16860,16862,16864,16866],{"class":54,"line":83},[52,16853,9631],{"class":58},[52,16855,13768],{"class":65},[52,16857,72],{"class":58},[52,16859,6396],{"class":58},[52,16861,3597],{"class":58},[52,16863,15829],{"class":75},[52,16865,72],{"class":58},[52,16867,384],{"class":58},[52,16869,16870,16872,16874,16876,16878,16880,16882,16884],{"class":54,"line":115},[52,16871,9631],{"class":58},[52,16873,15612],{"class":65},[52,16875,72],{"class":58},[52,16877,6396],{"class":58},[52,16879,3597],{"class":58},[52,16881,15621],{"class":75},[52,16883,72],{"class":58},[52,16885,384],{"class":58},[52,16887,16888],{"class":54,"line":142},[52,16889,16890],{"class":411},"    \u002F\u002F save で出力する\n",[52,16892,16893,16895,16897,16899,16901],{"class":54,"line":169},[52,16894,9631],{"class":58},[52,16896,11148],{"class":65},[52,16898,72],{"class":58},[52,16900,6396],{"class":58},[52,16902,10138],{"class":58},[52,16904,16905,16907,16909,16911,16913,16915,16917,16919],{"class":54,"line":302},[52,16906,14288],{"class":58},[52,16908,452],{"class":370},[52,16910,72],{"class":58},[52,16912,6396],{"class":58},[52,16914,3597],{"class":58},[52,16916,15878],{"class":75},[52,16918,72],{"class":58},[52,16920,384],{"class":58},[52,16922,16923,16925,16927,16929,16931],{"class":54,"line":308},[52,16924,14288],{"class":58},[52,16926,15664],{"class":370},[52,16928,72],{"class":58},[52,16930,6396],{"class":58},[52,16932,15895],{"class":2232},[52,16934,16935],{"class":54,"line":318},[52,16936,2696],{"class":58},[52,16938,16939,16941],{"class":54,"line":328},[52,16940,1312],{"class":58},[52,16942,384],{"class":105},[13,16944,16945],{},"とりあえず上記の４つのメソッドはブロックを作成する上で重要なものになりますので、ひとまず把握しておいてください。",[17,16947,16948],{"id":16948},"実装",[13,16950,16951],{},"とりあえずEditor.js上でブロックを選択できるようにしましょう。上記のクラスとメソッドを実装してDOMにマウントします。",[42,16953,16955],{"className":10507,"code":16954,"language":1434,"meta":47,"style":47},"class Accordion{\n    \u002F\u002F editor jsにブロックの情報を渡す\n    static get toolbox() {\n        return{\n            title: 'アコーディオン',\n            icon: 'svg' \u002F\u002F 長いので省略\n        }\n    } \n\n    constructor({ data,api,config }){\n    }\n\n    render(){}\n\n    save(blockContent){}\n}\n\nconst editor = new EditorJS({\n    holder: 'editorjs',\n    tools: { \n        accordion : {class:Accordion,inlineToolbar: true}\n    }\n});\n",[49,16956,16957,16965,16969,16981,16987,17002,17018,17022,17028,17032,17050,17054,17058,17064,17068,17078,17082,17086,17102,17116,17128,17155,17159],{"__ignoreMap":47},[52,16958,16959,16961,16963],{"class":54,"line":55},[52,16960,16669],{"class":65},[52,16962,16672],{"class":370},[52,16964,364],{"class":58},[52,16966,16967],{"class":54,"line":83},[52,16968,16679],{"class":411},[52,16970,16971,16973,16975,16977,16979],{"class":54,"line":115},[52,16972,16684],{"class":65},[52,16974,16687],{"class":65},[52,16976,16690],{"class":62},[52,16978,422],{"class":58},[52,16980,10138],{"class":58},[52,16982,16983,16985],{"class":54,"line":142},[52,16984,10166],{"class":360},[52,16986,364],{"class":58},[52,16988,16989,16991,16993,16995,16998,17000],{"class":54,"line":169},[52,16990,16705],{"class":62},[52,16992,373],{"class":58},[52,16994,6309],{"class":58},[52,16996,16997],{"class":75},"アコーディオン",[52,16999,376],{"class":58},[52,17001,384],{"class":58},[52,17003,17004,17006,17008,17010,17013,17015],{"class":54,"line":302},[52,17005,16721],{"class":62},[52,17007,373],{"class":58},[52,17009,6309],{"class":58},[52,17011,17012],{"class":75},"svg",[52,17014,376],{"class":58},[52,17016,17017],{"class":411}," \u002F\u002F 長いので省略\n",[52,17019,17020],{"class":54,"line":308},[52,17021,5398],{"class":58},[52,17023,17024,17026],{"class":54,"line":318},[52,17025,13168],{"class":58},[52,17027,283],{"class":105},[52,17029,17030],{"class":54,"line":328},[52,17031,341],{"emptyLinePlaceholder":340},[52,17033,17034,17036,17038,17040,17042,17044,17046,17048],{"class":54,"line":337},[52,17035,16749],{"class":65},[52,17037,16752],{"class":58},[52,17039,419],{"class":986},[52,17041,408],{"class":58},[52,17043,16759],{"class":986},[52,17045,408],{"class":58},[52,17047,16764],{"class":986},[52,17049,16767],{"class":58},[52,17051,17052],{"class":54,"line":344},[52,17053,2696],{"class":58},[52,17055,17056],{"class":54,"line":354},[52,17057,341],{"emptyLinePlaceholder":340},[52,17059,17060,17062],{"class":54,"line":367},[52,17061,16780],{"class":62},[52,17063,16783],{"class":58},[52,17065,17066],{"class":54,"line":387},[52,17067,341],{"emptyLinePlaceholder":340},[52,17069,17070,17072,17074,17076],{"class":54,"line":415},[52,17071,16792],{"class":62},[52,17073,932],{"class":58},[52,17075,16797],{"class":986},[52,17077,16800],{"class":58},[52,17079,17080],{"class":54,"line":427},[52,17081,536],{"class":58},[52,17083,17084],{"class":54,"line":435},[52,17085,341],{"emptyLinePlaceholder":340},[52,17087,17088,17090,17092,17094,17096,17098,17100],{"class":54,"line":446},[52,17089,10903],{"class":65},[52,17091,16572],{"class":105},[52,17093,69],{"class":58},[52,17095,2522],{"class":58},[52,17097,16579],{"class":418},[52,17099,932],{"class":105},[52,17101,364],{"class":58},[52,17103,17104,17106,17108,17110,17112,17114],{"class":54,"line":480},[52,17105,16588],{"class":62},[52,17107,373],{"class":58},[52,17109,6309],{"class":58},[52,17111,16315],{"class":75},[52,17113,376],{"class":58},[52,17115,384],{"class":58},[52,17117,17118,17121,17123,17126],{"class":54,"line":509},[52,17119,17120],{"class":62},"    tools",[52,17122,373],{"class":58},[52,17124,17125],{"class":58}," {",[52,17127,283],{"class":105},[52,17129,17130,17133,17135,17137,17139,17141,17144,17146,17149,17151,17153],{"class":54,"line":539},[52,17131,17132],{"class":62},"        accordion ",[52,17134,373],{"class":58},[52,17136,17125],{"class":58},[52,17138,16669],{"class":62},[52,17140,373],{"class":58},[52,17142,17143],{"class":105},"Accordion",[52,17145,408],{"class":58},[52,17147,17148],{"class":62},"inlineToolbar",[52,17150,373],{"class":58},[52,17152,10386],{"class":6196},[52,17154,536],{"class":58},[52,17156,17157],{"class":54,"line":547},[52,17158,2696],{"class":58},[52,17160,17161,17163,17165],{"class":54,"line":553},[52,17162,1312],{"class":58},[52,17164,938],{"class":105},[52,17166,1007],{"class":58},[13,17168,17169,17172,17173,17176,17177,17180],{},[49,17170,17171],{},"holder","のIDに指定したDOMにEditor.jsがマウントされます。そして",[49,17174,17175],{},"tools","に追加したいカスタムブロックのクラスを指定します。後でも解説しますが、インラインツールバーという斜体や太字などを使用できるようにする場合は",[49,17178,17179],{},"inlineToolbar:true","を指定します。",[729,17182],{":src":17183,":width":732},"'editorjs-repeatable\u002Fset1.png'",[13,17185,17186,17187,17189,17190,17193],{},"この時にブロックをリストから選択した時にeditor.jsにマウントされます。この時に",[49,17188,16825],{},"でreturnされた",[49,17191,17192],{},"Element","がマウントされます。まずは要素を追加するボタンを用意しましょう。",[1499,17195,17196],{"id":17196},"追加ボタンのレンダリング",[13,17198,17199,17200,17203,17204,17189,17206,17208,17209,17211],{},"ブロックを追加したい際にまずブロックの",[49,17201,17202],{},"constructor","が呼ばれます。そして",[49,17205,16825],{},[49,17207,17192],{},"がマウントされます。今は追加ですが、後ほど保存した値からブロックをレンダーする処理を追加することもあるので、",[49,17210,17202],{},"で処理処理を記述します。",[42,17213,17215],{"className":10507,"code":17214,"language":1434,"meta":47,"style":47},"class Accordion{\n    static get toolbox() {\n        return {\n          title: 'アコーディオン',\n          icon: 'svg'\n        };\n    }\n\n    constructor({data,config,api}){\n        const accordionData = data.itmes || [];\n        this.createParentWrapper();\n    }\n\n    render(){}\n\n    createParentWrapper(){}\n}\n",[49,17216,17217,17225,17237,17243,17258,17271,17276,17280,17284,17303,17328,17340,17344,17348,17354,17358,17365],{"__ignoreMap":47},[52,17218,17219,17221,17223],{"class":54,"line":55},[52,17220,16669],{"class":65},[52,17222,16672],{"class":370},[52,17224,364],{"class":58},[52,17226,17227,17229,17231,17233,17235],{"class":54,"line":83},[52,17228,16684],{"class":65},[52,17230,16687],{"class":65},[52,17232,16690],{"class":62},[52,17234,422],{"class":58},[52,17236,10138],{"class":58},[52,17238,17239,17241],{"class":54,"line":115},[52,17240,10166],{"class":360},[52,17242,10138],{"class":58},[52,17244,17245,17248,17250,17252,17254,17256],{"class":54,"line":142},[52,17246,17247],{"class":62},"          title",[52,17249,373],{"class":58},[52,17251,6309],{"class":58},[52,17253,16997],{"class":75},[52,17255,376],{"class":58},[52,17257,384],{"class":58},[52,17259,17260,17263,17265,17267,17269],{"class":54,"line":169},[52,17261,17262],{"class":62},"          icon",[52,17264,373],{"class":58},[52,17266,6309],{"class":58},[52,17268,17012],{"class":75},[52,17270,6315],{"class":58},[52,17272,17273],{"class":54,"line":302},[52,17274,17275],{"class":58},"        };\n",[52,17277,17278],{"class":54,"line":308},[52,17279,2696],{"class":58},[52,17281,17282],{"class":54,"line":318},[52,17283,341],{"emptyLinePlaceholder":340},[52,17285,17286,17288,17290,17292,17294,17296,17298,17300],{"class":54,"line":328},[52,17287,16749],{"class":65},[52,17289,16752],{"class":58},[52,17291,11148],{"class":986},[52,17293,408],{"class":58},[52,17295,16764],{"class":986},[52,17297,408],{"class":58},[52,17299,16759],{"class":986},[52,17301,17302],{"class":58},"}){\n",[52,17304,17305,17308,17311,17313,17315,17317,17320,17323,17326],{"class":54,"line":337},[52,17306,17307],{"class":65},"        const",[52,17309,17310],{"class":105}," accordionData",[52,17312,951],{"class":58},[52,17314,419],{"class":105},[52,17316,957],{"class":58},[52,17318,17319],{"class":105},"itmes",[52,17321,17322],{"class":58}," ||",[52,17324,17325],{"class":62}," []",[52,17327,1007],{"class":58},[52,17329,17330,17333,17336,17338],{"class":54,"line":344},[52,17331,17332],{"class":58},"        this.",[52,17334,17335],{"class":418},"createParentWrapper",[52,17337,422],{"class":62},[52,17339,1007],{"class":58},[52,17341,17342],{"class":54,"line":354},[52,17343,2696],{"class":58},[52,17345,17346],{"class":54,"line":367},[52,17347,341],{"emptyLinePlaceholder":340},[52,17349,17350,17352],{"class":54,"line":387},[52,17351,16780],{"class":62},[52,17353,16783],{"class":58},[52,17355,17356],{"class":54,"line":415},[52,17357,341],{"emptyLinePlaceholder":340},[52,17359,17360,17363],{"class":54,"line":427},[52,17361,17362],{"class":62},"    createParentWrapper",[52,17364,16783],{"class":58},[52,17366,17367],{"class":54,"line":435},[52,17368,536],{"class":58},[13,17370,17371,17374],{},[49,17372,17373],{},"createParentWrapper()","では最終的にアコーディオンのDOMを作成します。",[42,17376,17378],{"className":10507,"code":17377,"language":1434,"meta":47,"style":47},"createParentWrapper(){\n    const wrapper = document.createElement(\"div\");\n    const parentId = \"parent-\" + this.randomID();\n    wrapper.classList.add(\"accordion\");\n    wrapper.id = parentId;\n    wrapper.innerHTML = `\n    \u003Cbutton class=\"accordion-add btn btn-success d-block btn-sm\">\n    追加する\n    \u003C\u002Fbutton>\n    `;\n    this.wrapper = wrapper;\n}\n",[49,17379,17380,17388,17416,17443,17471,17485,17499,17504,17509,17514,17521,17534],{"__ignoreMap":47},[52,17381,17382,17384,17386],{"class":54,"line":55},[52,17383,17335],{"class":418},[52,17385,422],{"class":105},[52,17387,364],{"class":58},[52,17389,17390,17392,17395,17397,17399,17401,17404,17406,17408,17410,17412,17414],{"class":54,"line":83},[52,17391,12554],{"class":65},[52,17393,17394],{"class":105}," wrapper",[52,17396,951],{"class":58},[52,17398,2437],{"class":105},[52,17400,957],{"class":58},[52,17402,17403],{"class":418},"createElement",[52,17405,932],{"class":62},[52,17407,72],{"class":58},[52,17409,8503],{"class":75},[52,17411,72],{"class":58},[52,17413,938],{"class":62},[52,17415,1007],{"class":58},[52,17417,17418,17420,17423,17425,17427,17430,17432,17434,17436,17439,17441],{"class":54,"line":115},[52,17419,12554],{"class":65},[52,17421,17422],{"class":105}," parentId",[52,17424,951],{"class":58},[52,17426,3597],{"class":58},[52,17428,17429],{"class":75},"parent-",[52,17431,72],{"class":58},[52,17433,12669],{"class":58},[52,17435,10407],{"class":58},[52,17437,17438],{"class":418},"randomID",[52,17440,422],{"class":62},[52,17442,1007],{"class":58},[52,17444,17445,17448,17450,17453,17455,17458,17460,17462,17465,17467,17469],{"class":54,"line":142},[52,17446,17447],{"class":105},"    wrapper",[52,17449,957],{"class":58},[52,17451,17452],{"class":105},"classList",[52,17454,957],{"class":58},[52,17456,17457],{"class":418},"add",[52,17459,932],{"class":62},[52,17461,72],{"class":58},[52,17463,17464],{"class":75},"accordion",[52,17466,72],{"class":58},[52,17468,938],{"class":62},[52,17470,1007],{"class":58},[52,17472,17473,17475,17477,17479,17481,17483],{"class":54,"line":169},[52,17474,17447],{"class":105},[52,17476,957],{"class":58},[52,17478,13768],{"class":105},[52,17480,951],{"class":58},[52,17482,17422],{"class":105},[52,17484,1007],{"class":58},[52,17486,17487,17489,17491,17494,17496],{"class":54,"line":302},[52,17488,17447],{"class":105},[52,17490,957],{"class":58},[52,17492,17493],{"class":105},"innerHTML",[52,17495,951],{"class":58},[52,17497,17498],{"class":58}," `\n",[52,17500,17501],{"class":54,"line":308},[52,17502,17503],{"class":75},"    \u003Cbutton class=\"accordion-add btn btn-success d-block btn-sm\">\n",[52,17505,17506],{"class":54,"line":318},[52,17507,17508],{"class":75},"    追加する\n",[52,17510,17511],{"class":54,"line":328},[52,17512,17513],{"class":75},"    \u003C\u002Fbutton>\n",[52,17515,17516,17519],{"class":54,"line":337},[52,17517,17518],{"class":58},"    `",[52,17520,1007],{"class":58},[52,17522,17523,17525,17528,17530,17532],{"class":54,"line":344},[52,17524,1052],{"class":58},[52,17526,17527],{"class":105},"wrapper",[52,17529,951],{"class":58},[52,17531,17394],{"class":105},[52,17533,1007],{"class":58},[52,17535,17536],{"class":54,"line":354},[52,17537,536],{"class":58},[13,17539,17540],{},"こうすると追加するボタンが追加されたはずです。",[729,17542],{":src":17543,":width":732},"'editorjs-repeatable\u002Fset2.png'",[13,17545,17546],{},"この追加するボタンを押したらアコーディオンの要素が追加されるようにします。",[42,17548,17550],{"className":10507,"code":17549,"language":1434,"meta":47,"style":47},"randomID(){\n    let result           = '';\n    let characters       = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz';\n    let charactersLength = characters.length;\n    for ( let i = 0; i \u003C 10; i++ ) {\n        result += characters.charAt(Math.floor(Math.random() * \n        charactersLength));\n    }\n    return result;\n}\n\ncreateParentWrapper(){\n    const wrapper = document.createElement(\"div\");\n    const parentId = \"parent-\" + this.randomID();\n    wrapper.classList.add(\"accordion\");\n    wrapper.id = parentId;\n    wrapper.innerHTML = `\n    \u003Cbutton class=\"accordion-add btn btn-success d-block btn-sm\">\n    追加する\n    \u003C\u002Fbutton>\n    `;\n\n    wrapper.querySelector(\".accordion-add\").addEventListener(\"click\",()=>{\n        this.addItemEle();\n    });\n    this.wrapper = wrapper;\n}\n\naddItemEle(title=\"\",content=\"\"){\n    this.wrapper.insertBefore(this.generateItemEle(title,content),this.wrapper.querySelector(\".accordion-add\"))\n}\n\ngenerateItemEle(title,content){}\n",[49,17551,17552,17560,17575,17594,17612,17649,17687,17696,17700,17708,17712,17716,17724,17750,17774,17798,17812,17824,17828,17832,17836,17842,17846,17887,17898,17906,17918,17922,17926,17950,17998,18002,18006],{"__ignoreMap":47},[52,17553,17554,17556,17558],{"class":54,"line":55},[52,17555,17438],{"class":418},[52,17557,422],{"class":105},[52,17559,364],{"class":58},[52,17561,17562,17564,17567,17570,17573],{"class":54,"line":83},[52,17563,945],{"class":65},[52,17565,17566],{"class":105}," result",[52,17568,17569],{"class":58},"           =",[52,17571,17572],{"class":58}," ''",[52,17574,1007],{"class":58},[52,17576,17577,17579,17582,17585,17587,17590,17592],{"class":54,"line":115},[52,17578,945],{"class":65},[52,17580,17581],{"class":105}," characters",[52,17583,17584],{"class":58},"       =",[52,17586,6309],{"class":58},[52,17588,17589],{"class":75},"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz",[52,17591,376],{"class":58},[52,17593,1007],{"class":58},[52,17595,17596,17598,17601,17603,17605,17607,17610],{"class":54,"line":142},[52,17597,945],{"class":65},[52,17599,17600],{"class":105}," charactersLength",[52,17602,951],{"class":58},[52,17604,17581],{"class":105},[52,17606,957],{"class":58},[52,17608,17609],{"class":105},"length",[52,17611,1007],{"class":58},[52,17613,17614,17616,17619,17621,17624,17626,17628,17630,17632,17634,17637,17639,17641,17644,17647],{"class":54,"line":169},[52,17615,3647],{"class":360},[52,17617,17618],{"class":62}," ( ",[52,17620,1259],{"class":65},[52,17622,17623],{"class":105}," i",[52,17625,951],{"class":58},[52,17627,3333],{"class":2232},[52,17629,3661],{"class":58},[52,17631,17623],{"class":105},[52,17633,86],{"class":58},[52,17635,17636],{"class":2232}," 10",[52,17638,3661],{"class":58},[52,17640,17623],{"class":105},[52,17642,17643],{"class":58},"++",[52,17645,17646],{"class":62}," ) ",[52,17648,364],{"class":58},[52,17650,17651,17654,17656,17658,17660,17663,17665,17667,17669,17672,17674,17676,17678,17681,17683,17685],{"class":54,"line":302},[52,17652,17653],{"class":105},"        result",[52,17655,4883],{"class":58},[52,17657,17581],{"class":105},[52,17659,957],{"class":58},[52,17661,17662],{"class":418},"charAt",[52,17664,932],{"class":62},[52,17666,3705],{"class":105},[52,17668,957],{"class":58},[52,17670,17671],{"class":418},"floor",[52,17673,932],{"class":62},[52,17675,3705],{"class":105},[52,17677,957],{"class":58},[52,17679,17680],{"class":418},"random",[52,17682,12575],{"class":62},[52,17684,3702],{"class":58},[52,17686,283],{"class":62},[52,17688,17689,17692,17694],{"class":54,"line":308},[52,17690,17691],{"class":105},"        charactersLength",[52,17693,3741],{"class":62},[52,17695,1007],{"class":58},[52,17697,17698],{"class":54,"line":318},[52,17699,2696],{"class":58},[52,17701,17702,17704,17706],{"class":54,"line":328},[52,17703,11916],{"class":360},[52,17705,17566],{"class":105},[52,17707,1007],{"class":58},[52,17709,17710],{"class":54,"line":337},[52,17711,536],{"class":58},[52,17713,17714],{"class":54,"line":344},[52,17715,341],{"emptyLinePlaceholder":340},[52,17717,17718,17720,17722],{"class":54,"line":354},[52,17719,17335],{"class":418},[52,17721,422],{"class":105},[52,17723,364],{"class":58},[52,17725,17726,17728,17730,17732,17734,17736,17738,17740,17742,17744,17746,17748],{"class":54,"line":367},[52,17727,12554],{"class":65},[52,17729,17394],{"class":105},[52,17731,951],{"class":58},[52,17733,2437],{"class":105},[52,17735,957],{"class":58},[52,17737,17403],{"class":418},[52,17739,932],{"class":62},[52,17741,72],{"class":58},[52,17743,8503],{"class":75},[52,17745,72],{"class":58},[52,17747,938],{"class":62},[52,17749,1007],{"class":58},[52,17751,17752,17754,17756,17758,17760,17762,17764,17766,17768,17770,17772],{"class":54,"line":387},[52,17753,12554],{"class":65},[52,17755,17422],{"class":105},[52,17757,951],{"class":58},[52,17759,3597],{"class":58},[52,17761,17429],{"class":75},[52,17763,72],{"class":58},[52,17765,12669],{"class":58},[52,17767,10407],{"class":58},[52,17769,17438],{"class":418},[52,17771,422],{"class":62},[52,17773,1007],{"class":58},[52,17775,17776,17778,17780,17782,17784,17786,17788,17790,17792,17794,17796],{"class":54,"line":415},[52,17777,17447],{"class":105},[52,17779,957],{"class":58},[52,17781,17452],{"class":105},[52,17783,957],{"class":58},[52,17785,17457],{"class":418},[52,17787,932],{"class":62},[52,17789,72],{"class":58},[52,17791,17464],{"class":75},[52,17793,72],{"class":58},[52,17795,938],{"class":62},[52,17797,1007],{"class":58},[52,17799,17800,17802,17804,17806,17808,17810],{"class":54,"line":427},[52,17801,17447],{"class":105},[52,17803,957],{"class":58},[52,17805,13768],{"class":105},[52,17807,951],{"class":58},[52,17809,17422],{"class":105},[52,17811,1007],{"class":58},[52,17813,17814,17816,17818,17820,17822],{"class":54,"line":435},[52,17815,17447],{"class":105},[52,17817,957],{"class":58},[52,17819,17493],{"class":105},[52,17821,951],{"class":58},[52,17823,17498],{"class":58},[52,17825,17826],{"class":54,"line":446},[52,17827,17503],{"class":75},[52,17829,17830],{"class":54,"line":480},[52,17831,17508],{"class":75},[52,17833,17834],{"class":54,"line":509},[52,17835,17513],{"class":75},[52,17837,17838,17840],{"class":54,"line":539},[52,17839,17518],{"class":58},[52,17841,1007],{"class":58},[52,17843,17844],{"class":54,"line":547},[52,17845,341],{"emptyLinePlaceholder":340},[52,17847,17848,17850,17852,17855,17857,17859,17862,17864,17866,17868,17871,17873,17875,17878,17880,17883,17885],{"class":54,"line":553},[52,17849,17447],{"class":105},[52,17851,957],{"class":58},[52,17853,17854],{"class":418},"querySelector",[52,17856,932],{"class":62},[52,17858,72],{"class":58},[52,17860,17861],{"class":75},".accordion-add",[52,17863,72],{"class":58},[52,17865,938],{"class":62},[52,17867,957],{"class":58},[52,17869,17870],{"class":418},"addEventListener",[52,17872,932],{"class":62},[52,17874,72],{"class":58},[52,17876,17877],{"class":75},"click",[52,17879,72],{"class":58},[52,17881,17882],{"class":58},",()",[52,17884,990],{"class":65},[52,17886,364],{"class":58},[52,17888,17889,17891,17894,17896],{"class":54,"line":559},[52,17890,17332],{"class":58},[52,17892,17893],{"class":418},"addItemEle",[52,17895,422],{"class":62},[52,17897,1007],{"class":58},[52,17899,17900,17902,17904],{"class":54,"line":564},[52,17901,13168],{"class":58},[52,17903,938],{"class":62},[52,17905,1007],{"class":58},[52,17907,17908,17910,17912,17914,17916],{"class":54,"line":569},[52,17909,1052],{"class":58},[52,17911,17527],{"class":105},[52,17913,951],{"class":58},[52,17915,17394],{"class":105},[52,17917,1007],{"class":58},[52,17919,17920],{"class":54,"line":1106},[52,17921,536],{"class":58},[52,17923,17924],{"class":54,"line":1135},[52,17925,341],{"emptyLinePlaceholder":340},[52,17927,17928,17930,17933,17935,17938,17940,17942,17944,17946,17948],{"class":54,"line":1164},[52,17929,17893],{"class":418},[52,17931,17932],{"class":105},"(title",[52,17934,69],{"class":58},[52,17936,17937],{"class":58},"\"\"",[52,17939,408],{"class":58},[52,17941,15060],{"class":105},[52,17943,69],{"class":58},[52,17945,17937],{"class":58},[52,17947,938],{"class":105},[52,17949,364],{"class":58},[52,17951,17952,17954,17956,17958,17961,17963,17965,17968,17970,17972,17974,17976,17978,17981,17983,17985,17987,17989,17991,17993,17995],{"class":54,"line":1193},[52,17953,1052],{"class":58},[52,17955,17527],{"class":105},[52,17957,957],{"class":58},[52,17959,17960],{"class":418},"insertBefore",[52,17962,932],{"class":62},[52,17964,1371],{"class":58},[52,17966,17967],{"class":418},"generateItemEle",[52,17969,932],{"class":62},[52,17971,2195],{"class":105},[52,17973,408],{"class":58},[52,17975,15060],{"class":105},[52,17977,938],{"class":62},[52,17979,17980],{"class":58},",this.",[52,17982,17527],{"class":105},[52,17984,957],{"class":58},[52,17986,17854],{"class":418},[52,17988,932],{"class":62},[52,17990,72],{"class":58},[52,17992,17861],{"class":75},[52,17994,72],{"class":58},[52,17996,17997],{"class":62},"))\n",[52,17999,18000],{"class":54,"line":1200},[52,18001,536],{"class":58},[52,18003,18004],{"class":54,"line":1205},[52,18005,341],{"emptyLinePlaceholder":340},[52,18007,18008,18010,18012,18014,18017],{"class":54,"line":1210},[52,18009,17967],{"class":418},[52,18011,17932],{"class":105},[52,18013,408],{"class":58},[52,18015,18016],{"class":105},"content)",[52,18018,18019],{"class":58},"{}\n",[13,18021,18022,18025,18026,18029],{},[49,18023,18024],{},"addItemEle()","を呼ぶことでアイテムのDOMが挿入されるようにします。DOM自体は",[49,18027,18028],{},"generateItemEle()","で生成されるようにします。",[13,18031,18032,18033,18035],{},"次は",[49,18034,18028],{},"で生成されるDOMの記述を作成します。",[1499,18037,18038],{"id":18038},"要素の生成",[13,18040,18041,18043,18044,18047,18048,4793,18050,18052,18053,18055],{},[49,18042,18028],{},"では",[49,18045,18046],{},"document.createElement","を使用してbootstrap通りのHTMLを作成します。",[49,18049,2195],{},[49,18051,15060],{},"ではリッチテキストを挿入するために",[49,18054,17493],{},"で入れています。実際に表示する際はXSSの危険があるので入力値の検証が必要です。",[42,18057,18059],{"className":10507,"code":18058,"language":1434,"meta":47,"style":47},"generateItemEle(title,content){\n        const itemWrapper = document.createElement(\"div\");\n        const wrapperId = this.randomID();\n        itemWrapper.id = wrapperId;\n        itemWrapper.classList.add(\"accordion-item\");\n\n        const itemHeaderWrapper = document.createElement(\"h2\");\n        itemHeaderWrapper.classList.add(\"accordion-header\");\n        const collapseId = wrapperId+\"-collapse\";\n        itemHeaderWrapper.innerHTML = `\n        \u003Cbutton class=\"accordion-button\" type=\"button\" data-bs-toggle=\"collapse\">\n            \u003Cdiv class=\"accordion-button-wrapper\">${title}\u003C\u002Fdiv>\n        \u003C\u002Fbutton>\n        `;\n        itemHeaderWrapper.querySelector('.accordion-button-wrapper').contentEditable = true;\n\n        const itemBodyWrapper = document.createElement(\"div\");\n        itemBodyWrapper.classList.add(\"accordion-collapse\",\"collapse\",\"show\");\n        itemBodyWrapper.id = collapseId;\n        itemBodyWrapper.innerHTML = `\n        \u003Cdiv class=\"accordion-body\">\n            \u003Cp class=\"accordion-body-input\">${content}\u003C\u002Fp>\n            \u003Cdiv class=\"mt-2\">\n            \u003Cbutton class=\"accordion-delete btn btn-danger btn-sm\">削除\u003C\u002Fbutton>\n            \u003C\u002Fdiv>\n        \u003C\u002Fdiv>\n        `;\n        itemBodyWrapper.querySelector('.accordion-body-input').contentEditable = true;\n        itemBodyWrapper.querySelector('.accordion-delete').addEventListener('click',()=>{\n            if(window.confirm(\"削除してもよろしいですか？\")) itemWrapper.remove();\n        });\n\n        itemWrapper.appendChild(itemHeaderWrapper);\n        itemWrapper.appendChild(itemBodyWrapper);\n        return itemWrapper;\n        \u002F*\n        生成イメージ\n        \u003Cdiv class=\"accordion-item\">\n            \u003Ch2 class=\"accordion-header\" id=\"headingOne\">\n                \u003Cbutton class=\"accordion-button\" type=\"button\" data-bs-toggle=\"collapse\" >\n                    \u003Cdiv class=\"accordion-button-wrapper\">${title}\u003C\u002Fdiv>\n                \u003C\u002Fbutton>\n            \u003C\u002Fh2>\n            \u003Cdiv id=\"${wrapperId}-collaps\" class=\"accordion-collapse collapse show\">\n                \u003Cdiv class=\"accordion-body\">\n                    \u003Cp class=\"accordion-body-input\">${content}\u003C\u002Fp>\n                    \u003Cbutton class=\"accordion-delete btn btn-danger mt-2 btn-sm\">\n                    削除\n                    \u003C\u002Fbutton>\n                \u003C\u002Fdiv>\n            \u003C\u002Fdiv>\n        \u003C\u002Fdiv>\n        *\u002F\n    }\n",[49,18060,18061,18073,18100,18117,18132,18157,18161,18188,18214,18236,18248,18253,18268,18273,18280,18310,18314,18341,18385,18399,18411,18416,18430,18435,18440,18445,18450,18456,18485,18522,18560,18569,18573,18591,18608,18616,18621,18626,18631,18636,18641,18646,18651,18656,18661,18666,18671,18676,18681,18686,18691,18695,18699,18704],{"__ignoreMap":47},[52,18062,18063,18065,18067,18069,18071],{"class":54,"line":55},[52,18064,17967],{"class":418},[52,18066,17932],{"class":105},[52,18068,408],{"class":58},[52,18070,18016],{"class":105},[52,18072,364],{"class":58},[52,18074,18075,18077,18080,18082,18084,18086,18088,18090,18092,18094,18096,18098],{"class":54,"line":83},[52,18076,17307],{"class":65},[52,18078,18079],{"class":105}," itemWrapper",[52,18081,951],{"class":58},[52,18083,2437],{"class":105},[52,18085,957],{"class":58},[52,18087,17403],{"class":418},[52,18089,932],{"class":62},[52,18091,72],{"class":58},[52,18093,8503],{"class":75},[52,18095,72],{"class":58},[52,18097,938],{"class":62},[52,18099,1007],{"class":58},[52,18101,18102,18104,18107,18109,18111,18113,18115],{"class":54,"line":115},[52,18103,17307],{"class":65},[52,18105,18106],{"class":105}," wrapperId",[52,18108,951],{"class":58},[52,18110,10407],{"class":58},[52,18112,17438],{"class":418},[52,18114,422],{"class":62},[52,18116,1007],{"class":58},[52,18118,18119,18122,18124,18126,18128,18130],{"class":54,"line":142},[52,18120,18121],{"class":105},"        itemWrapper",[52,18123,957],{"class":58},[52,18125,13768],{"class":105},[52,18127,951],{"class":58},[52,18129,18106],{"class":105},[52,18131,1007],{"class":58},[52,18133,18134,18136,18138,18140,18142,18144,18146,18148,18151,18153,18155],{"class":54,"line":169},[52,18135,18121],{"class":105},[52,18137,957],{"class":58},[52,18139,17452],{"class":105},[52,18141,957],{"class":58},[52,18143,17457],{"class":418},[52,18145,932],{"class":62},[52,18147,72],{"class":58},[52,18149,18150],{"class":75},"accordion-item",[52,18152,72],{"class":58},[52,18154,938],{"class":62},[52,18156,1007],{"class":58},[52,18158,18159],{"class":54,"line":302},[52,18160,341],{"emptyLinePlaceholder":340},[52,18162,18163,18165,18168,18170,18172,18174,18176,18178,18180,18182,18184,18186],{"class":54,"line":308},[52,18164,17307],{"class":65},[52,18166,18167],{"class":105}," itemHeaderWrapper",[52,18169,951],{"class":58},[52,18171,2437],{"class":105},[52,18173,957],{"class":58},[52,18175,17403],{"class":418},[52,18177,932],{"class":62},[52,18179,72],{"class":58},[52,18181,17],{"class":75},[52,18183,72],{"class":58},[52,18185,938],{"class":62},[52,18187,1007],{"class":58},[52,18189,18190,18193,18195,18197,18199,18201,18203,18205,18208,18210,18212],{"class":54,"line":318},[52,18191,18192],{"class":105},"        itemHeaderWrapper",[52,18194,957],{"class":58},[52,18196,17452],{"class":105},[52,18198,957],{"class":58},[52,18200,17457],{"class":418},[52,18202,932],{"class":62},[52,18204,72],{"class":58},[52,18206,18207],{"class":75},"accordion-header",[52,18209,72],{"class":58},[52,18211,938],{"class":62},[52,18213,1007],{"class":58},[52,18215,18216,18218,18221,18223,18225,18227,18229,18232,18234],{"class":54,"line":328},[52,18217,17307],{"class":65},[52,18219,18220],{"class":105}," collapseId",[52,18222,951],{"class":58},[52,18224,18106],{"class":105},[52,18226,3735],{"class":58},[52,18228,72],{"class":58},[52,18230,18231],{"class":75},"-collapse",[52,18233,72],{"class":58},[52,18235,1007],{"class":58},[52,18237,18238,18240,18242,18244,18246],{"class":54,"line":337},[52,18239,18192],{"class":105},[52,18241,957],{"class":58},[52,18243,17493],{"class":105},[52,18245,951],{"class":58},[52,18247,17498],{"class":58},[52,18249,18250],{"class":54,"line":344},[52,18251,18252],{"class":75},"        \u003Cbutton class=\"accordion-button\" type=\"button\" data-bs-toggle=\"collapse\">\n",[52,18254,18255,18258,18261,18263,18265],{"class":54,"line":354},[52,18256,18257],{"class":75},"            \u003Cdiv class=\"accordion-button-wrapper\">",[52,18259,18260],{"class":58},"${",[52,18262,2195],{"class":105},[52,18264,1312],{"class":58},[52,18266,18267],{"class":75},"\u003C\u002Fdiv>\n",[52,18269,18270],{"class":54,"line":367},[52,18271,18272],{"class":75},"        \u003C\u002Fbutton>\n",[52,18274,18275,18278],{"class":54,"line":387},[52,18276,18277],{"class":58},"        `",[52,18279,1007],{"class":58},[52,18281,18282,18284,18286,18288,18290,18292,18295,18297,18299,18301,18304,18306,18308],{"class":54,"line":415},[52,18283,18192],{"class":105},[52,18285,957],{"class":58},[52,18287,17854],{"class":418},[52,18289,932],{"class":62},[52,18291,376],{"class":58},[52,18293,18294],{"class":75},".accordion-button-wrapper",[52,18296,376],{"class":58},[52,18298,938],{"class":62},[52,18300,957],{"class":58},[52,18302,18303],{"class":105},"contentEditable",[52,18305,951],{"class":58},[52,18307,10386],{"class":6196},[52,18309,1007],{"class":58},[52,18311,18312],{"class":54,"line":427},[52,18313,341],{"emptyLinePlaceholder":340},[52,18315,18316,18318,18321,18323,18325,18327,18329,18331,18333,18335,18337,18339],{"class":54,"line":435},[52,18317,17307],{"class":65},[52,18319,18320],{"class":105}," itemBodyWrapper",[52,18322,951],{"class":58},[52,18324,2437],{"class":105},[52,18326,957],{"class":58},[52,18328,17403],{"class":418},[52,18330,932],{"class":62},[52,18332,72],{"class":58},[52,18334,8503],{"class":75},[52,18336,72],{"class":58},[52,18338,938],{"class":62},[52,18340,1007],{"class":58},[52,18342,18343,18346,18348,18350,18352,18354,18356,18358,18361,18363,18365,18367,18370,18372,18374,18376,18379,18381,18383],{"class":54,"line":446},[52,18344,18345],{"class":105},"        itemBodyWrapper",[52,18347,957],{"class":58},[52,18349,17452],{"class":105},[52,18351,957],{"class":58},[52,18353,17457],{"class":418},[52,18355,932],{"class":62},[52,18357,72],{"class":58},[52,18359,18360],{"class":75},"accordion-collapse",[52,18362,72],{"class":58},[52,18364,408],{"class":58},[52,18366,72],{"class":58},[52,18368,18369],{"class":75},"collapse",[52,18371,72],{"class":58},[52,18373,408],{"class":58},[52,18375,72],{"class":58},[52,18377,18378],{"class":75},"show",[52,18380,72],{"class":58},[52,18382,938],{"class":62},[52,18384,1007],{"class":58},[52,18386,18387,18389,18391,18393,18395,18397],{"class":54,"line":480},[52,18388,18345],{"class":105},[52,18390,957],{"class":58},[52,18392,13768],{"class":105},[52,18394,951],{"class":58},[52,18396,18220],{"class":105},[52,18398,1007],{"class":58},[52,18400,18401,18403,18405,18407,18409],{"class":54,"line":509},[52,18402,18345],{"class":105},[52,18404,957],{"class":58},[52,18406,17493],{"class":105},[52,18408,951],{"class":58},[52,18410,17498],{"class":58},[52,18412,18413],{"class":54,"line":539},[52,18414,18415],{"class":75},"        \u003Cdiv class=\"accordion-body\">\n",[52,18417,18418,18421,18423,18425,18427],{"class":54,"line":547},[52,18419,18420],{"class":75},"            \u003Cp class=\"accordion-body-input\">",[52,18422,18260],{"class":58},[52,18424,15060],{"class":105},[52,18426,1312],{"class":58},[52,18428,18429],{"class":75},"\u003C\u002Fp>\n",[52,18431,18432],{"class":54,"line":553},[52,18433,18434],{"class":75},"            \u003Cdiv class=\"mt-2\">\n",[52,18436,18437],{"class":54,"line":559},[52,18438,18439],{"class":75},"            \u003Cbutton class=\"accordion-delete btn btn-danger btn-sm\">削除\u003C\u002Fbutton>\n",[52,18441,18442],{"class":54,"line":564},[52,18443,18444],{"class":75},"            \u003C\u002Fdiv>\n",[52,18446,18447],{"class":54,"line":569},[52,18448,18449],{"class":75},"        \u003C\u002Fdiv>\n",[52,18451,18452,18454],{"class":54,"line":1106},[52,18453,18277],{"class":58},[52,18455,1007],{"class":58},[52,18457,18458,18460,18462,18464,18466,18468,18471,18473,18475,18477,18479,18481,18483],{"class":54,"line":1135},[52,18459,18345],{"class":105},[52,18461,957],{"class":58},[52,18463,17854],{"class":418},[52,18465,932],{"class":62},[52,18467,376],{"class":58},[52,18469,18470],{"class":75},".accordion-body-input",[52,18472,376],{"class":58},[52,18474,938],{"class":62},[52,18476,957],{"class":58},[52,18478,18303],{"class":105},[52,18480,951],{"class":58},[52,18482,10386],{"class":6196},[52,18484,1007],{"class":58},[52,18486,18487,18489,18491,18493,18495,18497,18500,18502,18504,18506,18508,18510,18512,18514,18516,18518,18520],{"class":54,"line":1164},[52,18488,18345],{"class":105},[52,18490,957],{"class":58},[52,18492,17854],{"class":418},[52,18494,932],{"class":62},[52,18496,376],{"class":58},[52,18498,18499],{"class":75},".accordion-delete",[52,18501,376],{"class":58},[52,18503,938],{"class":62},[52,18505,957],{"class":58},[52,18507,17870],{"class":418},[52,18509,932],{"class":62},[52,18511,376],{"class":58},[52,18513,17877],{"class":75},[52,18515,376],{"class":58},[52,18517,17882],{"class":58},[52,18519,990],{"class":65},[52,18521,364],{"class":58},[52,18523,18524,18527,18529,18531,18533,18536,18538,18540,18543,18545,18548,18551,18553,18556,18558],{"class":54,"line":1193},[52,18525,18526],{"class":360},"            if",[52,18528,932],{"class":62},[52,18530,2569],{"class":105},[52,18532,957],{"class":58},[52,18534,18535],{"class":418},"confirm",[52,18537,932],{"class":62},[52,18539,72],{"class":58},[52,18541,18542],{"class":75},"削除してもよろしいですか？",[52,18544,72],{"class":58},[52,18546,18547],{"class":62},")) ",[52,18549,18550],{"class":105},"itemWrapper",[52,18552,957],{"class":58},[52,18554,18555],{"class":418},"remove",[52,18557,422],{"class":62},[52,18559,1007],{"class":58},[52,18561,18562,18565,18567],{"class":54,"line":1200},[52,18563,18564],{"class":58},"        }",[52,18566,938],{"class":62},[52,18568,1007],{"class":58},[52,18570,18571],{"class":54,"line":1205},[52,18572,341],{"emptyLinePlaceholder":340},[52,18574,18575,18577,18579,18582,18584,18587,18589],{"class":54,"line":1210},[52,18576,18121],{"class":105},[52,18578,957],{"class":58},[52,18580,18581],{"class":418},"appendChild",[52,18583,932],{"class":62},[52,18585,18586],{"class":105},"itemHeaderWrapper",[52,18588,938],{"class":62},[52,18590,1007],{"class":58},[52,18592,18593,18595,18597,18599,18601,18604,18606],{"class":54,"line":1215},[52,18594,18121],{"class":105},[52,18596,957],{"class":58},[52,18598,18581],{"class":418},[52,18600,932],{"class":62},[52,18602,18603],{"class":105},"itemBodyWrapper",[52,18605,938],{"class":62},[52,18607,1007],{"class":58},[52,18609,18610,18612,18614],{"class":54,"line":1220},[52,18611,10166],{"class":360},[52,18613,18079],{"class":105},[52,18615,1007],{"class":58},[52,18617,18618],{"class":54,"line":3800},[52,18619,18620],{"class":411},"        \u002F*\n",[52,18622,18623],{"class":54,"line":3821},[52,18624,18625],{"class":411},"        生成イメージ\n",[52,18627,18628],{"class":54,"line":3835},[52,18629,18630],{"class":411},"        \u003Cdiv class=\"accordion-item\">\n",[52,18632,18633],{"class":54,"line":3840},[52,18634,18635],{"class":411},"            \u003Ch2 class=\"accordion-header\" id=\"headingOne\">\n",[52,18637,18638],{"class":54,"line":3865},[52,18639,18640],{"class":411},"                \u003Cbutton class=\"accordion-button\" type=\"button\" data-bs-toggle=\"collapse\" >\n",[52,18642,18643],{"class":54,"line":3879},[52,18644,18645],{"class":411},"                    \u003Cdiv class=\"accordion-button-wrapper\">${title}\u003C\u002Fdiv>\n",[52,18647,18648],{"class":54,"line":5506},[52,18649,18650],{"class":411},"                \u003C\u002Fbutton>\n",[52,18652,18653],{"class":54,"line":4},[52,18654,18655],{"class":411},"            \u003C\u002Fh2>\n",[52,18657,18658],{"class":54,"line":5544},[52,18659,18660],{"class":411},"            \u003Cdiv id=\"${wrapperId}-collaps\" class=\"accordion-collapse collapse show\">\n",[52,18662,18663],{"class":54,"line":5561},[52,18664,18665],{"class":411},"                \u003Cdiv class=\"accordion-body\">\n",[52,18667,18668],{"class":54,"line":5566},[52,18669,18670],{"class":411},"                    \u003Cp class=\"accordion-body-input\">${content}\u003C\u002Fp>\n",[52,18672,18673],{"class":54,"line":5587},[52,18674,18675],{"class":411},"                    \u003Cbutton class=\"accordion-delete btn btn-danger mt-2 btn-sm\">\n",[52,18677,18678],{"class":54,"line":5636},[52,18679,18680],{"class":411},"                    削除\n",[52,18682,18683],{"class":54,"line":5641},[52,18684,18685],{"class":411},"                    \u003C\u002Fbutton>\n",[52,18687,18688],{"class":54,"line":5646},[52,18689,18690],{"class":411},"                \u003C\u002Fdiv>\n",[52,18692,18693],{"class":54,"line":5679},[52,18694,18444],{"class":411},[52,18696,18697],{"class":54,"line":5692},[52,18698,18449],{"class":411},[52,18700,18701],{"class":54,"line":5711},[52,18702,18703],{"class":411},"        *\u002F\n",[52,18705,18706],{"class":54,"line":5724},[52,18707,2696],{"class":58},[13,18709,18710],{},"そしてアイテムのHTMLを返すようにし、wrapperに挿入します。ここまで実装し、ボタンをクリックした時に以下のようにアコーディオンが表示されます。",[729,18712],{":src":18713,":width":732},"'editorjs-repeatable\u002Fset3.png'",[13,18715,18716,18717,18719],{},"また",[49,18718,18303],{},"をtrueにすることで、",[42,18721,18724],{"className":18722,"code":18723,"language":452},[1615],"itemHeaderWrapper.querySelector('.accordion-button-wrapper').contentEditable = true;\nitemBodyWrapper.querySelector('.accordion-body-input').contentEditable = true;\n",[49,18725,18723],{"__ignoreMap":47},[13,18727,18728],{},"以下の図のように文字の入力がinputなしでできるようになります。",[729,18730],{":src":18731,":width":732},"'editorjs-repeatable\u002Fset4.png'",[13,18733,18734],{},"そして以下のように削除ボタンにイベントリスナーを実装することで要素の削除ができるようになります。",[42,18736,18738],{"className":10507,"code":18737,"language":1434,"meta":47,"style":47},"itemBodyWrapper.querySelector('.accordion-delete').addEventListener('click',()=>{\n    if(window.confirm(\"削除してもよろしいですか？\")) itemWrapper.remove();\n});\n",[49,18739,18740,18776,18808],{"__ignoreMap":47},[52,18741,18742,18744,18746,18748,18750,18752,18754,18756,18758,18760,18762,18764,18766,18768,18770,18772,18774],{"class":54,"line":55},[52,18743,18603],{"class":105},[52,18745,957],{"class":58},[52,18747,17854],{"class":418},[52,18749,932],{"class":105},[52,18751,376],{"class":58},[52,18753,18499],{"class":75},[52,18755,376],{"class":58},[52,18757,938],{"class":105},[52,18759,957],{"class":58},[52,18761,17870],{"class":418},[52,18763,932],{"class":105},[52,18765,376],{"class":58},[52,18767,17877],{"class":75},[52,18769,376],{"class":58},[52,18771,17882],{"class":58},[52,18773,990],{"class":65},[52,18775,364],{"class":58},[52,18777,18778,18780,18782,18784,18786,18788,18790,18792,18794,18796,18798,18800,18802,18804,18806],{"class":54,"line":83},[52,18779,12846],{"class":360},[52,18781,932],{"class":62},[52,18783,2569],{"class":105},[52,18785,957],{"class":58},[52,18787,18535],{"class":418},[52,18789,932],{"class":62},[52,18791,72],{"class":58},[52,18793,18542],{"class":75},[52,18795,72],{"class":58},[52,18797,18547],{"class":62},[52,18799,18550],{"class":105},[52,18801,957],{"class":58},[52,18803,18555],{"class":418},[52,18805,422],{"class":62},[52,18807,1007],{"class":58},[52,18809,18810,18812,18814],{"class":54,"line":115},[52,18811,1312],{"class":58},[52,18813,938],{"class":105},[52,18815,1007],{"class":58},[1499,18817,18818],{"id":18818},"保存処理",[13,18820,18821],{},"保存ボタンをクリックした時に入力した内容を出力できるようにします。",[42,18823,18825],{"className":44,"code":18824,"language":46,"meta":47,"style":47},"\u003C!DOCTYPE html>\n\u003Chtml lang=\"ja\">\n\u003Chead>\n    \u003Cmeta charset=\"UTF-8\">\n    \u003Cmeta http-equiv=\"X-UA-Compatible\" content=\"IE=edge\">\n    \u003Cmeta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n    \u003Clink href=\"https:\u002F\u002Fcdn.jsdelivr.net\u002Fnpm\u002Fbootstrap@5.0.2\u002Fdist\u002Fcss\u002Fbootstrap.min.css\" rel=\"stylesheet\" integrity=\"sha384-EVSTQN3\u002FazprG1Anm3QDgpJLIm9Nao0Yz1ztcQTwFspd3yD65VohhpuuCOmLASjC\" crossorigin=\"anonymous\">\n    \u003Cscript src=\"https:\u002F\u002Fcdn.jsdelivr.net\u002Fnpm\u002Fbootstrap@5.2.2\u002Fdist\u002Fjs\u002Fbootstrap.bundle.min.js\" integrity=\"sha384-OERcA2EqjJCMA+\u002F3y+gxIOqMEjwtxJY7qPCqsdltbNJuaOe923+mo\u002F\u002Ff6V8Qbsw3\" crossorigin=\"anonymous\">\u003C\u002Fscript>\n    \u003Ctitle>editorjs\u003C\u002Ftitle>\n    \u003Clink rel=\"stylesheet\" href=\".\u002Fstyle.css\">\n\u003C\u002Fhead>\n\u003Cbody>\n    \u003Cdiv class=\"container\">\n        \u003Cdiv class=\"mt-5 border bg-light\">\n            \u003Cdiv id=\"editorjs\">\u003C\u002Fdiv>\n        \u003C\u002Fdiv>\n        \u003C!-- 追加 -->\n        \u003Cdiv class=\"mt-2\">　\n            \u003Cbutton id=\"save\" class=\"btn btn-primary\">保存＆出力\u003C\u002Fbutton>\n            \u003Cpre id=\"data\">\u003C\u002Fpre>\n        \u003C\u002Fdiv>\n    \u003C\u002Fdiv>\n\u003C\u002Fbody>\n\u003Cscript src=\"https:\u002F\u002Fcdn.jsdelivr.net\u002Fnpm\u002F@editorjs\u002Feditorjs@latest\">\u003C\u002Fscript>\n\u003Cscript src=\".\u002Fapp.js\">\u003C\u002Fscript>\n\u003C\u002Fhtml>\n",[49,18826,18827,18837,18855,18863,18881,18909,18937,18985,19029,19045,19073,19081,19089,19107,19125,19147,19155,19160,19181,19220,19242,19250,19258,19266,19288,19310],{"__ignoreMap":47},[52,18828,18829,18831,18833,18835],{"class":54,"line":55},[52,18830,2140],{"class":58},[52,18832,2143],{"class":62},[52,18834,2146],{"class":65},[52,18836,80],{"class":58},[52,18838,18839,18841,18843,18845,18847,18849,18851,18853],{"class":54,"line":83},[52,18840,59],{"class":58},[52,18842,46],{"class":62},[52,18844,16147],{"class":65},[52,18846,69],{"class":58},[52,18848,72],{"class":58},[52,18850,16154],{"class":75},[52,18852,72],{"class":58},[52,18854,80],{"class":58},[52,18856,18857,18859,18861],{"class":54,"line":115},[52,18858,59],{"class":58},[52,18860,2164],{"class":62},[52,18862,80],{"class":58},[52,18864,18865,18867,18869,18871,18873,18875,18877,18879],{"class":54,"line":142},[52,18866,2161],{"class":58},[52,18868,2174],{"class":62},[52,18870,2177],{"class":65},[52,18872,69],{"class":58},[52,18874,72],{"class":58},[52,18876,16181],{"class":75},[52,18878,72],{"class":58},[52,18880,80],{"class":58},[52,18882,18883,18885,18887,18889,18891,18893,18895,18897,18899,18901,18903,18905,18907],{"class":54,"line":169},[52,18884,2161],{"class":58},[52,18886,2174],{"class":62},[52,18888,16194],{"class":65},[52,18890,69],{"class":58},[52,18892,72],{"class":58},[52,18894,16201],{"class":75},[52,18896,72],{"class":58},[52,18898,16206],{"class":65},[52,18900,69],{"class":58},[52,18902,72],{"class":58},[52,18904,16213],{"class":75},[52,18906,72],{"class":58},[52,18908,80],{"class":58},[52,18910,18911,18913,18915,18917,18919,18921,18923,18925,18927,18929,18931,18933,18935],{"class":54,"line":302},[52,18912,2161],{"class":58},[52,18914,2174],{"class":62},[52,18916,66],{"class":65},[52,18918,69],{"class":58},[52,18920,72],{"class":58},[52,18922,16232],{"class":75},[52,18924,72],{"class":58},[52,18926,16206],{"class":65},[52,18928,69],{"class":58},[52,18930,72],{"class":58},[52,18932,16243],{"class":75},[52,18934,72],{"class":58},[52,18936,80],{"class":58},[52,18938,18939,18941,18943,18945,18947,18949,18951,18953,18955,18957,18959,18961,18963,18965,18967,18969,18971,18973,18975,18977,18979,18981,18983],{"class":54,"line":308},[52,18940,2161],{"class":58},[52,18942,16254],{"class":62},[52,18944,16257],{"class":65},[52,18946,69],{"class":58},[52,18948,72],{"class":58},[52,18950,16264],{"class":75},[52,18952,72],{"class":58},[52,18954,16269],{"class":65},[52,18956,69],{"class":58},[52,18958,72],{"class":58},[52,18960,16276],{"class":75},[52,18962,72],{"class":58},[52,18964,16281],{"class":65},[52,18966,69],{"class":58},[52,18968,72],{"class":58},[52,18970,16288],{"class":75},[52,18972,72],{"class":58},[52,18974,16293],{"class":65},[52,18976,69],{"class":58},[52,18978,72],{"class":58},[52,18980,16300],{"class":75},[52,18982,72],{"class":58},[52,18984,80],{"class":58},[52,18986,18987,18989,18991,18993,18995,18997,19000,19002,19004,19006,19008,19011,19013,19015,19017,19019,19021,19023,19025,19027],{"class":54,"line":318},[52,18988,2161],{"class":58},[52,18990,349],{"class":62},[52,18992,2368],{"class":65},[52,18994,69],{"class":58},[52,18996,72],{"class":58},[52,18998,18999],{"class":75},"https:\u002F\u002Fcdn.jsdelivr.net\u002Fnpm\u002Fbootstrap@5.2.2\u002Fdist\u002Fjs\u002Fbootstrap.bundle.min.js",[52,19001,72],{"class":58},[52,19003,16281],{"class":65},[52,19005,69],{"class":58},[52,19007,72],{"class":58},[52,19009,19010],{"class":75},"sha384-OERcA2EqjJCMA+\u002F3y+gxIOqMEjwtxJY7qPCqsdltbNJuaOe923+mo\u002F\u002Ff6V8Qbsw3",[52,19012,72],{"class":58},[52,19014,16293],{"class":65},[52,19016,69],{"class":58},[52,19018,72],{"class":58},[52,19020,16300],{"class":75},[52,19022,72],{"class":58},[52,19024,2347],{"class":58},[52,19026,349],{"class":62},[52,19028,80],{"class":58},[52,19030,19031,19033,19035,19037,19039,19041,19043],{"class":54,"line":328},[52,19032,2161],{"class":58},[52,19034,2195],{"class":62},[52,19036,102],{"class":58},[52,19038,16315],{"class":105},[52,19040,108],{"class":58},[52,19042,2195],{"class":62},[52,19044,80],{"class":58},[52,19046,19047,19049,19051,19053,19055,19057,19059,19061,19063,19065,19067,19069,19071],{"class":54,"line":337},[52,19048,2161],{"class":58},[52,19050,16254],{"class":62},[52,19052,16269],{"class":65},[52,19054,69],{"class":58},[52,19056,72],{"class":58},[52,19058,16276],{"class":75},[52,19060,72],{"class":58},[52,19062,16257],{"class":65},[52,19064,69],{"class":58},[52,19066,72],{"class":58},[52,19068,16346],{"class":75},[52,19070,72],{"class":58},[52,19072,80],{"class":58},[52,19074,19075,19077,19079],{"class":54,"line":344},[52,19076,108],{"class":58},[52,19078,2164],{"class":62},[52,19080,80],{"class":58},[52,19082,19083,19085,19087],{"class":54,"line":354},[52,19084,59],{"class":58},[52,19086,2314],{"class":62},[52,19088,80],{"class":58},[52,19090,19091,19093,19095,19097,19099,19101,19103,19105],{"class":54,"line":367},[52,19092,2161],{"class":58},[52,19094,8503],{"class":62},[52,19096,16375],{"class":65},[52,19098,69],{"class":58},[52,19100,72],{"class":58},[52,19102,16382],{"class":75},[52,19104,72],{"class":58},[52,19106,80],{"class":58},[52,19108,19109,19111,19113,19115,19117,19119,19121,19123],{"class":54,"line":387},[52,19110,2171],{"class":58},[52,19112,8503],{"class":62},[52,19114,16375],{"class":65},[52,19116,69],{"class":58},[52,19118,72],{"class":58},[52,19120,16401],{"class":75},[52,19122,72],{"class":58},[52,19124,80],{"class":58},[52,19126,19127,19129,19131,19133,19135,19137,19139,19141,19143,19145],{"class":54,"line":415},[52,19128,2330],{"class":58},[52,19130,8503],{"class":62},[52,19132,2336],{"class":65},[52,19134,69],{"class":58},[52,19136,72],{"class":58},[52,19138,16315],{"class":75},[52,19140,72],{"class":58},[52,19142,2347],{"class":58},[52,19144,8503],{"class":62},[52,19146,80],{"class":58},[52,19148,19149,19151,19153],{"class":54,"line":427},[52,19150,2294],{"class":58},[52,19152,8503],{"class":62},[52,19154,80],{"class":58},[52,19156,19157],{"class":54,"line":435},[52,19158,19159],{"class":411},"        \u003C!-- 追加 -->\n",[52,19161,19162,19164,19166,19168,19170,19172,19174,19176,19178],{"class":54,"line":446},[52,19163,2171],{"class":58},[52,19165,8503],{"class":62},[52,19167,16375],{"class":65},[52,19169,69],{"class":58},[52,19171,72],{"class":58},[52,19173,16450],{"class":75},[52,19175,72],{"class":58},[52,19177,102],{"class":58},[52,19179,19180],{"class":105},"　\n",[52,19182,19183,19185,19187,19189,19191,19193,19196,19198,19200,19202,19204,19207,19209,19211,19214,19216,19218],{"class":54,"line":480},[52,19184,2330],{"class":58},[52,19186,11599],{"class":62},[52,19188,2336],{"class":65},[52,19190,69],{"class":58},[52,19192,72],{"class":58},[52,19194,19195],{"class":75},"save",[52,19197,72],{"class":58},[52,19199,16375],{"class":65},[52,19201,69],{"class":58},[52,19203,72],{"class":58},[52,19205,19206],{"class":75},"btn btn-primary",[52,19208,72],{"class":58},[52,19210,102],{"class":58},[52,19212,19213],{"class":105},"保存＆出力",[52,19215,108],{"class":58},[52,19217,11599],{"class":62},[52,19219,80],{"class":58},[52,19221,19222,19224,19226,19228,19230,19232,19234,19236,19238,19240],{"class":54,"line":509},[52,19223,2330],{"class":58},[52,19225,42],{"class":62},[52,19227,2336],{"class":65},[52,19229,69],{"class":58},[52,19231,72],{"class":58},[52,19233,11148],{"class":75},[52,19235,72],{"class":58},[52,19237,2347],{"class":58},[52,19239,42],{"class":62},[52,19241,80],{"class":58},[52,19243,19244,19246,19248],{"class":54,"line":539},[52,19245,2294],{"class":58},[52,19247,8503],{"class":62},[52,19249,80],{"class":58},[52,19251,19252,19254,19256],{"class":54,"line":547},[52,19253,2303],{"class":58},[52,19255,8503],{"class":62},[52,19257,80],{"class":58},[52,19259,19260,19262,19264],{"class":54,"line":553},[52,19261,108],{"class":58},[52,19263,2314],{"class":62},[52,19265,80],{"class":58},[52,19267,19268,19270,19272,19274,19276,19278,19280,19282,19284,19286],{"class":54,"line":559},[52,19269,59],{"class":58},[52,19271,349],{"class":62},[52,19273,2368],{"class":65},[52,19275,69],{"class":58},[52,19277,72],{"class":58},[52,19279,16515],{"class":75},[52,19281,72],{"class":58},[52,19283,2347],{"class":58},[52,19285,349],{"class":62},[52,19287,80],{"class":58},[52,19289,19290,19292,19294,19296,19298,19300,19302,19304,19306,19308],{"class":54,"line":564},[52,19291,59],{"class":58},[52,19293,349],{"class":62},[52,19295,2368],{"class":65},[52,19297,69],{"class":58},[52,19299,72],{"class":58},[52,19301,2375],{"class":75},[52,19303,72],{"class":58},[52,19305,2347],{"class":58},[52,19307,349],{"class":62},[52,19309,80],{"class":58},[52,19311,19312,19314,19316],{"class":54,"line":569},[52,19313,108],{"class":58},[52,19315,46],{"class":62},[52,19317,80],{"class":58},[42,19319,19321],{"className":10507,"code":19320,"language":1434,"meta":47,"style":47},"const pre = document.getElementById(\"data\");\ndocument.getElementById(\"save\").addEventListener(\"click\",()=>{\n    editor.save().then((outputData) => {\n        pre.textContent = JSON.stringify(outputData, null , \"\\t\");\n    }).catch((error) => {\n        console.log('Saving failed: ', error)\n    });\n})\n",[49,19322,19323,19350,19387,19416,19459,19482,19508,19516],{"__ignoreMap":47},[52,19324,19325,19327,19330,19332,19334,19336,19338,19340,19342,19344,19346,19348],{"class":54,"line":55},[52,19326,10903],{"class":65},[52,19328,19329],{"class":105}," pre ",[52,19331,69],{"class":58},[52,19333,2437],{"class":105},[52,19335,957],{"class":58},[52,19337,2442],{"class":418},[52,19339,932],{"class":105},[52,19341,72],{"class":58},[52,19343,11148],{"class":75},[52,19345,72],{"class":58},[52,19347,938],{"class":105},[52,19349,1007],{"class":58},[52,19351,19352,19355,19357,19359,19361,19363,19365,19367,19369,19371,19373,19375,19377,19379,19381,19383,19385],{"class":54,"line":83},[52,19353,19354],{"class":105},"document",[52,19356,957],{"class":58},[52,19358,2442],{"class":418},[52,19360,932],{"class":105},[52,19362,72],{"class":58},[52,19364,19195],{"class":75},[52,19366,72],{"class":58},[52,19368,938],{"class":105},[52,19370,957],{"class":58},[52,19372,17870],{"class":418},[52,19374,932],{"class":105},[52,19376,72],{"class":58},[52,19378,17877],{"class":75},[52,19380,72],{"class":58},[52,19382,17882],{"class":58},[52,19384,990],{"class":65},[52,19386,364],{"class":58},[52,19388,19389,19392,19394,19396,19398,19400,19403,19405,19407,19410,19412,19414],{"class":54,"line":115},[52,19390,19391],{"class":105},"    editor",[52,19393,957],{"class":58},[52,19395,19195],{"class":418},[52,19397,422],{"class":62},[52,19399,957],{"class":58},[52,19401,19402],{"class":418},"then",[52,19404,932],{"class":62},[52,19406,932],{"class":58},[52,19408,19409],{"class":986},"outputData",[52,19411,938],{"class":58},[52,19413,10404],{"class":65},[52,19415,10138],{"class":58},[52,19417,19418,19421,19423,19426,19428,19431,19433,19436,19438,19440,19442,19445,19448,19450,19453,19455,19457],{"class":54,"line":142},[52,19419,19420],{"class":105},"        pre",[52,19422,957],{"class":58},[52,19424,19425],{"class":105},"textContent",[52,19427,951],{"class":58},[52,19429,19430],{"class":105}," JSON",[52,19432,957],{"class":58},[52,19434,19435],{"class":418},"stringify",[52,19437,932],{"class":62},[52,19439,19409],{"class":105},[52,19441,408],{"class":58},[52,19443,19444],{"class":58}," null",[52,19446,19447],{"class":58}," ,",[52,19449,3597],{"class":58},[52,19451,19452],{"class":105},"\\t",[52,19454,72],{"class":58},[52,19456,938],{"class":62},[52,19458,1007],{"class":58},[52,19460,19461,19463,19465,19467,19470,19472,19474,19476,19478,19480],{"class":54,"line":169},[52,19462,13168],{"class":58},[52,19464,938],{"class":62},[52,19466,957],{"class":58},[52,19468,19469],{"class":418},"catch",[52,19471,932],{"class":62},[52,19473,932],{"class":58},[52,19475,10218],{"class":986},[52,19477,938],{"class":58},[52,19479,10404],{"class":65},[52,19481,10138],{"class":58},[52,19483,19484,19487,19489,19492,19494,19496,19499,19501,19503,19506],{"class":54,"line":302},[52,19485,19486],{"class":105},"        console",[52,19488,957],{"class":58},[52,19490,19491],{"class":418},"log",[52,19493,932],{"class":62},[52,19495,376],{"class":58},[52,19497,19498],{"class":75},"Saving failed: ",[52,19500,376],{"class":58},[52,19502,408],{"class":58},[52,19504,19505],{"class":105}," error",[52,19507,1015],{"class":62},[52,19509,19510,19512,19514],{"class":54,"line":308},[52,19511,13168],{"class":58},[52,19513,938],{"class":62},[52,19515,1007],{"class":58},[52,19517,19518,19520],{"class":54,"line":318},[52,19519,1312],{"class":58},[52,19521,1015],{"class":105},[13,19523,19524,19525,19528,19529,19534,19535,19537,19538,19541,19542,19544,19545,19547],{},"editor.jsでブロックのデータを出力したい時は",[49,19526,19527],{},"editor.save()","を使用します。Promiseなので非同期でデータが出力されます。今回はJSONで出力するようにしました。",[2039,19530,19533],{"href":19531,"rel":19532},"https:\u002F\u002Fapps.jun-app.com\u002Feditorjs-sample\u002F",[2043],"デモサイト","で確認できます。",[49,19536,19527],{},"では全ブロッククラスの",[49,19539,19540],{},"save()","が呼ばれるため、",[49,19543,17143],{},"クラスでも",[49,19546,19540],{},"メソッドを実装します。",[42,19549,19551],{"className":10507,"code":19550,"language":1434,"meta":47,"style":47},"save(blockContent){\n    const contents = blockContent.querySelectorAll(\".accordion-item\");\n    const itmes = Array.prototype.map.call(contents, (item)=>{\n        const title = item.querySelector(\".accordion-button-wrapper\");\n        const content = item.querySelector(\".accordion-body-input\");\n\n        return {\n            title:title.innerHTML || \"\",\n            content:content.innerHTML || \"\",\n        };\n    })\n    return {\n        itmes\n    }\n}\n",[49,19552,19553,19562,19592,19636,19664,19690,19694,19700,19718,19737,19741,19747,19753,19758,19762],{"__ignoreMap":47},[52,19554,19555,19557,19560],{"class":54,"line":55},[52,19556,19195],{"class":418},[52,19558,19559],{"class":105},"(blockContent)",[52,19561,364],{"class":58},[52,19563,19564,19566,19569,19571,19574,19576,19579,19581,19583,19586,19588,19590],{"class":54,"line":83},[52,19565,12554],{"class":65},[52,19567,19568],{"class":105}," contents",[52,19570,951],{"class":58},[52,19572,19573],{"class":105}," blockContent",[52,19575,957],{"class":58},[52,19577,19578],{"class":418},"querySelectorAll",[52,19580,932],{"class":62},[52,19582,72],{"class":58},[52,19584,19585],{"class":75},".accordion-item",[52,19587,72],{"class":58},[52,19589,938],{"class":62},[52,19591,1007],{"class":58},[52,19593,19594,19596,19599,19601,19604,19606,19609,19611,19613,19615,19618,19620,19623,19625,19627,19630,19632,19634],{"class":54,"line":115},[52,19595,12554],{"class":65},[52,19597,19598],{"class":105}," itmes",[52,19600,951],{"class":58},[52,19602,19603],{"class":370}," Array",[52,19605,957],{"class":58},[52,19607,19608],{"class":105},"prototype",[52,19610,957],{"class":58},[52,19612,1031],{"class":105},[52,19614,957],{"class":58},[52,19616,19617],{"class":418},"call",[52,19619,932],{"class":62},[52,19621,19622],{"class":105},"contents",[52,19624,408],{"class":58},[52,19626,2499],{"class":58},[52,19628,19629],{"class":986},"item",[52,19631,938],{"class":58},[52,19633,990],{"class":65},[52,19635,364],{"class":58},[52,19637,19638,19640,19643,19645,19648,19650,19652,19654,19656,19658,19660,19662],{"class":54,"line":142},[52,19639,17307],{"class":65},[52,19641,19642],{"class":105}," title",[52,19644,951],{"class":58},[52,19646,19647],{"class":105}," item",[52,19649,957],{"class":58},[52,19651,17854],{"class":418},[52,19653,932],{"class":62},[52,19655,72],{"class":58},[52,19657,18294],{"class":75},[52,19659,72],{"class":58},[52,19661,938],{"class":62},[52,19663,1007],{"class":58},[52,19665,19666,19668,19670,19672,19674,19676,19678,19680,19682,19684,19686,19688],{"class":54,"line":169},[52,19667,17307],{"class":65},[52,19669,16206],{"class":105},[52,19671,951],{"class":58},[52,19673,19647],{"class":105},[52,19675,957],{"class":58},[52,19677,17854],{"class":418},[52,19679,932],{"class":62},[52,19681,72],{"class":58},[52,19683,18470],{"class":75},[52,19685,72],{"class":58},[52,19687,938],{"class":62},[52,19689,1007],{"class":58},[52,19691,19692],{"class":54,"line":302},[52,19693,341],{"emptyLinePlaceholder":340},[52,19695,19696,19698],{"class":54,"line":308},[52,19697,10166],{"class":360},[52,19699,10138],{"class":58},[52,19701,19702,19704,19706,19708,19710,19712,19714,19716],{"class":54,"line":318},[52,19703,16705],{"class":62},[52,19705,373],{"class":58},[52,19707,2195],{"class":105},[52,19709,957],{"class":58},[52,19711,17493],{"class":105},[52,19713,17322],{"class":58},[52,19715,16004],{"class":58},[52,19717,384],{"class":58},[52,19719,19720,19723,19725,19727,19729,19731,19733,19735],{"class":54,"line":328},[52,19721,19722],{"class":62},"            content",[52,19724,373],{"class":58},[52,19726,15060],{"class":105},[52,19728,957],{"class":58},[52,19730,17493],{"class":105},[52,19732,17322],{"class":58},[52,19734,16004],{"class":58},[52,19736,384],{"class":58},[52,19738,19739],{"class":54,"line":337},[52,19740,17275],{"class":58},[52,19742,19743,19745],{"class":54,"line":344},[52,19744,13168],{"class":58},[52,19746,1015],{"class":62},[52,19748,19749,19751],{"class":54,"line":354},[52,19750,11916],{"class":360},[52,19752,10138],{"class":58},[52,19754,19755],{"class":54,"line":367},[52,19756,19757],{"class":105},"        itmes\n",[52,19759,19760],{"class":54,"line":387},[52,19761,2696],{"class":58},[52,19763,19764],{"class":54,"line":415},[52,19765,536],{"class":58},[13,19767,19768,9302,19770,19772,19773,19776,19777,19780,19781,4793,19783,11465,19785,19788,19789,19791],{},[49,19769,16797],{},[49,19771,16825],{},"で表示しているwrapperのDOMが渡されます。今回のクラスの場合、",[49,19774,19775],{},"this.wrapper","とほぼ同じです。save()で行うことはブロックのDOMからアコーディオンのタイトルと内容を取得します。複数個のアコーディオン要素があるので",[49,19778,19779],{},"blockContent.querySelectorAll(\".accordion-item\")","でNodeListを取得し、",[49,19782,18294],{},[49,19784,18470],{},[49,19786,19787],{},"innnerHtml","を取得して、",[49,19790,19540],{},"で返したいオブジェクトに当てはめます。",[42,19793,19795],{"className":10507,"code":19794,"language":1434,"meta":47,"style":47},"return {\n    items\n};\n\u002F\u002F ↓\n\u002F\u002F return {\n\u002F\u002F     items:[\n\u002F\u002F         {\n\u002F\u002F             title:\"...\",\n\u002F\u002F             content:\"...\",\n\u002F\u002F         },\n\u002F\u002F         {\n\u002F\u002F             title:\"...\",\n\u002F\u002F             content:\"...\",\n\u002F\u002F         },\n\u002F\u002F         {\n\u002F\u002F             title:\"...\",\n\u002F\u002F             content:\"...\",\n\u002F\u002F         },\n\u002F\u002F         ...\n\u002F\u002F     ]\n\u002F\u002F };\n",[49,19796,19797,19803,19808,19813,19818,19823,19828,19833,19838,19843,19848,19852,19856,19860,19864,19868,19872,19876,19880,19885,19890],{"__ignoreMap":47},[52,19798,19799,19801],{"class":54,"line":55},[52,19800,10515],{"class":360},[52,19802,10138],{"class":58},[52,19804,19805],{"class":54,"line":83},[52,19806,19807],{"class":105},"    items\n",[52,19809,19810],{"class":54,"line":115},[52,19811,19812],{"class":58},"};\n",[52,19814,19815],{"class":54,"line":142},[52,19816,19817],{"class":411},"\u002F\u002F ↓\n",[52,19819,19820],{"class":54,"line":169},[52,19821,19822],{"class":411},"\u002F\u002F return {\n",[52,19824,19825],{"class":54,"line":302},[52,19826,19827],{"class":411},"\u002F\u002F     items:[\n",[52,19829,19830],{"class":54,"line":308},[52,19831,19832],{"class":411},"\u002F\u002F         {\n",[52,19834,19835],{"class":54,"line":318},[52,19836,19837],{"class":411},"\u002F\u002F             title:\"...\",\n",[52,19839,19840],{"class":54,"line":328},[52,19841,19842],{"class":411},"\u002F\u002F             content:\"...\",\n",[52,19844,19845],{"class":54,"line":337},[52,19846,19847],{"class":411},"\u002F\u002F         },\n",[52,19849,19850],{"class":54,"line":344},[52,19851,19832],{"class":411},[52,19853,19854],{"class":54,"line":354},[52,19855,19837],{"class":411},[52,19857,19858],{"class":54,"line":367},[52,19859,19842],{"class":411},[52,19861,19862],{"class":54,"line":387},[52,19863,19847],{"class":411},[52,19865,19866],{"class":54,"line":415},[52,19867,19832],{"class":411},[52,19869,19870],{"class":54,"line":427},[52,19871,19837],{"class":411},[52,19873,19874],{"class":54,"line":435},[52,19875,19842],{"class":411},[52,19877,19878],{"class":54,"line":446},[52,19879,19847],{"class":411},[52,19881,19882],{"class":54,"line":480},[52,19883,19884],{"class":411},"\u002F\u002F         ...\n",[52,19886,19887],{"class":54,"line":509},[52,19888,19889],{"class":411},"\u002F\u002F     ]\n",[52,19891,19892],{"class":54,"line":539},[52,19893,19894],{"class":411},"\u002F\u002F };\n",[13,19896,19897,19900],{},[2039,19898,19533],{"href":19531,"rel":19899},[2043],"でアコーディオンを作成し、文字を入力して「保存・出力」をクリックすると上記のオブジェクトをJSON化したものが表示されると思います。",[729,19902],{":src":19903,":width":732},"'editorjs-repeatable\u002Fset5.png'",[1499,19905,19906],{"id":19906},"インラインツールをサポートする",[13,19908,19909,19910,19913],{},"冒頭で",[49,19911,19912],{},"inlineToolbar: true","を指定するとリッチテキストを使用できるようになり、文章に対して太字、斜体、リンクを付与するツールが出現するようになります。",[729,19915],{":src":19916,":width":732},"'editorjs-repeatable\u002Fset6.png'",[13,19918,19919],{},"リッチテキストを保存、再現する場合はinnetHtmlをget、setする必要があります。",[1499,19921,19922],{"id":19922},"データを渡された時の処理",[13,19924,19925],{},"上記まではフォームを表示し、エディタ上で文章を入力して、保存・出力しました。しかし実際の用途では保存したデータから再度エディタ上に表示できるようにしましょう。",[13,19927,19928,19929,19932,19933,19935],{},"エディタにデータを渡す時は",[49,19930,19931],{},"Editor","クラスに",[49,19934,11148],{},"プロパティーに出力したデータを設定すれば大丈夫です。",[42,19937,19939],{"className":10507,"code":19938,"language":1434,"meta":47,"style":47},"const editor = new EditorJS({\n    holder: 'editorjs',\n    tools: { \n        accordion : {class:Accordion,inlineToolbar: true}\n    },\n\n    \u002F\u002F ここ\n    data:JSON.parse('出力したデータ')\n});\n",[49,19940,19941,19957,19971,19981,20005,20009,20013,20018,20043],{"__ignoreMap":47},[52,19942,19943,19945,19947,19949,19951,19953,19955],{"class":54,"line":55},[52,19944,10903],{"class":65},[52,19946,16572],{"class":105},[52,19948,69],{"class":58},[52,19950,2522],{"class":58},[52,19952,16579],{"class":418},[52,19954,932],{"class":105},[52,19956,364],{"class":58},[52,19958,19959,19961,19963,19965,19967,19969],{"class":54,"line":83},[52,19960,16588],{"class":62},[52,19962,373],{"class":58},[52,19964,6309],{"class":58},[52,19966,16315],{"class":75},[52,19968,376],{"class":58},[52,19970,384],{"class":58},[52,19972,19973,19975,19977,19979],{"class":54,"line":115},[52,19974,17120],{"class":62},[52,19976,373],{"class":58},[52,19978,17125],{"class":58},[52,19980,283],{"class":105},[52,19982,19983,19985,19987,19989,19991,19993,19995,19997,19999,20001,20003],{"class":54,"line":142},[52,19984,17132],{"class":62},[52,19986,373],{"class":58},[52,19988,17125],{"class":58},[52,19990,16669],{"class":62},[52,19992,373],{"class":58},[52,19994,17143],{"class":105},[52,19996,408],{"class":58},[52,19998,17148],{"class":62},[52,20000,373],{"class":58},[52,20002,10386],{"class":6196},[52,20004,536],{"class":58},[52,20006,20007],{"class":54,"line":169},[52,20008,10189],{"class":58},[52,20010,20011],{"class":54,"line":302},[52,20012,341],{"emptyLinePlaceholder":340},[52,20014,20015],{"class":54,"line":308},[52,20016,20017],{"class":411},"    \u002F\u002F ここ\n",[52,20019,20020,20022,20024,20027,20029,20032,20034,20036,20039,20041],{"class":54,"line":318},[52,20021,10159],{"class":62},[52,20023,373],{"class":58},[52,20025,20026],{"class":105},"JSON",[52,20028,957],{"class":58},[52,20030,20031],{"class":418},"parse",[52,20033,932],{"class":105},[52,20035,376],{"class":58},[52,20037,20038],{"class":75},"出力したデータ",[52,20040,376],{"class":58},[52,20042,1015],{"class":105},[52,20044,20045,20047,20049],{"class":54,"line":328},[52,20046,1312],{"class":58},[52,20048,938],{"class":105},[52,20050,1007],{"class":58},[13,20052,20053,20054,20056,20057,20059,20060,20062],{},"JSONで保存した時はパースでします。JSON内の",[49,20055,15573],{},"内の各ブロックの",[49,20058,15612],{},"から判別してブロッククラスを当てはめてくれます。そしてこのデータはブロッククラスのコンストラクタに",[49,20061,11148],{},"という引数で渡されます。",[42,20064,20066],{"className":10507,"code":20065,"language":1434,"meta":47,"style":47},"class Accordion{\n    constructor({data,config,api}){\n        \u002F\u002F これ！\n        const accordionData = data.itmes || [];\n\n        this.createParentWrapper();\n\n        \u002F\u002F データ当てはめ\n        if(accordionData.length > 0){\n            accordionData.forEach(element => {\n                this.addItemEle(element.title,element.content);\n            });\n        }\n    }\n}\n",[49,20067,20068,20076,20094,20099,20119,20123,20133,20137,20142,20164,20183,20210,20219,20223,20227],{"__ignoreMap":47},[52,20069,20070,20072,20074],{"class":54,"line":55},[52,20071,16669],{"class":65},[52,20073,16672],{"class":370},[52,20075,364],{"class":58},[52,20077,20078,20080,20082,20084,20086,20088,20090,20092],{"class":54,"line":83},[52,20079,16749],{"class":65},[52,20081,16752],{"class":58},[52,20083,11148],{"class":986},[52,20085,408],{"class":58},[52,20087,16764],{"class":986},[52,20089,408],{"class":58},[52,20091,16759],{"class":986},[52,20093,17302],{"class":58},[52,20095,20096],{"class":54,"line":115},[52,20097,20098],{"class":411},"        \u002F\u002F これ！\n",[52,20100,20101,20103,20105,20107,20109,20111,20113,20115,20117],{"class":54,"line":142},[52,20102,17307],{"class":65},[52,20104,17310],{"class":105},[52,20106,951],{"class":58},[52,20108,419],{"class":105},[52,20110,957],{"class":58},[52,20112,17319],{"class":105},[52,20114,17322],{"class":58},[52,20116,17325],{"class":62},[52,20118,1007],{"class":58},[52,20120,20121],{"class":54,"line":169},[52,20122,341],{"emptyLinePlaceholder":340},[52,20124,20125,20127,20129,20131],{"class":54,"line":302},[52,20126,17332],{"class":58},[52,20128,17335],{"class":418},[52,20130,422],{"class":62},[52,20132,1007],{"class":58},[52,20134,20135],{"class":54,"line":308},[52,20136,341],{"emptyLinePlaceholder":340},[52,20138,20139],{"class":54,"line":318},[52,20140,20141],{"class":411},"        \u002F\u002F データ当てはめ\n",[52,20143,20144,20147,20149,20152,20154,20156,20158,20160,20162],{"class":54,"line":328},[52,20145,20146],{"class":360},"        if",[52,20148,932],{"class":62},[52,20150,20151],{"class":105},"accordionData",[52,20153,957],{"class":58},[52,20155,17609],{"class":105},[52,20157,12755],{"class":58},[52,20159,3333],{"class":2232},[52,20161,938],{"class":62},[52,20163,364],{"class":58},[52,20165,20166,20169,20171,20174,20176,20179,20181],{"class":54,"line":337},[52,20167,20168],{"class":105},"            accordionData",[52,20170,957],{"class":58},[52,20172,20173],{"class":418},"forEach",[52,20175,932],{"class":62},[52,20177,20178],{"class":986},"element",[52,20180,10404],{"class":65},[52,20182,10138],{"class":58},[52,20184,20185,20188,20190,20192,20194,20196,20198,20200,20202,20204,20206,20208],{"class":54,"line":344},[52,20186,20187],{"class":58},"                this.",[52,20189,17893],{"class":418},[52,20191,932],{"class":62},[52,20193,20178],{"class":105},[52,20195,957],{"class":58},[52,20197,2195],{"class":105},[52,20199,408],{"class":58},[52,20201,20178],{"class":105},[52,20203,957],{"class":58},[52,20205,15060],{"class":105},[52,20207,938],{"class":62},[52,20209,1007],{"class":58},[52,20211,20212,20215,20217],{"class":54,"line":354},[52,20213,20214],{"class":58},"            }",[52,20216,938],{"class":62},[52,20218,1007],{"class":58},[52,20220,20221],{"class":54,"line":367},[52,20222,5398],{"class":58},[52,20224,20225],{"class":54,"line":387},[52,20226,2696],{"class":58},[52,20228,20229],{"class":54,"line":415},[52,20230,536],{"class":58},[13,20232,20233,20234,723,20237,8671,20239,20242],{},"そしてデータが存在する場合、",[49,20235,20236],{},"data.itmes",[49,20238,14921],{},[49,20240,20241],{},"this.addItemEle()","を通じてフォームのDOMを生成します。",[17,20244,13193],{"id":13193},[13,20246,20247],{},"Editor.jsにおける反復入力できるブロックの作成方法は以上となります。editor.jsでのブロック作成の基本は",[1687,20249,20250,20255,20261],{},[1470,20251,20252,20254],{},[49,20253,16825],{},"でブロックのDOMを返す。",[1470,20256,20257,20260],{},[49,20258,20259],{},"constructor()","で初期処理を行う。",[1470,20262,20263,20265],{},[49,20264,19540],{},"でブロックのDOMから値を取得してオブジェクトで返す。",[13,20267,20268,20269,20271,20272,20274],{},"以上となります。簡単のブロックの場合、",[49,20270,16825],{},"に",[49,20273,18028],{},"で作成したようなDOM生成のコードを書いてもいいですが、今回は要素の追加・削除が必要なためメソッドを分けました。",[13,20276,20277,20278,20280],{},"ただし",[49,20279,18028],{},"の中を見ると結構ごちゃごちゃしています。vue.jsやreact.jsに慣れた人にとってみると、saveでの値の取得が面倒に感じるかもしれません。DOMの生成部分は正直undersocre.jsのtemplateなどを使用してデータバインディングを使用した方がより複雑なブロックを作成できると思います。今度はundersocre.jsと組み合わせて複雑な入力形態をとれるようにしてみます。",[13,20282,20283,20284,20287],{},"今回作成したコードは",[2039,20285,19533],{"href":19531,"rel":20286},[2043],"からご確認ください。",[1414,20289,20290],{},"html pre.shiki code .sAklC, html code.shiki .sAklC{--shiki-default:#89DDFF}html pre.shiki code .sJ14y, html code.shiki .sJ14y{--shiki-default:#C792EA}html pre.shiki code .sx098, html code.shiki .sx098{--shiki-default:#F78C6C}html pre.shiki code .s5Dmg, html code.shiki .s5Dmg{--shiki-default:#FFCB6B}html pre.shiki code .sfyAc, html code.shiki .sfyAc{--shiki-default:#C3E88D}html pre.shiki code .s-wAU, html code.shiki .s-wAU{--shiki-default:#F07178}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html pre.shiki code .s0W1g, html code.shiki .s0W1g{--shiki-default:#BABED8}html pre.shiki code .sdLwU, html code.shiki .sdLwU{--shiki-default:#82AAFF}html pre.shiki code .s6YsC, html code.shiki .s6YsC{--shiki-default:#B2CCD6}html pre.shiki code .sC9rS, html code.shiki .sC9rS{--shiki-default:#464B5D;--shiki-default-font-style:italic}html pre.shiki code .s6cf3, html code.shiki .s6cf3{--shiki-default:#89DDFF;--shiki-default-font-style:italic}html pre.shiki code .s7ZW3, html code.shiki .s7ZW3{--shiki-default:#BABED8;--shiki-default-font-style:italic}html pre.shiki code .sbqyR, html code.shiki .sbqyR{--shiki-default:#FF9CAC}",{"title":47,"searchDepth":115,"depth":115,"links":20292},[20293,20294,20298,20299,20300,20301,20308],{"id":15459,"depth":83,"text":15459},{"id":15515,"depth":83,"text":15516,"children":20295},[20296,20297],{"id":16075,"depth":115,"text":16075},{"id":16081,"depth":115,"text":16081},{"id":16102,"depth":83,"text":16102},{"id":16111,"depth":83,"text":16112},{"id":16647,"depth":83,"text":16647},{"id":16948,"depth":83,"text":16948,"children":20302},[20303,20304,20305,20306,20307],{"id":17196,"depth":115,"text":17196},{"id":18038,"depth":115,"text":18038},{"id":18818,"depth":115,"text":18818},{"id":19906,"depth":115,"text":19906},{"id":19922,"depth":115,"text":19922},{"id":13193,"depth":83,"text":13193},[1424],"2025-10-21",{},"\u002Farticles\u002Feditorjs-repeatable",{"title":15448,"description":15448},"articles\u002Feditorjs-repeatable",[1434],"fqFXMzrX-J2WaHaq3V6xcHNSXu0M84XC2Fj6Q3A2fvY",{"id":20318,"title":20319,"body":20320,"category":23907,"createdAt":23908,"description":20319,"extension":1427,"index":1428,"meta":23909,"navigation":340,"path":23910,"publish":340,"seo":23911,"series":1428,"seriesTitle":1428,"stem":23912,"tag":23913,"thumbnail":23915,"updatedAt":23908,"__hash__":23916},"articles\u002Farticles\u002Flaravel-nuxt-jwt-spa.md","laravel 6 + Nuxt.js で作るJWT認証つきSPA構築",{"type":10,"value":20321,"toc":23870},[20322,20325,20328,20331,20334,20341,20345,20348,20351,20354,20369,20372,20375,20383,20619,20641,20659,20663,20666,20672,20689,20693,20696,20700,20703,20709,20715,20721,20725,20728,20734,20741,20747,20751,20762,20766,20769,20775,20791,20794,20797,20803,20809,20883,20890,20896,20899,20902,20905,20911,20914,20920,20926,20930,21160,21164,21173,21307,21310,21316,21320,21327,21372,21375,21381,21384,21391,21397,21402,21786,21789,21793,21802,21805,21808,21815,21818,21821,21828,21832,21835,21838,21842,21848,21852,21878,21882,21888,21906,21991,22004,22008,22017,22020,22023,22029,22033,22038,22044,22047,22053,22355,22358,22369,22376,22397,22404,22407,22410,22511,22524,22530,22542,22545,22548,22551,22554,22561,23710,23721,23727,23730,23733,23736,23739,23742,23745,23748,23751,23778,23782,23785,23791,23800,23803,23806,23810,23813,23839,23842,23845,23867],[13,20323,20324],{},"こんにちはjunです。laravel をAPIサーバーとして扱いフロントはNuxt.jsのSPAで構築するプロジェクトがありました。認証を実装したり初期のセッティングでそこそこ詰まったのでメモがてら記事にします。",[13,20326,20327],{},"この手の記事にありがちな「開発サーバーまでの段階しかやらない」ではなくSPAをビルドしてDocker上の仮想的に作成したサーバー環境で実際に一つのアプリケーションとして動かすとこまでやってみます。",[13,20329,20330],{},"独立したフロントエンド とバックをどうやってつなげて、どんなサーバー構成にすればいいのか？という視点で見ていただければ幸いです。また私も昨日にようやく自分の頭で纏った程度なので、この手のアプリケーション作成がﾁｮｯﾄﾃﾞｷﾙ人はぜひアドバイスやコメントお願いします。",[13,20332,20333],{},"Dockerを用いた環境構築概要から話しています。環境がありLravelとNuxtのAPI連携について知りたい人は **「Larvel にJWT認証機能を追加する」**から読みはじめてください。",[13,20335,20336,20337,20340],{},"使用環境\ndocker：19.03.13\nmaxOS Catalina：10.15.5\ncomposer：1.10.6\nNode.js：12.19.0\n",[1463,20338,20339],{},"Laravel：6.20","\nNuxt.js：2.14.6",[17,20342,20344],{"id":20343},"dockerで仮想環境を構築","Dockerで仮想環境を構築",[13,20346,20347],{},"私が今回構築するサーバー構成は以下の図の感じです。",[729,20349],{":src":20350,":width":2993,":center":1323},"'_mix\u002Flaravel-nuxt.jpeg'",[13,20352,20353],{},"webサーバーではバーチャルホスト を使って、80・443などの通常のHTTP通信に対してはNuxt.jsのビルドファイルを返します。そして8000ポートにはLaravelのプロジェクトをおいて、APIサーバーとして使用します。",[13,20355,20356,20357,20360,20361,20364,20365,20368],{},"Nuxt.jsはLaravelから発行されたトークンを用いてAPIにアクセスしてデータを引っ張ります。実際の運用ではドメインが付与されるので、例えば",[49,20358,20359],{},"service.com","として、",[49,20362,20363],{},"http(s):\u002F\u002Fservice.com","はNuxt.jsへ、そしてNuxt.jsは",[49,20366,20367],{},"https:\u002F\u002Fapi.service.com","へAPIを飛ばします。",[13,20370,20371],{},"バーチャルホスト の設定をすることで公開側とAPIをconfレベルで分けることができます。つまりDockerのwebサーバーイメージには、ApacheまたはNginxの設定ファイルをボリュームして、それぞれのドキュメントルート を指定できるような環境があれば大丈夫です。（無理に私のイメージとかに合わせなくても大丈夫ということです。）",[1499,20373,20374],{"id":20374},"docker-composeは以下のような感じ",[13,20376,20377,20379,20380,20382],{},[2039,20378,2044],{"href":9569},"記事で作成したイメージを使用します。cenotsから構築したLAMP環境です。",[49,20381,8670],{},"イメージを用いてweb側をビルドしています。",[42,20384,20386],{"className":6173,"code":20385,"filename":7810,"language":6175,"meta":47,"style":47},"version: '3'\nservices: \n  web_1:\n    image: centos_apache:1.0\n    depends_on: \n      - db\n    volumes: \n      - .\u002Fhtml\u002F:\u002Fvar\u002Fwww\u002Fhtml\u002F\n      - .\u002Fweb_1\u002Fhttpd.conf:\u002Fetc\u002Fhttpd\u002Fconf\u002Fhttpd.conf\n      - \u002Fsys\u002Ffs\u002Fcgroup:\u002Fsys\u002Ffs\u002Fcgroup:ro\n    ports: \n      - \"9000:80\"\n      - \"3000:3000\"\n      - \"8000:8000\"\n    privileged: true\n    command: \u002Fsbin\u002Finit\n  db:\n    image: mysql:5.7\n    environment:\n      MYSQL_DATABASE: trend_system\n      MYSQL_USER: trend\n      MYSQL_PASSWORD: trendtrend\n      MYSQL_ROOT_PASSWORD: rootroot\n    ports: \n      - \"3306:3306\"\n    volumes: \n      - laravel_data:\u002Fvar\u002Flib\u002Fmysql\nvolumes: \n  laravel_data: {}\n",[49,20387,20388,20400,20408,20414,20422,20430,20436,20444,20451,20458,20464,20472,20482,20492,20503,20511,20519,20525,20533,20539,20547,20555,20563,20571,20579,20589,20597,20603,20611],{"__ignoreMap":47},[52,20389,20390,20392,20394,20396,20398],{"class":54,"line":55},[52,20391,7817],{"class":62},[52,20393,373],{"class":58},[52,20395,6309],{"class":58},[52,20397,39],{"class":75},[52,20399,6315],{"class":58},[52,20401,20402,20404,20406],{"class":54,"line":83},[52,20403,7830],{"class":62},[52,20405,373],{"class":58},[52,20407,283],{"class":105},[52,20409,20410,20412],{"class":54,"line":115},[52,20411,7839],{"class":62},[52,20413,6220],{"class":58},[52,20415,20416,20418,20420],{"class":54,"line":142},[52,20417,2534],{"class":62},[52,20419,373],{"class":58},[52,20421,8730],{"class":75},[52,20423,20424,20426,20428],{"class":54,"line":169},[52,20425,7855],{"class":62},[52,20427,373],{"class":58},[52,20429,283],{"class":105},[52,20431,20432,20434],{"class":54,"line":302},[52,20433,7864],{"class":58},[52,20435,7867],{"class":75},[52,20437,20438,20440,20442],{"class":54,"line":308},[52,20439,7872],{"class":62},[52,20441,373],{"class":58},[52,20443,283],{"class":105},[52,20445,20446,20448],{"class":54,"line":318},[52,20447,7864],{"class":58},[52,20449,20450],{"class":75}," .\u002Fhtml\u002F:\u002Fvar\u002Fwww\u002Fhtml\u002F\n",[52,20452,20453,20455],{"class":54,"line":328},[52,20454,7864],{"class":58},[52,20456,20457],{"class":75}," .\u002Fweb_1\u002Fhttpd.conf:\u002Fetc\u002Fhttpd\u002Fconf\u002Fhttpd.conf\n",[52,20459,20460,20462],{"class":54,"line":337},[52,20461,7864],{"class":58},[52,20463,7890],{"class":75},[52,20465,20466,20468,20470],{"class":54,"line":344},[52,20467,7895],{"class":62},[52,20469,373],{"class":58},[52,20471,283],{"class":105},[52,20473,20474,20476,20478,20480],{"class":54,"line":354},[52,20475,7864],{"class":58},[52,20477,3597],{"class":58},[52,20479,7908],{"class":75},[52,20481,266],{"class":58},[52,20483,20484,20486,20488,20490],{"class":54,"line":367},[52,20485,7864],{"class":58},[52,20487,3597],{"class":58},[52,20489,7919],{"class":75},[52,20491,266],{"class":58},[52,20493,20494,20496,20498,20501],{"class":54,"line":387},[52,20495,7864],{"class":58},[52,20497,3597],{"class":58},[52,20499,20500],{"class":75},"8000:8000",[52,20502,266],{"class":58},[52,20504,20505,20507,20509],{"class":54,"line":415},[52,20506,7926],{"class":62},[52,20508,373],{"class":58},[52,20510,7931],{"class":6196},[52,20512,20513,20515,20517],{"class":54,"line":427},[52,20514,7936],{"class":62},[52,20516,373],{"class":58},[52,20518,7941],{"class":75},[52,20520,20521,20523],{"class":54,"line":435},[52,20522,7946],{"class":62},[52,20524,6220],{"class":58},[52,20526,20527,20529,20531],{"class":54,"line":446},[52,20528,2534],{"class":62},[52,20530,373],{"class":58},[52,20532,7957],{"class":75},[52,20534,20535,20537],{"class":54,"line":480},[52,20536,7962],{"class":62},[52,20538,6220],{"class":58},[52,20540,20541,20543,20545],{"class":54,"line":509},[52,20542,7969],{"class":62},[52,20544,373],{"class":58},[52,20546,8837],{"class":75},[52,20548,20549,20551,20553],{"class":54,"line":539},[52,20550,7979],{"class":62},[52,20552,373],{"class":58},[52,20554,8846],{"class":75},[52,20556,20557,20559,20561],{"class":54,"line":547},[52,20558,7989],{"class":62},[52,20560,373],{"class":58},[52,20562,8855],{"class":75},[52,20564,20565,20567,20569],{"class":54,"line":553},[52,20566,7999],{"class":62},[52,20568,373],{"class":58},[52,20570,8004],{"class":75},[52,20572,20573,20575,20577],{"class":54,"line":559},[52,20574,7895],{"class":62},[52,20576,373],{"class":58},[52,20578,283],{"class":105},[52,20580,20581,20583,20585,20587],{"class":54,"line":564},[52,20582,7864],{"class":58},[52,20584,3597],{"class":58},[52,20586,8021],{"class":75},[52,20588,266],{"class":58},[52,20590,20591,20593,20595],{"class":54,"line":569},[52,20592,7872],{"class":62},[52,20594,373],{"class":58},[52,20596,283],{"class":105},[52,20598,20599,20601],{"class":54,"line":1106},[52,20600,7864],{"class":58},[52,20602,8038],{"class":75},[52,20604,20605,20607,20609],{"class":54,"line":1135},[52,20606,8043],{"class":62},[52,20608,373],{"class":58},[52,20610,283],{"class":105},[52,20612,20613,20615,20617],{"class":54,"line":1164},[52,20614,8052],{"class":62},[52,20616,373],{"class":58},[52,20618,8057],{"class":58},[13,20620,20621,20622,20624,20625,20628,20629,20632,20633,20636,20637,20640],{},"ホストマシンの",[49,20623,9132],{},"にアクセスするとコンテナの",[49,20626,20627],{},"localhost:80","につながります。Nuxt.jsの開発サーバーポートとなる",[49,20630,20631],{},"3000","はホストとコンテナ一緒にしています。多分使わないのですが ",[49,20634,20635],{},"php artisan serve","で立てるLaravel開発サーバーポート",[49,20638,20639],{},"8000","も一応用意しておきます。",[13,20642,20643,20644,20646,20647,2969,20650,20652,20653,20655,20656,20658],{},"ホスト側には",[49,20645,46],{},"ディレクトリに",[49,20648,20649],{},"frontend",[49,20651,9274],{},"と言ったディレクトリがあります。Nuxt、Laravelのソースがそれぞれに配置されています。コンテナ起動時にはその",[49,20654,46],{},"ディレクトリはコンテナの",[49,20657,9325],{},"にボリュームされます。",[1499,20660,20662],{"id":20661},"httpdconfは以下の感じ","httpd.confは以下の感じ",[13,20664,20665],{},"Nginx兄貴達にはすみませんが、とりあえず以下のようなバーチャルホスト 設定が立てられば大丈夫です。",[42,20667,20670],{"className":20668,"code":20669,"filename":8093,"language":452,"meta":47},[1615],"Listen 8000\n\u003CDirectory \"\u002Fvar\u002Fwww\u002Fhtml\u002Ffrontend\u002Fdist\">\n    Options Indexes FollowSymLinks\n    AllowOverride All\n    Require all granted\n\u003C\u002FDirectory>\n\n\u003CVirtualHost *:80>\n    ServerName example.com\n    DocumentRoot \u002Fvar\u002Fwww\u002Fhtml\u002Ffrontend\u002Fdist\n\u003C\u002FVirtualHost>\n\n\u003CVirtualHost *:8000>\n    ServerName example.com\n    DocumentRoot \u002Fvar\u002Fwww\u002Fhtml\u002Flaravel\u002Fpublic\n\u003C\u002FVirtualHost>\n",[49,20671,20669],{"__ignoreMap":47},[13,20673,20674,20675,20677,20678,20681,20682,11465,20685,20688],{},"forntendはnuxtのプロジェクトディレクトリ、laravelはLaravelのプロジェクトディレクトリ名です。80でリクエストが来たらnuxtのビルドした",[49,20676,2121],{},"がある",[49,20679,20680],{},"\u002Fvar\u002Fwww\u002Fhtml\u002Ffrontend\u002Fdist","へ飛ばされます。一応後でブラウザからはアクセスできないようにしますが、8000できたリクエストは",[49,20683,20684],{},"\u002Fvar\u002Fwww\u002Fhtml\u002Flaravel\u002Fpublic",[49,20686,20687],{},"index.php","がキャッチします。",[1499,20690,20692],{"id":20691},"ぶっちゃけdockerなくてもとりあえず行けます","ぶっちゃけDockerなくてもとりあえず行けます",[13,20694,20695],{},"初心者の人は「？」となるかもしれません。しかしDBがあり、ホストマシン上でnuxtプロジェクトとLaravelお互いの開発サーバ同士で連絡が取れれば、見出しの内容は実装可能です。ビルドまでしたり総合的に確かめたい場合にDockerを使用してください。",[17,20697,20699],{"id":20698},"laravelの設定をする","Laravelの設定をする",[1499,20701,20702],{"id":20702},"コンテナを起動",[13,20704,20705,20706,20708],{},"docker-compose.ymlがあるディレクトリで",[49,20707,15387],{}," をします。特に問題なければコンテナーが起動するはずです。起動したら",[42,20710,20713],{"className":20711,"code":20712,"language":452},[1615],"docker exec -it {web側のコンテナ名} \u002Fbin\u002Fbash\n",[49,20714,20712],{"__ignoreMap":47},[13,20716,20717,20718,20720],{},"をしてコンテナに入りましょう。",[49,20719,9325],{},"にいくとマウントしたLaravelプロジェクトとNuxt.jsプロジェクトがいるはずです。",[1499,20722,20724],{"id":20723},"laravel-のenvファイルを変更","laravel のenvファイルを変更",[13,20726,20727],{},"マイグレーションをしたいのでlaravelのenvを設定します。",[42,20729,20732],{"className":20730,"code":20731,"filename":6497,"language":452,"meta":47},[1615],"DB_CONNECTION=mysql\nDB_HOST=db \u002F\u002F docker-compose.yml で定義したDBのコンテナ\nDB_PORT=3306\nDB_DATABASE=laravel_test\nDB_USERNAME=root\nDB_PASSWORD=rootroot\n",[49,20733,20731],{"__ignoreMap":47},[13,20735,20736,20737,20740],{},"docker-composeのおかげでこの設定であればつながります。コンテナの中",[49,20738,20739],{},"\u002Fvar\u002Fwww\u002Fhtmm\u002Flaravel","にてマイグレーションを実行します。",[42,20742,20745],{"className":20743,"code":20744,"language":452},[1615],"$ php artisan migrate\n",[49,20746,20744],{"__ignoreMap":47},[17,20748,20750],{"id":20749},"larvel-にjwt認証機能を追加する","Larvel にJWT認証機能を追加する",[13,20752,20753,20754,20757,20758,20761],{},"ここからが本番です。ちなみに私が使用している",[1463,20755,20756],{},"Laravel はバージョン6.2.0","です。バージョンによって設定が異なったりします。この ",[1463,20759,20760],{},"記事は2020年12月時点でLravel 8 は出ているけど、LTSなどの都合でLravel 6を使用するという状況で書いています。"," sライブラリなどはあえてバージョンを指定したりしていますので注意してください。",[1499,20763,20765],{"id":20764},"jwtライブラリをインストール","JWTライブラリをインストール",[13,20767,20768],{},"Laravelには標準でJTWが入っていないのでライブラリをインストールします。",[42,20770,20773],{"className":20771,"code":20772,"language":452},[1615],"composer require tymon\u002Fjwt-auth:1.0.0-rc.5\n",[49,20774,20772],{"__ignoreMap":47},[13,20776,20777,20780,20781,20784,20785,20790],{},[49,20778,20779],{},"tymon\u002Fjwt-auth"," というライブラリを入れますが、Laravel 6 の場合はバージョンに",[49,20782,20783],{},":1.0.0-rc.5","を指定しないとエラーになります。以下は",[2039,20786,20789],{"href":20787,"rel":20788},"https:\u002F\u002Fjwt-auth.readthedocs.io\u002Fen\u002Fdevelop\u002F",[2043],"このライブラリのドキュメント","通りの実装です。",[1499,20792,20793],{"id":20793},"設定を一部変更",[13,20795,20796],{},"ライブラリのインストールをすればJWTは使えるようになります。しかし今回の構築では一部変更しなければならない箇所があったので、設定ファイルを生成します。",[42,20798,20801],{"className":20799,"code":20800,"language":452},[1615],"php artisan vendor:publish --provider=\"Tymon\\JWTAuth\\Providers\\LaravelServiceProvider\"\n",[49,20802,20800],{"__ignoreMap":47},[13,20804,20805,20808],{},[49,20806,20807],{},"config\u002Fjwt.php","というファイルが生成されたはずです。このファイルにおいて以下の部分を書き換えます。",[42,20810,20812],{"className":13693,"code":20811,"language":8577,"meta":47,"style":47},"'providers' => [\n\n\u002F*\n|--------------------------------------------------------------------------\n| JWT Provider\n|--------------------------------------------------------------------------\n|\n| Specify the provider that is used to create and decode the tokens.\n|\n*\u002F\n\n\u002F\u002F 'jwt' => Tymon\\JWTAuth\\Providers\\JWT\\Lcobucci::class,\n'jwt' => Tymon\\JWTAuth\\Providers\\JWT\\Namshi::class,\n\n]\n",[49,20813,20814,20819,20823,20828,20833,20838,20842,20847,20852,20856,20861,20865,20870,20875,20879],{"__ignoreMap":47},[52,20815,20816],{"class":54,"line":55},[52,20817,20818],{},"'providers' => [\n",[52,20820,20821],{"class":54,"line":83},[52,20822,341],{"emptyLinePlaceholder":340},[52,20824,20825],{"class":54,"line":115},[52,20826,20827],{},"\u002F*\n",[52,20829,20830],{"class":54,"line":142},[52,20831,20832],{},"|--------------------------------------------------------------------------\n",[52,20834,20835],{"class":54,"line":169},[52,20836,20837],{},"| JWT Provider\n",[52,20839,20840],{"class":54,"line":302},[52,20841,20832],{},[52,20843,20844],{"class":54,"line":308},[52,20845,20846],{},"|\n",[52,20848,20849],{"class":54,"line":318},[52,20850,20851],{},"| Specify the provider that is used to create and decode the tokens.\n",[52,20853,20854],{"class":54,"line":328},[52,20855,20846],{},[52,20857,20858],{"class":54,"line":337},[52,20859,20860],{},"*\u002F\n",[52,20862,20863],{"class":54,"line":344},[52,20864,341],{"emptyLinePlaceholder":340},[52,20866,20867],{"class":54,"line":354},[52,20868,20869],{},"\u002F\u002F 'jwt' => Tymon\\JWTAuth\\Providers\\JWT\\Lcobucci::class,\n",[52,20871,20872],{"class":54,"line":367},[52,20873,20874],{},"'jwt' => Tymon\\JWTAuth\\Providers\\JWT\\Namshi::class,\n",[52,20876,20877],{"class":54,"line":387},[52,20878,341],{"emptyLinePlaceholder":340},[52,20880,20881],{"class":54,"line":415},[52,20882,6730],{},[13,20884,20885,20886,20889],{},"JWTのプロバイダーを",[49,20887,20888],{},"Tymon\\JWTAuth\\Providers\\JWT\\Namshi::class","に変更します。というのも標準の設定でいくと",[42,20891,20894],{"className":20892,"code":20893,"language":452},[1615],"Tymon\\JWTAuth\\Exceptions\\JWTException: Could not create token: Implicit conversion of keys from strings is deprecated. Please use InMemory or LocalFileReference classes. \n",[49,20895,20893],{"__ignoreMap":47},[13,20897,20898],{},"というエラーが生じます。JWTトークンを生成する処理に非推奨な部分があるそうで、プロバイダを変更する必要がありました。参考記事にあるgithub issueがお世話になりました。",[1499,20900,20901],{"id":20901},"シークレットを生成",[13,20903,20904],{},"JWTトークンを生成するためのシークレットを作成します。",[42,20906,20909],{"className":20907,"code":20908,"language":452},[1615],"php artisan jwt:secret\n",[49,20910,20908],{"__ignoreMap":47},[13,20912,20913],{},"するとenvファイルの下の方に",[42,20915,20918],{"className":20916,"code":20917,"language":452},[1615],"JWT_SECRET=ve1b******\n",[49,20919,20917],{"__ignoreMap":47},[13,20921,20922,20923,20925],{},"というものが生成されます。ちなみにこのシークレットは絶対に外部に漏れてはいけません。間違ってgithubとかに上げないように ",[49,20924,6497],{},"ファイルはgitignoreしましょう。",[1499,20927,20929],{"id":20928},"userモデルにメソッドを追加","Userモデルにメソッドを追加",[42,20931,20934],{"className":13693,"code":20932,"filename":20933,"language":8577,"meta":47,"style":47},"\u003C?php\n\nnamespace App;\n\nuse Illuminate\\Contracts\\Auth\\MustVerifyEmail;\nuse Illuminate\\Foundation\\Auth\\User as Authenticatable;\nuse Illuminate\\Notifications\\Notifiable;\nuse Tymon\\JWTAuth\\Contracts\\JWTSubject; \u002F\u002F追加\n\nclass User extends Authenticatable implements JWTSubject \u002F\u002F追加\n{\n    use Notifiable;\n\n    \u002F**\n     * The attributes that are mass assignable.\n     *\n     * @var array\n     *\u002F\n    protected $fillable = [\n        'name', 'email', 'password',\n    ];\n\n    \u002F**\n     * The attributes that should be hidden for arrays.\n     *\n     * @var array\n     *\u002F\n    protected $hidden = [\n        'password', 'remember_token',\n    ];\n\n    \u002F**\n     * The attributes that should be cast to native types.\n     *\n     * @var array\n     *\u002F\n    protected $casts = [\n        'email_verified_at' => 'datetime',\n    ];\n\n    public function getJWTIdentifier()　 \u002F\u002F追加\n    {\n        return $this->getKey();\n    }\n\n    public function getJWTCustomClaims()　 \u002F\u002F追加\n    {\n        return [];\n    }\n}\n","app\u002FUser.php",[49,20935,20936,20940,20944,20949,20953,20958,20963,20968,20973,20977,20982,20986,20991,20995,21000,21005,21010,21015,21020,21025,21030,21034,21038,21042,21047,21051,21055,21059,21064,21069,21073,21077,21081,21086,21090,21094,21098,21103,21108,21112,21116,21121,21125,21130,21134,21138,21143,21147,21152,21156],{"__ignoreMap":47},[52,20937,20938],{"class":54,"line":55},[52,20939,13868],{},[52,20941,20942],{"class":54,"line":83},[52,20943,341],{"emptyLinePlaceholder":340},[52,20945,20946],{"class":54,"line":115},[52,20947,20948],{},"namespace App;\n",[52,20950,20951],{"class":54,"line":142},[52,20952,341],{"emptyLinePlaceholder":340},[52,20954,20955],{"class":54,"line":169},[52,20956,20957],{},"use Illuminate\\Contracts\\Auth\\MustVerifyEmail;\n",[52,20959,20960],{"class":54,"line":302},[52,20961,20962],{},"use Illuminate\\Foundation\\Auth\\User as Authenticatable;\n",[52,20964,20965],{"class":54,"line":308},[52,20966,20967],{},"use Illuminate\\Notifications\\Notifiable;\n",[52,20969,20970],{"class":54,"line":318},[52,20971,20972],{},"use Tymon\\JWTAuth\\Contracts\\JWTSubject; \u002F\u002F追加\n",[52,20974,20975],{"class":54,"line":328},[52,20976,341],{"emptyLinePlaceholder":340},[52,20978,20979],{"class":54,"line":337},[52,20980,20981],{},"class User extends Authenticatable implements JWTSubject \u002F\u002F追加\n",[52,20983,20984],{"class":54,"line":344},[52,20985,364],{},[52,20987,20988],{"class":54,"line":354},[52,20989,20990],{},"    use Notifiable;\n",[52,20992,20993],{"class":54,"line":367},[52,20994,341],{"emptyLinePlaceholder":340},[52,20996,20997],{"class":54,"line":387},[52,20998,20999],{},"    \u002F**\n",[52,21001,21002],{"class":54,"line":415},[52,21003,21004],{},"     * The attributes that are mass assignable.\n",[52,21006,21007],{"class":54,"line":427},[52,21008,21009],{},"     *\n",[52,21011,21012],{"class":54,"line":435},[52,21013,21014],{},"     * @var array\n",[52,21016,21017],{"class":54,"line":446},[52,21018,21019],{},"     *\u002F\n",[52,21021,21022],{"class":54,"line":480},[52,21023,21024],{},"    protected $fillable = [\n",[52,21026,21027],{"class":54,"line":509},[52,21028,21029],{},"        'name', 'email', 'password',\n",[52,21031,21032],{"class":54,"line":539},[52,21033,14618],{},[52,21035,21036],{"class":54,"line":547},[52,21037,341],{"emptyLinePlaceholder":340},[52,21039,21040],{"class":54,"line":553},[52,21041,20999],{},[52,21043,21044],{"class":54,"line":559},[52,21045,21046],{},"     * The attributes that should be hidden for arrays.\n",[52,21048,21049],{"class":54,"line":564},[52,21050,21009],{},[52,21052,21053],{"class":54,"line":569},[52,21054,21014],{},[52,21056,21057],{"class":54,"line":1106},[52,21058,21019],{},[52,21060,21061],{"class":54,"line":1135},[52,21062,21063],{},"    protected $hidden = [\n",[52,21065,21066],{"class":54,"line":1164},[52,21067,21068],{},"        'password', 'remember_token',\n",[52,21070,21071],{"class":54,"line":1193},[52,21072,14618],{},[52,21074,21075],{"class":54,"line":1200},[52,21076,341],{"emptyLinePlaceholder":340},[52,21078,21079],{"class":54,"line":1205},[52,21080,20999],{},[52,21082,21083],{"class":54,"line":1210},[52,21084,21085],{},"     * The attributes that should be cast to native types.\n",[52,21087,21088],{"class":54,"line":1215},[52,21089,21009],{},[52,21091,21092],{"class":54,"line":1220},[52,21093,21014],{},[52,21095,21096],{"class":54,"line":3800},[52,21097,21019],{},[52,21099,21100],{"class":54,"line":3821},[52,21101,21102],{},"    protected $casts = [\n",[52,21104,21105],{"class":54,"line":3835},[52,21106,21107],{},"        'email_verified_at' => 'datetime',\n",[52,21109,21110],{"class":54,"line":3840},[52,21111,14618],{},[52,21113,21114],{"class":54,"line":3865},[52,21115,341],{"emptyLinePlaceholder":340},[52,21117,21118],{"class":54,"line":3879},[52,21119,21120],{},"    public function getJWTIdentifier()　 \u002F\u002F追加\n",[52,21122,21123],{"class":54,"line":5506},[52,21124,14283],{},[52,21126,21127],{"class":54,"line":4},[52,21128,21129],{},"        return $this->getKey();\n",[52,21131,21132],{"class":54,"line":5544},[52,21133,2696],{},[52,21135,21136],{"class":54,"line":5561},[52,21137,341],{"emptyLinePlaceholder":340},[52,21139,21140],{"class":54,"line":5566},[52,21141,21142],{},"    public function getJWTCustomClaims()　 \u002F\u002F追加\n",[52,21144,21145],{"class":54,"line":5587},[52,21146,14283],{},[52,21148,21149],{"class":54,"line":5636},[52,21150,21151],{},"        return [];\n",[52,21153,21154],{"class":54,"line":5641},[52,21155,2696],{},[52,21157,21158],{"class":54,"line":5646},[52,21159,536],{},[1499,21161,21163],{"id":21162},"authphpの認証ドライバを変更","auth.phpの認証ドライバを変更",[13,21165,21166,6465,21169,21172],{},[49,21167,21168],{},"config\u002Fauth.php",[49,21170,21171],{},"Authentication","認証ドライバを変更します。",[42,21174,21176],{"className":13693,"code":21175,"filename":21168,"language":8577,"meta":47,"style":47},"    \u002F*\n    |--------------------------------------------------------------------------\n    | Authentication Defaults\n    |--------------------------------------------------------------------------\n    |\n    | This option controls the default authentication \"guard\" and password\n    | reset options for your application. You may change these defaults\n    | as required, but they're a perfect start for most applications.\n    |\n    *\u002F\n\n    'defaults' => [\n        'guard' => 'api', \u002F\u002F変更\n        'passwords' => 'users',\n    ],\n...\n    'guards' => [\n        'web' => [\n            'driver' => 'session',\n            'provider' => 'users',\n        ],\n\n        'api' => [\n            'driver' => 'jwt', \u002F\u002F変更\n            'provider' => 'users',\n            'hash' => false,\n",[49,21177,21178,21183,21188,21193,21197,21202,21207,21212,21217,21221,21226,21230,21235,21243,21248,21253,21257,21262,21267,21272,21277,21282,21286,21291,21298,21302],{"__ignoreMap":47},[52,21179,21180],{"class":54,"line":55},[52,21181,21182],{},"    \u002F*\n",[52,21184,21185],{"class":54,"line":83},[52,21186,21187],{},"    |--------------------------------------------------------------------------\n",[52,21189,21190],{"class":54,"line":115},[52,21191,21192],{},"    | Authentication Defaults\n",[52,21194,21195],{"class":54,"line":142},[52,21196,21187],{},[52,21198,21199],{"class":54,"line":169},[52,21200,21201],{},"    |\n",[52,21203,21204],{"class":54,"line":302},[52,21205,21206],{},"    | This option controls the default authentication \"guard\" and password\n",[52,21208,21209],{"class":54,"line":308},[52,21210,21211],{},"    | reset options for your application. You may change these defaults\n",[52,21213,21214],{"class":54,"line":318},[52,21215,21216],{},"    | as required, but they're a perfect start for most applications.\n",[52,21218,21219],{"class":54,"line":328},[52,21220,21201],{},[52,21222,21223],{"class":54,"line":337},[52,21224,21225],{},"    *\u002F\n",[52,21227,21228],{"class":54,"line":344},[52,21229,341],{"emptyLinePlaceholder":340},[52,21231,21232],{"class":54,"line":354},[52,21233,21234],{},"    'defaults' => [\n",[52,21236,21237,21240],{"class":54,"line":367},[52,21238,21239],{},"        'guard' => 'api',",[52,21241,21242],{}," \u002F\u002F変更\n",[52,21244,21245],{"class":54,"line":387},[52,21246,21247],{},"        'passwords' => 'users',\n",[52,21249,21250],{"class":54,"line":415},[52,21251,21252],{},"    ],\n",[52,21254,21255],{"class":54,"line":427},[52,21256,14102],{},[52,21258,21259],{"class":54,"line":435},[52,21260,21261],{},"    'guards' => [\n",[52,21263,21264],{"class":54,"line":446},[52,21265,21266],{},"        'web' => [\n",[52,21268,21269],{"class":54,"line":480},[52,21270,21271],{},"            'driver' => 'session',\n",[52,21273,21274],{"class":54,"line":509},[52,21275,21276],{},"            'provider' => 'users',\n",[52,21278,21279],{"class":54,"line":539},[52,21280,21281],{},"        ],\n",[52,21283,21284],{"class":54,"line":547},[52,21285,341],{"emptyLinePlaceholder":340},[52,21287,21288],{"class":54,"line":553},[52,21289,21290],{},"        'api' => [\n",[52,21292,21293,21296],{"class":54,"line":559},[52,21294,21295],{},"            'driver' => 'jwt',",[52,21297,21242],{},[52,21299,21300],{"class":54,"line":564},[52,21301,21276],{},[52,21303,21304],{"class":54,"line":569},[52,21305,21306],{},"            'hash' => false,\n",[13,21308,21309],{},"configはキャッシュされていますので、configをいじった後はキャッシュクリアをしましょう。",[42,21311,21314],{"className":21312,"code":21313,"language":452},[1615],"php artisan config:clear\n",[49,21315,21313],{"__ignoreMap":47},[1499,21317,21319],{"id":21318},"jwt認証用のルートを作成","JWT認証用のルートを作成",[13,21321,21322,21323,21326],{},"認証のルートを作成します。",[49,21324,21325],{},"route\u002Fapi.php","に以下のように記述します。",[42,21328,21330],{"className":13693,"code":21329,"filename":21325,"language":8577,"meta":47,"style":47},"Route::prefix('v1')->group(function(){\n    Route::group(['middleware' => 'api', 'prefix' => 'auth'], function ($router) {\n        Route::post('login', 'AuthController@login')->name('login');;\n        Route::post('logout', 'AuthController@logout');\n        Route::post('refresh', 'AuthController@refresh');\n        Route::get('me', 'AuthController@me');\n    });\n});\n",[49,21331,21332,21337,21342,21347,21352,21357,21362,21367],{"__ignoreMap":47},[52,21333,21334],{"class":54,"line":55},[52,21335,21336],{},"Route::prefix('v1')->group(function(){\n",[52,21338,21339],{"class":54,"line":83},[52,21340,21341],{},"    Route::group(['middleware' => 'api', 'prefix' => 'auth'], function ($router) {\n",[52,21343,21344],{"class":54,"line":115},[52,21345,21346],{},"        Route::post('login', 'AuthController@login')->name('login');;\n",[52,21348,21349],{"class":54,"line":142},[52,21350,21351],{},"        Route::post('logout', 'AuthController@logout');\n",[52,21353,21354],{"class":54,"line":169},[52,21355,21356],{},"        Route::post('refresh', 'AuthController@refresh');\n",[52,21358,21359],{"class":54,"line":302},[52,21360,21361],{},"        Route::get('me', 'AuthController@me');\n",[52,21363,21364],{"class":54,"line":308},[52,21365,21366],{},"    });\n",[52,21368,21369],{"class":54,"line":318},[52,21370,21371],{},"});\n",[13,21373,21374],{},"ルートの設定は運用によって変わると思いますが、APIはバージョンで予め分けておくと将来の拡張性が高まります。一回ぽっきりのサービスでも実装して損はないと思います。上記のルートでは以下のような設定になります。",[42,21376,21379],{"className":21377,"code":21378,"language":452},[1615],"$ php artisan route:list\n+--------+----------+---------------------+-------+---------------------------------------------+--------------+\n| Domain | Method   | URI                 | Name  | Action                                      | Middleware   |\n+--------+----------+---------------------+-------+---------------------------------------------+--------------+\n|        | POST     | api\u002Fv1\u002Fauth\u002Flogin   | login | App\\Http\\Controllers\\AuthController@login   | api          |\n|        | POST     | api\u002Fv1\u002Fauth\u002Flogout  |       | App\\Http\\Controllers\\AuthController@logout  | api,auth:api |\n|        | GET|HEAD | api\u002Fv1\u002Fauth\u002Fme      |       | App\\Http\\Controllers\\AuthController@me      | api,auth:api |\n|        | POST     | api\u002Fv1\u002Fauth\u002Frefresh |       | App\\Http\\Controllers\\AuthController@refresh | api,auth:api |\n+--------+----------+---------------------+-------+---------------------------------------------+--------------+\n",[49,21380,21378],{"__ignoreMap":47},[1499,21382,21383],{"id":21383},"認証ルートに対するコントローラを作成",[13,21385,21386,21387,21390],{},"それぞれのルートは",[49,21388,21389],{},"AuthController.php","につなげる予定ですので、そのコントローラーを作成します。",[42,21392,21395],{"className":21393,"code":21394,"language":452},[1615],"php artisan make:controller AuthController\n",[49,21396,21394],{"__ignoreMap":47},[13,21398,21399,21401],{},[49,21400,21389],{},"は以下のような設定になります。",[42,21403,21406],{"className":13693,"code":21404,"filename":21405,"language":8577,"meta":47,"style":47},"\u003C?php\n\nnamespace App\\Http\\Controllers;\n\nuse Illuminate\\Http\\Request;\nuse Illuminate\\Support\\Facades\\Auth;\n\nclass AuthController extends Controller\n{\n    \u002F**\n     * Create a new AuthController instance.\n     *\n     * @return void\n     *\u002F\n    public function __construct()\n    {\n        $this->middleware('auth:api', ['except' => ['login']]);\n    }\n\n    \u002F**\n     * Get a JWT via given credentials.\n     *\n     * @return \\Illuminate\\Http\\JsonResponse\n     *\u002F\n    public function login()\n    {\n        $credentials = request(['email', 'password']);\n\n        if (! $token = auth()->attempt($credentials)) {\n            return response()->json(['error' => 'Unauthorized'], 401);\n        }\n\n        return $this->respondWithToken($token);\n    }\n\n    \u002F**\n     * Get the authenticated User.\n     *\n     * @return \\Illuminate\\Http\\JsonResponse\n     *\u002F\n    public function me()\n    {\n        return response()->json(auth()->user());\n    }\n\n    \u002F**\n     * Log the user out (Invalidate the token).\n     *\n     * @return \\Illuminate\\Http\\JsonResponse\n     *\u002F\n    public function logout()\n    {\n        auth()->logout();\n\n        return response()->json(['message' => 'Successfully logged out']);\n    }\n\n    \u002F**\n     * Refresh a token.\n     *\n     * @return \\Illuminate\\Http\\JsonResponse\n     *\u002F\n    public function refresh()\n    {\n        return $this->respondWithToken(auth()->refresh());\n    }\n\n    \u002F**\n     * Get the token array structure.\n     *\n     * @param  string $token\n     *\n     * @return \\Illuminate\\Http\\JsonResponse\n     *\u002F\n    protected function respondWithToken($token)\n    {\n        return response()->json([\n            'access_token' => $token,\n            'token_type' => 'bearer',\n            'expires_in' => auth()->factory()->getTTL() * 60\n        ]);\n    }\n}\n","app\u002FHttp\u002FControllers\u002FAuthController.php",[49,21407,21408,21412,21416,21421,21425,21430,21435,21439,21444,21448,21452,21457,21461,21466,21470,21475,21479,21484,21488,21492,21496,21501,21505,21510,21514,21519,21523,21528,21532,21537,21542,21546,21550,21555,21559,21563,21567,21572,21576,21580,21584,21589,21593,21598,21602,21606,21610,21615,21619,21623,21627,21632,21636,21641,21645,21650,21654,21658,21662,21667,21671,21675,21679,21684,21688,21693,21697,21701,21705,21710,21714,21720,21725,21730,21735,21741,21746,21752,21758,21764,21770,21776,21781],{"__ignoreMap":47},[52,21409,21410],{"class":54,"line":55},[52,21411,13868],{},[52,21413,21414],{"class":54,"line":83},[52,21415,341],{"emptyLinePlaceholder":340},[52,21417,21418],{"class":54,"line":115},[52,21419,21420],{},"namespace App\\Http\\Controllers;\n",[52,21422,21423],{"class":54,"line":142},[52,21424,341],{"emptyLinePlaceholder":340},[52,21426,21427],{"class":54,"line":169},[52,21428,21429],{},"use Illuminate\\Http\\Request;\n",[52,21431,21432],{"class":54,"line":302},[52,21433,21434],{},"use Illuminate\\Support\\Facades\\Auth;\n",[52,21436,21437],{"class":54,"line":308},[52,21438,341],{"emptyLinePlaceholder":340},[52,21440,21441],{"class":54,"line":318},[52,21442,21443],{},"class AuthController extends Controller\n",[52,21445,21446],{"class":54,"line":328},[52,21447,364],{},[52,21449,21450],{"class":54,"line":337},[52,21451,20999],{},[52,21453,21454],{"class":54,"line":344},[52,21455,21456],{},"     * Create a new AuthController instance.\n",[52,21458,21459],{"class":54,"line":354},[52,21460,21009],{},[52,21462,21463],{"class":54,"line":367},[52,21464,21465],{},"     * @return void\n",[52,21467,21468],{"class":54,"line":387},[52,21469,21019],{},[52,21471,21472],{"class":54,"line":415},[52,21473,21474],{},"    public function __construct()\n",[52,21476,21477],{"class":54,"line":427},[52,21478,14283],{},[52,21480,21481],{"class":54,"line":435},[52,21482,21483],{},"        $this->middleware('auth:api', ['except' => ['login']]);\n",[52,21485,21486],{"class":54,"line":446},[52,21487,2696],{},[52,21489,21490],{"class":54,"line":480},[52,21491,341],{"emptyLinePlaceholder":340},[52,21493,21494],{"class":54,"line":509},[52,21495,20999],{},[52,21497,21498],{"class":54,"line":539},[52,21499,21500],{},"     * Get a JWT via given credentials.\n",[52,21502,21503],{"class":54,"line":547},[52,21504,21009],{},[52,21506,21507],{"class":54,"line":553},[52,21508,21509],{},"     * @return \\Illuminate\\Http\\JsonResponse\n",[52,21511,21512],{"class":54,"line":559},[52,21513,21019],{},[52,21515,21516],{"class":54,"line":564},[52,21517,21518],{},"    public function login()\n",[52,21520,21521],{"class":54,"line":569},[52,21522,14283],{},[52,21524,21525],{"class":54,"line":1106},[52,21526,21527],{},"        $credentials = request(['email', 'password']);\n",[52,21529,21530],{"class":54,"line":1135},[52,21531,341],{"emptyLinePlaceholder":340},[52,21533,21534],{"class":54,"line":1164},[52,21535,21536],{},"        if (! $token = auth()->attempt($credentials)) {\n",[52,21538,21539],{"class":54,"line":1193},[52,21540,21541],{},"            return response()->json(['error' => 'Unauthorized'], 401);\n",[52,21543,21544],{"class":54,"line":1200},[52,21545,5398],{},[52,21547,21548],{"class":54,"line":1205},[52,21549,341],{"emptyLinePlaceholder":340},[52,21551,21552],{"class":54,"line":1210},[52,21553,21554],{},"        return $this->respondWithToken($token);\n",[52,21556,21557],{"class":54,"line":1215},[52,21558,2696],{},[52,21560,21561],{"class":54,"line":1220},[52,21562,341],{"emptyLinePlaceholder":340},[52,21564,21565],{"class":54,"line":3800},[52,21566,20999],{},[52,21568,21569],{"class":54,"line":3821},[52,21570,21571],{},"     * Get the authenticated User.\n",[52,21573,21574],{"class":54,"line":3835},[52,21575,21009],{},[52,21577,21578],{"class":54,"line":3840},[52,21579,21509],{},[52,21581,21582],{"class":54,"line":3865},[52,21583,21019],{},[52,21585,21586],{"class":54,"line":3879},[52,21587,21588],{},"    public function me()\n",[52,21590,21591],{"class":54,"line":5506},[52,21592,14283],{},[52,21594,21595],{"class":54,"line":4},[52,21596,21597],{},"        return response()->json(auth()->user());\n",[52,21599,21600],{"class":54,"line":5544},[52,21601,2696],{},[52,21603,21604],{"class":54,"line":5561},[52,21605,341],{"emptyLinePlaceholder":340},[52,21607,21608],{"class":54,"line":5566},[52,21609,20999],{},[52,21611,21612],{"class":54,"line":5587},[52,21613,21614],{},"     * Log the user out (Invalidate the token).\n",[52,21616,21617],{"class":54,"line":5636},[52,21618,21009],{},[52,21620,21621],{"class":54,"line":5641},[52,21622,21509],{},[52,21624,21625],{"class":54,"line":5646},[52,21626,21019],{},[52,21628,21629],{"class":54,"line":5679},[52,21630,21631],{},"    public function logout()\n",[52,21633,21634],{"class":54,"line":5692},[52,21635,14283],{},[52,21637,21638],{"class":54,"line":5711},[52,21639,21640],{},"        auth()->logout();\n",[52,21642,21643],{"class":54,"line":5724},[52,21644,341],{"emptyLinePlaceholder":340},[52,21646,21647],{"class":54,"line":5745},[52,21648,21649],{},"        return response()->json(['message' => 'Successfully logged out']);\n",[52,21651,21652],{"class":54,"line":5750},[52,21653,2696],{},[52,21655,21656],{"class":54,"line":5786},[52,21657,341],{"emptyLinePlaceholder":340},[52,21659,21660],{"class":54,"line":5836},[52,21661,20999],{},[52,21663,21664],{"class":54,"line":5862},[52,21665,21666],{},"     * Refresh a token.\n",[52,21668,21669],{"class":54,"line":5867},[52,21670,21009],{},[52,21672,21673],{"class":54,"line":5872},[52,21674,21509],{},[52,21676,21677],{"class":54,"line":5893},[52,21678,21019],{},[52,21680,21681],{"class":54,"line":5914},[52,21682,21683],{},"    public function refresh()\n",[52,21685,21686],{"class":54,"line":5927},[52,21687,14283],{},[52,21689,21690],{"class":54,"line":5932},[52,21691,21692],{},"        return $this->respondWithToken(auth()->refresh());\n",[52,21694,21695],{"class":54,"line":5953},[52,21696,2696],{},[52,21698,21699],{"class":54,"line":5966},[52,21700,341],{"emptyLinePlaceholder":340},[52,21702,21703],{"class":54,"line":5971},[52,21704,20999],{},[52,21706,21707],{"class":54,"line":5976},[52,21708,21709],{},"     * Get the token array structure.\n",[52,21711,21712],{"class":54,"line":5981},[52,21713,21009],{},[52,21715,21717],{"class":54,"line":21716},71,[52,21718,21719],{},"     * @param  string $token\n",[52,21721,21723],{"class":54,"line":21722},72,[52,21724,21009],{},[52,21726,21728],{"class":54,"line":21727},73,[52,21729,21509],{},[52,21731,21733],{"class":54,"line":21732},74,[52,21734,21019],{},[52,21736,21738],{"class":54,"line":21737},75,[52,21739,21740],{},"    protected function respondWithToken($token)\n",[52,21742,21744],{"class":54,"line":21743},76,[52,21745,14283],{},[52,21747,21749],{"class":54,"line":21748},77,[52,21750,21751],{},"        return response()->json([\n",[52,21753,21755],{"class":54,"line":21754},78,[52,21756,21757],{},"            'access_token' => $token,\n",[52,21759,21761],{"class":54,"line":21760},79,[52,21762,21763],{},"            'token_type' => 'bearer',\n",[52,21765,21767],{"class":54,"line":21766},80,[52,21768,21769],{},"            'expires_in' => auth()->factory()->getTTL() * 60\n",[52,21771,21773],{"class":54,"line":21772},81,[52,21774,21775],{},"        ]);\n",[52,21777,21779],{"class":54,"line":21778},82,[52,21780,2696],{},[52,21782,21784],{"class":54,"line":21783},83,[52,21785,536],{},[13,21787,21788],{},"これでJWT認証に必要なLarvel側の設定が終了しました。",[17,21790,21792],{"id":21791},"talend-api-testerでapiをチェック","Talend API TesterでAPIをチェック",[13,21794,21795,21796,21801],{},"きちんとAPIが存在しているかを確かめるために、chrome拡張の",[2039,21797,21800],{"href":21798,"rel":21799},"https:\u002F\u002Fchrome.google.com\u002Fwebstore\u002Fdetail\u002Ftalend-api-tester-free-ed\u002Faejoelaoggembcahagimdiliamlcdmfm",[2043],"Talend API Tester","を用いてチェックします。予めシーダーで作成した仮ユーザーデータを用いて以下のように入力してみます。",[729,21803],{":src":21804,":width":732},"'_mix\u002Fsch-2020-12-05-14.17.54-768x353.png'",[729,21806],{":src":21807,":width":732},"'_mix\u002Fsch-2020-12-05-14.20.21-768x318.png'",[13,21809,21810,21811,21814],{},"JSONでアクセストークン が帰ってきました。このアクセストークン をリクエストヘッダに入れてリクエストすることで認証が必要なルートにアクセスできるようになります。試しに ",[49,21812,21813],{},"\u002Fapi\u002Fv1\u002Fauth\u002Fme","という現在のユーザー情報を確かめるルートにアクセスしてみます。",[13,21816,21817],{},"HEADERSにAuthorizationというものを追加し、先ほどのトークンを入れています。トークン値は「bearer トークン」という形です。",[729,21819],{":src":21820,":width":732},"'_mix\u002Fsch-2020-12-05-14.23.34-768x224.png'",[13,21822,21823,21824,21827],{},"ユーザー情報が返ってきましたね。",[49,21825,21826],{},"Authcontroller@me","で定義した返り値がきちんとえられました。ちなみに認証情報がない場合はHTTP 401を返します。",[17,21829,21831],{"id":21830},"nuxtjsからapiを呼ぶ","Nuxt.jsからAPIを呼ぶ",[1499,21833,21834],{"id":21834},"前準備",[13,21836,21837],{},"Nuxt.jsの構築に移る前にCORS対策をします。APIサーバーはブラウザからのアクセスを禁止して、XMLHttpRequest（XHR）のみのリクエストを許可するようにします。そしてXHRにはCORSという制約があります。これがあるとローカル以外からのXHRを用いたAPIアクセスが制限されます。特にNuxtを3000ポートで開発している時でもAPIサーバーと通信できるように準備しておきます。",[1566,21839,21841],{"id":21840},"cors設定のライブラリをインストール","CORS設定のライブラリをインストール",[42,21843,21846],{"className":21844,"code":21845,"language":452},[1615],"composer require fruitcake\u002Flaravel-cors\n",[49,21847,21845],{"__ignoreMap":47},[1566,21849,21851],{"id":21850},"apphttpkernelphpのミドルウェア-に追加","app\u002FHttp\u002FKernel.phpのミドルウェア に追加",[42,21853,21856],{"className":13693,"code":21854,"filename":21855,"language":8577,"meta":47,"style":47},"protected $middleware = [\n    \u002F\u002F ...\n    \\Fruitcake\\Cors\\HandleCors::class,\n];\n","app\u002FHttp\u002FKernel.php",[49,21857,21858,21863,21868,21873],{"__ignoreMap":47},[52,21859,21860],{"class":54,"line":55},[52,21861,21862],{},"protected $middleware = [\n",[52,21864,21865],{"class":54,"line":83},[52,21866,21867],{},"    \u002F\u002F ...\n",[52,21869,21870],{"class":54,"line":115},[52,21871,21872],{},"    \\Fruitcake\\Cors\\HandleCors::class,\n",[52,21874,21875],{"class":54,"line":142},[52,21876,21877],{},"];\n",[1566,21879,21881],{"id":21880},"cors設定ファイルを出力して一部変更","CORS設定ファイルを出力して一部変更",[42,21883,21886],{"className":21884,"code":21885,"language":452},[1615],"php artisan vendor:publish --tag=\"cors\"\n",[49,21887,21885],{"__ignoreMap":47},[13,21889,21890,21891,21894,21895,21898,21899,21902,21903,21905],{},"先ほどの",[49,21892,21893],{},"jwt.php","のように",[49,21896,21897],{},"cors.php","が",[49,21900,21901],{},"config\u002F","配下に出現します。その",[49,21904,21897],{},"を以下のように変更します。",[42,21907,21910],{"className":13693,"code":21908,"filename":21909,"language":8577,"meta":47,"style":47},"...\n    \u002F*\n     * You can enable CORS for 1 or multiple paths.\n     * Example: ['api\u002F*']\n     *\u002F\n    'paths' => ['api\u002F*'],\n...\n    \u002F*\n     * Matches the request origin. `['*']` allows all origins. Wildcards can be used, eg `*.mydomain.com`\n     *\u002F\n    'allowed_origins' => [\n          'http:\u002F\u002Flocalhost',\n          'http:\u002F\u002Flocalhost:3000',\u002F\u002F nuxt\n          'http:\u002F\u002Flocalhost:9000' \u002F\u002F dockerの9000->80\n        ],\n\n...\n","confog\u002Fcors.php",[49,21911,21912,21916,21920,21925,21930,21934,21939,21943,21947,21952,21956,21961,21966,21971,21979,21983,21987],{"__ignoreMap":47},[52,21913,21914],{"class":54,"line":55},[52,21915,14102],{},[52,21917,21918],{"class":54,"line":83},[52,21919,21182],{},[52,21921,21922],{"class":54,"line":115},[52,21923,21924],{},"     * You can enable CORS for 1 or multiple paths.\n",[52,21926,21927],{"class":54,"line":142},[52,21928,21929],{},"     * Example: ['api\u002F*']\n",[52,21931,21932],{"class":54,"line":169},[52,21933,21019],{},[52,21935,21936],{"class":54,"line":302},[52,21937,21938],{},"    'paths' => ['api\u002F*'],\n",[52,21940,21941],{"class":54,"line":308},[52,21942,14102],{},[52,21944,21945],{"class":54,"line":318},[52,21946,21182],{},[52,21948,21949],{"class":54,"line":328},[52,21950,21951],{},"     * Matches the request origin. `['*']` allows all origins. Wildcards can be used, eg `*.mydomain.com`\n",[52,21953,21954],{"class":54,"line":337},[52,21955,21019],{},[52,21957,21958],{"class":54,"line":344},[52,21959,21960],{},"    'allowed_origins' => [\n",[52,21962,21963],{"class":54,"line":354},[52,21964,21965],{},"          'http:\u002F\u002Flocalhost',\n",[52,21967,21968],{"class":54,"line":367},[52,21969,21970],{},"          'http:\u002F\u002Flocalhost:3000',\u002F\u002F nuxt\n",[52,21972,21973,21976],{"class":54,"line":387},[52,21974,21975],{},"          'http:\u002F\u002Flocalhost:9000'",[52,21977,21978],{}," \u002F\u002F dockerの9000->80\n",[52,21980,21981],{"class":54,"line":415},[52,21982,21281],{},[52,21984,21985],{"class":54,"line":427},[52,21986,341],{"emptyLinePlaceholder":340},[52,21988,21989],{"class":54,"line":435},[52,21990,14102],{},[13,21992,21993,21996,21997,21999,22000,22003],{},[49,21994,21995],{},"api\u002F"," ルート配下に対してcors設定を行い、そして",[49,21998,9139],{},"及び",[49,22001,22002],{},"3000ポート","からの接続を許可しました。",[1499,22005,22007],{"id":22006},"nuxt-auth-モジュールを用いて認証系の機能を整える","nuxt auth モジュールを用いて認証系の機能を整える",[13,22009,22010,22011,22016],{},"nuxt.jsの構築は飛ばします。私の環境では nuxt+bootstrap+axiosで始めます。認証系をpluginで構築してもいいのですが、なかなか大変ですのでnuxt authモジュールを使用します。このモジュールがあれば認証つきのNuxtアプリを簡単に作成できます。",[2039,22012,22015],{"href":22013,"rel":22014},"https:\u002F\u002Fdev.auth.nuxtjs.org\u002F",[2043],"本家のドキュメント","に習いながら進めましょう。",[1566,22018,22019],{"id":22019},"モジュールのインストール",[13,22021,22022],{},"nuxt.jsのプロジェクトルートに移動いして以下のモジュールをインストールします。",[42,22024,22027],{"className":22025,"code":22026,"language":452},[1615],"npm install @nuxtjs\u002Fauth-next @nuxtjs\u002Faxios\n",[49,22028,22026],{"__ignoreMap":47},[1566,22030,22032],{"id":22031},"nuxtconfigjsでの設定","nuxt.config.jsでの設定",[13,22034,22035,22037],{},[49,22036,9455],{},"でまずモジュールの読み込みをします。",[42,22039,22042],{"className":22040,"code":22041,"language":452},[1615],"...javascript[nuxt.config.js]\n\u002F\u002F Modules (https:\u002F\u002Fgo.nuxtjs.dev\u002Fconfig-modules)\n  modules: [\n    '@nuxtjs\u002Faxios',\n    '@nuxtjs\u002Fauth'\n  ],\n...\n",[49,22043,22041],{"__ignoreMap":47},[13,22045,22046],{},"これでauthモジュールとaxiosモジュールが使えます。authモジュールはstoreを使用しますので、storeにindex.jsがない場合は空でもいいので作っていきます。",[13,22048,22049,22050,22052],{},"そしてauthモジュールの設定を",[49,22051,9455],{},"で行います。",[42,22054,22056],{"className":1250,"code":22055,"filename":9455,"language":1252,"meta":47,"style":47},"...\nauth:{\n    localStorage: false,\n    strategies:{\n      local:{\n        tokenType:'bearer',\n        endpoints:{\n          login:{\n            url:'\u002Fauth\u002Flogin',\n            method:'post',\n            propertyName:'access_token'\n          },\n          logout:{\n            url:'\u002Fauth\u002Flogout',\n            method:'post',\n          },\n          user:{\n            url:'\u002Fauth\u002Fme',\n            method:'get',\n            propertyName:false\n          }\n        }\n      },\n      redirect: {\n        login: '\u002Flogin',\n        logout: '\u002F',\n        callback: '\u002Flogin',\n        home: '\u002Fhome'\n      }\n}\n...\n",[49,22057,22058,22062,22069,22081,22088,22095,22111,22118,22125,22140,22155,22169,22174,22181,22196,22210,22214,22221,22236,22251,22260,22265,22269,22274,22283,22299,22314,22329,22343,22347,22351],{"__ignoreMap":47},[52,22059,22060],{"class":54,"line":55},[52,22061,14102],{"class":58},[52,22063,22064,22067],{"class":54,"line":83},[52,22065,22066],{"class":370},"auth",[52,22068,924],{"class":58},[52,22070,22071,22074,22076,22079],{"class":54,"line":115},[52,22072,22073],{"class":370},"    localStorage",[52,22075,373],{"class":58},[52,22077,22078],{"class":6196}," false",[52,22080,384],{"class":58},[52,22082,22083,22086],{"class":54,"line":142},[52,22084,22085],{"class":370},"    strategies",[52,22087,924],{"class":58},[52,22089,22090,22093],{"class":54,"line":169},[52,22091,22092],{"class":370},"      local",[52,22094,924],{"class":58},[52,22096,22097,22100,22102,22104,22107,22109],{"class":54,"line":302},[52,22098,22099],{"class":370},"        tokenType",[52,22101,373],{"class":58},[52,22103,376],{"class":58},[52,22105,22106],{"class":75},"bearer",[52,22108,376],{"class":58},[52,22110,384],{"class":58},[52,22112,22113,22116],{"class":54,"line":308},[52,22114,22115],{"class":370},"        endpoints",[52,22117,924],{"class":58},[52,22119,22120,22123],{"class":54,"line":318},[52,22121,22122],{"class":370},"          login",[52,22124,924],{"class":58},[52,22126,22127,22129,22131,22133,22136,22138],{"class":54,"line":328},[52,22128,10645],{"class":370},[52,22130,373],{"class":58},[52,22132,376],{"class":58},[52,22134,22135],{"class":75},"\u002Fauth\u002Flogin",[52,22137,376],{"class":58},[52,22139,384],{"class":58},[52,22141,22142,22144,22146,22148,22151,22153],{"class":54,"line":337},[52,22143,10665],{"class":370},[52,22145,373],{"class":58},[52,22147,376],{"class":58},[52,22149,22150],{"class":75},"post",[52,22152,376],{"class":58},[52,22154,384],{"class":58},[52,22156,22157,22160,22162,22164,22167],{"class":54,"line":344},[52,22158,22159],{"class":370},"            propertyName",[52,22161,373],{"class":58},[52,22163,376],{"class":58},[52,22165,22166],{"class":75},"access_token",[52,22168,6315],{"class":58},[52,22170,22171],{"class":54,"line":354},[52,22172,22173],{"class":58},"          },\n",[52,22175,22176,22179],{"class":54,"line":367},[52,22177,22178],{"class":370},"          logout",[52,22180,924],{"class":58},[52,22182,22183,22185,22187,22189,22192,22194],{"class":54,"line":387},[52,22184,10645],{"class":370},[52,22186,373],{"class":58},[52,22188,376],{"class":58},[52,22190,22191],{"class":75},"\u002Fauth\u002Flogout",[52,22193,376],{"class":58},[52,22195,384],{"class":58},[52,22197,22198,22200,22202,22204,22206,22208],{"class":54,"line":415},[52,22199,10665],{"class":370},[52,22201,373],{"class":58},[52,22203,376],{"class":58},[52,22205,22150],{"class":75},[52,22207,376],{"class":58},[52,22209,384],{"class":58},[52,22211,22212],{"class":54,"line":427},[52,22213,22173],{"class":58},[52,22215,22216,22219],{"class":54,"line":435},[52,22217,22218],{"class":370},"          user",[52,22220,924],{"class":58},[52,22222,22223,22225,22227,22229,22232,22234],{"class":54,"line":446},[52,22224,10645],{"class":370},[52,22226,373],{"class":58},[52,22228,376],{"class":58},[52,22230,22231],{"class":75},"\u002Fauth\u002Fme",[52,22233,376],{"class":58},[52,22235,384],{"class":58},[52,22237,22238,22240,22242,22244,22247,22249],{"class":54,"line":480},[52,22239,10665],{"class":370},[52,22241,373],{"class":58},[52,22243,376],{"class":58},[52,22245,22246],{"class":75},"get",[52,22248,376],{"class":58},[52,22250,384],{"class":58},[52,22252,22253,22255,22257],{"class":54,"line":509},[52,22254,22159],{"class":370},[52,22256,373],{"class":58},[52,22258,22259],{"class":6196},"false\n",[52,22261,22262],{"class":54,"line":539},[52,22263,22264],{"class":58},"          }\n",[52,22266,22267],{"class":54,"line":547},[52,22268,5398],{"class":58},[52,22270,22271],{"class":54,"line":553},[52,22272,22273],{"class":58},"      },\n",[52,22275,22276,22279,22281],{"class":54,"line":559},[52,22277,22278],{"class":370},"      redirect",[52,22280,373],{"class":58},[52,22282,10138],{"class":58},[52,22284,22285,22288,22290,22292,22295,22297],{"class":54,"line":564},[52,22286,22287],{"class":370},"        login",[52,22289,373],{"class":58},[52,22291,6309],{"class":58},[52,22293,22294],{"class":75},"\u002Flogin",[52,22296,376],{"class":58},[52,22298,384],{"class":58},[52,22300,22301,22304,22306,22308,22310,22312],{"class":54,"line":569},[52,22302,22303],{"class":370},"        logout",[52,22305,373],{"class":58},[52,22307,6309],{"class":58},[52,22309,2602],{"class":75},[52,22311,376],{"class":58},[52,22313,384],{"class":58},[52,22315,22316,22319,22321,22323,22325,22327],{"class":54,"line":1106},[52,22317,22318],{"class":370},"        callback",[52,22320,373],{"class":58},[52,22322,6309],{"class":58},[52,22324,22294],{"class":75},[52,22326,376],{"class":58},[52,22328,384],{"class":58},[52,22330,22331,22334,22336,22338,22341],{"class":54,"line":1135},[52,22332,22333],{"class":370},"        home",[52,22335,373],{"class":58},[52,22337,6309],{"class":58},[52,22339,22340],{"class":75},"\u002Fhome",[52,22342,6315],{"class":58},[52,22344,22345],{"class":54,"line":1164},[52,22346,4249],{"class":58},[52,22348,22349],{"class":54,"line":1193},[52,22350,536],{"class":58},[52,22352,22353],{"class":54,"line":1200},[52,22354,14102],{"class":58},[13,22356,22357],{},"ここでは予めauthモジュールで使用するログイン用のルートを指定したり、使用する通信パターンを定義します。",[13,22359,22360,22361,22364,22365,22368],{},"JWTトークンをローカルストレージに入れておくのは危ないらしいので、",[49,22362,22363],{},"localStorage: false","としておきます。そして",[49,22366,22367],{},"strategies","の中で通信パターンやルートの定義を行います。",[13,22370,22371,22372,22375],{},"今は",[49,22373,22374],{},"local","という通信パターンしかありませんが、outhとかapi2とか他のapiサーバーに対しての通信パターンを複数定義できます。",[13,22377,22378,22381,22382,22385,22386,22389,22390,22392,22393,22396],{},[49,22379,22380],{},"tokenType","で先ほどの",[49,22383,22384],{},"beare","を指定しておきます。こうすると自動的に",[49,22387,22388],{},"authorization","ヘッダーに",[49,22391,22384],{},"という文字を追加してくれます。",[49,22394,22395],{},"endopoints","でそれぞれのログイン（login）、ログアウト（logout）、ユーザー確認（user）、それぞれのルートを指定します。",[13,22398,22399,22400,22403],{},"指定しないとauthモジュールのデフォルトのURLでアクセスしてしまいます。特に",[49,22401,22402],{},"user","はページが読み込みされた際に、トークンをサーバーに送ってログイン状態かどうかをNuxt.jsに伝える機能があります。ここをキチンと設定しないとNuxt側で現在ログインが必要なページが開けないなどの状態に陥ります。",[1566,22405,22406],{"id":22406},"axiosの設定",[13,22408,22409],{},"最後にaxiosの設定をします。",[42,22411,22413],{"className":1250,"code":22412,"filename":9455,"language":1252,"meta":47,"style":47},"const ENV = require('dotenv').config().parsed;\nexport default {\n... \nenv:ENV,\naxios: {\n    baseURL: ENV.API_BASE_URL,\n  },\n...\n}\n",[49,22414,22415,22451,22459,22466,22473,22482,22499,22503,22507],{"__ignoreMap":47},[52,22416,22417,22419,22422,22424,22427,22429,22431,22434,22436,22438,22440,22442,22444,22446,22449],{"class":54,"line":55},[52,22418,10903],{"class":65},[52,22420,22421],{"class":105}," ENV ",[52,22423,69],{"class":58},[52,22425,22426],{"class":418}," require",[52,22428,932],{"class":105},[52,22430,376],{"class":58},[52,22432,22433],{"class":75},"dotenv",[52,22435,376],{"class":58},[52,22437,938],{"class":105},[52,22439,957],{"class":58},[52,22441,16764],{"class":418},[52,22443,422],{"class":105},[52,22445,957],{"class":58},[52,22447,22448],{"class":105},"parsed",[52,22450,1007],{"class":58},[52,22452,22453,22455,22457],{"class":54,"line":83},[52,22454,357],{"class":360},[52,22456,361],{"class":360},[52,22458,10138],{"class":58},[52,22460,22461,22464],{"class":54,"line":115},[52,22462,22463],{"class":58},"...",[52,22465,283],{"class":105},[52,22467,22468,22471],{"class":54,"line":142},[52,22469,22470],{"class":105},"env:ENV",[52,22472,384],{"class":58},[52,22474,22475,22478,22480],{"class":54,"line":169},[52,22476,22477],{"class":62},"axios",[52,22479,373],{"class":58},[52,22481,10138],{"class":58},[52,22483,22484,22487,22489,22492,22494,22497],{"class":54,"line":302},[52,22485,22486],{"class":62},"    baseURL",[52,22488,373],{"class":58},[52,22490,22491],{"class":105}," ENV",[52,22493,957],{"class":58},[52,22495,22496],{"class":105},"API_BASE_URL",[52,22498,384],{"class":58},[52,22500,22501],{"class":54,"line":308},[52,22502,13843],{"class":58},[52,22504,22505],{"class":54,"line":318},[52,22506,14102],{"class":58},[52,22508,22509],{"class":54,"line":328},[52,22510,536],{"class":58},[13,22512,22513,22514,22516,22517,22520,22521,22523],{},"nuxtのdotenvモジュールを用いて",[49,22515,6497],{},"ファイルから",[49,22518,22519],{},"baseURL","つまりAPIのアクセス先ルートを指定します。",[49,22522,6497],{},"は以下のようになっています。",[42,22525,22528],{"className":22526,"code":22527,"filename":6497,"language":452,"meta":47},[1615],"API_BASE_URL=http:\u002F\u002Flocalhost:8000\u002Fapi\u002Fv1\n",[49,22529,22527],{"__ignoreMap":47},[13,22531,22532,22533,22535,22536,22538,22539,22541],{},"今はローカルの開発環境でやっていますが本番は",[49,22534,9139],{},"ではなくドメインになったりします。環境ごとに異なる値は",[49,22537,6497],{},"に記述して環境ごとに",[49,22540,6497],{},"ファイルを作成してそれをインポートします。",[1499,22543,22544],{"id":22544},"ログインフォームを整える",[13,22546,22547],{},"デフォルトのページとかを削除してログインフォームとかを用意します。今回はbootstrapで構築しました。このようにヘッダーがあり、「ログイン」をクリックすると入力モーダルが現れます。",[729,22549],{":src":22550,":width":732},"'_mix\u002Fsch-2020-12-05-15.07.38-768x389.png'",[729,22552],{":src":22553,":width":732},"'_mix\u002Fsch-2020-12-05-15.09.41-768x529.png'",[13,22555,22556,22557,22560],{},"この送信をクリックするとnuxt authの関数、",[49,22558,22559],{},"loginwith()","が実行されます。コンポーネントレベルですが以下のコードになっています。",[42,22562,22565],{"className":202,"code":22563,"filename":22564,"language":204,"meta":47,"style":47},"\u003Ctemplate>\n    \u003Cb-modal\n      id=\"login-modal\"\n      ref=\"modal\"\n      title=\"ログイン情報を入力してください。\"\n      ok-only\n      :okTitle=\"'送信'\"\n      @show=\"resetModal\"\n      @hidden=\"resetModal\"\n      @ok=\"handleOk\"\n    >\n      \u003Cform @submit.stop.prevent=\"handleSubmit\">\n        \u003Cb-alert v-if=\"loginErrMes\" show variant=\"danger\">{{loginErrMes}}\u003C\u002Fb-alert>\n        \u003Cb-form-group\n          :state=\"emailState\"\n          label=\"メールアドレス\"\n          type=\"email\"\n          label-for=\"name-input\"\n          invalid-feedback=\"メールアドレスは入力必須です。\"\n        >\n          \u003Cb-form-input\n            id=\"login-email-input\"\n            v-model=\"email\"\n            :state=\"emailState\"\n            required\n          \u002F>\n        \u003C\u002Fb-form-group>\n\n        \u003Cb-form-group\n          :state=\"passState\"\n          label=\"パスワード\"\n          label-for=\"name-input\"\n          invalid-feedback=\"パスワードは入力必須です。\"\n          autocomplete=\"username\"\n        >\n          \u003Cb-form-input\n            id=\"login-password-input\"\n            v-model=\"pass\"\n            type=\"password\"\n            :state=\"passState\"\n            autocomplete=\"current-password\"\n            required\n          \u002F>\n        \u003C\u002Fb-form-group>\n      \u003C\u002Fform>\n    \u003C\u002Fb-modal>\n\u003C\u002Ftemplate>\n\n\u003Cscript>\nimport { required, minLength, between } from 'vuelidate\u002Flib\u002Fvalidators'\nexport default {\n    name:'loginModal',\n    data(){\n        return{\n            email:'',\n            pass:'',\n            emailState:null,\n            passState:null,\n            loginErrMes:null,\n        }\n    },\n    validations:{\n      email:{\n        required,\n      },\n      pass:{\n        required\n      },\n    },\n    methods:{\n        checkFormHasError(){\n            this.emailState = !this.$v.email.$invalid;\n            this.passState = !this.$v.pass.$invalid;\n            return this.$v.$invalid;\n        },\n        resetModal(){\n            this.email='';\n            this.pass='';\n            this.emailState=null;\n            this.passState=null;\n            this.loginErrMes=null;\n        },\n        handleOk(bvModalEvt){\n            bvModalEvt.preventDefault()\n            this.handleSubmit();\n        },\n        async handleSubmit() {\n            if(this.checkFormHasError()) return;\n\n            try{\n              await this.$auth.loginWith('local', { data:{\n                email:this.email,\n                password:this.pass\n              }})\n              this.resetModal();\n              this.$store.dispatch('message\u002FsetFlashMessage',{\n                content:'ログインしました。',\n                messageType:'success'\n              })\n              this.$bvModal.hide('login-modal')\n            }catch(error){\n              this.loginErrMes='パスワードまたはメールアドレスが異なります。';\n            }\n        }\n    },\n}\n","component\u002FloginModal",[49,22566,22567,22575,22582,22596,22610,22624,22629,22643,22657,22670,22684,22688,22710,22754,22761,22775,22789,22802,22816,22830,22835,22843,22857,22870,22883,22888,22893,22902,22906,22912,22925,22938,22950,22963,22977,22981,22987,23000,23013,23027,23039,23053,23057,23061,23069,23078,23087,23095,23099,23107,23139,23147,23162,23168,23174,23185,23196,23204,23211,23218,23222,23226,23233,23240,23247,23251,23258,23263,23267,23271,23277,23284,23309,23331,23345,23349,23356,23368,23380,23389,23397,23405,23409,23421,23434,23445,23450,23463,23482,23487,23495,23527,23539,23550,23558,23570,23595,23612,23627,23634,23657,23672,23690,23695,23700,23705],{"__ignoreMap":47},[52,22568,22569,22571,22573],{"class":54,"line":55},[52,22570,59],{"class":58},[52,22572,213],{"class":62},[52,22574,80],{"class":58},[52,22576,22577,22579],{"class":54,"line":83},[52,22578,2161],{"class":58},[52,22580,22581],{"class":62},"b-modal\n",[52,22583,22584,22587,22589,22591,22594],{"class":54,"line":115},[52,22585,22586],{"class":65},"      id",[52,22588,69],{"class":58},[52,22590,72],{"class":58},[52,22592,22593],{"class":75},"login-modal",[52,22595,266],{"class":58},[52,22597,22598,22601,22603,22605,22608],{"class":54,"line":142},[52,22599,22600],{"class":65},"      ref",[52,22602,69],{"class":58},[52,22604,72],{"class":58},[52,22606,22607],{"class":75},"modal",[52,22609,266],{"class":58},[52,22611,22612,22615,22617,22619,22622],{"class":54,"line":169},[52,22613,22614],{"class":65},"      title",[52,22616,69],{"class":58},[52,22618,72],{"class":58},[52,22620,22621],{"class":75},"ログイン情報を入力してください。",[52,22623,266],{"class":58},[52,22625,22626],{"class":54,"line":302},[52,22627,22628],{"class":65},"      ok-only\n",[52,22630,22631,22634,22636,22638,22641],{"class":54,"line":308},[52,22632,22633],{"class":65},"      :okTitle",[52,22635,69],{"class":58},[52,22637,72],{"class":58},[52,22639,22640],{"class":75},"'送信'",[52,22642,266],{"class":58},[52,22644,22645,22648,22650,22652,22655],{"class":54,"line":318},[52,22646,22647],{"class":65},"      @show",[52,22649,69],{"class":58},[52,22651,72],{"class":58},[52,22653,22654],{"class":75},"resetModal",[52,22656,266],{"class":58},[52,22658,22659,22662,22664,22666,22668],{"class":54,"line":328},[52,22660,22661],{"class":65},"      @hidden",[52,22663,69],{"class":58},[52,22665,72],{"class":58},[52,22667,22654],{"class":75},[52,22669,266],{"class":58},[52,22671,22672,22675,22677,22679,22682],{"class":54,"line":337},[52,22673,22674],{"class":65},"      @ok",[52,22676,69],{"class":58},[52,22678,72],{"class":58},[52,22680,22681],{"class":75},"handleOk",[52,22683,266],{"class":58},[52,22685,22686],{"class":54,"line":344},[52,22687,11583],{"class":58},[52,22689,22690,22693,22696,22699,22701,22703,22706,22708],{"class":54,"line":354},[52,22691,22692],{"class":58},"      \u003C",[52,22694,22695],{"class":62},"form",[52,22697,22698],{"class":65}," @submit.stop.prevent",[52,22700,69],{"class":58},[52,22702,72],{"class":58},[52,22704,22705],{"class":75},"handleSubmit",[52,22707,72],{"class":58},[52,22709,80],{"class":58},[52,22711,22712,22714,22717,22720,22722,22724,22727,22729,22732,22734,22736,22738,22741,22743,22745,22748,22750,22752],{"class":54,"line":367},[52,22713,2171],{"class":58},[52,22715,22716],{"class":62},"b-alert",[52,22718,22719],{"class":65}," v-if",[52,22721,69],{"class":58},[52,22723,72],{"class":58},[52,22725,22726],{"class":75},"loginErrMes",[52,22728,72],{"class":58},[52,22730,22731],{"class":65}," show",[52,22733,11602],{"class":65},[52,22735,69],{"class":58},[52,22737,72],{"class":58},[52,22739,22740],{"class":75},"danger",[52,22742,72],{"class":58},[52,22744,102],{"class":58},[52,22746,22747],{"class":105},"{{loginErrMes}}",[52,22749,108],{"class":58},[52,22751,22716],{"class":62},[52,22753,80],{"class":58},[52,22755,22756,22758],{"class":54,"line":387},[52,22757,2171],{"class":58},[52,22759,22760],{"class":62},"b-form-group\n",[52,22762,22763,22766,22768,22770,22773],{"class":54,"line":415},[52,22764,22765],{"class":65},"          :state",[52,22767,69],{"class":58},[52,22769,72],{"class":58},[52,22771,22772],{"class":75},"emailState",[52,22774,266],{"class":58},[52,22776,22777,22780,22782,22784,22787],{"class":54,"line":427},[52,22778,22779],{"class":65},"          label",[52,22781,69],{"class":58},[52,22783,72],{"class":58},[52,22785,22786],{"class":75},"メールアドレス",[52,22788,266],{"class":58},[52,22790,22791,22794,22796,22798,22800],{"class":54,"line":435},[52,22792,22793],{"class":65},"          type",[52,22795,69],{"class":58},[52,22797,72],{"class":58},[52,22799,14349],{"class":75},[52,22801,266],{"class":58},[52,22803,22804,22807,22809,22811,22814],{"class":54,"line":446},[52,22805,22806],{"class":65},"          label-for",[52,22808,69],{"class":58},[52,22810,72],{"class":58},[52,22812,22813],{"class":75},"name-input",[52,22815,266],{"class":58},[52,22817,22818,22821,22823,22825,22828],{"class":54,"line":480},[52,22819,22820],{"class":65},"          invalid-feedback",[52,22822,69],{"class":58},[52,22824,72],{"class":58},[52,22826,22827],{"class":75},"メールアドレスは入力必須です。",[52,22829,266],{"class":58},[52,22831,22832],{"class":54,"line":509},[52,22833,22834],{"class":58},"        >\n",[52,22836,22837,22840],{"class":54,"line":539},[52,22838,22839],{"class":58},"          \u003C",[52,22841,22842],{"class":62},"b-form-input\n",[52,22844,22845,22848,22850,22852,22855],{"class":54,"line":547},[52,22846,22847],{"class":65},"            id",[52,22849,69],{"class":58},[52,22851,72],{"class":58},[52,22853,22854],{"class":75},"login-email-input",[52,22856,266],{"class":58},[52,22858,22859,22862,22864,22866,22868],{"class":54,"line":553},[52,22860,22861],{"class":65},"            v-model",[52,22863,69],{"class":58},[52,22865,72],{"class":58},[52,22867,14349],{"class":75},[52,22869,266],{"class":58},[52,22871,22872,22875,22877,22879,22881],{"class":54,"line":559},[52,22873,22874],{"class":65},"            :state",[52,22876,69],{"class":58},[52,22878,72],{"class":58},[52,22880,22772],{"class":75},[52,22882,266],{"class":58},[52,22884,22885],{"class":54,"line":564},[52,22886,22887],{"class":65},"            required\n",[52,22889,22890],{"class":54,"line":569},[52,22891,22892],{"class":58},"          \u002F>\n",[52,22894,22895,22897,22900],{"class":54,"line":1106},[52,22896,2294],{"class":58},[52,22898,22899],{"class":62},"b-form-group",[52,22901,80],{"class":58},[52,22903,22904],{"class":54,"line":1135},[52,22905,341],{"emptyLinePlaceholder":340},[52,22907,22908,22910],{"class":54,"line":1164},[52,22909,2171],{"class":58},[52,22911,22760],{"class":62},[52,22913,22914,22916,22918,22920,22923],{"class":54,"line":1193},[52,22915,22765],{"class":65},[52,22917,69],{"class":58},[52,22919,72],{"class":58},[52,22921,22922],{"class":75},"passState",[52,22924,266],{"class":58},[52,22926,22927,22929,22931,22933,22936],{"class":54,"line":1200},[52,22928,22779],{"class":65},[52,22930,69],{"class":58},[52,22932,72],{"class":58},[52,22934,22935],{"class":75},"パスワード",[52,22937,266],{"class":58},[52,22939,22940,22942,22944,22946,22948],{"class":54,"line":1205},[52,22941,22806],{"class":65},[52,22943,69],{"class":58},[52,22945,72],{"class":58},[52,22947,22813],{"class":75},[52,22949,266],{"class":58},[52,22951,22952,22954,22956,22958,22961],{"class":54,"line":1210},[52,22953,22820],{"class":65},[52,22955,69],{"class":58},[52,22957,72],{"class":58},[52,22959,22960],{"class":75},"パスワードは入力必須です。",[52,22962,266],{"class":58},[52,22964,22965,22968,22970,22972,22975],{"class":54,"line":1215},[52,22966,22967],{"class":65},"          autocomplete",[52,22969,69],{"class":58},[52,22971,72],{"class":58},[52,22973,22974],{"class":75},"username",[52,22976,266],{"class":58},[52,22978,22979],{"class":54,"line":1220},[52,22980,22834],{"class":58},[52,22982,22983,22985],{"class":54,"line":3800},[52,22984,22839],{"class":58},[52,22986,22842],{"class":62},[52,22988,22989,22991,22993,22995,22998],{"class":54,"line":3821},[52,22990,22847],{"class":65},[52,22992,69],{"class":58},[52,22994,72],{"class":58},[52,22996,22997],{"class":75},"login-password-input",[52,22999,266],{"class":58},[52,23001,23002,23004,23006,23008,23011],{"class":54,"line":3835},[52,23003,22861],{"class":65},[52,23005,69],{"class":58},[52,23007,72],{"class":58},[52,23009,23010],{"class":75},"pass",[52,23012,266],{"class":58},[52,23014,23015,23018,23020,23022,23025],{"class":54,"line":3840},[52,23016,23017],{"class":65},"            type",[52,23019,69],{"class":58},[52,23021,72],{"class":58},[52,23023,23024],{"class":75},"password",[52,23026,266],{"class":58},[52,23028,23029,23031,23033,23035,23037],{"class":54,"line":3865},[52,23030,22874],{"class":65},[52,23032,69],{"class":58},[52,23034,72],{"class":58},[52,23036,22922],{"class":75},[52,23038,266],{"class":58},[52,23040,23041,23044,23046,23048,23051],{"class":54,"line":3879},[52,23042,23043],{"class":65},"            autocomplete",[52,23045,69],{"class":58},[52,23047,72],{"class":58},[52,23049,23050],{"class":75},"current-password",[52,23052,266],{"class":58},[52,23054,23055],{"class":54,"line":5506},[52,23056,22887],{"class":65},[52,23058,23059],{"class":54,"line":4},[52,23060,22892],{"class":58},[52,23062,23063,23065,23067],{"class":54,"line":5544},[52,23064,2294],{"class":58},[52,23066,22899],{"class":62},[52,23068,80],{"class":58},[52,23070,23071,23074,23076],{"class":54,"line":5561},[52,23072,23073],{"class":58},"      \u003C\u002F",[52,23075,22695],{"class":62},[52,23077,80],{"class":58},[52,23079,23080,23082,23085],{"class":54,"line":5566},[52,23081,2303],{"class":58},[52,23083,23084],{"class":62},"b-modal",[52,23086,80],{"class":58},[52,23088,23089,23091,23093],{"class":54,"line":5587},[52,23090,108],{"class":58},[52,23092,213],{"class":62},[52,23094,80],{"class":58},[52,23096,23097],{"class":54,"line":5636},[52,23098,341],{"emptyLinePlaceholder":340},[52,23100,23101,23103,23105],{"class":54,"line":5641},[52,23102,59],{"class":58},[52,23104,349],{"class":62},[52,23106,80],{"class":58},[52,23108,23109,23111,23113,23116,23118,23121,23123,23126,23129,23132,23134,23137],{"class":54,"line":5646},[52,23110,9979],{"class":360},[52,23112,17125],{"class":58},[52,23114,23115],{"class":105}," required",[52,23117,408],{"class":58},[52,23119,23120],{"class":105}," minLength",[52,23122,408],{"class":58},[52,23124,23125],{"class":105}," between",[52,23127,23128],{"class":58}," }",[52,23130,23131],{"class":360}," from",[52,23133,6309],{"class":58},[52,23135,23136],{"class":75},"vuelidate\u002Flib\u002Fvalidators",[52,23138,6315],{"class":58},[52,23140,23141,23143,23145],{"class":54,"line":5679},[52,23142,357],{"class":360},[52,23144,361],{"class":360},[52,23146,10138],{"class":58},[52,23148,23149,23151,23153,23155,23158,23160],{"class":54,"line":5692},[52,23150,10143],{"class":62},[52,23152,373],{"class":58},[52,23154,376],{"class":58},[52,23156,23157],{"class":75},"loginModal",[52,23159,376],{"class":58},[52,23161,384],{"class":58},[52,23163,23164,23166],{"class":54,"line":5711},[52,23165,10159],{"class":62},[52,23167,2424],{"class":58},[52,23169,23170,23172],{"class":54,"line":5724},[52,23171,10166],{"class":360},[52,23173,364],{"class":58},[52,23175,23176,23179,23181,23183],{"class":54,"line":5745},[52,23177,23178],{"class":62},"            email",[52,23180,373],{"class":58},[52,23182,11746],{"class":58},[52,23184,384],{"class":58},[52,23186,23187,23190,23192,23194],{"class":54,"line":5750},[52,23188,23189],{"class":62},"            pass",[52,23191,373],{"class":58},[52,23193,11746],{"class":58},[52,23195,384],{"class":58},[52,23197,23198,23201],{"class":54,"line":5786},[52,23199,23200],{"class":62},"            emailState",[52,23202,23203],{"class":58},":null,\n",[52,23205,23206,23209],{"class":54,"line":5836},[52,23207,23208],{"class":62},"            passState",[52,23210,23203],{"class":58},[52,23212,23213,23216],{"class":54,"line":5862},[52,23214,23215],{"class":62},"            loginErrMes",[52,23217,23203],{"class":58},[52,23219,23220],{"class":54,"line":5867},[52,23221,5398],{"class":58},[52,23223,23224],{"class":54,"line":5872},[52,23225,10189],{"class":58},[52,23227,23228,23231],{"class":54,"line":5893},[52,23229,23230],{"class":62},"    validations",[52,23232,924],{"class":58},[52,23234,23235,23238],{"class":54,"line":5914},[52,23236,23237],{"class":62},"      email",[52,23239,924],{"class":58},[52,23241,23242,23245],{"class":54,"line":5927},[52,23243,23244],{"class":105},"        required",[52,23246,384],{"class":58},[52,23248,23249],{"class":54,"line":5932},[52,23250,22273],{"class":58},[52,23252,23253,23256],{"class":54,"line":5953},[52,23254,23255],{"class":62},"      pass",[52,23257,924],{"class":58},[52,23259,23260],{"class":54,"line":5966},[52,23261,23262],{"class":105},"        required\n",[52,23264,23265],{"class":54,"line":5971},[52,23266,22273],{"class":58},[52,23268,23269],{"class":54,"line":5976},[52,23270,10189],{"class":58},[52,23272,23273,23275],{"class":54,"line":5981},[52,23274,10194],{"class":62},[52,23276,924],{"class":58},[52,23278,23279,23282],{"class":54,"line":21716},[52,23280,23281],{"class":62},"        checkFormHasError",[52,23283,2424],{"class":58},[52,23285,23286,23288,23290,23292,23295,23298,23300,23302,23304,23307],{"class":54,"line":21722},[52,23287,11988],{"class":58},[52,23289,22772],{"class":105},[52,23291,951],{"class":58},[52,23293,23294],{"class":58}," !this.",[52,23296,23297],{"class":105},"$v",[52,23299,957],{"class":58},[52,23301,14349],{"class":105},[52,23303,957],{"class":58},[52,23305,23306],{"class":105},"$invalid",[52,23308,1007],{"class":58},[52,23310,23311,23313,23315,23317,23319,23321,23323,23325,23327,23329],{"class":54,"line":21727},[52,23312,11988],{"class":58},[52,23314,22922],{"class":105},[52,23316,951],{"class":58},[52,23318,23294],{"class":58},[52,23320,23297],{"class":105},[52,23322,957],{"class":58},[52,23324,23010],{"class":105},[52,23326,957],{"class":58},[52,23328,23306],{"class":105},[52,23330,1007],{"class":58},[52,23332,23333,23335,23337,23339,23341,23343],{"class":54,"line":21732},[52,23334,10271],{"class":360},[52,23336,10407],{"class":58},[52,23338,23297],{"class":105},[52,23340,957],{"class":58},[52,23342,23306],{"class":105},[52,23344,1007],{"class":58},[52,23346,23347],{"class":54,"line":21737},[52,23348,10229],{"class":58},[52,23350,23351,23354],{"class":54,"line":21743},[52,23352,23353],{"class":62},"        resetModal",[52,23355,2424],{"class":58},[52,23357,23358,23360,23362,23364,23366],{"class":54,"line":21748},[52,23359,11988],{"class":58},[52,23361,14349],{"class":105},[52,23363,69],{"class":58},[52,23365,11746],{"class":58},[52,23367,1007],{"class":58},[52,23369,23370,23372,23374,23376,23378],{"class":54,"line":21754},[52,23371,11988],{"class":58},[52,23373,23010],{"class":105},[52,23375,69],{"class":58},[52,23377,11746],{"class":58},[52,23379,1007],{"class":58},[52,23381,23382,23384,23386],{"class":54,"line":21760},[52,23383,11988],{"class":58},[52,23385,22772],{"class":105},[52,23387,23388],{"class":58},"=null;\n",[52,23390,23391,23393,23395],{"class":54,"line":21766},[52,23392,11988],{"class":58},[52,23394,22922],{"class":105},[52,23396,23388],{"class":58},[52,23398,23399,23401,23403],{"class":54,"line":21772},[52,23400,11988],{"class":58},[52,23402,22726],{"class":105},[52,23404,23388],{"class":58},[52,23406,23407],{"class":54,"line":21778},[52,23408,10229],{"class":58},[52,23410,23411,23414,23416,23419],{"class":54,"line":21783},[52,23412,23413],{"class":62},"        handleOk",[52,23415,932],{"class":58},[52,23417,23418],{"class":986},"bvModalEvt",[52,23420,3439],{"class":58},[52,23422,23424,23427,23429,23432],{"class":54,"line":23423},84,[52,23425,23426],{"class":105},"            bvModalEvt",[52,23428,957],{"class":58},[52,23430,23431],{"class":418},"preventDefault",[52,23433,12380],{"class":62},[52,23435,23437,23439,23441,23443],{"class":54,"line":23436},85,[52,23438,11988],{"class":58},[52,23440,22705],{"class":418},[52,23442,422],{"class":62},[52,23444,1007],{"class":58},[52,23446,23448],{"class":54,"line":23447},86,[52,23449,10229],{"class":58},[52,23451,23453,23456,23459,23461],{"class":54,"line":23452},87,[52,23454,23455],{"class":65},"        async",[52,23457,23458],{"class":62}," handleSubmit",[52,23460,422],{"class":58},[52,23462,10138],{"class":58},[52,23464,23466,23468,23470,23472,23475,23478,23480],{"class":54,"line":23465},88,[52,23467,18526],{"class":360},[52,23469,932],{"class":62},[52,23471,1371],{"class":58},[52,23473,23474],{"class":418},"checkFormHasError",[52,23476,23477],{"class":62},"()) ",[52,23479,10515],{"class":360},[52,23481,1007],{"class":58},[52,23483,23485],{"class":54,"line":23484},89,[52,23486,341],{"emptyLinePlaceholder":340},[52,23488,23490,23493],{"class":54,"line":23489},90,[52,23491,23492],{"class":360},"            try",[52,23494,364],{"class":58},[52,23496,23498,23501,23503,23506,23508,23511,23513,23515,23517,23519,23521,23523,23525],{"class":54,"line":23497},91,[52,23499,23500],{"class":360},"              await",[52,23502,10407],{"class":58},[52,23504,23505],{"class":105},"$auth",[52,23507,957],{"class":58},[52,23509,23510],{"class":418},"loginWith",[52,23512,932],{"class":62},[52,23514,376],{"class":58},[52,23516,22374],{"class":75},[52,23518,376],{"class":58},[52,23520,408],{"class":58},[52,23522,17125],{"class":58},[52,23524,419],{"class":62},[52,23526,924],{"class":58},[52,23528,23530,23533,23535,23537],{"class":54,"line":23529},92,[52,23531,23532],{"class":62},"                email",[52,23534,11256],{"class":58},[52,23536,14349],{"class":105},[52,23538,384],{"class":58},[52,23540,23542,23545,23547],{"class":54,"line":23541},93,[52,23543,23544],{"class":62},"                password",[52,23546,11256],{"class":58},[52,23548,23549],{"class":105},"pass\n",[52,23551,23553,23556],{"class":54,"line":23552},94,[52,23554,23555],{"class":58},"              }}",[52,23557,1015],{"class":62},[52,23559,23561,23564,23566,23568],{"class":54,"line":23560},95,[52,23562,23563],{"class":58},"              this.",[52,23565,22654],{"class":418},[52,23567,422],{"class":62},[52,23569,1007],{"class":58},[52,23571,23573,23575,23578,23580,23583,23585,23587,23590,23592],{"class":54,"line":23572},96,[52,23574,23563],{"class":58},[52,23576,23577],{"class":105},"$store",[52,23579,957],{"class":58},[52,23581,23582],{"class":418},"dispatch",[52,23584,932],{"class":62},[52,23586,376],{"class":58},[52,23588,23589],{"class":75},"message\u002FsetFlashMessage",[52,23591,376],{"class":58},[52,23593,23594],{"class":58},",{\n",[52,23596,23598,23601,23603,23605,23608,23610],{"class":54,"line":23597},97,[52,23599,23600],{"class":62},"                content",[52,23602,373],{"class":58},[52,23604,376],{"class":58},[52,23606,23607],{"class":75},"ログインしました。",[52,23609,376],{"class":58},[52,23611,384],{"class":58},[52,23613,23615,23618,23620,23622,23625],{"class":54,"line":23614},98,[52,23616,23617],{"class":62},"                messageType",[52,23619,373],{"class":58},[52,23621,376],{"class":58},[52,23623,23624],{"class":75},"success",[52,23626,6315],{"class":58},[52,23628,23630,23632],{"class":54,"line":23629},99,[52,23631,1012],{"class":58},[52,23633,1015],{"class":62},[52,23635,23637,23639,23642,23644,23647,23649,23651,23653,23655],{"class":54,"line":23636},100,[52,23638,23563],{"class":58},[52,23640,23641],{"class":105},"$bvModal",[52,23643,957],{"class":58},[52,23645,23646],{"class":418},"hide",[52,23648,932],{"class":62},[52,23650,376],{"class":58},[52,23652,22593],{"class":75},[52,23654,376],{"class":58},[52,23656,1015],{"class":62},[52,23658,23660,23662,23664,23666,23668,23670],{"class":54,"line":23659},101,[52,23661,20214],{"class":58},[52,23663,19469],{"class":360},[52,23665,932],{"class":62},[52,23667,10218],{"class":105},[52,23669,938],{"class":62},[52,23671,364],{"class":58},[52,23673,23675,23677,23679,23681,23683,23686,23688],{"class":54,"line":23674},102,[52,23676,23563],{"class":58},[52,23678,22726],{"class":105},[52,23680,69],{"class":58},[52,23682,376],{"class":58},[52,23684,23685],{"class":75},"パスワードまたはメールアドレスが異なります。",[52,23687,376],{"class":58},[52,23689,1007],{"class":58},[52,23691,23693],{"class":54,"line":23692},103,[52,23694,2251],{"class":58},[52,23696,23698],{"class":54,"line":23697},104,[52,23699,5398],{"class":58},[52,23701,23703],{"class":54,"line":23702},105,[52,23704,10189],{"class":58},[52,23706,23708],{"class":54,"line":23707},106,[52,23709,536],{"class":58},[13,23711,23712,23713,23716,23717,23720],{},"このアプリでは後でいろいろフォームとかある予定なので",[49,23714,23715],{},"vuelidate","というバリデーションライブラリを入れています。このログインフォーム程度であれば必要ありませんけど。ログイン処理をしているのは下の方にある",[49,23718,23719],{},"async handleSubmit()","です。入力値が正規値であれば実行されます。",[13,23722,23723,23724,23726],{},"nuxt authは",[49,23725,9455],{},"で定義した設定を元にログインのルートにデータをPOSTします。超便利です。",[729,23728],{":src":23729,":width":732},"'_mix\u002Fsch-2020-12-05-15.14.45-768x518.png'",[13,23731,23732],{},"先ほどテストで入れたようにシーダーのアドレスとパスワードを入れて送信します。",[729,23734],{":src":23735,":width":732},"'_mix\u002Fsch-2020-12-05-15.17.31-768x81.png'",[13,23737,23738],{},"ネットワークを見てみるとキチンと8000ポートで待機しているlaravelへ送信されています。レスポンスが200で成功しています。クッキーを見てみるとトークンが保存されているのが分かります。",[729,23740],{":src":23741,":width":732},"'_mix\u002Fsch-2020-12-05-15.16.35-768x71.png'",[13,23743,23744],{},"そしてユーザー情報があるか＝ログインしているかで「ログイン」を「ログアウト」を制御しています。",[729,23746],{":src":23747,":width":732},"'_mix\u002Fsch-2020-12-05-15.22.55-768x168.png'",[13,23749,23750],{},"authモジュールを入れている場合は以下のコードでログインしているか、ユーザーの情報を取得することができます。",[42,23752,23754],{"className":1250,"code":23753,"language":1252,"meta":47,"style":47},"this.$auth.user\nthis.$auth.loggedIn\n",[49,23755,23756,23767],{"__ignoreMap":47},[52,23757,23758,23760,23762,23764],{"class":54,"line":55},[52,23759,1371],{"class":58},[52,23761,23505],{"class":105},[52,23763,957],{"class":58},[52,23765,23766],{"class":105},"user\n",[52,23768,23769,23771,23773,23775],{"class":54,"line":83},[52,23770,1371],{"class":58},[52,23772,23505],{"class":105},[52,23774,957],{"class":58},[52,23776,23777],{"class":105},"loggedIn\n",[17,23779,23781],{"id":23780},"nuxtのspaをビルドする","NuxtのSPAをビルドする",[13,23783,23784],{},"Nuxtは3000ポートの開発サーバで見ていたので、今度はキチンとビルドしてユーザー視点でアクセスしてみましょう。",[42,23786,23789],{"className":23787,"code":23788,"language":452},[1615],"npm run build\n",[49,23790,23788],{"__ignoreMap":47},[13,23792,23793,23794,23796,23797,23799],{},"distファイルが出されたのを確認し",[49,23795,9132],{},"へアクセスします。私の環境ではホストの",[49,23798,9132],{},"はコンテナのlocalhost:80につながります。最初の設定の通り、ドキュメントルート はdist配下に通じているのでindex.htmlが返されます。ログインが成功し、ネットワークでもAPIが送信されているのが分かります。",[729,23801],{":src":23802,":width":732},"'_mix\u002Fsch-2020-12-05-15.34.55-768x213.png'",[13,23804,23805],{},"後はどんどんAPIルートを作成して、nuxtからはaxiosを用いてトークン付きリクエストを送れば認証ルートにアクセスすることができます。これでJWT認証つきのSPAアプリの設定が完了しました。",[17,23807,23809],{"id":23808},"以上","以上！",[13,23811,23812],{},"以上がLravel6とNuxt.jsで構築するJWT認証つきSPAの構築です。サーバーの構成は人によって様々ですがバーチャルホスト で公開側ページとAPIを分けてしまうのが簡単な気がします。とにかくトークン認証を用いたSPAを構築する際には",[1467,23814,23815,23818,23821,23824,23827,23830,23833,23836],{},[1470,23816,23817],{},"Nuxt側とAPI側でサーバーを分ける",[1470,23819,23820],{},"laravelにJWT認証ライブラリを入れる",[1470,23822,23823],{},"ログイン用ルートを整える",[1470,23825,23826],{},"CORSの設定を行う",[1470,23828,23829],{},"NuxtからのログインルートにPOSTリクエストを送る",[1470,23831,23832],{},"トークンをブラウザのクッキーなどに保存",[1470,23834,23835],{},"認証ルートにはリクエストヘッダにBeare＋トークンでリクエストをする",[1470,23837,23838],{},"おのつど、またはページがリロードされたら \u002Fapi\u002Fauth\u002Fmeでトークンが有効かを確かめてNuxt側に認証状態を知らせる。",[13,23840,23841],{},"以上のまとめを意識すればおおよそのNuxt+API認証はわかってくると思います。ReactとかNext.jsなどもこの部分のエッセンスは同じなのでぜひ応用してください。",[17,23843,23844],{"id":23844},"参考記事",[1467,23846,23847,23854,23861],{},[1470,23848,23849],{},[2039,23850,23853],{"href":23851,"rel":23852},"https:\u002F\u002Fpgmemo.tokyo\u002Fdata\u002Farchives\u002F1703.html",[2043],"Laravel6 に jwt-auth をインストールしSPAからログインする（バックエンド Laravel編）",[1470,23855,23856],{},[2039,23857,23860],{"href":23858,"rel":23859},"https:\u002F\u002Fgithub.com\u002Ftymondesigns\u002Fjwt-auth\u002Fissues\u002F2059",[2043],"Tymon\\JWTAuth\\Exceptions\\JWTException: Could not create token: Implicit conversion of keys from strings is deprecated. Please use InMemory or LocalFileReference classes. ",[1470,23862,23863],{},[2039,23864,23866],{"href":22013,"rel":23865},[2043],"Nuxt auth introduction",[1414,23868,23869],{},"html pre.shiki code .s-wAU, html code.shiki .s-wAU{--shiki-default:#F07178}html pre.shiki code .sAklC, html code.shiki .sAklC{--shiki-default:#89DDFF}html pre.shiki code .sfyAc, html code.shiki .sfyAc{--shiki-default:#C3E88D}html pre.shiki code .s0W1g, html code.shiki .s0W1g{--shiki-default:#BABED8}html pre.shiki code .sbqyR, html code.shiki .sbqyR{--shiki-default:#FF9CAC}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html pre.shiki code .s5Dmg, html code.shiki .s5Dmg{--shiki-default:#FFCB6B}html pre.shiki code .sJ14y, html code.shiki .sJ14y{--shiki-default:#C792EA}html pre.shiki code .sdLwU, html code.shiki .sdLwU{--shiki-default:#82AAFF}html pre.shiki code .s6cf3, html code.shiki .s6cf3{--shiki-default:#89DDFF;--shiki-default-font-style:italic}html pre.shiki code .s7ZW3, html code.shiki .s7ZW3{--shiki-default:#BABED8;--shiki-default-font-style:italic}",{"title":47,"searchDepth":115,"depth":115,"links":23871},[23872,23877,23881,23890,23891,23904,23905,23906],{"id":20343,"depth":83,"text":20344,"children":23873},[23874,23875,23876],{"id":20374,"depth":115,"text":20374},{"id":20661,"depth":115,"text":20662},{"id":20691,"depth":115,"text":20692},{"id":20698,"depth":83,"text":20699,"children":23878},[23879,23880],{"id":20702,"depth":115,"text":20702},{"id":20723,"depth":115,"text":20724},{"id":20749,"depth":83,"text":20750,"children":23882},[23883,23884,23885,23886,23887,23888,23889],{"id":20764,"depth":115,"text":20765},{"id":20793,"depth":115,"text":20793},{"id":20901,"depth":115,"text":20901},{"id":20928,"depth":115,"text":20929},{"id":21162,"depth":115,"text":21163},{"id":21318,"depth":115,"text":21319},{"id":21383,"depth":115,"text":21383},{"id":21791,"depth":83,"text":21792},{"id":21830,"depth":83,"text":21831,"children":23892},[23893,23898,23903],{"id":21834,"depth":115,"text":21834,"children":23894},[23895,23896,23897],{"id":21840,"depth":142,"text":21841},{"id":21850,"depth":142,"text":21851},{"id":21880,"depth":142,"text":21881},{"id":22006,"depth":115,"text":22007,"children":23899},[23900,23901,23902],{"id":22019,"depth":142,"text":22019},{"id":22031,"depth":142,"text":22032},{"id":22406,"depth":142,"text":22406},{"id":22544,"depth":115,"text":22544},{"id":23780,"depth":83,"text":23781},{"id":23808,"depth":83,"text":23809},{"id":23844,"depth":83,"text":23844},[1424],"2025-09-05",{},"\u002Farticles\u002Flaravel-nuxt-jwt-spa",{"title":20319,"description":20319},"articles\u002Flaravel-nuxt-jwt-spa",[9274,23914],"nuxt","_mix\u002Flaravel-nuxt.jpeg","PoGyyswEs82YuxYkbUxf6PSHnouWa2M9g-hxfKYHTNE",{"id":23918,"title":23919,"body":23920,"category":26569,"createdAt":26570,"description":23919,"extension":1427,"index":1428,"meta":26571,"navigation":340,"path":26572,"publish":340,"seo":26573,"series":1428,"seriesTitle":1428,"stem":26574,"tag":26575,"thumbnail":26577,"updatedAt":26570,"__hash__":26578},"articles\u002Farticles\u002Fzoom-api-laravel.md","Zoom APIとLaravelを使って自動ミーティング作成フォームを構築する。",{"type":10,"value":23921,"toc":26523},[23922,23931,23939,23942,23946,23949,23952,23958,23961,23964,23968,23971,23974,23977,23983,23987,23990,23993,23996,23999,24005,24008,24011,24014,24017,24031,24034,24038,24041,24044,24047,24050,24053,24056,24059,24062,24065,24068,24071,24074,24080,24089,24121,24125,24129,24138,24141,24150,24153,24156,24159,24162,24165,24168,24177,24180,24194,24203,24219,24230,24233,24236,24239,24243,24246,24249,24252,24255,24258,24261,24265,24268,24272,24289,24295,24300,24306,24322,24326,24329,24421,24424,24438,24445,24448,24454,24457,24465,24471,24475,24478,24488,24557,24636,24649,24652,24664,24694,24701,24714,24718,24748,24751,24757,24760,24763,24767,24770,24773,24782,24945,24957,24960,25017,25028,25034,25044,25047,25081,25100,25103,25109,25116,25228,25231,25234,25237,25240,25243,25317,25331,25344,25350,25353,25356,25359,25362,25484,25487,25490,25494,25497,25500,25504,25715,25721,25727,25737,25740,25742,25746,25749,26094,26097,26100,26103,26163,26180,26184,26241,26250,26253,26256,26267,26270,26274,26431,26434,26437,26440,26443,26446,26449,26452,26455,26458,26461,26464,26467,26470,26473,26476,26479,26482,26497,26500,26502,26505,26513,26517,26520],[13,23923,23924,23925,23930],{},"こんにちはJuneです。年明け早々コロナが本気出してきて、ますます外に出れない日々が続きます。まあエンジニアは家にいてもプログラムでいろんなもの作れるのでいい暇つぶしになります。そこでコロナで株が爆上がりの",[2039,23926,23929],{"href":23927,"rel":23928},"https:\u002F\u002Fmarketplace.zoom.us\u002Fdocs\u002Fapi-reference\u002Fintroduction",[2043],"zoomにAPI","があるということを知って、早速使ってみました。会社でも「zoom APIには金の匂いがする！」とみんなで盛り上がったので是非探索してみます。",[13,23932,23933,23934,23938],{},"zoom API自体は2018年ごろから出ていたらしく、現在はv2がリリースされています。",[2039,23935,23937],{"href":23927,"rel":23936},[2043],"ドキュメント","を一通り読んだ所、zoomで行えることは一通りAPIを通じて行えるそうです。zoom APIについてわかった箇所を詳細に説明したいですが、この記事では実際の活用例を説明したいので部分省略します。",[13,23940,23941],{},"しかし、zoom APIを使用する認証フローや仕組みについては簡単に解説します。その説明から始めますので、「webアプリのはよ動き見せろや！」「そんなの知っとんじゃ！」という方は「Laravelを立ち上げる」から読み始めてください。",[17,23943,23945],{"id":23944},"zoom-apiの概要","Zoom APIの概要",[13,23947,23948],{},"詳しくはZoom API レファランスを見ればわかりますが、zoomのGUIでできることは基本的に可能です。ミーティングを作ったり、ウェビナーを開催したり、ユーザー情報を取ったり、開催中のzoomに対してチャットを送るなどなんでもできます。他にもwebhookや独自のコードでブラウザ上に映像・音声を出力できるSDKやエンドポイントもあるみたいです。",[13,23950,23951],{},"これらのAPIはRestAPIであり、特定のルートに対してGET\u002FPOST\u002FPUT\u002FDELTEでリクエストし、付属の情報はGETパラメータやPOSTパラメータで送信します。",[13,23953,23954,23955],{},"参考：",[2039,23956,23927],{"href":23927,"rel":23957},[2043],[1499,23959,23960],{"id":23960},"認証方法",[13,23962,23963],{},"zoom APIにアクセスするには、JWTとOAuth2.0が使用できます。二者の違いはアクセスできる機能の量とマーケットプレイスに公開できるかが主になります。",[1566,23965,23967],{"id":23966},"jwt","JWT",[13,23969,23970],{},"ます。JSONで認証情報をやりとりします。",[13,23972,23973],{},"JWTによる認証はOAuthで使用できる権限範囲より狭く、自分自身で簡単にライトに使用したい場合に使うらしいです。また開発したzoom アプリはマーケットプレイスを通じて公開することができますが、JWT認証の場合はその公開ができません。",[13,23975,23976],{},"独自のwebアプリとZoomを連携させたい場合は次のOAuth2.0認証をお勧めします。",[13,23978,23954,23979],{},[2039,23980,23981],{"href":23981,"rel":23982},"https:\u002F\u002Fmarketplace.zoom.us\u002Fdocs\u002Fapi-reference\u002Fusing-zoom-apis#using-jwt",[2043],[1566,23984,23986],{"id":23985},"oauth-20","OAuth 2.0",[13,23988,23989],{},"OAuthは自身のwebサービスの資格情報を第三者のサービスへ提供する際に使用される認証フローです。ここでいう「webサービス」は「zoom」で「第三者のサービス」は「私のLaravelアプリ」です。",[13,23991,23992],{},"つまりOAuth認証を用いることで「私のLaravelアプリ」は連携させた人の「zoom」の資格情報を利用できる様になります。よって「私のLaravelアプリ」は連携させた人のzoomのミーティング情報を読み取ったり、作成したり、ユーザー情報を取得することができます。",[13,23994,23995],{},"資格情報を与え、操作権限も付与するのでかなり厳重な認証システムが必要となりそこで、秘密鍵や特定のプロトコルなどを用いたOAuthが使用されます。上記のJWTよりセキュアであり、また様々なAPIを利用できる様になります。",[13,23997,23998],{},"OAuthで実装したzoom APIはマーケットプレイスに出して公開することができます。今回の説明ではこのOAuth認証をLaravelを用いて実装していきます。まだ認証・連携のイメージがつかないと思いますが、読んでいくうちにわかると思います（多分）。",[13,24000,23954,24001],{},[2039,24002,24003],{"href":24003,"rel":24004},"https:\u002F\u002Fmarketplace.zoom.us\u002Fdocs\u002Fapi-reference\u002Fusing-zoom-apis#using-oauth",[2043],[1499,24006,24007],{"id":24007},"連携の流れ",[13,24009,24010],{},"OAuthでの認証は以下の様に行われます。",[729,24012],{":src":24013,":width":732},"'_mix\u002F1570826762485.png'",[13,24015,24016],{},"もう少し具体的に解説すると",[1687,24018,24019,24022,24025,24028],{},[1470,24020,24021],{},"ユーザー（zoomにログイン済み）を認証画面へリダイレクトさせる",[1470,24023,24024],{},"確認後、ユーザーが承認したという証であるcodeを取得。",[1470,24026,24027],{},"OAuthプロトコルに従い作成した認証キーとリクエストをzoomに送る",[1470,24029,24030],{},"アクセストークンを得る。そのアクセストークン でzoom APIにアクセスする。アクセストークン などはアプリのDBに保存しておく。",[13,24032,24033],{},"こちらも後で実際の画面のスクショ付きで解説しますので、今は「ヘェ〜」程度の理解で大丈夫です。",[1499,24035,24037],{"id":24036},"apiへのアクセス方法","APIへのアクセス方法",[13,24039,24040],{},"OAuthでアクセストークン を取得したら、そのトークンをリクエストヘッダーに仕込んでAPIにアクセスします。",[17,24042,24043],{"id":24043},"今回作るアプリ",[13,24045,24046],{},"まずアプリの機能と概要を説明しておきます。今回作るアプリは「匿名ユーザーが入力したフォームの内容に応じてzoomミーティングが作成され、それを管理できるwebアプリ」です。見た目は以下の感じです。",[729,24048],{":src":24049,":width":732},"'_mix\u002Fsch-2021-01-10-20.51.27-768x445.png'",[13,24051,24052],{},"アプリを管理し連携させるzoomアカウントを持っている「管理者」と、フォームに入力する「匿名ユーザー（お客さん）」が存在するとします。",[1499,24054,24055],{"id":24055},"機能の概要",[13,24057,24058],{},"場面としては「zoomでのご相談はこちら」的な会社用のフォームであると思ってください。最初にお客さんはフォームにて名前・アドレス、zoomの希望開始時間を入力します。",[13,24060,24061],{},"フォーム入力内容が正しければ、zoom APIを通じて指定の時間で始まるzoom ミーティングを連携先のアカウントで作成。",[13,24063,24064],{},"APIからのレスポンスよりミーティングURLを取得し、DBに保存すると共にお客さんへミーティング情報をメールで飛ばす。（メールはローカルの環境でできなかったので、実装は割愛。ソースはあります。）",[13,24066,24067],{},"管理者は管理画面にて誰が・いつミーティングを開くのかを一覧で確認できる。またお客さんは時間変更用・削除用URLにアクセスして時間の変更・ミーティングのキャンセルが可能。",[13,24069,24070],{},"以上の様な機能を持たせたいと思います。とりあえずこれらの機能を実装する程度なので、厳密なバリデーションや細かい機能は割愛します。",[17,24072,24073],{"id":24073},"開発環境",[13,24075,24076,24077,24079],{},"私が慣れているLaravel 6を用いて作成します。Laravelのインストール方法は省略します。一応",[2039,24078,2044],{"href":9569},"で作ったDocker開発環境を用いています。またzoomへのHTTPアクセスをよく行うので、PHPのHTTPライブラリであるguzzlehttpをインストールしてください。",[13,24081,24082,24083,24088],{},"また、以下詳細な開発環境情報です。",[2039,24084,24087],{"href":24085,"rel":24086},"https:\u002F\u002Fgithub.com\u002FjunjiIshii\u002Flaravel_zoom",[2043],"開発したリポジトリも開放してます","のでぜひどうぞ。",[1467,24090,24091,24094,24097,24100,24103,24106,24109,24112,24115,24118],{},[1470,24092,24093],{},"MacOS Catalina 10.15.5",[1470,24095,24096],{},"Docker 20.10.0",[1470,24098,24099],{},"Docker-compose 1.27.4",[1470,24101,24102],{},"（コンテナ内）composer 1.10.19",[1470,24104,24105],{},"（コンテナ内）Laravel 6.20",[1470,24107,24108],{},"（コンテナ内）guzzlehttp 7.2",[1470,24110,24111],{},"（コンテナ内）centod 8",[1470,24113,24114],{},"（コンテナ内）httpd 2.4.37",[1470,24116,24117],{},"（コンテナ内）php 7.4.7",[1470,24119,24120],{},"（コンテナ内・公式イメージ）mysql:5.7",[17,24122,24124],{"id":24123},"zoom-apiキーを手に入れる","Zoom APIキーを手に入れる",[1499,24126,24128],{"id":24127},"zoom-アプリの作成","zoom アプリの作成",[13,24130,24131,24132,24137],{},"それでは初めていきましょう。Laravelを実装する前に自身のZoomアカウントにて、zoomアプリを作成していきましょう。",[2039,24133,24136],{"href":24134,"rel":24135},"https:\u002F\u002Fmarketplace.zoom.us\u002F",[2043],"zoom マーケットプレイス","へ移動します。そして画面上部の「Develop」をクリックし「Build App」をクリックします。",[729,24139],{":src":24140,":width":732},"'_mix\u002Fzoom_marget-768x330.jpeg'",[13,24142,24143,24144,24149],{},"すると",[2039,24145,24148],{"href":24146,"rel":24147},"https:\u002F\u002Fmarketplace.zoom.us\u002Fdevelop\u002Fcreate",[2043],"zoomアプリを選択する以下の様な画面","が表示されますので、「OAuth」の「Create」をクリック",[729,24151],{":src":24152,":width":732},"'_mix\u002Fzoom_app_create-768x385.jpeg'",[13,24154,24155],{},"「Create」を押すとアプリの名前などを入力するモーダルが出現します。任意の名前を入力し、アプリのタイプ、マーケットプレイスに公表するかを決定します。とりあえず私は以下の様にしました。",[729,24157],{":src":24158,":width":2993,":center":1323},"'_mix\u002Fzoom_mordal-768x637.jpeg'",[13,24160,24161],{},"名前とマーケットプレイスへの公開は後から変更できます。ひとまず入力したら「Create」を押します。",[1499,24163,24164],{"id":24164},"諸所の設定を入力",[1566,24166,24167],{"id":24167},"アプリの認証情報",[13,24169,24170,24171,24176],{},"作成後には",[2039,24172,24175],{"href":24173,"rel":24174},"https:\u002F\u002Fmarketplace.zoom.us\u002Fuser\u002Fbuild",[2043],"アプリの管理画面","に飛ばされると思います。そこから作成したアプリを選択して「App Credentials」を選択",[729,24178],{":src":24179,":width":732},"'_mix\u002Fzoom_app_info-768x637.jpeg'",[13,24181,24182,24183,24186,24187,24189,24190,24193],{},"Larvel側には ",[1463,24184,24185],{},"「Client ID」「Client Secret」"," が必要となります。後で",[49,24188,6497],{},"ファイルに記載します。ちなみに ",[1463,24191,24192],{},"「Client Secret」"," は絶対に外に漏れてはいけません。",[13,24195,24196,24199,24200,24202],{},[1463,24197,24198],{},"「Redirect URL for OAuth」"," にて承認画面からのリダイレクト先を指定しておきます。承認画面でアプリ連携を許可した際にはトークンなどが ",[1463,24201,24198],{}," あてへ送信され、ユーザーもリダイレクトされます。ここの値が異なっているとエラーで認証が進みません。",[13,24204,24205,24206,24208,24209,24211,24212,24214,24215,24218],{},"今は開発環境なのでドメインに",[49,24207,9139],{},"を指定しています。（私の環境では",[49,24210,9132],{},"でLaravelの画面が表示される様に設定しています。",[49,24213,20635],{},"などの場合は",[49,24216,24217],{},"localhost:8000","になると思いますので、ポートの指定に気をつけてください。）",[13,24220,24221,24222,24225,24226,24229],{},"「whitelist URL」はOAuthのリダイレクト先として許可するURLを指定できます。OAuthリダイレクトのURLに完全一致させるか、前方一致させる必要があります。設定したリダイレクト先は",[49,24223,24224],{},"http:\u002F\u002Flocalhost:9000\u002Fzoomauth\u002Fcheck","としていたので、その前方を含める様に",[49,24227,24228],{},"http:\u002F\u002Flocalhost:9000\u002F","に設定しておきます。",[1566,24231,24232],{"id":24232},"アプリの公開情報",[729,24234],{":src":24235,":width":732},"'_mix\u002Fzoom_information-572x1024.jpeg'",[13,24237,24238],{},"「Information」にて「Optional」と書かれている箇所以外を入力し記述します。",[1566,24240,24242],{"id":24241},"アプリのスコープアクセス範囲","アプリのスコープ（アクセス範囲）",[13,24244,24245],{},"ここが結構重要です。「Scopes」という箇所ではアプリの操作権限、アクセス範囲を設定できます。ユーザー情報の取得やミーティングの作成にもそれぞれスコープが用意されて、スコープ外の操作へのアクセスは401と認証エラーとなります。「Add Scopes」でスコープを追加します。",[13,24247,24248],{},"よくわからなければ全部追加してもいいですが、「Meeting」「User」のスコープを全て追加しておけば今回のアプリの実装は可能です。",[729,24250],{":src":24251,":width":732},"'_mix\u002Fzoom_scopes-768x431.jpeg'",[1566,24253,24254],{"id":24254},"アプリのアクティベート",[13,24256,24257],{},"最後に「Activation」にて確認します。不足箇所は以下の様にオレンジ文字で指摘されるので直しましょう。全てが入力できていれば後は問題ありません。「Install」などは押さなくても問題ありません。",[729,24259],{":src":24260,":width":732},"'_mix\u002Fzoom_activation-768x412.jpeg'",[17,24262,24264],{"id":24263},"lravelを立ち上げるdocker","Lravelを立ち上げる(Docker）",[13,24266,24267],{},"それではLaravelを立ち上げましょう。インストールはされており、ユーザーテーブルのマイグレーションを行う前だと仮定します。",[1499,24269,24271],{"id":24270},"clien-id-と-client-secretを設定","Clien ID と Client Secretを設定",[13,24273,24274,24275,24277,24278,24281,24282,4793,24285,24288],{},"Larvelのプロジェクトルートに",[49,24276,6497],{},"という環境変数を記述するファイルがありますので、そちらに ",[1463,24279,24280],{},"「App Credentials」"," で取得できる",[49,24283,24284],{},"Client ID",[49,24286,24287],{},"Client Secret","を設定します。",[42,24290,24293],{"className":24291,"code":24292,"filename":6497,"language":452,"meta":47},[1615],"APP_NAME=Laravel\nAPP_ENV=local\n...\nMIX_PUSHER_APP_CLUSTER=\"${PUSHER_APP_CLUSTER}\"\n\nZOOM_CLIENT_ID=clientid\nZOOM_CLIENT_SECRET=clientsecret\n",[49,24294,24292],{"__ignoreMap":47},[13,24296,24297,24299],{},[49,24298,6497],{},"を更新したらキャッシュをクリアして反映させます。",[42,24301,24304],{"className":24302,"code":24303,"language":452},[1615],"php artisan config:clear\nphp artisan cache:clear\n",[49,24305,24303],{"__ignoreMap":47},[13,24307,24308,24310,24311,24314,24315,24317,24318,24321],{},[49,24309,6497],{},"に記述することで他のソースコード内で",[49,24312,24313],{},"env('ZOOM_CLIENT_ID')","と行った形で出力できます。また",[49,24316,6497],{},"は基本的に",[49,24319,24320],{},".gitignore","に登録されているので公開リポジトリに秘密鍵が載ってしまうという様な事故を防げます。",[1499,24323,24325],{"id":24324},"user-tableをちょっと改造","User tableをちょっと改造",[13,24327,24328],{},"今回のアプリの管理者は一人ですが、もし複数人に使用してもらいたい時に「ユーザーごとにトークンを分けたいな」と思ったのでその改造をします。初期で用意されているユーザーテーブルを以下の様に書き換えます。",[42,24330,24333],{"className":13693,"code":24331,"filename":24332,"language":8577,"meta":47,"style":47},"public function up()\n{\n    Schema::create('users', function (Blueprint $table) {\n        $table->bigIncrements('id');\n        $table->string('name');\n        $table->string('email')->unique();\n        $table->timestamp('email_verified_at')->nullable();\n        $table->string('password');\n    \n        \u002F\u002Fここから追加\n        $table->longText('zoom_code')->nullable()->default(null);\n        $table->longText('access_token')->nullable()->default(null);\n        $table->longText('refresh_token')->nullable()->default(null);\n        $table->timestamp('zoom_expires_in', 0)->nullable()->default(null);\n        $table->rememberToken();\n        $table->timestamps();\n    });\n}\n","database\u002Fmigrations\u002F2014_10_12_000000_create_users_table.php",[49,24334,24335,24340,24344,24349,24354,24359,24364,24369,24374,24378,24383,24388,24393,24398,24403,24408,24413,24417],{"__ignoreMap":47},[52,24336,24337],{"class":54,"line":55},[52,24338,24339],{},"public function up()\n",[52,24341,24342],{"class":54,"line":83},[52,24343,364],{},[52,24345,24346],{"class":54,"line":115},[52,24347,24348],{},"    Schema::create('users', function (Blueprint $table) {\n",[52,24350,24351],{"class":54,"line":142},[52,24352,24353],{},"        $table->bigIncrements('id');\n",[52,24355,24356],{"class":54,"line":169},[52,24357,24358],{},"        $table->string('name');\n",[52,24360,24361],{"class":54,"line":302},[52,24362,24363],{},"        $table->string('email')->unique();\n",[52,24365,24366],{"class":54,"line":308},[52,24367,24368],{},"        $table->timestamp('email_verified_at')->nullable();\n",[52,24370,24371],{"class":54,"line":318},[52,24372,24373],{},"        $table->string('password');\n",[52,24375,24376],{"class":54,"line":328},[52,24377,3235],{},[52,24379,24380],{"class":54,"line":337},[52,24381,24382],{},"        \u002F\u002Fここから追加\n",[52,24384,24385],{"class":54,"line":344},[52,24386,24387],{},"        $table->longText('zoom_code')->nullable()->default(null);\n",[52,24389,24390],{"class":54,"line":354},[52,24391,24392],{},"        $table->longText('access_token')->nullable()->default(null);\n",[52,24394,24395],{"class":54,"line":367},[52,24396,24397],{},"        $table->longText('refresh_token')->nullable()->default(null);\n",[52,24399,24400],{"class":54,"line":387},[52,24401,24402],{},"        $table->timestamp('zoom_expires_in', 0)->nullable()->default(null);\n",[52,24404,24405],{"class":54,"line":415},[52,24406,24407],{},"        $table->rememberToken();\n",[52,24409,24410],{"class":54,"line":427},[52,24411,24412],{},"        $table->timestamps();\n",[52,24414,24415],{"class":54,"line":435},[52,24416,21366],{},[52,24418,24419],{"class":54,"line":446},[52,24420,536],{},[13,24422,24423],{},"それぞれのカラムの説明はこの通り。",[1467,24425,24426,24429,24432,24435],{},[1470,24427,24428],{},"zoom_code：連携許可の際に得られる許可コード。",[1470,24430,24431],{},"access_token：APIにアクセスするためのアクセストークン これを手にしたら勝ち。",[1470,24433,24434],{},"refresh_token：access_tokenを更新するためのトークン。",[1470,24436,24437],{},"zoom_expires_in：access_tokenの期限を記録しておく。APIにアクセスする前にこれでチェックする。",[13,24439,24440,24441,24444],{},"ユーザーごとのトークンが管理できる様になり、",[49,24442,24443],{},"$user->auth()->access_token","みたいな感じでトークンを使用できます。",[13,24446,24447],{},"それではマイグレーションをしましょう。（仮ユーザーのseedも忘れずに）",[42,24449,24452],{"className":24450,"code":24451,"language":452},[1615],"php artisan migrate --seed\nMigrating: 2014_10_12_000000_create_users_table\nMigrated:  2014_10_12_000000_create_users_table (0.02 seconds)\nMigrating: 2014_10_12_100000_create_password_resets_table\nMigrated:  2014_10_12_100000_create_password_resets_table (0.01 seconds)\nMigrating: 2019_08_19_000000_create_failed_jobs_table\nMigrated:  2019_08_19_000000_create_failed_jobs_table (0.01 seconds)\nSeeding: UsersTableSeeder\nSeeded:  UsersTableSeeder (0.06 seconds)\nDatabase seeding completed successfully.\n",[49,24453,24451],{"__ignoreMap":47},[1499,24455,24456],{"id":24456},"フォームと管理画面を適当に作る",[13,24458,24459,24460,24464],{},"詳しくは",[2039,24461,24463],{"href":24085,"rel":24462},[2043],"アップしたリポジトリ","を見てください。スタイルはbootstrapで調整しています。ルート情報だけ載せておきます。",[42,24466,24469],{"className":24467,"code":24468,"language":452},[1615],"\u002F                   フォームを表示  \n\u002Fconfirm            フォームの受付完了確認画面\n\u002Fadmin              管理者用ページ\n\u002Flogin              ログインページ\n\u002Flogout             ログアウトルート\n\u002Fzoomoatuh\u002Fcheck    zoom OAuthリダイレクト先\n\u002Fform\u002Falter         ミーティングの時間変更画面\n\u002Fform\u002Fdelete        ミーティングのキャンセル画面\n",[49,24470,24468],{"__ignoreMap":47},[17,24472,24474],{"id":24473},"lravel-と-zoomのoatuh-2-認証連携","Lravel と ZoomのOatuh 2 認証・連携",[1499,24476,24477],{"id":24477},"管理画面から連携確認画面へ誘導とユーザー認証",[13,24479,24480,24481,24484,24485,24487],{},"それではLaravelとZoomの連携処理を実装していきます。連携処理はログインした管理者のアクセス配下で行います。管理画面は",[49,24482,24483],{},"\u002Fadmin","です。",[49,24486,24483],{},"のコントローラーとビューは以下の通りです。",[42,24489,24492],{"className":13693,"code":24490,"filename":24491,"language":8577,"meta":47,"style":47},"public function index(Request $request){\n    $user = auth()->user();\n    $noZoomCode = $user->zoom_code == null; \u002F\u002F連携を行っているか\n    $zoomOuthLink = 'https:\u002F\u002Fzoom.us\u002Foauth\u002Fauthorize?'.http_build_query([\n        'response_type'=>'code',\n        'redirect_uri'=>env('APP_URL').'\u002Fzoomoatuh\u002Fcheck',\n        'client_id'=>env('ZOOM_CLIENT_ID'),\n    ]);\n    $oauthSuccess=false;\n    $meetings = Meeting::all();\n\n    return view('admin',compact('noZoomCode','zoomOuthLink','oauthSuccess','meetings'));\n}\n","app\u002FHttp\u002FControllers\u002FAdminController.php",[49,24493,24494,24499,24504,24509,24514,24519,24524,24529,24534,24539,24544,24548,24553],{"__ignoreMap":47},[52,24495,24496],{"class":54,"line":55},[52,24497,24498],{},"public function index(Request $request){\n",[52,24500,24501],{"class":54,"line":83},[52,24502,24503],{},"    $user = auth()->user();\n",[52,24505,24506],{"class":54,"line":115},[52,24507,24508],{},"    $noZoomCode = $user->zoom_code == null; \u002F\u002F連携を行っているか\n",[52,24510,24511],{"class":54,"line":142},[52,24512,24513],{},"    $zoomOuthLink = 'https:\u002F\u002Fzoom.us\u002Foauth\u002Fauthorize?'.http_build_query([\n",[52,24515,24516],{"class":54,"line":169},[52,24517,24518],{},"        'response_type'=>'code',\n",[52,24520,24521],{"class":54,"line":302},[52,24522,24523],{},"        'redirect_uri'=>env('APP_URL').'\u002Fzoomoatuh\u002Fcheck',\n",[52,24525,24526],{"class":54,"line":308},[52,24527,24528],{},"        'client_id'=>env('ZOOM_CLIENT_ID'),\n",[52,24530,24531],{"class":54,"line":318},[52,24532,24533],{},"    ]);\n",[52,24535,24536],{"class":54,"line":328},[52,24537,24538],{},"    $oauthSuccess=false;\n",[52,24540,24541],{"class":54,"line":337},[52,24542,24543],{},"    $meetings = Meeting::all();\n",[52,24545,24546],{"class":54,"line":344},[52,24547,341],{"emptyLinePlaceholder":340},[52,24549,24550],{"class":54,"line":354},[52,24551,24552],{},"    return view('admin',compact('noZoomCode','zoomOuthLink','oauthSuccess','meetings'));\n",[52,24554,24555],{"class":54,"line":367},[52,24556,536],{},[42,24558,24561],{"className":13693,"code":24559,"filename":24560,"language":8577,"meta":47,"style":47},"@extends('layouts.layout')\n\n@section('main-content')\n    \u003Cdiv class=\"main-content\">\n        @if($noZoomCode)\n        \u003Cdiv class=\"alert alert-danger mb-3\" role=\"alert\">\n            \u003Ch4 class=\"alert-heading\">Zoomとの連携が行われていません。\u003C\u002Fh4>\n            \u003Cp>このシステムをご利用する場合、Zoomとの連携を行ってください。\u003C\u002Fp>\n            \u003Ca href=\"{{$zoomOuthLink}}\" class=\"btn btn-danger\">Zoomと連携\u003C\u002Fa>\n        \u003C\u002Fdiv>\n        @else\n        \u003Ch1>予約一覧\u003C\u002Fh1>\n        @endif\n    \u003C\u002Fdiv>\n@endsection\n","resources\u002Fviews\u002Fadmin.blade.php",[49,24562,24563,24568,24572,24577,24582,24587,24592,24597,24602,24607,24611,24616,24621,24626,24631],{"__ignoreMap":47},[52,24564,24565],{"class":54,"line":55},[52,24566,24567],{},"@extends('layouts.layout')\n",[52,24569,24570],{"class":54,"line":83},[52,24571,341],{"emptyLinePlaceholder":340},[52,24573,24574],{"class":54,"line":115},[52,24575,24576],{},"@section('main-content')\n",[52,24578,24579],{"class":54,"line":142},[52,24580,24581],{},"    \u003Cdiv class=\"main-content\">\n",[52,24583,24584],{"class":54,"line":169},[52,24585,24586],{},"        @if($noZoomCode)\n",[52,24588,24589],{"class":54,"line":302},[52,24590,24591],{},"        \u003Cdiv class=\"alert alert-danger mb-3\" role=\"alert\">\n",[52,24593,24594],{"class":54,"line":308},[52,24595,24596],{},"            \u003Ch4 class=\"alert-heading\">Zoomとの連携が行われていません。\u003C\u002Fh4>\n",[52,24598,24599],{"class":54,"line":318},[52,24600,24601],{},"            \u003Cp>このシステムをご利用する場合、Zoomとの連携を行ってください。\u003C\u002Fp>\n",[52,24603,24604],{"class":54,"line":328},[52,24605,24606],{},"            \u003Ca href=\"{{$zoomOuthLink}}\" class=\"btn btn-danger\">Zoomと連携\u003C\u002Fa>\n",[52,24608,24609],{"class":54,"line":337},[52,24610,18449],{},[52,24612,24613],{"class":54,"line":344},[52,24614,24615],{},"        @else\n",[52,24617,24618],{"class":54,"line":354},[52,24619,24620],{},"        \u003Ch1>予約一覧\u003C\u002Fh1>\n",[52,24622,24623],{"class":54,"line":367},[52,24624,24625],{},"        @endif\n",[52,24627,24628],{"class":54,"line":387},[52,24629,24630],{},"    \u003C\u002Fdiv>\n",[52,24632,24633],{"class":54,"line":415},[52,24634,24635],{},"@endsection\n",[13,24637,24638,24639,24641,24642,21898,24645,24648],{},"管理画面では管理者がzoomと連携しているかで表示を変更しています。連携しているかは",[49,24640,22402],{},"テーブルの",[49,24643,24644],{},"zoom_code",[49,24646,24647],{},"null","かで確認しています。",[13,24650,24651],{},"連携が済んでいない場合はzoomの連携確認画面へ飛ばすリンクボタンを表示させています。",[13,24653,4461,24654,24657,24658,24663],{},[49,24655,24656],{},"$zoomOuthLink","の作成は",[2039,24659,24662],{"href":24660,"rel":24661},"https:\u002F\u002Fmarketplace.zoom.us\u002Fdocs\u002Fguides\u002Fauth\u002Foauth#getting-access-token",[2043],"こちらのドキュメント","にある通り、ルールがあります。",[42,24665,24667],{"className":13693,"code":24666,"filename":24491,"language":8577,"meta":47,"style":47},"$zoomOuthLink = 'https:\u002F\u002Fzoom.us\u002Foauth\u002Fauthorize?'.http_build_query([\n    'response_type'=>'code',\n    'redirect_uri'=>env('APP_URL').'\u002Fzoomoatuh\u002Fcheck',\n    'client_id'=>env('ZOOM_CLIENT_ID'),\n]);\n",[49,24668,24669,24674,24679,24684,24689],{"__ignoreMap":47},[52,24670,24671],{"class":54,"line":55},[52,24672,24673],{},"$zoomOuthLink = 'https:\u002F\u002Fzoom.us\u002Foauth\u002Fauthorize?'.http_build_query([\n",[52,24675,24676],{"class":54,"line":83},[52,24677,24678],{},"    'response_type'=>'code',\n",[52,24680,24681],{"class":54,"line":115},[52,24682,24683],{},"    'redirect_uri'=>env('APP_URL').'\u002Fzoomoatuh\u002Fcheck',\n",[52,24685,24686],{"class":54,"line":142},[52,24687,24688],{},"    'client_id'=>env('ZOOM_CLIENT_ID'),\n",[52,24690,24691],{"class":54,"line":169},[52,24692,24693],{},"]);\n",[13,24695,24696,24697,24700],{},"今はOAuthのステップの中で「ユーザー認証」というユーザーへ「このアプリ（Laravel）とzoomを連携してもいい？」とzoomが聞いている段階です。そのユーザー認証にはまず",[49,24698,24699],{},"https:\u002F\u002Fzoom.us\u002Foauth\u002Fauthorize","へGETで管理者本人がアクセスします。",[13,24702,24703,24704,2969,24707,2969,24710,24713],{},"その際にGETパラメータに",[49,24705,24706],{},"response_type",[49,24708,24709],{},"redirect_uri",[49,24711,24712],{},"client_id","を入力します。",[8503,24715,24717],{"className":24716},[8506,8507],"\nresponse_typeAccess response type being requested. The supported authorization workflow requires the value `code`.\n",[13,24719,24720,24721,24723,24724,24726,24727,24729,24730,24735,24736,24738,24739,9302,24741,24743,24744,24747],{},"とある様に",[49,24722,24706],{},"には",[49,24725,49],{},"という文字を設定します。そして",[49,24728,24709],{},"はzoom ",[2039,24731,24734],{"href":24732,"rel":24733},"https:\u002F\u002Fjun-app.com\u002Fzoom-api-laravel\u002F#zoom-redirect-url",[2043],"アプリ作成時にも設定した通りのURL","を入力しますので、",[49,24737,24224],{},"を設定。",[49,24740,24712],{},[49,24742,6497],{},"で設定値を",[49,24745,24746],{},"env()","で呼び出します。",[13,24749,24750],{},"それらをGETパラメータとして一つのURLにまとめます。以下の様な感じです。",[42,24752,24755],{"className":24753,"code":24754,"language":452},[1615],"https:\u002F\u002Fzoom.us\u002Foauth\u002Fauthorize?response_type=code&redirect_uri=http:\u002F\u002Flocalhost:9000\u002Fzoomauth\u002Fcheck&client_id=clientid\n",[49,24756,24754],{"__ignoreMap":47},[13,24758,24759],{},"予めサーバーサイドで作っておき、ボタンのリンクにはめ込んでおきます。画面では以下の様に表示されます。",[729,24761],{":src":24762,":width":732},"'_mix\u002Fsch-2021-01-10-18.08.06.png'",[1499,24764,24766],{"id":24765},"認証画面からのリダイレクトurlでの処理","認証画面からのリダイレクトURLでの処理",[13,24768,24769],{},"ボタンをクリックすると以下の画面が表示されます。（正確には承認画面のGETを叩く）",[729,24771],{":src":24772,":width":2993,":center":1323},"'_mix\u002Fzoom_approve.jpeg'",[13,24774,24775,24776,24778,24779,24781],{},"管理者に対してこのアプリが自身のzoomアカウントに対して、何をするのかが書かれています。管理者はこの「認可」を押すと、",[49,24777,24709],{},"のリダイレクト先に飛ばされます。OAuthではこのリダイレクト先の処理が大切です！",[49,24780,24224],{},"のコントローラーは以下の通りです。（ビューはなし）",[42,24783,24785],{"className":13693,"code":24784,"filename":24491,"language":8577,"meta":47,"style":47},"public function zoomOauth(Request $request){\n    $user = auth()->user();\n\n    if($user->zoom_code==null){\n        $code = $request['code'];\n\n        $user->zoom_code = $code;\n        $user->save();\n\n        $basic = base64_encode(env('ZOOM_CLIENT_ID').':'.env('ZOOM_CLIENT_SECRET'));\n        $client = new \\GuzzleHttp\\Client([\n            'headers' => ['Authorization' => 'Basic '.$basic]\n        ]);\n        $res = $client->request('POST','https:\u002F\u002Fzoom.us\u002Foauth\u002Ftoken',[\n            'query' => [\n                'grant_type'=>'authorization_code',\n                'code'=>$code,\n                'redirect_uri'=>'http:\u002F\u002Flocalhost:9000\u002Fzoomoatuh\u002Fcheck'\n            ]\n        ]);\n        $result = json_decode($res->getBody()->getContents());\n\n        $user->access_token= $result->access_token;\n        $user->refresh_token= $result->refresh_token;\n        $unixTime = time();\n        $user->zoom_expires_in= date(\"Y-m-d H:i:s\",$unixTime+$result->expires_in);\n        $user->save();\n\n        return redirect()->route('amdin')->with([\n            'noZoomCode'=>false,\n            'oauthSuccess'=>true\n        ]);\n    }\n}\n",[49,24786,24787,24792,24796,24800,24805,24810,24814,24819,24824,24828,24833,24838,24843,24847,24852,24857,24862,24867,24872,24877,24881,24886,24890,24895,24900,24905,24910,24914,24918,24923,24928,24933,24937,24941],{"__ignoreMap":47},[52,24788,24789],{"class":54,"line":55},[52,24790,24791],{},"public function zoomOauth(Request $request){\n",[52,24793,24794],{"class":54,"line":83},[52,24795,24503],{},[52,24797,24798],{"class":54,"line":115},[52,24799,341],{"emptyLinePlaceholder":340},[52,24801,24802],{"class":54,"line":142},[52,24803,24804],{},"    if($user->zoom_code==null){\n",[52,24806,24807],{"class":54,"line":169},[52,24808,24809],{},"        $code = $request['code'];\n",[52,24811,24812],{"class":54,"line":302},[52,24813,341],{"emptyLinePlaceholder":340},[52,24815,24816],{"class":54,"line":308},[52,24817,24818],{},"        $user->zoom_code = $code;\n",[52,24820,24821],{"class":54,"line":318},[52,24822,24823],{},"        $user->save();\n",[52,24825,24826],{"class":54,"line":328},[52,24827,341],{"emptyLinePlaceholder":340},[52,24829,24830],{"class":54,"line":337},[52,24831,24832],{},"        $basic = base64_encode(env('ZOOM_CLIENT_ID').':'.env('ZOOM_CLIENT_SECRET'));\n",[52,24834,24835],{"class":54,"line":344},[52,24836,24837],{},"        $client = new \\GuzzleHttp\\Client([\n",[52,24839,24840],{"class":54,"line":354},[52,24841,24842],{},"            'headers' => ['Authorization' => 'Basic '.$basic]\n",[52,24844,24845],{"class":54,"line":367},[52,24846,21775],{},[52,24848,24849],{"class":54,"line":387},[52,24850,24851],{},"        $res = $client->request('POST','https:\u002F\u002Fzoom.us\u002Foauth\u002Ftoken',[\n",[52,24853,24854],{"class":54,"line":415},[52,24855,24856],{},"            'query' => [\n",[52,24858,24859],{"class":54,"line":427},[52,24860,24861],{},"                'grant_type'=>'authorization_code',\n",[52,24863,24864],{"class":54,"line":435},[52,24865,24866],{},"                'code'=>$code,\n",[52,24868,24869],{"class":54,"line":446},[52,24870,24871],{},"                'redirect_uri'=>'http:\u002F\u002Flocalhost:9000\u002Fzoomoatuh\u002Fcheck'\n",[52,24873,24874],{"class":54,"line":480},[52,24875,24876],{},"            ]\n",[52,24878,24879],{"class":54,"line":509},[52,24880,21775],{},[52,24882,24883],{"class":54,"line":539},[52,24884,24885],{},"        $result = json_decode($res->getBody()->getContents());\n",[52,24887,24888],{"class":54,"line":547},[52,24889,341],{"emptyLinePlaceholder":340},[52,24891,24892],{"class":54,"line":553},[52,24893,24894],{},"        $user->access_token= $result->access_token;\n",[52,24896,24897],{"class":54,"line":559},[52,24898,24899],{},"        $user->refresh_token= $result->refresh_token;\n",[52,24901,24902],{"class":54,"line":564},[52,24903,24904],{},"        $unixTime = time();\n",[52,24906,24907],{"class":54,"line":569},[52,24908,24909],{},"        $user->zoom_expires_in= date(\"Y-m-d H:i:s\",$unixTime+$result->expires_in);\n",[52,24911,24912],{"class":54,"line":1106},[52,24913,24823],{},[52,24915,24916],{"class":54,"line":1135},[52,24917,341],{"emptyLinePlaceholder":340},[52,24919,24920],{"class":54,"line":1164},[52,24921,24922],{},"        return redirect()->route('amdin')->with([\n",[52,24924,24925],{"class":54,"line":1193},[52,24926,24927],{},"            'noZoomCode'=>false,\n",[52,24929,24930],{"class":54,"line":1200},[52,24931,24932],{},"            'oauthSuccess'=>true\n",[52,24934,24935],{"class":54,"line":1205},[52,24936,21775],{},[52,24938,24939],{"class":54,"line":1210},[52,24940,2696],{},[52,24942,24943],{"class":54,"line":1215},[52,24944,536],{},[13,24946,24947,24949,24950,24952,24953,24956],{},[49,24948,24699],{}," から ",[49,24951,24224],{}," へリダイレクトされると自動的にGETパラメータに",[49,24954,24955],{},"?code=~~~~","というものが付与されています。このcodeは後の認証に必要になります。",[13,24958,24959],{},"リダイレクトURLからcodeの値を取得します。いったんDBに保存してから、実際にAPIへリクエストするのに必要なアクセストークンの取得処理を行います。そこで以下の様なリクエストを行います。",[42,24961,24963],{"className":13693,"code":24962,"filename":24491,"language":8577,"meta":47,"style":47},"$basic = base64_encode(env('ZOOM_CLIENT_ID').':'.env('ZOOM_CLIENT_SECRET'));\n$client = new \\GuzzleHttp\\Client([\n    'headers' => ['Authorization' => 'Basic '.$basic]\n]);\n$res = $client->request('POST','https:\u002F\u002Fzoom.us\u002Foauth\u002Ftoken',[\n    'query' => [\n        'grant_type'=>'authorization_code',\n        'code'=>$code,\n        'redirect_uri'=>'http:\u002F\u002Flocalhost:9000\u002Fzoomoatuh\u002Fcheck'\n    ]\n]);\n",[49,24964,24965,24970,24975,24980,24984,24989,24994,24999,25004,25009,25013],{"__ignoreMap":47},[52,24966,24967],{"class":54,"line":55},[52,24968,24969],{},"$basic = base64_encode(env('ZOOM_CLIENT_ID').':'.env('ZOOM_CLIENT_SECRET'));\n",[52,24971,24972],{"class":54,"line":83},[52,24973,24974],{},"$client = new \\GuzzleHttp\\Client([\n",[52,24976,24977],{"class":54,"line":115},[52,24978,24979],{},"    'headers' => ['Authorization' => 'Basic '.$basic]\n",[52,24981,24982],{"class":54,"line":142},[52,24983,24693],{},[52,24985,24986],{"class":54,"line":169},[52,24987,24988],{},"$res = $client->request('POST','https:\u002F\u002Fzoom.us\u002Foauth\u002Ftoken',[\n",[52,24990,24991],{"class":54,"line":302},[52,24992,24993],{},"    'query' => [\n",[52,24995,24996],{"class":54,"line":308},[52,24997,24998],{},"        'grant_type'=>'authorization_code',\n",[52,25000,25001],{"class":54,"line":318},[52,25002,25003],{},"        'code'=>$code,\n",[52,25005,25006],{"class":54,"line":328},[52,25007,25008],{},"        'redirect_uri'=>'http:\u002F\u002Flocalhost:9000\u002Fzoomoatuh\u002Fcheck'\n",[52,25010,25011],{"class":54,"line":337},[52,25012,16060],{},[52,25014,25015],{"class":54,"line":344},[52,25016,24693],{},[13,25018,25019,25020,25023,25024,25027],{},"Zoomにも書いてある通りの処理ですが、アクセストークンを得る ",[49,25021,25022],{},"https:\u002F\u002Fzoom.us\u002Foauth\u002Ftoken"," というルートにアクセスするときは、まずリクエストヘッダーを付与します。リクエストヘッダーは ",[49,25025,25026],{},"'headers' => ['Authorization' => 'Basic '.$basic]"," です。ここに client IDとclient secretをコロンで付けて一つの文字列にし、それをbase64エンコードをします。つまり明示的に処理を表示すると以下の様な感じです。",[42,25029,25032],{"className":25030,"code":25031,"language":452},[1615],"client_id:cilent_secret \u002F\u002Fこれで一行の文字列\n↓\nこの値を64base encode\n↓\nsi84nf7435934jdfsdfi... \u002F\u002Fエンコード化された文字。これを送る\n",[49,25033,25031],{"__ignoreMap":47},[13,25035,25036,25037,25039,25040,25043],{},"そしてそれをリクエストヘッダーに付与します。",[49,25038,25026],{}," これを文字列として表示すると、",[49,25041,25042],{},"Authorization: Basic si84nf7435934jdfsdfi…"," みたいな感じです。ちなみに Basicとエンコード文字の間は半角が空いていますので注意。",[13,25045,25046],{},"リクエストヘッダーを付けたら先ほどと似た感じでGETパラメータを以下の様に設定します。",[42,25048,25050],{"className":13693,"code":25049,"language":8577,"meta":47,"style":47},"$res = $client->request('POST','https:\u002F\u002Fzoom.us\u002Foauth\u002Ftoken',[\n    'query' => [\n        'grant_type'=>'authorization_code',\n        'code'=>$code,\n        'redirect_uri'=>'http:\u002F\u002Flocalhost:9000\u002Fzoomoatuh\u002Fcheck'\n    ]\n])\n",[49,25051,25052,25056,25060,25064,25068,25072,25076],{"__ignoreMap":47},[52,25053,25054],{"class":54,"line":55},[52,25055,24988],{},[52,25057,25058],{"class":54,"line":83},[52,25059,24993],{},[52,25061,25062],{"class":54,"line":115},[52,25063,24998],{},[52,25065,25066],{"class":54,"line":142},[52,25067,25003],{},[52,25069,25070],{"class":54,"line":169},[52,25071,25008],{},[52,25073,25074],{"class":54,"line":302},[52,25075,16060],{},[52,25077,25078],{"class":54,"line":308},[52,25079,25080],{},"])\n",[13,25082,25083,21898,25086,25089,25090,25093,25094,25096,25097,25099],{},[49,25084,25085],{},"grant_type",[49,25087,25088],{},"authorization_code","という文字とし、codeにはリダイレクト時についてきた値である",[49,25091,25092],{},"$request['code']","を用います。",[49,25095,24709],{},"は先ほどと同じです。（",[49,25098,24709],{},"を別にすると認証が通りません！）",[13,25101,25102],{},"これでセットアップが完了です。実際のURLとしては以下の感じです。",[42,25104,25107],{"className":25105,"code":25106,"language":452},[1615],"https:\u002F\u002Fzoom.us\u002Foauth\u002Ftoken?grant_type=authorization_code&code=~~~~~&redirect_uri=http:\u002F\u002Flocalhost:9000\u002Fzoomoatuh\u002Fcheck\n（そして直接は見えないですが、リクエストヘッダーには 「Authorization: Basic si84nf7435934jdfsdfi…」 という値がついています！\n",[49,25108,25106],{"__ignoreMap":47},[13,25110,25111,25112,25115],{},"リクエストが成功するとアクセストークン を含んだレスポンスがJSONで戻ってきます。それを展開してDBへ保存します。",[49,25113,25114],{},"zoom_expires_in","は現在時刻と足し合わせて、期限切れ時刻を計算してから格納しています。",[42,25117,25119],{"className":13693,"code":25118,"language":8577,"meta":47,"style":47},"\u002F*\n$resultの中身の例\n{\n    \"access_token\": \"eyJhbGciOiJIUz...\",\n    \"token_type\": \"bearer\",\n    \"refresh_token\": \"eyJhbGciOiJI..\",\n    \"expires_in\": 3599,\n    \"scope\": \"user:read\"\n}\n*\u002F\n\n$result = json_decode($res->getBody()->getContents());\n\n$user->access_token= $result->access_token;\n$user->refresh_token= $result->refresh_token;\n$unixTime = time();\n$user->zoom_expires_in= date(\"Y-m-d H:i:s\",$unixTime+$result->expires_in);\n$user->save();\n\nreturn redirect()->route('amdin')->with([\n         'noZoomCode'=>false,\n         'oauthSuccess'=>true\n]);\n",[49,25120,25121,25125,25130,25134,25139,25144,25149,25154,25159,25163,25167,25171,25176,25180,25185,25190,25195,25200,25205,25209,25214,25219,25224],{"__ignoreMap":47},[52,25122,25123],{"class":54,"line":55},[52,25124,20827],{},[52,25126,25127],{"class":54,"line":83},[52,25128,25129],{},"$resultの中身の例\n",[52,25131,25132],{"class":54,"line":115},[52,25133,364],{},[52,25135,25136],{"class":54,"line":142},[52,25137,25138],{},"    \"access_token\": \"eyJhbGciOiJIUz...\",\n",[52,25140,25141],{"class":54,"line":169},[52,25142,25143],{},"    \"token_type\": \"bearer\",\n",[52,25145,25146],{"class":54,"line":302},[52,25147,25148],{},"    \"refresh_token\": \"eyJhbGciOiJI..\",\n",[52,25150,25151],{"class":54,"line":308},[52,25152,25153],{},"    \"expires_in\": 3599,\n",[52,25155,25156],{"class":54,"line":318},[52,25157,25158],{},"    \"scope\": \"user:read\"\n",[52,25160,25161],{"class":54,"line":328},[52,25162,536],{},[52,25164,25165],{"class":54,"line":337},[52,25166,20860],{},[52,25168,25169],{"class":54,"line":344},[52,25170,341],{"emptyLinePlaceholder":340},[52,25172,25173],{"class":54,"line":354},[52,25174,25175],{},"$result = json_decode($res->getBody()->getContents());\n",[52,25177,25178],{"class":54,"line":367},[52,25179,341],{"emptyLinePlaceholder":340},[52,25181,25182],{"class":54,"line":387},[52,25183,25184],{},"$user->access_token= $result->access_token;\n",[52,25186,25187],{"class":54,"line":415},[52,25188,25189],{},"$user->refresh_token= $result->refresh_token;\n",[52,25191,25192],{"class":54,"line":427},[52,25193,25194],{},"$unixTime = time();\n",[52,25196,25197],{"class":54,"line":435},[52,25198,25199],{},"$user->zoom_expires_in= date(\"Y-m-d H:i:s\",$unixTime+$result->expires_in);\n",[52,25201,25202],{"class":54,"line":446},[52,25203,25204],{},"$user->save();\n",[52,25206,25207],{"class":54,"line":480},[52,25208,341],{"emptyLinePlaceholder":340},[52,25210,25211],{"class":54,"line":509},[52,25212,25213],{},"return redirect()->route('amdin')->with([\n",[52,25215,25216],{"class":54,"line":539},[52,25217,25218],{},"         'noZoomCode'=>false,\n",[52,25220,25221],{"class":54,"line":547},[52,25222,25223],{},"         'oauthSuccess'=>true\n",[52,25225,25226],{"class":54,"line":553},[52,25227,24693],{},[13,25229,25230],{},"そして最後は管理画面へリダイレクトしてあげます。管理者からしてみるとzoomの画面で「許可」を押すと元のサイトに戻って、グルグルローディングしてるなーと思ったら管理画面に戻ってきた感覚となります。実際の画面ではzoom連携の警告がなくなり以下の様な感じになります。",[729,25232],{":src":25233,":width":732},"'_mix\u002Fsch-2021-01-10-20.30.08-768x155.png'",[13,25235,25236],{},"これでOAuthは完了です。意外と簡単ですね。access_tokenは1時間で切れてしまうので、APIアクセスの際は期限切れでないかをチェック、そしてダメならtokenをリフレッシュする機能が必要となります。次はaccess_tokenのチェック方ら連携した人のユーザー情報を取得するとともに、リフレッシュ機能を実装します。",[17,25238,25239],{"id":25239},"ユーザー情報を取得",[13,25241,25242],{},"ミーティングを作成したりなどはユーザーIDが必要となります。他のAPIで使用するのでコントローラー内の共通メソッドとして分離しておきましょう。以下の様にします。",[42,25244,25247],{"className":13693,"code":25245,"filename":25246,"language":8577,"meta":47,"style":47},"class ZoomApiController extends Controller\n{\n    \u002F\u002F\n    protected function me(){\n        $user = auth()->user();\n        $client = new \\GuzzleHttp\\Client([\n            'headers' => ['Authorization' => 'Bearer '.$user->access_token]\n        ]);\n        $res = $client->request('GET','https:\u002F\u002Fapi.zoom.us\u002Fv2\u002Fusers\u002Fme');\n        $result = json_decode($res->getBody()->getContents());\n        \u002F\u002F dd($result);\n        return $result;\n    }\n...\n}\n","app\u002FHttp\u002FControllers\u002FZoomApiController.php",[49,25248,25249,25254,25258,25263,25268,25273,25277,25282,25286,25291,25295,25300,25305,25309,25313],{"__ignoreMap":47},[52,25250,25251],{"class":54,"line":55},[52,25252,25253],{},"class ZoomApiController extends Controller\n",[52,25255,25256],{"class":54,"line":83},[52,25257,364],{},[52,25259,25260],{"class":54,"line":115},[52,25261,25262],{},"    \u002F\u002F\n",[52,25264,25265],{"class":54,"line":142},[52,25266,25267],{},"    protected function me(){\n",[52,25269,25270],{"class":54,"line":169},[52,25271,25272],{},"        $user = auth()->user();\n",[52,25274,25275],{"class":54,"line":302},[52,25276,24837],{},[52,25278,25279],{"class":54,"line":308},[52,25280,25281],{},"            'headers' => ['Authorization' => 'Bearer '.$user->access_token]\n",[52,25283,25284],{"class":54,"line":318},[52,25285,21775],{},[52,25287,25288],{"class":54,"line":328},[52,25289,25290],{},"        $res = $client->request('GET','https:\u002F\u002Fapi.zoom.us\u002Fv2\u002Fusers\u002Fme');\n",[52,25292,25293],{"class":54,"line":337},[52,25294,24885],{},[52,25296,25297],{"class":54,"line":344},[52,25298,25299],{},"        \u002F\u002F dd($result);\n",[52,25301,25302],{"class":54,"line":354},[52,25303,25304],{},"        return $result;\n",[52,25306,25307],{"class":54,"line":367},[52,25308,2696],{},[52,25310,25311],{"class":54,"line":387},[52,25312,14102],{},[52,25314,25315],{"class":54,"line":415},[52,25316,536],{},[13,25318,25319,25320,25322,25323,25326,25327,25330],{},"ユーザーテーブルに",[49,25321,22166],{},"があるので",[49,25324,25325],{},"$user = auth()->user();","で現在のログインユーザーを取り出して、",[49,25328,25329],{},"$user->access_token","にて出力します。",[13,25332,25333,25335,25336,25339,25340,25343],{},[49,25334,22166],{},"があればAPIへのアクセスはリクエストヘッダーにトークンを入れるだけでアクセスできます。リクエストヘッダーは",[49,25337,25338],{},"'headers' => ['Authorization' => 'Bearer '.$user->access_token]","です。さっきはBasicだったのが、",[49,25341,25342],{},"Bearer（ベアラー）","になっていますのでタイポに注意。",[13,25345,25346,25349],{},[49,25347,25348],{},"dd($result)","を有効にして出力してみると",[729,25351],{":src":25352,":width":2993,":center":1323},"'_mix\u002Fzoom_me.jpeg'",[13,25354,25355],{},"こんな感じのJSONが返ってきますので、適宜IDなどを使用します。",[17,25357,25358],{"id":25358},"リフレッシュ機能を実装",[13,25360,25361],{},"access tokenは1時間しか持たないのでもし期限切れになった際にはリフレッシュトークンを使用してトークンを更新します。ちなみにリフレッシュトークンの有効期限は15年です笑私の場合は以下の様に実装しました。",[42,25363,25365],{"className":13693,"code":25364,"filename":25246,"language":8577,"meta":47,"style":47},"protected function checkRefresh(){\n    $user = auth()->user();\n    $token_expires =  new \\DateTime($user->zoom_expires_in);\n    $now = new \\DateTime();\n\n    if($now >= $token_expires){\n        $basic = base64_encode(env('ZOOM_CLIENT_ID').':'.env('ZOOM_CLIENT_SECRET'));\n        $client = new \\GuzzleHttp\\Client([\n            'headers' => ['Authorization' => 'Basic '.$basic]\n        ]);\n        $res = $client->request('POST','https:\u002F\u002Fzoom.us\u002Foauth\u002Ftoken',[\n            'query' => [\n                'grant_type'=>'refresh_token',\n                'refresh_token'=>$user->refresh_token\n            ]\n        ]);\n        $result = json_decode($res->getBody()->getContents());\n\n        $user->access_token= $result->access_token;\n        $user->refresh_token= $result->access_token;\n        $unixTime = time();\n        $user->zoom_expires_in= date(\"Y-m-d H:i:s\",$unixTime+$result->expires_in);\n        $user->save();\n        return $user;\n    }\n    return $user;\n}\n",[49,25366,25367,25372,25376,25381,25386,25390,25395,25399,25403,25407,25411,25415,25419,25424,25429,25433,25437,25441,25445,25449,25454,25458,25462,25466,25471,25475,25480],{"__ignoreMap":47},[52,25368,25369],{"class":54,"line":55},[52,25370,25371],{},"protected function checkRefresh(){\n",[52,25373,25374],{"class":54,"line":83},[52,25375,24503],{},[52,25377,25378],{"class":54,"line":115},[52,25379,25380],{},"    $token_expires =  new \\DateTime($user->zoom_expires_in);\n",[52,25382,25383],{"class":54,"line":142},[52,25384,25385],{},"    $now = new \\DateTime();\n",[52,25387,25388],{"class":54,"line":169},[52,25389,341],{"emptyLinePlaceholder":340},[52,25391,25392],{"class":54,"line":302},[52,25393,25394],{},"    if($now >= $token_expires){\n",[52,25396,25397],{"class":54,"line":308},[52,25398,24832],{},[52,25400,25401],{"class":54,"line":318},[52,25402,24837],{},[52,25404,25405],{"class":54,"line":328},[52,25406,24842],{},[52,25408,25409],{"class":54,"line":337},[52,25410,21775],{},[52,25412,25413],{"class":54,"line":344},[52,25414,24851],{},[52,25416,25417],{"class":54,"line":354},[52,25418,24856],{},[52,25420,25421],{"class":54,"line":367},[52,25422,25423],{},"                'grant_type'=>'refresh_token',\n",[52,25425,25426],{"class":54,"line":387},[52,25427,25428],{},"                'refresh_token'=>$user->refresh_token\n",[52,25430,25431],{"class":54,"line":415},[52,25432,24876],{},[52,25434,25435],{"class":54,"line":427},[52,25436,21775],{},[52,25438,25439],{"class":54,"line":435},[52,25440,24885],{},[52,25442,25443],{"class":54,"line":446},[52,25444,341],{"emptyLinePlaceholder":340},[52,25446,25447],{"class":54,"line":480},[52,25448,24894],{},[52,25450,25451],{"class":54,"line":509},[52,25452,25453],{},"        $user->refresh_token= $result->access_token;\n",[52,25455,25456],{"class":54,"line":539},[52,25457,24904],{},[52,25459,25460],{"class":54,"line":547},[52,25461,24909],{},[52,25463,25464],{"class":54,"line":553},[52,25465,24823],{},[52,25467,25468],{"class":54,"line":559},[52,25469,25470],{},"        return $user;\n",[52,25472,25473],{"class":54,"line":564},[52,25474,2696],{},[52,25476,25477],{"class":54,"line":569},[52,25478,25479],{},"    return $user;\n",[52,25481,25482],{"class":54,"line":1106},[52,25483,536],{},[13,25485,25486],{},"APIリクエストごとにトークンをチェックできる様にしています。有効期限をテーブルに保存してあるのでそれを比較して、現在時刻が有効期限を過ぎていたらリフレッシュ処理を行う様します。そして戻ってきたトークンをテーブルで更新させ、ユーザーモデルをreturnします。",[13,25488,25489],{},"有効期限ないであればそのままユーザーモデルを返却するという感じです。",[17,25491,25493],{"id":25492},"apiからミーティングを作成","APIからミーティングを作成",[13,25495,25496],{},"それではフォームから入力された値を元にミーティングを作れる様にしましょう。メール通知は機能してはいませんが、実装したコードはコメントアウトさせてますので、頑張れる人はメールも実装してみてください。",[13,25498,25499],{},"まずフォームから取得したミーティング情報を格納するテーブルを以下の様に定義して、マイグレーションを実施します。",[1499,25501,25503],{"id":25502},"フォームミーティング管理テーブルを作成","フォーム＆ミーティング管理テーブルを作成",[42,25505,25508],{"className":13693,"code":25506,"filename":25507,"language":8577,"meta":47,"style":47},"\u003C?php\n\nuse Illuminate\\Database\\Migrations\\Migration;\nuse Illuminate\\Database\\Schema\\Blueprint;\nuse Illuminate\\Support\\Facades\\Schema;\nuse Illuminate\\Support\\Facades\\DB;\n\nclass CreateMeeting extends Migration\n{\n    \u002F**\n     * Run the migrations.\n     *\n     * @return void\n     *\u002F\n    public function up()\n    {\n        Schema::create('meeting', function (Blueprint $table) {\n            $table->bigIncrements('id');\n            $table->string('name');\n            $table->string('company_name');\n            $table->string('email');\n            $table->longText('content');\n\n            $table->timestamp('start_at', 0)->default(DB::raw('CURRENT_TIMESTAMP'));\n            $table->longText('hash');\n            $table->boolean('is_canceled');\n\n            $table->longText('zoom_meeting_id');\n            $table->longText('zoom_join_url');\n            $table->longText('zoom_start_url');\n            $table->longText('zoom_password');\n            $table->timestamps();\n        });\n    }\n\n    \u002F**\n     * Reverse the migrations.\n     *\n     * @return void\n     *\u002F\n    public function down()\n    {\n        Schema::dropIfExists('meeting');\n    }\n}\n","database\u002Fmigrations\u002F2021_01_10_005145_create_meeting.php",[49,25509,25510,25514,25518,25523,25528,25533,25538,25542,25547,25551,25555,25560,25564,25568,25572,25577,25581,25586,25591,25596,25601,25606,25611,25615,25620,25625,25630,25634,25639,25644,25649,25654,25659,25664,25668,25672,25676,25681,25685,25689,25693,25698,25702,25707,25711],{"__ignoreMap":47},[52,25511,25512],{"class":54,"line":55},[52,25513,13868],{},[52,25515,25516],{"class":54,"line":83},[52,25517,341],{"emptyLinePlaceholder":340},[52,25519,25520],{"class":54,"line":115},[52,25521,25522],{},"use Illuminate\\Database\\Migrations\\Migration;\n",[52,25524,25525],{"class":54,"line":142},[52,25526,25527],{},"use Illuminate\\Database\\Schema\\Blueprint;\n",[52,25529,25530],{"class":54,"line":169},[52,25531,25532],{},"use Illuminate\\Support\\Facades\\Schema;\n",[52,25534,25535],{"class":54,"line":302},[52,25536,25537],{},"use Illuminate\\Support\\Facades\\DB;\n",[52,25539,25540],{"class":54,"line":308},[52,25541,341],{"emptyLinePlaceholder":340},[52,25543,25544],{"class":54,"line":318},[52,25545,25546],{},"class CreateMeeting extends Migration\n",[52,25548,25549],{"class":54,"line":328},[52,25550,364],{},[52,25552,25553],{"class":54,"line":337},[52,25554,20999],{},[52,25556,25557],{"class":54,"line":344},[52,25558,25559],{},"     * Run the migrations.\n",[52,25561,25562],{"class":54,"line":354},[52,25563,21009],{},[52,25565,25566],{"class":54,"line":367},[52,25567,21465],{},[52,25569,25570],{"class":54,"line":387},[52,25571,21019],{},[52,25573,25574],{"class":54,"line":415},[52,25575,25576],{},"    public function up()\n",[52,25578,25579],{"class":54,"line":427},[52,25580,14283],{},[52,25582,25583],{"class":54,"line":435},[52,25584,25585],{},"        Schema::create('meeting', function (Blueprint $table) {\n",[52,25587,25588],{"class":54,"line":446},[52,25589,25590],{},"            $table->bigIncrements('id');\n",[52,25592,25593],{"class":54,"line":480},[52,25594,25595],{},"            $table->string('name');\n",[52,25597,25598],{"class":54,"line":509},[52,25599,25600],{},"            $table->string('company_name');\n",[52,25602,25603],{"class":54,"line":539},[52,25604,25605],{},"            $table->string('email');\n",[52,25607,25608],{"class":54,"line":547},[52,25609,25610],{},"            $table->longText('content');\n",[52,25612,25613],{"class":54,"line":553},[52,25614,341],{"emptyLinePlaceholder":340},[52,25616,25617],{"class":54,"line":559},[52,25618,25619],{},"            $table->timestamp('start_at', 0)->default(DB::raw('CURRENT_TIMESTAMP'));\n",[52,25621,25622],{"class":54,"line":564},[52,25623,25624],{},"            $table->longText('hash');\n",[52,25626,25627],{"class":54,"line":569},[52,25628,25629],{},"            $table->boolean('is_canceled');\n",[52,25631,25632],{"class":54,"line":1106},[52,25633,341],{"emptyLinePlaceholder":340},[52,25635,25636],{"class":54,"line":1135},[52,25637,25638],{},"            $table->longText('zoom_meeting_id');\n",[52,25640,25641],{"class":54,"line":1164},[52,25642,25643],{},"            $table->longText('zoom_join_url');\n",[52,25645,25646],{"class":54,"line":1193},[52,25647,25648],{},"            $table->longText('zoom_start_url');\n",[52,25650,25651],{"class":54,"line":1200},[52,25652,25653],{},"            $table->longText('zoom_password');\n",[52,25655,25656],{"class":54,"line":1205},[52,25657,25658],{},"            $table->timestamps();\n",[52,25660,25661],{"class":54,"line":1210},[52,25662,25663],{},"        });\n",[52,25665,25666],{"class":54,"line":1215},[52,25667,2696],{},[52,25669,25670],{"class":54,"line":1220},[52,25671,341],{"emptyLinePlaceholder":340},[52,25673,25674],{"class":54,"line":3800},[52,25675,20999],{},[52,25677,25678],{"class":54,"line":3821},[52,25679,25680],{},"     * Reverse the migrations.\n",[52,25682,25683],{"class":54,"line":3835},[52,25684,21009],{},[52,25686,25687],{"class":54,"line":3840},[52,25688,21465],{},[52,25690,25691],{"class":54,"line":3865},[52,25692,21019],{},[52,25694,25695],{"class":54,"line":3879},[52,25696,25697],{},"    public function down()\n",[52,25699,25700],{"class":54,"line":5506},[52,25701,14283],{},[52,25703,25704],{"class":54,"line":4},[52,25705,25706],{},"        Schema::dropIfExists('meeting');\n",[52,25708,25709],{"class":54,"line":5544},[52,25710,2696],{},[52,25712,25713],{"class":54,"line":5561},[52,25714,536],{},[13,25716,25717,25720],{},[49,25718,25719],{},"start_at","はお客さんが入力した希望zoom開催時間です。本来は管理側の都合を合わせた機能にすべきですが今回はプロトタイプなので割愛。フロントからはdatetime形式で来たものを受け取ります。",[13,25722,25723,25726],{},[49,25724,25725],{},"hash","は後でお客さんがミーティングをキャンセルしたり、時間を変更するときにアクセスするURLに付けるランダムな文字列です。一種のパスワードみたいなものです。後ほど使い方を解説します。",[13,25728,25729,25730,4793,25733,25736],{},"そしてzoomへCreate Meeting API を送信すると、ミーティングURLなどが返ってきますのでそれを",[49,25731,25732],{},"zoom_join_url",[49,25734,25735],{},"zoom_start_url","に入れておきます 。他にフォームの内容を格納する箇所を定義してマイグレーションします。",[13,25738,25739],{},"フォームの画面以下の様に実装しました。",[729,25741],{":src":24049,":width":732},[1499,25743,25745],{"id":25744},"バリデーションとmeeting-apiをリクエスト","バリデーションとmeeting apiをリクエスト",[13,25747,25748],{},"フォームのビューがPOSTリクエストを受けたら以下のコントローラーが実行されます。",[42,25750,25752],{"className":13693,"code":25751,"filename":25246,"language":8577,"meta":47,"style":47},"public function createMeeting(Request $request){\n    $validator = Validator::make($request->all(),[\n        'email'=>'required|email:rfc',\n        'yourname'=>'required',\n        'companyname'=>'required',\n        'startAt'=>'date|required',\n        'content'=>'required|max:1000',\n    ]);\n\n    $error = $validator->getMessageBag()->toArray();\n    \n    \u002F\u002Fバリデーションエラーがあれば元の画面へ\n    if ($validator->fails()) {\n        return view('form',compact('error'));\n    }\n    \n    $user = $this->checkRefresh();\n    $user = auth()->user();\n\n    $zoom_user = $this->me();\n\n    $url = 'https:\u002F\u002Fapi.zoom.us\u002Fv2\u002Fusers\u002F'.$zoom_user->id.'\u002Fmeetings';\n    $client = new \\GuzzleHttp\\Client([\n        'headers' => [\n            'Authorization' => 'Bearer '.$user->access_token,\n            'Content-Type'=>'application\u002Fjson'\n        ],\n    ]);\n\n    $topic = $request->companyname.' '.$request->yourname.'様 ご相談';\n    $meeting_password = substr(base_convert(bin2hex(openssl_random_pseudo_bytes(9)),16,36),0,9);\n    $res = $client->request('POST',$url,[\n        \\GuzzleHttp\\RequestOptions::JSON => [\n            'topic'=>$topic,\n            'type'=>2,\n            'start_time'=>$request->startAt,\n            'password'=>$meeting_password\n        ]\n    ]);\n    $result = json_decode($res->getBody()->getContents());\n\n    $meeting = new Meeting();\n    $meeting->name=$request->yourname;\n    $meeting->company_name=$request->companyname;\n    $meeting->email=$request->email;\n    $meeting->content=$request->content;\n\n    $start = new \\DateTime($result->start_time);\n    $meeting->start_at=$start;\n    $meeting->hash=substr(base_convert(bin2hex(openssl_random_pseudo_bytes(64)),16,36),0,64);\n    $meeting->is_canceled=false;\n\n    $meeting->zoom_meeting_id=$result->id;\n    $meeting->zoom_join_url=$result->join_url;\n    $meeting->zoom_start_url=$result->start_url;\n    $meeting->zoom_password=$result->password;\n    $meeting->save();\n\n    $format = $start->format('Y年m月d日 H時i分');\n    \u002F\u002F $meeting->start_at = $format;\n    \u002F\u002F $mail = new ContactMail($meeting);\n    \u002F\u002F Mail::to($request->email)->send($mail);\n\n\n    return redirect('\u002Fconfirm')->with([\n        'form_id'=>$meeting->id,\n        'name'=>$request->yourname,\n        'companyname'=>$request->companyname,\n        'content'=>$request->content,\n        'start_time'=>$format\n    ]);\n}\n",[49,25753,25754,25759,25764,25769,25774,25779,25784,25789,25793,25797,25802,25806,25811,25816,25821,25825,25829,25834,25838,25842,25847,25851,25856,25861,25866,25871,25876,25880,25884,25888,25893,25898,25903,25908,25913,25918,25923,25928,25933,25937,25942,25946,25951,25956,25961,25966,25971,25975,25980,25985,25990,25995,25999,26004,26009,26014,26019,26024,26028,26033,26038,26043,26048,26052,26056,26061,26066,26071,26076,26081,26086,26090],{"__ignoreMap":47},[52,25755,25756],{"class":54,"line":55},[52,25757,25758],{},"public function createMeeting(Request $request){\n",[52,25760,25761],{"class":54,"line":83},[52,25762,25763],{},"    $validator = Validator::make($request->all(),[\n",[52,25765,25766],{"class":54,"line":115},[52,25767,25768],{},"        'email'=>'required|email:rfc',\n",[52,25770,25771],{"class":54,"line":142},[52,25772,25773],{},"        'yourname'=>'required',\n",[52,25775,25776],{"class":54,"line":169},[52,25777,25778],{},"        'companyname'=>'required',\n",[52,25780,25781],{"class":54,"line":302},[52,25782,25783],{},"        'startAt'=>'date|required',\n",[52,25785,25786],{"class":54,"line":308},[52,25787,25788],{},"        'content'=>'required|max:1000',\n",[52,25790,25791],{"class":54,"line":318},[52,25792,24533],{},[52,25794,25795],{"class":54,"line":328},[52,25796,341],{"emptyLinePlaceholder":340},[52,25798,25799],{"class":54,"line":337},[52,25800,25801],{},"    $error = $validator->getMessageBag()->toArray();\n",[52,25803,25804],{"class":54,"line":344},[52,25805,3235],{},[52,25807,25808],{"class":54,"line":354},[52,25809,25810],{},"    \u002F\u002Fバリデーションエラーがあれば元の画面へ\n",[52,25812,25813],{"class":54,"line":367},[52,25814,25815],{},"    if ($validator->fails()) {\n",[52,25817,25818],{"class":54,"line":387},[52,25819,25820],{},"        return view('form',compact('error'));\n",[52,25822,25823],{"class":54,"line":415},[52,25824,2696],{},[52,25826,25827],{"class":54,"line":427},[52,25828,3235],{},[52,25830,25831],{"class":54,"line":435},[52,25832,25833],{},"    $user = $this->checkRefresh();\n",[52,25835,25836],{"class":54,"line":446},[52,25837,24503],{},[52,25839,25840],{"class":54,"line":480},[52,25841,341],{"emptyLinePlaceholder":340},[52,25843,25844],{"class":54,"line":509},[52,25845,25846],{},"    $zoom_user = $this->me();\n",[52,25848,25849],{"class":54,"line":539},[52,25850,341],{"emptyLinePlaceholder":340},[52,25852,25853],{"class":54,"line":547},[52,25854,25855],{},"    $url = 'https:\u002F\u002Fapi.zoom.us\u002Fv2\u002Fusers\u002F'.$zoom_user->id.'\u002Fmeetings';\n",[52,25857,25858],{"class":54,"line":553},[52,25859,25860],{},"    $client = new \\GuzzleHttp\\Client([\n",[52,25862,25863],{"class":54,"line":559},[52,25864,25865],{},"        'headers' => [\n",[52,25867,25868],{"class":54,"line":564},[52,25869,25870],{},"            'Authorization' => 'Bearer '.$user->access_token,\n",[52,25872,25873],{"class":54,"line":569},[52,25874,25875],{},"            'Content-Type'=>'application\u002Fjson'\n",[52,25877,25878],{"class":54,"line":1106},[52,25879,21281],{},[52,25881,25882],{"class":54,"line":1135},[52,25883,24533],{},[52,25885,25886],{"class":54,"line":1164},[52,25887,341],{"emptyLinePlaceholder":340},[52,25889,25890],{"class":54,"line":1193},[52,25891,25892],{},"    $topic = $request->companyname.' '.$request->yourname.'様 ご相談';\n",[52,25894,25895],{"class":54,"line":1200},[52,25896,25897],{},"    $meeting_password = substr(base_convert(bin2hex(openssl_random_pseudo_bytes(9)),16,36),0,9);\n",[52,25899,25900],{"class":54,"line":1205},[52,25901,25902],{},"    $res = $client->request('POST',$url,[\n",[52,25904,25905],{"class":54,"line":1210},[52,25906,25907],{},"        \\GuzzleHttp\\RequestOptions::JSON => [\n",[52,25909,25910],{"class":54,"line":1215},[52,25911,25912],{},"            'topic'=>$topic,\n",[52,25914,25915],{"class":54,"line":1220},[52,25916,25917],{},"            'type'=>2,\n",[52,25919,25920],{"class":54,"line":3800},[52,25921,25922],{},"            'start_time'=>$request->startAt,\n",[52,25924,25925],{"class":54,"line":3821},[52,25926,25927],{},"            'password'=>$meeting_password\n",[52,25929,25930],{"class":54,"line":3835},[52,25931,25932],{},"        ]\n",[52,25934,25935],{"class":54,"line":3840},[52,25936,24533],{},[52,25938,25939],{"class":54,"line":3865},[52,25940,25941],{},"    $result = json_decode($res->getBody()->getContents());\n",[52,25943,25944],{"class":54,"line":3879},[52,25945,341],{"emptyLinePlaceholder":340},[52,25947,25948],{"class":54,"line":5506},[52,25949,25950],{},"    $meeting = new Meeting();\n",[52,25952,25953],{"class":54,"line":4},[52,25954,25955],{},"    $meeting->name=$request->yourname;\n",[52,25957,25958],{"class":54,"line":5544},[52,25959,25960],{},"    $meeting->company_name=$request->companyname;\n",[52,25962,25963],{"class":54,"line":5561},[52,25964,25965],{},"    $meeting->email=$request->email;\n",[52,25967,25968],{"class":54,"line":5566},[52,25969,25970],{},"    $meeting->content=$request->content;\n",[52,25972,25973],{"class":54,"line":5587},[52,25974,341],{"emptyLinePlaceholder":340},[52,25976,25977],{"class":54,"line":5636},[52,25978,25979],{},"    $start = new \\DateTime($result->start_time);\n",[52,25981,25982],{"class":54,"line":5641},[52,25983,25984],{},"    $meeting->start_at=$start;\n",[52,25986,25987],{"class":54,"line":5646},[52,25988,25989],{},"    $meeting->hash=substr(base_convert(bin2hex(openssl_random_pseudo_bytes(64)),16,36),0,64);\n",[52,25991,25992],{"class":54,"line":5679},[52,25993,25994],{},"    $meeting->is_canceled=false;\n",[52,25996,25997],{"class":54,"line":5692},[52,25998,341],{"emptyLinePlaceholder":340},[52,26000,26001],{"class":54,"line":5711},[52,26002,26003],{},"    $meeting->zoom_meeting_id=$result->id;\n",[52,26005,26006],{"class":54,"line":5724},[52,26007,26008],{},"    $meeting->zoom_join_url=$result->join_url;\n",[52,26010,26011],{"class":54,"line":5745},[52,26012,26013],{},"    $meeting->zoom_start_url=$result->start_url;\n",[52,26015,26016],{"class":54,"line":5750},[52,26017,26018],{},"    $meeting->zoom_password=$result->password;\n",[52,26020,26021],{"class":54,"line":5786},[52,26022,26023],{},"    $meeting->save();\n",[52,26025,26026],{"class":54,"line":5836},[52,26027,341],{"emptyLinePlaceholder":340},[52,26029,26030],{"class":54,"line":5862},[52,26031,26032],{},"    $format = $start->format('Y年m月d日 H時i分');\n",[52,26034,26035],{"class":54,"line":5867},[52,26036,26037],{},"    \u002F\u002F $meeting->start_at = $format;\n",[52,26039,26040],{"class":54,"line":5872},[52,26041,26042],{},"    \u002F\u002F $mail = new ContactMail($meeting);\n",[52,26044,26045],{"class":54,"line":5893},[52,26046,26047],{},"    \u002F\u002F Mail::to($request->email)->send($mail);\n",[52,26049,26050],{"class":54,"line":5914},[52,26051,341],{"emptyLinePlaceholder":340},[52,26053,26054],{"class":54,"line":5927},[52,26055,341],{"emptyLinePlaceholder":340},[52,26057,26058],{"class":54,"line":5932},[52,26059,26060],{},"    return redirect('\u002Fconfirm')->with([\n",[52,26062,26063],{"class":54,"line":5953},[52,26064,26065],{},"        'form_id'=>$meeting->id,\n",[52,26067,26068],{"class":54,"line":5966},[52,26069,26070],{},"        'name'=>$request->yourname,\n",[52,26072,26073],{"class":54,"line":5971},[52,26074,26075],{},"        'companyname'=>$request->companyname,\n",[52,26077,26078],{"class":54,"line":5976},[52,26079,26080],{},"        'content'=>$request->content,\n",[52,26082,26083],{"class":54,"line":5981},[52,26084,26085],{},"        'start_time'=>$format\n",[52,26087,26088],{"class":54,"line":21716},[52,26089,24533],{},[52,26091,26092],{"class":54,"line":21722},[52,26093,536],{},[13,26095,26096],{},"長いですが、バリデーションからAPIのアクセスまで一通り行われています。",[1566,26098,26099],{"id":26099},"有効期限チェックとエンドポイントリクエストの作成",[13,26101,26102],{},"まずは最初の方では有効期限のチェックを行い、そしてミーティングを作成するユーザー情報を取得しています。",[42,26104,26106],{"className":13693,"code":26105,"filename":25246,"language":8577,"meta":47,"style":47},"$user = $this->checkRefresh();\n$user = auth()->user();\n\n$zoom_user = $this->me();\n\n$url = 'https:\u002F\u002Fapi.zoom.us\u002Fv2\u002Fusers\u002F'.$zoom_user->id.'\u002Fmeetings';\n$client = new \\GuzzleHttp\\Client([\n    'headers' => [\n        'Authorization' => 'Bearer '.$user->access_token,\n        'Content-Type'=>'application\u002Fjson'\n    ],\n]);\n",[49,26107,26108,26113,26118,26122,26127,26131,26136,26140,26145,26150,26155,26159],{"__ignoreMap":47},[52,26109,26110],{"class":54,"line":55},[52,26111,26112],{},"$user = $this->checkRefresh();\n",[52,26114,26115],{"class":54,"line":83},[52,26116,26117],{},"$user = auth()->user();\n",[52,26119,26120],{"class":54,"line":115},[52,26121,341],{"emptyLinePlaceholder":340},[52,26123,26124],{"class":54,"line":142},[52,26125,26126],{},"$zoom_user = $this->me();\n",[52,26128,26129],{"class":54,"line":169},[52,26130,341],{"emptyLinePlaceholder":340},[52,26132,26133],{"class":54,"line":302},[52,26134,26135],{},"$url = 'https:\u002F\u002Fapi.zoom.us\u002Fv2\u002Fusers\u002F'.$zoom_user->id.'\u002Fmeetings';\n",[52,26137,26138],{"class":54,"line":308},[52,26139,24974],{},[52,26141,26142],{"class":54,"line":318},[52,26143,26144],{},"    'headers' => [\n",[52,26146,26147],{"class":54,"line":328},[52,26148,26149],{},"        'Authorization' => 'Bearer '.$user->access_token,\n",[52,26151,26152],{"class":54,"line":337},[52,26153,26154],{},"        'Content-Type'=>'application\u002Fjson'\n",[52,26156,26157],{"class":54,"line":344},[52,26158,21252],{},[52,26160,26161],{"class":54,"line":354},[52,26162,24693],{},[13,26164,26165,26166,24484,26169,24723,26172,26175,26176,26179],{},"ミーティングの作成を行うエンドポイントは ",[49,26167,26168],{},"https:\u002F\u002Fapi.zoom.us\u002Fv2\u002Fusers\u002F{zoom_user_id}\u002Fmeetings",[49,26170,26171],{},"{zoom_user_id}",[49,26173,26174],{},"me","で取得した",[49,26177,26178],{},"user_id","（連携したzoomアカウントのuser id）を挿入します。そしてリクエストヘッダーを付けてひとまず、GuzzleHttpのインスタンスを作成します。",[1566,26181,26183],{"id":26182},"ミーティングパスワードを設定してapiへリクエスト","ミーティングパスワードを設定してAPIへリクエスト",[42,26185,26187],{"className":13693,"code":26186,"filename":25246,"language":8577,"meta":47,"style":47},"$topic = $request->companyname.' '.$request->yourname.'様 ご相談';        \n$meeting_password = substr(base_convert(bin2hex(openssl_random_pseudo_bytes(9)),16,36),0,9);\n$res = $client->request('POST',$url,[\n    \\GuzzleHttp\\RequestOptions::JSON => [\n        'topic'=>$topic,\n        'type'=>2,\n        'start_time'=>$request->startAt,\n        'password'=>$meeting_password\n    ]\n]);\n$result = json_decode($res->getBody()->getContents());\n",[49,26188,26189,26194,26199,26204,26209,26214,26219,26224,26229,26233,26237],{"__ignoreMap":47},[52,26190,26191],{"class":54,"line":55},[52,26192,26193],{},"$topic = $request->companyname.' '.$request->yourname.'様 ご相談';        \n",[52,26195,26196],{"class":54,"line":83},[52,26197,26198],{},"$meeting_password = substr(base_convert(bin2hex(openssl_random_pseudo_bytes(9)),16,36),0,9);\n",[52,26200,26201],{"class":54,"line":115},[52,26202,26203],{},"$res = $client->request('POST',$url,[\n",[52,26205,26206],{"class":54,"line":142},[52,26207,26208],{},"    \\GuzzleHttp\\RequestOptions::JSON => [\n",[52,26210,26211],{"class":54,"line":169},[52,26212,26213],{},"        'topic'=>$topic,\n",[52,26215,26216],{"class":54,"line":302},[52,26217,26218],{},"        'type'=>2,\n",[52,26220,26221],{"class":54,"line":308},[52,26222,26223],{},"        'start_time'=>$request->startAt,\n",[52,26225,26226],{"class":54,"line":318},[52,26227,26228],{},"        'password'=>$meeting_password\n",[52,26230,26231],{"class":54,"line":328},[52,26232,16060],{},[52,26234,26235],{"class":54,"line":337},[52,26236,24693],{},[52,26238,26239],{"class":54,"line":344},[52,26240,25175],{},[13,26242,26243,26244,26249],{},"お客さんに入力してもらう様のパスワードを生成し、そして指定したエンドポイントへPOSTします。ここでPOSTパラメーター内にミーティング設定情報をJSONで記入します。どんな値が設定できるかは",[2039,26245,26248],{"href":26246,"rel":26247},"https:\u002F\u002Fmarketplace.zoom.us\u002Fdocs\u002Fapi-reference\u002Fzoom-api\u002Fmeetings\u002Fmeetingcreate",[2043],"ここで確認","できます。",[13,26251,26252],{},"上手く想像できない方は以下のGUIで行うzoom画面を参考にするといいです",[729,26254],{":src":26255,":width":2993,":center":1323},"'_mix\u002Fsch-2021-01-10-21.02.47-768x480.png'",[13,26257,26258,26259,26262,26263,26266],{},"ここで入力できる値は全て、APIでも入力できますのでリファランスでフォーマットなど確認しながら自分なりの設定をしましょう。私の場合、まずトピックを",[49,26260,26261],{},"「{会社名}　{客名様}　ご相談」","として必ず定義しており、そこは",[49,26264,26265],{},"'topic'=>$topic,","と定義してます。",[13,26268,26269],{},"start_timeは開催日時であり、フォームで入力された値を入れています。passwordは念のため付与しています。そしてAPIをリクエストします。",[1566,26271,26273],{"id":26272},"api処理終了後","API処理終了後",[42,26275,26277],{"className":13693,"code":26276,"filename":25246,"language":8577,"meta":47,"style":47},"$result = json_decode($res->getBody()->getContents());\n\n$meeting = new Meeting();\n$meeting->name=$request->yourname;\n$meeting->company_name=$request->companyname;\n$meeting->email=$request->email;\n$meeting->content=$request->content;\n\n$start = new \\DateTime($result->start_time);\n$meeting->start_at=$start;\n$meeting->hash=substr(base_convert(bin2hex(openssl_random_pseudo_bytes(64)),16,36),0,64);\n$meeting->is_canceled=false;\n\n$meeting->zoom_meeting_id=$result->id;\n$meeting->zoom_join_url=$result->join_url;\n$meeting->zoom_start_url=$result->start_url;\n$meeting->zoom_password=$result->password;\n$meeting->save();\n\n$format = $start->format('Y年m月d日 H時i分');\n\u002F\u002F $meeting->start_at = $format;\n\u002F\u002F $mail = new ContactMail($meeting);\n\u002F\u002F Mail::to($request->email)->send($mail);\n\n\nreturn redirect('\u002Fconfirm')->with([\n    'form_id'=>$meeting->id,\n    'name'=>$request->yourname,\n    'companyname'=>$request->companyname,\n    'content'=>$request->content,\n    'start_time'=>$format\n]);\n",[49,26278,26279,26283,26287,26292,26297,26302,26307,26312,26316,26321,26326,26331,26336,26340,26345,26350,26355,26360,26365,26369,26374,26379,26384,26389,26393,26397,26402,26407,26412,26417,26422,26427],{"__ignoreMap":47},[52,26280,26281],{"class":54,"line":55},[52,26282,25175],{},[52,26284,26285],{"class":54,"line":83},[52,26286,341],{"emptyLinePlaceholder":340},[52,26288,26289],{"class":54,"line":115},[52,26290,26291],{},"$meeting = new Meeting();\n",[52,26293,26294],{"class":54,"line":142},[52,26295,26296],{},"$meeting->name=$request->yourname;\n",[52,26298,26299],{"class":54,"line":169},[52,26300,26301],{},"$meeting->company_name=$request->companyname;\n",[52,26303,26304],{"class":54,"line":302},[52,26305,26306],{},"$meeting->email=$request->email;\n",[52,26308,26309],{"class":54,"line":308},[52,26310,26311],{},"$meeting->content=$request->content;\n",[52,26313,26314],{"class":54,"line":318},[52,26315,341],{"emptyLinePlaceholder":340},[52,26317,26318],{"class":54,"line":328},[52,26319,26320],{},"$start = new \\DateTime($result->start_time);\n",[52,26322,26323],{"class":54,"line":337},[52,26324,26325],{},"$meeting->start_at=$start;\n",[52,26327,26328],{"class":54,"line":344},[52,26329,26330],{},"$meeting->hash=substr(base_convert(bin2hex(openssl_random_pseudo_bytes(64)),16,36),0,64);\n",[52,26332,26333],{"class":54,"line":354},[52,26334,26335],{},"$meeting->is_canceled=false;\n",[52,26337,26338],{"class":54,"line":367},[52,26339,341],{"emptyLinePlaceholder":340},[52,26341,26342],{"class":54,"line":387},[52,26343,26344],{},"$meeting->zoom_meeting_id=$result->id;\n",[52,26346,26347],{"class":54,"line":415},[52,26348,26349],{},"$meeting->zoom_join_url=$result->join_url;\n",[52,26351,26352],{"class":54,"line":427},[52,26353,26354],{},"$meeting->zoom_start_url=$result->start_url;\n",[52,26356,26357],{"class":54,"line":435},[52,26358,26359],{},"$meeting->zoom_password=$result->password;\n",[52,26361,26362],{"class":54,"line":446},[52,26363,26364],{},"$meeting->save();\n",[52,26366,26367],{"class":54,"line":480},[52,26368,341],{"emptyLinePlaceholder":340},[52,26370,26371],{"class":54,"line":509},[52,26372,26373],{},"$format = $start->format('Y年m月d日 H時i分');\n",[52,26375,26376],{"class":54,"line":539},[52,26377,26378],{},"\u002F\u002F $meeting->start_at = $format;\n",[52,26380,26381],{"class":54,"line":547},[52,26382,26383],{},"\u002F\u002F $mail = new ContactMail($meeting);\n",[52,26385,26386],{"class":54,"line":553},[52,26387,26388],{},"\u002F\u002F Mail::to($request->email)->send($mail);\n",[52,26390,26391],{"class":54,"line":559},[52,26392,341],{"emptyLinePlaceholder":340},[52,26394,26395],{"class":54,"line":564},[52,26396,341],{"emptyLinePlaceholder":340},[52,26398,26399],{"class":54,"line":569},[52,26400,26401],{},"return redirect('\u002Fconfirm')->with([\n",[52,26403,26404],{"class":54,"line":1106},[52,26405,26406],{},"    'form_id'=>$meeting->id,\n",[52,26408,26409],{"class":54,"line":1135},[52,26410,26411],{},"    'name'=>$request->yourname,\n",[52,26413,26414],{"class":54,"line":1164},[52,26415,26416],{},"    'companyname'=>$request->companyname,\n",[52,26418,26419],{"class":54,"line":1193},[52,26420,26421],{},"    'content'=>$request->content,\n",[52,26423,26424],{"class":54,"line":1200},[52,26425,26426],{},"    'start_time'=>$format\n",[52,26428,26429],{"class":54,"line":1205},[52,26430,24693],{},[13,26432,26433],{},"$resultにzoomからのレスポンスがあるので適宜Meetingモデルやメールに格納します。そして最後にユーザーは確認画面が表示されます。",[17,26435,26436],{"id":26436},"自分のアカウントで実験",[13,26438,26439],{},"では実験してみます。連携した管理者のzoomアカウントでミーティング一覧をみています。リクエスト前はこの様に何もありません。",[729,26441],{":src":26442,":width":732},"'_mix\u002Fzoom_meeting_index-768x322.jpeg'",[13,26444,26445],{},"そこでフォームにこの様に入力していきます。（今回は管理者自身が入力）",[729,26447],{":src":26448,":width":732},"'_mix\u002Fsch-2021-01-10-21.10.26-768x699.png'",[13,26450,26451],{},"そして送信を押すとちょっとロードして、こちらの画面にリダイレクトされます。",[729,26453],{":src":26454,":width":732},"'_mix\u002Fzoom_confirm-768x902.jpeg'",[13,26456,26457],{},"そして先ほどのzoom一覧を見てみると、",[729,26459],{":src":26460,":width":732},"'_mix\u002Fzoom_meeting_create-768x412.jpeg'",[13,26462,26463],{},"JUNE様ですね。きちんとミーティングが作られています。（時間がずれているのはコンテナ側のタイムゾーンの設定をすっかり忘れていたからです。)そしてテーブルを見てみると",[729,26465],{":src":26466,":width":732},"'_mix\u002Fsch-2021-01-10-21.20.37-768x75.png'",[13,26468,26469],{},"きちんと作られていました。zoom_join_urlの値にアクセスすると",[729,26471],{":src":26472,":width":732},"'_mix\u002Fzoom_url_test-768x467.jpeg'",[13,26474,26475],{},"管理者が直接言っているのでミーティングの開始となっていますが、きちんと有効なミーティングURLを取得し保存できています。本来であればメールでお客様にこのURLとパスワードをお知らせします。",[13,26477,26478],{},"また管理画面では",[729,26480],{":src":26481,":width":732},"'_mix\u002Fsch-2021-01-10-21.24.07-768x143.png'",[13,26483,26484,26485,26488,26489,26492,26493,26496],{},"この様にして一覧で確認ができます。ちなみに「ミーティングを削除」などのボタンは",[49,26486,26487],{},"http:\u002F\u002Flocalhost:9000\u002Fform\u002Fdelete?hash=cgckkwc040okko..","へリンクされています。",[49,26490,26491],{},"form\u002Fdelete","でミーティングを削除する確認画面へ飛べます。そして照合のために",[49,26494,26495],{},"hash=cgckkwc040okko..","の値を用いています。（途中にあった$hashの値です。）",[13,26498,26499],{},"お客様が匿名であり、ユーザーセッションによる識別ができない時は、予測困難なハッシュ付きURLをお客様だけのメールに渡してミーティングの制御が可能です。",[17,26501,13193],{"id":13193},[13,26503,26504],{},"以上がアプリの実装の流れです。今回作成したOAuth認証zoom アプリはプライベートなので作った本人しか今は利用できませんが、しっかり実装して審査を受けることでマーケットプレイスに出店して自由に使用してもらうことが可能になります。",[13,26506,26507,26508,26512],{},"OAuthも意外と簡単でしたが、公式のAPIリクエストライブラリが出ているわけでないので本格的な開発の際には独自のzoom APIライブラリを作っておくといいかもしれません。zoomでできることはこれだけでないので、もっと色々調べてみようと思います。ひとまず今回のzoomアプリはここまでとします。",[2039,26509,26511],{"href":24085,"rel":26510},[2043],"一応リポジトリに公開してある","のでぜひクローンして遊んでみてください。",[1499,26514,26516],{"id":26515},"追記-2021-3-31","追記 2021 3 31",[13,26518,26519],{},"なんか、これぐらいの規模で特定のアプリでの利用であればJWTでも十分でした汗。JWT編もそのうちやろうと思います。",[1414,26521,26522],{},"html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}",{"title":47,"searchDepth":115,"depth":115,"links":26524},[26525,26533,26536,26537,26546,26551,26555,26556,26557,26565,26566],{"id":23944,"depth":83,"text":23945,"children":26526},[26527,26531,26532],{"id":23960,"depth":115,"text":23960,"children":26528},[26529,26530],{"id":23966,"depth":142,"text":23967},{"id":23985,"depth":142,"text":23986},{"id":24007,"depth":115,"text":24007},{"id":24036,"depth":115,"text":24037},{"id":24043,"depth":83,"text":24043,"children":26534},[26535],{"id":24055,"depth":115,"text":24055},{"id":24073,"depth":83,"text":24073},{"id":24123,"depth":83,"text":24124,"children":26538},[26539,26540],{"id":24127,"depth":115,"text":24128},{"id":24164,"depth":115,"text":24164,"children":26541},[26542,26543,26544,26545],{"id":24167,"depth":142,"text":24167},{"id":24232,"depth":142,"text":24232},{"id":24241,"depth":142,"text":24242},{"id":24254,"depth":142,"text":24254},{"id":24263,"depth":83,"text":24264,"children":26547},[26548,26549,26550],{"id":24270,"depth":115,"text":24271},{"id":24324,"depth":115,"text":24325},{"id":24456,"depth":115,"text":24456},{"id":24473,"depth":83,"text":24474,"children":26552},[26553,26554],{"id":24477,"depth":115,"text":24477},{"id":24765,"depth":115,"text":24766},{"id":25239,"depth":83,"text":25239},{"id":25358,"depth":83,"text":25358},{"id":25492,"depth":83,"text":25493,"children":26558},[26559,26560],{"id":25502,"depth":115,"text":25503},{"id":25744,"depth":115,"text":25745,"children":26561},[26562,26563,26564],{"id":26099,"depth":142,"text":26099},{"id":26182,"depth":142,"text":26183},{"id":26272,"depth":142,"text":26273},{"id":26436,"depth":83,"text":26436},{"id":13193,"depth":83,"text":13193,"children":26567},[26568],{"id":26515,"depth":115,"text":26516},[1424],"2025-08-14",{},"\u002Farticles\u002Fzoom-api-laravel",{"title":23919,"description":23919},"articles\u002Fzoom-api-laravel",[9274,26576],"zoom","_mix\u002Ftop-768x463.jpeg","jNr1TCK61sAidVSCnsx9vzTvrRaOg6Bur8MSs3PYh8I",1780987132380]