Skip to content

Commit 6dbba4b

Browse files
authoredFeb 25, 2025··
Merge pull request #476 from adobe/release
RC-20250218
2 parents 046146f + d19979b commit 6dbba4b

25 files changed

+502
-251
lines changed
 

‎.circleci/config.yml

+1-1
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ executors:
66

77
orbs:
88
browser-tools: circleci/browser-tools@1.5.2
9-
codecov: codecov/codecov@5.2.0
9+
codecov: codecov/codecov@5.2.1
1010

1111
commands:
1212
setup:

‎package-lock.json

+201-238
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

‎package.json

+6-5
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@
3737
"reporter-options": "configFile=.mocha-multi.json"
3838
},
3939
"dependencies": {
40-
"@lit/context": "1.1.3",
40+
"@lit/context": "1.1.4",
4141
"@spectrum-web-components/action-button": "0.43.0",
4242
"@spectrum-web-components/action-group": "0.43.0",
4343
"@spectrum-web-components/action-menu": "0.43.0",
@@ -53,6 +53,7 @@
5353
"@spectrum-web-components/menu": "0.43.0",
5454
"@spectrum-web-components/overlay": "0.43.0",
5555
"@spectrum-web-components/picker": "0.43.0",
56+
"@spectrum-web-components/popover": "0.43.0",
5657
"@spectrum-web-components/progress-circle": "0.43.0",
5758
"@spectrum-web-components/search": "0.43.0",
5859
"@spectrum-web-components/status-light": "0.43.0",
@@ -85,7 +86,7 @@
8586
"@types/mocha": "10.0.10",
8687
"@web/rollup-plugin-html": "2.3.0",
8788
"@web/rollup-plugin-import-meta-assets": "2.2.1",
88-
"@web/test-runner": "0.19.0",
89+
"@web/test-runner": "0.20.0",
8990
"@web/test-runner-commands": "0.9.0",
9091
"ajv": "8.17.1",
9192
"archiver": "7.0.1",
@@ -101,11 +102,11 @@
101102
"lint-staged": "15.4.3",
102103
"nock": "14.0.1",
103104
"rimraf": "6.0.1",
104-
"rollup": "4.34.6",
105+
"rollup": "4.34.8",
105106
"rollup-plugin-copy": "3.5.0",
106107
"rollup-plugin-esbuild": "6.2.0",
107-
"semantic-release": "24.2.2",
108-
"semantic-release-react-native": "1.12.1",
108+
"semantic-release": "24.2.3",
109+
"semantic-release-react-native": "1.12.2",
109110
"shelljs": "0.8.5",
110111
"sinon": "19.0.2"
111112
},

‎src/extension/_locales/de/messages.json

+6
Original file line numberDiff line numberDiff line change
@@ -485,6 +485,12 @@
485485
"next": {
486486
"message": "Weiter"
487487
},
488+
"no_data": {
489+
"message": "Keine Daten"
490+
},
491+
"no_data_subheading": {
492+
"message": "Dieses Blatt ist leer"
493+
},
488494
"no_results": {
489495
"message": "Keine Ergebnisse gefunden"
490496
},

‎src/extension/_locales/en/messages.json

+6
Original file line numberDiff line numberDiff line change
@@ -559,6 +559,12 @@
559559
"search": {
560560
"message": "Search"
561561
},
562+
"no_data": {
563+
"message": "No Data"
564+
},
565+
"no_data_subheading": {
566+
"message": "This sheet is empty"
567+
},
562568
"no_results": {
563569
"message": "No Results Found"
564570
},

‎src/extension/_locales/es/messages.json

+6
Original file line numberDiff line numberDiff line change
@@ -485,6 +485,12 @@
485485
"next": {
486486
"message": "Siguiente"
487487
},
488+
"no_data": {
489+
"message": "No hay datos"
490+
},
491+
"no_data_subheading": {
492+
"message": "Esta hoja está vacía"
493+
},
488494
"no_results": {
489495
"message": "No se han encontrado resultados"
490496
},

‎src/extension/_locales/fr/messages.json

