Hello.

I am Paul Kinlan.

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

testing-file-share-target

Paul Kinlan

Il s'agit d'un test de l'API Partager la cible sur Android et de sa capacité à partager des fichiers. Si vous voyez quelque chose ici, alors tout va bien :)

Read More

Paul Kinlan

Trying to make the web and developers better.

RSS Github Medium

Ricky Mondello: Adoption of Well-Known URL for Changing Passwords

Paul Kinlan

Ricky Mondello, de l’équipe Safari, vient de partager une note sur la façon dont Twitter utilise la spécification ./well-known/change-password.

I just noticed that Twitter has adopted the Well-Known URL for Changing Passwords! Is anyone aware of other sites that have adopted it?

Twitter’s implementation: https://twitter.com/.well-known/change-password; Github’s: https://github.com/.well-known/change-password; Specification :https://github.com/WICG/change-password-url

Read full post .

La fonctionnalité m’a complètement dépassée, mais c’est une idée géniale: à partir d’un fichier situé dans un emplacement bien connu, le navigateur peut-il offrir une interface utilisateur à l’utilisateur lui permettant de réinitialiser rapidement son mot de passe sans avoir à naviguer dans l’interface utilisateur complexe du site ..

La spécification est d'une simplicité trompeuse: le fichier bien connu contient simplement l'URL vers laquelle l'utilisateur doit être dirigé lorsqu'il souhaite exécuter l'action. Cela me conduit à penser que nous pouvons offrir plus de ces fonctionnalités:

  • Emplacement bien connu pour les modèles de consentement basés sur le GDPR (consentement des cookies) - les propriétaires de sites pourraient proposer un lien vers la page permettant à un utilisateur de gérer et éventuellement de révoquer tous les cookies et autres éléments de consentement des données.
  • Un emplacement bien connu pour la gestion des autorisations du navigateur - les propriétaires de sites pourraient offrir un emplacement rapide pour permettre aux utilisateurs de révoquer les autorisations d'accès à des éléments tels que la géolocalisation, les notifications et autres primitives.
  • Un chemin bien connu pour la suppression et les modifications de compte
  • Un chemin bien connu pour la gestion des abonnements aux listes de diffusion

La liste s'allonge … J'aime beaucoup l'idée de simples fichiers de redirection pour aider les utilisateurs à découvrir les actions courantes des utilisateurs et pour permettre au navigateur de les mettre en évidence.

pinch-zoom-element

Paul Kinlan

Jake et l'équipe ont créé cet élément personnalisé plutôt impressionnant pour la gestion du zoom pincement sur tout jeu de code HTML en dehors de la dynamique de pincement-zoom du navigateur (pensez au zoom de la fenêtre d'affichage mobile). L'élément était l'un des composants centraux dont nous avions besoin pour l'application squoosh que nous avions conçue et publiée lors du Sommet des développeurs Chrome (… je dis "publiée au Sommet des développeurs Chrome" - Jake l'a montré à tout le monde lors de la Journée des développeurs Google Google. même si le reste de l'équipe était sous embargo;) …)

install: npm install --save-dev pinch-zoom-element

<pinch-zoom>
  <h1>Hello!</h1>
</pinch-zoom>

Read full post .

Je viens de l'ajouter à mon blog (cela ne m'a pris que quelques minutes), vous pouvez le vérifier dans la section " life " où je partage les photos que j'ai prises. Si vous utilisez un appareil tactile, vous pouvez rapidement effectuer un zoom-pincement sur l'élément si vous utilisez un pavé tactile pouvant gérer plusieurs entrées au doigt qui fonctionnent également.

