mirror of
https://github.com/ThatGuySam/doesitarm.git
synced 2026-05-15 06:35:20 -07:00
Merge branch 'feat/eleventy-tv' into develop
This commit is contained in:
commit
374ff84e21
18 changed files with 997 additions and 145 deletions
77
.eleventy.js
77
.eleventy.js
|
|
@ -1,7 +1,35 @@
|
|||
/**
|
||||
*
|
||||
* Eleventy command line debugging codes
|
||||
*
|
||||
*
|
||||
* Eleventy:Config
|
||||
* Eleventy:UserConfig
|
||||
* Eleventy:TemplateConfig
|
||||
*
|
||||
* Eleventy:CommandCheck
|
||||
*
|
||||
* Eleventy:EleventyFiles
|
||||
* Eleventy:TemplateWriter
|
||||
* Eleventy:TemplatePassthroughManager
|
||||
* Eleventy:TemplatePassthrough
|
||||
* Eleventy:TemplateData
|
||||
* Eleventy:Template
|
||||
*
|
||||
* Eleventy:ComputedData
|
||||
*
|
||||
* Eleventy:Benchmark
|
||||
*
|
||||
* EleventyAssets
|
||||
*/
|
||||
|
||||
|
||||
import fs from 'fs'
|
||||
import replace_css_url from 'replace-css-url'
|
||||
import dotenv from 'dotenv'
|
||||
import { InlineCodeManager } from '@11ty/eleventy-assets'
|
||||
import { transformSync } from 'esbuild'
|
||||
|
||||
|
||||
import nuxtConfig from './nuxt.config'
|
||||
|
||||
|
|
@ -13,6 +41,8 @@ function getAssetFilePath(componentName) {
|
|||
return `./${componentName}`
|
||||
}
|
||||
|
||||
const inlineAssetCache = new Map()
|
||||
|
||||
module.exports = function ( eleventyConfig ) {
|
||||
// console.log('eleventyConfig', eleventyConfig)
|
||||
|
||||
|
|
@ -25,17 +55,50 @@ module.exports = function ( eleventyConfig ) {
|
|||
const cssManager = new InlineCodeManager()
|
||||
const jsManager = new InlineCodeManager()
|
||||
|
||||
eleventyConfig.addJavaScriptFunction('usingComponent', function ( componentName ) {
|
||||
eleventyConfig.addJavaScriptFunction('usingComponent', async function ( componentName ) {
|
||||
// console.log('Getting component', componentName)
|
||||
|
||||
const assetFileName = getAssetFilePath( componentName )
|
||||
|
||||
// console.log('Cache size', inlineAssetCache)
|
||||
|
||||
if ( componentName.includes('.js') ) {
|
||||
|
||||
// If a never before seen component, add the JS code
|
||||
if(!jsManager.hasComponentCode(componentName)) {
|
||||
const fileContents = fs.readFileSync(getAssetFilePath(componentName), { encoding: "UTF-8" })
|
||||
if( !inlineAssetCache.has( assetFileName ) ) {
|
||||
|
||||
let contents = ''
|
||||
|
||||
// if ( inlineAssetCache.has( assetFileName ) ) {
|
||||
// console.log('Reading component from cache', componentName)
|
||||
// console.log('Cache size', inlineAssetCache.size)
|
||||
|
||||
// contents = inlineAssetCache.get( assetFileName )
|
||||
|
||||
// } else {
|
||||
|
||||
// }
|
||||
|
||||
console.log('Reading component file', componentName)
|
||||
const javascriptCode = fs.readFileSync( assetFileName, { encoding: "UTF-8" })
|
||||
|
||||
console.log('Minifying code', componentName)
|
||||
const minified = await transformSync(javascriptCode, {
|
||||
// loader: 'ts',
|
||||
// bundle: true,
|
||||
minify: true,
|
||||
// format: 'iife',
|
||||
})//minify( javascriptCode )
|
||||
|
||||
// console.log('minified', minified)
|
||||
|
||||
contents = minified.code
|
||||
|
||||
inlineAssetCache.set( assetFileName, contents )
|
||||
|
||||
// console.log('Got component', componentName, componentCss)
|
||||
|
||||
jsManager.addComponentCode(componentName, fileContents)
|
||||
jsManager.addComponentCode(componentName, contents)
|
||||
}
|
||||
|
||||
// Log usage for this url
|
||||
|
|
@ -44,7 +107,7 @@ module.exports = function ( eleventyConfig ) {
|
|||
} else if ( componentName.includes('.css') ) {
|
||||
// If a never before seen component, add the CSS code
|
||||
if(!cssManager.hasComponentCode(componentName)) {
|
||||
const fileContents = fs.readFileSync(getAssetFilePath(componentName), { encoding: "UTF-8" })
|
||||
const fileContents = fs.readFileSync( assetFileName, { encoding: "UTF-8" })
|
||||
|
||||
// Replace urls in css with relative urls for dist folder
|
||||
const parsedCss = replace_css_url(
|
||||
|
|
@ -94,6 +157,10 @@ module.exports = function ( eleventyConfig ) {
|
|||
|
||||
|
||||
return {
|
||||
// https://www.11ty.dev/docs/config/#template-formats
|
||||
// Default: html,liquid,ejs,md,hbs,mustache,haml,pug,njk,11ty.js
|
||||
templateFormats: [ '11ty.js' ],
|
||||
|
||||
dir: {
|
||||
input: 'pages-eleventy',
|
||||
output: 'dist',
|
||||
|
|
|
|||
20
README.md
20
README.md
|
|
@ -140,7 +140,7 @@ Builds - [Java on M1 Benchmarks](https://docs.google.com/spreadsheets/d/1g4U7LAI
|
|||
* [SmartGit](https://www.syntevo.com/smartgit/) - ✳️ Yes, works via Rosetta 2 Translation - [Verification](https://github.com/ThatGuySam/doesitarm/issues/541#issuecomment-806068252)
|
||||
* [SourceTree](https://www.sourcetreeapp.com/) - ✳️ Yes, works via Rosetta 2 - [Verification](https://github.com/ThatGuySam/doesitarm/issues/200)
|
||||
* [SubEthaEdit](https://subethaedit.net/) - ✅ Yes, full native Apple Silicon Support as of v5.1.5 - [Release Notes](https://github.com/subethaedit/SubEthaEdit/releases/tag/SubEthaEdit-MacFull-5.1.5)
|
||||
* [Sublime Text](https://www.sublimetext.com/) - ✳️ Yes, works via Rosetta 2 - [Forum Discussion](https://forum.sublimetext.com/t/arm-build/5882/97)
|
||||
* [Sublime Text](https://www.sublimetext.com/) - ✅ Yes, Full Native Apple Silicon Support as of Build 4100 - [Release Notes](https://gist.github.com/jfcherng/7bf4103ea486d1f67b7970e846b3a619#build-4100)
|
||||
* [Surge](https://nssurge.com/) - ✅ Yes, full native Apple Silicon Support as of v4.0.0 - [Issue](https://github.com/ThatGuySam/doesitarm/issues/157)
|
||||
* [TablePlus](https://tableplus.com/) - ✅ Yes, Full Native Apple Silicon Support as of Build 352 - [Release Notes](https://twitter.com/TablePlus/status/1327650704295489536) [View on Setapp](https://setapp.sjv.io/c/2708043/455094/5114)
|
||||
* [TeaCode](https://www.apptorium.com/teacode) - ✳️ Yes, works via Rosetta 2 translation - [Article](https://setapp.sjv.io/apple-silicon-supported-apps) [View on Setapp](https://setapp.sjv.io/teacode)
|
||||
|
|
@ -196,7 +196,7 @@ Builds - [Java on M1 Benchmarks](https://docs.google.com/spreadsheets/d/1g4U7LAI
|
|||
|
||||
#### Music and Audio Tools
|
||||
|
||||
* [Adobe Audition](https://www.adobe.com/products/audition.html) - ✳️ Yes, works via Rosetta 2 - [Official Adobe Status Page](https://helpx.adobe.com/download-install/kb/apple-silicon-m1-chip.html)
|
||||
* [Adobe Audition](https://www.adobe.com/products/audition.html) - ✅ Yes, Full Native Apple Silicon Support - [Release Notes](https://helpx.adobe.com/audition/user-guide.html/audition/using/whats-new/2021-1.ug.html) [Official Adobe Status Page](https://helpx.adobe.com/download-install/kb/apple-silicon-m1-chip.html)
|
||||
* [Airfoil](https://rogueamoeba.com/airfoil/mac/) - ✅ Yes, Initial Native Apple Silicon Support as of v5.10.0 - [Release Notes](https://rogueamoeba.com/airfoil/mac/releasenotes.php) [Official Post](https://weblog.rogueamoeba.com/2020/11/16/rogue-amoeba-software-updates-for-macos-11-big-sur-and-m1-chip-based-macs/)
|
||||
* [Ardour](https://community.ardour.org/download) - ✳️ Yes, it works via Rosetta 2 with native support in progress - [Apple Silicon Pre-release](https://discourse.ardour.org/t/ardour-for-apple-m1-arm-pre-release/105174)
|
||||
* [Audacity](https://www.audacityteam.org/download/) - ✳️ Yes, it works via Rosetta 2 with no reported issues - [Github Issue Comment](https://github.com/audacity/audacity/issues/684#issuecomment-710726323)
|
||||
|
|
@ -268,6 +268,7 @@ Builds - [Java on M1 Benchmarks](https://docs.google.com/spreadsheets/d/1g4U7LAI
|
|||
* [Typeface for Mac](https://typefaceapp.com/) - ✳️ Yes, works via Rosetta 2 translation - [Article](https://setapp.sjv.io/apple-silicon-supported-apps) [View on Setapp](https://setapp.sjv.io/c/2708043/507348/5114)
|
||||
* [Vectornator](https://www.vectornator.io/) - ✅ Yes, it was shown at the November 10th event - [Apple Nov 10 Event](https://youtu.be/5AwdkGKmZ0I?t=1300)
|
||||
* [waifu2x](https://github.com/nihui/waifu2x-ncnn-vulkan/releases) - ✅ Yes, Full Native Apple Silicon Support as of v20210102 - [Release Notes](https://github.com/nihui/waifu2x-ncnn-vulkan/releases/tag/20210102)
|
||||
* [Wonderdraft](https://www.wonderdraft.net/) - ✳️ Yes, works via Rosetta 2 translation - [Report #1](https://community.folivora.ai/t/m1-mac-trackpad-disappearing-laggy-pointer/21069/9)
|
||||
|
||||
|
||||
|
||||
|
|
@ -312,6 +313,14 @@ Builds - [Java on M1 Benchmarks](https://docs.google.com/spreadsheets/d/1g4U7LAI
|
|||
* [Dante Virtual Soundcard](https://my.audinate.com/support/downloads/download-latest-dante-software) - 🚫 No, not yet supported only works on Intel-based Macs - [Official Article](https://www.audinate.com/learning/faqs/dante-software-on-macos-big-sur-and-apple-m1-based-computers)
|
||||
* [djay](https://www.algoriddim.com/) - ✅ Yes, Full Native Apple Silicon Support as of v3.0 - [Release Notes](https://www.algoriddim.com/djay-pro-mac/releasenotes)
|
||||
* [Ecamm Live](https://www.ecamm.com/mac/ecammlive/?fp_ref=lsp) - ✳️ Yes, works via Rosetta 2 - [Official Article](https://support.ecamm.com/en/articles/4616420-ecamm-live-on-macos-11-big-sur-and-apple-silicon-m1-macs)
|
||||
* [Elgato Control Center](https://help.elgato.com/hc/en-us/articles/360028242091-Elgato-Control-Center-Release-Notes-macOS-) - 🚫 No, not yet supported, only works on Intel-based Macs - [Official Article](https://help.elgato.com/hc/en-us/articles/360052527791-Apple-Silicon-Compatibility-M1-Chip-)
|
||||
* [Elgato EpocCam Driver](https://help.elgato.com/hc/en-us/articles/360052826852-EpocCam-Release-Notes-macOS) - ✅ Yes, Native Apple Silicon Support - [Official Article](https://help.elgato.com/hc/en-us/articles/360052527791-Apple-Silicon-Compatibility-M1-Chip-)
|
||||
* [Elgato Game Capture](https://help.elgato.com/hc/en-us/articles/360027963512-Elgato-Game-Capture-HD-Software-Release-Notes-macOS-) - 🚫 No, not yet working, only works on Intel-based Macs - [Official Article](https://help.elgato.com/hc/en-us/articles/360052527791-Apple-Silicon-Compatibility-M1-Chip-)
|
||||
* [Elgato OBS Link](https://help.elgato.com/hc/en-us/articles/360031314272-Elgato-OBS-Link-Software-Release-Notes) - 🚫 No, not yet supported only works on Intel-based Macs - [Official Article](https://help.elgato.com/hc/en-us/articles/360052527791-Apple-Silicon-Compatibility-M1-Chip-)
|
||||
* [Elgato Stream Deck](https://help.elgato.com/hc/en-us/articles/360028242631-Elgato-Stream-Deck-Software-Release-Notes) - ✳️ Yes, works via Rosetta 2 translation - [Official Article](https://help.elgato.com/hc/en-us/articles/360052527791-Apple-Silicon-Compatibility-M1-Chip-)
|
||||
* [Elgato Thunderbolt Dock](https://help.elgato.com/hc/en-us/articles/360035293992-Elgato-Thunderbolt-Dock-Utility-Software-Release-Notes) - 🚫 No, not yet supported only works on Intel-based Macs - [Official Article](https://help.elgato.com/hc/en-us/articles/360052527791-Apple-Silicon-Compatibility-M1-Chip-)
|
||||
* [Elgato Video Capture](https://help.elgato.com/hc/en-us/articles/360028243991-Elgato-Video-Capture-Software-Release-Notes) - ✳️ Yes, works via Rosetta 2 translation - [Official Article](https://help.elgato.com/hc/en-us/articles/360052527791-Apple-Silicon-Compatibility-M1-Chip-)
|
||||
* [Elgato Wave Link](https://help.elgato.com/hc/en-us/articles/360043289491-Elgato-Wave-Link-Release-Notes) - ✳️ Yes, works via Rosetta 2 translation - [Official Article](https://help.elgato.com/hc/en-us/articles/360052527791-Apple-Silicon-Compatibility-M1-Chip-)
|
||||
* [Farrago](https://rogueamoeba.com/farrago/) - ✅ Yes, Initial Native Apple Silicon Support as of v1.6.0 - [Release Notes](https://rogueamoeba.com/farrago/releasenotes.php) [Official Post](https://weblog.rogueamoeba.com/2020/11/16/rogue-amoeba-software-updates-for-macos-11-big-sur-and-m1-chip-based-macs/)
|
||||
* [grandMA3 onPC](https://www.malighting.com/downloads/products/grandma3/) - 🚫 No, not yet supported only works on Intel-based Macs - [Report #1](https://forum.malighting.com/thread/4671-works-with-new-apple-m1/?postID=12236#post12236)
|
||||
* [Loopback](https://rogueamoeba.com/loopback/) - ✅ Yes, Initial Native Apple Silicon Support as of v2.2.0 - [Release Notes](https://rogueamoeba.com/loopback/releasenotes.php) [Official Post](https://weblog.rogueamoeba.com/2020/11/16/rogue-amoeba-software-updates-for-macos-11-big-sur-and-m1-chip-based-macs/)
|
||||
|
|
@ -493,6 +502,7 @@ Builds - [Java on M1 Benchmarks](https://docs.google.com/spreadsheets/d/1g4U7LAI
|
|||
* [Time Out for Mac](https://www.dejal.com/timeout/) - ✅ Yes, Full Native Apple Silicon Support - [Article](https://setapp.sjv.io/apple-silicon-supported-apps) [View on Setapp](https://setapp.sjv.io/c/2708043/689682/5114)
|
||||
* [Timing for Mac](https://timingapp.com/) - ✅ Yes, Full Native Apple Silicon Support - [Article](https://setapp.sjv.io/apple-silicon-supported-apps) [View on Setapp](https://setapp.sjv.io/c/2708043/344474/5114)
|
||||
* [Tuple](https://tuple.app/) - ✳️ Yes, works via Rosetta 2 - [Verification](https://github.com/ThatGuySam/doesitarm/issues/514#issuecomment-761276719)
|
||||
* [TurboTax](https://turbotax.intuit.com/personal-taxes/cd-download/) - ✳️ Runs via Rosetta with no immediate plans for native support - [Discussion](https://ttlc.intuit.com/community/taxes/discussion/re-turbotax-deluxe-under-macos-bigsur-on-macbook-air-apple-m1-chip-turbotax-app-doesn-t-open/01/2273549#M815580)
|
||||
* [UctoX](https://software.charliemonroe.net/uctox/) - ✅ Yes, Full Native Apple Silicon Support - [Developer Article](http://blog.charliemonroe.net/big-sur-and-apple-silicon/) [View on Setapp](https://setapp.sjv.io/uctox)
|
||||
* [Ulysses](https://ulysses.app/) - ✳️ Yes, works via Rosetta 2 - [Verification](https://github.com/ThatGuySam/doesitarm/issues/120#issuecomment-750041670) [MacRumors Article](https://www.macrumors.com/2020/11/13/apple-silicon-macs-universal-apps/) [View on Setapp](https://setapp.sjv.io/c/2708043/344477/5114)
|
||||
* [Unibox](https://www.uniboxapp.com/) - ✳️ Yes, works via Rosetta 2 translation - [Article](https://setapp.sjv.io/apple-silicon-supported-apps) [View on Setapp](https://setapp.sjv.io/c/2708043/368242/5114)
|
||||
|
|
@ -502,6 +512,7 @@ Builds - [Java on M1 Benchmarks](https://docs.google.com/spreadsheets/d/1g4U7LAI
|
|||
* [VMware Fusion](https://www.vmware.com/products/fusion.html) - ⏹ No, not working at all but support is in development - [Official Tweet](https://twitter.com/VMwareFusion/status/1326229094648832000)
|
||||
* [VMWare Horizon Client](https://my.vmware.com/en/web/vmware/downloads/info/slug/desktop_end_user_computing/vmware_horizon_clients/horizon_8) - ✳️ Yes, works via Rosetta 2 - [Verification](https://github.com/ThatGuySam/doesitarm/issues/297)
|
||||
* [VMWare Remote Console](https://apps.apple.com/app/vmware-remote-console/id1230249825) - ✅ Yes, Full Native Apple Silicon Support as of v12.0.0 - [Release Notes](https://docs.vmware.com/en/VMware-Remote-Console/12.0/rn/VMware-Remote-Console-120-Release-Notes.html)
|
||||
* [VNC Viewer](https://www.realvnc.com/en/connect/download/viewer/macos/) - ✳️ Yes, works via Rosetta 2 translation - [Verification](https://github.com/ThatGuySam/doesitarm/issues/643#issue-880713134)
|
||||
* [Workspaces](https://www.apptorium.com/workspaces) - ✅ Yes, Full Native Apple Silicon Support - [Article](https://setapp.sjv.io/apple-silicon-supported-apps) [View on Setapp](https://setapp.sjv.io/c/2708043/389439/5114)
|
||||
* [WPS Office](https://apps.apple.com/cn/app/wps-office/id1443749478?l=en&mt=12) - ✅ Yes, full native support as of v3.0.1 - [Verification](https://github.com/ThatGuySam/doesitarm/issues/149)
|
||||
* [Yoink](https://eternalstorms.at/yoink/mac/) - ✅ Yes, Full Native Apple Silicon Support as of v3.5.11 - [Blog Post](https://blog.eternalstorms.at/2020/11/09/app-update-galore/)
|
||||
|
|
@ -513,6 +524,7 @@ Builds - [Java on M1 Benchmarks](https://docs.google.com/spreadsheets/d/1g4U7LAI
|
|||
|
||||
* [2048 Game](https://apps.apple.com/us/app/2048-game/id871033113?mt=12) - ✅ Yes, Full Native Apple Silicon Support - [🧪 Apple Silicon App Tested](https://doesitarm.com/apple-silicon-app-test/)
|
||||
* [Amazon Music](https://music.amazon.com/NA/help/apps) - ✳️ Yes, works via Rosetta 2 translation - [Verification](https://github.com/ThatGuySam/doesitarm/issues/634#issue-870540625)
|
||||
* [BlueStacks](https://www.bluestacks.com/) - 🚫 No, not yet supported only works on Intel-based Macs - [Report #1](https://www.reddit.com/r/BlueStacks/comments/n9f9wn/apple_silicon/gxowg9g?utm_source=share&utm_medium=web2x&context=3) [Report #2](https://www.reddit.com/r/BlueStacks/comments/kea3uw/macos_bigsur_111_with_m1_issue/gg3xi2r?utm_source=share&utm_medium=web2x&context=3)
|
||||
* [Celeste](http://www.celestegame.com/) - ⏹ No, not yet but can be made to run with a work around - [Apple Silicon Work Around](https://github.com/AbeJellinek/celeste-on-m1) [View on Itch.io](https://mattmakesgames.itch.io/celeste?ac=QWRPsg4sVvG)
|
||||
* [Cog Player](https://cog.losno.co/download) - ✅ Yes, Full Native Apple Silicon Support - [🧪 Apple Silicon App Tested](https://doesitarm.com/apple-silicon-app-test/)
|
||||
* [DeSmuME](https://desmume.org/download/) - ✳️ Yes, works via Rosetta 2 Translation - [Verification](https://github.com/ThatGuySam/doesitarm/issues/611#issue-840038939)
|
||||
|
|
@ -598,7 +610,7 @@ Builds - [Java on M1 Benchmarks](https://docs.google.com/spreadsheets/d/1g4U7LAI
|
|||
* [LastPass](https://www.lastpass.com/) - ✳️ Yes, works via Rosetta 2, but the Safari extension has issues - [Verification](https://github.com/ThatGuySam/doesitarm/issues/233#issuecomment-731506984) [Release Notes](https://lastpass.com/upgrade.php?fromwebsite=1&releasenotes=1) [Safari Issue](https://community.logmein.com/t5/Support-Discussions/macOS-Big-Sur-Safari-14-0-1-Official-Support/m-p/256549)
|
||||
* [Little Snitch](https://www.obdev.at/products/littlesnitch/index.html) - ✅ Yes, Full Native Apple Silicon Support from 5.0 - [Release notes](https://www.obdev.at/products/littlesnitch/releasenotes.html)
|
||||
* [Mullvad VPN](https://mullvad.net/en/download/macos/) - ✳️ Yes, works via Rosetta 2 - [Verification](https://github.com/ThatGuySam/doesitarm/issues/566#issue-808856204)
|
||||
* [NordVPN](https://nordvpn.com/download/nordvpn-site/) - ✳️ Yes, works via Rosetta 2 - [Source](https://github.com/ThatGuySam/doesitarm/issues/317#issuecomment-736876490)
|
||||
* [NordVPN](https://nordvpn.com/download/nordvpn-site/) - ✳️ Yes, works via Rosetta 2 - [Source](https://github.com/ThatGuySam/doesitarm/issues/317#issuecomment-736876490) [Release Notes](https://nordvpn.com/blog/nordvpn-mac-release-notes/)
|
||||
* [ProtonVPN](https://protonvpn.com/download-macos) - ✳️ Yes, works via Rosetta 2 translation - [Report #1](https://forums.macrumors.com/threads/vpn-software.2270086/post-29435392) [Report #2](https://forums.macrumors.com/threads/vpn-software.2270086/post-29294735)
|
||||
* [SecKey](https://lighthouse16.com/seckey/) - ✅ Yes, fully supported as of v1.3.3 - [Source](https://lighthouse16.com/journal/apple-silicon-support/)
|
||||
* [Secrets for Mac](https://apps.apple.com/app/secrets/id973049011?mt=12) - ✅ Yes, Full Native Apple Silicon Support as of v3.4.0 - [Official Post](https://outercorner.com/blog/2020/11/password-autofill-now-on-macos-big-sur/) [View on Setapp](https://setapp.sjv.io/c/2708043/442278/5114)
|
||||
|
|
@ -632,6 +644,7 @@ Builds - [Java on M1 Benchmarks](https://docs.google.com/spreadsheets/d/1g4U7LAI
|
|||
* [Burn for Mac](https://burn-osx.sourceforge.io/Pages/English/home.html) - ✅ Yes, Full Native Apple Silicon Support as of v3.0.0 - [Release Notes](https://burn-osx.sourceforge.io/Pages/English/news.html)
|
||||
* [CalHash](https://titanium-software.fr/en/calhash.html) - ✅ Yes, Full Native Apple Silicon Support - [Release Notes](https://titanium-software.fr/en/release_calhash.html)
|
||||
* [Camo Studio](https://reincubate.com/camo/) - ✅ Yes, Full Native Apple Silicon Support - [Release Notes](https://reincubate.com/support/camo/release-notes/#1.1.3.122)
|
||||
* [Chia Blockchain](https://www.chia.net/) - ✳️ Yes, works via Rosetta 2 translation - [Issue](https://github.com/Chia-Network/chia-blockchain/issues/2313) [Changelog](https://github.com/Chia-Network/chia-blockchain/blob/main/CHANGELOG.md)
|
||||
* [coconutBattery](https://www.coconut-flavour.com/coconutbattery/) - ✅ Yes, full native support as of v3.9.2
|
||||
* [CleanMyMac](https://cleanmymac.com/) - ✅ Yes, Full Native Apple Silicon Support - [Article](https://setapp.sjv.io/apple-silicon-supported-apps) [View on Setapp](https://setapp.sjv.io/c/2708043/344326/5114)
|
||||
* [CleanShot X](https://cleanshot.com) - ✅ Yes, fully supported as of v3.4.4 - [Changelog](https://cleanshot.com/changelog/) [View on Setapp](https://setapp.sjv.io/c/2708043/569389/5114)
|
||||
|
|
@ -678,6 +691,7 @@ Builds - [Java on M1 Benchmarks](https://docs.google.com/spreadsheets/d/1g4U7LAI
|
|||
* [Rocket for Mac](https://matthewpalmer.net/rocket/) - ✅ Yes, Full Native Apple Silicon Support - [Verifcation](https://github.com/ThatGuySam/doesitarm/issues/605#issue-835338120)
|
||||
* [SheepShaver](https://www.emaculation.com/forum/viewtopic.php?f=20&t=7360) - ✅ Yes, Full Native Apple Silicon Support - [Release Notes](https://www.emaculation.com/sheepshaver/build-history.txt) [🧪 Apple Silicon App Tested](https://doesitarm.com/apple-silicon-app-test/)
|
||||
* [Silenz](https://silenzapp.com/) - ✅ Yes, Full Native Apple Silicon Support - [Article](https://setapp.sjv.io/apple-silicon-supported-apps) [View on Setapp](https://setapp.sjv.io/c/2708043/653749/5114)
|
||||
* [sndcpy](https://github.com/rom1v/sndcpy) - ✅ Yes, Full Native Apple Silicon Support - [Verification](https://github.com/ThatGuySam/doesitarm/issues/645#issuecomment-839911450)
|
||||
* [start for Mac](https://apps.apple.com/us/app/start/id1329701389?mt=12) - ✅ Yes, Full Native Apple Silicon Support - [Article](https://setapp.sjv.io/apple-silicon-supported-apps) [View on Setapp](https://setapp.sjv.io/c/2708043/752736/5114)
|
||||
* [Swish for macOS](https://highlyopinionated.co/swish/) - ✅ Yes, Full Native Apple Silicon Support as of v1.7.1 - [Article](https://setapp.sjv.io/apple-silicon-supported-apps) [View on Setapp](https://setapp.sjv.io/swish)
|
||||
* [TG Pro](https://www.tunabellysoftware.com/tgpro/) - ✅ Yes, Full Native Apple Silicon Support as of v2.53 - [Release Notes](https://www.tunabellysoftware.com/tgpro/releasenotes/)
|
||||
|
|
|
|||
|
|
@ -236,12 +236,12 @@ class BuildLists {
|
|||
const appType = getAppType( app )
|
||||
|
||||
if ( isVideo( app ) ) {
|
||||
// this.endpointMaps.eleventy.add({
|
||||
// route: getVideoEndpoint(app),
|
||||
// payload: buildVideoPayload( app, this.allVideoAppsList, this.lists.video )
|
||||
// })
|
||||
this.endpointMaps.eleventy.set(
|
||||
getVideoEndpoint(app),
|
||||
buildVideoPayload( app, this.allVideoAppsList, this.lists.video )
|
||||
)
|
||||
|
||||
this.endpointMaps.nuxt.set( getVideoEndpoint(app), buildVideoPayload( app, this.allVideoAppsList, this.lists.video ) )
|
||||
// this.endpointMaps.nuxt.set( getVideoEndpoint(app), buildVideoPayload( app, this.allVideoAppsList, this.lists.video ) )
|
||||
|
||||
return
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
import renderPoster from './poster.js'
|
||||
|
||||
function pill ( text ) {
|
||||
return /* html */`
|
||||
|
|
@ -9,17 +10,19 @@ function pill ( text ) {
|
|||
`
|
||||
}
|
||||
|
||||
export default function ( video, options = {} ) {
|
||||
export default async function ( video, options = {} ) {
|
||||
const {
|
||||
width = '325px',
|
||||
classes = 'w-full flex-shrink-0 flex-grow-0 border-2 border-transparent rounded-2xl overflow-hidden'
|
||||
} = options
|
||||
|
||||
// Setup inline lazysizes
|
||||
this.usingComponent( 'node_modules/lazysizes/lazysizes.min.js' )
|
||||
// await this.usingComponent( 'node_modules/lazysizes/lazysizes.min.js' )
|
||||
|
||||
// console.log('video', video)
|
||||
|
||||
const posterHtml = renderPoster( video )
|
||||
|
||||
return /* html */`
|
||||
<div class="video-card ${ classes }" style="max-width: ${ width }; flex-basis: ${ width }; scroll-snap-align: start;">
|
||||
<a
|
||||
|
|
@ -29,18 +32,7 @@ export default function ( video, options = {} ) {
|
|||
<div class="video-card-container relative overflow-hidden bg-black">
|
||||
<div class="video-card-image ratio-wrapper">
|
||||
<div class="relative overflow-hidden w-full pb-16/9">
|
||||
<picture>
|
||||
<source
|
||||
sizes="${video.thumbnail.sizes}"
|
||||
data-srcset="${video.thumbnail.srcset}"
|
||||
type="image/jpg"
|
||||
>
|
||||
<img
|
||||
data-src="${video.thumbnail.src}"
|
||||
alt="${video.name}"
|
||||
class="lazyload absolute h-full w-full object-cover"
|
||||
>
|
||||
</picture>
|
||||
${ posterHtml }
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
|
|
|
|||
76
components-eleventy/video/player.js
Normal file
76
components-eleventy/video/player.js
Normal file
|
|
@ -0,0 +1,76 @@
|
|||
import renderPoster from './poster.js'
|
||||
|
||||
function renderTimestamps ( video ) {
|
||||
const timestampsForRender = video.timestamps.map( timestamp => {
|
||||
const [ minutes, seconds ] = timestamp.time.split(':')
|
||||
|
||||
return {
|
||||
...timestamp,
|
||||
inSeconds: (minutes * 60) + Number(seconds)
|
||||
}
|
||||
})
|
||||
|
||||
return timestampsForRender.map( timestamp => (/* html */`
|
||||
<button
|
||||
time="${timestamp.time}"
|
||||
class="inline-block text-xs rounded-lg py-1 px-2 border-2 border-white focus:outline-none border-opacity-0 neumorphic-shadow-inner">
|
||||
${ timestamp.fullText }
|
||||
</button>
|
||||
`) ).join('')
|
||||
}
|
||||
|
||||
export default async function ( video, options = {} ) {
|
||||
const {
|
||||
coverBottomHtml = ''
|
||||
// classes = 'w-full flex-shrink-0 flex-grow-0 border-2 border-transparent rounded-2xl overflow-hidden'
|
||||
} = options
|
||||
|
||||
// Setup inline player script
|
||||
await this.usingComponent( 'node_modules/can-autoplay/build/can-autoplay.min.js' )
|
||||
|
||||
await this.usingComponent( 'helpers/lite-youtube.js' )
|
||||
|
||||
// Setup inline lazysizes
|
||||
await this.usingComponent( 'node_modules/lazysizes/lazysizes.min.js' )
|
||||
|
||||
// console.log('video', video)
|
||||
|
||||
const posterHtml = renderPoster( video )
|
||||
|
||||
const timestampsHtml = renderTimestamps( video )
|
||||
|
||||
return /* html */`
|
||||
<lite-youtube
|
||||
class="video-canvas w-screen flex flex-col justify-center items-center bg-black pt-16"
|
||||
style="left:50%;right:50%;margin-left:-50vw;margin-right:-50vw;"
|
||||
>
|
||||
<script class="video-data" type="application/json">
|
||||
${ JSON.stringify( video ) }
|
||||
</script>
|
||||
|
||||
<div class="ratio-wrapper w-full max-w-4xl">
|
||||
<div class="player-container relative overflow-hidden w-full pb-16/9">
|
||||
<div class="player-poster cursor-pointer">
|
||||
|
||||
${ posterHtml }
|
||||
|
||||
<div class="video-card-overlay absolute inset-0 flex flex-col justify-center items-center bg-gradient-to-tr from-black to-transparent p-4" style="--gradient-from-color:rgba(0, 0, 0, 1); --gradient-to-color:rgba(0, 0, 0, 0.7);">
|
||||
<div class="cover-top h-full"></div>
|
||||
<div class="play-circle bg-white-2 bg-blur flex justify-center items-center outline-0 rounded-full ease p-4">
|
||||
<svg viewBox="0 0 18 18" style="width:18px;height:18px;margin-left:3px">
|
||||
<path fill="currentColor" d="M15.562 8.1L3.87.225c-.818-.562-1.87 0-1.87.9v15.75c0 .9 1.052 1.462 1.87.9L15.563 9.9c.584-.45.584-1.35 0-1.8z"></path>
|
||||
</svg>
|
||||
</div>
|
||||
<div class="cover-bottom h-full">${ coverBottomHtml }</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="player-timestamps w-full max-w-4xl">
|
||||
<div class="player-timestamps-wrapper overflow-x-auto overflow-y-visible whitespace-no-wrap py-2 space-x-2">
|
||||
${ timestampsHtml }
|
||||
</div>
|
||||
</div>
|
||||
</lite-youtube>
|
||||
`
|
||||
}
|
||||
32
components-eleventy/video/poster.js
Normal file
32
components-eleventy/video/poster.js
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
|
||||
|
||||
export default function ( video ) {
|
||||
const webpSource = {
|
||||
...video.thumbnail,
|
||||
srcset: video.thumbnail.srcset.replaceAll('ytimg.com/vi/', 'ytimg.com/vi_webp/').replace(/.png|.jpg|.jpeg/g, '.webp')
|
||||
}
|
||||
|
||||
const mergedSources = {
|
||||
webp: webpSource,
|
||||
jpeg: video.thumbnail
|
||||
}
|
||||
|
||||
return /* html */`
|
||||
<picture>
|
||||
|
||||
${ Object.entries( mergedSources ).map( ([ key, source ]) => (/* html */`
|
||||
<source
|
||||
sizes="${ source.sizes }"
|
||||
data-srcset="${ source.srcset }"
|
||||
type="image/${ key }"
|
||||
>
|
||||
`) ).join('') }
|
||||
|
||||
<img
|
||||
:data-src="${ video.thumbnail.src }"
|
||||
alt="${ video.name }"
|
||||
class="absolute inset-0 h-full w-full object-cover lazyload"
|
||||
>
|
||||
</picture>
|
||||
`
|
||||
}
|
||||
|
|
@ -10,7 +10,7 @@ function getCardType ( video ) {
|
|||
return VideoCard
|
||||
}
|
||||
|
||||
export default function ( videos, options = {} ) {
|
||||
export default async function ( videos, options = {} ) {
|
||||
|
||||
const {
|
||||
cardWidth = '325',
|
||||
|
|
@ -23,18 +23,23 @@ export default function ( videos, options = {} ) {
|
|||
const uid = Math.random().toString(36).substr(2, 9)
|
||||
const rowId = `row-${ uid }`
|
||||
|
||||
// Setup inline scroll script
|
||||
await this.usingComponent( 'helpers/scroll.js' )
|
||||
|
||||
// Setup inline lazysizes
|
||||
this.usingComponent( 'helpers/scroll.js' )
|
||||
await this.usingComponent( 'node_modules/lazysizes/lazysizes.min.js' )
|
||||
|
||||
// console.log('video', video)
|
||||
|
||||
const cardsHtml = videos.map( video => {
|
||||
const renderedCards = await Promise.all(videos.map( async video => {
|
||||
const Card = getCardType( video )
|
||||
|
||||
// console.log('Card', this.boundComponent(Card)( video ) )
|
||||
|
||||
return this.boundComponent(Card)( video )
|
||||
} ).join('')
|
||||
return await this.boundComponent(Card)( video )
|
||||
} ))
|
||||
|
||||
const cardsHtml = renderedCards.join('')
|
||||
|
||||
// console.log( 'cardsHtml', cardsHtml )
|
||||
|
||||
|
|
|
|||
|
|
@ -143,9 +143,9 @@
|
|||
'underline px-3 py-2 rounded-md text-xs font-medium leading-5 focus:outline-none focus:text-white focus:bg-gray-700 transition duration-150 ease-in-out',
|
||||
//($nuxt.$route.path === item.url) ? 'text-white bg-darker hover:text-white neumorphic-shadow' : 'text-gray-300 hover:bg-darker hover:neumorphic-shadow'
|
||||
]"
|
||||
href="https://amzn.to/334Oyu6"
|
||||
href="https://track.adtraction.com/t/t?a=1404987138&as=1609291769&t=2&tk=1&url=https://surfshark.com/blog/m1-chip-compatibility"
|
||||
>
|
||||
🖥 M1 iMac is now available for preorder
|
||||
✅ Surfshark VPN is now Apple Silicon native!
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -56,4 +56,10 @@ export function getVideoEndpoint ( video ) {
|
|||
return `/tv/${video.slug}`
|
||||
}
|
||||
|
||||
export function getRouteType ( routeString ) {
|
||||
// Remove first slash and split by remaining
|
||||
// slashes to get first part of route
|
||||
const [ routeType ] = routeString.substring(1).split('/')
|
||||
|
||||
return routeType
|
||||
}
|
||||
|
|
|
|||
485
helpers/lite-youtube.js
Normal file
485
helpers/lite-youtube.js
Normal file
|
|
@ -0,0 +1,485 @@
|
|||
// https://github.com/paulirish/lite-youtube-embed/blob/master/src/lite-yt-embed.js
|
||||
|
||||
// import canAutoPlay from 'can-autoplay'
|
||||
|
||||
|
||||
/**
|
||||
* A lightweight youtube embed. Still should feel the same to the user, just MUCH faster to initialize and paint.
|
||||
*
|
||||
* Thx to these as the inspiration
|
||||
* https://storage.googleapis.com/amp-vs-non-amp/youtube-lazy.html
|
||||
* https://autoplay-youtube-player.glitch.me/
|
||||
*
|
||||
* Once built it, I also found these:
|
||||
* https://github.com/ampproject/amphtml/blob/master/extensions/amp-youtube (👍👍)
|
||||
* https://github.com/Daugilas/lazyYT
|
||||
* https://github.com/vb/lazyframe
|
||||
*/
|
||||
|
||||
function isObject( maybeObject ) {
|
||||
return maybeObject === Object( maybeObject )
|
||||
}
|
||||
class LiteYTEmbed extends HTMLElement {
|
||||
constructor() {
|
||||
// Always call super first in constructor
|
||||
super()
|
||||
|
||||
this._uid = Date.now()
|
||||
this.$refs = {}
|
||||
|
||||
this.$refs['timestamps-scroll-container'] = this.querySelector('.player-timestamps-wrapper')
|
||||
|
||||
this.playerLoaded = false
|
||||
this.player = null
|
||||
this.playing = false
|
||||
this.progressInterval = null
|
||||
this.playerTime = 0
|
||||
this.preconnected = false
|
||||
|
||||
this.highlightedTimestampElement = null
|
||||
}
|
||||
|
||||
connectedCallback() {
|
||||
this.videoId = this.getAttribute('videoid')
|
||||
this.videoDataScript = this.querySelector('.video-data')
|
||||
this.video = JSON.parse( this.videoDataScript.innerHTML )
|
||||
|
||||
this.playerContainer = this.querySelector('.player-container')
|
||||
this.playerPoster = this.querySelector('.player-poster')
|
||||
|
||||
// console.log('canAutoplay from connectedCallback', canAutoplay)
|
||||
|
||||
console.log('video', this.video)
|
||||
console.log('this.playerContainer', this.playerContainer)
|
||||
|
||||
|
||||
// Start watchers here
|
||||
|
||||
|
||||
// On hover (or tap), warm up the TCP connections we're (likely) about to use.
|
||||
this.playerContainer.addEventListener('pointerover', this.warmConnections, {once: true})
|
||||
|
||||
if ( this.hasTimestamps() ) {
|
||||
// Array.from( this.querySelectorAll(`.player-timestamps [time]`) )
|
||||
|
||||
this.timestamps().map( timestamp => {
|
||||
const timestampButton = this.querySelector(`.player-timestamps [time="${timestamp.time}"]`)
|
||||
|
||||
timestampButton.addEventListener('click', e => {
|
||||
this.seekTo(timestamp.inSeconds)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
// Once the user clicks, add the real iframe and drop our play button
|
||||
// TODO: In the future we could be like amp-youtube and silently swap in the iframe during idle time
|
||||
// We'd want to only do this for in-viewport or near-viewport ones: https://github.com/ampproject/amphtml/pull/5003
|
||||
this.playerPoster.addEventListener('click', e => {
|
||||
this.startPlayerLoad()
|
||||
})
|
||||
|
||||
|
||||
// Mounted
|
||||
|
||||
this.detectAutoplay()
|
||||
.then( ({ willAutoplay }) => {
|
||||
console.log('willAutoplay', willAutoplay)
|
||||
|
||||
// If we're allowed to autoplay
|
||||
// then start loading the player
|
||||
if ( willAutoplay === true ) {
|
||||
this.startPlayerLoad()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// // TODO: Support the the user changing the [videoid] attribute
|
||||
// attributeChangedCallback() {
|
||||
// }
|
||||
|
||||
/**
|
||||
* Add a <link rel={preload | preconnect} ...> to the head
|
||||
*/
|
||||
// static c(kind, url, as) {
|
||||
// const linkEl = document.createElement('link')
|
||||
// linkEl.rel = kind
|
||||
// linkEl.href = url
|
||||
// if (as) {
|
||||
// linkEl.as = as
|
||||
// }
|
||||
// document.head.append(linkEl)
|
||||
// }
|
||||
|
||||
/**
|
||||
* Begin pre-connecting to warm up the iframe load
|
||||
* Since the embed's network requests load within its iframe,
|
||||
* preload/prefetch'ing them outside the iframe will only cause double-downloads.
|
||||
* So, the best we can do is warm up a few connections to origins that are in the critical path.
|
||||
*
|
||||
* Maybe `<link rel=preload as=document>` would work, but it's unsupported: http://crbug.com/593267
|
||||
* But TBH, I don't think it'll happen soon with Site Isolation and split caches adding serious complexity.
|
||||
*/
|
||||
// static warmConnections() {
|
||||
// if (LiteYTEmbed.preconnected) return
|
||||
|
||||
// // The iframe document and most of its subresources come right off youtube.com
|
||||
// LiteYTEmbed.addPrefetch('preconnect', 'https://www.youtube-nocookie.com')
|
||||
// // The botguard script is fetched off from google.com
|
||||
// LiteYTEmbed.addPrefetch('preconnect', 'https://www.google.com')
|
||||
|
||||
// // Not certain if these ad related domains are in the critical path. Could verify with domain-specific throttling.
|
||||
// LiteYTEmbed.addPrefetch('preconnect', 'https://googleads.g.doubleclick.net')
|
||||
// LiteYTEmbed.addPrefetch('preconnect', 'https://static.doubleclick.net')
|
||||
|
||||
// LiteYTEmbed.preconnected = true
|
||||
// }
|
||||
|
||||
addIframe() {
|
||||
const classNames = 'absolute inset-0 h-full w-full object-cover'
|
||||
|
||||
// https://www.youtube-nocookie.com/embed/${video.id}?enablejsapi=1&autoplay=1&modestbranding=1&playsinline=1
|
||||
|
||||
// const params = new URLSearchParams(this.getAttribute('params') || [])
|
||||
// params.append('autoplay', '1')
|
||||
|
||||
const iframeEl = document.createElement('iframe')
|
||||
|
||||
this.$refs['frame'] = iframeEl
|
||||
|
||||
iframeEl.width = '100%'
|
||||
iframeEl.height = '100%'
|
||||
// No encoding necessary as [title] is safe. https://cheatsheetseries.owasp.org/cheatsheets/Cross_Site_Scripting_Prevention_Cheat_Sheet.html#:~:text=Safe%20HTML%20Attributes%20include
|
||||
// iframeEl.title = this.playLabel
|
||||
iframeEl.id = this.frameId()
|
||||
iframeEl.classList.add(...classNames.split(' '))
|
||||
iframeEl.frameborder = '0'
|
||||
iframeEl.allow = 'accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture'
|
||||
iframeEl.allowFullscreen = true
|
||||
// AFAIK, the encoding here isn't necessary for XSS, but we'll do it only because this is a URL
|
||||
// https://stackoverflow.com/q/64959723/89484
|
||||
iframeEl.src = `https://www.youtube-nocookie.com/embed/${encodeURIComponent(this.video.id)}?enablejsapi=1&autoplay=1&modestbranding=1&playsinline=1`
|
||||
|
||||
// this.append(iframeEl)
|
||||
this.playerContainer.innerHTML = ''
|
||||
this.playerContainer.append( iframeEl )
|
||||
|
||||
this.classList.add('lyt-activated')
|
||||
|
||||
// Set focus for a11y
|
||||
this.querySelector('iframe').focus()
|
||||
}
|
||||
|
||||
|
||||
// Computed methods
|
||||
|
||||
frameId = () => {
|
||||
return `youtube-player-${this.video.id}-${this._uid}`
|
||||
}
|
||||
|
||||
timestamps = () => {
|
||||
return this.video.timestamps.map( timestamp => {
|
||||
const [ minutes, seconds ] = timestamp.time.split(':')
|
||||
|
||||
return {
|
||||
...timestamp,
|
||||
inSeconds: (minutes * 60) + Number(seconds)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
hasTimestamps = () => {
|
||||
return this.timestamps().length > 0
|
||||
}
|
||||
|
||||
hasPlayer = () => {
|
||||
return this.player !== null
|
||||
}
|
||||
|
||||
activeTimestamp = () => {
|
||||
const currentTime = this.playerTime// / 100
|
||||
|
||||
const reversesTimestamps = [
|
||||
...this.timestamps()
|
||||
]
|
||||
|
||||
// reversesTimestamps.reverse()
|
||||
|
||||
let foundTimestamp = null
|
||||
|
||||
for (const timestamp of reversesTimestamps) {
|
||||
const hasStarted = currentTime > 1
|
||||
const currentTimeisAfterPreviousTimestamp = (foundTimestamp !== null) ? currentTime > foundTimestamp.inSeconds : true
|
||||
// const isPastCurrentTime = currentTime > timestamp.inSeconds
|
||||
// const isBeforeCurrentTime = currentTime > timestamp.inSeconds
|
||||
const currentTimeIsBeforeThisTimestamp = currentTime < timestamp.inSeconds
|
||||
|
||||
if (currentTimeisAfterPreviousTimestamp && currentTimeIsBeforeThisTimestamp) {
|
||||
return foundTimestamp
|
||||
}
|
||||
|
||||
foundTimestamp = timestamp
|
||||
}
|
||||
|
||||
// No active timestamp
|
||||
return null
|
||||
}
|
||||
|
||||
highlightActiveTimestamp = () => {
|
||||
const activeClassList = 'border-opacity-100 bg-darkest'
|
||||
const inactiveClassList = 'border-opacity-0 neumorphic-shadow-inner'
|
||||
|
||||
const activeTimestamp = this.activeTimestamp()
|
||||
|
||||
// If there's no active timestamp
|
||||
// then stop
|
||||
if ( activeTimestamp === null ) return
|
||||
|
||||
// console.log('activeTimestamp', activeTimestamp)
|
||||
|
||||
if ( isObject( this.highlightedTimestampElement ) && this.highlightedTimestampElement.time === activeTimestamp.time) return
|
||||
|
||||
// Change active timestamp
|
||||
|
||||
const newActiveTimestamp = this.querySelector(`[time="${activeTimestamp.time}"]`)
|
||||
|
||||
// If there's already a highlited time stamp
|
||||
// then unhighlight it
|
||||
if ( isObject( this.highlightedTimestampElement ) ) {
|
||||
this.highlightedTimestampElement.classList.remove(...activeClassList.split(' '))
|
||||
this.highlightedTimestampElement.classList.add(...inactiveClassList.split(' '))
|
||||
}
|
||||
|
||||
newActiveTimestamp.classList.remove(...inactiveClassList.split(' '))
|
||||
newActiveTimestamp.classList.add(...activeClassList.split(' '))
|
||||
|
||||
// Scroll to new timestamp
|
||||
this.scrollToTimestampButton( newActiveTimestamp )
|
||||
|
||||
this.highlightedTimestampElement = newActiveTimestamp
|
||||
|
||||
// console.log('newActiveTimestamp', newActiveTimestamp)
|
||||
// console.log('this.highlightedTimestampElement', this.highlightedTimestampElement)
|
||||
}
|
||||
|
||||
scrollToTimestampButton ( timestampButton ) {
|
||||
|
||||
// If timestamp button doesn't exist
|
||||
// then stop
|
||||
if ( !isObject( timestampButton ) ) return
|
||||
|
||||
const timestampsScroller = this.$refs['timestamps-scroll-container']
|
||||
// const [ timestampButton ] = this.$refs[`timestamp-${timestamp.time}`]
|
||||
|
||||
// https://stackoverflow.com/a/63773123/1397641
|
||||
const newScrollPosition = timestampButton.offsetLeft - timestampsScroller.offsetLeft
|
||||
|
||||
// console.log('timestampsScroller', timestampsScroller)
|
||||
// console.log('timestampButton', timestampButton)
|
||||
// console.log('newScrollPosition', newScrollPosition)
|
||||
|
||||
timestampsScroller.scroll({ left: newScrollPosition, behavior: 'smooth' })
|
||||
}
|
||||
|
||||
detectAutoplay = async () => {
|
||||
|
||||
// if ( !process.client ) return { willAutoplay: false }
|
||||
|
||||
// const { default: canAutoPlay } = await import('can-autoplay')
|
||||
|
||||
const willAutoplay = await canAutoplay.video()
|
||||
// const willAutoplayMuted = await canAutoPlay.video({ muted: true, inline: true })
|
||||
|
||||
return {
|
||||
willAutoplay: willAutoplay.result
|
||||
}
|
||||
}
|
||||
|
||||
seekTo = async (timestampInSeconds) => {
|
||||
|
||||
if (this.playerLoaded === false) {
|
||||
await this.startPlayerLoad()
|
||||
}
|
||||
|
||||
this.player.seekTo(timestampInSeconds)
|
||||
}
|
||||
|
||||
// async playVideo() {
|
||||
|
||||
// if (this.playerLoaded === false) {
|
||||
// await this.startPlayerLoad()
|
||||
// }
|
||||
|
||||
// this.$nextTick(() => {
|
||||
// // console.log('this.player', JSON.stringify(this.player))
|
||||
// this.player.playVideo()
|
||||
// })
|
||||
// },
|
||||
|
||||
addPrefetch = (kind, url, as) => {
|
||||
// console.log('prefetching', url)
|
||||
|
||||
const linkEl = document.createElement('link')
|
||||
|
||||
linkEl.rel = kind
|
||||
linkEl.href = url
|
||||
|
||||
if (as) {
|
||||
linkEl.as = as;
|
||||
}
|
||||
|
||||
document.head.append(linkEl)
|
||||
}
|
||||
|
||||
warmConnections = () => {
|
||||
if (this.preconnected) return
|
||||
|
||||
console.log('Warming connections')
|
||||
|
||||
// The iframe document and most of its subresources come right off youtube.com
|
||||
this.addPrefetch('preconnect', 'https://www.youtube-nocookie.com')
|
||||
// The botguard script is fetched off from google.com
|
||||
this.addPrefetch('preconnect', 'https://www.google.com')
|
||||
|
||||
// Not certain if these ad related domains are in the critical path. Could verify with domain-specific throttling.
|
||||
this.addPrefetch('preconnect', 'https://googleads.g.doubleclick.net')
|
||||
this.addPrefetch('preconnect', 'https://static.doubleclick.net')
|
||||
|
||||
this.preconnected = true
|
||||
}
|
||||
|
||||
startPlayerLoad = async () => {
|
||||
// console.log('Starting player load')
|
||||
|
||||
this.addIframe()
|
||||
|
||||
this.playerLoaded = true
|
||||
|
||||
await this.initializePlayer()
|
||||
|
||||
// this.$nextTick(() => {
|
||||
// this.initializePlayer()
|
||||
// })
|
||||
}
|
||||
|
||||
initializePlayer = async () => {
|
||||
console.log('Initializing player')
|
||||
|
||||
// Clear player
|
||||
this.player = null
|
||||
|
||||
// Clear progession interval
|
||||
clearInterval(this.progressInterval)
|
||||
|
||||
// If there are no timestamps
|
||||
// then stop
|
||||
if ( !this.hasTimestamps() ) {
|
||||
console.log('No timestamps. Skipping Youtube API initialization')
|
||||
|
||||
this.playerLoaded = true
|
||||
return
|
||||
}
|
||||
|
||||
if (typeof YT === 'undefined') {
|
||||
await this.initializeApi()
|
||||
}
|
||||
|
||||
const stateHandlers = {
|
||||
// unstarted
|
||||
'-1': () => null,
|
||||
// ended
|
||||
'0': () => null,
|
||||
// playing
|
||||
'1': this.onPlayerPlaying,
|
||||
// paused
|
||||
'2': this.onPlayerPaused,
|
||||
// buffering
|
||||
'3': () => null,
|
||||
// video cued
|
||||
'4': () => null,
|
||||
}
|
||||
|
||||
// console.log('frame', this.$refs['frame'])
|
||||
// console.log('frame id', this.$refs['frame'].id)
|
||||
|
||||
const onReady = () => new Promise( resolve => {
|
||||
|
||||
// console.log('Started onReady')
|
||||
|
||||
this.player = new YT.Player(this.$refs['frame'].id, {
|
||||
events: {
|
||||
'onReady': readyEvent => {
|
||||
// console.log('Resolving onReady')
|
||||
|
||||
this.onPlayerReady( readyEvent )
|
||||
|
||||
resolve( readyEvent )
|
||||
},
|
||||
'onStateChange': event => {
|
||||
// console.log('state changed', event)
|
||||
|
||||
const stateHandler = stateHandlers[String(event.data)]
|
||||
// console.log('stateHandler', stateHandler)
|
||||
stateHandler(event)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
})
|
||||
|
||||
// console.log('Waiting for ready')
|
||||
|
||||
const readyEvent = await onReady()
|
||||
|
||||
// console.log('Youtube Player API ready', readyEvent, JSON.stringify(this.player))
|
||||
}
|
||||
|
||||
initializeApi = () => {
|
||||
return new Promise( resolve => {
|
||||
const tag = document.createElement('script')
|
||||
tag.id = `youtube-api-script-${this._uid}`
|
||||
tag.src = 'https://www.youtube.com/iframe_api'
|
||||
|
||||
const firstScriptTag = document.getElementsByTagName('script')[0]
|
||||
firstScriptTag.parentNode.insertBefore(tag, firstScriptTag)
|
||||
|
||||
|
||||
window.onYouTubeIframeAPIReady = resolve
|
||||
})
|
||||
}
|
||||
|
||||
onPlayerPlaying = () => {
|
||||
// console.log('Player playing')
|
||||
this.playing = true
|
||||
|
||||
this.progressInterval = setInterval(() => {
|
||||
// console.log('this.player.getCurrentTime()', this.player.getCurrentTime())
|
||||
|
||||
// If player is empty
|
||||
// then stop
|
||||
if (this.player === null) {
|
||||
clearInterval(this.progressInterval)
|
||||
return
|
||||
}
|
||||
|
||||
// console.log('this.player', this.player.hasOwnProperty('getCurrentTime'))
|
||||
|
||||
this.playerTime = this.player.getCurrentTime()
|
||||
|
||||
this.highlightActiveTimestamp()
|
||||
|
||||
}, 500)
|
||||
}
|
||||
|
||||
onPlayerPaused = () => {
|
||||
console.log('Player paused')
|
||||
this.playing = false
|
||||
|
||||
clearInterval(this.progressInterval)
|
||||
}
|
||||
|
||||
onPlayerReady (event) {
|
||||
// console.log('Player is ready', event, this.player )
|
||||
}
|
||||
}
|
||||
// Register custom element
|
||||
window.customElements.define('lite-youtube', LiteYTEmbed)
|
||||
45
helpers/structured-data.js
Normal file
45
helpers/structured-data.js
Normal file
|
|
@ -0,0 +1,45 @@
|
|||
function makeFeaturedAppsString ( featuredApps ) {
|
||||
return featuredApps.slice(0, 5).map(app => app.name).join(', ')
|
||||
}
|
||||
|
||||
export function buildVideoStructuredData ( video, featuredApps, options ) {
|
||||
// console.log('video', video)
|
||||
|
||||
const {
|
||||
siteUrl
|
||||
} = options
|
||||
|
||||
const thumbnailUrls = video.thumbnail.srcset.split(',').map( srcSetImage => {
|
||||
const [ imageUrl ] = srcSetImage.trim().split(' ')
|
||||
|
||||
return imageUrl
|
||||
})
|
||||
|
||||
const featuredAppsString = makeFeaturedAppsString( featuredApps )
|
||||
|
||||
const embedUrl = new URL( `${ siteUrl }/embed/rich-results-player` )
|
||||
|
||||
embedUrl.searchParams.append( 'youtube-id', video.id )
|
||||
embedUrl.searchParams.append( 'name', video.name )
|
||||
|
||||
return {
|
||||
"@context": "https://schema.org",
|
||||
// https://developers.google.com/search/docs/data-types/video
|
||||
// https://schema.org/VideoObject
|
||||
"@type": "VideoObject",
|
||||
"name": video.name,
|
||||
"description": `Includes the following apps: ${featuredAppsString}`,
|
||||
"thumbnailUrl": thumbnailUrls,
|
||||
// https://en.wikipedia.org/wiki/ISO_8601
|
||||
"uploadDate": video.lastUpdated.raw,
|
||||
// "duration": "PT1M54S", // Need to updaet Youtube API Request for this
|
||||
// "contentUrl": "https://www.example.com/video/123/file.mp4",
|
||||
"embedUrl": embedUrl.href,
|
||||
// "interactionStatistic": {
|
||||
// "@type": "InteractionCounter",
|
||||
// "interactionType": { "@type": "http://schema.org/WatchAction" },
|
||||
// "userInteractionCount": 5647018
|
||||
// },
|
||||
// "regionsAllowed": "US,NL"
|
||||
}
|
||||
}
|
||||
|
|
@ -1,7 +1,7 @@
|
|||
import fs from 'fs'
|
||||
import { JSDOM } from 'jsdom'
|
||||
|
||||
import config from '../nuxt.config'
|
||||
import config from '../nuxt.config.js'
|
||||
|
||||
|
||||
console.log('Running Default Layout file')
|
||||
|
|
@ -94,6 +94,9 @@ const cleanNuxtLayout = ( layout ) => {
|
|||
// Set link tags
|
||||
document.querySelector('title').insertAdjacentHTML('afterend', templateVar('link-tags') )
|
||||
|
||||
// Add meta tags after title node
|
||||
document.querySelector('title').insertAdjacentHTML('afterend', templateVar('structured-data') )
|
||||
|
||||
// Add meta tags after title node
|
||||
document.querySelector('title').insertAdjacentHTML('afterend', templateVar('meta-tags') )
|
||||
|
||||
|
|
@ -172,11 +175,30 @@ class DefaultLayout {
|
|||
return Object.values(meta).join('')
|
||||
}
|
||||
|
||||
generateLinkTags = ( pageLinkTags = [] ) => {
|
||||
generateStructuredData = function ( renderData ) {
|
||||
|
||||
const {
|
||||
structuredData = null
|
||||
} = renderData
|
||||
|
||||
// console.log('renderData', Object.keys(renderData))
|
||||
|
||||
if ( structuredData === null ) return ''
|
||||
|
||||
const structuredDataJson = JSON.stringify( structuredData )
|
||||
|
||||
return `<script type="application/ld+json">${ structuredDataJson }</script>`
|
||||
}
|
||||
|
||||
generateLinkTags = ( renderData ) => {
|
||||
|
||||
const {
|
||||
headLinkTags = []
|
||||
} = renderData
|
||||
|
||||
const linkTags = {
|
||||
...defaultLinkTags,
|
||||
...Object.fromEntries(pageLinkTags.map( mapLinkTag ))
|
||||
...Object.fromEntries( headLinkTags.map( mapLinkTag ) )
|
||||
}
|
||||
|
||||
return Object.values( linkTags ).join('')
|
||||
|
|
@ -205,12 +227,15 @@ class DefaultLayout {
|
|||
|
||||
// Set link tags
|
||||
// this.generateLinkTags()
|
||||
workingLayoutString = workingLayoutString.replace( templateVar('link-tags'), this.generateLinkTags() )
|
||||
workingLayoutString = workingLayoutString.replace( templateVar('link-tags'), this.generateLinkTags( data ) )
|
||||
|
||||
// Add meta tags after title node
|
||||
// this.generateMetaTags( data )
|
||||
workingLayoutString = workingLayoutString.replace( templateVar('meta-tags'), this.generateMetaTags( data ) )
|
||||
|
||||
// Add structured data
|
||||
workingLayoutString = workingLayoutString.replace( templateVar('structured-data'), this.generateStructuredData( data ) )
|
||||
|
||||
// Set page css
|
||||
// document.querySelector('head').insertAdjacentHTML('beforeend', this.getCss() )
|
||||
|
||||
|
|
|
|||
40
package-lock.json
generated
40
package-lock.json
generated
|
|
@ -16,6 +16,7 @@
|
|||
"can-autoplay": "^3.0.0",
|
||||
"chance": "^1.1.7",
|
||||
"cross-env": "^5.2.0",
|
||||
"esbuild": "^0.11.20",
|
||||
"jsdom": "^16.4.0",
|
||||
"lazysizes": "^5.3.0-beta1",
|
||||
"markdown-it": "^11.0.1",
|
||||
|
|
@ -26,6 +27,7 @@
|
|||
"pretty-bytes": "^5.5.0",
|
||||
"scroll-into-view-if-needed": "^2.2.26",
|
||||
"slugify": "^1.4.6",
|
||||
"terser": "^4.8.0",
|
||||
"vue-full-screen-file-drop": "^2.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
|
@ -5999,8 +6001,7 @@
|
|||
"node_modules/buffer-from": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.1.tgz",
|
||||
"integrity": "sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A==",
|
||||
"dev": true
|
||||
"integrity": "sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A=="
|
||||
},
|
||||
"node_modules/buffer-json": {
|
||||
"version": "2.0.0",
|
||||
|
|
@ -6795,8 +6796,7 @@
|
|||
"node_modules/commander": {
|
||||
"version": "2.20.3",
|
||||
"resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz",
|
||||
"integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==",
|
||||
"dev": true
|
||||
"integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ=="
|
||||
},
|
||||
"node_modules/commondir": {
|
||||
"version": "1.0.1",
|
||||
|
|
@ -9128,6 +9128,15 @@
|
|||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/esbuild": {
|
||||
"version": "0.11.20",
|
||||
"resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.11.20.tgz",
|
||||
"integrity": "sha512-QOZrVpN/Yz74xfat0H6euSgn3RnwLevY1mJTEXneukz1ln9qB+ieaerRMzSeETpz/UJWsBMzRVR/andBht5WKw==",
|
||||
"hasInstallScript": true,
|
||||
"bin": {
|
||||
"esbuild": "bin/esbuild"
|
||||
}
|
||||
},
|
||||
"node_modules/escalade": {
|
||||
"version": "3.1.1",
|
||||
"resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz",
|
||||
|
|
@ -22997,7 +23006,6 @@
|
|||
"version": "0.5.19",
|
||||
"resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.19.tgz",
|
||||
"integrity": "sha512-Wonm7zOCIJzBGQdB+thsPar0kYuCIzYvxZwlBa87yi/Mdjv7Tip2cyVbLj5o0cFPN4EVkuTwb3GDDyUx2DGnGw==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"buffer-from": "^1.0.0",
|
||||
"source-map": "^0.6.0"
|
||||
|
|
@ -23007,7 +23015,6 @@
|
|||
"version": "0.6.1",
|
||||
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
|
||||
"integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
|
|
@ -23882,7 +23889,6 @@
|
|||
"version": "4.8.0",
|
||||
"resolved": "https://registry.npmjs.org/terser/-/terser-4.8.0.tgz",
|
||||
"integrity": "sha512-EAPipTNeWsb/3wLPeup1tVPaXfIaU68xMnVdPafIL1TV05OhASArYyIfFvnvJCNrR2NIOvDVNNTFRa+Re2MWyw==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"commander": "^2.20.0",
|
||||
"source-map": "~0.6.1",
|
||||
|
|
@ -23994,7 +24000,6 @@
|
|||
"version": "0.6.1",
|
||||
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
|
||||
"integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
|
|
@ -31632,8 +31637,7 @@
|
|||
"buffer-from": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.1.tgz",
|
||||
"integrity": "sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A==",
|
||||
"dev": true
|
||||
"integrity": "sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A=="
|
||||
},
|
||||
"buffer-json": {
|
||||
"version": "2.0.0",
|
||||
|
|
@ -32280,8 +32284,7 @@
|
|||
"commander": {
|
||||
"version": "2.20.3",
|
||||
"resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz",
|
||||
"integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==",
|
||||
"dev": true
|
||||
"integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ=="
|
||||
},
|
||||
"commondir": {
|
||||
"version": "1.0.1",
|
||||
|
|
@ -34229,6 +34232,11 @@
|
|||
"is-symbol": "^1.0.2"
|
||||
}
|
||||
},
|
||||
"esbuild": {
|
||||
"version": "0.11.20",
|
||||
"resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.11.20.tgz",
|
||||
"integrity": "sha512-QOZrVpN/Yz74xfat0H6euSgn3RnwLevY1mJTEXneukz1ln9qB+ieaerRMzSeETpz/UJWsBMzRVR/andBht5WKw=="
|
||||
},
|
||||
"escalade": {
|
||||
"version": "3.1.1",
|
||||
"resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz",
|
||||
|
|
@ -45481,7 +45489,6 @@
|
|||
"version": "0.5.19",
|
||||
"resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.19.tgz",
|
||||
"integrity": "sha512-Wonm7zOCIJzBGQdB+thsPar0kYuCIzYvxZwlBa87yi/Mdjv7Tip2cyVbLj5o0cFPN4EVkuTwb3GDDyUx2DGnGw==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"buffer-from": "^1.0.0",
|
||||
"source-map": "^0.6.0"
|
||||
|
|
@ -45490,8 +45497,7 @@
|
|||
"source-map": {
|
||||
"version": "0.6.1",
|
||||
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
|
||||
"integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
|
||||
"dev": true
|
||||
"integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g=="
|
||||
}
|
||||
}
|
||||
},
|
||||
|
|
@ -46198,7 +46204,6 @@
|
|||
"version": "4.8.0",
|
||||
"resolved": "https://registry.npmjs.org/terser/-/terser-4.8.0.tgz",
|
||||
"integrity": "sha512-EAPipTNeWsb/3wLPeup1tVPaXfIaU68xMnVdPafIL1TV05OhASArYyIfFvnvJCNrR2NIOvDVNNTFRa+Re2MWyw==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"commander": "^2.20.0",
|
||||
"source-map": "~0.6.1",
|
||||
|
|
@ -46208,8 +46213,7 @@
|
|||
"source-map": {
|
||||
"version": "0.6.1",
|
||||
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
|
||||
"integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
|
||||
"dev": true
|
||||
"integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g=="
|
||||
}
|
||||
}
|
||||
},
|
||||
|
|
|
|||
|
|
@ -14,6 +14,9 @@
|
|||
"generate-eleventy": "node --max-old-space-size=60000 -r esm node_modules/.bin/eleventy --quiet",
|
||||
"generate-postcss": "ENV=production postcss assets/css/tailwind.css --o static/tailwind.css",
|
||||
"dev-eleventy": "node --max-old-space-size=60000 -r esm node_modules/.bin/eleventy --quiet --watch --serve",
|
||||
"dev-eleventy-debug": "DEBUG=Eleventy* node --max-old-space-size=60000 -r esm node_modules/.bin/eleventy --quiet --watch --serve",
|
||||
"dev-eleventy-benchmark": "DEBUG=Eleventy:Benchmark* node --max-old-space-size=60000 -r esm node_modules/.bin/eleventy --quiet --watch --serve",
|
||||
"dev-eleventy-computed": "DEBUG=Eleventy:ComputedData* node --max-old-space-size=60000 -r esm node_modules/.bin/eleventy --quiet --watch --serve",
|
||||
"lint": "eslint --ext .js,.vue --ignore-path .gitignore .",
|
||||
"lint:fix": "eslint --fix --ext .js,.vue --ignore-path .gitignore .",
|
||||
"precommit": "npm run lint",
|
||||
|
|
@ -29,6 +32,7 @@
|
|||
"can-autoplay": "^3.0.0",
|
||||
"chance": "^1.1.7",
|
||||
"cross-env": "^5.2.0",
|
||||
"esbuild": "^0.11.20",
|
||||
"jsdom": "^16.4.0",
|
||||
"lazysizes": "^5.3.0-beta1",
|
||||
"markdown-it": "^11.0.1",
|
||||
|
|
@ -39,6 +43,7 @@
|
|||
"pretty-bytes": "^5.5.0",
|
||||
"scroll-into-view-if-needed": "^2.2.26",
|
||||
"slugify": "^1.4.6",
|
||||
"terser": "^4.8.0",
|
||||
"vue-full-screen-file-drop": "^2.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ import dotenv from 'dotenv'
|
|||
|
||||
import config from '../nuxt.config.js'
|
||||
|
||||
import { getAppType } from '../helpers/app-derived.js'
|
||||
import { getAppType, getRouteType } from '../helpers/app-derived.js'
|
||||
import { deviceSupportsApp } from '../helpers/devices.js'
|
||||
import { makeLastUpdatedFriendly } from '../helpers/parse-date'
|
||||
|
||||
|
|
@ -23,6 +23,20 @@ export const makeDescription = function ( app ) {
|
|||
return `Latest reported support status of ${ app.name } on Apple Silicon and Apple M1 Processors.`
|
||||
}
|
||||
|
||||
// https://stackoverflow.com/a/15069646/1397641
|
||||
function makeEnglishList ( array, conjunction = 'and' ) {
|
||||
const total = array.length
|
||||
|
||||
if ( total < 3 ) return array.join(` ${conjunction} `)
|
||||
|
||||
array = array.slice()
|
||||
|
||||
// Prepend conjunction to final part
|
||||
array[ total-1 ] = `${ conjunction } ${ array[ total-1 ] }`
|
||||
|
||||
return array.join(', ')
|
||||
}
|
||||
|
||||
export function renderPageLinksHtml ( links ) {
|
||||
return links.map( (link, i) => {
|
||||
|
||||
|
|
@ -56,15 +70,16 @@ export class AppTemplate {
|
|||
|
||||
before: function( data ) {
|
||||
return data.filter( entry => {
|
||||
const appType = getAppType( entry.payload.app )
|
||||
// const [ _, routeType ] = entry.route.split('/')
|
||||
const routeType = getRouteType( entry.route )
|
||||
|
||||
return appType === 'app'
|
||||
return routeType === 'app'
|
||||
})
|
||||
}
|
||||
},
|
||||
|
||||
eleventyComputed: {
|
||||
title: ({ app: { payload: { app } } }) => {
|
||||
title: ({ app: { payload: { app } } }) => {
|
||||
// console.log('data', data)
|
||||
return makeTitle( app )
|
||||
},
|
||||
|
|
@ -83,17 +98,21 @@ export class AppTemplate {
|
|||
}
|
||||
}
|
||||
|
||||
render( data ) {
|
||||
async render( data ) {
|
||||
|
||||
const {
|
||||
app: { payload: { app, relatedVideos = [] } },
|
||||
'device-list': deviceList
|
||||
} = data
|
||||
|
||||
const hasRelatedVideos = relatedVideos.length > 0
|
||||
|
||||
// console.log('deviceList', deviceList)
|
||||
|
||||
// console.log('video.payload', Object.keys(video.payload))
|
||||
|
||||
const hasMultipleAliases = !!app.aliases && app.aliases.length > 1
|
||||
|
||||
const appDeviceSupport = deviceList.map( device => {
|
||||
const supportsApp = deviceSupportsApp( device, app )
|
||||
return {
|
||||
|
|
@ -107,8 +126,12 @@ export class AppTemplate {
|
|||
|
||||
const relatedLinksHtml = renderPageLinksHtml( app.relatedLinks )
|
||||
|
||||
|
||||
const relatedVideosRowHtml = hasRelatedVideos ? await this.boundComponent(VideoRow)( relatedVideos ) : null
|
||||
|
||||
return /* html */`
|
||||
<section class="container py-32">
|
||||
<section class="container space-y-8 py-32">
|
||||
|
||||
<div class="intro-content flex flex-col items-center text-center min-h-3/4-screen md:min-h-0 space-y-8">
|
||||
<h1 class="title text-sm md:text-2xl font-bold">
|
||||
${ data.mainHeading }
|
||||
|
|
@ -117,6 +140,10 @@ export class AppTemplate {
|
|||
${ app.text }
|
||||
</h2>
|
||||
|
||||
${ hasMultipleAliases ? /* html */`
|
||||
<small class="text-xs opacity-75">May also be known as ${ makeEnglishList( app.aliases, 'or' ) }</small>
|
||||
` : '' }
|
||||
|
||||
<div class="subscribe">
|
||||
<iframe src="/embed-subscribe" loading="lazy" style="width: 350px; height: 150px;" class="-my-10"></iframe>
|
||||
</div>
|
||||
|
|
@ -148,7 +175,7 @@ export class AppTemplate {
|
|||
</div>
|
||||
</div>
|
||||
|
||||
${ relatedVideos.length > 0 ? /* html */`
|
||||
${ hasRelatedVideos ? /* html */`
|
||||
<div
|
||||
class="related-videos w-full"
|
||||
>
|
||||
|
|
@ -156,7 +183,7 @@ export class AppTemplate {
|
|||
Related Videos
|
||||
</h2>
|
||||
|
||||
${ this.boundComponent(VideoRow)( relatedVideos ) }
|
||||
${ relatedVideosRowHtml }
|
||||
|
||||
</div>
|
||||
` : '' }
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ import dotenv from 'dotenv'
|
|||
|
||||
import config from '../nuxt.config.js'
|
||||
|
||||
import { getAppType } from '../helpers/app-derived.js'
|
||||
import { getAppType, getRouteType } from '../helpers/app-derived.js'
|
||||
|
||||
import { AppTemplate } from './app.11ty.js'
|
||||
|
||||
|
|
@ -33,9 +33,9 @@ class FormulaTemplate extends AppTemplate {
|
|||
alias: 'app',
|
||||
before: function( data ) {
|
||||
return data.filter( entry => {
|
||||
const appType = getAppType( entry.payload.app )
|
||||
const routeType = getRouteType( entry.route )
|
||||
|
||||
return appType === 'formula'
|
||||
return routeType === 'formula'
|
||||
})
|
||||
}
|
||||
},
|
||||
|
|
|
|||
|
|
@ -1,27 +1,56 @@
|
|||
import dotenv from 'dotenv'
|
||||
|
||||
import config from '../nuxt.config'
|
||||
import config from '../nuxt.config.js'
|
||||
|
||||
import VideoPlayer from '../components-eleventy/video/player.js'
|
||||
import VideoRow from '../components-eleventy/video/row.js'
|
||||
import { isVideo } from '../helpers/app-derived'
|
||||
|
||||
import { getRouteType } from '../helpers/app-derived.js'
|
||||
import { buildVideoStructuredData } from '../helpers/structured-data.js'
|
||||
|
||||
// Setup dotenv
|
||||
dotenv.config()
|
||||
|
||||
export const makeTitle = function ( video ) {
|
||||
return `${ video.name } - ${ config.head.title }`
|
||||
export const myChannelId = 'UCB3jOb5QVjX7lYecvyCoTqQ'
|
||||
|
||||
export const makeTitle = function ( name ) {
|
||||
// console.log('tvEntry', tvEntry)
|
||||
|
||||
return `${ name } - ${ config.head.title }`
|
||||
}
|
||||
|
||||
export const makeDescription = function ( video ) {
|
||||
if ( video.payload.featuredApps.length === 0 ) return 'Apple Silicon performance and support videos'
|
||||
export const makeDescription = function ( tvEntry ) {
|
||||
if ( tvEntry.payload.featuredApps.length === 0 ) return 'Apple Silicon performance and support videos'
|
||||
|
||||
const featuredAppsString = video.payload.featuredApps.slice(0, 5).map(app => app.name).join(', ')
|
||||
const featuredAppsString = tvEntry.payload.featuredApps.slice(0, 5).map(app => app.name).join(', ')
|
||||
|
||||
// console.log('video.payload.featuredApps', video.payload.featuredApps)
|
||||
// console.log('tvEntry.payload.featuredApps', tvEntry.payload.featuredApps)
|
||||
|
||||
return `Apple Silicon performance and support videos for ${ featuredAppsString }`
|
||||
}
|
||||
|
||||
|
||||
function renderFeaturedApps ( featuredApps ) {
|
||||
return /* html */`
|
||||
<div
|
||||
class="related-apps w-full"
|
||||
>
|
||||
<h2 class="subtitle text-xl md:text-2xl font-bold mb-3">
|
||||
Related Apps
|
||||
</h2>
|
||||
<div class="featured-apps overflow-x-auto overflow-y-visible whitespace-no-wrap py-2 space-x-2">
|
||||
${ featuredApps.map( app => ( /* html */`
|
||||
<a
|
||||
href="${ app.endpoint }"
|
||||
role="button"
|
||||
class="relative inline-flex items-center rounded-md px-4 py-2 leading-5 font-bold text-white border border-transparent focus:outline-none focus:border-indigo-600 neumorphic-shadow-inner bg-darker hover:bg-indigo-400 active:bg-indigo-600 transition duration-150 ease-in-out inline-block text-xs rounded-lg py-1 px-2"
|
||||
>${ app.name }</a>
|
||||
`) ).join('') }
|
||||
</div>
|
||||
</div>
|
||||
`
|
||||
}
|
||||
|
||||
class TV {
|
||||
// or `async data() {`
|
||||
// or `get data() {`
|
||||
|
|
@ -32,62 +61,135 @@ class TV {
|
|||
pagination: {
|
||||
data: 'eleventy-endpoints',
|
||||
size: 1,
|
||||
alias: 'payload',
|
||||
before: function( data ) {
|
||||
return data.filter( entry => {
|
||||
return entry.payload.hasOwnProperty('video') && isVideo( entry.payload.video )
|
||||
alias: 'tvEntry',
|
||||
before: function( endpoint ) {
|
||||
// console.log('Before runs')
|
||||
|
||||
return endpoint.filter( entry => {
|
||||
const routeType = getRouteType( entry.route )
|
||||
|
||||
return routeType === 'tv'
|
||||
})
|
||||
},
|
||||
},
|
||||
|
||||
// tags: [ 'tv' ],
|
||||
|
||||
eleventyComputed: {
|
||||
title: data => {
|
||||
// Declare dependencies for Eleventy
|
||||
// https://www.11ty.dev/docs/data-computed/#declaring-your-dependencies
|
||||
data.tvEntry
|
||||
|
||||
return makeTitle( data.tvEntry.payload.video.name )
|
||||
},
|
||||
description: ( data ) => {
|
||||
// Declare dependencies for Eleventy
|
||||
// https://www.11ty.dev/docs/data-computed/#declaring-your-dependencies
|
||||
data.tvEntry
|
||||
|
||||
return makeDescription( data.tvEntry )
|
||||
},
|
||||
|
||||
headLinkTags: data => {
|
||||
// Declare dependencies for Eleventy
|
||||
// https://www.11ty.dev/docs/data-computed/#declaring-your-dependencies
|
||||
data.tvEntry
|
||||
|
||||
return [
|
||||
// Preload video thumbnail
|
||||
// <link rel="preload" as="image" href="img.png" />
|
||||
{
|
||||
'rel': 'preload',
|
||||
'as': 'image',
|
||||
'href': `https://i.ytimg.com/vi_webp/${ data.tvEntry.payload.video.id }/sddefault.webp`
|
||||
},
|
||||
]
|
||||
},
|
||||
|
||||
structuredData: data => {
|
||||
// Declare dependencies for Eleventy
|
||||
// https://www.11ty.dev/docs/data-computed/#declaring-your-dependencies
|
||||
data.tvEntry
|
||||
|
||||
return buildVideoStructuredData( data.tvEntry.payload.video, data.tvEntry.payload.featuredApps, {
|
||||
siteUrl: process.env.URL
|
||||
} )
|
||||
}
|
||||
},
|
||||
|
||||
eleventyComputed: {
|
||||
title: ({ payload: { video } }) => {
|
||||
// console.log('data', data)
|
||||
return makeTitle( video )
|
||||
},
|
||||
description: ({ payload: { video } }) => {
|
||||
return makeDescription( video )
|
||||
},
|
||||
},
|
||||
permalink: ( data ) => {
|
||||
|
||||
permalink: ({ payload: { video } }) => {
|
||||
// console.log('data', data)
|
||||
return `tv/${ video.slug }/`
|
||||
return data.tvEntry.route.substring(1) + '/'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
render({ payload: { video } }) {
|
||||
async render( data ) {
|
||||
|
||||
const {
|
||||
tvEntry: {
|
||||
// route,
|
||||
payload: {
|
||||
video,
|
||||
relatedVideos = [],
|
||||
featuredApps = []
|
||||
}
|
||||
},
|
||||
// 'device-list': deviceList
|
||||
} = data
|
||||
|
||||
|
||||
// console.log('video.payload', Object.keys(video.payload))
|
||||
|
||||
const coverBottomHtml = /* html */`
|
||||
<div class="page-heading h-full flex items-end md:p-4">
|
||||
<h1 class="title text-xs text-left md:text-2xl font-bold">${ video.name }</h1>
|
||||
</div>
|
||||
`
|
||||
|
||||
const playerHtml = await this.boundComponent(VideoPlayer)( video, {
|
||||
coverBottomHtml
|
||||
} )
|
||||
|
||||
const hasFeaturedApps = featuredApps.length > 0
|
||||
const featuredAppsHtml = hasFeaturedApps ? renderFeaturedApps( featuredApps ) : ''
|
||||
|
||||
const rowHtml = await this.boundComponent(VideoRow)( relatedVideos )
|
||||
|
||||
// const rowHtml = renderedRow.join('')
|
||||
|
||||
return /* html */`
|
||||
<section class="container pb-16">
|
||||
<div class="flex flex-col items-center text-center space-y-6">
|
||||
<div class="video-canvas w-screen flex flex-col justify-center items-center bg-black pt-16" style="left:50%;right:50%;margin-left:-50vw;margin-right:-50vw;">
|
||||
<div class="ratio-wrapper w-full max-w-4xl">
|
||||
<div class="relative overflow-hidden w-full pb-16/9">
|
||||
<iframe id="youtube-player-${ video.id }-17212" src="https://www.youtube-nocookie.com/embed/${ video.id }?enablejsapi=1&autoplay=1&modestbranding=1&playsinline=1" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen class="absolute h-full w-full"></iframe>
|
||||
</div>
|
||||
</div>
|
||||
<!---->
|
||||
</div>
|
||||
|
||||
${ playerHtml }
|
||||
|
||||
<div class="md:flex w-full justify-between space-y-4 md:space-y-0 md:px-10">
|
||||
<h1 class="title text-lg md:text-2xl font-bold">${ video.name }</h1>
|
||||
<div class="channel-credit"><a href="https://www.youtube.com/channel/UCptwuAv0XQHo1OQUSaO6NHw" target="_blank" rel="noopener" role="button" class="relative inline-flex items-center rounded-md px-4 py-2 leading-5 font-bold text-white border border-transparent focus:outline-none focus:border-indigo-600 neumorphic-shadow focus:shadow-outline-indigo bg-darker hover:bg-indigo-400 active:bg-indigo-600 transition duration-150 ease-in-out">Subscribe to Max Tech</a></div>
|
||||
|
||||
${ video.channel.id !== myChannelId ? /* html */`
|
||||
<div
|
||||
class="channel-credit"
|
||||
>
|
||||
<a
|
||||
href="https://www.youtube.com/channel/${ video.channel.id }?sub_confirmation=1"
|
||||
target="_blank"
|
||||
rel="noopener"
|
||||
role="button"
|
||||
class="relative inline-flex items-center rounded-md px-4 py-2 leading-5 font-bold text-white border border-transparent focus:outline-none focus:border-indigo-600 neumorphic-shadow focus:shadow-outline-indigo bg-darker hover:bg-indigo-400 active:bg-indigo-600 transition duration-150 ease-in-out"
|
||||
>Subscribe to ${ video.channel.name }</a>
|
||||
</div>
|
||||
` : '' }
|
||||
</div>
|
||||
|
||||
<hr class="w-full">
|
||||
|
||||
<div class="related-apps w-full">
|
||||
<h2 class="subtitle text-xl md:text-2xl font-bold mb-3">Related Apps</h2>
|
||||
<div class="featured-apps overflow-x-auto overflow-y-visible whitespace-no-wrap py-2 space-x-2"><a href="/app/xcode" role="button" class="relative inline-flex items-center rounded-md px-4 py-2 leading-5 font-bold text-white border border-transparent focus:outline-none focus:border-indigo-600 neumorphic-shadow-inner bg-darker hover:bg-indigo-400 active:bg-indigo-600 transition duration-150 ease-in-out inline-block text-xs rounded-lg py-1 px-2">Xcode</a><a href="/app/logic-pro" role="button" class="relative inline-flex items-center rounded-md px-4 py-2 leading-5 font-bold text-white border border-transparent focus:outline-none focus:border-indigo-600 neumorphic-shadow-inner bg-darker hover:bg-indigo-400 active:bg-indigo-600 transition duration-150 ease-in-out inline-block text-xs rounded-lg py-1 px-2">Logic Pro</a><a href="/app/lightroom" role="button" class="relative inline-flex items-center rounded-md px-4 py-2 leading-5 font-bold text-white border border-transparent focus:outline-none focus:border-indigo-600 neumorphic-shadow-inner bg-darker hover:bg-indigo-400 active:bg-indigo-600 transition duration-150 ease-in-out inline-block text-xs rounded-lg py-1 px-2">Lightroom</a><a href="/app/lightroom-classic" role="button" class="relative inline-flex items-center rounded-md px-4 py-2 leading-5 font-bold text-white border border-transparent focus:outline-none focus:border-indigo-600 neumorphic-shadow-inner bg-darker hover:bg-indigo-400 active:bg-indigo-600 transition duration-150 ease-in-out inline-block text-xs rounded-lg py-1 px-2">Lightroom Classic</a><a href="/app/cinebench" role="button" class="relative inline-flex items-center rounded-md px-4 py-2 leading-5 font-bold text-white border border-transparent focus:outline-none focus:border-indigo-600 neumorphic-shadow-inner bg-darker hover:bg-indigo-400 active:bg-indigo-600 transition duration-150 ease-in-out inline-block text-xs rounded-lg py-1 px-2">Cinebench</a><a href="/app/final-cut-pro" role="button" class="relative inline-flex items-center rounded-md px-4 py-2 leading-5 font-bold text-white border border-transparent focus:outline-none focus:border-indigo-600 neumorphic-shadow-inner bg-darker hover:bg-indigo-400 active:bg-indigo-600 transition duration-150 ease-in-out inline-block text-xs rounded-lg py-1 px-2">Final Cut Pro</a><a href="/app/geekbench" role="button" class="relative inline-flex items-center rounded-md px-4 py-2 leading-5 font-bold text-white border border-transparent focus:outline-none focus:border-indigo-600 neumorphic-shadow-inner bg-darker hover:bg-indigo-400 active:bg-indigo-600 transition duration-150 ease-in-out inline-block text-xs rounded-lg py-1 px-2">Geekbench</a></div>
|
||||
</div>
|
||||
${ featuredAppsHtml }
|
||||
|
||||
<div class="related-videos w-full">
|
||||
<h2 class="subtitle text-xl md:text-2xl font-bold mb-3">Related Videos</h2>
|
||||
|
||||
${ this.boundComponent(VideoRow)( video.payload.relatedVideos ) }
|
||||
${ rowHtml }
|
||||
|
||||
</div>
|
||||
|
||||
|
|
|
|||
|
|
@ -81,6 +81,8 @@
|
|||
|
||||
import { getAppEndpoint } from '~/helpers/app-derived.js'
|
||||
|
||||
import { buildVideoStructuredData } from '~/helpers/structured-data.js'
|
||||
|
||||
import LinkButton from '~/components/link-button.vue'
|
||||
import EmailSubscribe from '~/components/email-subscribe.vue'
|
||||
import VideoRow from '~/components/video/row.vue'
|
||||
|
|
@ -93,43 +95,6 @@ function makeFeaturedAppsString ( featuredApps ) {
|
|||
}
|
||||
|
||||
|
||||
function buildVideoStructuredData ( video, featuredApps ) {
|
||||
// console.log('video', video)
|
||||
|
||||
const thumbnailUrls = video.thumbnail.srcset.split(',').map( srcSetImage => {
|
||||
const [ imageUrl ] = srcSetImage.trim().split(' ')
|
||||
|
||||
return imageUrl
|
||||
})
|
||||
|
||||
const featuredAppsString = makeFeaturedAppsString( featuredApps )
|
||||
|
||||
const embedUrl = new URL( `${ this.$config.siteUrl }/embed/rich-results-player` )
|
||||
|
||||
embedUrl.searchParams.append( 'youtube-id', video.id )
|
||||
embedUrl.searchParams.append( 'name', video.name )
|
||||
|
||||
return {
|
||||
"@context": "https://schema.org",
|
||||
// https://developers.google.com/search/docs/data-types/video
|
||||
// https://schema.org/VideoObject
|
||||
"@type": "VideoObject",
|
||||
"name": video.name,
|
||||
"description": `Includes the following apps: ${featuredAppsString}`,
|
||||
"thumbnailUrl": thumbnailUrls,
|
||||
// https://en.wikipedia.org/wiki/ISO_8601
|
||||
"uploadDate": video.lastUpdated.raw,
|
||||
// "duration": "PT1M54S", // Need to updaet Youtube API Request for this
|
||||
// "contentUrl": "https://www.example.com/video/123/file.mp4",
|
||||
"embedUrl": embedUrl.href,
|
||||
// "interactionStatistic": {
|
||||
// "@type": "InteractionCounter",
|
||||
// "interactionType": { "@type": "http://schema.org/WatchAction" },
|
||||
// "userInteractionCount": 5647018
|
||||
// },
|
||||
// "regionsAllowed": "US,NL"
|
||||
}
|
||||
}
|
||||
export default {
|
||||
components: {
|
||||
LinkButton,
|
||||
|
|
@ -184,7 +149,9 @@ export default {
|
|||
getAppEndpoint
|
||||
},
|
||||
head() {
|
||||
const structuredData = buildVideoStructuredData.bind(this)( this.video, this.featuredApps )
|
||||
const structuredData = buildVideoStructuredData( this.video, this.featuredApps, {
|
||||
siteUrl: this.$config.siteUrl
|
||||
} )
|
||||
|
||||
|
||||
return {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue