Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 25 additions & 2 deletions src/helpers/get-github-repo-info.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,10 @@ const repoSidebarSectionSelectors = [
'.Layout-sidebar .BorderGrid-cell .hide-sm.hide-md',
'.BorderGrid-cell > .hide-sm.hide-md',
'.BorderGrid-cell .hide-sm.hide-md',
'.hide-sm.hide-md',
];

const repoSidebarMarkerSelector = [
export const repoSidebarMarkerSelector = [
'a[href$="/stargazers"]',
'a[href$="/watchers"]',
'a[href$="/forks"]',
Expand Down Expand Up @@ -69,7 +70,8 @@ export function getRepoSidebarSection() {
export function getRepoSidebarBorderGrid() {
const $sidebarSection = getRepoSidebarSection();
if ($sidebarSection.length > 0) {
return $sidebarSection.closest('.BorderGrid').first();
const $borderGrid = $sidebarSection.closest('.BorderGrid').first();
return $borderGrid.length > 0 ? $borderGrid : $sidebarSection.parent();
}

const $borderGrids = $('.Layout-sidebar .BorderGrid, .BorderGrid')
Expand All @@ -79,6 +81,27 @@ export function getRepoSidebarBorderGrid() {
return pickFirstVisible($borderGrids);
}

export function observeRepoSidebarReplacement(featureId: string, repoName: string, remount: () => void) {
const observerRoot = getRepoSidebarBorderGrid().parent()[0];
if (!observerRoot) {
return;
}

const observer = new MutationObserver(() => {
if (document.getElementById(featureId) || getRepoName() !== repoName) {
return;
}

requestAnimationFrame(() => {
if (!document.getElementById(featureId) && getRepoName() === repoName) {
observer.disconnect();
remount();
}
});
});
observer.observe(observerRoot, { childList: true, subtree: true });
}

export async function isRepoRoot() {
return pageDetect.isRepoRoot();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,11 @@ import elementReady from 'element-ready';
import {
getRepoName,
getRepoSidebarBorderGrid,
getRepoSidebarSection,
isPublicRepoWithMeta,
isRepoRoot,
observeRepoSidebarReplacement,
repoSidebarMarkerSelector,
} from '../../../../helpers/get-github-repo-info';
import { getActivity, getOpenrank } from '../../../../api/repo';
import { RepoMeta, metaStore } from '../../../../api/common';
Expand Down Expand Up @@ -38,31 +41,45 @@ const createTrendRow = () => {
newBorderGridRow.id = featureId;
newBorderGridRow.className = 'BorderGrid-row';
const newBorderGridCell = document.createElement('div');
newBorderGridCell.className = 'BorderGrid-cell';
newBorderGridCell.className = 'BorderGrid-cell hypercrx-repo-activity-openrank-trends-content';
newBorderGridRow.appendChild(newBorderGridCell);

return { row: newBorderGridRow, cell: newBorderGridCell };
};

const mountTrendRow = (row: HTMLElement) => {
const $borderGrid = getRepoSidebarBorderGrid();
if ($borderGrid.length === 0) {
const $sidebarContainer = getRepoSidebarBorderGrid();
if ($sidebarContainer.length === 0) {
return false;
}

$borderGrid.append(row);
if (!$sidebarContainer.is('.BorderGrid')) {
row.className = getRepoSidebarSection().attr('class') || 'hide-sm hide-md';
$(row).children().first().removeClass('BorderGrid-cell');
}

$sidebarContainer.append(row);
return true;
};

const init = async (): Promise<void> => {
platform = getPlatform();
repoName = getRepoName();
await getData();
await elementReady('.BorderGrid, .BorderGrid-cell, .Layout-sidebar');
await elementReady(repoSidebarMarkerSelector);

const { row, cell } = createTrendRow();
renderTo(cell);
mountTrendRow(row);
if (!mountTrendRow(row)) {
return;
}
observeRepoSidebarReplacement(featureId, repoName, () => {
const { row, cell } = createTrendRow();
renderTo(cell);
if (!mountTrendRow(row)) {
return;
}
});
};

const restore = async () => {
Expand All @@ -73,8 +90,8 @@ const restore = async () => {
await getData();
}
// rerender the chart or it will be empty
await elementReady('.BorderGrid, .BorderGrid-cell, .Layout-sidebar');
let container = $(`#${featureId}`).children('.BorderGrid-cell')[0];
await elementReady(repoSidebarMarkerSelector);
let container = $(`#${featureId}`).children()[0];

if (!container) {
const { row, cell } = createTrendRow();
Expand Down
30 changes: 24 additions & 6 deletions src/pages/ContentScripts/features/repo-sidebar-labels/index.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,12 @@
import features from '../../../../feature-manager';
import elementReady from 'element-ready';
import { getRepoName, getRepoSidebarSection, isPublicRepoWithMeta } from '../../../../helpers/get-github-repo-info';
import {
getRepoName,
getRepoSidebarSection,
isPublicRepoWithMeta,
observeRepoSidebarReplacement,
repoSidebarMarkerSelector,
} from '../../../../helpers/get-github-repo-info';
import { Label, RepoMeta, metaStore } from '../../../../api/common';
import { createRoot } from 'react-dom/client';
import OpenDiggerLabel from './OpenDiggerLabel';
Expand All @@ -10,7 +16,10 @@ import $ from 'jquery';
import isGithub from '../../../../helpers/is-github';
import { getPlatform } from '../../../../helpers/get-platform';
const featureId = features.getFeatureID(import.meta.url);
const markerId = `${featureId}-marker`;
let platform: string;
let repoName: string;
let labels: Label[] = [];
const getLabels = async (repoName: string) => {
const meta = (await metaStore.get(platform, repoName)) as RepoMeta;
// filtered all xxx-n and n is not 0
Expand All @@ -20,12 +29,16 @@ const getLabels = async (repoName: string) => {
};

const renderTags = (labels: Label[]) => {
if (document.getElementById(markerId)) {
return;
}

const $sidebarSection = getRepoSidebarSection();
if ($sidebarSection.length === 0) {
return;
}

let githubTagContainer = $sidebarSection.find('.topic-tag.topic-tag-link').parent().first();
let githubTagContainer = $sidebarSection.find('.topic-tag.topic-tag-link, a[href^="/topics/"]').parent().first();
// some repositories don't have tags, create a tag container for our tags
if (githubTagContainer.length === 0) {
githubTagContainer = $('<div class="f6"></div>');
Expand All @@ -51,23 +64,28 @@ const renderTags = (labels: Label[]) => {
}
for (const label of labels) {
const id = `opendigger-label-${label.id}`;
// if the tag already exists, skip
if (document.getElementById(id)) {
continue;
}
const labelElement = document.createElement('span');
createRoot(labelElement).render(<OpenDiggerLabel label={label} />);
githubTagContainer.append(labelElement);
}

const marker = document.createElement('span');
marker.id = markerId;
marker.hidden = true;
githubTagContainer.append(marker);
};

const init = async (): Promise<void> => {
platform = getPlatform();
const repoName = getRepoName();
await elementReady('.BorderGrid-cell, .Layout-sidebar');
const labels = await getLabels(repoName);
repoName = getRepoName();
await elementReady(repoSidebarMarkerSelector);
labels = (await getLabels(repoName)) ?? [];
if (labels && labels.length > 0) {
renderTags(labels);
observeRepoSidebarReplacement(markerId, repoName, () => renderTags(labels));
}
};

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,10 @@ test.describe('content script component: repo activity openrank trends', () => {

// ECharts canvases should be rendered
expect(await trendRow.locator('canvas').count()).toBeGreaterThan(0);

// GitHub may replace its React-managed sidebar after the extension mounts.
await trendRow.evaluate((element) => element.remove());
await expect(trendRow).toHaveCount(1);
});

test('does not render on non-root repository pages', async () => {
Expand Down
13 changes: 13 additions & 0 deletions tests/unit/get-github-repo-info.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ import {
getRepoName,
getRepoNameByPage,
getRepoNameByUrl,
getRepoSidebarBorderGrid,
hasRepoContainerHeader,
isPublicRepo,
isPublicRepoWithMeta,
Expand Down Expand Up @@ -69,6 +70,18 @@ describe('get-github-repo-info', () => {
expect(hasRepoContainerHeader()).toBe(false);
});

it('finds the Primer repository sidebar without relying on generated class names', () => {
document.body.innerHTML = `
<aside data-testid="primer-sidebar">
<div class="hide-sm hide-md">
<a href="/owner/repo/stargazers">Stars</a>
</div>
</aside>
`;

expect(getRepoSidebarBorderGrid().attr('data-testid')).toBe('primer-sidebar');
});

it('returns public repo status from meta and repo detection', async () => {
document.head.innerHTML = '<meta name="octolytics-dimension-repository_public" content="true" />';
mocks.elementReady.mockResolvedValue(true);
Expand Down