Cet élément est un excellent exemple de la raison pour laquelle j'aime les composants Web en tant que modèle de création de composants d'interface utilisateur. L’élément pinch-zoom représente un peu moins de 3 Ko sur le réseau (non compressé) et des dépendances minimales pour la construction. Il effectue un travail exceptionnellement bien, sans lier une logique personnalisée au niveau de l’application qui le rendrait difficile à utiliser (j’ai quelques réflexions sur la logique de l’interface utilisateur vs composants logiques App que je vais partager en fonction de mon apprentissage de l'application Squoosh).

J'adorerais voir des éléments comme ceux-ci avoir plus de notoriété et d'utilisation, par exemple, je pourrais imaginer que cet élément pourrait remplacer ou normaliser la fonctionnalité de zoom d'image que vous voyez sur de nombreux sites de commerce et dissiper à jamais la douleur des développeurs.

Registering as a Share Target with the Web Share Target API

Paul Kinlan

Pete LePage présente l'API cible de partage Web et la disponibilité dans Chrome via un essai de l'origine

Until now, only native apps could register as a share target. The Web Share Target API allows installed web apps to register with the underlying OS as a share target to receive shared content from either the Web Share API or system events, like the OS-level share button.

Read full post .

Cette API est un changeur de jeu sur le Web, elle ouvre le Web à quelque chose qui n'était auparavant disponible que pour les applications natives: le partage natif. Les applications sont des silos, elles aspirent toutes les données et rendent difficile l'accès aux différentes plateformes. Partager cible commence à niveler le terrain pour que le Web puisse jouer dans le même jeu.

L'expérience Twitter Mobile a Partager cible already enabled . Ce message a été créé à l'aide de la cible de partage que j'ai définie dans le panneau d'administration de mes sites " manifest.json ". Elle fonctionne assez bien et dès qu'elle manifest.json la manifest.json charge des fichiers, je suis en mesure de publier sur mon blog une image ou une goutte sur mon appareil.

Temps très excitant.

Lisez l'article lié pour en savoir plus sur les échéances à respecter pour le lancement de cette API et sur son utilisation.

Why Build Progressive Web Apps: Push, but Don't be Pushy! Video Write-Up

Paul Kinlan

Un excellent article, une vidéo et un exemple de Thomas Steiner sur les bonnes notifications push sur le Web.

A particularly bad practice is to pop up the permission dialog on page load, without any context at all. Several high traffic sites have been caught doing this. To subscribe people to push notifications, you use the the PushManager interface. Now to be fair, this does not allow the developer to specify the context or the to-be-expected frequency of notifications. So where does this leave us?

Read full post .

Web Push est une API incroyablement puissante, mais il est facile d’abuser et d’ennuyer vos utilisateurs. La mauvaise chose pour votre site, c'est que si un utilisateur bloque les notifications parce que vous le lui demandez sans avertissement, vous n'aurez plus l'occasion de le demander.

Traitez vos utilisateurs avec respect, le contexte est roi pour les notifications Web Push.

Maybe Our Documentation "Best Practices" Aren''t Really Best Practices

Paul Kinlan

Kayce Basques, un formidable rédacteur technique de notre équipe, a écrit un article plutôt étonnant sur ses expériences, mesurant la mesure dans laquelle les meilleures pratiques de la documentation existante permettent d'expliquer du matériel technique. Les meilleures pratiques dans ce sens peuvent être des normes bien connues de l’industrie en matière de rédaction technique, ou bien il peut s’agir du guide de rédaction de votre propre entreprise. Vérifiez-le!

Recently I discovered that a supposed documentation “best practice” may not actually stand up to scrutiny when measured in the wild. I’m now on a mission to get a “was this page helpful?” feedback widget on every documentation page on the web. It’s not the end-all be-all solution, but it’s a start towards a more rigorous understanding of what actually makes our docs more helpful.

Read full post .

Bien que je ne sois pas un rédacteur technique, mon rôle implique une très grande implication de notre équipe de rédacteurs techniques ainsi que la publication de nombreuses «meilleures pratiques» pour les développeurs. J’ai été émerveillé par toute la profondeur et les recherches que Kayce a effectuées sur l’art d’écrire des documents modernes à travers le prisme du contenu de nos équipes. Je vous encourage fortement à lire l'article de Kayce en profondeur - j'ai beaucoup appris. Merci Kayce!

Grep your git commit log

Paul Kinlan

Finding code that was changed in a commit

Read More

Performance and Resilience: Stress-Testing Third Parties by CSS Wizardry

Paul Kinlan

J’étais en Chine il y a quelques semaines pour la journée des développeurs Google et je montrais à tout le monde mon scanner QRCode, cela fonctionnait très bien jusqu’à ce que je passe au mode hors connexion. Lorsque l’utilisateur était hors ligne (ou partiellement connecté), la caméra ne démarrait pas, ce qui signifiait que vous ne pouviez pas prendre de code QR. Il m’a fallu un certain temps pour comprendre ce qui se passait, et il s’avère que j’ai démarré la caméra par erreur lors de mon événement onload et que la demande de Google Analytics était bloquée et non résolue rapidement. C’est ce commit qui l’a corrigé.

Because these types of assets block rendering, the browser will not paint anything to the screen until they have been downloaded (and executed/parsed). If the service that provides the file is offline, then that’s a lot of time that the browser has to spend trying to access the file, and during that period the user is left potentially looking at a blank screen. After a certain period has elapsed, the browser will eventually timeout and display the page without the asset(s) in question. How long is that certain period of time?

It’s 1 minute and 20 seconds.

If you have any render-blocking, critical, third party assets hosted on an external domain, you run the risk of showing users a blank page for 1.3 minutes.

Below, you’ll see the DOMContentLoaded and Load events on a site that has a render-blocking script hosted elsewhere. The browser was completely held up for 78 seconds, showing nothing at all until it ended up timing out.

Lire l’article complet.

Je vous encourage à lire le post car il y a beaucoup de perspicacité.

Chrome Bug 897727 - MediaRecorder using Canvas.captureStream() fails for large canvas elements on Android

Paul Kinlan

Le week-end, je jouais avec un encodeur vidéo à effet Boomerang, vous pouvez le faire fonctionner presque en temps réel (je l’expliquerai plus tard). Je l’ai obtenu sur Chrome sur le Bureau, mais cela ne fonctionnerait jamais correctement sur Chrome sur Android. Voir le code ici.

Cela ressemble à lorsque vous utilisez captureStream () sur un <canvas>Avec une résolution relativement importante (1280x720 dans mon cas), l’API MediaRecorder ne pourra pas encoder les vidéos, elle ne provoquera pas d’erreur et vous ne pourrez pas détecter qu’elle ne peut pas encoder la vidéo à l’avance.

(1) Capture a large res video (from getUM 1280x720) to a buffer for later processing. (2) Create a MediaRecorder with a stream from a canvas element (via captureStream) sized to 1280x720 (3) For each frame captured putImageData on the canvas (4) For each frame call canvasTrack.requestFrame() at 60fps

context.putImageData(frame, 0, 0); canvasStreamTrack.requestFrame();

Demo: https://boomerang-video-chrome-on-android-bug.glitch.me/ Code: https://glitch.com/edit/#!/boomerang-video-chrome-on-android-bug?path=script.js:21:42

What is the expected result?

For the exact demo, I buffer the frames and then reverse them so you would see the video play forwards and backwards (it works on desktop). In generall I would expect all frames sent to the canvas to be processed by the MediaRecorder API - yet they are not.

What happens instead?

It only captures the stream from the canvas for a partial part of the video and then stops. It’s not predicatable where it will stop.

I suspect there is a limit with the MediaRecorder API and what resolution it can encode depending on the device, and there is no way to know about these limits ahead of time.

As far as I can tell this has never worked on Android. If you use https://boomerang-video-chrome-on-android-bug.glitch.me which has a 640x480 video frame it records just fine. The demo works at higher-resolution just fine on desktop.

Lire l’article complet.

Si vous voulez jouer avec la démo qui marche sur les deux, alors cliquez ici

Why Microsoft and Google love progressive web apps | Computerworld

Paul Kinlan

Un bel article sur PWA de Mike Elgan. Je ne suis pas sûr de l’objectif de Microsoft avec PWA, mais je pense que le nôtre est assez simple: nous voulons que les utilisateurs aient accès au contenu et aux fonctionnalités instantanément et d’une manière qu’ils espèrent pouvoir interagir avec ce contenu sur leurs appareils. Le Web doit atteindre tout le monde sur chaque appareil connecté et un utilisateur doit pouvoir accéder à sa modalité préférée, en tant qu’application si c’est ce à quoi il s’attend (mobile, peut-être), ou de la voix sur un assistant, etc.

Nous sommes encore loin du réseau sans tête, cependant, une chose m’a vraiment frappé dans l’article:

Another downside is that PWAs are highly isolated. So it’s hard and unlikely for different PWAs to share resources or data directly.

Lire l’article complet.

Les sites et les applications sur le Web ne sont pas censés être isolés, le Web est linkable, indexable, éphemeral, mais chaque site que nous construisons devient plus silencieux. Nous créons des silos inattendus car la plate-forme ne permet pas facilement aux * utilisateurs * d’importer * facilement * leurs données hors site. Je ne parle pas de RDF ou de quelque chose du genre, les opérations de base telles que copier / coller, glisser-déposer, partager sur site et partager à partir d’un site sont interrompues sur le Web d’aujourd’hui, et c’est avant que nous ne passions à IPC entre les images, travailleurs et des fenêtres.

Building a video editor on the web. Part 0.1 - Screencast

Paul Kinlan

Vous devriez pouvoir créer et éditer des vidéos en utilisant uniquement le Web dans le navigateur. Il devrait être possible de fournir une interface utilisateur semblable à Screenflow qui vous permet de créer une sortie vidéo combinant plusieurs vidéos, images et son en une seule vidéo pouvant être téléchargée vers des services tels que YouTube. Après mon précédent post qui décrit brièvement les exigences de l’éditeur de vidéo, je voulais simplement montrer rapidement, dans un screencast, comment j’ai construit l’enregistreur de webcam, et comment créer un screencast.

Read More

894556 - Multiple video tracks in a MediaStream are not reflected on the videoTracks object on the video element

Paul Kinlan

Le premier numéro que j’ai trouvé en essayant de créer un éditeur de vidéo sur le Web.

J’ai plusieurs flux vidéo (ordinateur de bureau et webcam) et je voulais pouvoir basculer entre les flux vidéo sur un élément vidéo afin de pouvoir basculer rapidement entre la webcam et le bureau et ne pas casser le MediaRecorder.

Il semble que vous devriez pouvoir le faire en basculant la propriété selected sur l'objetvideoTracks du <video>élément, mais vous ne pouvez pas, le tableau de pistes ne contient qu'un seul élément (la première piste vidéo sur le flux multimédia).

What steps will reproduce the problem? (1) Get two MediaStreams with video tracks (2) Add them to a new MediaStream and attach as srcObject on a videoElement (3) Check the videoElement.videoTracks object and see there is only one track

Demo at https://multiple-tracks-bug.glitch.me/

What is the expected result? I would expect videoElement.videoTracks to have two elements.

What happens instead? It only has the first videoTrack that was added to the MediaStream.

Lire l’article complet.

Cas de repro.

window.onload = () => {
  if('getDisplayMedia' in navigator) warning.style.display = 'none';

  let blobs;
  let blob;
  let rec;
  let stream;
  let webcamStream;
  let desktopStream;

  captureBtn.onclick = async () => {

       
    desktopStream = await navigator.getDisplayMedia({video:true});
    webcamStream = await navigator.mediaDevices.getUserMedia({video: { height: 1080, width: 1920 }, audio: true});
    
    // Always 
    let tracks = [...desktopStream.getTracks(), ... webcamStream.getTracks()]
    console.log('Tracks to add to stream', tracks);
    stream = new MediaStream(tracks);
    
    console.log('Tracks on stream', stream.getTracks());
    
    videoElement.srcObject = stream;
    
    console.log('Tracks on video element that has stream', videoElement.videoTracks)
    
    // I would expect the length to be 2 and not 1
  };

};

Building a video editor on the web. Part 0.

Paul Kinlan

Vous devriez pouvoir créer et éditer des vidéos en utilisant uniquement le Web dans le navigateur. Il devrait être possible de fournir une interface utilisateur semblable à Screenflow qui vous permet de créer une sortie vidéo combinant plusieurs vidéos, images et son en une seule vidéo pouvant être téléchargée vers des services tels que YouTube. Ce message est vraiment juste une déclaration d’intention. Je vais commencer le long processus consistant à déterminer ce qui est disponible ou non sur la plate-forme et à voir jusqu’où nous pouvons aller aujourd’hui.

Read More

Barcode detection in a Web Worker using Comlink

Paul Kinlan

Je suis un grand fan des QRCodes, ils sont un moyen très simple et pratique d’échanger des données entre le monde réel et le monde numérique. Depuis quelques années, j’ai un petit projet parallèle appelé QRSnapper & mdash; Eh bien, il porte quelques noms, mais c’est celui sur lequel j’ai opté pour & mdash; qui utilise l’API getUserMedia pour extraire des données en direct de la caméra de l’utilisateur afin de lui permettre de rechercher les codes QR en temps quasi réel.

Le but de l’application était de maintenir 60 images par seconde dans l’interface utilisateur et de détecter le code QR de manière quasi instantanée. Cela signifiait que je devais mettre le code de détection dans un Web Worker (élément plutôt standard). Dans cet article, je voulais simplement partager rapidement la façon dont j’ai utilisé comlink pour simplifier massivement la logique du travailleur.

qrclient.js

import * as Comlink from './comlink.js';

const proxy = Comlink.proxy(new Worker('/scripts/qrworker.js')); 

export const decode = async function (context) {
  try {
    let canvas = context.canvas;
    let width = canvas.width;
    let height = canvas.height;
    let imageData = context.getImageData(0, 0, width, height);
    return await proxy.detectUrl(width, height, imageData);
  } catch (err) {
    console.log(err);
  }
};

qrworker.js (travailleur Web)

import * as Comlink from './comlink.js';
import {qrcode} from './qrcode.js';

// Use the native API's
let nativeDetector = async (width, height, imageData) => {
  try {
    let barcodeDetector = new BarcodeDetector();
    let barcodes = await barcodeDetector.detect(imageData);
    // return the first barcode.
    if (barcodes.length > 0) {
      return barcodes[0].rawValue;
    }
  } catch(err) {
    detector = workerDetector;
  }
};

// Use the polyfil
let workerDetector = async (width, height, imageData) => {
  try {
    return qrcode.decode(width, height, imageData);
  } catch (err) {
    // the library throws an excpetion when there are no qrcodes.
    return;
  }
}

let detectUrl = async (width, height, imageData) => {
  return detector(width, height, imageData);
};

let detector = ('BarcodeDetector' in self) ? nativeDetector : workerDetector;
// Expose the API to the client pages.
Comlink.expose({detectUrl}, self);

J’aime vraiment Comlink, je pense que c’est un changeur de jeu d’une bibliothèque, en particulier lorsqu’il s’agit de créer du code JavaScript idiomatique qui fonctionne sur plusieurs threads. Enfin, il est intéressant de noter que l’API de détection de code à barres native peut être exécutée à l’intérieur d’un serveur, de sorte que toute la logique soit encapsulée à l’écart de l’UI.

Lire l’article complet.

Running FFMPEG with WASM in a Web Worker

Paul Kinlan

J’aime FFMPEG.js, c’est un outil soigné compilé avec asm.js` qui me permet de créer des applications Web JS pouvant éditer rapidement des vidéos. FFMPEG.js fonctionne également avec les travailleurs Web afin que vous puissiez encoder des vidéos sans bloquer le fil principal.

J’aime aussi Comlink. Comlink me permet d’interagir facilement avec les travailleurs Web en exposant des fonctions et des classes sans avoir à traiter avec une machine à états postMessage complexe.

J’ai récemment eu à combiner les deux ensemble. J’essayais d’essayer de faire exporter FFMPEG vers Web Assembly (ça marche - oui) et je voulais nettoyer tout le travail postMessage du projet actuel FFMPEG.js. Le code ci-dessous ressemble à ceci: je pense que c’est assez chouette. Nous avons un opérateur qui importe ffmpeg.js et comlink. Il expose simplement l’interface ffmpeg. La page Web qui charge l’agent et utilise ensuite comlink pour créer un proxy pour l’API ffmpeg.

Soigné.

worker.js

importScripts('https://cdn.jsdelivr.net/npm/comlinkjs@3.0.2/umd/comlink.js');
importScripts('../ffmpeg-webm.js'); 
Comlink.expose(ffmpegjs, self);

client.html

let ffmpegjs = await Comlink.proxy(worker);
let result = await ffmpegjs({
   arguments: ['-y','-i', file.name, 'output.webm'],
   MEMFS: [{name: file.name, data: data}],
   stdin: Comlink.proxyValue(() => {}),
   onfilesready: Comlink.proxyValue((e) => {
     let data = e.MEMFS[0].data;
     output.src = URL.createObjectURL(new Blob([data]))
     console.log('ready', e)
   }),
   print: Comlink.proxyValue(function(data) { console.log(data); stdout += data + "\n"; }),
   printErr: Comlink.proxyValue(function(data) { console.log('error', data); stderr += data + "\n"; }),
   postRun: Comlink.proxyValue(function(result) { console.log('DONE', result); }),
   onExit: Comlink.proxyValue(function(code) {
     console.log("Process exited with code " + code);
     console.log(stdout);
   }),
});

J’aime beaucoup la manière dont les modules compilés par Comlink, Workers et WASM peuvent jouer ensemble. Je reçois du code JavaScript idiomatique qui interagit directement avec le module WASM et qui s’exécute à partir du thread principal.

Lire l’article complet.

Translating a blog using Google Cloud Translate and Hugo

Paul Kinlan

Je suis récemment rentré d’un voyage en Inde pour assister à l’événement Google4India (rapport prochainement) et rencontrer de nombreuses entreprises et développeurs. L’un des changements les plus intéressants abordés concernait la demande de contenu dans la langue des utilisateurs du pays et était particulièrement visible dans tous les produits de Google, allant de la recherche dans la langue de l’utilisateur à la recherche de contenu, et aussi de le lire aux utilisateurs sous forme de texte ou de voix.

Tout le voyage m’a fait réfléchir. Mon blog est construit avec Hugo. Hugo prend désormais en charge le contenu écrit en plusieurs langues. Hugo est entièrement statique, la création de nouveaux contenus consiste donc simplement à créer un nouveau fichier et à laisser le système de compilation faire de la magie. Alors peut-être que je peux créer quelque chose qui rendra mon contenu plus accessible à davantage de personnes en exécutant mon contenu statique via un outil de traduction, car la traduction humaine de contenu est très coûteuse.

Quelques heures avant mon retour au Royaume-Uni, j’ai créé un petit script qui prendra mes fichiers de démarques et les exécutera via Google Cloud Translate pour créer rapidement un fichier. traduction de la page que je peux ensuite héberger rapidement. La solution complète est présentée ci-dessous. C’est un processeur relativement basique, il ignore le préambule d’Hugo, il ignore le «code» et ignore les guillemets - mon hypothèse était que ceux-ci sont toujours destinés à rester tels qu’ils ont été écrits.

Remarque: il semble que notre logiciel d’apprentissage pour les traductions soit important, il est donc important de marquer votre page pour que les outils d’apprentissage n’utilisent pas le contenu Google Translated comme entrée dans ses algorithmes.

// Imports the Google Cloud client library
const Translate = require('@google-cloud/translate');
const program = require('commander');
const fs = require('fs');
const path = require('path');

program
  .version('0.1.0')
  .option('-s, --source [path]', 'Add in the source file.')
  .option('-t, --target [lang]', 'Add target language.')
  .parse(process.argv);

// Creates a client
const translate = new Translate({
  projectId: 'html5rocks-hrd'
});

const options = {
  to:  program.target,
};

async function translateLines(text) {
  if(text === ' ') return ' ';
  const output = [];
  let results = await translate.translate(text, options);

  let translations = results[0];
  translations = Array.isArray(translations)
    ? translations
    : [translations];

  translations.forEach((translation, i) => {
    output.push(translation)
  });

  return output.join('\n');
};

// Translates the text into the target language. "text" can be a string for
// translating a single piece of text, or an array of strings for translating
// multiple texts.
(async function (filePath, target) {

  const text = fs.readFileSync(filePath, 'utf8');

  const lines = text.split('\n');
  let translateBlock = [];
  const output = [];

  let inHeader = false;
  let inCode = false;
  let inQuote = false;
  for (const line of lines) {
    // Don't translate preampble
    if (line.startsWith('---') && inHeader) { inHeader = false; output.push(line); continue; }
    if (line.startsWith('---')) { inHeader = true; output.push(line); continue; }
    if (inHeader) { output.push(line); continue; }

    // Don't translate code
    if (line.startsWith('```') && inCode) { inCode = false; output.push(line); continue; }
    if (line.startsWith('```')) { inCode = true; output.push(await translateLines(translateBlock.join(' '))); translateBlock = []; output.push(line); continue; }
    if (inCode) { output.push(line); continue; }

    // Dont translate quotes
    if (inQuote && line.startsWith('>') === false) { inQuote = false; }
    if (line.startsWith('>')) { inQuote = true; output.push(await translateLines(translateBlock.join(' '))); translateBlock = []; output.push(line); }
    if (inQuote) { output.push(line); continue; }

    if (line.charAt(0) === '\n' || line.length === 0) { output.push(await translateLines(translateBlock.join(' '))); output.push(line); translateBlock = []; continue;} 

    translateBlock.push(line);
  }

  if(translateBlock.length > 0) output.push(await translateLines(translateBlock.join(' ')))

  const result = output.join('\n');
  const newFileName = path.parse(filePath);
  fs.writeFileSync(`content/${newFileName.name}.${target}${newFileName.ext}`, result);

})(program.source, program.target);

