Hello.

I am Paul Kinlan.

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

Brexit: History will judge us all

Paul Kinlan

История рассудит нас всех по этому беспорядку, и я надеюсь, что это будет тематическое исследование для всех о влиянии национализма, личных интересов, колониального высокомерия, знаменитостей и шуток.

Fuckers.

Paul Kinlan

Trying to make the web and developers better.

RSS Github Medium

File Web Share Target

Paul Kinlan

Я часто говорил, что для того, чтобы веб-приложения эффективно конкурировали в мире приложений, они должны быть интегрированы во все места, где пользователи ожидают, что приложения будут. Обмен данными между приложениями является одной из основных недостающих частей веб-платформы, и, в частности, одной из последних основных отсутствующих функций является общий data out of their silo на уровне: веб-приложения должны иметь возможность получать data out of their silo и data out of their silo в другие веб-сайты и приложения; они также должны иметь возможность получать данные из других собственных приложений и сайтов.

Read More

Testing-file-share-target-from-camera

Paul Kinlan

Это тестирование обмена непосредственно из приложения камеры. Похоже, это сработало :)

Read More

testing-file-share-target

Paul Kinlan

Это тест API Target Share на Android и его способность обмениваться файлами. Если вы видите что-то здесь, то все хорошо :)

Read More

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

Paul Kinlan

Рики Монделло из команды Safari недавно поделился заметкой о том, как Twitter использует спецификацию ./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 .

Эта особенность полностью прошла мимо меня, но это хорошая идея: если файл находится в известном месте, может ли браузер предложить пользователю пользовательский интерфейс, который позволяет им быстро сбросить пароль без необходимости навигации по сложному пользовательскому интерфейсу сайтов.

Спецификация обманчиво проста: известный файл просто содержит URL-адрес, по которому пользователь может указать, когда он хочет выполнить действие. Это заставило меня задуматься, можем ли мы предложить больше этих функций:

  • Хорошо известное местоположение для моделей согласия на основе GDPR (согласие cookie) - владельцы сайтов могут предложить ссылку на страницу, где пользователь может управлять и потенциально отзывать все файлы cookie и другие элементы согласия с данными.
  • Хорошо известное место для управления разрешениями браузера - владельцы сайтов могут предложить пользователям быстрое место для отзыва прав доступа к таким вещам, как геолокация, уведомления и другие примитивы.
  • Хорошо известный путь для удаления учетной записи и изменений
  • Хорошо известный путь для управления подпиской на рассылку

Этот список можно продолжить …. Мне очень нравится идея простых файлов перенаправления, чтобы помочь пользователям обнаружить общие действия пользователя, и для браузера.

pinch-zoom-element

Paul Kinlan

Джейк и его команда создали этот довольно удивительный пользовательский элемент для управления масштабированием пинча в любом наборе HTML, за исключением собственной динамики масштабирования в браузере (например, масштабирование в мобильной области просмотра). Этот элемент был одним из центральных компонентов, которые нам нужны для приложения squoosh , которое мы создали и выпустили на Chrome Dev Summit (… я говорю «выпущено на Chrome Dev Summit» - Джейк показывал его всем на Китайском дне разработчиков Google). хотя остальная часть команды была под эмбарго;) …)

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

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

Read full post .

Я только добавил его в свой блог (это заняло всего пару минут), вы можете проверить это в моем разделе « life », где я делюсь фотографиями, которые я сделал. Если вы работаете на устройстве с сенсорным экраном, вы можете быстро увеличить масштаб элемента, если вы используете трекпад, который может работать с несколькими нажатиями пальцев, что тоже работает.

Этот элемент является отличным примером того, почему я люблю веб-компоненты как модель для создания компонентов пользовательского интерфейса. Элемент pinch-zoom имеет pinch-zoom (без сжатия) и минимальные зависимости для сборки, и он просто отлично справляется с одной работой, не связывая никакой пользовательской логики уровня приложения, которая затруднит его использование (у меня есть некоторые мысли по поводу логики пользовательского интерфейса против компонентов логики приложения, которыми я поделюсь на основе своих знаний из приложения Squoosh).

