Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 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
13 changes: 13 additions & 0 deletions src/button/CaretForProvider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,17 @@ const GitLabCaret = () => {
);
};

const AzureDevOpsCaret = () => {
return (
<svg width="16" height="16" fill="none" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
<path
d="M4.22 8.47a.75.75 0 0 1 1.06 0L12 15.19l6.72-6.72a.75.75 0 1 1 1.06 1.06l-7.25 7.25a.75.75 0 0 1-1.06 0L4.22 9.53a.75.75 0 0 1 0-1.06Z"
fill="currentColor"
/>
</svg>
);
};

type Props = {
provider: SupportedApplication;
};
Expand All @@ -61,6 +72,8 @@ export const CaretForProvider = ({ provider }: Props) => {
return <BitbucketCaret />;
case "gitlab":
return <GitLabCaret />;
case "azure-devops":
return <AzureDevOpsCaret />;
default:
return (
<svg width="16" viewBox="0 0 24 24" className={classNames("chevron-icon")}>
Expand Down
63 changes: 60 additions & 3 deletions src/button/button-contributions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
* Happy about anyone who's able to make this work with imports (i.e. run the tests in this project), but I couldn't figure it out and gave up.
*/

export type SupportedApplication = "github" | "gitlab" | "bitbucket-server" | "bitbucket";
export type SupportedApplication = "github" | "gitlab" | "bitbucket-server" | "bitbucket" | "azure-devops";

const resolveMetaAppName = (head: HTMLHeadElement): string | undefined => {
const metaApplication = head.querySelector("meta[name=application-name]");
Expand All @@ -18,16 +18,24 @@ const resolveMetaAppName = (head: HTMLHeadElement): string | undefined => {
return undefined;
};

export const DEFAULT_HOSTS = ["github.com", "gitlab.com", "bitbucket.org", "dev.azure.com"];

/**
* Provides a fast check to see if the current URL is on a supported site.
*/
export const isSiteSuitable = (): boolean => {
const isWhitelistedHost = DEFAULT_HOSTS.some((host) => location.host === host);
if (isWhitelistedHost) {
return true;
}

const appName = resolveMetaAppName(document.head);
if (!appName) {
return false;
}
const allowedApps = ["GitHub", "GitLab", "Bitbucket"];
return allowedApps.includes(appName);

return allowedApps.some((allowedApp) => appName.includes(allowedApp));
};

export interface ButtonContributionParams {
Expand All @@ -51,7 +59,7 @@ export interface ButtonContributionParams {
/**
* The element in which the button should be inserted.
*
* This element will be inserted into teh main document and allows for styling within the original page.
* This element will be inserted into the main document and allows for styling within the original page.
*
* The structure looks like this:
*
Expand Down Expand Up @@ -98,6 +106,8 @@ export interface ButtonContributionParams {
* the classnames to remove and add.
*/
manipulations?: { element: string; remove?: string; add?: string; style?: Partial<CSSStyleDeclaration> }[];

additionalParser?: (lookupElement: (path: string) => HTMLElement, originalUrl: string) => string;
}

function createElement(
Expand All @@ -113,6 +123,53 @@ function createElement(
}

export const buttonContributions: ButtonContributionParams[] = [
// Azure DevOps
{
id: "ado-repo",
exampleUrls: [
// "https://dev.azure.com/services-azure/_git/project2"
],
selector: "div.repos-files-header-commandbar:nth-child(1)",
containerElement: createElement("div", {}),
application: "azure-devops",
insertBefore: `div.bolt-header-command-item-button:has(button[id^="__bolt-header-command-bar-menu-button"])`,
manipulations: [
{
element: "div.repos-files-header-commandbar.scroll-hidden",
remove: "scroll-hidden",
},
],
additionalParser(lookupElement, originalUrl) {
if (originalUrl.includes("version=GB")) {
return originalUrl;
}
const url = new URL(originalUrl);
// version=GBdevelop
const branch = lookupElement("xpath://div[contains(@class, 'version-dropdown')]//span[contains(@class, 'text-ellipsis')]/text()")?.textContent;
if (branch) {
url.searchParams.set("version", `GB${branch}`);
}
return url.toString();
},
},
{
id: "ado-pr",
exampleUrls: [
// "https://dev.azure.com/services-azure/test-project/_git/repo2/pullrequest/1"
],
selector: ".repos-pr-header > div:nth-child(2) > div:nth-child(1)",
containerElement: createElement("div", {}),
application: "azure-devops",
insertBefore: `div.bolt-header-command-item-button:has(button[id^="__bolt-menu-button-"])`,
},
{
id: "ado-repo-empty",
exampleUrls: [],
selector: "div.clone-with-application",
application: "azure-devops",
containerElement: createElement("div", { marginLeft: "4px", marginRight: "4px" }),
},

// GitLab
{
id: "gl-repo", // also taking care of branches
Expand Down
23 changes: 23 additions & 0 deletions src/button/button.css
Original file line number Diff line number Diff line change
Expand Up @@ -315,3 +315,26 @@
0 0 1px rgba(9, 30, 66, 0.31)
);
}

.azure-devops {
--font-family: "Segoe UI", "-apple-system", BlinkMacSystemFont, Roboto, "Helvetica Neue", Helvetica, Ubuntu, Arial,
sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol";
--primary-bg-color: var(--communication-background, rgba(0, 120, 212, 1));
--primary-hover-bg-color: rgba(var(--palette-primary-darkened-6, 0, 103, 181), 1);
--primary-color: var(--text-on-communication-background, rgba(255, 255, 255, 1));
--primary-hover-color: var(--text-on-communication-background, rgba(255, 255, 255, 1));
--primary-separator-color: rgba(var(--palette-primary-darkened-10, 0, 91, 161), 1);
--font-weight: 600;
--primary-height: 32px;

--dropdown-color: var(--text-primary-color, rgba(0, 0, 0, 0.9));
--dropdown-bg-color: var(--callout-background-color, rgba(255, 255, 255, 1));
--dropdown-hover-bg-color: var(--palette-black-alpha-4, rgba(0, 0, 0, 0.04));
--dropdown-box-shadow: 0 3.2px 7.2px 0 var(--callout-shadow-color, rgba(0, 0, 0, 0.132)),
0 0.6px 1.8px 0 var(--callout-shadow-secondary-color, rgba(0, 0, 0, 0.108));
--dropdown-border-width: 0;
--dropdown-border-radius: 4px;

--border-radius: 2px;
--border-width: 0px;
}
30 changes: 17 additions & 13 deletions src/button/button.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,14 @@ import { DEFAULT_GITPOD_ENDPOINT, EVENT_CURRENT_URL_CHANGED } from "~constants";
import { STORAGE_KEY_ADDRESS, STORAGE_KEY_ALWAYS_OPTIONS, STORAGE_KEY_NEW_TAB } from "~storage";

import type { SupportedApplication } from "./button-contributions";
import { BitbucketCaret, CaretForProvider, GitHubCaret } from "./CaretForProvider";
import { CaretForProvider } from "./CaretForProvider";

type Props = {
application: SupportedApplication;
additionalClassNames?: string[];
contextParser?: (url: string) => string;
};
export const GitpodButton = ({ application, additionalClassNames }: Props) => {
export const GitpodButton = ({ application, additionalClassNames, contextParser }: Props) => {
const [address] = useStorage<string>(STORAGE_KEY_ADDRESS, DEFAULT_GITPOD_ENDPOINT);
const [openInNewTab] = useStorage<boolean>(STORAGE_KEY_NEW_TAB, true);
const [disableAutostart] = useStorage<boolean>(STORAGE_KEY_ALWAYS_OPTIONS, false);
Expand All @@ -37,17 +38,20 @@ export const GitpodButton = ({ application, additionalClassNames }: Props) => {
}, []);

const actions = useMemo(
() => [
{
href: `${address}/?autostart=${!disableAutostart}#${currentHref}`,
label: "Open",
},
{
href: `${address}/?autostart=false#${currentHref}`,
label: "Open with options...",
},
],
[address, disableAutostart, currentHref],
() => {
const parsedHref = !contextParser ? currentHref : contextParser(currentHref);
return [
{
href: `${address}/?autostart=${!disableAutostart}#${parsedHref}`,
label: "Open",
},
{
href: `${address}/?autostart=false#${parsedHref}`,
label: "Open with options...",
},
];
},
[address, disableAutostart, currentHref, contextParser],
);
const dropdownRef = useRef<HTMLDivElement | null>(null);
const firstActionRef = useRef<HTMLAnchorElement | null>(null);
Expand Down
4 changes: 3 additions & 1 deletion src/contents/button.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,9 @@ import { EVENT_CURRENT_URL_CHANGED } from "~constants";
import { GitpodButton } from "../button/button";
import { buttonContributions, isSiteSuitable, type ButtonContributionParams } from "../button/button-contributions";

// keep in sync with DEFAULT_HOSTS in src/button/button-contributions.ts
export const config: PlasmoCSConfig = {
matches: ["https://github.com/*", "https://gitlab.com/*", "https://bitbucket.org/*"],
matches: ["https://github.com/*", "https://gitlab.com/*", "https://bitbucket.org/*", "https://dev.azure.com/*"],
};

export const getStyle = () => {
Expand Down Expand Up @@ -45,6 +46,7 @@ class ButtonContributionManager {
key={containerId}
application={contribution.application}
additionalClassNames={contribution.additionalClassNames}
contextParser={(url) => contribution.additionalParser ? contribution.additionalParser((path) => this.lookupElement(path), url) : url}
/>,
);
}
Expand Down
65 changes: 62 additions & 3 deletions test/src/button-contributions-copy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
* Happy about anyone who's able to make this work with imports (i.e. run the tests in this project), but I couldn't figure it out and gave up.
*/

export type SupportedApplication = "github" | "gitlab" | "bitbucket-server" | "bitbucket";
export type SupportedApplication = "github" | "gitlab" | "bitbucket-server" | "bitbucket" | "azure-devops";

const resolveMetaAppName = (head: HTMLHeadElement): string | undefined => {
const metaApplication = head.querySelector("meta[name=application-name]");
Expand All @@ -18,16 +18,24 @@ const resolveMetaAppName = (head: HTMLHeadElement): string | undefined => {
return undefined;
};

export const DEFAULT_HOSTS = ["github.com", "gitlab.com", "bitbucket.org", "dev.azure.com"];

/**
* Provides a fast check to see if the current URL is on a supported site.
*/
export const isSiteSuitable = (): boolean => {
const isWhitelistedHost = DEFAULT_HOSTS.some((host) => location.host === host);
if (isWhitelistedHost) {
return true;
}

const appName = resolveMetaAppName(document.head);
if (!appName) {
return false;
}
const allowedApps = ["GitHub", "GitLab", "Bitbucket"];
return allowedApps.includes(appName);

return allowedApps.some((allowedApp) => appName.includes(allowedApp));
};

export interface ButtonContributionParams {
Expand All @@ -51,7 +59,7 @@ export interface ButtonContributionParams {
/**
* The element in which the button should be inserted.
*
* This element will be inserted into teh main document and allows for styling within the original page.
* This element will be inserted into the main document and allows for styling within the original page.
*
* The structure looks like this:
*
Expand Down Expand Up @@ -98,6 +106,8 @@ export interface ButtonContributionParams {
* the classnames to remove and add.
*/
manipulations?: { element: string; remove?: string; add?: string; style?: Partial<CSSStyleDeclaration> }[];

additionalParser?: (lookupElement: (path: string) => HTMLElement) => (originalUrl: string) => string;
}

function createElement(
Expand All @@ -113,6 +123,55 @@ function createElement(
}

export const buttonContributions: ButtonContributionParams[] = [
// Azure DevOps
{
id: "ado-repo",
exampleUrls: [
// "https://dev.azure.com/services-azure/_git/project2"
],
selector: "div.repos-files-header-commandbar:nth-child(1)",
containerElement: createElement("div", {}),
application: "azure-devops",
insertBefore: `div.bolt-header-command-item-button:has(button[id^="__bolt-header-command-bar-menu-button"])`,
manipulations: [
{
element: "div.repos-files-header-commandbar.scroll-hidden",
remove: "scroll-hidden",
},
],
additionalParser(lookupElement) {
return (originalUrl) => {
if (originalUrl.includes("version=GB")) {
return originalUrl;
}
const url = new URL(originalUrl);
// version=GBdevelop
const branch = lookupElement("xpath://div[contains(@class, 'version-dropdown')]//span[contains(@class, 'text-ellipsis')]/text()")?.textContent;
if (branch) {
url.searchParams.set("version", `GB${branch}`);
}
return url.toString();
};
},
},
{
id: "ado-pr",
exampleUrls: [
// "https://dev.azure.com/services-azure/test-project/_git/repo2/pullrequest/1"
],
selector: ".repos-pr-header > div:nth-child(2) > div:nth-child(1)",
containerElement: createElement("div", {}),
application: "azure-devops",
insertBefore: `div.bolt-header-command-item-button:has(button[id^="__bolt-menu-button-"])`,
},
{
id: "ado-repo-empty",
exampleUrls: [],
selector: "div.clone-with-application",
application: "azure-devops",
containerElement: createElement("div", { marginLeft: "4px", marginRight: "4px" }),
},

// GitLab
{
id: "gl-repo", // also taking care of branches
Expand Down
Loading