+6
Original file line numberDiff line numberDiff line change
@@ -485,6 +485,12 @@
485485
"next": {
486486
"message": "Suivant"
487487
},
488+
"no_data": {
489+
"message": "Aucune donnée"
490+
},
491+
"no_data_subheading": {
492+
"message": "Cette feuille est vide."
493+
},
488494
"no_results": {
489495
"message": "Aucun résultat trouvé"
490496
},

‎src/extension/_locales/it/messages.json

+6
Original file line numberDiff line numberDiff line change
@@ -485,6 +485,12 @@
485485
"next": {
486486
"message": "Avanti"
487487
},
488+
"no_data": {
489+
"message": "Nessun dato"
490+
},
491+
"no_data_subheading": {
492+
"message": "Il foglio è vuoto"
493+
},
488494
"no_results": {
489495
"message": "Nessun risultato"
490496
},

‎src/extension/_locales/ja/messages.json

+6
Original file line numberDiff line numberDiff line change
@@ -485,6 +485,12 @@
485485
"next": {
486486
"message": "次へ"
487487
},
488+
"no_data": {
489+
"message": "データなし"
490+
},
491+
"no_data_subheading": {
492+
"message": "このシートは空です"
493+
},
488494
"no_results": {
489495
"message": "結果が見つかりません"
490496
},

‎src/extension/_locales/ko/messages.json

+6
Original file line numberDiff line numberDiff line change
@@ -485,6 +485,12 @@
485485
"next": {
486486
"message": "다음"
487487
},
488+
"no_data": {
489+
"message": "데이터 없음"
490+
},
491+
"no_data_subheading": {
492+
"message": "이 시트는 비어 있습니다."
493+
},
488494
"no_results": {
489495
"message": "검색 결과 없음"
490496
},

‎src/extension/_locales/pt_BR/messages.json

+6
Original file line numberDiff line numberDiff line change
@@ -485,6 +485,12 @@
485485
"next": {
486486
"message": "Próximo"
487487
},
488+
"no_data": {
489+
"message": "Não há dados"
490+
},
491+
"no_data_subheading": {
492+
"message": "Esta folha está vazia"
493+
},
488494
"no_results": {
489495
"message": "Nenhum resultado encontrado"
490496
},

‎src/extension/_locales/zh_CN/messages.json

+6
Original file line numberDiff line numberDiff line change
@@ -485,6 +485,12 @@
485485
"next": {
486486
"message": "下一页"
487487
},
488+
"no_data": {
489+
"message": "无数据"
490+
},
491+
"no_data_subheading": {
492+
"message": "此表为空"
493+
},
488494
"no_results": {
489495
"message": "未找到结果"
490496
},

‎src/extension/_locales/zh_TW/messages.json

+6
Original file line numberDiff line numberDiff line change
@@ -485,6 +485,12 @@
485485
"next": {
486486
"message": "下一步"
487487
},
488+
"no_data": {
489+
"message": "無資料"
490+
},
491+
"no_data_subheading": {
492+
"message": "此工作表是空的"
493+
},
488494
"no_results": {
489495
"message": "找不到任何結果"
490496
},

‎src/extension/actions.js