Мне бы очень хотелось, чтобы такие элементы получили больше осведомленности и использования, например, я мог бы представить, что этот элемент может заменить или стандартизировать функциональность масштабирования изображения, которую вы видите на многих коммерческих сайтах, и навсегда избавить разработчиков от этой боли.

Registering as a Share Target with the Web Share Target API

Paul Kinlan

Пит ЛеПейдж представляет API Target Web Share и доступность в Chrome через пробную версию

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-интерфейс меняет правила игры в Интернете, он открывает веб-интерфейс для чего-то, что было когда-то доступно только для собственных приложений: Native Sharing. Приложения - это бункеры, они поглощают все данные и затрудняют доступ к ним на разных платформах. Share Target начинает выравнивать игровое поле, чтобы веб мог играть в одну и ту же игру.

В Twitter Mobile есть Share Target already enabled . Этот пост был создан с использованием цели manifest.json я определил в «панели администратора» manifest.json - она работает довольно хорошо, и как только они получат поддержку файлов, я смогу опубликовать любое изображение или блоб на своем устройстве в своем блоге.

Очень захватывающие времена.

Прочтите связанный пост, чтобы узнать больше о сроках, когда этот API должен появиться, и о том, как использовать API.

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

Paul Kinlan

Отличная статья, видео и пример Томаса Штайнера о хороших push-уведомлениях в Интернете.

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 - это удивительно мощный API, но он легко ругает и раздражает ваших пользователей. Плохая вещь для вашего сайта заключается в том, что если пользователь блокирует уведомления из-за того, что вы запрашиваете без предупреждения, у вас не будет возможности запросить снова.

Относитесь к своим пользователям с уважением, контекст является королем для уведомлений Web Push.

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

Paul Kinlan

Кейси Баскес, замечательный технический писатель из нашей команды, написал довольно удивительную статью о своем опыте измерения того, насколько хорошо существующие рекомендации по работе с документацией работают для объяснения технического материала. Лучшие практики в этом смысле могут быть общеизвестными отраслевыми стандартами технического письма или руководством по стилю написания ваших собственных компаний. Проверьте это!

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 .

Несмотря на то, что я не технический писатель, моя роль включает в себя огромное взаимодействие с нашей командой технических писателей, а также публикацию большого количества «лучших практик» для самих разработчиков. Я был поражен тем, как много Кейси провела исследование искусства написания современных документов через призму содержания наших команд. Я полностью призываю вас прочитать статью Кейси всесторонне - я многому научился. Спасибо, Кейси!

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

Я был в Китае пару недель назад в День разработчиков Google, и я показывал всех своим QRCode scanner, он отлично работал, пока я не вышел в автономный режим. Когда пользователь был в автономном режиме (или частично подключен), камера не запустилась, а это означало, что вы не можете привязать QR-коды. Мне потребовался возраст, чтобы понять, что происходит, и оказалось, что я ошибочно начал работу с камерой в своем «onload» событии, и запрос Google Analytics зависает и не разрешается своевременно. Это это фиксация, которая зафиксировала это.

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.

Читать полностью.

Я рекомендую вам прочитать сообщение, потому что есть много замечательного понимания.

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

Paul Kinlan

В выходные я играл с видео кодеком эффектов Boomerang, вы можете получить его работу в режиме реального времени (я объясню позже). Я работал над Chrome на рабочем столе, но он никогда не будет работать должным образом в Chrome на Android. См. Код здесь.

Похоже, когда вы используете captureStream () на <canvas>, который имеет относительно большое разрешение (1280x720 в моем случае), MediaRecorder API не сможет кодировать видео, и это не будет ошибкой, и вы не можете обнаружить, что он не может кодировать видео раньше времени.

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

Читать полностью.

Если вы хотите поиграть с демоверсией, которая работает на обоих, то нажмите здесь

Why Microsoft and Google love progressive web apps | Computerworld

Paul Kinlan

