Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

1329 when already a fundingjson present propose editing it instead of creating #1386

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
4e56585
Establish method to fetch funding.json, struggle with typescript
Dec 5, 2024
3beed21
Code to support changed funding.json and highlighting
Dec 6, 2024
859f0ca
Display additions when funding.json already has content
Dec 6, 2024
8984e96
Remove dripsJsonTemplate in favor of methods for changed template
Dec 6, 2024
d1832aa
Code cleanup
Dec 6, 2024
d8a6f45
Integrate github changes with newer changes
Dec 6, 2024
5bf1399
Highlight last ,
Dec 9, 2024
d85d1bd
First pass at edit mode for the code box
Dec 9, 2024
d972b99
Remove commented out code
Dec 9, 2024
f10de04
Remove invalid value parameter from edit link
Dec 9, 2024
f5679db
Make determination of highlight start less magic string-y
Dec 9, 2024
d859956
Further improve determination of highlight start
Dec 9, 2024
88462b7
Update insertion order comment
Dec 9, 2024
55d4b6f
Change the text box label when editing
Dec 9, 2024
9df18bc
Update instructional text
Dec 10, 2024
a5ded18
Do project information gathering before we ask user to add/update fun…
Dec 10, 2024
85d4ff1
Use context to display funding.json related data
Dec 10, 2024
c49391b
Extra loading of funding json data into own function
Dec 10, 2024
4017139
Centralize github api client for loading funding.json
Dec 10, 2024
ffe189e
Load funding info when going forward from enter git url
Dec 11, 2024
454dbd0
Add some docs
Dec 11, 2024
6678488
Comment correction
Dec 11, 2024
7c0f64e
Use load instead of loading
Dec 11, 2024
1c62b2b
Remove unnecessary async for awaited going forward
Dec 11, 2024
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
28 changes: 20 additions & 8 deletions src/lib/components/code-box/code-box.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,14 @@
import CopyIcon from '$lib/components/icons/Copy.svelte';
import Button from '../button/button.svelte';
import sanitize from 'sanitize-html';
import insertTextAtIndices from '$lib/utils/insert-text-at-indicies';

export let path: string;
export let code: string;
export let repoUrl: string;
export let defaultBranch = 'main';
export let highlight: [number | null, number | null] = [null, null];
export let editing: boolean = false;

let headerElem: HTMLElement | undefined;

Expand All @@ -19,6 +22,18 @@

$: textColor = primaryColor ? getContrastColor(primaryColor) : undefined;

$: sanitizedCode = sanitize(code, {
allowedTags: [],
allowedAttributes: {},
});
$: displayCode = highlight.some((v) => v === null)
? sanitizedCode
: insertTextAtIndices(sanitizedCode, {
[highlight[0] as number]: '<mark class="typo-text-diff-additive">',
[highlight[1] as number]: '</mark>',
});
$: ctaText = editing ? 'Edit on GitHub' : 'Add to your repo';

let copySuccess = false;

async function copyClipboard(text: string) {
Expand All @@ -28,9 +43,9 @@
}

// TODO: add support for Gitlab.
$: gitHubProposalUrl = `${repoUrl}/new/${defaultBranch}?filename=FUNDING.json&value=${encodeURIComponent(
code,
)}`;
$: gitHubProposalUrl = editing
? `${repoUrl}/edit/${defaultBranch}/FUNDING.json`
: `${repoUrl}/new/${defaultBranch}?filename=FUNDING.json&value=${encodeURIComponent(code)}`;
</script>

