mirror of
https://github.com/gitbutlerapp/gitbutler.git
synced 2024-12-25 10:33:21 +03:00
fix: use our own open-rs
implementation instead of relying on tauri's "shell-open" (#4748)
Co-authored-by: Yerke Tulibergenov <yerke@squareup.com> Co-authored-by: Caleb Owens <caleb@gitbutler.com> Co-authored-by: Pavel Laptev <pawellaptew@gmail.com> Co-authored-by: Mattias Granlund <mtsgrd@gmail.com> Co-authored-by: Sebastian Thiel <sebastian.thiel@icloud.com> Co-authored-by: GitButler <gitbutler@gitbutler.com>
This commit is contained in:
parent
6fdcf9fcc8
commit
68f0a3c288
73
Cargo.lock
generated
73
Cargo.lock
generated
@ -2579,7 +2579,7 @@ dependencies = [
|
||||
"gix",
|
||||
"log",
|
||||
"once_cell",
|
||||
"open 5.3.0",
|
||||
"open",
|
||||
"parking_lot 0.12.3",
|
||||
"pretty_assertions",
|
||||
"reqwest 0.12.7",
|
||||
@ -2599,6 +2599,7 @@ dependencies = [
|
||||
"tracing-appender",
|
||||
"tracing-forest",
|
||||
"tracing-subscriber",
|
||||
"url",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -5342,16 +5343,6 @@ version = "11.1.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b410bbe7e14ab526a0e86877eb47c6996a2bd7746f027ba551028c925390e4e9"
|
||||
|
||||
[[package]]
|
||||
name = "open"
|
||||
version = "3.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2078c0039e6a54a0c42c28faa984e115fb4c2d5bf2208f77d1961002df8576f8"
|
||||
dependencies = [
|
||||
"pathdiff",
|
||||
"windows-sys 0.42.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "open"
|
||||
version = "5.3.0"
|
||||
@ -7345,12 +7336,10 @@ dependencies = [
|
||||
"indexmap 1.9.3",
|
||||
"objc",
|
||||
"once_cell",
|
||||
"open 3.2.0",
|
||||
"os_info",
|
||||
"percent-encoding",
|
||||
"rand 0.8.5",
|
||||
"raw-window-handle",
|
||||
"regex",
|
||||
"reqwest 0.11.27",
|
||||
"rfd",
|
||||
"semver",
|
||||
@ -7408,7 +7397,6 @@ dependencies = [
|
||||
"png",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"regex",
|
||||
"semver",
|
||||
"serde",
|
||||
"serde_json",
|
||||
@ -8657,21 +8645,6 @@ dependencies = [
|
||||
"windows-targets 0.52.6",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-sys"
|
||||
version = "0.42.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5a3e1820f08b8513f676f7ab6c1f99ff312fb97b553d30ff4dd86f9f15728aa7"
|
||||
dependencies = [
|
||||
"windows_aarch64_gnullvm 0.42.2",
|
||||
"windows_aarch64_msvc 0.42.2",
|
||||
"windows_i686_gnu 0.42.2",
|
||||
"windows_i686_msvc 0.42.2",
|
||||
"windows_x86_64_gnu 0.42.2",
|
||||
"windows_x86_64_gnullvm 0.42.2",
|
||||
"windows_x86_64_msvc 0.42.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-sys"
|
||||
version = "0.48.0"
|
||||
@ -8745,12 +8718,6 @@ dependencies = [
|
||||
"windows-targets 0.52.6",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows_aarch64_gnullvm"
|
||||
version = "0.42.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8"
|
||||
|
||||
[[package]]
|
||||
name = "windows_aarch64_gnullvm"
|
||||
version = "0.48.5"
|
||||
@ -8775,12 +8742,6 @@ version = "0.39.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ec7711666096bd4096ffa835238905bb33fb87267910e154b18b44eaabb340f2"
|
||||
|
||||
[[package]]
|
||||
name = "windows_aarch64_msvc"
|
||||
version = "0.42.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43"
|
||||
|
||||
[[package]]
|
||||
name = "windows_aarch64_msvc"
|
||||
version = "0.48.5"
|
||||
@ -8805,12 +8766,6 @@ version = "0.39.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "763fc57100a5f7042e3057e7e8d9bdd7860d330070251a73d003563a3bb49e1b"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_gnu"
|
||||
version = "0.42.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_gnu"
|
||||
version = "0.48.5"
|
||||
@ -8841,12 +8796,6 @@ version = "0.39.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7bc7cbfe58828921e10a9f446fcaaf649204dcfe6c1ddd712c5eebae6bda1106"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_msvc"
|
||||
version = "0.42.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_msvc"
|
||||
version = "0.48.5"
|
||||
@ -8871,12 +8820,6 @@ version = "0.39.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6868c165637d653ae1e8dc4d82c25d4f97dd6605eaa8d784b5c6e0ab2a252b65"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_gnu"
|
||||
version = "0.42.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_gnu"
|
||||
version = "0.48.5"
|
||||
@ -8889,12 +8832,6 @@ version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_gnullvm"
|
||||
version = "0.42.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_gnullvm"
|
||||
version = "0.48.5"
|
||||
@ -8919,12 +8856,6 @@ version = "0.39.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5e4d40883ae9cae962787ca76ba76390ffa29214667a111db9e0a1ad8377e809"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_msvc"
|
||||
version = "0.42.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_msvc"
|
||||
version = "0.48.5"
|
||||
|
@ -6,7 +6,7 @@
|
||||
import * as events from '$lib/utils/events';
|
||||
import { createKeybind } from '$lib/utils/hotkeys';
|
||||
import { unsubscribe } from '$lib/utils/unsubscribe';
|
||||
import { open } from '@tauri-apps/api/shell';
|
||||
import { openExternalUrl } from '$lib/utils/url';
|
||||
import { onMount } from 'svelte';
|
||||
import { goto } from '$app/navigation';
|
||||
|
||||
@ -29,7 +29,7 @@
|
||||
'menu://project/open-in-vscode/clicked',
|
||||
async () => {
|
||||
const path = `${$editor}://file${project.vscodePath}?windowId=_blank`;
|
||||
open(path);
|
||||
openExternalUrl(path);
|
||||
}
|
||||
);
|
||||
|
||||
|
@ -1,10 +1,9 @@
|
||||
<script lang="ts">
|
||||
import { MessageRole } from '$lib/ai/types';
|
||||
import Markdown from '$lib/components/Markdown.svelte';
|
||||
import { autoHeight } from '$lib/utils/autoHeight';
|
||||
import { getMarkdownRenderer } from '$lib/utils/markdown';
|
||||
import Button from '@gitbutler/ui/Button.svelte';
|
||||
import Icon from '@gitbutler/ui/Icon.svelte';
|
||||
import { marked } from 'marked';
|
||||
import { createEventDispatcher } from 'svelte';
|
||||
|
||||
export let disableRemove = false;
|
||||
@ -19,7 +18,6 @@
|
||||
addExample: void;
|
||||
input: string;
|
||||
}>();
|
||||
const markedRenderer = getMarkdownRenderer();
|
||||
let textareaElement: HTMLTextAreaElement | undefined;
|
||||
|
||||
function focusTextareaOnMount(
|
||||
@ -73,7 +71,7 @@
|
||||
></textarea>
|
||||
{:else}
|
||||
<div class="markdown bubble-message scrollbar text-13 text-body">
|
||||
{@html marked.parse(promptMessage.content, { renderer: markedRenderer })}
|
||||
<Markdown content={promptMessage.content} />
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
|
@ -50,10 +50,7 @@
|
||||
</div>
|
||||
<button
|
||||
class="empty-board__suggestions__link"
|
||||
on:click={async () =>
|
||||
await openExternalUrl(
|
||||
'https://docs.gitbutler.com/features/virtual-branches/branch-lanes'
|
||||
)}
|
||||
on:click={async () => await openExternalUrl('https://docs.gitbutler.com')}
|
||||
>
|
||||
<div class="empty-board__suggestions__link__icon">
|
||||
<Icon name="docs" />
|
||||
|
@ -4,19 +4,18 @@
|
||||
import { Project } from '$lib/backend/projects';
|
||||
import CommitCard from '$lib/commit/CommitCard.svelte';
|
||||
import { transformAnyCommit } from '$lib/commitLines/transformers';
|
||||
import Markdown from '$lib/components/Markdown.svelte';
|
||||
import FileCard from '$lib/file/FileCard.svelte';
|
||||
import { getGitHost } from '$lib/gitHost/interface/gitHost';
|
||||
import ScrollableContainer from '$lib/scroll/ScrollableContainer.svelte';
|
||||
import { SETTINGS, type Settings } from '$lib/settings/userSettings';
|
||||
import { RemoteBranchService } from '$lib/stores/remoteBranches';
|
||||
import { getContext, getContextStoreBySymbol } from '$lib/utils/context';
|
||||
import { getMarkdownRenderer } from '$lib/utils/markdown';
|
||||
import { FileIdSelection } from '$lib/vbranches/fileIdSelection';
|
||||
import { BranchData, type Branch } from '$lib/vbranches/types';
|
||||
import LineGroup from '@gitbutler/ui/commitLines/LineGroup.svelte';
|
||||
import { LineManagerFactory } from '@gitbutler/ui/commitLines/lineManager';
|
||||
import lscache from 'lscache';
|
||||
import { marked } from 'marked';
|
||||
import { onMount, setContext } from 'svelte';
|
||||
import { writable } from 'svelte/store';
|
||||
import type { PullRequest } from '$lib/gitHost/interface/types';
|
||||
@ -93,8 +92,6 @@
|
||||
onMount(() => {
|
||||
laneWidth = lscache.get(laneWidthKey);
|
||||
});
|
||||
|
||||
const renderer = getMarkdownRenderer();
|
||||
</script>
|
||||
|
||||
{#if remoteBranch || localBranch}
|
||||
@ -112,7 +109,7 @@
|
||||
<div class="card__header text-14 text-body text-semibold">{pr.title}</div>
|
||||
{#if pr.body}
|
||||
<div class="markdown card__content text-13 text-body">
|
||||
{@html marked.parse(pr.body, { renderer })}
|
||||
<Markdown content={pr.body} />
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
|
@ -3,6 +3,7 @@
|
||||
import gbLogoSvg from '$lib/assets/gb-logo.svg?raw';
|
||||
import { User } from '$lib/stores/user';
|
||||
import { getContextStore } from '$lib/utils/context';
|
||||
import { openExternalUrl } from '$lib/utils/url';
|
||||
import Icon from '@gitbutler/ui/Icon.svelte';
|
||||
import { type Snippet } from 'svelte';
|
||||
|
||||
@ -68,22 +69,20 @@
|
||||
|
||||
<div class="right-side__meta">
|
||||
<div class="right-side__links">
|
||||
<a
|
||||
<button
|
||||
class="right-side__link"
|
||||
target="_blank"
|
||||
href="https://docs.gitbutler.com/features/virtual-branches/branch-lanes"
|
||||
onclick={async () => await openExternalUrl('https://docs.gitbutler.com/')}
|
||||
>
|
||||
<Icon name="docs" opacity={0.6} />
|
||||
<span class="text-14 text-semibold">GitButler docs</span>
|
||||
</a>
|
||||
<a
|
||||
</button>
|
||||
<button
|
||||
class="right-side__link"
|
||||
target="_blank"
|
||||
href="https://discord.com/invite/MmFkmaJ42D"
|
||||
onclick={async () => await openExternalUrl('https://discord.com/invite/MmFkmaJ42D')}
|
||||
>
|
||||
<Icon name="discord" opacity={0.6} />
|
||||
<span class="text-14 text-semibold">Join community</span>
|
||||
</a>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="wordmark">
|
||||
|
24
apps/desktop/src/lib/components/Markdown.svelte
Normal file
24
apps/desktop/src/lib/components/Markdown.svelte
Normal file
@ -0,0 +1,24 @@
|
||||
<script lang="ts">
|
||||
import MarkdownContent from '$lib/components/MarkdownContent.svelte';
|
||||
import { options } from '$lib/utils/markdownRenderers';
|
||||
import { Lexer } from 'marked';
|
||||
|
||||
interface Props {
|
||||
content: string;
|
||||
}
|
||||
|
||||
let { content }: Props = $props();
|
||||
|
||||
const lexer = new Lexer(options);
|
||||
const tokens = lexer.lex(content);
|
||||
</script>
|
||||
|
||||
<div class="markdown-content">
|
||||
<MarkdownContent type="init" {tokens} />
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.markdown-content {
|
||||
display: inline;
|
||||
}
|
||||
</style>
|
32
apps/desktop/src/lib/components/MarkdownContent.svelte
Normal file
32
apps/desktop/src/lib/components/MarkdownContent.svelte
Normal file
@ -0,0 +1,32 @@
|
||||
<script lang="ts">
|
||||
/* eslint svelte/valid-compile: "off" */
|
||||
import { renderers } from '$lib/utils/markdownRenderers';
|
||||
import type { Tokens, Token } from 'marked';
|
||||
|
||||
type Props =
|
||||
| { type: 'init'; tokens: Token[] }
|
||||
| Tokens.Link
|
||||
| Tokens.Heading
|
||||
| Tokens.Image
|
||||
| Tokens.Space
|
||||
| Tokens.Blockquote
|
||||
| Tokens.Code
|
||||
| Tokens.Codespan
|
||||
| Tokens.Text;
|
||||
|
||||
let { type, ...rest }: Props = $props();
|
||||
</script>
|
||||
|
||||
{#if type && renderers[type as keyof typeof renderers]}
|
||||
<svelte:component this={renderers[type as keyof typeof renderers] as any} {...rest}>
|
||||
{#if 'tokens' in rest}
|
||||
<svelte:self tokens={rest.tokens} />
|
||||
{/if}
|
||||
</svelte:component>
|
||||
{:else if 'tokens' in rest && rest.tokens}
|
||||
{#each rest.tokens as token}
|
||||
<svelte:self {...token} />
|
||||
{/each}
|
||||
{:else if 'raw' in rest}
|
||||
{@html rest.raw?.replaceAll('\n', '<br />') ?? ''}
|
||||
{/if}
|
@ -2,18 +2,17 @@
|
||||
// This is always displayed in the context of not having a cooresponding vbranch or remote
|
||||
import { Project } from '$lib/backend/projects';
|
||||
import { BaseBranchService } from '$lib/baseBranch/baseBranchService';
|
||||
import Markdown from '$lib/components/Markdown.svelte';
|
||||
import { RemotesService } from '$lib/remotes/service';
|
||||
import Link from '$lib/shared/Link.svelte';
|
||||
import TextBox from '$lib/shared/TextBox.svelte';
|
||||
import { getContext } from '$lib/utils/context';
|
||||
import { getMarkdownRenderer } from '$lib/utils/markdown';
|
||||
import * as toasts from '$lib/utils/toasts';
|
||||
import { remoteUrlIsHttp } from '$lib/utils/url';
|
||||
import { BranchController } from '$lib/vbranches/branchController';
|
||||
import { VirtualBranchService } from '$lib/vbranches/virtualBranch';
|
||||
import Button from '@gitbutler/ui/Button.svelte';
|
||||
import Modal from '@gitbutler/ui/Modal.svelte';
|
||||
import { marked } from 'marked';
|
||||
import { get } from 'svelte/store';
|
||||
import type { PullRequest } from '$lib/gitHost/interface/types';
|
||||
import { goto } from '$app/navigation';
|
||||
@ -25,7 +24,6 @@
|
||||
const remotesService = getContext(RemotesService);
|
||||
const baseBranchService = getContext(BaseBranchService);
|
||||
const virtualBranchService = getContext(VirtualBranchService);
|
||||
const renderer = getMarkdownRenderer();
|
||||
|
||||
let remoteName = structuredClone(pullrequest.repoName) || '';
|
||||
let createRemoteModal: Modal | undefined;
|
||||
@ -108,9 +106,9 @@
|
||||
{#if pullrequest.draft}
|
||||
<Button size="tag" clickable={false} style="neutral" icon="draft-pr-small">Draft</Button>
|
||||
{:else}
|
||||
<Button size="tag" clickable={false} style="success" kind="solid" icon="pr-small"
|
||||
>Open</Button
|
||||
>
|
||||
<Button size="tag" clickable={false} style="success" kind="solid" icon="pr-small">
|
||||
Open
|
||||
</Button>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
@ -130,7 +128,7 @@
|
||||
</div>
|
||||
{#if pullrequest.body}
|
||||
<div class="markdown">
|
||||
{@html marked.parse(pullrequest.body, { renderer })}
|
||||
<Markdown content={pullrequest.body} />
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
|
@ -7,7 +7,7 @@
|
||||
export let disabled = false;
|
||||
</script>
|
||||
|
||||
<button class="menu-item" class:disabled {disabled} on:mousedown on:click>
|
||||
<button class="menu-item" class:disabled {disabled} on:click>
|
||||
{#if icon}
|
||||
<Icon name={icon} />
|
||||
{/if}
|
||||
|
@ -0,0 +1,20 @@
|
||||
<script lang="ts">
|
||||
import type { Snippet } from 'svelte';
|
||||
|
||||
interface Props {
|
||||
children: Snippet;
|
||||
}
|
||||
|
||||
const { children }: Props = $props();
|
||||
</script>
|
||||
|
||||
<blockquote>
|
||||
{@render children()}
|
||||
</blockquote>
|
||||
|
||||
<style>
|
||||
blockquote {
|
||||
border-left: 2px solid #ccc;
|
||||
padding: 0 0 0 1rem;
|
||||
}
|
||||
</style>
|
@ -0,0 +1,10 @@
|
||||
<script lang="ts">
|
||||
interface Props {
|
||||
text: string;
|
||||
lang: string;
|
||||
}
|
||||
|
||||
const { text, lang }: Props = $props();
|
||||
</script>
|
||||
|
||||
<pre class={`language-${lang}`}>{text}</pre>
|
@ -0,0 +1,9 @@
|
||||
<script lang="ts">
|
||||
interface Props {
|
||||
raw: string;
|
||||
}
|
||||
|
||||
const { raw = '' }: Props = $props();
|
||||
</script>
|
||||
|
||||
<code>{raw.replace(/`/g, '')}</code>
|
@ -0,0 +1,41 @@
|
||||
<script lang="ts">
|
||||
import { slugify } from '$lib/utils/string';
|
||||
|
||||
interface Props {
|
||||
depth: number;
|
||||
raw: string;
|
||||
text: string;
|
||||
}
|
||||
|
||||
const { depth, raw, text }: Props = $props();
|
||||
|
||||
const id = $derived(slugify(text) ?? text);
|
||||
</script>
|
||||
|
||||
{#if depth === 1}
|
||||
<h1 {id}>
|
||||
{text}
|
||||
</h1>
|
||||
{:else if depth === 2}
|
||||
<h2 {id}>
|
||||
{text}
|
||||
</h2>
|
||||
{:else if depth === 3}
|
||||
<h3 {id}>
|
||||
{text}
|
||||
</h3>
|
||||
{:else if depth === 4}
|
||||
<h4 {id}>
|
||||
{text}
|
||||
</h4>
|
||||
{:else if depth === 5}
|
||||
<h5 {id}>
|
||||
{text}
|
||||
</h5>
|
||||
{:else if depth === 6}
|
||||
<h6 {id}>
|
||||
{text}
|
||||
</h6>
|
||||
{:else}
|
||||
{raw}
|
||||
{/if}
|
@ -0,0 +1,11 @@
|
||||
<script lang="ts">
|
||||
interface Props {
|
||||
href?: string;
|
||||
title?: string;
|
||||
text?: string;
|
||||
}
|
||||
|
||||
const { href = '', title = undefined, text = '' }: Props = $props();
|
||||
</script>
|
||||
|
||||
<img src={href} {title} alt={text} />
|
@ -0,0 +1,14 @@
|
||||
<script lang="ts">
|
||||
import type { Snippet } from 'svelte';
|
||||
|
||||
interface Props {
|
||||
text: string;
|
||||
children: Snippet;
|
||||
}
|
||||
|
||||
const { children }: Props = $props();
|
||||
</script>
|
||||
|
||||
<p>
|
||||
{@render children()}
|
||||
</p>
|
@ -0,0 +1,2 @@
|
||||
<br />
|
||||
<br />
|
@ -0,0 +1,11 @@
|
||||
<script lang="ts">
|
||||
interface Props {
|
||||
text: string;
|
||||
}
|
||||
|
||||
const { text }: Props = $props();
|
||||
</script>
|
||||
|
||||
<span>
|
||||
{text}
|
||||
</span>
|
@ -7,12 +7,12 @@
|
||||
import { getContext } from '$lib/utils/context';
|
||||
import { computeFileStatus } from '$lib/utils/fileStatus';
|
||||
import * as toasts from '$lib/utils/toasts';
|
||||
import { openExternalUrl } from '$lib/utils/url';
|
||||
import { BranchController } from '$lib/vbranches/branchController';
|
||||
import { LocalFile, type AnyFile } from '$lib/vbranches/types';
|
||||
import Button from '@gitbutler/ui/Button.svelte';
|
||||
import Modal from '@gitbutler/ui/Modal.svelte';
|
||||
import { join } from '@tauri-apps/api/path';
|
||||
import { open as openFile } from '@tauri-apps/api/shell';
|
||||
|
||||
export let branchId: string | undefined;
|
||||
export let target: HTMLElement | undefined;
|
||||
@ -85,7 +85,7 @@
|
||||
if (!project) return;
|
||||
for (let file of item.files) {
|
||||
const absPath = await join(project.vscodePath, file.path);
|
||||
openFile(`${$editor}://file${absPath}`);
|
||||
openExternalUrl(`${$editor}://file${absPath}`);
|
||||
}
|
||||
contextMenu.close();
|
||||
} catch {
|
||||
|
@ -87,7 +87,7 @@ export class GitHubPrService implements GitHostPrService {
|
||||
|
||||
showToast({
|
||||
title: 'Failed to fetch pull request template',
|
||||
message: `Template not found at path: <code>${path}</code>.`,
|
||||
message: `Template not found at path: \`${path}\`.`,
|
||||
style: 'neutral'
|
||||
});
|
||||
}
|
||||
|
@ -4,8 +4,8 @@
|
||||
import ContextMenuSection from '$lib/components/contextmenu/ContextMenuSection.svelte';
|
||||
import { editor } from '$lib/editorLink/editorLink';
|
||||
import { getContext } from '$lib/utils/context';
|
||||
import { openExternalUrl } from '$lib/utils/url';
|
||||
import { BranchController } from '$lib/vbranches/branchController';
|
||||
import { open as openFile } from '@tauri-apps/api/shell';
|
||||
|
||||
interface Props {
|
||||
target: HTMLElement | undefined;
|
||||
@ -43,10 +43,10 @@
|
||||
{/if}
|
||||
{#if item.lineNumber}
|
||||
<ContextMenuItem
|
||||
label="Open in VS Code"
|
||||
on:mousedown={() => {
|
||||
label="Open in VSCode"
|
||||
on:click={() => {
|
||||
projectPath &&
|
||||
openFile(`${$editor}://file${projectPath}/${filePath}:${item.lineNumber}`);
|
||||
openExternalUrl(`${$editor}://file${projectPath}/${filePath}:${item.lineNumber}`);
|
||||
contextMenu.close();
|
||||
}}
|
||||
/>
|
||||
|
@ -1,11 +1,8 @@
|
||||
<script lang="ts">
|
||||
import Markdown from '$lib/components/Markdown.svelte';
|
||||
import { dismissToast, toastStore } from '$lib/notifications/toasts';
|
||||
import InfoMessage from '$lib/shared/InfoMessage.svelte';
|
||||
import { getMarkdownRenderer } from '$lib/utils/markdown';
|
||||
import { marked } from 'marked';
|
||||
import { slide } from 'svelte/transition';
|
||||
|
||||
var renderer = getMarkdownRenderer();
|
||||
</script>
|
||||
|
||||
<div class="toast-controller hide-native-scrollbar">
|
||||
@ -24,7 +21,9 @@
|
||||
</svelte:fragment>
|
||||
|
||||
<svelte:fragment slot="content">
|
||||
{@html marked.parse(toast.message ?? '', { renderer })}
|
||||
{#if toast.message}
|
||||
<Markdown content={toast.message} />
|
||||
{/if}
|
||||
</svelte:fragment>
|
||||
</InfoMessage>
|
||||
</div>
|
||||
|
@ -1,5 +1,6 @@
|
||||
<script lang="ts">
|
||||
import SupportersBanner from './SupportersBanner.svelte';
|
||||
import { openExternalUrl } from '$lib/utils/url';
|
||||
import Button from '@gitbutler/ui/Button.svelte';
|
||||
import Icon from '@gitbutler/ui/Icon.svelte';
|
||||
import { goto } from '$app/navigation';
|
||||
@ -109,23 +110,21 @@
|
||||
|
||||
<section class="profile-sidebar__bottom">
|
||||
<div class="social-banners">
|
||||
<a
|
||||
<button
|
||||
class="social-banner"
|
||||
href="mailto:hello@gitbutler.com?subject=Feedback or question!"
|
||||
target="_blank"
|
||||
on:click={async () =>
|
||||
await openExternalUrl('mailto:hello@gitbutler.com?subject=Feedback or question!')}
|
||||
>
|
||||
<span class="text-14 text-bold">Contact us</span>
|
||||
<Icon name="mail" />
|
||||
</a>
|
||||
<a
|
||||
</button>
|
||||
<button
|
||||
class="social-banner"
|
||||
href="https://discord.gg/MmFkmaJ42D"
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
on:click={async () => await openExternalUrl('https://discord.gg/MmFkmaJ42D')}
|
||||
>
|
||||
<span class="text-14 text-bold">Join our Discord</span>
|
||||
<Icon name="discord" />
|
||||
</a>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<SupportersBanner />
|
||||
|
@ -1,13 +1,20 @@
|
||||
<a class="banner" href="https://docs.gitbutler.com/community/supporters" target="_blank">
|
||||
<div class="benner-content">
|
||||
<h4 class="benner-label text-14 text-bold">Thank you to all GitButler early supporters</h4>
|
||||
<i class="benner-arrow-wrap">
|
||||
<div class="benner-arrow-tail"></div>
|
||||
<script lang="ts">
|
||||
import { openExternalUrl } from '$lib/utils/url';
|
||||
</script>
|
||||
|
||||
<button
|
||||
class="banner"
|
||||
on:click={async () => await openExternalUrl('https://docs.gitbutler.com/community/supporters')}
|
||||
>
|
||||
<div class="banner-content">
|
||||
<h4 class="banner-label text-14 text-bold">Thank you to all GitButler early supporters</h4>
|
||||
<i class="banner-arrow-wrap">
|
||||
<div class="banner-arrow-tail"></div>
|
||||
<svg
|
||||
viewBox="0 0 7 11"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
class="benner-arrow-head"
|
||||
class="banner-arrow-head"
|
||||
>
|
||||
<path
|
||||
d="M0.871094 1L5.00013 5.5L0.871094 10"
|
||||
@ -18,7 +25,7 @@
|
||||
</i>
|
||||
</div>
|
||||
<img class="banner-img" src="/images/banners/support.svg" alt="" />
|
||||
</a>
|
||||
</button>
|
||||
|
||||
<style>
|
||||
.banner {
|
||||
@ -29,22 +36,22 @@
|
||||
background-color: #d7f2f1;
|
||||
|
||||
&:hover {
|
||||
& .benner-arrow-wrap {
|
||||
& .banner-arrow-wrap {
|
||||
width: 12px;
|
||||
}
|
||||
|
||||
& .benner-arrow-tail {
|
||||
& .banner-arrow-tail {
|
||||
width: 90%;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.benner-content {
|
||||
.banner-content {
|
||||
display: inline;
|
||||
color: #000;
|
||||
}
|
||||
|
||||
.benner-label {
|
||||
.banner-label {
|
||||
display: inline;
|
||||
}
|
||||
|
||||
@ -55,7 +62,7 @@
|
||||
|
||||
/* ARROW */
|
||||
|
||||
.benner-arrow-wrap {
|
||||
.banner-arrow-wrap {
|
||||
position: relative;
|
||||
display: inline-flex;
|
||||
transform: translateY(2px);
|
||||
@ -64,7 +71,7 @@
|
||||
transition: width 0.2s;
|
||||
}
|
||||
|
||||
.benner-arrow-tail {
|
||||
.banner-arrow-tail {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
@ -75,7 +82,7 @@
|
||||
transition: width 0.2s;
|
||||
}
|
||||
|
||||
.benner-arrow-head {
|
||||
.banner-arrow-head {
|
||||
position: absolute;
|
||||
right: 0;
|
||||
top: 0;
|
||||
|
@ -1,15 +1,24 @@
|
||||
<script lang="ts">
|
||||
export let href: string;
|
||||
import { openExternalUrl } from '$lib/utils/url';
|
||||
import Icon from '@gitbutler/ui/Icon.svelte';
|
||||
import type iconsJson from '@gitbutler/ui/data/icons.json';
|
||||
import type { Snippet } from 'svelte';
|
||||
|
||||
export let icon: keyof typeof iconsJson;
|
||||
interface Props {
|
||||
href: string;
|
||||
icon: keyof typeof iconsJson;
|
||||
children: Snippet;
|
||||
}
|
||||
|
||||
const { href, icon, children }: Props = $props();
|
||||
</script>
|
||||
|
||||
<a class="link" target="_blank" {href}>
|
||||
<button class="link" onclick={async () => await openExternalUrl(href)}>
|
||||
<Icon name={icon} />
|
||||
<span class="text-12"><slot /></span>
|
||||
</a>
|
||||
<span class="text-12">
|
||||
{@render children()}
|
||||
</span>
|
||||
</button>
|
||||
|
||||
<style lang="postcss">
|
||||
.link {
|
||||
|
@ -1,17 +1,29 @@
|
||||
<script lang="ts">
|
||||
import { openExternalUrl } from '$lib/utils/url';
|
||||
import Icon from '@gitbutler/ui/Icon.svelte';
|
||||
import { onMount } from 'svelte';
|
||||
import { onMount, type Snippet } from 'svelte';
|
||||
|
||||
let classes = '';
|
||||
export { classes as class };
|
||||
export let target: '_blank' | '_self' | '_parent' | '_top' | undefined = undefined;
|
||||
export let rel: string | undefined = undefined;
|
||||
export let role: 'basic' | 'primary' | 'error' = 'basic';
|
||||
export let disabled = false;
|
||||
export let href: string | undefined = undefined;
|
||||
interface Props {
|
||||
href: string;
|
||||
children: Snippet;
|
||||
class?: string;
|
||||
target?: '_blank' | '_self' | '_parent' | '_top' | undefined;
|
||||
rel?: string | undefined;
|
||||
role?: 'basic' | 'primary' | 'error';
|
||||
disabled?: boolean;
|
||||
}
|
||||
|
||||
let element: HTMLAnchorElement | HTMLButtonElement | undefined;
|
||||
const {
|
||||
href,
|
||||
target = undefined,
|
||||
class: classes,
|
||||
rel = undefined,
|
||||
role = 'basic',
|
||||
disabled = false,
|
||||
children
|
||||
}: Props = $props();
|
||||
|
||||
let element = $state<HTMLAnchorElement | HTMLButtonElement>();
|
||||
|
||||
onMount(() => {
|
||||
if (element) {
|
||||
@ -19,33 +31,31 @@
|
||||
}
|
||||
});
|
||||
|
||||
$: isExternal = href?.startsWith('http');
|
||||
const isExternal = $derived(href?.startsWith('http'));
|
||||
</script>
|
||||
|
||||
{#if href}
|
||||
<a
|
||||
{href}
|
||||
{target}
|
||||
{rel}
|
||||
class="link {role} {classes}"
|
||||
bind:this={element}
|
||||
class:disabled
|
||||
on:click={(e) => {
|
||||
if (href && isExternal) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
openExternalUrl(href);
|
||||
}
|
||||
}}
|
||||
>
|
||||
<slot />
|
||||
{#if isExternal}
|
||||
<div class="link-icon">
|
||||
<Icon name="open-link" />
|
||||
</div>
|
||||
{/if}
|
||||
</a>
|
||||
{/if}
|
||||
<a
|
||||
{href}
|
||||
{target}
|
||||
{rel}
|
||||
class="link {role} {classes}"
|
||||
bind:this={element}
|
||||
class:disabled
|
||||
onclick={(e) => {
|
||||
if (href && isExternal) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
openExternalUrl(href);
|
||||
}
|
||||
}}
|
||||
>
|
||||
{@render children()}
|
||||
{#if isExternal}
|
||||
<div class="link-icon">
|
||||
<Icon name="open-link" />
|
||||
</div>
|
||||
{/if}
|
||||
</a>
|
||||
|
||||
<style lang="postcss">
|
||||
.link {
|
||||
@ -62,6 +72,7 @@
|
||||
text-decoration: none;
|
||||
}
|
||||
}
|
||||
|
||||
.link-icon {
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
@ -1,10 +0,0 @@
|
||||
import { marked } from 'marked';
|
||||
|
||||
export function getMarkdownRenderer() {
|
||||
const renderer = new marked.Renderer({});
|
||||
renderer.link = function (href, title, text) {
|
||||
if (!title) title = text;
|
||||
return '<a target="_blank" href="' + href + '" title="' + title + '">' + text + '</a>';
|
||||
};
|
||||
return renderer;
|
||||
}
|
32
apps/desktop/src/lib/utils/markdownRenderers.ts
Normal file
32
apps/desktop/src/lib/utils/markdownRenderers.ts
Normal file
@ -0,0 +1,32 @@
|
||||
import Blockquote from '$lib/components/markdownRenderers/Blockquote.svelte';
|
||||
import Code from '$lib/components/markdownRenderers/Code.svelte';
|
||||
import Codespan from '$lib/components/markdownRenderers/Codespan.svelte';
|
||||
import Heading from '$lib/components/markdownRenderers/Heading.svelte';
|
||||
import Image from '$lib/components/markdownRenderers/Image.svelte';
|
||||
import Paragraph from '$lib/components/markdownRenderers/Paragraph.svelte';
|
||||
import Space from '$lib/components/markdownRenderers/Space.svelte';
|
||||
import Text from '$lib/components/markdownRenderers/Text.svelte';
|
||||
import Link from '$lib/shared/Link.svelte';
|
||||
|
||||
export const renderers = {
|
||||
link: Link,
|
||||
image: Image,
|
||||
space: Space,
|
||||
blockquote: Blockquote,
|
||||
code: Code,
|
||||
codespan: Codespan,
|
||||
text: Text,
|
||||
heading: Heading,
|
||||
paragraph: Paragraph
|
||||
};
|
||||
|
||||
export const options = {
|
||||
async: false,
|
||||
breaks: true,
|
||||
gfm: true,
|
||||
pedantic: false,
|
||||
renderer: null,
|
||||
silent: false,
|
||||
tokenizer: null,
|
||||
walkTokens: null
|
||||
};
|
@ -23,3 +23,14 @@ export function isStr(s: unknown): s is string {
|
||||
export function isWhiteSpaceString(s: string) {
|
||||
return s.trim() === '';
|
||||
}
|
||||
|
||||
export function slugify(input: string) {
|
||||
return String(input)
|
||||
.normalize('NFKD')
|
||||
.replace(/[\u0300-\u036f]/g, '')
|
||||
.trim()
|
||||
.toLowerCase()
|
||||
.replace(/[^a-z0-9 -]/g, '')
|
||||
.replace(/\s+/g, '-')
|
||||
.replace(/-+/g, '-');
|
||||
}
|
||||
|
@ -1,11 +1,11 @@
|
||||
import { invoke } from '$lib/backend/ipc';
|
||||
import { showToast } from '$lib/notifications/toasts';
|
||||
import { open } from '@tauri-apps/api/shell';
|
||||
import GitUrlParse from 'git-url-parse';
|
||||
import { posthog } from 'posthog-js';
|
||||
|
||||
export async function openExternalUrl(href: string) {
|
||||
try {
|
||||
await open(href);
|
||||
await invoke<void>('open_url', { url: href });
|
||||
} catch (e) {
|
||||
if (typeof e === 'string' || e instanceof String) {
|
||||
// TODO: Remove if/when we've resolved all external URL problems.
|
||||
|
@ -67,6 +67,7 @@ gitbutler-diff.workspace = true
|
||||
gitbutler-operating-modes.workspace = true
|
||||
gitbutler-edit-mode.workspace = true
|
||||
open = "5"
|
||||
url = "2.5.2"
|
||||
|
||||
[dependencies.tauri]
|
||||
version = "1.7.0"
|
||||
@ -78,7 +79,6 @@ features = [
|
||||
"path-all",
|
||||
"process-relaunch",
|
||||
"protocol-asset",
|
||||
"shell-open",
|
||||
"window-maximize",
|
||||
"window-start-dragging",
|
||||
"window-unmaximize",
|
||||
|
@ -26,6 +26,7 @@ pub mod config;
|
||||
pub mod error;
|
||||
pub mod github;
|
||||
pub mod modes;
|
||||
pub mod open;
|
||||
pub mod projects;
|
||||
pub mod remotes;
|
||||
pub mod repo;
|
||||
|
@ -12,8 +12,8 @@
|
||||
)]
|
||||
|
||||
use gitbutler_tauri::{
|
||||
askpass, commands, config, github, logs, menu, modes, projects, remotes, repo, secret, undo,
|
||||
users, virtual_branches, zip, App, WindowState,
|
||||
askpass, commands, config, github, logs, menu, modes, open, projects, remotes, repo, secret,
|
||||
undo, users, virtual_branches, zip, App, WindowState,
|
||||
};
|
||||
use tauri::{generate_context, Manager};
|
||||
use tauri_plugin_log::LogTarget;
|
||||
@ -206,7 +206,8 @@ fn main() {
|
||||
modes::enter_edit_mode,
|
||||
modes::save_edit_and_return_to_workspace,
|
||||
modes::abort_edit_and_return_to_workspace,
|
||||
modes::edit_initial_index_state
|
||||
modes::edit_initial_index_state,
|
||||
open::open_url
|
||||
])
|
||||
.menu(menu::build(tauri_context.package_info()))
|
||||
.on_menu_event(|event| menu::handle_event(&event))
|
||||
|
@ -1,5 +1,6 @@
|
||||
use std::{env, fs};
|
||||
|
||||
use crate::open::open_that as open_url;
|
||||
use anyhow::Context;
|
||||
use gitbutler_error::{error, error::Code};
|
||||
use serde_json::json;
|
||||
@ -303,17 +304,13 @@ pub fn handle_event<R: Runtime>(event: &WindowMenuEvent<R>) {
|
||||
|
||||
'open_link: {
|
||||
let result = match event.menu_item_id() {
|
||||
"help/documentation" => open::that("https://docs.gitbutler.com"),
|
||||
"help/github" => open::that("https://github.com/gitbutlerapp/gitbutler"),
|
||||
"help/release-notes" => {
|
||||
open::that("https://discord.com/channels/1060193121130000425/1183737922785116161")
|
||||
}
|
||||
"help/report-issue" => {
|
||||
open::that("https://github.com/gitbutlerapp/gitbutler/issues/new")
|
||||
}
|
||||
"help/discord" => open::that("https://discord.com/invite/MmFkmaJ42D"),
|
||||
"help/youtube" => open::that("https://www.youtube.com/@gitbutlerapp"),
|
||||
"help/x" => open::that("https://x.com/gitbutler"),
|
||||
"help/documentation" => open_url("https://docs.gitbutler.com"),
|
||||
"help/github" => open_url("https://github.com/gitbutlerapp/gitbutler"),
|
||||
"help/release-notes" => open_url("https://github.com/gitbutlerapp/gitbutler/releases"),
|
||||
"help/report-issue" => open_url("https://github.com/gitbutlerapp/gitbutler/issues/new"),
|
||||
"help/discord" => open_url("https://discord.com/invite/MmFkmaJ42D"),
|
||||
"help/youtube" => open_url("https://www.youtube.com/@gitbutlerapp"),
|
||||
"help/x" => open_url("https://x.com/gitbutler"),
|
||||
_ => break 'open_link,
|
||||
};
|
||||
|
||||
|
75
crates/gitbutler-tauri/src/open.rs
Normal file
75
crates/gitbutler-tauri/src/open.rs
Normal file
@ -0,0 +1,75 @@
|
||||
use crate::error::Error;
|
||||
use anyhow::{bail, Context};
|
||||
use std::env;
|
||||
use tracing::instrument;
|
||||
use url::Url;
|
||||
|
||||
pub(crate) fn open_that(path: &str) -> anyhow::Result<()> {
|
||||
let target_url = Url::parse(path).with_context(|| format!("Invalid path format: '{path}'"))?;
|
||||
if !["http", "https", "mailto", "vscode", "vscodium"].contains(&target_url.scheme()) {
|
||||
bail!("Invalid path scheme: {}", target_url.scheme());
|
||||
}
|
||||
|
||||
fn clean_env_vars<'a, 'b>(
|
||||
var_names: &'a [&'b str],
|
||||
) -> impl Iterator<Item = (&'b str, String)> + 'a {
|
||||
var_names
|
||||
.iter()
|
||||
.filter_map(|name| env::var(name).map(|value| (*name, value)).ok())
|
||||
.map(|(name, value)| {
|
||||
(
|
||||
name,
|
||||
value
|
||||
.split(':')
|
||||
.filter(|path| {
|
||||
!path.contains("appimage-run") && !path.contains("/tmp/.mount")
|
||||
})
|
||||
.collect::<Vec<_>>()
|
||||
.join(":"),
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
let mut cmd_errors = Vec::new();
|
||||
|
||||
for mut cmd in open::commands(path) {
|
||||
let cleaned_vars = clean_env_vars(&[
|
||||
"APPDIR",
|
||||
"GDK_PIXBUF_MODULE_FILE",
|
||||
"GIO_EXTRA_MODULES",
|
||||
"GIO_EXTRA_MODULES",
|
||||
"GSETTINGS_SCHEMA_DIR",
|
||||
"GST_PLUGIN_SYSTEM_PATH",
|
||||
"GST_PLUGIN_SYSTEM_PATH_1_0",
|
||||
"GTK_DATA_PREFIX",
|
||||
"GTK_EXE_PREFIX",
|
||||
"GTK_IM_MODULE_FILE",
|
||||
"GTK_PATH",
|
||||
"LD_LIBRARY_PATH",
|
||||
"PATH",
|
||||
"PERLLIB",
|
||||
"PYTHONHOME",
|
||||
"PYTHONPATH",
|
||||
"QT_PLUGIN_PATH",
|
||||
"XDG_DATA_DIRS",
|
||||
]);
|
||||
|
||||
cmd.envs(cleaned_vars);
|
||||
cmd.current_dir(env::temp_dir());
|
||||
if cmd.status().is_ok() {
|
||||
return Ok(());
|
||||
} else {
|
||||
cmd_errors.push(anyhow::anyhow!("Failed to execute command {:?}", cmd));
|
||||
}
|
||||
}
|
||||
if !cmd_errors.is_empty() {
|
||||
bail!("Errors occurred: {:?}", cmd_errors);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tauri::command(async)]
|
||||
#[instrument(err(Debug))]
|
||||
pub fn open_url(url: &str) -> Result<(), Error> {
|
||||
Ok(open_that(url)?)
|
||||
}
|
@ -15,9 +15,6 @@
|
||||
"readFile": true,
|
||||
"scope": ["$APPCACHE/archives/*", "$RESOURCE/_up_/scripts/*"]
|
||||
},
|
||||
"shell": {
|
||||
"open": "^((https://)|(http://)|(mailto:)|(vscode://)|(vscodium://)).+"
|
||||
},
|
||||
"dialog": {
|
||||
"open": true
|
||||
},
|
||||
|
@ -6,11 +6,6 @@
|
||||
"productName": "GitButler Nightly"
|
||||
},
|
||||
"tauri": {
|
||||
"allowlist": {
|
||||
"shell": {
|
||||
"open": "^((https://)|(http://)|(mailto:)|(vscode://)|(vscodium://)).+"
|
||||
}
|
||||
},
|
||||
"bundle": {
|
||||
"identifier": "com.gitbutler.app.nightly",
|
||||
"icon": [
|
||||
|
@ -6,11 +6,6 @@
|
||||
"productName": "GitButler"
|
||||
},
|
||||
"tauri": {
|
||||
"allowlist": {
|
||||
"shell": {
|
||||
"open": "^((https://)|(http://)|(mailto:)|(vscode://)|(vscodium://)).+"
|
||||
}
|
||||
},
|
||||
"bundle": {
|
||||
"identifier": "com.gitbutler.app",
|
||||
"icon": [
|
||||
|
@ -12,9 +12,6 @@
|
||||
"readFile": true,
|
||||
"scope": ["$APPCACHE/archives/*", "$RESOURCE/_up_/scripts/*"]
|
||||
},
|
||||
"shell": {
|
||||
"open": "^((https://)|(http://)|(mailto:)|(vscode://)|(vscodium://)).+"
|
||||
},
|
||||
"dialog": {
|
||||
"open": true
|
||||
},
|
||||
|
Loading…
Reference in New Issue
Block a user