Hello.

I am Paul Kinlan.

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

pinch-zoom-element

Paul Kinlan

Jake và nhóm đã xây dựng yếu tố tùy chỉnh khá tuyệt vời này để quản lý thu phóng pinch trên bất kỳ bộ HTML nào bên ngoài các động lực phóng to pinch của chính trình duyệt (nghĩ rằng phóng to khung nhìn di động). Yếu tố này là một trong những thành phần trung tâm mà chúng tôi cần cho ứng dụng squoosh mà chúng tôi đã xây dựng và phát hành tại Chrome Dev Summit (… Tôi nói rằng 'được phát hành tại Chrome Dev Summit' - Jake đã hiển thị cho mọi người tại Ngày nhà phát triển Google Trung Quốc mặc dù phần còn lại của đội bị cấm vận;) …)

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

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

Read full post .

Tôi vừa thêm nó vào blog của mình (chỉ mất vài phút), bạn có thể kiểm tra nó trên phần ' life ' của tôi nơi tôi chia sẻ ảnh mà tôi đã chụp. Nếu bạn đang sử dụng thiết bị hỗ trợ cảm ứng, bạn có thể nhanh chóng thu nhỏ phần tử, nếu bạn đang sử dụng bàn phím có thể xử lý nhiều đầu vào ngón tay cũng hoạt động.

Yếu tố này là một ví dụ tuyệt vời về lý do tại sao tôi yêu các thành phần web như một mô hình để tạo các thành phần giao diện người dùng. Phần tử pinch-zoom chỉ dưới 3kb trên dây (không nén) và phụ thuộc tối thiểu để xây dựng và nó chỉ thực hiện một công việc đặc biệt tốt, mà không cần sử dụng bất kỳ logic cấp ứng dụng tùy chỉnh nào khiến nó khó sử dụng (Tôi có một vài suy nghĩ về logic UI vs các thành phần logic ứng dụng mà tôi sẽ chia sẻ dựa trên việc học của tôi từ ứng dụng Squoosh).

Tôi rất thích thấy các yếu tố như thế này nhận được nhiều nhận thức và sử dụng hơn, ví dụ tôi có thể tưởng tượng rằng yếu tố này có thể thay thế hoặc tiêu chuẩn hóa chức năng thu phóng hình ảnh mà bạn thấy trên nhiều trang web thương mại và mãi mãi lấy đi nỗi đau từ các nhà phát triển.

Paul Kinlan

Trying to make the web and developers better.

RSS Github Medium

Registering as a Share Target with the Web Share Target API

Paul Kinlan

Pete LePage giới thiệu API mục tiêu chia sẻ web và tính khả dụng trong Chrome thông qua bản dùng thử gốc

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 .

API này là một công cụ thay đổi trò chơi trên web, nó mở web lên một thứ chỉ có sẵn cho các ứng dụng gốc: Chia sẻ gốc. Các ứng dụng là silo, chúng hút tất cả dữ liệu và làm cho nó khó truy cập trên các nền tảng. Chia sẻ mục tiêu bắt đầu san bằng sân chơi để web có thể chơi trong cùng một trò chơi.

Trải nghiệm Twitter Mobile có Chia sẻ mục tiêu already enabled . Bài đăng này được tạo bằng cách sử dụng Mục tiêu chia sẻ mà tôi đã xác định trong bảng điều khiển quản trị trang ' manifest.json - nó hoạt động khá tốt và ngay khi họ hỗ trợ tệp, tôi sẽ có thể đăng bất kỳ hình ảnh hoặc blob nào trên thiết bị của mình lên blog.

Thời gian rất thú vị.

Đọc bài đăng được liên kết để tìm hiểu thêm về dòng thời gian khi API này được phát hành và cách sử dụng API.

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

Paul Kinlan

Một bài viết và video và mẫu tuyệt vời của Thomas Steiner về các thông báo đẩy tốt trên 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 là một API mạnh mẽ đáng kinh ngạc, nhưng nó dễ lạm dụng và gây khó chịu cho người dùng của bạn. Điều tồi tệ cho trang web của bạn là nếu người dùng chặn thông báo vì bạn nhắc mà không cảnh báo, thì bạn không có cơ hội để hỏi lại.

