Skip to content

Commit 9f159d4

Browse files
authored
Merge branch 'main' into importer-error-surfacing
2 parents 5dd20a1 + e0a2907 commit 9f159d4

7 files changed

Lines changed: 122 additions & 7 deletions

File tree

apps/sodo-search/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@tryghost/sodo-search",
3-
"version": "1.8.22",
3+
"version": "1.8.23",
44
"license": "MIT",
55
"repository": "https://github.com/TryGhost/Ghost",
66
"author": "Ghost Foundation",

apps/sodo-search/src/components/popup-modal.js

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -507,7 +507,7 @@ function SearchResultBox() {
507507
return null;
508508
}
509509

510-
function Results({posts, authors, tags}) {
510+
export function Results({posts, authors, tags}) {
511511
const {searchValue} = useContext(AppContext);
512512

513513
const allResults = useMemo(() => {
@@ -527,7 +527,11 @@ function Results({posts, authors, tags}) {
527527
}, [allResults]);
528528

529529
useEffect(() => {
530-
let keyUphandler = (event) => {
530+
let keyDownHandler = (event) => {
531+
// keyCode 229 is the IME composition key for legacy browsers
532+
if (event.isComposing || event.keyCode === 229) {
533+
return;
534+
}
531535
const selectedResultIdx = allResults.findIndex((d) => {
532536
return d.id === selectedResult;
533537
});
@@ -548,11 +552,12 @@ function Results({posts, authors, tags}) {
548552
};
549553

550554
const containeRefNode = containerRef?.current;
551-
containeRefNode?.ownerDocument.removeEventListener('keyup', keyUphandler);
552-
containeRefNode?.ownerDocument.addEventListener('keyup', keyUphandler);
555+
const doc = containeRefNode?.ownerDocument;
556+
doc?.removeEventListener('keydown', keyDownHandler);
557+
doc?.addEventListener('keydown', keyDownHandler);
553558

554559
return () => {
555-
containeRefNode?.ownerDocument?.removeEventListener('keyup', keyUphandler);
560+
doc?.removeEventListener('keydown', keyDownHandler);
556561
};
557562
}, [allResults, selectedResult]);
558563

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
import AppContext from '../../src/app-context';
2+
import React from 'react';
3+
import {Results} from '../../src/components/popup-modal';
4+
import {fireEvent, render} from '@testing-library/react';
5+
6+
describe('Results keyboard navigation', () => {
7+
const posts = [
8+
{id: 'post-1', title: 'First post', excerpt: 'First post excerpt', url: 'https://example.com/first-post/'},
9+
{id: 'post-2', title: 'Second post', excerpt: 'Second post excerpt', url: 'https://example.com/second-post/'}
10+
];
11+
12+
const renderResults = () => {
13+
return render(
14+
<AppContext.Provider value={{searchValue: 'post', t: str => str}}>
15+
<Results posts={posts} authors={[]} tags={[]} />
16+
</AppContext.Provider>
17+
);
18+
};
19+
20+
let originalLocation;
21+
22+
beforeEach(() => {
23+
originalLocation = window.location;
24+
delete window.location;
25+
window.location = {href: 'https://example.com/'};
26+
});
27+
28+
afterEach(() => {
29+
window.location = originalLocation;
30+
});
31+
32+
test('navigates to the selected result on Enter', () => {
33+
renderResults();
34+
35+
fireEvent.keyDown(document, {key: 'Enter'});
36+
37+
expect(window.location.href).toBe('https://example.com/first-post/');
38+
});
39+
40+
test('does not navigate on Enter during IME composition', () => {
41+
renderResults();
42+
43+
fireEvent.keyDown(document, {key: 'Enter', isComposing: true});
44+
45+
expect(window.location.href).toBe('https://example.com/');
46+
});
47+
48+
test('does not navigate on Enter with keyCode 229 (legacy IME)', () => {
49+
renderResults();
50+
51+
fireEvent.keyDown(document, {key: 'Enter', keyCode: 229});
52+
53+
expect(window.location.href).toBe('https://example.com/');
54+
});
55+
});

ghost/core/core/server/services/oembed/oembed-service.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -180,7 +180,7 @@ class OEmbedService {
180180
'user-agent': USER_AGENT
181181
},
182182
timeout: {
183-
request: 2000
183+
request: 5000
184184
},
185185
followRedirect: true,
186186
...options
@@ -294,6 +294,7 @@ class OEmbedService {
294294
};
295295

296296
const metascraper = require('metascraper')([
297+
require('metascraper-amazon')(),
297298
require('metascraper-url')(),
298299
require('metascraper-title')(),
299300
require('metascraper-description')(),

ghost/core/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -204,6 +204,7 @@
204204
"luxon": "3.7.2",
205205
"mailgun.js": "10.4.0",
206206
"metascraper": "5.45.15",
207+
"metascraper-amazon": "5.45.10",
207208
"metascraper-author": "5.45.10",
208209
"metascraper-description": "5.45.10",
209210
"metascraper-image": "5.45.10",

ghost/core/test/unit/server/services/oembed/oembed-service.test.js

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,24 @@ describe('oembed-service', function () {
104104
});
105105
});
106106

107+
describe('fetchPage', function () {
108+
it('requests pages with a 5 second timeout', async function () {
109+
const externalRequest = sinon.stub().resolves({});
110+
111+
const service = new OembedService({
112+
config: {get() {
113+
return true;
114+
}},
115+
externalRequest
116+
});
117+
118+
await service.fetchPage('https://www.example.com', {});
119+
120+
const options = externalRequest.firstCall.args[1];
121+
assert.equal(options.timeout.request, 5000);
122+
});
123+
});
124+
107125
describe('fetchOembedData', function () {
108126
const pageHtml = `<html><head><link type="application/json+oembed" href="https://www.example.com/oembed"></head></html>`;
109127

@@ -182,6 +200,24 @@ describe('oembed-service', function () {
182200
assert.equal(response.metadata.title, 'Example');
183201
});
184202

203+
it('extracts Amazon product metadata via the metascraper-amazon ruleset', async function () {
204+
nock('https://www.amazon.com')
205+
.get('/dp/B08N5WRWNW')
206+
.query(true)
207+
.reply(200, `<html><head><title>Amazon.com</title></head><body>
208+
<span id="productTitle">Example Product Title</span>
209+
<img class="a-dynamic-image" src="https://m.media-amazon.com/images/I/example.jpg" data-old-hires="https://m.media-amazon.com/images/I/example-hires.jpg">
210+
</body></html>`);
211+
212+
const response = await oembedService.fetchOembedDataFromUrl('https://www.amazon.com/dp/B08N5WRWNW', 'bookmark');
213+
214+
assert.equal(response.version, '1.0');
215+
assert.equal(response.type, 'bookmark');
216+
assert.equal(response.metadata.title, 'Example Product Title');
217+
assert.equal(response.metadata.publisher, 'Amazon');
218+
assert.equal(response.metadata.thumbnail, 'https://m.media-amazon.com/images/I/example-hires.jpg');
219+
});
220+
185221
it('should return a bookmark response when the oembed endpoint returns a link type', async function () {
186222
nock('https://www.example.com')
187223
.get('/')

pnpm-lock.yaml

Lines changed: 17 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)