Skip to content

Commit 8909539

Browse files
authored
index videos and add external data to search (#1266)
1 parent 5eb5b74 commit 8909539

File tree

13 files changed

+219
-22
lines changed

13 files changed

+219
-22
lines changed

packages/lit-dev-content/.eleventy.js

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -216,6 +216,22 @@ ${content}
216216
return value;
217217
});
218218

219+
eleventyConfig.addFilter('videosToAlgoliaRecords', function (videos) {
220+
return videos.map((video) => {
221+
return {
222+
relativeUrl: video.url,
223+
title: video.title,
224+
heading: '',
225+
text: video.summary,
226+
docType: {
227+
type: 'Video',
228+
tag: 'video',
229+
},
230+
isExternal: true,
231+
};
232+
});
233+
});
234+
219235
const sortDocs = (a, b) => {
220236
if (a.fileSlug == 'docs') {
221237
return -1;

packages/lit-dev-content/site/_data/externalSearchData.json

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,5 +9,49 @@
99
"tag": "other"
1010
},
1111
"isExternal": true
12+
},
13+
{
14+
"relativeUrl": "https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/attachInternals",
15+
"title": "HTMLElement: attachInternals() method",
16+
"heading": "",
17+
"text": "The HTMLElement.attachInternals() method returns an ElementInternals object. This method allows a custom element to participate in HTML forms. The ElementInternals interface provides utilities for working with these elements in the same way you would work with any standard HTML form element, and also exposes the Accessibility Object Model to the element.",
18+
"docType": {
19+
"type": "MDN",
20+
"tag": "other"
21+
},
22+
"isExternal": true
23+
},
24+
{
25+
"relativeUrl": "https://webkit.org/blog/13711/elementinternals-and-form-associated-custom-elements/",
26+
"title": "ElementInternals and Form-Associated Custom Elements",
27+
"heading": "",
28+
"text": "In Safari Technology Preview 162 we enabled the support for ElementInternals and the form-associated custom elements by default. Custom elements is a feature which lets web developers create reusable components by defining their own HTML elements without relying on a JavaScript framework. ElementInternals is a new addition to custom elements API, which allows developers to manage a custom element’s internal states such as default ARIA role or ARIA label as well as having custom elements participate in form submissions and validations.",
29+
"docType": {
30+
"type": "WebKit",
31+
"tag": "other"
32+
},
33+
"isExternal": true
34+
},
35+
{
36+
"relativeUrl": "https://web.dev/articles/more-capable-form-controls",
37+
"title": "More capable form controls",
38+
"heading": "",
39+
"text": "With a new event, and custom elements APIs, participating in forms just got a lot easier. Many developers build custom form controls, either to provide controls that aren't built in to the browser, or to customize the look and feel beyond what's possible with the built-in form controls. However, it can be difficult to replicate the features of built-in HTML form controls. Consider some of the features an <input> element gets automatically when you add it to a form: The input is automatically added to the form's list of controls. The input's value is automatically submitted with the form. The input participates in form validation. You can style the input using the :valid and :invalid pseudoclasses. The input is notified when the form is reset, when the form is reloaded, or when the browser tries to autofill form entries. Custom form controls typically have few of these features. Developers can work around some of the limitations in JavaScript, like adding a hidden <input> to a form to participate in form submission. But other features just can't be replicated in JavaScript alone. Two new web features make it easier to build custom form controls, and remove the limitations of current custom controls: The formdata event lets an arbitrary JavaScript object participate in form submission, so you can add form data without using a hidden <input>. The Form-associated custom elements API lets custom elements act more like built-in form controls. These two features can be used to create new kinds of controls that work better.",
40+
"docType": {
41+
"type": "Web.dev",
42+
"tag": "other"
43+
},
44+
"isExternal": true
45+
},
46+
{
47+
"relativeUrl": "https://stackblitz.com/edit/stackblitz-starters-bdtxdy?file=stories%2FButton.js,components%2Fmy-button.js",
48+
"title": "Lit + Storybook Example",
49+
"heading": "",
50+
"text": "Using npx storybook@latest init and following the prompts for web components, you can generate a Storybook project for your Lit Elements like this project!",
51+
"docType": {
52+
"type": "StackBlitz",
53+
"tag": "other"
54+
},
55+
"isExternal": true
1256
}
1357
]
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
permalink: external-search-data/data.json
3+
---
4+
5+
{% if not env.DEV %}{{ externalSearchData | dump | safe }}{% endif %}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
---
2+
permalink: external-search-data/videos.json
3+
---
4+
5+
{% if not env.DEV %}
6+
{{ videos | videosToAlgoliaRecords | dump | safe }}
7+
{% endif %}