Đối xử với người dùng của bạn với sự tôn trọng, Bối cảnh là vua cho các thông báo Đẩy Web.

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

Paul Kinlan

Kayce Basques, một nhà văn công nghệ tuyệt vời trong nhóm của chúng tôi đã viết một bài báo khá tuyệt vời về kinh nghiệm của anh ta đo lường cách thức tài liệu tốt nhất hiện hành hoạt động tốt để giải thích tài liệu kỹ thuật. Thực tiễn tốt nhất theo nghĩa này có thể là các tiêu chuẩn công nghiệp nổi tiếng về văn bản kỹ thuật, hoặc nó có thể là hướng dẫn phong cách viết của công ty bạn. Kiểm tra nó ra!

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 .

Mặc dù tôi không phải là một nhà văn công nghệ, vai trò của tôi liên quan đến một lượng lớn sự tham gia với nhóm viết công nghệ của chúng tôi cũng như xuất bản rất nhiều 'thực tiễn tốt nhất' cho chính các nhà phát triển. Tôi đã rất ngạc nhiên bởi bao nhiêu chiều sâu và nghiên cứu mà Kayce đã thực hiện về nghệ thuật viết các tài liệu hiện đại thông qua lăng kính của nội dung nhóm chúng tôi. Tôi hoàn toàn khuyến khích bạn đọc sâu bài viết của Kayce - tôi đã học được rất nhiều. Cảm ơn bạn 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

Tôi đã ở Trung Quốc vài tuần trước cho Ngày nhà phát triển của Google và tôi đã hiển thị cho mọi người [Trình quét QRCode] của tôi (0), nó hoạt động tốt cho đến khi tôi ngoại tuyến. Khi người dùng ngoại tuyến (hoặc được kết nối một phần) máy ảnh sẽ không khởi động, điều đó có nghĩa là bạn không thể chụp mã QR. Nó đã cho tôi một tuổi để làm việc ra những gì đang xảy ra, và nó chỉ ra tôi đã nhầm lẫn bắt đầu máy ảnh trong sự kiện onload của tôi và yêu cầu Google Analytics sẽ treo và không giải quyết một cách kịp thời. Đó là cam kết cố định nó.

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.

Đọc toàn bộ bài đăng.

Tôi khuyến khích bạn đọc bài đăng vì có rất nhiều thông tin chi tiết tuyệt vời.

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

Paul Kinlan

Vào cuối tuần, tôi đã chơi với bộ mã hóa video hiệu ứng Boomerang, bạn có thể làm cho nó hoạt động gần thời gian thực (tôi sẽ giải thích sau). Tôi đã làm việc trên Chrome trên máy tính để bàn, nhưng nó sẽ không bao giờ hoạt động bình thường trên Chrome trên Android. Xem mã ở đây.

Có vẻ như khi bạn sử dụng captureStream () trên một <canvas>có độ phân giải tương đối lớn (1280x720 trong trường hợp của tôi) API MediaRecorder sẽ không thể mã hóa video và nó sẽ không lỗi và bạn không thể phát hiện ra rằng nó không thể mã hóa video trước thời hạn.