Хорошая статья о PWA из Mike Elgan. Я не уверен в цели Microsoft с PWA, но я думаю, что наша задача довольно проста: мы хотим, чтобы пользователи мгновенно получали доступ к контенту и функциям и таким образом, как они ожидают, что смогут взаимодействовать с ним на своих устройствах. Интернет должен охватывать всех через каждое подключенное устройство, и пользователь должен иметь доступ к их предпочтительной форме, в качестве приложения, если это так, как они ожидают (мобильный, может быть), или голос на помощнике и т. Д.

Мы все еще длинный путь от безголовой паутины, однако, одна вещь действительно поразила меня в статье:

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

Читать полностью.

Сайты и приложения в Интернете не должны быть изолированы, веб - linkable, indexable, ephemeral, но мы становимся более сильными с каждым созданным нами сайтом. Мы создаем непредвиденные силосы, потому что платформа не позволяет пользователям легко * легко и быстро удалять * свои данные. Я не говорю о RDF или что-то в этом роде, основные операции, такие как копирование и вставка, перетаскивание, совместное использование на сайте и совместное использование с сайта, нарушены в Интернете сегодня, и до этого мы добираемся до IPC между кадрами, работниками и окна.

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

Paul Kinlan

Вы должны иметь возможность создавать и редактировать видео, используя только веб-браузер. Должно быть возможно предоставить пользовательский интерфейс, похожий на Screenflow, который позволяет создавать выходное видео, которое объединяет несколько видео, изображений и аудио в одно видео, которое может быть загружено на такие сервисы, как YouTube. Следуя моему предыдущему сообщению, который кратко описывает требования видеоредактора, в этом посте я просто хотел быстро показать в скринкасте, как я построил рекордер для веб-камеры, а также как создать скринкаст рекордер :)

Read More

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

Paul Kinlan

Первая проблема, которую я нашел, пытается создать видеоредактор в Интернете (0).

У меня есть несколько видеопотоков (рабочий стол и веб-камера), и я хотел иметь возможность переключаться между видеопотоками на один элемент видео, чтобы я мог быстро переключаться между веб-камерой и рабочим столом, а не прерывать «MediaRecorder».