<section class="codebox relative text-left">
Expand All @@ -48,16 +63,13 @@
</header>
<div class="code-wrapper">
<code class="typo-text-mono">
{@html sanitize(code, {
allowedTags: [],
allowedAttributes: {},
})}
{@html displayCode}
</code>
</div>
{#if repoUrl.includes('github')}
<footer class="absolute bottom-4 right-4">
<Button variant="primary" icon={ArrowBoxUpRight} href={gitHubProposalUrl} target="_blank">
Add to your repo</Button
{ctaText}</Button
>
</footer>
{/if}
Expand Down
12 changes: 12 additions & 0 deletions src/lib/flows/claim-project-flow/claim-project-flow.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@ import { gql } from 'graphql-request';
import type { ClaimProjectFlowProjectFragment } from './__generated__/gql.generated';
import type { Items, Weights } from '$lib/components/list-editor/types';
import ChooseNetwork from './steps/choose-network/choose-network.svelte';
import type { FundingJson } from '$lib/utils/github/GitHub';
import type { TemplateHighlight } from './steps/add-ethereum-address/drips-json-template';

export const CLAIM_PROJECT_FLOW_PROJECT_FRAGMENT = gql`
${ENTER_GIT_URL_STEP_PROJECT_FRAGMENT}
Expand Down Expand Up @@ -67,6 +69,11 @@ export interface State {
cid: string;
};
projectColor: string;
funding: {
json: string;
object: FundingJson;
highlight: TemplateHighlight;
};
}

export const state = () =>
Expand All @@ -92,6 +99,11 @@ export const state = () =>
emoji: '💧',
},
projectColor: '#000000',
funding: {
json: '{}',
object: {},
highlight: [null, null],
},
});

export function slotsTemplate(state: State, stepIndex: number): Slots {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@
import unreachable from '$lib/utils/unreachable';
import { createEventDispatcher, onMount } from 'svelte';
import StandaloneFlowStepLayout from '$lib/components/standalone-flow-step-layout/standalone-flow-step-layout.svelte';
import dripsJsonTemplate from './drips-json-template';
import type { StepComponentEvents } from '$lib/components/stepper/types';
import Button from '$lib/components/button/button.svelte';
import ArrowLeft from '$lib/components/icons/ArrowLeft.svelte';
Expand All @@ -36,6 +35,19 @@

export let context: Writable<State>;

$: network = $walletStore.network.name
? $walletStore.network.name === 'homestead'
? 'ethereum'
: $walletStore.network.name
: unreachable();
$: editing = $context.funding.object && Object.keys($context.funding.object).length > 0;
$: description = editing
? `To verify you are the owner of this project, please add your owner address for ${network} to your FUNDING.json file.`
: `To verify you are the owner of this project, please add a FUNDING.json file with your owner address for ${network} to the default branch of your repository.`;
$: checkboxLabel = editing
? 'I edited the FUNDING.json file'
: 'I added the FUNDING.json file to the root of my repo.';

onMount(() => {
$context.linkedToRepo = false;
});
Expand Down Expand Up @@ -113,24 +125,16 @@
$: formValid = $walletStore.connected && checked;
</script>

<StandaloneFlowStepLayout
headline="Verify project ownership"
description="To verify you are the owner of this project, please add a FUNDING.json file with your Ethereum address to the default branch of your repository. "
>
<StandaloneFlowStepLayout headline="Verify project ownership" {description}>
<CodeBox
repoUrl={$context.gitUrl}
defaultBranch={$context.projectMetadata?.defaultBranch}
path="./FUNDING.json"
code={dripsJsonTemplate(
$walletStore.address ?? unreachable(),
$walletStore.network.name
? $walletStore.network.name === 'homestead'
? 'ethereum'
: $walletStore.network.name
: unreachable(),
)}
code={$context.funding.json}
highlight={$context.funding.highlight}
{editing}
/>
<Checkbox bind:checked label="I added the FUNDING.json file to the root of my repo." />
<Checkbox bind:checked label={checkboxLabel} />
<svelte:fragment slot="left-actions">
<Button icon={ArrowLeft} on:click={() => dispatch('goBackward')}>Back</Button>
</svelte:fragment>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,41 @@
export default (address: string, network = 'ethereum') => `{
"drips": {
"${network}": {
"ownedBy": "${address}"
}
import type { FundingJson } from '$lib/utils/github/GitHub';

const NUM_SPACES = 2;

export type TemplateHighlight = [number | null, number | null];

export const getChangedTemplate = (
existingJson: object,
address: string,
network: string,
): [string, TemplateHighlight] => {
// no change, so don't highlight anything
if (Object.keys(existingJson).length === 0) {
return [JSON.stringify(objectTemplate(address, network), null, NUM_SPACES), [null, null]];
}
}
`;

// object string keys are iterated in insertion order, so when we add the new network here, it will
// always appear last in the JSON representation.
const existingJsonCopy = JSON.parse(JSON.stringify(existingJson));
existingJsonCopy.drips[network] = {
ownedBy: address,
};

const asJSON = JSON.stringify(existingJsonCopy, null, NUM_SPACES);
const end = asJSON.lastIndexOf('}');
// highlight should start where the drips object starts a final
// object listing.
const start = asJSON.lastIndexOf(',\n');

return [asJSON, [start, end]];
};

export const objectTemplate = (address: string, network = 'ethereum'): FundingJson => {
return {
drips: {
[network]: {
ownedBy: address,
},
},
};
};
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,23 @@
import { createEventDispatcher } from 'svelte';
import type { StepComponentEvents } from '$lib/components/stepper/types';
import Button from '$lib/components/button/button.svelte';
import type { Writable } from 'svelte/store';
import type { State } from '../../claim-project-flow';
import { loadFundingInfo } from '../enter-git-url/enter-git-url';

const dispatch = createEventDispatcher<StepComponentEvents>();

export let context: Writable<State>;

$: formValid = $walletStore.connected;

function verifyProject() {
dispatch('goForward');
dispatch('await', {
message: 'Gathering project information…',
promise: () => {
return loadFundingInfo(context);
},
});
}
</script>

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@
import walletStore from '$lib/stores/wallet/wallet.store';
import ArrowLeft from '$lib/components/icons/ArrowLeft.svelte';
import modal from '$lib/stores/modal';
import { loadFundingInfo } from './enter-git-url';

export let context: Writable<State>;
export let projectUrl: string | undefined = undefined;
Expand Down Expand Up @@ -258,6 +259,15 @@
submitInput();
}

function goForward() {
dispatch('await', {
message: 'Gathering project information…',
promise: () => {
return loadFundingInfo(context);
},
});
}

onMount(() => {
modal.setWarnOnNavigate(true);
});
Expand Down Expand Up @@ -297,9 +307,7 @@
{/if}
<svelte:fragment slot="actions">
{#if formValid}
<Button icon={ArrowRightIcon} variant="primary" on:click={() => dispatch('goForward')}
>Continue</Button
>
<Button icon={ArrowRightIcon} variant="primary" on:click={goForward}>Continue</Button>
{:else}
<Button
disabled={!inputSubmittable}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import type { State } from '../../claim-project-flow';
import unreachable from '$lib/utils/unreachable';
import { getChangedTemplate } from '../add-ethereum-address/drips-json-template';
import GitHub from '$lib/utils/github/GitHub';
import { get, type Writable } from 'svelte/store';
import walletStore from '$lib/stores/wallet/wallet.store';
import { Octokit } from '@octokit/rest';

const octokit = new Octokit();
const github = new GitHub(octokit);

export async function loadFundingInfo(context: Writable<State>): Promise<void> {
const $walletStore = get(walletStore);
const address = $walletStore.address ?? '';
const network = $walletStore.network.name
? $walletStore.network.name === 'homestead'
? 'ethereum'
: $walletStore.network.name
: '';

// We can't make a useful FUNDING.json without an address or network.
if (!address || !network) {
return;
}

let fundingObject = {};
try {
const $context = get(context);
const { ownerName, repoName } = $context.project?.source ?? unreachable();
fundingObject = await github.fetchFundingJson(ownerName, repoName);
} catch (error) {
// if the FUNDING.json is not found, that's fine. It means we need a new one, so
// we continue below. Otherwise (here), something is wrong and we should show
// an error.
if (!(error as Error).message.includes('not found')) {
throw error;
}
}

const [fundingJson, jsonHighlight] = getChangedTemplate(fundingObject, address, network);

context.update((c) => {
c.funding = {
object: fundingObject,
json: fundingJson,
highlight: jsonHighlight,
};
return c;
});
}
44 changes: 27 additions & 17 deletions src/lib/utils/github/GitHub.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,14 @@ import network from '$lib/stores/wallet/network';
import type { Octokit } from '@octokit/rest';
import { Buffer } from 'buffer';

export type FundingJson = {
drips?: {
[key: string]: {
ownedBy: string;
};
};
};

export default class GitHub {
private octokit: Octokit;

Expand Down Expand Up @@ -31,35 +39,37 @@ export default class GitHub {
}

public async getFundingJsonAddress(owner: string, repo: string): Promise<string | null> {
const { data } = await this.octokit.repos
.getContent({
const fundingJson = await this.fetchFundingJson(owner, repo);
return (
fundingJson.drips?.[network.name === 'homestead' ? 'ethereum' : network.name].ownedBy ?? null
);
}

public async fetchFundingJson(owner: string, repo: string): Promise<FundingJson> {
let data;
try {
({ data } = await this.octokit.repos.getContent({
owner,
repo,
path: 'FUNDING.json',
request: {
cache: 'reload',
},
headers: {
'If-None-Match': '',
},
})
.catch(() => {
throw new Error('FUNDING.json not found.');
});
}));
} catch (error) {
// eslint-disable-next-line no-console
console.error('FUNDING.json not found', error);
throw new Error('FUNDING.json not found.');
}

try {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const fileContent = Buffer.from((data as any).content, 'base64').toString('utf-8');

const fundingJson = JSON.parse(fileContent);
return (
fundingJson.drips?.[network.name === 'homestead' ? 'ethereum' : network.name].ownedBy ??
null
);
} catch (e) {
return JSON.parse(fileContent);
} catch (error) {
// eslint-disable-next-line no-console
console.error(e);

console.error('FUNDING.json not parseable', error);
throw new Error(
`Unable to parse the FUNDING.json file. Ensure it exists, is valid JSON, and includes an address for ${network.label}.`,
);
Expand Down
8 changes: 8 additions & 0 deletions src/lib/utils/insert-text-at-indicies.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
export default function insertTextAtIndices(
content: string,
replacements: { [key: number]: string },
): string {
return content.replace(/./g, (character, index) => {
return replacements[index] ? replacements[index] + character : character;
});
}
Loading
Loading