(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.

Đọc toàn bộ bài đăng.

Nếu bạn muốn chơi xung quanh với bản demo hoạt động trên cả hai thì nhấp vào đây

Why Microsoft and Google love progressive web apps | Computerworld

Paul Kinlan

Một bài đăng hay về PWA từ Mike Elgan. Tôi không chắc chắn về mục tiêu của Microsoft với PWA, nhưng tôi nghĩ rằng chúng tôi khá đơn giản: chúng tôi muốn người dùng có quyền truy cập vào nội dung và chức năng ngay lập tức và theo cách họ mong đợi để có thể tương tác với nó trên thiết bị của họ. Web sẽ tiếp cận mọi người trên mọi thiết bị được kết nối và người dùng có thể truy cập vào phương thức ưa thích của họ, dưới dạng ứng dụng nếu đó là cách họ mong đợi (di động, có thể) hoặc giọng nói trên trợ lý, v.v.

Chúng tôi vẫn còn là một cách xa web không đầu, tuy nhiên, có một điều thực sự khiến tôi trong bài viết:

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

Đọc toàn bộ bài đăng.

Các trang web và ứng dụng trên web không được phân tách, web là liên kết, có thể lập chỉ mục, tạm thời, nhưng chúng tôi đang nhận được nhiều thông tin hơn với từng trang web mà chúng tôi xây dựng. Chúng tôi đang tạo ra các silo không mong muốn vì nền tảng không dễ dàng cho phép người dùng nhận * dữ liệu của họ trong và ngoài các trang web dễ dàng. Tôi không nói về RDF hay bất cứ thứ gì như vậy, các thao tác cơ bản như sao chép và dán, kéo và thả, chia sẻ lên trang web và chia sẻ từ trang web bị hỏng trên web ngày hôm nay, và đó là trước khi chúng tôi truy cập IPC giữa các khung, công nhân và cửa sổ.

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

Paul Kinlan

Bạn sẽ có thể tạo và chỉnh sửa video chỉ bằng cách sử dụng web trong trình duyệt. Bạn có thể cung cấp giao diện người dùng giống như Luồng màn hình cho phép bạn tạo video đầu ra kết hợp nhiều video, hình ảnh và âm thanh thành một video có thể tải lên các dịch vụ như YouTube. Tiếp theo từ [bài viết trước] của tôi (0) mô tả ngắn gọn các yêu cầu của trình chỉnh sửa video, trong bài viết này tôi chỉ muốn nhanh chóng hiển thị trên màn hình cách tôi đã xây dựng máy ghi web cam và cách xây dựng một màn hình máy ghi âm :)

Read More

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

Paul Kinlan

Vấn đề đầu tiên tôi tìm thấy là cố gắng xây dựng trình chỉnh sửa video trên web.

Tôi có nhiều luồng video (máy tính để bàn và web cam) và tôi muốn có thể chuyển đổi giữa các luồng video trên một phần tử video để tôi có thể nhanh chóng chuyển đổi giữa web cam và màn hình nền và không phá vỡ MediaRecorder.

Có vẻ như bạn có thể làm điều đó thông qua việc chuyển đổi thuộc tính selected trên đối tượngvideoTracks trên <video>phần tử, nhưng bạn không thể, mảng các bản nhạc chỉ chứa 1 phần tử (đoạn video đầu tiên trên MediaStream).

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.

Đọc toàn bộ bài đăng.

Repro case.

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

Bạn sẽ có thể tạo và chỉnh sửa video chỉ bằng cách sử dụng web trong trình duyệt. Bạn có thể cung cấp giao diện người dùng giống như Luồng màn hình cho phép bạn tạo video đầu ra kết hợp nhiều video, hình ảnh và âm thanh thành một video có thể tải lên các dịch vụ như YouTube. Bài đăng này thực sự chỉ là một tuyên bố về ý định.

Read More

Barcode detection in a Web Worker using Comlink

Paul Kinlan

Tôi là một fan hâm mộ lớn của QRCodes, họ rất đơn giản và gọn gàng để trao đổi dữ liệu giữa thế giới thực và thế giới kỹ thuật số. Trong một vài năm nay tôi đã có một dự án nhỏ bên gọi là QRSnapper & mdash; nó cũng có một vài cái tên, nhưng đây là cái tôi đã giải quyết trên & mdash; sử dụng API getUserMedia để lấy dữ liệu trực tiếp từ máy ảnh của người dùng để nó có thể quét mã QR trong thời gian thực gần.

Mục tiêu của ứng dụng là duy trì 60 khung hình / giây trong giao diện người dùng và gần ngay lập tức phát hiện Mã QR, điều này có nghĩa là tôi phải đặt mã phát hiện vào một Công nhân Web (những thứ khá chuẩn). Trong bài viết này, tôi chỉ muốn chia sẻ nhanh cách tôi sử dụng comlink để đơn giản hóa logic trong Worker.

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 (nhân viên 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);

