Hello.

I am Paul Kinlan.

A Developer Advocate for Chrome and the Open Web at Google.

Puppeteer Go🔗

Paul Kinlan

Puppeteerが大好きですThe Headless WebのアイデアでThe Headless WebでThe Headless Web -目に見えるブラウザーのないブラウザーでWebを実行し、さらにDOM-curl (JavaScriptを実行するCurl)のようなツールを作成します。具体的には、ページをスクレイピング、操作、および対話するためのブラウザのスクリプト作成が大好きです。

私が作りたかったデモの1つは、IreのCapturing 422 live images投稿に触発され、そこで彼女は多くのページにナビゲートしてスクリーンショットを撮る操り人形スクリプトを実行しました。多くのページに行く代わりに、ページ上の要素のスクリーンショットをたくさん撮りたかった。

私がPuppeteerで抱えている問題は、あなたが何でもする必要がある最初のスタンザです。起動、タブを開く、ナビゲート-複雑ではなく、単純なスクリプト用に作成するよりも定型的なだけです。それがPuppeteer Goを作成したPuppeteer Goです。これは、ブラウザを開いてページにナビゲートし、_your_アクションを実行し、その後自動的にクリーンアップするCLIユーティリティを簡単に作成するのに役立つ小さなスクリプトです。

見てみな。

const { go } = require('puppeteer-go');

go('https://paul.kinlan.me', async (page) => {
    const elements = await page.$$("h1");
    let count = 0;
    for(let element of elements) {
      try {
        await element.screenshot({ path: `${count++}.png`});
      } catch (err) {
        console.log(count, err);
      }
    }
});

上記のコードは私のブログでh1要素を見つけ、スクリーンショットを撮ります。これはIreの仕事ほど優れたものではありませんが、canisuse.comからページから直接スクリーンショットをすばやく取得できるかどうかを確認するのは素晴らしいと思いました。

const { go } = require('puppeteer-go');

go('https://caniuse.com/#search=css', async (page) => {
    const elements = await page.$$("article.feature-block.feature-block--feature");
    let count = 0;
    for(let element of elements) {
      try {
        await element.screenshot({ path: `${count++}.png`});
      } catch (err) {
        console.log(count, err);
      }
    }
});
4.png
3.png
2.png
1.png
0.png

楽しい!

Paul Kinlan

Trying to make the web and developers better.

RSS Github Medium

A simple video insertion tool for EditorJS🔗

Paul Kinlan

私はEditorJS本当に好きEditorJS 。静的なHugoブログ用に、非常にシンプルなWebホストインターフェイスを作成できます。

EditorJSには、単純なブロックベースのエディターで必要なものがほとんど揃っています。ヘッダー、コードのプラグインがあり、ホスティングインフラストラクチャを必要とせずにエディターに画像を追加する簡単な方法さえあります。今まで、ビデオをエディターに追加する簡単な方法はありません。

simple-imageプラグインリポジトリを取得し、それを(ほんの少し)変更してsimple-videoプラグイン( npm module )を作成しました。これで、このブログにビデオを簡単に含めることができます。

EditorJSに慣れている場合、プロジェクトに含めるのはかなり簡単です。次のようにインストールするだけです

npm i simple-video-editorjs

そして、必要に応じてプロジェクトに含めるだけです。

const SimpleVideo = require('simple-video-editorjs');

var editor = EditorJS({
  ...
  
  tools: {
    ...
    video: SimpleVideo,
  }
  
  ...
});

エディターには、ページでビデオをホストする方法を構成できるいくつかの簡単なオプションがあります。

1.自動再生-ページの読み込み時にビデオが自動的に再生されます 1.ミュート-デフォルトでは動画の音声はオンになりません(自動再生に必要) 1.コントロール-ビデオにはデフォルトのHTMLコントロールがあります。

以下は、埋め込まれたビデオの簡単な例です(オプションの一部を示しています)。

とにかく、この小さなプラグインを作成するのは楽しかったです-作成するのはそれほど難しくなく、simple-imagesが使用するbase64への変換を延期し、代わりにBlob URLを使用するだけでした。

Test post Video upload

Paul Kinlan

ここにビデオが表示されていれば、うまくいきました。

Read More

Friendly Project Name Generator with Zeit🔗

Paul Kinlan

Webでサイトを簡単に作成できるプロジェクトのアイデアがいくつかあります-アイデアの1つは、 zeitベースのプロジェクトにnetlify-like drag and drop interfaceをzeitすることです(zeitが好きですが、展開するにはちょっとしたcliマジックが必要です)。

