mirror of
https://github.com/gitbutlerapp/gitbutler.git
synced 2024-12-26 11:08:38 +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",
|
"gix",
|
||||||
"log",
|
"log",
|
||||||
"once_cell",
|
"once_cell",
|
||||||
"open 5.3.0",
|
"open",
|
||||||
"parking_lot 0.12.3",
|
"parking_lot 0.12.3",
|
||||||
"pretty_assertions",
|
"pretty_assertions",
|
||||||
"reqwest 0.12.7",
|
"reqwest 0.12.7",
|
||||||
@ -2599,6 +2599,7 @@ dependencies = [
|
|||||||
"tracing-appender",
|
"tracing-appender",
|
||||||
"tracing-forest",
|
"tracing-forest",
|
||||||
"tracing-subscriber",
|
"tracing-subscriber",
|
||||||
|
"url",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@ -5342,16 +5343,6 @@ version = "11.1.4"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "b410bbe7e14ab526a0e86877eb47c6996a2bd7746f027ba551028c925390e4e9"
|
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]]
|
[[package]]
|
||||||
name = "open"
|
name = "open"
|
||||||
version = "5.3.0"
|
version = "5.3.0"
|
||||||
@ -7345,12 +7336,10 @@ dependencies = [
|
|||||||
"indexmap 1.9.3",
|
"indexmap 1.9.3",
|
||||||
"objc",
|
"objc",
|
||||||
"once_cell",
|
"once_cell",
|
||||||
"open 3.2.0",
|
|
||||||
"os_info",
|
"os_info",
|
||||||
"percent-encoding",
|
"percent-encoding",
|
||||||
"rand 0.8.5",
|
"rand 0.8.5",
|
||||||
"raw-window-handle",
|
"raw-window-handle",
|
||||||
"regex",
|
|
||||||
"reqwest 0.11.27",
|
"reqwest 0.11.27",
|
||||||
"rfd",
|
"rfd",
|
||||||
"semver",
|
"semver",
|
||||||
@ -7408,7 +7397,6 @@ dependencies = [
|
|||||||
"png",
|
"png",
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"regex",
|
|
||||||
"semver",
|
"semver",
|
||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
@ -8657,21 +8645,6 @@ dependencies = [
|
|||||||
"windows-targets 0.52.6",
|
"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]]
|
[[package]]
|
||||||
name = "windows-sys"
|
name = "windows-sys"
|
||||||
version = "0.48.0"
|
version = "0.48.0"
|
||||||
@ -8745,12 +8718,6 @@ dependencies = [
|
|||||||
"windows-targets 0.52.6",
|
"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]]
|
[[package]]
|
||||||
name = "windows_aarch64_gnullvm"
|
name = "windows_aarch64_gnullvm"
|
||||||
version = "0.48.5"
|
version = "0.48.5"
|
||||||
@ -8775,12 +8742,6 @@ version = "0.39.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "ec7711666096bd4096ffa835238905bb33fb87267910e154b18b44eaabb340f2"
|
checksum = "ec7711666096bd4096ffa835238905bb33fb87267910e154b18b44eaabb340f2"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "windows_aarch64_msvc"
|
|
||||||
version = "0.42.2"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "windows_aarch64_msvc"
|
name = "windows_aarch64_msvc"
|
||||||
version = "0.48.5"
|
version = "0.48.5"
|
||||||
@ -8805,12 +8766,6 @@ version = "0.39.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "763fc57100a5f7042e3057e7e8d9bdd7860d330070251a73d003563a3bb49e1b"
|
checksum = "763fc57100a5f7042e3057e7e8d9bdd7860d330070251a73d003563a3bb49e1b"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "windows_i686_gnu"
|
|
||||||
version = "0.42.2"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "windows_i686_gnu"
|
name = "windows_i686_gnu"
|
||||||
version = "0.48.5"
|
version = "0.48.5"
|
||||||
@ -8841,12 +8796,6 @@ version = "0.39.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "7bc7cbfe58828921e10a9f446fcaaf649204dcfe6c1ddd712c5eebae6bda1106"
|
checksum = "7bc7cbfe58828921e10a9f446fcaaf649204dcfe6c1ddd712c5eebae6bda1106"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "windows_i686_msvc"
|
|
||||||
version = "0.42.2"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "windows_i686_msvc"
|
name = "windows_i686_msvc"
|
||||||
version = "0.48.5"
|
version = "0.48.5"
|
||||||
@ -8871,12 +8820,6 @@ version = "0.39.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "6868c165637d653ae1e8dc4d82c25d4f97dd6605eaa8d784b5c6e0ab2a252b65"
|
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]]
|
[[package]]
|
||||||
name = "windows_x86_64_gnu"
|
name = "windows_x86_64_gnu"
|
||||||
version = "0.48.5"
|
version = "0.48.5"
|
||||||
@ -8889,12 +8832,6 @@ version = "0.52.6"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78"
|
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]]
|
[[package]]
|
||||||
name = "windows_x86_64_gnullvm"
|
name = "windows_x86_64_gnullvm"
|
||||||
version = "0.48.5"
|
version = "0.48.5"
|
||||||
@ -8919,12 +8856,6 @@ version = "0.39.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "5e4d40883ae9cae962787ca76ba76390ffa29214667a111db9e0a1ad8377e809"
|
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]]
|
[[package]]
|
||||||
name = "windows_x86_64_msvc"
|
name = "windows_x86_64_msvc"
|
||||||
version = "0.48.5"
|
version = "0.48.5"
|
||||||
|
@ -6,7 +6,7 @@
|
|||||||
import * as events from '$lib/utils/events';
|
import * as events from '$lib/utils/events';
|
||||||
import { createKeybind } from '$lib/utils/hotkeys';
|
import { createKeybind } from '$lib/utils/hotkeys';
|
||||||
import { unsubscribe } from '$lib/utils/unsubscribe';
|
import { unsubscribe } from '$lib/utils/unsubscribe';
|
||||||
import { open } from '@tauri-apps/api/shell';
|
import { openExternalUrl } from '$lib/utils/url';
|
||||||
import { onMount } from 'svelte';
|
import { onMount } from 'svelte';
|
||||||
import { goto } from '$app/navigation';
|
import { goto } from '$app/navigation';
|
||||||
|
|
||||||
@ -29,7 +29,7 @@
|
|||||||
'menu://project/open-in-vscode/clicked',
|
'menu://project/open-in-vscode/clicked',
|
||||||
async () => {
|
async () => {
|
||||||
const path = `${$editor}://file${project.vscodePath}?windowId=_blank`;
|
const path = `${$editor}://file${project.vscodePath}?windowId=_blank`;
|
||||||
open(path);
|
openExternalUrl(path);
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -1,10 +1,9 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { MessageRole } from '$lib/ai/types';
|
import { MessageRole } from '$lib/ai/types';
|
||||||
|
import Markdown from '$lib/components/Markdown.svelte';
|
||||||
import { autoHeight } from '$lib/utils/autoHeight';
|
import { autoHeight } from '$lib/utils/autoHeight';
|
||||||
import { getMarkdownRenderer } from '$lib/utils/markdown';
|
|
||||||
import Button from '@gitbutler/ui/Button.svelte';
|
import Button from '@gitbutler/ui/Button.svelte';
|
||||||
import Icon from '@gitbutler/ui/Icon.svelte';
|
import Icon from '@gitbutler/ui/Icon.svelte';
|
||||||
import { marked } from 'marked';
|
|
||||||
import { createEventDispatcher } from 'svelte';
|
import { createEventDispatcher } from 'svelte';
|
||||||
|
|
||||||
export let disableRemove = false;
|
export let disableRemove = false;
|
||||||
@ -19,7 +18,6 @@
|
|||||||
addExample: void;
|
addExample: void;
|
||||||
input: string;
|
input: string;
|
||||||
}>();
|
}>();
|
||||||
const markedRenderer = getMarkdownRenderer();
|
|
||||||
let textareaElement: HTMLTextAreaElement | undefined;
|
let textareaElement: HTMLTextAreaElement | undefined;
|
||||||
|
|
||||||
function focusTextareaOnMount(
|
function focusTextareaOnMount(
|
||||||
@ -73,7 +71,7 @@
|
|||||||
></textarea>
|
></textarea>
|
||||||
{:else}
|
{:else}
|
||||||
<div class="markdown bubble-message scrollbar text-13 text-body">
|
<div class="markdown bubble-message scrollbar text-13 text-body">
|
||||||
{@html marked.parse(promptMessage.content, { renderer: markedRenderer })}
|
<Markdown content={promptMessage.content} />
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
|
@ -50,10 +50,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<button
|
<button
|
||||||
class="empty-board__suggestions__link"
|
class="empty-board__suggestions__link"
|
||||||
on:click={async () =>
|
on:click={async () => await openExternalUrl('https://docs.gitbutler.com')}
|
||||||
await openExternalUrl(
|
|
||||||
'https://docs.gitbutler.com/features/virtual-branches/branch-lanes'
|
|
||||||
)}
|
|
||||||
>
|
>
|
||||||
<div class="empty-board__suggestions__link__icon">
|
<div class="empty-board__suggestions__link__icon">
|
||||||
<Icon name="docs" />
|
<Icon name="docs" />
|
||||||
|
@ -4,19 +4,18 @@
|
|||||||
import { Project } from '$lib/backend/projects';
|
import { Project } from '$lib/backend/projects';
|
||||||
import CommitCard from '$lib/commit/CommitCard.svelte';
|
import CommitCard from '$lib/commit/CommitCard.svelte';
|
||||||
import { transformAnyCommit } from '$lib/commitLines/transformers';
|
import { transformAnyCommit } from '$lib/commitLines/transformers';
|
||||||
|
import Markdown from '$lib/components/Markdown.svelte';
|
||||||
import FileCard from '$lib/file/FileCard.svelte';
|
import FileCard from '$lib/file/FileCard.svelte';
|
||||||
import { getGitHost } from '$lib/gitHost/interface/gitHost';
|
import { getGitHost } from '$lib/gitHost/interface/gitHost';
|
||||||
import ScrollableContainer from '$lib/scroll/ScrollableContainer.svelte';
|
import ScrollableContainer from '$lib/scroll/ScrollableContainer.svelte';
|
||||||
import { SETTINGS, type Settings } from '$lib/settings/userSettings';
|
import { SETTINGS, type Settings } from '$lib/settings/userSettings';
|
||||||
import { RemoteBranchService } from '$lib/stores/remoteBranches';
|
import { RemoteBranchService } from '$lib/stores/remoteBranches';
|
||||||
import { getContext, getContextStoreBySymbol } from '$lib/utils/context';
|
import { getContext, getContextStoreBySymbol } from '$lib/utils/context';
|
||||||
import { getMarkdownRenderer } from '$lib/utils/markdown';
|
|
||||||
import { FileIdSelection } from '$lib/vbranches/fileIdSelection';
|
import { FileIdSelection } from '$lib/vbranches/fileIdSelection';
|
||||||
import { BranchData, type Branch } from '$lib/vbranches/types';
|
import { BranchData, type Branch } from '$lib/vbranches/types';
|
||||||
import LineGroup from '@gitbutler/ui/commitLines/LineGroup.svelte';
|
import LineGroup from '@gitbutler/ui/commitLines/LineGroup.svelte';
|
||||||
import { LineManagerFactory } from '@gitbutler/ui/commitLines/lineManager';
|
import { LineManagerFactory } from '@gitbutler/ui/commitLines/lineManager';
|
||||||
import lscache from 'lscache';
|
import lscache from 'lscache';
|
||||||
import { marked } from 'marked';
|
|
||||||
import { onMount, setContext } from 'svelte';
|
import { onMount, setContext } from 'svelte';
|
||||||
import { writable } from 'svelte/store';
|
import { writable } from 'svelte/store';
|
||||||
import type { PullRequest } from '$lib/gitHost/interface/types';
|
import type { PullRequest } from '$lib/gitHost/interface/types';
|
||||||
@ -93,8 +92,6 @@
|
|||||||
onMount(() => {
|
onMount(() => {
|
||||||
laneWidth = lscache.get(laneWidthKey);
|
laneWidth = lscache.get(laneWidthKey);
|
||||||
});
|
});
|
||||||
|
|
||||||
const renderer = getMarkdownRenderer();
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
{#if remoteBranch || localBranch}
|
{#if remoteBranch || localBranch}
|
||||||
@ -112,7 +109,7 @@
|
|||||||
<div class="card__header text-14 text-body text-semibold">{pr.title}</div>
|
<div class="card__header text-14 text-body text-semibold">{pr.title}</div>
|
||||||
{#if pr.body}
|
{#if pr.body}
|
||||||
<div class="markdown card__content text-13 text-body">
|
<div class="markdown card__content text-13 text-body">
|
||||||
{@html marked.parse(pr.body, { renderer })}
|
<Markdown content={pr.body} />
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
|
@ -3,6 +3,7 @@
|
|||||||
import gbLogoSvg from '$lib/assets/gb-logo.svg?raw';
|
import gbLogoSvg from '$lib/assets/gb-logo.svg?raw';
|
||||||
import { User } from '$lib/stores/user';
|
import { User } from '$lib/stores/user';
|
||||||
import { getContextStore } from '$lib/utils/context';
|
import { getContextStore } from '$lib/utils/context';
|
||||||
|
import { openExternalUrl } from '$lib/utils/url';
|
||||||
import Icon from '@gitbutler/ui/Icon.svelte';
|
import Icon from '@gitbutler/ui/Icon.svelte';
|
||||||
import { type Snippet } from 'svelte';
|
import { type Snippet } from 'svelte';
|
||||||
|
|
||||||
@ -68,22 +69,20 @@
|
|||||||
|
|
||||||
<div class="right-side__meta">
|
<div class="right-side__meta">
|
||||||
<div class="right-side__links">
|
<div class="right-side__links">
|
||||||
<a
|
<button
|
||||||
class="right-side__link"
|
class="right-side__link"
|
||||||
target="_blank"
|
onclick={async () => await openExternalUrl('https://docs.gitbutler.com/')}
|
||||||
href="https://docs.gitbutler.com/features/virtual-branches/branch-lanes"
|
|
||||||
>
|
>
|
||||||
<Icon name="docs" opacity={0.6} />
|
<Icon name="docs" opacity={0.6} />
|
||||||
<span class="text-14 text-semibold">GitButler docs</span>
|
<span class="text-14 text-semibold">GitButler docs</span>
|
||||||
</a>
|
</button>
|
||||||
<a
|
<button
|
||||||
class="right-side__link"
|
class="right-side__link"
|
||||||
target="_blank"
|
onclick={async () => await openExternalUrl('https://discord.com/invite/MmFkmaJ42D')}
|
||||||
href="https://discord.com/invite/MmFkmaJ42D"
|
|
||||||
>
|
>
|
||||||
<Icon name="discord" opacity={0.6} />
|
<Icon name="discord" opacity={0.6} />
|
||||||
<span class="text-14 text-semibold">Join community</span>
|
<span class="text-14 text-semibold">Join community</span>
|
||||||
</a>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="wordmark">
|
<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
|
// This is always displayed in the context of not having a cooresponding vbranch or remote
|
||||||
import { Project } from '$lib/backend/projects';
|
import { Project } from '$lib/backend/projects';
|
||||||
import { BaseBranchService } from '$lib/baseBranch/baseBranchService';
|
import { BaseBranchService } from '$lib/baseBranch/baseBranchService';
|
||||||
|
import Markdown from '$lib/components/Markdown.svelte';
|
||||||
import { RemotesService } from '$lib/remotes/service';
|
import { RemotesService } from '$lib/remotes/service';
|
||||||
import Link from '$lib/shared/Link.svelte';
|
import Link from '$lib/shared/Link.svelte';
|
||||||
import TextBox from '$lib/shared/TextBox.svelte';
|
import TextBox from '$lib/shared/TextBox.svelte';
|
||||||
import { getContext } from '$lib/utils/context';
|
import { getContext } from '$lib/utils/context';
|
||||||
import { getMarkdownRenderer } from '$lib/utils/markdown';
|
|
||||||
import * as toasts from '$lib/utils/toasts';
|
import * as toasts from '$lib/utils/toasts';
|
||||||
import { remoteUrlIsHttp } from '$lib/utils/url';
|
import { remoteUrlIsHttp } from '$lib/utils/url';
|
||||||
import { BranchController } from '$lib/vbranches/branchController';
|
import { BranchController } from '$lib/vbranches/branchController';
|
||||||
import { VirtualBranchService } from '$lib/vbranches/virtualBranch';
|
import { VirtualBranchService } from '$lib/vbranches/virtualBranch';
|
||||||
import Button from '@gitbutler/ui/Button.svelte';
|
import Button from '@gitbutler/ui/Button.svelte';
|
||||||
import Modal from '@gitbutler/ui/Modal.svelte';
|
import Modal from '@gitbutler/ui/Modal.svelte';
|
||||||
import { marked } from 'marked';
|
|
||||||
import { get } from 'svelte/store';
|
import { get } from 'svelte/store';
|
||||||
import type { PullRequest } from '$lib/gitHost/interface/types';
|
import type { PullRequest } from '$lib/gitHost/interface/types';
|
||||||
import { goto } from '$app/navigation';
|
import { goto } from '$app/navigation';
|
||||||
@ -25,7 +24,6 @@
|
|||||||
const remotesService = getContext(RemotesService);
|
const remotesService = getContext(RemotesService);
|
||||||
const baseBranchService = getContext(BaseBranchService);
|
const baseBranchService = getContext(BaseBranchService);
|
||||||
const virtualBranchService = getContext(VirtualBranchService);
|
const virtualBranchService = getContext(VirtualBranchService);
|
||||||
const renderer = getMarkdownRenderer();
|
|
||||||
|
|
||||||
let remoteName = structuredClone(pullrequest.repoName) || '';
|
let remoteName = structuredClone(pullrequest.repoName) || '';
|
||||||
let createRemoteModal: Modal | undefined;
|
let createRemoteModal: Modal | undefined;
|
||||||
@ -108,9 +106,9 @@
|
|||||||
{#if pullrequest.draft}
|
{#if pullrequest.draft}
|
||||||
<Button size="tag" clickable={false} style="neutral" icon="draft-pr-small">Draft</Button>
|
<Button size="tag" clickable={false} style="neutral" icon="draft-pr-small">Draft</Button>
|
||||||
{:else}
|
{:else}
|
||||||
<Button size="tag" clickable={false} style="success" kind="solid" icon="pr-small"
|
<Button size="tag" clickable={false} style="success" kind="solid" icon="pr-small">
|
||||||
>Open</Button
|
Open
|
||||||
>
|
</Button>
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -130,7 +128,7 @@
|
|||||||
</div>
|
</div>
|
||||||
{#if pullrequest.body}
|
{#if pullrequest.body}
|
||||||
<div class="markdown">
|
<div class="markdown">
|
||||||
{@html marked.parse(pullrequest.body, { renderer })}
|
<Markdown content={pullrequest.body} />
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
|
@ -7,7 +7,7 @@
|
|||||||
export let disabled = false;
|
export let disabled = false;
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<button class="menu-item" class:disabled {disabled} on:mousedown on:click>
|
<button class="menu-item" class:disabled {disabled} on:click>
|
||||||
{#if icon}
|
{#if icon}
|
||||||
<Icon name={icon} />
|
<Icon name={icon} />
|
||||||
{/if}
|
{/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 { getContext } from '$lib/utils/context';
|
||||||
import { computeFileStatus } from '$lib/utils/fileStatus';
|
import { computeFileStatus } from '$lib/utils/fileStatus';
|
||||||
import * as toasts from '$lib/utils/toasts';
|
import * as toasts from '$lib/utils/toasts';
|
||||||
|
import { openExternalUrl } from '$lib/utils/url';
|
||||||
import { BranchController } from '$lib/vbranches/branchController';
|
import { BranchController } from '$lib/vbranches/branchController';
|
||||||
import { LocalFile, type AnyFile } from '$lib/vbranches/types';
|
import { LocalFile, type AnyFile } from '$lib/vbranches/types';
|
||||||
import Button from '@gitbutler/ui/Button.svelte';
|
import Button from '@gitbutler/ui/Button.svelte';
|
||||||
import Modal from '@gitbutler/ui/Modal.svelte';
|
import Modal from '@gitbutler/ui/Modal.svelte';
|
||||||
import { join } from '@tauri-apps/api/path';
|
import { join } from '@tauri-apps/api/path';
|
||||||
import { open as openFile } from '@tauri-apps/api/shell';
|
|
||||||
|
|
||||||
export let branchId: string | undefined;
|
export let branchId: string | undefined;
|
||||||
export let target: HTMLElement | undefined;
|
export let target: HTMLElement | undefined;
|
||||||
@ -85,7 +85,7 @@
|
|||||||
if (!project) return;
|
if (!project) return;
|
||||||
for (let file of item.files) {
|
for (let file of item.files) {
|
||||||
const absPath = await join(project.vscodePath, file.path);
|
const absPath = await join(project.vscodePath, file.path);
|
||||||
openFile(`${$editor}://file${absPath}`);
|
openExternalUrl(`${$editor}://file${absPath}`);
|
||||||
}
|
}
|
||||||
contextMenu.close();
|
contextMenu.close();
|
||||||
} catch {
|
} catch {
|
||||||
|
@ -87,7 +87,7 @@ export class GitHubPrService implements GitHostPrService {
|
|||||||
|
|
||||||
showToast({
|
showToast({
|
||||||
title: 'Failed to fetch pull request template',
|
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'
|
style: 'neutral'
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -4,8 +4,8 @@
|
|||||||
import ContextMenuSection from '$lib/components/contextmenu/ContextMenuSection.svelte';
|
import ContextMenuSection from '$lib/components/contextmenu/ContextMenuSection.svelte';
|
||||||
import { editor } from '$lib/editorLink/editorLink';
|
import { editor } from '$lib/editorLink/editorLink';
|
||||||
import { getContext } from '$lib/utils/context';
|
import { getContext } from '$lib/utils/context';
|
||||||
|
import { openExternalUrl } from '$lib/utils/url';
|
||||||
import { BranchController } from '$lib/vbranches/branchController';
|
import { BranchController } from '$lib/vbranches/branchController';
|
||||||
import { open as openFile } from '@tauri-apps/api/shell';
|
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
target: HTMLElement | undefined;
|
target: HTMLElement | undefined;
|
||||||
@ -44,9 +44,9 @@
|
|||||||
{#if item.lineNumber}
|
{#if item.lineNumber}
|
||||||
<ContextMenuItem
|
<ContextMenuItem
|
||||||
label="Open in VSCode"
|
label="Open in VSCode"
|
||||||
on:mousedown={() => {
|
on:click={() => {
|
||||||
projectPath &&
|
projectPath &&
|
||||||
openFile(`${$editor}://file${projectPath}/${filePath}:${item.lineNumber}`);
|
openExternalUrl(`${$editor}://file${projectPath}/${filePath}:${item.lineNumber}`);
|
||||||
contextMenu.close();
|
contextMenu.close();
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
@ -1,11 +1,8 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
|
import Markdown from '$lib/components/Markdown.svelte';
|
||||||
import { dismissToast, toastStore } from '$lib/notifications/toasts';
|
import { dismissToast, toastStore } from '$lib/notifications/toasts';
|
||||||
import InfoMessage from '$lib/shared/InfoMessage.svelte';
|
import InfoMessage from '$lib/shared/InfoMessage.svelte';
|
||||||
import { getMarkdownRenderer } from '$lib/utils/markdown';
|
|
||||||
import { marked } from 'marked';
|
|
||||||
import { slide } from 'svelte/transition';
|
import { slide } from 'svelte/transition';
|
||||||
|
|
||||||
var renderer = getMarkdownRenderer();
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="toast-controller hide-native-scrollbar">
|
<div class="toast-controller hide-native-scrollbar">
|
||||||
@ -24,7 +21,9 @@
|
|||||||
</svelte:fragment>
|
</svelte:fragment>
|
||||||
|
|
||||||
<svelte:fragment slot="content">
|
<svelte:fragment slot="content">
|
||||||
{@html marked.parse(toast.message ?? '', { renderer })}
|
{#if toast.message}
|
||||||
|
<Markdown content={toast.message} />
|
||||||
|
{/if}
|
||||||
</svelte:fragment>
|
</svelte:fragment>
|
||||||
</InfoMessage>
|
</InfoMessage>
|
||||||
</div>
|
</div>
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import SupportersBanner from './SupportersBanner.svelte';
|
import SupportersBanner from './SupportersBanner.svelte';
|
||||||
|
import { openExternalUrl } from '$lib/utils/url';
|
||||||
import Button from '@gitbutler/ui/Button.svelte';
|
import Button from '@gitbutler/ui/Button.svelte';
|
||||||
import Icon from '@gitbutler/ui/Icon.svelte';
|
import Icon from '@gitbutler/ui/Icon.svelte';
|
||||||
import { goto } from '$app/navigation';
|
import { goto } from '$app/navigation';
|
||||||
@ -109,23 +110,21 @@
|
|||||||
|
|
||||||
<section class="profile-sidebar__bottom">
|
<section class="profile-sidebar__bottom">
|
||||||
<div class="social-banners">
|
<div class="social-banners">
|
||||||
<a
|
<button
|
||||||
class="social-banner"
|
class="social-banner"
|
||||||
href="mailto:hello@gitbutler.com?subject=Feedback or question!"
|
on:click={async () =>
|
||||||
target="_blank"
|
await openExternalUrl('mailto:hello@gitbutler.com?subject=Feedback or question!')}
|
||||||
>
|
>
|
||||||
<span class="text-14 text-bold">Contact us</span>
|
<span class="text-14 text-bold">Contact us</span>
|
||||||
<Icon name="mail" />
|
<Icon name="mail" />
|
||||||
</a>
|
</button>
|
||||||
<a
|
<button
|
||||||
class="social-banner"
|
class="social-banner"
|
||||||
href="https://discord.gg/MmFkmaJ42D"
|
on:click={async () => await openExternalUrl('https://discord.gg/MmFkmaJ42D')}
|
||||||
target="_blank"
|
|
||||||
rel="noreferrer"
|
|
||||||
>
|
>
|
||||||
<span class="text-14 text-bold">Join our Discord</span>
|
<span class="text-14 text-bold">Join our Discord</span>
|
||||||
<Icon name="discord" />
|
<Icon name="discord" />
|
||||||
</a>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<SupportersBanner />
|
<SupportersBanner />
|
||||||
|
@ -1,13 +1,20 @@
|
|||||||
<a class="banner" href="https://docs.gitbutler.com/community/supporters" target="_blank">
|
<script lang="ts">
|
||||||
<div class="benner-content">
|
import { openExternalUrl } from '$lib/utils/url';
|
||||||
<h4 class="benner-label text-14 text-bold">Thank you to all GitButler early supporters</h4>
|
</script>
|
||||||
<i class="benner-arrow-wrap">
|
|
||||||
<div class="benner-arrow-tail"></div>
|
<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
|
<svg
|
||||||
viewBox="0 0 7 11"
|
viewBox="0 0 7 11"
|
||||||
fill="none"
|
fill="none"
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
class="benner-arrow-head"
|
class="banner-arrow-head"
|
||||||
>
|
>
|
||||||
<path
|
<path
|
||||||
d="M0.871094 1L5.00013 5.5L0.871094 10"
|
d="M0.871094 1L5.00013 5.5L0.871094 10"
|
||||||
@ -18,7 +25,7 @@
|
|||||||
</i>
|
</i>
|
||||||
</div>
|
</div>
|
||||||
<img class="banner-img" src="/images/banners/support.svg" alt="" />
|
<img class="banner-img" src="/images/banners/support.svg" alt="" />
|
||||||
</a>
|
</button>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
.banner {
|
.banner {
|
||||||
@ -29,22 +36,22 @@
|
|||||||
background-color: #d7f2f1;
|
background-color: #d7f2f1;
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
& .benner-arrow-wrap {
|
& .banner-arrow-wrap {
|
||||||
width: 12px;
|
width: 12px;
|
||||||
}
|
}
|
||||||
|
|
||||||
& .benner-arrow-tail {
|
& .banner-arrow-tail {
|
||||||
width: 90%;
|
width: 90%;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.benner-content {
|
.banner-content {
|
||||||
display: inline;
|
display: inline;
|
||||||
color: #000;
|
color: #000;
|
||||||
}
|
}
|
||||||
|
|
||||||
.benner-label {
|
.banner-label {
|
||||||
display: inline;
|
display: inline;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -55,7 +62,7 @@
|
|||||||
|
|
||||||
/* ARROW */
|
/* ARROW */
|
||||||
|
|
||||||
.benner-arrow-wrap {
|
.banner-arrow-wrap {
|
||||||
position: relative;
|
position: relative;
|
||||||
display: inline-flex;
|
display: inline-flex;
|
||||||
transform: translateY(2px);
|
transform: translateY(2px);
|
||||||
@ -64,7 +71,7 @@
|
|||||||
transition: width 0.2s;
|
transition: width 0.2s;
|
||||||
}
|
}
|
||||||
|
|
||||||
.benner-arrow-tail {
|
.banner-arrow-tail {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 50%;
|
top: 50%;
|
||||||
transform: translateY(-50%);
|
transform: translateY(-50%);
|
||||||
@ -75,7 +82,7 @@
|
|||||||
transition: width 0.2s;
|
transition: width 0.2s;
|
||||||
}
|
}
|
||||||
|
|
||||||
.benner-arrow-head {
|
.banner-arrow-head {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
right: 0;
|
right: 0;
|
||||||
top: 0;
|
top: 0;
|
||||||
|
@ -1,15 +1,24 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
export let href: string;
|
import { openExternalUrl } from '$lib/utils/url';
|
||||||
import Icon from '@gitbutler/ui/Icon.svelte';
|
import Icon from '@gitbutler/ui/Icon.svelte';
|
||||||
import type iconsJson from '@gitbutler/ui/data/icons.json';
|
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>
|
</script>
|
||||||
|
|
||||||
<a class="link" target="_blank" {href}>
|
<button class="link" onclick={async () => await openExternalUrl(href)}>
|
||||||
<Icon name={icon} />
|
<Icon name={icon} />
|
||||||
<span class="text-12"><slot /></span>
|
<span class="text-12">
|
||||||
</a>
|
{@render children()}
|
||||||
|
</span>
|
||||||
|
</button>
|
||||||
|
|
||||||
<style lang="postcss">
|
<style lang="postcss">
|
||||||
.link {
|
.link {
|
||||||
|
@ -1,17 +1,29 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { openExternalUrl } from '$lib/utils/url';
|
import { openExternalUrl } from '$lib/utils/url';
|
||||||
import Icon from '@gitbutler/ui/Icon.svelte';
|
import Icon from '@gitbutler/ui/Icon.svelte';
|
||||||
import { onMount } from 'svelte';
|
import { onMount, type Snippet } from 'svelte';
|
||||||
|
|
||||||
let classes = '';
|
interface Props {
|
||||||
export { classes as class };
|
href: string;
|
||||||
export let target: '_blank' | '_self' | '_parent' | '_top' | undefined = undefined;
|
children: Snippet;
|
||||||
export let rel: string | undefined = undefined;
|
class?: string;
|
||||||
export let role: 'basic' | 'primary' | 'error' = 'basic';
|
target?: '_blank' | '_self' | '_parent' | '_top' | undefined;
|
||||||
export let disabled = false;
|
rel?: string | undefined;
|
||||||
export let href: string | undefined = 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(() => {
|
onMount(() => {
|
||||||
if (element) {
|
if (element) {
|
||||||
@ -19,10 +31,9 @@
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
$: isExternal = href?.startsWith('http');
|
const isExternal = $derived(href?.startsWith('http'));
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
{#if href}
|
|
||||||
<a
|
<a
|
||||||
{href}
|
{href}
|
||||||
{target}
|
{target}
|
||||||
@ -30,7 +41,7 @@
|
|||||||
class="link {role} {classes}"
|
class="link {role} {classes}"
|
||||||
bind:this={element}
|
bind:this={element}
|
||||||
class:disabled
|
class:disabled
|
||||||
on:click={(e) => {
|
onclick={(e) => {
|
||||||
if (href && isExternal) {
|
if (href && isExternal) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
@ -38,14 +49,13 @@
|
|||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<slot />
|
{@render children()}
|
||||||
{#if isExternal}
|
{#if isExternal}
|
||||||
<div class="link-icon">
|
<div class="link-icon">
|
||||||
<Icon name="open-link" />
|
<Icon name="open-link" />
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
</a>
|
</a>
|
||||||
{/if}
|
|
||||||
|
|
||||||
<style lang="postcss">
|
<style lang="postcss">
|
||||||
.link {
|
.link {
|
||||||
@ -62,6 +72,7 @@
|
|||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.link-icon {
|
.link-icon {
|
||||||
flex-shrink: 0;
|
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) {
|
export function isWhiteSpaceString(s: string) {
|
||||||
return s.trim() === '';
|
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 { showToast } from '$lib/notifications/toasts';
|
||||||
import { open } from '@tauri-apps/api/shell';
|
|
||||||
import GitUrlParse from 'git-url-parse';
|
import GitUrlParse from 'git-url-parse';
|
||||||
import { posthog } from 'posthog-js';
|
import { posthog } from 'posthog-js';
|
||||||
|
|
||||||
export async function openExternalUrl(href: string) {
|
export async function openExternalUrl(href: string) {
|
||||||
try {
|
try {
|
||||||
await open(href);
|
await invoke<void>('open_url', { url: href });
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
if (typeof e === 'string' || e instanceof String) {
|
if (typeof e === 'string' || e instanceof String) {
|
||||||
// TODO: Remove if/when we've resolved all external URL problems.
|
// 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-operating-modes.workspace = true
|
||||||
gitbutler-edit-mode.workspace = true
|
gitbutler-edit-mode.workspace = true
|
||||||
open = "5"
|
open = "5"
|
||||||
|
url = "2.5.2"
|
||||||
|
|
||||||
[dependencies.tauri]
|
[dependencies.tauri]
|
||||||
version = "1.7.0"
|
version = "1.7.0"
|
||||||
@ -78,7 +79,6 @@ features = [
|
|||||||
"path-all",
|
"path-all",
|
||||||
"process-relaunch",
|
"process-relaunch",
|
||||||
"protocol-asset",
|
"protocol-asset",
|
||||||
"shell-open",
|
|
||||||
"window-maximize",
|
"window-maximize",
|
||||||
"window-start-dragging",
|
"window-start-dragging",
|
||||||
"window-unmaximize",
|
"window-unmaximize",
|
||||||
|
@ -26,6 +26,7 @@ pub mod config;
|
|||||||
pub mod error;
|
pub mod error;
|
||||||
pub mod github;
|
pub mod github;
|
||||||
pub mod modes;
|
pub mod modes;
|
||||||
|
pub mod open;
|
||||||
pub mod projects;
|
pub mod projects;
|
||||||
pub mod remotes;
|
pub mod remotes;
|
||||||
pub mod repo;
|
pub mod repo;
|
||||||
|
@ -12,8 +12,8 @@
|
|||||||
)]
|
)]
|
||||||
|
|
||||||
use gitbutler_tauri::{
|
use gitbutler_tauri::{
|
||||||
askpass, commands, config, github, logs, menu, modes, projects, remotes, repo, secret, undo,
|
askpass, commands, config, github, logs, menu, modes, open, projects, remotes, repo, secret,
|
||||||
users, virtual_branches, zip, App, WindowState,
|
undo, users, virtual_branches, zip, App, WindowState,
|
||||||
};
|
};
|
||||||
use tauri::{generate_context, Manager};
|
use tauri::{generate_context, Manager};
|
||||||
use tauri_plugin_log::LogTarget;
|
use tauri_plugin_log::LogTarget;
|
||||||
@ -206,7 +206,8 @@ fn main() {
|
|||||||
modes::enter_edit_mode,
|
modes::enter_edit_mode,
|
||||||
modes::save_edit_and_return_to_workspace,
|
modes::save_edit_and_return_to_workspace,
|
||||||
modes::abort_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()))
|
.menu(menu::build(tauri_context.package_info()))
|
||||||
.on_menu_event(|event| menu::handle_event(&event))
|
.on_menu_event(|event| menu::handle_event(&event))
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
use std::{env, fs};
|
use std::{env, fs};
|
||||||
|
|
||||||
|
use crate::open::open_that as open_url;
|
||||||
use anyhow::Context;
|
use anyhow::Context;
|
||||||
use gitbutler_error::{error, error::Code};
|
use gitbutler_error::{error, error::Code};
|
||||||
use serde_json::json;
|
use serde_json::json;
|
||||||
@ -303,17 +304,13 @@ pub fn handle_event<R: Runtime>(event: &WindowMenuEvent<R>) {
|
|||||||
|
|
||||||
'open_link: {
|
'open_link: {
|
||||||
let result = match event.menu_item_id() {
|
let result = match event.menu_item_id() {
|
||||||
"help/documentation" => open::that("https://docs.gitbutler.com"),
|
"help/documentation" => open_url("https://docs.gitbutler.com"),
|
||||||
"help/github" => open::that("https://github.com/gitbutlerapp/gitbutler"),
|
"help/github" => open_url("https://github.com/gitbutlerapp/gitbutler"),
|
||||||
"help/release-notes" => {
|
"help/release-notes" => open_url("https://github.com/gitbutlerapp/gitbutler/releases"),
|
||||||
open::that("https://discord.com/channels/1060193121130000425/1183737922785116161")
|
"help/report-issue" => open_url("https://github.com/gitbutlerapp/gitbutler/issues/new"),
|
||||||
}
|
"help/discord" => open_url("https://discord.com/invite/MmFkmaJ42D"),
|
||||||
"help/report-issue" => {
|
"help/youtube" => open_url("https://www.youtube.com/@gitbutlerapp"),
|
||||||
open::that("https://github.com/gitbutlerapp/gitbutler/issues/new")
|
"help/x" => open_url("https://x.com/gitbutler"),
|
||||||
}
|
|
||||||
"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"),
|
|
||||||
_ => break 'open_link,
|
_ => 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,
|
"readFile": true,
|
||||||
"scope": ["$APPCACHE/archives/*", "$RESOURCE/_up_/scripts/*"]
|
"scope": ["$APPCACHE/archives/*", "$RESOURCE/_up_/scripts/*"]
|
||||||
},
|
},
|
||||||
"shell": {
|
|
||||||
"open": "^((https://)|(http://)|(mailto:)|(vscode://)|(vscodium://)).+"
|
|
||||||
},
|
|
||||||
"dialog": {
|
"dialog": {
|
||||||
"open": true
|
"open": true
|
||||||
},
|
},
|
||||||
|
@ -6,11 +6,6 @@
|
|||||||
"productName": "GitButler Nightly"
|
"productName": "GitButler Nightly"
|
||||||
},
|
},
|
||||||
"tauri": {
|
"tauri": {
|
||||||
"allowlist": {
|
|
||||||
"shell": {
|
|
||||||
"open": "^((https://)|(http://)|(mailto:)|(vscode://)|(vscodium://)).+"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"bundle": {
|
"bundle": {
|
||||||
"identifier": "com.gitbutler.app.nightly",
|
"identifier": "com.gitbutler.app.nightly",
|
||||||
"icon": [
|
"icon": [
|
||||||
|
@ -6,11 +6,6 @@
|
|||||||
"productName": "GitButler"
|
"productName": "GitButler"
|
||||||
},
|
},
|
||||||
"tauri": {
|
"tauri": {
|
||||||
"allowlist": {
|
|
||||||
"shell": {
|
|
||||||
"open": "^((https://)|(http://)|(mailto:)|(vscode://)|(vscodium://)).+"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"bundle": {
|
"bundle": {
|
||||||
"identifier": "com.gitbutler.app",
|
"identifier": "com.gitbutler.app",
|
||||||
"icon": [
|
"icon": [
|
||||||
|
@ -12,9 +12,6 @@
|
|||||||
"readFile": true,
|
"readFile": true,
|
||||||
"scope": ["$APPCACHE/archives/*", "$RESOURCE/_up_/scripts/*"]
|
"scope": ["$APPCACHE/archives/*", "$RESOURCE/_up_/scripts/*"]
|
||||||
},
|
},
|
||||||
"shell": {
|
|
||||||
"open": "^((https://)|(http://)|(mailto:)|(vscode://)|(vscodium://)).+"
|
|
||||||
},
|
|
||||||
"dialog": {
|
"dialog": {
|
||||||
"open": true
|
"open": true
|
||||||
},
|
},
|
||||||
|
Loading…
Reference in New Issue
Block a user