Tôi thực sự yêu Comlink, tôi nghĩ rằng đó là một sự thay đổi trò chơi của một thư viện đặc biệt là khi nói đến việc tạo JavaScript thành ngữ hoạt động trên các chủ đề. Cuối cùng là một điều gọn gàng ở đây, đó là API phát hiện mã vạch gốc có thể được chạy bên trong một nhân viên để tất cả logic được gói gọn khỏi giao diện người dùng.

Đọc toàn bộ bài đăng.

Running FFMPEG with WASM in a Web Worker

Paul Kinlan

Tôi yêu FFMPEG.js, nó là một công cụ gọn gàng được biên dịch với asm.js` và nó cho phép tôi xây dựng các ứng dụng web JS có thể chỉnh sửa video nhanh chóng. FFMPEG.js cũng hoạt động với các nhân viên web để bạn có thể mã hóa video mà không chặn luồng chính.

Tôi cũng yêu Comlink. Comlink cho phép tôi dễ dàng tương tác với các nhân viên web bằng cách trưng ra các hàm và các lớp mà không cần phải xử lý một máy trạng thái postMessage phức tạp.

Gần đây tôi đã kết hợp cả hai. Tôi đã thử nghiệm lấy FFMPEG xuất ra Web Assembly (nó hoạt động - yay) và tôi muốn dọn sạch tất cả công việc postMessage trong dự án FFMPEG.js hiện tại. Dưới đây là những gì mã bây giờ trông giống như - Tôi nghĩ rằng nó khá gọn gàng. Chúng tôi có một công nhân nhập khẩu ffmpeg.js và comlink và nó đơn giản cho thấy giao diện ffmpeg, và sau đó chúng tôi có trang web tải nhân viên và sau đó sử dụng comlink để tạo proxy cho API ffmpeg.

Khéo léo.

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);
   }),
});

Tôi thực sự thích cách Comlink, Worker và WASM biên dịch các mô-đun có thể chơi cùng nhau. Tôi nhận được JavaScript thành ngữ tương tác trực tiếp với mô-đun WASM và nó chạy ra khỏi luồng chính.

Đọc toàn bộ bài đăng.

Translating a blog using Google Cloud Translate and Hugo

Paul Kinlan

Gần đây tôi đã trở về từ một chuyến đi đến Ấn Độ để tham dự sự kiện Google4India (báo cáo sớm) và gặp gỡ nhiều doanh nghiệp và nhà phát triển. Một trong những thay đổi thú vị nhất được thảo luận là thúc đẩy nhiều nội dung hơn bằng ngôn ngữ của người dùng trong nước và đặc biệt rõ ràng trên tất cả các sản phẩm của Google, giúp tìm kiếm bằng ngôn ngữ người dùng dễ dàng hơn, để tìm nội dung, và cũng có thể đọc lại cho người dùng dưới dạng văn bản hoặc giọng nói.

Toàn bộ chuyến đi khiến tôi suy nghĩ. Blog của tôi được xây dựng với Hugo. Hugo hiện hỗ trợ nội dung bằng nhiều ngôn ngữ. Hugo hoàn toàn tĩnh, vì vậy việc tạo nội dung mới là vấn đề chỉ tạo một tệp mới và cho phép hệ thống xây dựng thực hiện phép thuật của nó. Vì vậy, có lẽ tôi có thể xây dựng thứ gì đó sẽ làm cho nội dung của tôi có sẵn cho nhiều người hơn bằng cách chạy nội dung tĩnh của tôi thông qua một công cụ dịch vì bản dịch nội dung của con người rất tốn kém.

Một vài giờ trước khi chuyến bay của tôi trở lại Vương quốc Anh, tôi đã tạo một tập lệnh nhỏ sẽ lấy các tệp đánh dấu và chạy chúng thông qua Google Cloud Translate để tạo nhanh dịch của trang mà tôi có thể nhanh chóng lưu trữ. Toàn bộ giải pháp được trình bày dưới đây. Đó là một bộ xử lý tương đối cơ bản, nó bỏ qua phần mở đầu của Hugo nó bỏ qua ‘mã’ và nó bỏ qua các trích dẫn kéo - giả định của tôi là ở chỗ chúng luôn được để lại như cách chúng được viết.

Lưu ý: Có vẻ như phần mềm học tập của chúng tôi dành cho bản dịch sử dụng nên điều quan trọng là đánh dấu trang của bạn để công cụ học tập không sử dụng nội dung được dịch của Google làm đầu vào cho thuật toán của nó.

// 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);

Nói chung, tôi rất hài lòng với quy trình này. Tôi hiểu rằng bản dịch máy không hoàn hảo nhưng suy nghĩ của tôi là tôi có thể tăng khả năng tiếp cận nội dung của mình với những người có thể đang tìm kiếm bằng ngôn ngữ của riêng họ và không phải bằng tiếng Anh tôi có thể tăng diện tích bề mặt khám phá nội dung của mình và những người.

Nó sẽ mất một lúc để xem nếu điều này thực sự giúp mọi người, vì vậy tôi sẽ báo cáo lại khi tôi có thêm dữ liệu …. Bây giờ để chạy kịch bản của tôi trên nhiều trang web của tôi :)

Apple - Web apps - All Categories

Paul Kinlan

Hãy nhớ rằng khi các ứng dụng web được * a * đề xuất cách sử dụng các ứng dụng trên iPhone?

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

Đọc toàn bộ bài đăng.

Vào khoảng năm 2013, Apple bắt đầu chuyển hướng / webapps / thư mục cấp cao nhất đến / iphone /

Vấn đề là, thư mục thực sự khá tốt, rất nhiều ứng dụng trong đó vẫn hoạt động hiện nay. Tuy nhiên, nhìn vào AppStore, nó giải quyết được nhiều vấn đề hơn mà các nhà phát triển đã có: Phát hiện và tìm kiếm tốt hơn cụ thể vì AppStore đã trực tiếp trên thiết bị. AppStore cũng bắt đầu giới thiệu rằng ma sát bị loại bỏ khỏi người dùng và nhà phát triển cụ thể liên quan đến thanh toán.

Gears API

Paul Kinlan

Tôi đang viết một bài đăng trên blog về API Web di động sớm và Alex Russell đã nhắc tôi về 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

Đọc toàn bộ bài đăng.

Tôi nghĩ thật thú vị khi thấy rằng AppCache và WebSQL, Geolocation và WebWorkers xuất hiện từ những ý tưởng trong Google Gears và nó chỉ là hai ý tưởng sau đó thực sự tồn tại. WebSQL không bao giờ được hỗ trợ rộng rãi và được thay thế bởi IndexedDB; và AppCache được thay thế bởi ServiceWorker

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

Paul Kinlan

Chúng tôi sử dụng Google Trò chuyện trong nội bộ rất nhiều để giao tiếp trong nhóm của chúng tôi - nó giống như chùng của chúng tôi; Chúng tôi cũng tạo nhiều nội dung có thể truy cập thông qua nguồn cấp dữ liệu RSS, thậm chí chúng tôi có feed nhóm mà tất cả các bạn có thể xem. Cho đến gần đây tôi mới phát hiện ra rằng việc tạo [bot đơn giản chỉ sau qua WebHooks] thật đơn giản (https://developers.google.com/hangouts/chat/how-tos/webhooks) và rằng đã cho tôi ý tưởng, tôi có thể tạo ra một dịch vụ đơn giản để thăm dò nguồn cấp dữ liệu RSS và sau đó gửi chúng đến webhook của chúng tôi có thể đăng trực tiếp vào cuộc trò chuyện nhóm của chúng tôi.

Nó đã được khá đơn giản cuối cùng, và tôi đã bao gồm tất cả các mã dưới đây. Tôi đã sử dụng các chức năng Firebase - Tôi nghi ngờ rằng điều này cũng dễ dàng trên các trang web chức năng như một dịch vụ khác - và Superfeedr. Superfeedr là một dịch vụ có thể nghe các ping Pubsubhubbub (nay là WebSub) và nó cũng sẽ thăm dò các nguồn cấp dữ liệu RSS không có Pubsub được thiết lập. Sau đó, khi tìm thấy nguồn cấp dữ liệu, nó sẽ ping một URL được cấu hình (trong trường hợp của tôi là Cloud Function của tôi trong Firebase) với một biểu diễn XML hoặc JSON của dữ liệu nguồn cấp dữ liệu mới được tìm thấy - tất cả những gì bạn phải làm là phân tích cú pháp dữ liệu và thực hiện điều gì đó với nó.

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);

Đọc toàn bộ bài đăng.

Tôi đã rất ngạc nhiên và vui mừng về cách dễ dàng để thiết lập.

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

Getting Lighthouse scores from HTTPArchive for sites in India.

Paul Kinlan

A quick dive in to how to use Lighthouse to try and understand how users in a country might experience the web.

Read More

'Moving to a Chromebook' by Rumyra's Blog

Paul Kinlan

Ruth John đã chuyển sang Chrome OS (tạm thời):

The first thing, and possibly the thing with the least amount of up to date information out there, was enabling Crostini. This runs Linux in a container on the Chromebook, something you pretty much want straight away after spending 15 minutes on it.

I have the most recent Pixel, the 256GB version. Here’s what you do.

  • Go to settings.
  • Click on the hamburger menu (top left) - right at the bottom it says ‘About Chrome OS’
  • Open this and there’s an option to put your machine into dev mode
  • It’ll restart and you’ll be in dev mode - this is much like running Canary over Chrome and possibly turning on a couple of flags. It may crash, but what the hell you’ll have Linux capabilities ��
  • Now you can go back into Settings and in regular settings there’s a ‘Linux apps’ option. Turn this on. It’ll install Linux. Once this is complete you’ll have a terminal open for you. Perfect

Đọc toàn bộ bài đăng.

Ruth có một bản ghi tuyệt vời để chuyển sang Chrome OS vì máy chính của cô đã bị hỏng.

Tôi đã chuyển sang Chrome OS toàn thời gian cách đây 4 tháng (trước Google I / O) và chỉ chuyển sang Mac vì tôi đã phá vỡ PixelBook của mình (hiện đã được sửa).

Đối với tôi, đây là một trong những máy phát triển web tốt nhất hiện nay. Đó là thiết bị duy nhất mà tôi có thể thử nghiệm ‘thiết bị di động thực sự’ - bạn có thể cài đặt Chrome trên thiết bị di động, Firefox Mobile, Samsung Browser, Brave, v.v. qua nền tảng ARC. Crostini cũng là một thay đổi trò chơi cho Chrome OS vì nó mang lại rất nhiều hệ sinh thái ứng dụng Linux cho Chrome OS và nó thực sự bắt đầu lấp đầy một khoảng trống lớn cho tôi trên Chrome OS; Tôi đã có Firefox, vim, git, VS Code, Node, npm, tất cả các công cụ xây dựng của tôi, GIMP và Inkscape … Điều đó không có nghĩa là nó hoàn hảo, Crostini có thể nhanh hơn, không phải GPU tăng tốc và nó có thể được tích hợp nhiều hơn với Filemanager vv, và cuối cùng PixelBook thực sự cần nhiều cổng vật lý hơn - tôi có thể đính kèm hai màn hình 4k vào nó, nhưng tôi không thể sạc cùng một lúc.

Tôi nghĩ rằng sự quấn của Ruth cũng khá chính xác, PixelBook là một chiếc máy đắt tiền, nhưng tôi rất vui mừng khi thấy điều này đến với nhiều thiết bị hơn (đặc biệt là ở những điểm giá thấp hơn nhiều).

Would I pay full price for it? I’m not sure I would pay full price for anything on the market right now. Point me in the direction of a system that will run my graphics software and makes a good dev machine (with minimal setup) and lasts more than 18 months, point me in the direction of a worthy investment and I will pay the money.

Yup.