この投稿では、プロジェクト名の作成というパズルのほんの一部を取り上げています。

Glitchはこの良い例です。プロジェクトを作成すると、ランダムに生成された気まぐれな名前が付けられます。また、チームはうまく組み合わせたgood dictionary of fairly safe wordsを作成しました(そして、必要に応じて、ホストする単純なサーバーが必要です)。

そのため、 serverless-functions日曜日のサイドプロジェクトは、Zeitのserverless-functionsとGlitchの辞書を使用して、ランダムなプロジェクト名を生成するシンプルなマイクロサービスを作成することでした。

And here it is ( code )、かなり短く、複雑すぎません。

const words = require("friendly-words");

function generate(count = 1, separator = "-") {
  const { predicates, objects } = words;
  const pCount = predicates.length;
  const oCount = objects.length;
  const output = [];

  for (let i = 0; i < count; i++) {
    const pair = [predicates[Math.floor(Math.random() * pCount)], objects[Math.floor(Math.random() * oCount)]];
    output.push(pair.join(separator));
  }

  return output;
}

module.exports = { generate }

プロジェクトに直接含めたくない場合は、HTTPエンドポイントを使用して、https:// friendly-project-nameにWebリクエストを行うことにより、ランダムなプロジェクト名(「XY」の形式)を生成できます。 kinlan.now.sh/api/names。次のようなものを返します。

["momentous-professor"]

また、https://friendly-project-name.kinlan.now.sh/api/names? count = 100などのcount = xのクエリ文字列パラメーターを使用して、生成する名前の数を制御することもできます。

["melon-tangerine","broad-jury","rebel-hardcover","far-friend","notch-hornet","principled-wildcat","level-pilot","steadfast-bovid","holistic-plant","expensive-ulna","sixth-gear","political-wrench","marred-spatula","aware-weaver","awake-pair","nosy-hub","absorbing-petunia","rhetorical-birth","paint-sprint","stripe-reward","fine-guardian","coconut-jumbo","spangle-eye","sudden-euphonium","familiar-fossa","third-seaplane","workable-cough","hot-light","diligent-ceratonykus","literate-cobalt","tranquil-sandalwood","alabaster-pest","sage-detail","mousy-diascia","burly-food","fern-pie","confusion-capybara","harsh-asterisk","simple-triangle","brindle-collard","destiny-poppy","power-globeflower","ruby-crush","absorbed-trollius","meadow-blackberry","fierce-zipper","coal-mailbox","sponge-language","snow-lawyer","adjoining-bramble","deserted-flower","able-tortoise","equatorial-bugle","neat-evergreen","pointy-quart","occipital-tax","balsam-fork","dear-fairy","polished-produce","darkened-gondola","sugar-pantry","broad-slouch","safe-cormorant","foregoing-ostrich","quasar-mailman","glittery-marble","abalone-titanosaurus","descriptive-arch","nickel-ostrich","historical-candy","mire-mistake","painted-eater","pineapple-sassafras","pastoral-thief","holy-waterlily","mewing-humor","bubbly-cave","pepper-situation","nosy-colony","sprout-aries","cyan-bestseller","humorous-plywood","heavy-beauty","spiral-riverbed","gifted-income","lead-kiwi","pointed-catshark","ninth-ocean","purple-toucan","tundra-cut","coal-geography","icy-lunaria","agate-wildcat","respected-garlic","polar-almandine","periodic-narcissus","carbonated-waiter","lavish-breadfruit","confirmed-brand","repeated-period"]

separatorのquery-stringパラメーターを使用して、セパレーターを制御できます。すなわち、separator = @、例えばhttps://friendly-project-name.kinlan.now.sh/api/names?separator=@

["handsomely@asterisk"]

このプロジェクトの非常に有用な側面は、言葉の組み合わせが不快になる傾向がある場合、Glitchレポを簡単に更新して、再び発生しないようにすることです。

プロジェクトのホスティングが高くなりすぎないと仮定して、サービスを維持しますが、同様のマイクロサービスを作成したい場合は自分でクローンを作成してください。

ライブの例

以下は、実行中のAPIの非常に簡単な例です。

const render = (promise, elementId) => {
  promise.then(async(response) => {
    const el = document.getElementById(elementId);
    el.innerText = await response.text();
  })
};


onload = () => {
  render(fetch("https://friendly-project-name.kinlan.now.sh/api/names"), "basic");
  render(fetch("https://friendly-project-name.kinlan.now.sh/api/names?count=100"), "many");
  render(fetch("https://friendly-project-name.kinlan.now.sh/api/names?separator=@"), "separator");
}