Dans l’ensemble, je suis très content du processus. Je comprends que la traduction automatique n’est pas parfaite mais je pense que je peux étendre la portée de mon contenu aux personnes qui pourraient chercher dans leur propre langue et non en anglais. Je peux augmenter la surface de découverte de mon contenu et, espérons-le, aider davantage gens.

Il faudra un certain temps pour voir si cela aide réellement les gens, alors je vais rapporter quand j’ai plus de données …. Maintenant, pour exécuter mon script sur plus de mon site :)

Apple - Web apps - All Categories

Paul Kinlan

Rappelez-vous que lorsque Web Apps était * une * méthode recommandée pour utiliser des applications sur l’iPhone?

What are web apps? Learn what they are and how to use them.

Lire l’article complet.

Vers 2013, Apple a commencé à rediriger le répertoire / webapps / top-level vers / iphone /

Le fait est que le répertoire était en fait très bon, beaucoup d’applications fonctionnent encore. Cependant, en regardant l’AppStore, cela résolvait beaucoup plus de problèmes que les développeurs avaient: Une meilleure découverte et une meilleure recherche en particulier parce que l’AppStore était directement sur l’appareil. L’AppStore commençait également à introduire des éléments qui éliminaient les frictions des utilisateurs et des développeurs, notamment en ce qui concerne les paiements.

