技術編:ブログシステムを6年ぶりに作り直しました
このブログのシステムを、全面的に作り直しました。いま読んでいるこのページは、数日前までとは中身がまるごと別物になっています。 最初にお断りしておくと、この話は二部構成です。今回は技術の話をします。何がどう変わったのかを、実際に動くデモを交えて紹介していきます。「そもそもなぜ、noteもある時代に自分でブログを作り続けるのか」という思想の話は、次回にとっておきます。

このブログを開発していった歴史
このブログのシステムを、全面的に作り直しました。いま読んでいるこのページは、数日前までとは中身がまるごと別物になっています。 最初にお断りしておくと、この話は二部構成です。今回は技術の話をします。何がどう変わったのかを、実際に動くデモを交えて紹介していきます。「そもそもなぜ、noteもある時代に自分でブログを作り続けるのか」という思想の話は、次回にとっておきます。
やっぱりテクノロジー系のブログの評価指標ははてブかなと思うので。 ブックマークボタンの追加とauthor設定、ついでに眠らせてたはてなアカウントを整備しました ボタンの作成 こちらから作成 サイズは20 x 115で作成 jsの読み込みはheadで実施 head (){ return { meta: [...], script: [ { src: " } ] } } jsが読み込まれて表示されるまでダミーの画像表示が引き伸ばされるのでdisplay:noneを追加 .hatena-bookmark-button{ display:none; } authorを追加 link: [ { rel:"author" ,href:" ] アカウント整備 以後はてブよろしくお願いします!
最近chatGPTとStable Diffusionが楽しいので、それを組み合わせて作成しました
今後講義でこのブログを使う予定なので、任意のJavaScriptコードを実行できるようにした。 {{test}} コード HTML <div> <input type="number" id="a"> <span>+</span> <input type="number" id="b"> <span>=</span> <span id="ans"></span> <br> <input type="button" value="calc" id="calc"> </div> JavaScript document.getElementById("calc").onclick = function(){ let a = parseInt(document.getElementById("a").value,10); let b = parseInt(document.getElementById("b").value,10); document.getElementById("ans").innerHTML = a+b; } cdnからライブラリを読み込んだりも可能 {{chart}} コード HTML <canvas id="myChart"></canvas> JavaScript if(typeof(script) == "undefined"){ let script = document.createElement('script'); script.src = " document.head.append(script); script.onload = function() { createChart(); }; }else{ createChart(); } function createChart(){ new Chart(document.getElementById('myChart'), { type: 'pie', data: { labels: ['A', 'B', 'C'], datasets: [{ label: '割合', data: [20,50,30], backgroundColor: [ 'rgb(255, 99, 132)', 'rgb(54, 162, 235)', 'rgb(255, 205, 86)' ], hoverOffset: 4 }] }, }); }
UA→GA4への変更に合わせてこのブログで使っているpvシステムもAPIの切り替えを行いました。
健気に毎週弱音も吐かず回り続けていたActionがある日突然動かなくなっていました。 調査したところFTP-Deploy-Actionが動かなくなっていた模様 とりあえずバージョンを確認したところ最新バージョンの4.3.3が公開されていたのでアップデートをしました。 - name: Deploy via FTP uses: SamKirkland/FTP-Deploy-Action@4.3.3 with: server: ${{ secrets.FTP_HOST }} username: ${{ secrets.FTP_USERNAME }} password: ${{ secrets.FTP_PASSWORD }} server-dir: ${{ secrets.FTP_REMOTE_ROOT }} local-dir: dist/ Settingsのkey nameが変わったようです。 ftp-server -> server //デプロイ先のホスト ftp-username -> username //デプロイ先のユーザー名 ftp-password -> password //デプロイ先のパスワード git-ftp-args: --remote-root -> server-dir //展開するサーバーのディレクトリ 展開先の指定がしやすくなったのが良いですね あとは送信中のログもわかりやすくなりました v3.0.0 #=#=# ##O#-# ##O=# # #=#=-# # -#O#- # # -=#=# # # -=O#-# # # -=O=# # # # -=O=-# # # # -=O=- # # # # -=O=- # # # # -=O=- # # # # ######################################################################## 100.0% #=#=# ##O#-# ##O=# # #=#=-# # -#O#- # # -=#=# # # -=O#-# # # -=O=# # # # -=O=-# # # # -=O=- # # # # -=O=- # # # # -=O=- # # # # -=O=- # # # # -=O=- # # # # -=O=- # # # # ############################## 42.5% ####################################################### 76.9% ############################################################# 84.9% ######################################################################## 100.0% v4.3.3 📁 Create: _nuxt/static/pls/tag/php 📁 Create: _nuxt/static/pls/tag/Pixel5 📁 Create: _nuxt/static/pls/tag/PlayCanvas 📁 Create: _nuxt/static/pls/tag/post ... 📄 Upload: _nuxt/static/pls/backnumber/2020-10/payload.js 📄 Upload: _nuxt/static/pls/backnumber/2020-11/payload.js 📄 Upload: _nuxt/static/pls/backnumber/2020-12/payload.js 📄 Upload: _nuxt/static/pls/backnumber/2021-1/payload.js ... 🔁 File replace: backnumber/2020-10/index.html 🔁 File replace: backnumber/2020-11/index.html 🔁 File replace: backnumber/2020-12/index.html 🔁 File replace: backnumber/2021-1/index.html ... 📁 Delete: _nuxt/static/****** ... ⚖️ File content is the same, doing nothing: _nuxt/app.js ⚖️ File content is the same, doing nothing: _nuxt/assets/logos/*********.svg ⚖️ File content is the same, doing nothing: _nuxt/assets/logos/5296501_*********.svg ⚖️ File content is the same, doing nothing: _nuxt/assets/logos/5296514_*********.svg ... ---------------------------------------------------------------- Making changes to 1046 files/folders to sync server state Uploading: 15.7 MB -- Deleting: 15.9 MB -- Replacing: 83.5 MB ---------------------------------------------------------------- 新規作成されるディレクトリ、変更されるファイル、変更のないファイル、削除されたファイルが検出されたあと、アップロード、リプレース、削除がそれぞれ行われます。 nuxt generate時のファイル名固定、_nuxt/static/以下のハッシュ名固定をしておくとデプロイが高速化できそうです。
この記事はmicroCMS Advent Calendar 2021 17日目の記事です。 皆さんの記事を見ていて自分も知らなかったMicroCMSの機能や使い方をしれたので、Advent Calendarはとても好きです。 個人的には@Ryusouさんの1日目の記事で、MicroCMSダッシュボードが知らない間にアップデートされていたことを知れたのが良かったです。 後はPOST入稿の際にリッチテキストをもうちょっとなんとかできると個人的に嬉しいなあ。。。
TL;DR スクロールスパイを入れたかったので、入れてみた。 もともとvue-scrolltoを入れてスムーススクロールは実装していたのだが、共存させて見たら思いのほかうまく言ってかっこよくなったので記事に残す
JAMStackでもページビュー数を取得して表示したい! ということで実装しました。 技術要素はこちら Google Analytics Reporting API v4 google-api-php-client MicroCMS patch API Google Analyticsのデータを閲覧権限で取得してきてNuxtのビルド時にごそっとpatchイベントを介してMicroCMS上で確認できるような設計にしました。 最終的にこんな感じでpv数がmicroCMSで確認できます。 あとは通常のJAMStackと同じで取得したpv数を表示する流れです。 実装: まずPHPでGoogle Analytics Reporting APIの導通をします こちらのページを参考に簡単に作れました。 そのあと、今回は記事ページのpv一覧がほしいので、このようにphpを記述しました <?php // Google API クライアントライブラリの読込み require_once __DIR__ . '/google-api-php-client/vendor/autoload.php'; // 鍵ファイルのパス $key_file_location = __DIR__ . '/service-account-credentials.json'; // ビューID $view_id = 'view_id'; // 認証処理 $client = new Google_Client(); $client->setApplicationName('sample'); $client->setAuthConfig($key_file_location); $client->setScopes([' $analytics = new Google_Service_AnalyticsReporting($client); // データを取得する期間 $dateRange = new Google_Service_AnalyticsReporting_DateRange(); $dateRange->setStartDate('2020-09-01'); $dateRange->setEndDate('today'); // 取得するメトリクスの指定 // // PV $pageviews = new Google_Service_AnalyticsReporting_Metric(); $pageviews->setExpression('ga:pageviews'); //ディメンションをpathに設定 $dimention = new \Google_Service_AnalyticsReporting_Dimension(); $dimention->setName( 'ga:pagePath' ); //部分一致"post"でページ全体をフィルタリング $filter = new \Google_Service_AnalyticsReporting_DimensionFilter(); $filter->setDimensionName( 'ga:pagePath' ); $filter->setOperator( 'PARTIAL' ); $filter->setExpressions( [ 'post' ] ); //フィルタを適用 $filters = new \Google_Service_AnalyticsReporting_DimensionFilterClause(); $filters->setFilters([$filter]); // リクエストの作成 $request = new Google_Service_AnalyticsReporting_ReportRequest(); $request->setViewId($view_id); $request->setDateRanges($dateRange); $request->setMetrics(array($pageviews)); $request->setDimensions( [ $dimention ] ); $request->setDimensionFilterClauses( $filters ); // リクエスト実行 $body = new Google_Service_AnalyticsReporting_GetReportsRequest(); $body->setReportRequests(array($request)); $reports_obj = $analytics->reports->batchGet($body); // オブジェクトを配列変数に変換 $reports_array = json_decode(json_encode($reports_obj), true); //一旦整形する $pageviewarray = $reports_array['reports'][0]['data'][rows]; $outputarray = []; //クエリパラメータ付きのパスは同一PVとして加算する for ($i = 0; $i < count($pageviewarray); $i++) { $realpath = explode("?",$pageviewarray[$i]['dimensions'][0])[0]; if(substr($realpath, -1) != '/'){ $realpath = $realpath . "/"; } if($outputarray[$realpath]['pageview'] == null){ $outputarray[$realpath]['pageview'] = intval($pageviewarray[$i]['metrics'][0]['values'][0]); }else{ $outputarray[$realpath]['pageview'] += intval($pageviewarray[$i]['metrics'][0]['values'][0]); } } //JSで扱いやすいようにフォーマット変更 $output = []; for ($l = 0; $l < count($outputarray) - 1; $l++) { if(substr(array_keys($outputarray)[$l],0,6) == "/post/"){ $obj = array('path'=>array_keys($outputarray)[$l],'id'=>rtrim(substr(array_keys($outputarray)[$l],6),'/'),'pageview'=>$outputarray[array_keys($outputarray)[$l]]['pageview']); } array_push($output,$obj); } echo json_encode($output); するといい感じにAPIができました あとはこいつをnuxt.config.jsでビルド時に記事ページにpatchします let pvobj = await axios.get("getpageview.php"); for(let pv = 0;pv<pvobj.data.length;pv++){ try { console.log("try : " + pvobj.data[pv].id); await axios.patch(' { "X-MICROCMS-API-KEY": "your_microcms_key","Content-Type":"application/json" }}) } catch (e) { console.log('Error!!') } console.log("done"); } これでmicroCMSにPV数が記録されます。 問題点: ビルドプロセスにpatch処理を入れたことで、microCMS側で設定しているGithub ActionのRepository dispatchイベントが発火され続け、Github Actionsのワークフロー一覧が大変なことになりました。 POSTイベントで自動投稿する記事があったので、API入稿もCIを走らせていましたが、今後patchを動かしていくのであればCI動いては困るので、API入稿でのCI発火は止めました。 また、このプロセス追加によってビルド時間が1分→5分くらいに伸びました そんなにリアルタイム性が求めらていないデータなので、今後Github Actionsの無料枠を逼迫するようであればPHPにそのままpatchの処理を移管してcronでも適当に週一くらいで走らせる設計に変更しようと思います。
🔒 本文は非公開です
作ったもの タグページにメタデータを持たせられるようになりました。 現在はdescriptionと画像を設定可能。 URLとかも持たせられるようにする予定。 実装 タグデータのmicroCMSプロジェクトを作成 タグページ生成時にタグデータ一覧になかったらタグを登録 タグがつけられたページ数を確認して変更があればpatchで情報を上書き for(let i=0;i<tagarr.length;i++){ generates.push({ route: '/tag/' + tagarr[i].name, payload :{"name":tagarr[i].name,"posts":tagarr[i].posts,"info":taginfomationarr[tagarr[i].name]}}) if(!configtags.includes(tagarr[i].name)){ await axios.post(' { "X-WRITE-API-KEY": "xxxxxxxxxx","Content-Type":"application/json" }}) }else if(taginfomationarr[tagarr[i].name].postsnum != tagarr[i].posts.length){ await axios.patch(' { "X-WRITE-API-KEY": "xxxxxxxxxx","Content-Type":"application/json" }}) } } infoデータが有ればタグページ上部に表示 <div class="text-center" v-bind:style="'color : #'+ string_to_utf8_hex_string(name+name+name+name+name).substr(name.length,6) + ';'"> <h1>#{{name}}</h1> <p class="text-muted" v-if="info.description">{{info.description}}</p> </div> <div v-if="info.icon" class="d-flex justify-content-center"> <b-img v-bind:src="info.icon.url" style="width:30%;min-width: 200px;"></b-img> </div> 使うかわからないけど、今後学校の授業とかで長期で資料とか載せたい場合とかでも いい感じに使えるようになってきた。
パスワードを知らないと読めない記事を作りました。
🔒 本文は非公開です
これは非公開記事です
ようやくつけました。めっちゃめんどくさかった 最終的なymlはこんな感じ # This is a basic workflow to help you get started with Actions name: CI # Controls when the action will run. Triggers the workflow on push or pull request # events but only for the master branch on: push: branches: [ master ] repository_dispatch: types: [trigger] # A workflow run is made up of one or more jobs that can run sequentially or in parallel jobs: # This workflow contains a single job called "build" build: # The type of runner that the job will run on runs-on: ubuntu-latest # Steps represent a sequence of tasks that will be executed as part of the job steps: # Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it - name: Checkout 🛎️ uses: actions/checkout@v2 - name: setup node uses: actions/setup-node@v1 with: node-version: '10.x' - name: Cache dependencies uses: actions/cache@v1 with: path: ~/.npm key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }} restore-keys: | ${{ runner.os }}-node- - name: install run: npm install . - name: generate run: npm run generate - name: Commit & Push changes uses: actions-js/push@master with: github_token: ${{ secrets.GITHUB_TOKEN }} branch: 'master' - name: Deploy via FTP uses: SamKirkland/FTP-Deploy-Action@3.0.0 with: ftp-server: ${{ secrets.FTP_HOST }} ftp-username: ${{ secrets.FTP_USERNAME }} ftp-password: ${{ secrets.FTP_PASSWORD }} git-ftp-args: --remote-root ${{ secrets.FTP_REMOTE_ROOT }} local-dir: dist/ - name: get title if: ${{ github.event.client_payload.type == 'new'}} id: myRequest uses: fjogeleit/http-request-action@master with: url: " github.event.client_payload.id }}" method: GET customHeaders: '{"X-API-KEY": "${{ secrets.X_API_KEY }}"}' - name: tweet if: ${{ github.event.client_payload.type == 'new'}} run: curl -X POST -H "Content-Type:application/json" -d '{"value1":"${{ fromJson(steps.myRequest.outputs.response).title }}","value2":" ${{ secrets.IFTTT_URL }} ちょうど空きができたので、tweetの部分はIFTTTでアプレットを作成(Twitterのアプリ登録めんどいしね) 投稿されたidを取得するのは簡単だったけど、それを利用してtitleを取得してくるのがだるかった。。。 忘備録: idを指定したstepはsteps.[step_id].outputsで取得可能 JSON.parseはfromJson()でいける 事前にcurlしてそれを利用したい場合、このactionが良かった sawa-zenさんありがとうございました!
Qiitaとかである記事のタイトルなんかをOGPに入れ込んでシェアした段階でわかりやすくなるあれです 動的OGPを作成する手続きとしてはいくつかあるんですが、大きく分けでサーバーサイドで画像を作成するもの、image CDNを利用していい感じに作ってもらうものの2種類があります。 VOXELCANVASなんかはモデル作成後にphpで画像化してimage CDNに保存して、最後にimage CDNでロゴとblendするというハイブリッドな方法をとっています。 このブログも今まで記事ページは動的OGPを噛ませていなかったので、そういえばimgixとmicroCMSがパートナーとなったことだし、もしかしたら超簡単にできるんじゃね?ってことで挑戦してみました 手順を解説します。 まず最初に動的OGPのベースとなる画像を作成してmicroCMSにアップロードします アップロードした時点でimgixの機能が使えるようになるので、テキストなんかを自由に入力できます {{image}} あとはこれをOGPとしてNuxtで設定してあげるだけです。 head (){ return { title : this.title + " | ", meta: [ { hid: 'description', name: 'description', content: this.body.replace(/<("[^"]*"|'[^']*'|[^'">])*>/g,'').substr(0, 99)+"..." }, { hid: 'og:title', property: 'og:title', content: this.title + ' | 空想は世界を包み込む' }, { hid : 'og:image', property: 'og:image', content: " { hid: 'og:url', property: 'og:url', content: ' + this.$route.path + "/" }, { hid: 'og:description', property: 'og:description', content: this.body.replace(/<("[^"]*"|'[^']*'|[^'">])*>/g,'').substr(0, 99)+"..." }, ] } } 以上で動的OGPが設定できます。 imgix周りの環境構築をすべてmicroCMSがやってくれるのでとても楽でした。 今回は1行で実現したので楽でしたが複数行にする場合は少しややこしくなるようです。 →対応したので追記しました また、このブログでも何回か紹介しているimagekit.ioでは選べるフォントが少なめなのでまだimgixのほうが良さそうです。 2022/08/10追記:複数行に対応しました blendで複数行もワンラインでできることに気づき、対応しました。 {{imagemul}} og:imageを以下のように変更しています。 { hid : 'og:image', property: 'og:image', content: "
URLを貼り付けたらOGP情報があればそれを引っ張ってきて貼り付けるような仕様に対応しました
ある日突然CIが動かなくなった このCI自体はこのブログシステムのJAMStackアーキテクチャ内で利用していて、ブログシステムのアップデートと記事の更新によるトリガでCIが動くような仕組みになっている。 triggerと書かれたworkflow runが記事の更新によるものでソースコードについては編集を加えていないため、CIが動作しなくなるのはなにかのライブラリのはず ということで調査を行った 失敗したCIのログがこちら エラーが出ているActionはこちら エラー箇所のログはこちら Run actions-js/push@master with: github_token: *** author_email: github-actions[bot]@users.noreply.github.com author_name: github-actions[bot] branch: main directory: . Started: bash /home/runner/work/_actions/actions-js/push/master/start.sh Push to branch main [master 856abcb] chore: autopublish 2021-08-08T14:39:20Z (中略) To ! [rejected] HEAD -> main (fetch first) error: failed to push some refs to ' 原因はbranch: mainの箇所 ドキュメントを読んでみると、Inputsの中のbranchはデフォルトで'master'を参照していると書いてあったのだが、Destination branch to push changes とあるので、デフォルトブランチを参照するという意味とも取れる。ややこしい。 まだ自分の管理しているリポジトリではmasterからmainへの移行がすすんでいなかったため起きたエラーだった。以下のように修正し、問題なく動作することが確認できた。 - name: Commit & Push changes uses: actions-js/push@master with: github_token: ${{ secrets.GITHUB_TOKEN }} branch: 'master' #追加
何故かmicroCMSは埋め込みが特定のサービスしか対応していないので 独自でHTMLを書いて埋め込むのができない なので埋め込みを対応できるように自力で実装しようと思う。
やりたいこと noteみたいにURLを貼ったらogpを読み取ってリッチに表示するようにする 検討している実装手段 ビルドのタイミングでbodyを直接書き換える方法を取る。 具体的に URLを検出したら以下のライブラリを利用してogp情報を取得する Nuxt.jsでのプライグインの利用方法も忘れない 課題 microCMSではaタグが使えるのだけど、aタグで貼ったときはむりやりリッチリンク化したくないので、そこは無視するようにする。 具体的に これはリッチリンク化したいURL これはしたくないURL とりあえずやってみよう
やりたいこと ↑この上の目次をデフォルトで開いてる状態にしたい 現状想定しているやり方 まずはDOMにアクセスしないといけないので、これを試す Bootstrapのイベントで取れるようなら撮ってみる だめならJavaScript書く できた bootstrap-vueのcollapseの詳細を見て解決。 toggleの項ばっかみてた
目次を作りたい 目次とは hタグをまとめて上部にアンカーリンクを置く 要件 完成物 とりあえずhタグが全部リンク化されている状態にしたい どうやって作るか? とりあえずフルスクラッチで作ろう 具体的に bodyをデータで持ってるからそれからhタグを検索。 idはmicroCMSがどうしてるのか調査したい 見出し5 見出し5って使うの?パラグラフより小さいけど まとめ とりあえず頑張って作る 作った 作り方 正規表現でタグ切り取り <h1>-<h5>までのタグを正規表現で切り取り let reg = /<h[1-5].*?<\/h[1-5]>/g; let tables = payload.post.body.match(reg); 配列に入れる microCMSだとh1-h5は自動でidを振ってくれるのでひと手間減って便利 if(tables){ for(let i=0;i<tables.length;i++){ let obj = { "tag" : tables[i].slice(1,3), "id" : '#' + tables[i].match(/".*"/)[0].slice(1).slice(0,-1), "text" : tables[i].match(/>.*</)[0].slice(1).slice(0,-1), } t.push(obj); } } 表示 横全面だとダサいのでgridでmd=4へ いい感じ <b-container class="p-0 m-0"> <b-row> <b-col md="4"> <div class="tableofcontent bg-light p-2 mb-2" v-if="t[0]"> <b-list-group> <p class="p-1 m-0 text-center"><strong>もくじ</strong></p> <b-list-group-item class="py-1" v-bind:class="'t'+table.tag" v-bind:href="table.id" v-for="table in t">{{table.text}}</b-list-group-item> </b-list-group> </div> </b-col> <b-col> </b-col> </b-row> </b-container> h1-h5でインデントをCSSでつけている .th1{ padding-left: 0.5rem !important; } .th2{ padding-left: 1.0rem !important; } .th3{ padding-left: 1.5rem !important; } .th4{ padding-left: 2.0rem !important; } .th5{ padding-left: 2.5rem !important; }結果 やったぜ
vue-highlight.jsもmarkdownitも試したがうまくいかなかったので最終手段CDNで入れたら秒でできた。 2時間返してくれ。 C++ #include <iostream> int main(int argc, char *argv[]) { /* An annoying "Hello World" example */ for (auto i = 0; i < 0xFFFF; i++) cout << "Hello, World!" << endl; char c = '\n'; unordered_map <string, vector<string> > m; m["key"] = "\\\\"; // this is an error return -2e3 + 12l; } HTML <!DOCTYPE html> <title>Title</title> <style>body {width: 500px;}</style> <script type="application/javascript"> function $init() {return true;} </script> <body> <p checked class="title" id='title'>Title</p> <!-- here goes the rest of the page --> </body> C# using System.IO.Compression; #pragma warning disable 414, 3021 namespace MyApplication { [Obsolete("...")] class Program : IInterface { public static List<int> JustDoIt(int count) { Console.WriteLine($"Hello {Name}!"); return new List<int>(new int[] { 1, 2, 3 }) } } } CSS @font-face { font-family: Chunkfive; src: url('Chunkfive.otf'); } body, .usertext { color: #F0F0F0; background: #600; font-family: Chunkfive, sans; --heading-1: 30px/32px Helvetica, sans-serif; } @import url(print.css); @media print { a[href^=http]::after { content: attr(href) } } JavaScript function $initHighlight(block, cls) { try { if (cls.search(/\bno\-highlight\b/) != -1) return process(block, true, 0x0F) + ` class="${cls}"`; } catch (e) { /* handle exception */ } for (var i = 0 / 2; i < classes.length; i++) { if (checkCondition(classes[i]) === undefined) console.log('undefined'); } return ( <div> <web-component>{block}</web-component> </div> ) } export $initHighlight; PHP require_once 'Zend/Uri/Http.php'; namespace Location\Web; interface Factory { static function _factory(); } abstract class URI extends BaseURI implements Factory { abstract function test(); public static $st1 = 1; const ME = "Yo"; var $list = NULL; private $var; /** * Returns a URI * * @return URI */ static public function _factory($stats = array(), $uri = 'http') { echo __METHOD__; $uri = explode(':', $uri, 0b10); $schemeSpecific = isset($uri[1]) ? $uri[1] : ''; $desc = 'Multi line description'; // Security check if (!ctype_alnum($scheme)) { throw new Zend_Uri_Exception('Illegal scheme'); } $this->var = 0 - self::$st; $this->list = list(Array("1"=> 2, 2=>self::ME, 3 => \Location\Web\URI::class)); return [ 'uri' => $uri, 'value' => null, ]; } } echo URI::ME . URI::$st1; __halt_compiler () ; datahere datahere datahere */ datahere CDN最高
不具合メモ タグが一文字だと表示されないバグ発見
タグに自動で色がつくようにしました。なお、同一のタグは同一の色がつくようになっています タグのコンポーネントはこんな感じ <template> <b-badge pill v-bind:style="'background-color : #'+ string_to_utf8_hex_string(tag).substr(1,6) + ';'" class="mr-2" v-bind:to="to">{{tag}}</b-badge> </template> <script> export default { props:["tag","to"], methods : { string_to_utf8_hex_string : function(text){ var bytes1 = this.string_to_utf8_bytes(text); var hex_str1 = this.bytes_to_hex_string(bytes1); return hex_str1; }, string_to_utf8_bytes : function(text){ var result = []; if (text == null) return result; for (var i = 0; i < text.length; i++) { var c = text.charCodeAt(i); if (c <= 0x7f) { result.push(c); } else if (c <= 0x07ff) { result.push(((c >> 6) & 0x1F) | 0xC0); result.push((c & 0x3F) | 0x80); } else { result.push(((c >> 12) & 0x0F) | 0xE0); result.push(((c >> 6) & 0x3F) | 0x80); result.push((c & 0x3F) | 0x80); } } return result; }, bytes_to_hex_string : function(bytes){ var result = ""; for (var i = 0; i < bytes.length; i++) { result += this.byte_to_hex(bytes[i]); } return result; }, byte_to_hex : function(byte_num){ var digits = (byte_num).toString(16); if (byte_num < 16) return '0' + digits; return digits; } } } </script> この後はワードクラウドとか作りたい
こんな感じ microCMSのcreatedAtで、年月だけを抽出して記事を作成する仕組みを作った これで、タグ機能、バックナンバー機能とブログとしての体裁は整ったかな 最近は、Github Actionsのビルド時間が長いことが悩み。一回大体6分くらい ほぼFTPでの時間がかかっているようで、悩み中 現在29記事でそのレベルなら、増えてきたら大変そう どうしようかなあ、、、まあまだGithub Actionsの無料2000分/月には遠く及ばないので、いったんは無視できるかな とりあえず記事を書き溜めていく
自分のツイートを収集してブログに残す仕組みを作りました IFTTT GAS microCMS で構成しています。 まず、Twitter APIがめんどくさそうむずかしそうだっため、IFTTTでスプレッドシートにツイート取得し続ける仕組みを構築しました。 IFTTTも最近有料になってしまい、ちょっと辛いです。50ほどあったレシピは2個まで絞りました。Google asistant系だけはどうしようもない で、週に一回蓄積されたスプレッドシートの内容をmicroCMSにPOSTして、tweetlogとして出力するような形にしました。 microCMS, POSTでアップするときは\nみたいな改行コードが効くのですが、HTMLを直接流し込むとすべてがエスケープシーケンスにされてしまい困りました。解決策あれば教えてください。 ひとまず週一回のロギングで運用開始。問題なければ毎週日曜のお昼にツイートログが出力されます。 こうご期待!
概要 EMC2NARY(イマジナリー)は、AI・Visualization・Human Computer Interaction(HCI)を中心に、ソフトウェアアーキテクチャやプロダクト開発、日常から得た気付きを記録する個人ブログです。 技術だけでなく、「人はどう理解し、どう考え、どう意思決定するのか」というテーマに興味があります。 AIシステムの設計、データ可視化、WebGL、ゲーム開発、UX、子育てや日常の出来事まで、一見関係のないテーマも、自分なりの視点で構造化しながら書いています。 このブログは、自分の思考を整理し、未来の自分への忘備録として、そして誰かの新しい発想のきっかけになればという思いで続けています。 ブログへの感想やコメントは、各記事のはてなブックマークコメントへお気軽にお寄せください。 技術スタック このブログは Astro による静的サイトとして構築し、ビルド時にすべてを解決する「JSをできるだけ届けない」思想で組んでいます。2020年に Nuxt 2 で作ったものを、2026年にAstroで全面刷新しました。 Frontend : Astro CMS : microCMS Architecture : Jamstack / Islands Architecture Search : Pagefind Math : KaTeX Code Highlight : Shiki OGP Generator : Satori CI/CD : GitHub Actions Hosting : lolipop! Preview : Cloudflare Workers Server Logic : PHP Analytics : GA4 PV Counter : Google Analytics Data API Auto Post : IFTTT EMC2NARYという名前について EMC2NARY(イマジナリー)は、 Imagination encircles the world. 「想像力は世界を包み込む」 という考えから名付けました。 名前は、アインシュタインの有名な言葉 Imagination is more important than knowledge. Knowledge is limited. Imagination encircles the world. に着想を得ています。 「想像力は知識よりも重要である」という言葉そのものよりも、「自分にしかできない発想を大切にしたい」という考え方に強く惹かれました。 知識は調べれば手に入ります。 しかし、新しい視点や発想は、自分自身が考え続けることでしか生まれません。 EMC2NARYという名前は、アインシュタインのエネルギー方程式 E = mc² と Imaginary を組み合わせた造語です。 「空想(Imaginary)が世界(E=mc²)を包み込む」 そんな意味を込めています。 シンボルマーク シンボルマークは、五角形を「手」、円を「地球」に見立てています。 この手で生み出したものが、世界へ広がっていく。 技術も、デザインも、文章も、ものづくりのすべてが誰かに届き、新しい価値につながっていく──そんな思いを表現しています。 注意事項 本ブログは個人の見解を発信する場であり、所属する組織の公式な見解を示すものではありません。 掲載している内容については十分配慮していますが、本ブログの情報を利用したことによって生じた損害等については責任を負いかねます。あらかじめご了承ください。