diff --git a/.prettierignore b/.prettierignore new file mode 100644 index 00000000..889bcbe1 --- /dev/null +++ b/.prettierignore @@ -0,0 +1,2 @@ +**/*.html +**/*.md \ No newline at end of file diff --git a/.prettierrc.json b/.prettierrc.json new file mode 100644 index 00000000..9c1044f6 --- /dev/null +++ b/.prettierrc.json @@ -0,0 +1,3 @@ +{ + "bracketSameLine": true +} diff --git a/audiocontext-setsinkid/style.css b/audiocontext-setsinkid/style.css index c370c14a..0a6b7073 100644 --- a/audiocontext-setsinkid/style.css +++ b/audiocontext-setsinkid/style.css @@ -1,46 +1,53 @@ -button, select, label { - font-weight: 400; - line-height: 1.5; - font-size: 1rem; - font-family: sans-serif; - } - - button, select { - padding: 6px 12px; - text-align: center; - background-color: transparent; - border-radius: .25rem; - } - - button { - color: #0d6efd; - border: 1px solid transparent; - border-color: #0d6efd; - cursor: pointer; - outline: 0; - display: inline-block; - transition: color .15s ease-in-out,background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out; - margin-bottom: 20px; - } - - button:hover { - color: #fff; - background-color: #0d6efd; - border-color: #0d6efd; - } - - button:disabled { - color: #999; - border-color: #999; - background: white; - cursor: not-allowed; - } - - label { - padding-right: 24px; - } - - .select-container p { - color: red; - margin-bottom: 36px; - } \ No newline at end of file +button, +select, +label { + font-weight: 400; + line-height: 1.5; + font-size: 1rem; + font-family: sans-serif; +} + +button, +select { + padding: 6px 12px; + text-align: center; + background-color: transparent; + border-radius: 0.25rem; +} + +button { + color: #0d6efd; + border: 1px solid transparent; + border-color: #0d6efd; + cursor: pointer; + outline: 0; + display: inline-block; + transition: + color 0.15s ease-in-out, + background-color 0.15s ease-in-out, + border-color 0.15s ease-in-out, + box-shadow 0.15s ease-in-out; + margin-bottom: 20px; +} + +button:hover { + color: #fff; + background-color: #0d6efd; + border-color: #0d6efd; +} + +button:disabled { + color: #999; + border-color: #999; + background: white; + cursor: not-allowed; +} + +label { + padding-right: 24px; +} + +.select-container p { + color: red; + margin-bottom: 36px; +} diff --git a/channel-messaging-multimessage/style.css b/channel-messaging-multimessage/style.css index 0c10a11d..ea128420 100644 --- a/channel-messaging-multimessage/style.css +++ b/channel-messaging-multimessage/style.css @@ -1,6 +1,7 @@ -html,body { - margin: 0; - font-family: 'Open Sans Condensed', sans-serif; +html, +body { + margin: 0; + font-family: "Open Sans Condensed", sans-serif; } body { @@ -15,7 +16,7 @@ form { } form input { - width: 55%; + width: 55%; } form label { @@ -24,7 +25,7 @@ form label { } form button { - width: 60%; + width: 60%; display: block; margin: 10px auto 0; } @@ -33,14 +34,15 @@ p { margin: 10px 0; } -h1, p { +h1, +p { text-align: center; } h1 { - font-family: 'Lobster Two', cursive; + font-family: "Lobster Two", cursive; } ul { width: 90%; -} \ No newline at end of file +} diff --git a/css-painting/half-highlight-fixed-size/style.css b/css-painting/half-highlight-fixed-size/style.css index 9697b809..26f56448 100644 --- a/css-painting/half-highlight-fixed-size/style.css +++ b/css-painting/half-highlight-fixed-size/style.css @@ -1,4 +1,3 @@ .fancy { - background-image: paint(headerHighlight); - } - \ No newline at end of file + background-image: paint(headerHighlight); +} diff --git a/document-picture-in-picture/main.css b/document-picture-in-picture/main.css index 06f31119..ea4ed45c 100644 --- a/document-picture-in-picture/main.css +++ b/document-picture-in-picture/main.css @@ -1,13 +1,33 @@ #contents { - width: 600px; - font: 14px "Open Sans", sans-serif; + width: 600px; + font: + 14px "Open Sans", + sans-serif; } #credits { - padding: 0 0 10px 0; - font: italic 10px "Open Sans", sans-serif; + padding: 0 0 10px 0; + font: + italic 10px "Open Sans", + sans-serif; } #in-pip-message { - display: none; + display: none; +} + +@media (display-mode: picture-in-picture) and (prefers-color-scheme: light) { + body { + background: antiquewhite; + } +} + +@media (display-mode: picture-in-picture) and (prefers-color-scheme: dark) { + body { + background: #333; + } + + a { + color: antiquewhite; + } } diff --git a/document-picture-in-picture/main.js b/document-picture-in-picture/main.js index 512e958a..d0b8635a 100644 --- a/document-picture-in-picture/main.js +++ b/document-picture-in-picture/main.js @@ -22,7 +22,7 @@ async function togglePictureInPicture() { // Open a Picture-in-Picture window. const pipWindow = await window.documentPictureInPicture.requestWindow({ width: videoPlayer.clientWidth, - height: videoPlayer.clientHeight, + height: videoPlayer.clientHeight + 50, }); // Add pagehide listener to handle the case of the pip window being closed using the browser X button diff --git a/edit-context/html-editor/converter.js b/edit-context/html-editor/converter.js new file mode 100644 index 00000000..4c0b844b --- /dev/null +++ b/edit-context/html-editor/converter.js @@ -0,0 +1,96 @@ +// The EditContext object only knows about a plain text string and about +// character offsets. However, our editor view renders the text by using +// DOM nodes. So we sometimes need to convert between the two. +// This function converts from a DOM selection object to character offsets. +export function fromSelectionToOffsets(selection, editorEl) { + const treeWalker = document.createTreeWalker(editorEl, NodeFilter.SHOW_TEXT); + + let anchorNodeFound = false; + let extentNodeFound = false; + let anchorOffset = 0; + let extentOffset = 0; + + while (treeWalker.nextNode()) { + const node = treeWalker.currentNode; + if (node === selection.anchorNode) { + anchorNodeFound = true; + anchorOffset += selection.anchorOffset; + } + + if (node === selection.extentNode) { + extentNodeFound = true; + extentOffset += selection.extentOffset; + } + + if (!anchorNodeFound) { + anchorOffset += node.textContent.length; + } + if (!extentNodeFound) { + extentOffset += node.textContent.length; + } + } + + if (!anchorNodeFound || !extentNodeFound) { + return null; + } + + return { start: anchorOffset, end: extentOffset }; +} + +// The EditContext object only knows about a plain text string and about +// character offsets. However, our editor view renders the text by using +// DOM nodes. So we sometimes need to convert between the two. +// This function converts character offsets to a DOM selection object. +export function fromOffsetsToSelection(start, end, editorEl) { + const treeWalker = document.createTreeWalker(editorEl, NodeFilter.SHOW_TEXT); + + let offset = 0; + let anchorNode = null; + let anchorOffset = 0; + let extentNode = null; + let extentOffset = 0; + + while (treeWalker.nextNode()) { + const node = treeWalker.currentNode; + + if (!anchorNode && offset + node.textContent.length >= start) { + anchorNode = node; + anchorOffset = start - offset; + } + + if (!extentNode && offset + node.textContent.length >= end) { + extentNode = node; + extentOffset = end - offset; + } + + if (anchorNode && extentNode) { + break; + } + + offset += node.textContent.length; + } + + return { anchorNode, anchorOffset, extentNode, extentOffset }; +} + +// The EditContext object only knows about character offsets. But out editor +// view renders HTML tokens as DOM nodes. This function finds DOM node tokens +// that are in the provided EditContext offset range. +export function fromOffsetsToRenderedTokenNodes(renderedTokens, start, end) { + const tokenNodes = []; + + for (let offset = start; offset < end; offset++) { + const token = renderedTokens.find( + (token) => token.pos <= offset && token.pos + token.value.length > offset + ); + if (token) { + tokenNodes.push({ + node: token.node, + nodeOffset: token.pos, + charOffset: offset, + }); + } + } + + return tokenNodes; +} diff --git a/edit-context/html-editor/editor.js b/edit-context/html-editor/editor.js new file mode 100644 index 00000000..728b89ef --- /dev/null +++ b/edit-context/html-editor/editor.js @@ -0,0 +1,222 @@ +import { tokenizeHTML } from "./tokenizer.js"; +import { + fromOffsetsToRenderedTokenNodes, + fromSelectionToOffsets, + fromOffsetsToSelection, +} from "./converter.js"; + +const IS_EDIT_CONTEXT_SUPPORTED = "EditContext" in window; +const IS_CUSTOM_HIGHLIGHT_SUPPORTED = "Highlight" in window; + +// The editor element. +const editorEl = document.getElementById("html-editor"); + +// The current tokens from the html text. +let currentTokens = []; + +// Instances of CSS custom Highlight objects, used to render +// the IME composition text formats. +const imeHighlights = { + "solid-thin": null, + "solid-thick": null, + "dotted-thin": null, + "dotted-thick": null, + "dashed-thin": null, + "dashed-thick": null, + "wavy-thin": null, + "wavy-thick": null, + "squiggle-thin": null, + "squiggle-thick": null, +}; +if (IS_CUSTOM_HIGHLIGHT_SUPPORTED) { + for (const [key, value] of Object.entries(imeHighlights)) { + imeHighlights[key] = new Highlight(); + CSS.highlights.set(`ime-${key}`, imeHighlights[key]); + } +} else { + console.warn( + "Custom highlights are not supported in this browser. IME formats will not be rendered." + ); +} + +(function () { + if (!IS_EDIT_CONTEXT_SUPPORTED) { + editorEl.textContent = + "Sorry, your browser doesn't support the EditContext API. This demo will not work."; + return; + } + + // Instantiate the EditContext object. + const editContext = new EditContext({ + text: "\n
\nhello
How are you? test