Gears API

Paul Kinlan

Je rédige un article sur les premières API Web mobiles et Alex Russell m’a rappelé Google Gears

Gears modules include:

  • LocalServer Cache and serve application resources (HTML, JavaScript, images, etc.) locally
  • Database Store data locally in a fully-searchable relational database
  • WorkerPool Make your web applications more responsive by performing resource-intensive operations asynchronously

Lire l’article complet.

Je pense qu’il est intéressant de voir qu’AppCache et WebSQL, Geolocation et WebWorkers sont issus des idées de Google Gears et que seules les deux dernières ont réellement survécu. WebSQL n’a jamais été largement pris en charge et a été remplacé par IndexedDB; et AppCache remplacé par ServiceWorker

RSS Feed to Google Chat Webhook using Cloud Functions for Firebase and Superfeedr

Paul Kinlan

Nous utilisons beaucoup Google Chat pour communiquer au sein de notre équipe. Nous créons également beaucoup de contenu accessible via les flux RSS, nous avons même un flux d’équipe que vous pouvez voir tous. Ce n’est que récemment que j’ai découvert qu’il était assez facile de créer un simple post-only bot via WebHooks et que m’a donné l’idée, je peux créer un service simple qui interroge les flux RSS et les envoie ensuite à notre webhook qui peut poster directement dans notre chat d’équipe.

C’était assez simple au final, et j’ai inclus tout le code ci-dessous. J’ai utilisé les fonctions de Firebase - je pense que c’est aussi simple sur d’autres sites fonctionnels que le service - et Superfeedr. Superfeedr est un service qui peut écouter les pings de Pubsubhubbub (maintenant WebSub) et interroge également les flux RSS pour lesquels Pubsub n’a pas été configuré. Ensuite, lorsqu’il trouve un flux, il envoie une requête ping à une URL configurée (dans mon cas, ma fonction Cloud dans Firebase) avec une représentation XML ou JSON des données de flux nouvellement trouvées. Il vous suffit d’analyser les données et de faire quelque chose.

const functions = require('firebase-functions');
const express = require('express');
const cors = require('cors');
const fetch = require('node-fetch');
const app = express();

// Automatically allow cross-origin requests
app.use(cors({ origin: true }));

app.post('/', (req, res) => {
  const { webhook_url } = req.query;
  const { body } = req;
  if (body.items === undefined || body.items.length === 0) {
    res.send('');
    return;
  }

  const item = body.items[0];
  const actor = (item.actor && item.actor.displayName) ? item.actor.displayName : body.title;

  fetch(webhook_url, {
    method: 'POST',
    headers: {
      "Content-Type": "application/json; charset=utf-8",
    },
    body: JSON.stringify({
      "text": `*${actor}* published <${item.permalinkUrl}|${item.title}>. Please consider <https://twitter.com/intent/tweet?url=${encodeURIComponent(body.items[0].permalinkUrl)}&text=${encodeURIComponent(body.items[0].title)}|Sharing it>.`
    })  
  }).then(() => {
    return res.send('ok');
  }).catch(() => {
    return res.send('error')
  });
})
// Expose Express API as a single Cloud Function:
exports.publish = functions.https.onRequest(app);

Lire l’article complet.

J’ai été surpris et ravi de la facilité d’installation.

Using HTTPArchive and Chrome UX report to get Lighthouse score for top visited sites in India.

Paul Kinlan

A quick dive in to how to use Lighthouse,HTTPArchive and Chrome UX report to try and understand how users in a country might experience the web.

Read More