packages/lit-dev-content/src/components/litdev-search-option.ts

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import {LitElement, html, css, PropertyValues} from 'lit';
88
import {property, customElement} from 'lit/decorators.js';
99
import {hashtagIcon} from '../icons/hashtag-icon.js';
1010
import {paperDocumentIcon} from '../icons/paper-document-icon.js';
11+
import {openInNewIcon} from '../icons/open-in-new-icon.js';
1112
import {renderAlgoliaSnippet} from '../util/render-algolia-suggestions.js';
1213

1314
/**
@@ -33,6 +34,9 @@ export class LitdevSearchOption extends LitElement {
3334
@property({type: Boolean, attribute: true})
3435
checked = false;
3536

37+
@property({type: Boolean})
38+
isExternal = false;
39+
3640
static styles = css`
3741
:host {
3842
display: block;
@@ -107,21 +111,29 @@ export class LitdevSearchOption extends LitElement {
107111
`;
108112

109113
render() {
114+
const showText = this.isSubsection || (this.isExternal && this.text);
110115
return html`
111116
<div class="suggestion">
112117
<div class="icon-wrapper" aria-hidden="true">
113118
${this.isSubsection ? hashtagIcon : paperDocumentIcon}
114119
</div>
115-
<div class="title-and-text ${this.isSubsection ? 'has-text' : ''}">
116-
${this.isSubsection
120+
<div class="title-and-text ${showText ? 'has-text' : ''}">
121+
${showText
117122
? html`<span class="title">
118-
${renderAlgoliaSnippet(this.heading)}
123+
${renderAlgoliaSnippet(this.heading || this.title)}
119124
</span>
120125
<span class="text"> ${renderAlgoliaSnippet(this.text)} </span>`
121126
: html`<span class="title">
122127
${renderAlgoliaSnippet(this.title)}
123128
</span>`}
124129
</div>
130+
${this.isExternal
131+
? html`
132+
<div class="icon-wrapper end" aria-hidden="true">
133+
${openInNewIcon}
134+
</div>
135+
`
136+
: ''}
125137
</div>
126138
`;
127139
}

packages/lit-dev-content/src/components/litdev-search.ts

Lines changed: 22 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,8 @@ import './lazy-svg.js';
1919
/**
2020
* Generic that denotes the type of document.
2121
*/
22-
interface DocType<T extends string, U extends string> {
23-
type: T;
22+
interface DocType<U extends string> {
23+
type: string;
2424
tag: U;
2525
}
2626

@@ -29,11 +29,12 @@ interface DocType<T extends string, U extends string> {
2929
* frontend and used to re-rank results on the frontend.
3030
*/
3131
type DocTypes =
32-
| DocType<'Article', 'article'>
33-
| DocType<'Tutorial', 'tutorial'>
34-
| DocType<'Docs', 'docs'>
35-
| DocType<'API', 'api'>
36-
| DocType<'Other', 'other'>;
32+
| DocType<'article'>
33+
| DocType<'tutorial'>
34+
| DocType<'docs'>
35+
| DocType<'api'>
36+
| DocType<'video'>
37+
| DocType<'other'>;
3738

3839
/**
3940
* Representation of each record indexed by our PageChunker which is published
@@ -49,6 +50,7 @@ interface UserFacingPageData {
4950
text: string;
5051
parentID?: string;
5152
docType: DocTypes;
53+
isExternal?: boolean;
5254
}
5355

5456
/**
@@ -269,7 +271,13 @@ export class LitDevSearch extends LitElement {
269271
${repeat(
270272
suggestionGroup.suggestions,
271273
({objectID}) => objectID,
272-
({relativeUrl, _highlightResult, _snippetResult, parentID}) => {
274+
({
275+
relativeUrl,
276+
_highlightResult,
277+
_snippetResult,
278+
parentID,
279+
isExternal,
280+
}) => {
273281
const title = _highlightResult.title.value;
274282
const heading = _highlightResult.heading.value;
275283
const text = _snippetResult.text.value;
@@ -284,6 +292,7 @@ export class LitDevSearch extends LitElement {
284292
.heading="${heading}"
285293
.text="${text}"
286294
.isSubsection="${!!parentID}"
295+
.isExternal="${!!isExternal}"
287296
role="option"
288297
@pointerenter=${this._onSuggestionHover(suggestionIndex)}
289298
@click="${() => this._navigate(relativeUrl)}"
@@ -583,6 +592,11 @@ export class LitDevSearch extends LitElement {
583592
background-color: #324fff;
584593
}
585594
595+
.group .tag.video {
596+
color: white;
597+
background-color: #eb0000;
598+
}
599+
586600
.group .tag.tutorial {
587601
color: black;
588602
background-color: #40dcff;
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
/**
2+
* @license
3+
* Copyright 2023 Google LLC
4+
* SPDX-License-Identifier: BSD-3-Clause
5+
*/
6+
7+
import {html} from 'lit';
8+
9+
// Source: https://fonts.google.com/icons?selected=Material+Symbols+Outlined:open_in_new:FILL@0;wght@400;GRAD@0;opsz@24&icon.query=open_in_new
10+
export const openInNewIcon = html` <svg
11+
height="24px"
12+
viewBox="0 -960 960 960"
13+
width="24px"
14+
fill="currentcolor"
15+
>
16+
<path
17+
d="M200-120q-33 0-56.5-23.5T120-200v-560q0-33 23.5-56.5T200-840h280v80H200v560h560v-280h80v280q0 33-23.5 56.5T760-120H200Zm188-212-56-56 372-372H560v-80h280v280h-80v-144L388-332Z"
18+
/>
19+
</svg>`;
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
/**
2+
* @license
3+
* Copyright 2023 Google LLC
4+
* SPDX-License-Identifier: BSD-3-Clause
5+
*/
6+
7+
import * as fs from 'fs/promises';
8+
import * as path from 'path';
9+
import type {UserFacingPageData} from '../plugin';
10+
11+
export async function indexExternalData(
12+
outputDir: '_dev' | '_site',
13+
idOffset = 0
14+
) {
15+
if (outputDir === '_dev') {
16+
return [];
17+
}
18+
19+
// Path of the external data index.
20+
const EXTERNAL_DATA_INDEX_PATH = path.resolve(
21+
__dirname,
22+
`../../../../lit-dev-content/${outputDir}/external-search-data/data.json`
23+
);
24+
25+
const fileContents = await fs.readFile(EXTERNAL_DATA_INDEX_PATH, 'utf-8');
26+
const data = JSON.parse(fileContents) as UserFacingPageData[];
27+
data.forEach((searchRecord) => {
28+
searchRecord.objectID = `${++idOffset}`;
29+
searchRecord.isExternal = true;
30+
});
31+
32+
return data;
33+
}

packages/lit-dev-tools-cjs/src/search/indexers/index-tutorials.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,10 @@ export const indexTutorials = async (
2929
outputDir: string,
3030
idOffset = 0
3131
): Promise<UserFacingPageData[]> => {
32+
if (outputDir === '_dev') {
33+
return [];
34+
}
35+
3236
const TUTORIAL_PATH = path.resolve(
3337
__dirname,
3438
`../../../../lit-dev-content/${outputDir}/tutorials`
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
/**
2+
* @license
3+
* Copyright 2023 Google LLC
4+
* SPDX-License-Identifier: BSD-3-Clause
5+
*/
6+
7+
import * as fs from 'fs/promises';
8+
import * as path from 'path';
9+
import type {UserFacingPageData} from '../plugin';
10+
11+
export async function indexVideos(outputDir: '_dev' | '_site', idOffset = 0) {
12+
if (outputDir === '_dev') {
13+
return [];
14+
}
15+
16+
// Path of the video index.
17+
const VIDEO_INDEX_PATH = path.resolve(
18+
__dirname,
19+
`../../../../lit-dev-content/${outputDir}/external-search-data/videos.json`
20+
);
21+
22+
const fileContents = await fs.readFile(VIDEO_INDEX_PATH, 'utf-8');
23+
const videos = JSON.parse(fileContents) as UserFacingPageData[];
24+
videos.forEach((video) => {
25+
video.objectID = `${++idOffset}`;
26+
});
27+
28+
return videos;
29+
}

0 commit comments

Comments
 (0)