Похоже, вы должны сделать это, переключив свойство selected на объектvideoTracks на <video>, но вы не можете, массив треков содержит только 1 элемент (первый видео трек на 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.

Читать полностью.

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

Вы должны иметь возможность создавать и редактировать видео, используя только веб-браузер. Должно быть возможно предоставить пользовательский интерфейс, похожий на Screenflow, который позволяет создавать выходное видео, которое объединяет несколько видео, изображений и аудио в одно видео, которое может быть загружено на такие сервисы, как YouTube. Этот пост - это просто заявление о намерениях. Я собираюсь начать долгий процесс разработки того, что есть и недоступно на платформе, и посмотреть, как далеко мы можем сегодня добраться.

Read More

Barcode detection in a Web Worker using Comlink

Paul Kinlan

Я большой поклонник QRCodes, они очень простой и опрятный способ обмена данными между реальным миром и цифровым миром. В течение нескольких лет у меня был небольшой побочный проект под названием QRSnapper & mdash; ну, у него было несколько имен, но это тот, который я поселил в & mdash; который использует API getUserMedia для получения живых данных с камеры пользователя, чтобы он мог сканировать QR-коды в ближайшем реальном времени.

Целью приложения было поддерживать 60 кадров в секунду в пользовательском интерфейсе и почти мгновенное обнаружение QR-кода, это означало, что мне пришлось ввести код обнаружения в веб-рабочего (довольно стандартный материал). В этом посте я просто хотел быстро поделиться тем, как я использовал комлинк, чтобы значительно упростить логику в Рабочем.

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 (веб-работник)

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

Мне очень нравится Comlink, я думаю, что это игровой чейнджер библиотеки, особенно когда речь заходит о создании идиоматического JavaScript, который работает по потокам. В конце концов, здесь стоит упомянуть, что встроенный API обнаружения штрих-кода можно запустить внутри рабочего, так что вся логика инкапсулирована в сторону от пользовательского интерфейса.

Читать полностью.

Running FFMPEG with WASM in a Web Worker

Paul Kinlan

Я люблю FFMPEG.js, это аккуратный инструмент, который скомпилирован с помощью asm.js`and, и пусть он будет создавать JS-приложения, которые могут быстро редактировать видео. FFMPEG.js также работает с веб-рабочими, чтобы вы могли кодировать видео без блокировки основного потока.

Я также люблю Comlink. Comlink позволяет мне легко взаимодействовать с веб-рабочими, выставляя функции и классы, не имея дело с сложным конечным автоматом postMessage.

Недавно мне удалось объединить этих двух. Я экспериментировал с тем, чтобы экспортировать FFMPEG в Web Assembly (он работает - yay), и я хотел очистить всю работу postMessage в текущем проекте FFMPEG.js. Ниже приведен код, который теперь выглядит - я думаю, что это довольно аккуратно. У нас есть один рабочий, который импортирует ffmpeg.js и comlink, и он просто предоставляет интерфейс ffmpeg, а затем у нас есть веб-страница, которая загружает рабочего, а затем использует комлинк для создания прокси-сервера API ffmpeg.

Ухоженная.

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

Мне очень нравится, как компилируемые модули Comlink, Workers и WASM могут играть вместе. Я получаю идиоматический JavaScript, который напрямую взаимодействует с модулем WASM, и он отключается от основного потока.

Читать полностью.

Translating a blog using Google Cloud Translate and Hugo

Paul Kinlan

Недавно я вернулся из поездки в Индию для участия в мероприятии Google4India (вскоре сообщается) и встретиться с большим количеством компаний и разработчиков. Одной из наиболее интересных изменений стало стремление к большему количеству контента на языке пользователей в стране, и это было особенно очевидно во всех продуктах Google, которые варьировались от упрощения поиска на языке пользователей, поиска контента, а также прочитать его пользователям в текстовой или речевой форме.

Вся поездка заставила меня задуматься. Мой блог построен с Хьюго. Hugo теперь поддерживает контент в письменной форме на нескольких языках. Hugo полностью статичен, поэтому создание нового контента - это вопрос только создания нового файла и создания системы сборки. Поэтому, возможно, я смогу создать что-то, что сделает мой контент более доступным для большего количества людей, запустив мой статический контент с помощью инструмента перевода, потому что человеческий перевод содержимого очень дорог.

За пару часов до моего полета обратно в Великобританию я создал небольшой скрипт, который будет брать мои файлы с разметкой и запускать их через Google Cloud Translate, чтобы создать быстрый перевод страницы, которую я могу быстро разместить. Полное решение представлено ниже. Это относительно базовый процессор, он игнорирует преамбулу Хьюго, он игнорирует «код», и он игнорирует кавычки тяги - мое предположение заключалось в том, что они всегда должны быть оставлены так, как они были написаны.

Примечание. Похоже, наше учебное программное обеспечение для переводов используется, поэтому важно пометить свою страницу, чтобы учебные инструменты не использовали контент Google Translated в качестве входных данных для его алгоритмов.

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

В целом, я очень доволен процессом. Я понимаю, что машинный перевод не идеален, но я думаю, что я могу увеличить доступ к моему контенту людям, которые могут искать их на своих языках, а не на английском. Я могу увеличить площадь поверхности моего контента и, надеюсь, поможет больше люди.

Понадобится некоторое время, чтобы узнать, действительно ли это помогает людям, поэтому я буду отчитываться, когда у меня будет больше данных … Теперь, чтобы запустить мой скрипт на более моем сайте :)

Apple - Web apps - All Categories

Paul Kinlan

Помните, когда веб-приложения были * рекомендованным способом использования приложений на iPhone?

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

Читать полное сообщение.

Примерно в 2013 году Apple начала перенаправлять каталог / webapps / top-level в / iphone /

Дело в том, что каталог действительно хорош, многие приложения в нем все еще работают сегодня. Однако, глядя на AppStore, он решил намного больше проблем, которые были у разработчиков: Лучшее обнаружение и поиск именно потому, что AppStore был непосредственно на устройстве. AppStore также начал вводить это устранение трений со стороны пользователей и разработчиков, особенно в отношении платежей.