+22-3
Original file line numberDiff line numberDiff line change
@@ -110,7 +110,7 @@ export async function showSidekickNotification(tabId, data, callback) {
110110
* @param {string} origin
111111
* @returns {boolean} true - trusted / false - untrusted
112112
*/
113-
function isGetAuthInfoTrustedOrigin(origin) {
113+
function isTrustedOrigin(origin) {
114114
const TRUSTED_ORIGINS = [
115115
ADMIN_ORIGIN,
116116
'https://labs.aem.live',
@@ -138,10 +138,10 @@ function isGetAuthInfoTrustedOrigin(origin) {
138138
* Returns the organizations the user is currently authenticated for.
139139
* @returns {Promise<string[]>} The organizations
140140
*/
141-
async function getAuthInfo(message, sender) {
141+
async function getAuthInfo(_, sender) {
142142
const { origin } = new URL(sender.url);
143143

144-
if (!isGetAuthInfoTrustedOrigin(origin)) {
144+
if (!isTrustedOrigin(origin)) {
145145
return []; // don't give out any information
146146
}
147147

@@ -151,6 +151,24 @@ async function getAuthInfo(message, sender) {
151151
.map(({ owner }) => owner);
152152
}
153153

154+
/**
155+
* Returns the configured sites.
156+
* @returns {Promise<Object[]>} The sites
157+
*/
158+
async function getSites(_, sender) {
159+
const { origin } = new URL(sender.url);
160+
161+
if (!isTrustedOrigin(origin)) {
162+
return []; // don't give out any information
163+
}
164+
165+
return (await getConfig('sync', 'projects') || [])
166+
.map((handle) => {
167+
const [org, site] = handle.split('/');
168+
return { org, site };
169+
});
170+
}
171+
154172
/**
155173
* Adds or removes a project based on the tab's URL
156174
* @param {chrome.tabs.Tab} tab The tab
@@ -306,4 +324,5 @@ export const internalActions = {
306324
export const externalActions = {
307325
updateAuthToken,
308326
getAuthInfo,
327+
getSites,
309328
};

‎src/extension/app/aem-sidekick.css.js

+1
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ export const style = css`
2424
color: initial;
2525
font: initial;
2626
letter-spacing: initial;
27+
text-align: initial;
2728
}
2829
2930
:host([open='true']) {

‎src/extension/app/components/plugin/plugin-action-bar.css.js

+12
Original file line numberDiff line numberDiff line change
@@ -143,6 +143,18 @@ export const style = css`
143143
opacity: 0.5;
144144
}
145145
146+
action-bar overlay-trigger sp-popover .content {
147+
padding: 10px;
148+
height: 100%;
149+
}
150+
151+
action-bar overlay-trigger sp-popover .content iframe {
152+
width: 100%;
153+
height: 100%;
154+
border: 0;
155+
color-scheme: auto;
156+
}
157+
146158
#plugin-menu sp-menu-group [slot="header"] {
147159
text-transform: uppercase;
148160
font-size: var(--spectrum-global-dimension-font-size-75);

‎src/extension/app/components/plugin/plugin-action-bar.js

+7
Original file line numberDiff line numberDiff line change
@@ -147,6 +147,13 @@ export class PluginActionBar extends ConnectedElement {
147147
},
148148
);
149149

150+
reaction(
151+
() => this.appStore.theme,
152+
async () => {
153+
this.setupPlugins();
154+
},
155+
);
156+
150157
reaction(
151158
() => this.appStore.bulkStore.selection,
152159
() => {

‎src/extension/app/components/plugin/plugin.js

+41-1
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
/* eslint-disable max-len */
1414

1515
import { html } from 'lit';
16+
import { ifDefined } from 'lit/directives/if-defined.js';
1617
import { EXTERNAL_EVENTS } from '../../constants.js';
1718

1819
/**
@@ -150,6 +151,14 @@ export class Plugin {
150151
return this.config.isBadge;
151152
}
152153

154+
/**
155+
* Is this plugin a popover?
156+
* @returns {boolean} True if the plugin is a popover
157+
*/
158+
isPopover() {
159+
return this.config.isPopover;
160+
}
161+
153162
/**
154163
* Adds a plugin to this plugin's children.
155164
* @param {Plugin} plugin The plugin to add
@@ -257,13 +266,44 @@ export class Plugin {
257266
`;
258267
}
259268

269+
if (this.isPopover() && this.config.url) {
270+
const {
271+
url, popoverRect, title, titleI18n,
272+
} = this.config;
273+
274+
const popoverTitle = titleI18n?.[this.appStore.siteStore.lang] || title;
275+
const src = new URL(url);
276+
src.searchParams.set('theme', this.appStore.theme);
277+
278+
let filteredPopoverRect = popoverRect;
279+
if (popoverRect) {
280+
filteredPopoverRect = `${popoverRect
281+
.split(';')
282+
.map((s) => s.trim())
283+
.filter((s) => s.startsWith('width:') || s.startsWith('height:'))
284+
.join('; ')};`;
285+
}
286+
287+
return html`
288+
<overlay-trigger receivesFocus="true" offset="-3">
289+
<sk-action-button quiet slot="trigger">${this.getButtonText()}</sk-action-button>
290+
<sp-popover slot="click-content" placement="top" tip style=${ifDefined(filteredPopoverRect)}>
291+
<div class="content">
292+
<iframe title=${popoverTitle || 'Popover content'} src=${src}></iframe>
293+
</div>
294+
</sp-popover>
295+
</overlay-trigger>`;
296+
}
297+
260298
return html`
261299
<sk-action-button
262300
class=${this.getId()}
263301
.disabled=${!this.isEnabled()}
264302
quiet
265303
@click=${(evt) => this.onButtonClick(evt)}
266-
>${this.getButtonText()}</sk-action-button>
304+
>
305+
${this.getButtonText()}
306+
</sk-action-button>
267307
`;
268308
}
269309

‎src/extension/app/spectrum-2.css.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -97,7 +97,7 @@ export const spectrum2 = css`
9797
--spectrum2-background-color-hover-dark: rgba(255, 255, 255, 0.05);
9898
9999
/* Sidekick theme tokens */
100-
--sidekick-max-width: 800px;
100+
--sidekick-max-width: 805px;
101101
--sidekick-color-light: #292929;
102102
--sidekick-color-dark: #DBDBDB;
103103
--sidekick-background-light: #FFFFFFCC;

‎src/extension/app/store/app.js

+7
Original file line numberDiff line numberDiff line change
@@ -337,6 +337,8 @@ export class AppStore {
337337
passConfig,
338338
passReferrer,
339339
isPalette,
340+
isPopover,
341+
popoverRect,
340342
event: eventName,
341343
environments,
342344
excludePaths,
@@ -378,6 +380,8 @@ export class AppStore {
378380
const plugin = {
379381
custom: true,
380382
id,
383+
title,
384+
titleI18n,
381385
condition,
382386
button: {
383387
text: (titleI18n && titleI18n[lang]) || title,
@@ -417,7 +421,10 @@ export class AppStore {
417421
pinned,
418422
confirm,
419423
container: containerId,
424+
url,
420425
isBadge,
426+
isPopover,
427+
popoverRect,
421428
badgeVariant,
422429
};
423430

‎src/extension/index.js

+2
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@ import '@spectrum-web-components/divider/sp-divider.js';
1818
import '@spectrum-web-components/dialog/sp-dialog-base.js';
1919
import '@spectrum-web-components/menu/sp-menu-divider.js';
2020
import '@spectrum-web-components/overlay/sp-overlay.js';
21+
import '@spectrum-web-components/overlay/overlay-trigger.js';
22+
import '@spectrum-web-components/popover/sp-popover.js';
2123
import '@spectrum-web-components/picker/sp-picker.js';
2224
import '@spectrum-web-components/status-light/sp-status-light.js';
2325
import '@spectrum-web-components/switch/sp-switch.js';

‎src/extension/types/typedefs.js

+2
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,8 @@
109109
* @prop {boolean} [isContainer] Determines whether to turn this plugin into a dropdown
110110
* @prop {boolean} [isPalette] Determines whether a URL is opened in a palette instead of a new tab
111111
* @prop {string} [paletteRect] The dimensions and position of a palette (optional)
112+
* @prop {boolean} [isPopover] Determines whether a URL is opened in a popover instead of a new tab
113+
* @prop {string} [popoverRect] The dimensions of a popover (optional)
112114
* @prop {boolean} [isBadge] Determines whether the plugin is a badge (optional)
113115
* @prop {string} [badgeVariant] The color variant of the badge (optional)
114116
* @prop {string[]} [environments] Specifies when to show this plugin (admin, edit, dev, preview, live, prod)

‎src/extension/views/json/json.js

+17-2
Original file line numberDiff line numberDiff line change
@@ -295,7 +295,7 @@ export class JSONView extends LitElement {
295295

296296
tableContainer.appendChild(table);
297297
} else {
298-
const noResults = `
298+
const noResults = this.filterText ? `
299299
<sp-illustrated-message
300300
heading="${i18n(this.languageDict, 'no_results')}"
301301
description="${i18n(this.languageDict, 'no_results_subheading')}"
@@ -308,7 +308,22 @@ export class JSONView extends LitElement {
308308
</g>
309309
</svg>
310310
</sp-illustrated-message>
311-
`;
311+
` : `
312+
<sp-illustrated-message
313+
heading="${i18n(this.languageDict, 'no_data')}"
314+
description="${i18n(this.languageDict, 'no_data_subheading')}"
315+
>
316+
<svg xmlns="http://www.w3.org/2000/svg" width="100.25" height="87.2">
317+
<path d="M94.55,87.2H5.85c-3.1,0-5.7-2.5-5.7-5.7V5.7C.15,2.6,2.65,0,5.85,0h88.7c3.1,0,5.7,2.5,5.7,5.7v75.8c0,3.1-2.5,5.7-5.7,5.7ZM5.85.5C2.95.5.65,2.8.65,5.7v75.8c0,2.9,2.3,5.2,5.2,5.2h88.7c2.9,0,5.2-2.3,5.2-5.2V5.7c0-2.9-2.3-5.2-5.2-5.2H5.85Z"/>
318+
<rect x=".45" y="15.5" width="99.5" height=".5"/>
319+
<rect x=".45" y="33.1" width="99.5" height=".5"/>
320+
<rect x=".45" y="51.2" width="99.5" height=".5"/>
321+
<rect x=".45" y="69.4" width="99.5" height=".5"/>
322+
<rect x="33.33" y="15.1" width=".5" height="71.8"/>
323+
<rect x="66.67" y="15.1" width=".5" height="71.8"/>
324+
</svg>
325+
</sp-illustrated-message>
326+
`;
312327
tableContainer.innerHTML = noResults;
313328
}
314329

‎test/actions.test.js

+49
Original file line numberDiff line numberDiff line change
@@ -156,6 +156,55 @@ describe('Test actions', () => {
156156
expect(resp).to.deep.equal([]);
157157
});
158158

159+
it('external: getSites', async () => {
160+
const getStub = sandbox.stub(chrome.storage.sync, 'get');
161+
const projects = [
162+
'foo/bar',
163+
'foo1/baz',
164+
'foo2/baz',
165+
];
166+
const expectedOutput = [
167+
{ org: 'foo', site: 'bar' },
168+
{ org: 'foo1', site: 'baz' },
169+
{ org: 'foo2', site: 'baz' },
170+
];
171+
172+
let resp;
173+
174+
// without projects
175+
getStub.resolves({});
176+
resp = await externalActions.getSites({}, mockTab('https://tools.aem.live'));
177+
expect(resp).to.deep.equal([]);
178+
179+
// with auth info
180+
getStub.resolves({
181+
projects,
182+
});
183+
184+
// trusted actors
185+
resp = await externalActions.getSites({}, mockTab('https://tools.aem.live/foo'));
186+
expect(resp).to.deep.equal(expectedOutput);
187+
188+
resp = await externalActions.getSites({}, mockTab('https://labs.aem.live/foo'));
189+
expect(resp).to.deep.equal(expectedOutput);
190+
191+
resp = await externalActions.getSites({}, mockTab('https://feature--helix-labs-website--adobe.aem.page/feature'));
192+
expect(resp).to.deep.equal(expectedOutput);
193+
194+
// untrusted actors
195+
resp = await externalActions.getSites({}, mockTab('https://evil.live'));
196+
expect(resp).to.deep.equal([]);
197+
198+
resp = await externalActions.getSites({}, mockTab('https://main--site--owner.aem.live'));
199+
expect(resp).to.deep.equal([]);
200+
201+
resp = await externalActions.getSites({}, mockTab('https://tools.aem.live.evil.com'));
202+
expect(resp).to.deep.equal([]);
203+
204+
resp = await externalActions.getSites({}, mockTab('https://main--helix-tools-website--adobe-evl.aem.live'));
205+
expect(resp).to.deep.equal([]);
206+
});
207+
159208
it('internal: addRemoveProject', async () => {
160209
const set = sandbox.spy(chrome.storage.sync, 'set');
161210
const remove = sandbox.spy(chrome.storage.sync, 'remove');

‎test/app/components/plugin/plugin.test.js

+73
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
/* eslint-disable no-unused-expressions, import/no-extraneous-dependencies */
1313

1414
import { expect } from '@open-wc/testing';
15+
import { render } from 'lit';
1516
import chromeMock from '../../../mocks/chrome.js';
1617
import { Plugin } from '../../../../src/extension/app/components/plugin/plugin.js';
1718
import { AppStore } from '../../../../src/extension/app/store/app.js';
@@ -48,6 +49,18 @@ const TEST_BADGE_CONFIG = {
4849
},
4950
};
5051

52+
const TEST_POPOVER_CONFIG = {
53+
id: 'test',
54+
title: 'Test Popover',
55+
isPopover: true,
56+
popoverRect: 'width: 100px; height: 100px;',
57+
url: 'https://labs.aem.live/tools/snapshot-admin/palette.html?foo=bar',
58+
button: {
59+
text: 'Test Child',
60+
action: () => {},
61+
},
62+
};
63+
5164
describe('Plugin', () => {
5265
const appStore = new AppStore();
5366

@@ -124,6 +137,66 @@ describe('Plugin', () => {
124137
parent.append(child);
125138
});
126139

140+
it('renders a popover plugin', async () => {
141+
appStore.theme = 'dark';
142+
const plugin = new Plugin({ ...TEST_POPOVER_CONFIG }, appStore);
143+
144+
const container = document.createElement('div');
145+
render(plugin.render(), container);
146+
147+
// Wait for next time to let lit process the update
148+
await Promise.resolve();
149+
150+
const overlayTrigger = container.querySelector('overlay-trigger');
151+
expect(overlayTrigger).to.exist;
152+
expect(overlayTrigger.getAttribute('offset')).to.equal('-3');
153+
154+
const popover = container.querySelector('sp-popover');
155+
expect(popover).to.exist;
156+
expect(popover.getAttribute('placement')).to.equal('top');
157+
158+
const popoverStyle = popover.getAttribute('style');
159+
expect(popoverStyle).to.include('width: 100px; height: 100px;');
160+
161+
const iframe = container.querySelector('iframe');
162+
expect(iframe).to.exist;
163+
expect(iframe.getAttribute('title')).to.equal(TEST_POPOVER_CONFIG.title);
164+
expect(iframe.getAttribute('src')).to.include(TEST_POPOVER_CONFIG.url);
165+
expect(iframe.getAttribute('src')).to.include('?foo=bar&theme=dark');
166+
});
167+
168+
it('renders a popover plugin with filtered popoverRect', async () => {
169+
const config = { ...TEST_POPOVER_CONFIG };
170+
config.popoverRect = 'width: 100px; height: 100px; background-color: red;';
171+
const plugin = new Plugin({ ...config }, appStore);
172+
173+
const container = document.createElement('div');
174+
render(plugin.render(), container);
175+
176+
// Wait for next time to let lit process the update
177+
await Promise.resolve();
178+
179+
const popover = container.querySelector('sp-popover');
180+
const popoverStyle = popover.getAttribute('style');
181+
expect(popoverStyle).to.include('width: 100px; height: 100px;');
182+
});
183+
184+
it('renders a popover without a popoverRect', async () => {
185+
const config = { ...TEST_POPOVER_CONFIG };
186+
config.popoverRect = undefined;
187+
const plugin = new Plugin({ ...config }, appStore);
188+
189+
const container = document.createElement('div');
190+
render(plugin.render(), container);
191+
192+
// Wait for next time to let lit process the update
193+
await Promise.resolve();
194+
195+
const popover = container.querySelector('sp-popover');
196+
const popoverStyle = popover.getAttribute('style');
197+
expect(popoverStyle).to.be.null;
198+
});
199+
127200
it('does not render if not pinned', async () => {
128201
const plugin = new Plugin({
129202
id: 'test',

0 commit comments

Comments
 (0)
Please sign in to comment.