シングルレスポンス



多くの回答



カスタムセパレーター



{{<raw-html>}}

{{</ raw-html>}}

Frankie and Bennys: Pay for your meal via the web

Paul Kinlan

レストランでモバイルで支払いができると言われたときはいつでもチェックします。ほとんどの場合、アプリを使用する必要があるという事実を嘆くことができます。 QRコードがWebベースの支払いフローにつながることに驚いたことを想像してください。 素晴らしい仕事フランキーとベニーズ! この時点で、Google Payを選択しましたが、機能しませんでした(内部送信メール!) むしろ素晴らしいもので、エンドツーエンドで約1分でした。

Read More

Podroll

Paul Kinlan

私はポッドキャストが大好きで、かなりの数の曲を聴いていますが、新しいポッドキャストを見つけるのは非常に難しいプロセスであり、聴いているものを共有するために友人に頼っています。そのため、ここに私のpodrollます。私はPlayer.fmを使用していますPlayer.fmこれは、私の良き友人であるMike Mahemoffが作成し、サブスクリプションリストを共有およびエクスポートする機能があります。 このページはrefreshed frequently using my little scriptになりrefreshed frequently using my little script 。

Read More

Adding "dark mode" to my blog

Paul Kinlan

Jeremy Keithのpost about adding dark mode to his blogを見て、簡単そうに見えたので、それを旋回することにしました。 すべてを見るためのdiff of the workは次のdiff of the workです。それは驚くほど簡単でした(私の側のばかげたエラー以外)。 CSS変数をサポートするための小さなリファクタリングがあり、CSSカスタムプロパティをサポートしないブラウザーがある場合にフォールバックできるようにしましたが、それはそれに関するものです。私はジェレミーがやったのとほとんど同じことをしました。 Chromeには、設定されているダークモード( I hear it’s coming )をエミュレートするDevToolsのサポートがなかったため、HTML要素に追加して動作を簡単にテストできる単純なCSSクラスを作成しました(以下を参照)。 @media (prefers-color-scheme: dark) { html { --background-color: rgb(36, 36, 36); --text-color: #fefefe; --block-quote-before-color: #333; --link-color-visited: #7ad857; --post-shadow: #333; } .post.moi a[rel=me] img { filter: invert(0.8); } } html.dark { --background-color: rgb(36, 36, 36); --text-color: #fefefe; --block-quote-before-color: #333; --link-color: #1bcba2; --link-color-visited: #7ad857; --post-shadow: #333; } html.dark .post.moi a[rel=me] img { filter: invert(0.

Read More

Using Web Mentions in a static site (Hugo)

Paul Kinlan

私のブログは完全に静的なサイトで、Hugoで構築され、Zeitでホストされています。これは私にとって素晴らしいソリューションです。シンプルなブログは非常にシンプルな展開プロセスを持ち、非常に高速にロードされます。 静的に生成されたサイトにはいくつかの欠点がありますが、最大のものは、動的なものをページに統合する必要がある場合です(コメントなど)。動的コンテンツを簡単にホストできないということは、最終的にはサードパーティのJavaScriptに依存してページにフルアクセスできるようになり、何をしているのかわからなくなることを意味します。ユーザーを追跡したり、ページを遅くしたりする可能性があります負荷。 私は最近、ユーザーがコメントにスクロールするときに( IntersectionObserverを使用して)ロードするだけで、現在のコメントウィジェット(Disqus)をクリティカルレンダーパスから削除しました。すべて一緒に。 Webmention仕様を入力します。 Webmentionは、別のサイトがサイト上のコンテンツに「メンション」を付けた(または「いいね!」)ときに、サイト作成者に連絡する方法を説明する仕様です。これにより、最終的にはサイトにリンクするコンテンツを発見するための分散化された方法が可能になり、できれば価値と洞察が得られます。 Webmention仕様では、「メンションサイト」が言ったことを伝えるために使用するデータ形式については説明していません。標準のマイクロフォーマットまたはページのコンテンツを理解する他のメカニズムを使用して解析する必要があります。これは素晴らしいことですが、 webmention.ioなどの集中型サービスにつながり、ページから意味を引き出すために非常に必要なインフラストラクチャを提供すると考えています。 私はWebmentionを使用するというアイデアが好きでしたが、誰かがあなたのサイトに言及したときの通知を取得(そしておそらく保存)するためにサーバー側のセットアップが必要です。これは私のサイトにあるような静的ビルダーでは常に可能ではありません。この投稿の残りの部分では、ZeitがホストするHugoビルドでホストされているいいね!、言及、再投稿の方法について簡単に説明します。 ステップ1-Webmentionハブを見つける webmention.ioを見つけました。着信するpingbackとメンションを処理します。また、呼び出し元のサイトが実際にコンテンツにリンクしていることを検証し、最終的にコンテキストを理解できるようにページからデータを解析します。 Webmention.ioは、オープン認証プロセスを介してサイトを所有していることを検証します(認証プロバイダーを指すrel = meを探すのはきれいでした) ステップ###を処理できることをページに伝えます これは、次の2つのlinkタグを追加するのと同じくらい簡単です <link rel="webmention" href="https://webmention.io/paul.kinlan.me/webmention"> <link rel="pingback" href="https://webmention.io/paul.kinlan.me/xmlrpc"> ステップ3-webmention.io APIをサイトに統合します ここには2つのオプションがあります。webmention.ioAPIを呼び出すウィジェットをページに追加するか、webmention.io APIをビルドステップに統合できます。 JSをホストするサードパーティはできるだけ少なくしたいので、後者を選択しました。デプロイメントプロセスにWebmentionを統合しました。 ビルドが高速であるため、Hugoを使用します。これを念頭に置いて、Webmention APIを最適な方法でHugoに統合する方法を検討する必要がありました。厳しい制約は、サイトのすべてのページでAPIエンドポイントを呼び出さないことでした。私には多くのページがあり、まだ多くのコメントはありません。 幸いなことに、Webmention.ioサイトは、ドメインに関するすべての言及を受け取ることができる便利なエンドポイントを提供します。不幸なことに、このファイルには、サイトに対して行われたすべてのアクションに対して1つのエントリが含まれています。 Hugoには、特定のページのテンプレートに直接プルできるデータファイルの概念もあるため、Webmentionデータファイルを、Hugoテンプレート内で読みやすい新しい構造にマッピングする必要があります。 私が選択したプロセスは以下のとおりですが、要約は、アクションのリストから、APIによって公開されたアクション(再投稿や返信など)を含むURLの辞書に配列を変換し、最後のステップがURLの辞書を、URLのmd5ハッシュという名前の個々のファイルに分割します。 "use strict"; const fs = require('fs'); const fetch = require('node-fetch'); const md5 = require('md5'); const processMentionsJson = (data) => { const urlData = {}; data.children.forEach(item => { const wmProperty = item["wm-property"]; const url = item[wmProperty]; if(url in urlData === false) urlData[url] = {}; const urlDataItem = urlData[url]; if(wmProperty in urlDataItem === false) urlDataItem[wmProperty] = []; urlDataItem[wmProperty].

Read More

Creating a pop-out iframe with adoptNode and "magic iframes"

Paul Kinlan

更新:10月8日-このドキュメントに関する重要な問題。 私はこの投稿についてJake Archibaldに追いつきました。何か新しいものがあると思ったからです。会話の中で、この投稿の一部を無効にする多くのことを発見しました。また、ほとんどの開発者が知っています。 .append()と.appendChild()を呼び出すと、ノードが採用されます。これにより、append Algorithmによりノードが確実に採用されるため、このインスタンスでのadoptNodeの使用は役に立たなくなります。これはMDNドキュメントでは言及されていませんでしたが、 specます。前に戻って問題を解決する必要があるのですが、 DocumentFragmentを追加しようとしていたためだとDocumentFragmentます。これは、 w.document.body.appendChild(document.adoptNode(airhornerIframe));とw.document.body.appendChild(airhornerIframe);両方が同じ効果を持つことを意味します。 DOM要素は状態を保持しますが(カスタム要素を確認します)、iframeがDOM内で移動されると、再読み込みされます。期間。つまり、iframe間で移動しても、最初にテストしたような状態が維持されないということです。これは、SWがページを非常に速くロードしたためだと思います。ポータルAPIはこれによる影響を受けない可能性があります。したがって、将来このエクスペリエンスは機能するはずです:) ドキュメント間で要素を移動するという概念は依然として有効で興味深いものですが、iframeの利点はそこにはありません。ウィンドウ間を移動するとビデオ要素がリセットされることに気づきました。iframeが実際に状態をリセットしなかったことを確認するためにもっと熱心にすべきだったはずです。 いつものように、 commit history for this postを見ることができます。 元の投稿2010年にGoogleに入社したとき、「 magic iframes 」というGmailの概念に言及したドキュメントに出くわしました。クールな名前が付けられ、概念は斬新でした。 Targeted at apps with multiple windows All code and data go into an IFRAME If window hosting the IFRAME unloads, it gets adopted by another of the windows In Gmail for example: Tearoff / pop-out compose creates bare window that is filled by code in IFRAME in main window If you close the main window, the code looks for a tear-off that can accept the IFRAME and moves it You finish your compose and can still send the email Old way: create new instance of Gmail tailored to the task.

Read More

Meatspace Augmented Reality: From Chester to Nagoya

Paul Kinlan

Some thoughts on AR after finding some during my travels. TL;DR - cheaper content creation and better discovery tools are needed.

Read More

Photos from Carlisle Castle

Paul Kinlan

私は最近男の子と一緒に休暇に行き、 Carlisle Castle (世界のCarlisle Castle地ジェイク・アーチボルド)をCarlisle Castle 。これは英国で行ったより良い城の一つであり、もしあなたがその地域にいるなら、時間をかけて訪れることを心からお勧めします。カーライル城がイングランドとスコットランドの歴史で果たした役割はそれほど重要ではありませんでしたが、私たちがそこにいる間にもっと知るのは素晴らしいことでした。スコットランドから戻ったばかりの私は、Brexitがスコットランドとの将来の関係に与える可能性のある潜在的な影響について考える機会がありました。また、カーライルがどれだけ近いかを考えると、独立への新たなプッシュがあるとどうなるかを知っています。 子供がいない写真はあまり多くないので、お城から写真を散らして、何が期待できるかを考えてみました。

Read More

Idle observation: Indexing text in images

Paul Kinlan

先日、スランゴスレンの男の子たちと一緒に(それは美しい町です)、その地域の歴史のいくつかを含む情報標識の写真を撮っていたので、後で読むことができたので、標識を通り過ぎて歩いている人だけでなく情報が利用可能かどうかを確認するためにWebを使用します。その後、私はリーフレットについて考えました。それらのコンテンツはウェブ上で見つけることはほとんど不可能です。 多くの人が画像内のテキストに関心を持ち、検索エンジンや読むのに苦労しているユーザーに利用できるようにするかどうかはわかりませんが、より多くのコンテンツをウェブに持ち込み、情報へのアクセスを改善することはかなり良い勝利のようですみんな。 2018年のGoogle 4 Indiaへの旅行を振り返って、Googleはインドの雑誌や新聞の多くの出版社がオフライン専用である、つまりウェブの存在がないためにツールを作成したことをGoogleが特定したことをはっきりと覚えていますNavlekhāと呼ばれNavlekhāこれにより、テキスト検出などの多くの機能を使用して、コンテンツのPDFを簡単にWebにNavlekhāができます。やりたいことから100万マイルではありません… 私は戻って、先月書いたextract text from imagesツールを使用して、情報サインのある画像でどのように機能するかを確認しました(1時間のプロジェクトの場合)。見てみな。 サインの写真を集めただけの小さなサイトを立ち上げて、アクセスしやすく索引付け可能な形式でテキストを配置したいと思っています。 ローマ庭園 南北戦争の要塞 1642年から1646年の間、イングランドは王党派と国会議員の間の内戦によって引き裂かれました。危機にissuesした問題は基本的なものでした。彼らは国王と議会のそれぞれの権利と役割、そして王国の宗教的アイデンティティについてでした。この激しい紛争により、誰もが陣営を選択せざるを得なくなり、王国が分裂し、家族も分裂しました。 王党派と国会議員は、チェスターのような大都市の支配に苦労しました。なぜなら、彼らはお金、食糧、武器、人材などの重要な資源が豊富だったからです。チェスターは、戦略的な場所と優れた防御のため、特に重要な都市でした。イングランド北西部で最大の要塞であり、北ウェールズ、スコットランド、アイルランドへのルートを制御していました。 キングが去ったので、ブレトンは戻ってチェスターを捕まえる努力を倍増した。彼の砲手は都市を容赦なく砲撃し、あなたの左の望楼で、あなたはまだ彼らの砲弾が家にぶつかった跡を見ることができます。 バイロンはあきらめることを拒否し、王党派は反撃を続けましたが、彼らの立場は絶望的でした。チェスターの食料は断ち切られ、兵士と町民は飢え死にかけていました。バイロンはついにチェスターをブレトンに降伏させた。ブレトンは1646å¹´2月3日に市を支配した。 チェスターは王党派の拠点でしたが、1644年までにチェシャーの大部分は議会に支配され、議会は市を包囲しました。紛争の中心には、非常に異なる2人の男性がいました。チェスターの王党派守備隊は、バイロンLord-戦いで傷ついた顔をしたタフなキャバリアー、ジョンJohnによって指揮されました。議員たちはウィリアム・ブレトンirが率いていました。ウィリアム・ブレトンirは、チェシャーの王党派運動を弱体化させるためにスパイのネットワークを構築した熱心なピューリタンです。バイロンとブレトンは、ミドルウィッチの戦い(王党派が勝利)とナントウィッチ(議会派が勝利)ですでに衝突していました。 この時までに、最初の英国内戦の終わりは間近でした。議会は現在、北西とウェールズ北部へのアプローチを管理しており、チャールズがアイルランドから援軍を派遣することを妨げていた。 Kinngは1646å¹´5月5日に降伏しました。 1645å¹´9月、チャールズ王は4,000の騎兵隊でチェスターへの救助任務を率いたが、9月24日の近くのロートンムーアの戦いで彼の軍隊は敗北した。翌日、チャールズはウェールズに逃げ、チェスターをその運命に任せた。 Saliety、ロンドン チェスターの包囲、1645å¹´9月〜12月

Read More

Liverpool World Museum

Paul Kinlan

先週、子供たちをリバプール世界歴史博物館に連れて行きました。 空間と時間のセクションは約30年も変わっておらず、バグエンクロージャーの大きなセクションが閉じられており、水族館は私が覚えているよりも少し小さく見えました。エジプトのセクションは開いていて(最後に行ったときではありませんでした)、かなりすばらしかったです。

Read More

Bookstore - Llangollen

Paul Kinlan

私はこの場所が大好きです。スランゴスレンのカフェの上にあります。私は30年近く前に祖父母とここに来ましたが、今ではほとんど同じです。 私の唯一の願いは、さらに多くのCommixの本があったことです。子供の頃はもっと山があったと断言します。 Check it out

Read More

Webmention.app🔗

Paul Kinlan

私はWebmentionsのアイデアが大好きですが、それを自分のサイトに実装する時間がありませんでした。高レベルのWeb言及では、Web上の他のコンテンツにコメント、いいね、返信したり、Disqus(私のサイトから削除したい)などのツールで一元化することなく、そのコンテンツに表示することができます。

Webメンションは、送信者と受信者の2つのコンポーネントに分割されます。受信者は、私が投稿を書いているサイトであり、彼らのサイトには、インバウンドリンクやブログへの反応を示す何かがあるかもしれません。そして、送信者は私です。私が作成したリモートサイトに、作成したコンテンツに反応させる必要があります。

かなり素晴らしいRemy Sharpがwebmention.appを作成して、問題の一部を解決しましたwebmention.app送信です。 Remyのツールを使用すると、CLIスクリプトを呼び出すだけで、リンクした潜在的な受信者に「ping」を簡単に送信できます。

私は、Hugoと静的ビルダーツールを使用してZeitを使用してブログをホストしているため、 relatively trivial for me to add in support for webmention app 。 npm i webmentionだけをnpm i webmentionしてから、 build.shファイルからCLIバージョンのツールをbuild.shます。これは本当に簡単です。

投稿を作成すると、サイトに関するコンテンツを作成したすべての新しいURLに簡単なpingが送信されます。

Creating a commit with multiple files to Github with JS on the web

Paul Kinlan

私のサイトはentirely staticです。 Hugoで構築され、 HugoでホストされていZeit 。私はセットアップにかなり満足しています。インスタントビルドと超高速CDNによるコンテンツ配信に近づき、状態を管理する必要がないため、必要なすべてのことを実行できます。 このサイトのpodcast creatorと、静的にホストされたサイトに新しいコンテンツをすばやく投稿できるsimple UIを作成しました。 そう。どうやってやったの? これは、私のGithub Repoに対するFirebase Auth、コンテンツを編集するEditorJS(それはきちんとしている)、およびリポジトリにコミットするOctokat.jsと、Hugoビルドを行うZeitのGithub統合の組み合わせです。この設定により、ユーザーがWordpressのようなCMSを使用したデータベースに投稿を作成する方法と同様に、完全に自己ホスト型の静的CMSを作成できます。 この投稿では、インフラストラクチャの一部に焦点を当てるだけです。Githubに複数のファイルをコミットするのは、少し時間がかかったためです。 コード全体は、私のrepoで見ることができます。 Githubに直接コミットする必要があるWeb UIを構築している場合、私が見つけた最高のライブラリはOctokatです-CORSで動作し、Github APIのAPI面全体を処理するようです。 Gitは、ツリー、ブランチ、その他の部分がどのように機能するかを理解することになると、複雑な獣になる可能性があるので、それを簡単にする決定をしました。 heads/masterとして知られるmasterブランチにのみプッシュできます。 1.特定のファイルが保存される場所がわかります(Hugoは特定のディレクトリ構造を強制します) それを念頭に置いて、複数のファイルでコミットを作成する一般的なプロセスは次のとおりです。 リポジトリへの参照を取得します。 heads/masterブランチのツリーの先端への参照を取得します。 1.コミットするファイルごとにblobを作成し、 sha識別子、パス、モードへの参照を配列に保存します。 1.すべてのブロブを含む新しいtreeを作成して、 heads/masterツリーの先端への参照に追加し、このツリーへの新しいshaポインターをshaます。 1.この新しいツリーを指すコミットを作成し、 heads/masterブランチにプッシュします。 コードはほとんどそのフローに従います。特定の入力のパス構造を想定できるため、複雑なUIやファイルの管理を構築する必要はありません。 const createCommit = async (repositoryUrl, filename, data, images, commitMessage, recording) => { try { const token = localStorage.getItem('accessToken'); const github = new Octokat({ 'token': token }); const [user, repoName] = repositoryUrl.split('/'); if(user === null || repoName === null) { alert('Please specifiy a repo'); return; } const markdownPath = `site/content/${filename}.

Read More

Screen Recorder: recording microphone and the desktop audio at the same time🔗

Paul Kinlan

私は世界で最もシンプルなスクリーンレコーディングソフトウェアを構築することを目標としており、この2か月間、プロジェクトについてゆっくりとうろついています(本当に遅いということです)。

以前の投稿では、すべての入力ソースからのストリームをscreen recording and a voice overlayてWORDS0を取得しscreen recording and a voice overlayた。しかし、フラストレーションの1つの領域は、デスクトップからオーディオを取得する方法と、スピーカーからのオーディオをオーバーレイする方法を見つけることができなかったことです。私は最終的にそれを行う方法を考え出した。

まず、ChromeのgetDisplayMediaでオーディオキャプチャが可能になりました。機能呼び出しでaudio: trueを指定できなかったという点で、仕様に奇妙な見落としがあるようです。

const audio = audioToggle.checked || false;
desktopStream = await navigator.mediaDevices.getDisplayMedia({ video:true, audio: audio });

第二に、もともとオーディオストリームに2つのトラックを作成することで、必要なものを取得できると考えていましたが、ChromeのMediaRecorder APIは1つのトラックしか出力できないこと、そして2つ目は、とにかく機能しなかったことを学びました一度に1つしか再生できないという点で、DVDの複数のオーディオトラックに似ています。

解決策はおそらく多くの人にとっては簡単ですが、私にとっては新しいものでした:Web Audioを使用します。

WebAudio APIには、問題を解決するために必要なAPIであるcreateMediaStreamSourceとcreateMediaStreamDestinationがあります。 createMediaStreamSourceは、デスクトップオーディオとマイクからストリームをcreateMediaStreamSourceできます。この2つをcreateMediaStreamDestinationで作成されたオブジェクトに接続することにより、この1つのストリームをMediaRecorder APIにパイプすることがMediaRecorderます。

const mergeAudioStreams = (desktopStream, voiceStream) => {
  const context = new AudioContext();
    
  // Create a couple of sources
  const source1 = context.createMediaStreamSource(desktopStream);
  const source2 = context.createMediaStreamSource(voiceStream);
  const destination = context.createMediaStreamDestination();
  
  const desktopGain = context.createGain();
  const voiceGain = context.createGain();
    
  desktopGain.gain.value = 0.7;
  voiceGain.gain.value = 0.7;
   
  source1.connect(desktopGain).connect(destination);
  // Connect source2
  source2.connect(voiceGain).connect(destination);
    
  return destination.stream.getAudioTracks();
};

シンプル。

完全なコードはmy glitchにあり、デモはhttps://screen-record-voice.glitch.me/にあります。

{{<fast-youtube oGIdqcMFKlA>}}

Extracting text from an image: Experiments with Shape Detection🔗

Paul Kinlan

Google IOの後に少しダウンタイムがあり、長期にわたるかゆみを掻きたいと思いました。ブラウザの画像内に保持されているテキストをコピーしたいだけです。以上です。誰にとっても素晴らしい機能になると思います。

Chromeに機能を直接追加するのは簡単ではありませんが、Androidのインテントシステムを利用できることはわかっています。Web(または少なくともAndroidのChrome)でそれを実行できるようになりました。

二つの新しいWebプラットフォームへの追加-共有ターゲットレベル2(または私はそれが共有ファイルを呼び出すために好きなように)とTextDetector形状検出APIで- have allowed me to build a utility that I can Share images to and get the text held inside them 。

基本的な実装は比較的単純で、Service Workerで共有ターゲットとハンドラーを作成し、ユーザーが共有したイメージをTextDetectorしたら、 TextDetectorを実行します。

Share Target API使用すると、Webアプリケーションをネイティブ共有サブシステムの一部にすることができます。この場合、次のようにWeb App Manifest内で宣言することにより、すべてのimage/*タイプを処理するように登録できます。

"share_target": {
  "action": "/index.html",
  "method": "POST",
  "enctype": "multipart/form-data",
  "params": {
    "files": [
      {
        "name": "file",
        "accept": ["image/*"]
      }
    ]
  }
}

PWAをインストールすると、次のように、画像を共有するすべての場所でPWAが表示されます。

Share Target APIは、共有ファイルをフォームポストのように扱います。ファイルがWebアプリと共有されると、サービスワーカーがアクティブになり、ファイルデータを使用してfetchハンドラーが呼び出されます。現在、データはService Worker内にありますが、現在のウィンドウで必要になります。処理できるように、サービスはどのウィンドウがリクエストを呼び出したかを知っているので、クライアントを簡単にターゲットにしてデータを送信できます。

self.addEventListener('fetch', event => {
  if (event.request.method === 'POST') {
    event.respondWith(Response.redirect('/index.html'));
    event.waitUntil(async function () {
      const data = await event.request.formData();
      const client = await self.clients.get(event.resultingClientId || event.clientId);
      const file = data.get('file');
      client.postMessage({ file, action: 'load-image' });
    }());
    
    return;
  }
  ...
  ...
}

画像がユーザーインターフェイスに表示されたら、テキスト検出APIで処理します。

navigator.serviceWorker.onmessage = (event) => {  
  const file = event.data.file;
  const imgEl = document.getElementById('img');
  const outputEl = document.getElementById('output');
  const objUrl = URL.createObjectURL(file);
  imgEl.src = objUrl;
  imgEl.onload = () => {
    const texts = await textDetector.detect(imgEl);
    texts.forEach(text => {
      const textEl = document.createElement('p');
      textEl.textContent = text.rawValue;
      outputEl.appendChild(textEl);
    });
  };
  ...
};

最大の問題は、ブラウザが画像を自然に回転させないことです(以下を参照)。ShapeDetection APIでは、テキストを正しい読み方向にする必要があります。

かなり使いやすいEXIF-Js libraryを使用して回転を検出し、基本的なキャンバス操作を行って画像の向きを変更しました。

EXIF.getData(imgEl, async function() {
  // http://sylvana.net/jpegcrop/exif_orientation.html
  const orientation = EXIF.getTag(this, 'Orientation');
  const [width, height] = (orientation > 4) 
                  ? [ imgEl.naturalWidth, imgEl.naturalHeight ]
                  : [ imgEl.naturalHeight, imgEl.naturalWidth ];

  canvas.width = width;
  canvas.height = height;
  const context = canvas.getContext('2d');
  // We have to get the correct orientation for the image
  // See also https://stackoverflow.com/questions/20600800/js-client-side-exif-orientation-rotate-and-mirror-jpeg-images
  switch(orientation) {
    case 2: context.transform(-1, 0, 0, 1, width, 0); break;
    case 3: context.transform(-1, 0, 0, -1, width, height); break;
    case 4: context.transform(1, 0, 0, -1, 0, height); break;
    case 5: context.transform(0, 1, 1, 0, 0, 0); break;
    case 6: context.transform(0, 1, -1, 0, height, 0); break;
    case 7: context.transform(0, -1, -1, 0, height, width); break;
    case 8: context.transform(0, -1, 1, 0, 0, width); break;
  }
  context.drawImage(imgEl, 0, 0);
}

そして出来事、あなたがアプリに画像を共有する場合、それは画像を回転させてから、それを見つけて見つけたテキストの出力を返します。

この小さな実験を作成するのは信じられないほど楽しかったです、そして、それは私にとってすぐに役に立ちました。ただし、 inconsistency of the web platform強調表示しinconsistency of the web platform 。これらのAPIはすべてのブラウザで利用できるわけではなく、Chromeのすべてのバージョンで利用できるわけでもありません-つまり、この記事を書いているChrome OSではアプリを使用できませんが、同時に使用できるのです… OMG、とてもクール。

Wood Carving found in Engakuji Shrine near Kamakura

Sakura

Paul Kinlan

私は、これが「やえ桜」であることをより具体的に言う

Read More