generated from nhcarrigan/template
feat: add streamer mode for privacy during streaming
- Add quick toggle button in InputBar for easy access - Mask API keys in settings when streamer mode active - Optional path masking to hide usernames in file paths - Visual LIVE indicator in both InputBar and StatusBar - Keyboard shortcut Ctrl+Shift+S for quick toggle - Privacy section in settings for additional options Closes #35
This commit is contained in:
@@ -73,6 +73,12 @@ pub struct HikariConfig {
|
|||||||
|
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub minimize_to_tray: bool,
|
pub minimize_to_tray: bool,
|
||||||
|
|
||||||
|
#[serde(default)]
|
||||||
|
pub streamer_mode: bool,
|
||||||
|
|
||||||
|
#[serde(default)]
|
||||||
|
pub streamer_hide_paths: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for HikariConfig {
|
impl Default for HikariConfig {
|
||||||
@@ -93,6 +99,8 @@ impl Default for HikariConfig {
|
|||||||
character_panel_width: None,
|
character_panel_width: None,
|
||||||
font_size: 14,
|
font_size: 14,
|
||||||
minimize_to_tray: false,
|
minimize_to_tray: false,
|
||||||
|
streamer_mode: false,
|
||||||
|
streamer_hide_paths: false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -147,6 +155,8 @@ mod tests {
|
|||||||
assert!(config.character_panel_width.is_none());
|
assert!(config.character_panel_width.is_none());
|
||||||
assert_eq!(config.font_size, 14);
|
assert_eq!(config.font_size, 14);
|
||||||
assert!(!config.minimize_to_tray);
|
assert!(!config.minimize_to_tray);
|
||||||
|
assert!(!config.streamer_mode);
|
||||||
|
assert!(!config.streamer_hide_paths);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|||||||
@@ -27,6 +27,8 @@
|
|||||||
update_checks_enabled: true,
|
update_checks_enabled: true,
|
||||||
character_panel_width: null,
|
character_panel_width: null,
|
||||||
font_size: 14,
|
font_size: 14,
|
||||||
|
streamer_mode: false,
|
||||||
|
streamer_hide_paths: false,
|
||||||
});
|
});
|
||||||
|
|
||||||
let isOpen = $state(false);
|
let isOpen = $state(false);
|
||||||
@@ -187,47 +189,60 @@
|
|||||||
<div class="mb-4">
|
<div class="mb-4">
|
||||||
<label for="api-key" class="block text-sm text-[var(--text-secondary)] mb-1">
|
<label for="api-key" class="block text-sm text-[var(--text-secondary)] mb-1">
|
||||||
API Key <span class="text-[var(--text-tertiary)]">(optional override)</span>
|
API Key <span class="text-[var(--text-tertiary)]">(optional override)</span>
|
||||||
|
{#if config.streamer_mode}
|
||||||
|
<span class="text-yellow-500 text-xs ml-2">🔒 Hidden (Streamer Mode)</span>
|
||||||
|
{/if}
|
||||||
</label>
|
</label>
|
||||||
<div class="relative">
|
<div class="relative">
|
||||||
<input
|
{#if config.streamer_mode}
|
||||||
id="api-key"
|
<input
|
||||||
type={showApiKey ? "text" : "password"}
|
id="api-key"
|
||||||
bind:value={config.api_key}
|
type="password"
|
||||||
placeholder="Falls back to ~/.claude settings"
|
value="••••••••••••••••••••••••"
|
||||||
class="w-full px-3 py-2 pr-10 bg-[var(--bg-primary)] border border-[var(--border-color)] rounded-lg text-[var(--text-primary)] focus:outline-none focus:border-[var(--accent-primary)]"
|
disabled
|
||||||
/>
|
class="w-full px-3 py-2 pr-10 bg-[var(--bg-primary)] border border-[var(--border-color)] rounded-lg text-[var(--text-tertiary)] focus:outline-none cursor-not-allowed"
|
||||||
<button
|
/>
|
||||||
type="button"
|
{:else}
|
||||||
onclick={() => (showApiKey = !showApiKey)}
|
<input
|
||||||
class="absolute right-2 top-1/2 -translate-y-1/2 text-[var(--text-tertiary)] hover:text-[var(--text-primary)]"
|
id="api-key"
|
||||||
aria-label={showApiKey ? "Hide API key" : "Show API key"}
|
type={showApiKey ? "text" : "password"}
|
||||||
>
|
bind:value={config.api_key}
|
||||||
{#if showApiKey}
|
placeholder="Falls back to ~/.claude settings"
|
||||||
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
class="w-full px-3 py-2 pr-10 bg-[var(--bg-primary)] border border-[var(--border-color)] rounded-lg text-[var(--text-primary)] focus:outline-none focus:border-[var(--accent-primary)]"
|
||||||
<path
|
/>
|
||||||
stroke-linecap="round"
|
<button
|
||||||
stroke-linejoin="round"
|
type="button"
|
||||||
stroke-width="2"
|
onclick={() => (showApiKey = !showApiKey)}
|
||||||
d="M13.875 18.825A10.05 10.05 0 0112 19c-4.478 0-8.268-2.943-9.543-7a9.97 9.97 0 011.563-3.029m5.858.908a3 3 0 114.243 4.243M9.878 9.878l4.242 4.242M9.88 9.88l-3.29-3.29m7.532 7.532l3.29 3.29M3 3l3.59 3.59m0 0A9.953 9.953 0 0112 5c4.478 0 8.268 2.943 9.543 7a10.025 10.025 0 01-4.132 5.411m0 0L21 21"
|
class="absolute right-2 top-1/2 -translate-y-1/2 text-[var(--text-tertiary)] hover:text-[var(--text-primary)]"
|
||||||
/>
|
aria-label={showApiKey ? "Hide API key" : "Show API key"}
|
||||||
</svg>
|
>
|
||||||
{:else}
|
{#if showApiKey}
|
||||||
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||||
<path
|
<path
|
||||||
stroke-linecap="round"
|
stroke-linecap="round"
|
||||||
stroke-linejoin="round"
|
stroke-linejoin="round"
|
||||||
stroke-width="2"
|
stroke-width="2"
|
||||||
d="M15 12a3 3 0 11-6 0 3 3 0 016 0z"
|
d="M13.875 18.825A10.05 10.05 0 0112 19c-4.478 0-8.268-2.943-9.543-7a9.97 9.97 0 011.563-3.029m5.858.908a3 3 0 114.243 4.243M9.878 9.878l4.242 4.242M9.88 9.88l-3.29-3.29m7.532 7.532l3.29 3.29M3 3l3.59 3.59m0 0A9.953 9.953 0 0112 5c4.478 0 8.268 2.943 9.543 7a10.025 10.025 0 01-4.132 5.411m0 0L21 21"
|
||||||
/>
|
/>
|
||||||
<path
|
</svg>
|
||||||
stroke-linecap="round"
|
{:else}
|
||||||
stroke-linejoin="round"
|
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||||
stroke-width="2"
|
<path
|
||||||
d="M2.458 12C3.732 7.943 7.523 5 12 5c4.478 0 8.268 2.943 9.542 7-1.274 4.057-5.064 7-9.542 7-4.477 0-8.268-2.943-9.542-7z"
|
stroke-linecap="round"
|
||||||
/>
|
stroke-linejoin="round"
|
||||||
</svg>
|
stroke-width="2"
|
||||||
{/if}
|
d="M15 12a3 3 0 11-6 0 3 3 0 016 0z"
|
||||||
</button>
|
/>
|
||||||
|
<path
|
||||||
|
stroke-linecap="round"
|
||||||
|
stroke-linejoin="round"
|
||||||
|
stroke-width="2"
|
||||||
|
d="M2.458 12C3.732 7.943 7.523 5 12 5c4.478 0 8.268 2.943 9.542 7-1.274 4.057-5.064 7-9.542 7-4.477 0-8.268-2.943-9.542-7z"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
{/if}
|
||||||
|
</button>
|
||||||
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -519,6 +534,45 @@
|
|||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
|
<!-- Privacy / Streamer Mode Section -->
|
||||||
|
<section class="mb-6">
|
||||||
|
<h3 class="text-sm font-medium text-[var(--accent-primary)] uppercase tracking-wider mb-3">
|
||||||
|
Privacy / Streamer Mode
|
||||||
|
</h3>
|
||||||
|
|
||||||
|
<!-- Streamer Mode Toggle -->
|
||||||
|
<div class="mb-4">
|
||||||
|
<label class="flex items-center gap-3 cursor-pointer">
|
||||||
|
<input
|
||||||
|
type="checkbox"
|
||||||
|
bind:checked={config.streamer_mode}
|
||||||
|
class="w-4 h-4 text-[var(--accent-primary)] bg-[var(--bg-primary)] border-[var(--border-color)] rounded focus:ring-[var(--accent-primary)] focus:ring-2"
|
||||||
|
/>
|
||||||
|
<span class="text-sm text-[var(--text-primary)]">Enable streamer mode</span>
|
||||||
|
</label>
|
||||||
|
<p class="text-xs text-[var(--text-tertiary)] mt-1 ml-7">
|
||||||
|
Hide sensitive information like API keys when streaming (Ctrl+Shift+S to toggle)
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Hide Paths Toggle -->
|
||||||
|
{#if config.streamer_mode}
|
||||||
|
<div class="mb-4">
|
||||||
|
<label class="flex items-center gap-3 cursor-pointer">
|
||||||
|
<input
|
||||||
|
type="checkbox"
|
||||||
|
bind:checked={config.streamer_hide_paths}
|
||||||
|
class="w-4 h-4 text-[var(--accent-primary)] bg-[var(--bg-primary)] border-[var(--border-color)] rounded focus:ring-[var(--accent-primary)] focus:ring-2"
|
||||||
|
/>
|
||||||
|
<span class="text-sm text-[var(--text-primary)]">Also hide file paths</span>
|
||||||
|
</label>
|
||||||
|
<p class="text-xs text-[var(--text-tertiary)] mt-1 ml-7">
|
||||||
|
Mask directory paths (e.g., /home/user → /home/****)
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
</section>
|
||||||
|
|
||||||
<!-- Notifications Section -->
|
<!-- Notifications Section -->
|
||||||
<section class="mb-6">
|
<section class="mb-6">
|
||||||
<h3 class="text-sm font-medium text-[var(--accent-primary)] uppercase tracking-wider mb-3">
|
<h3 class="text-sm font-medium text-[var(--accent-primary)] uppercase tracking-wider mb-3">
|
||||||
|
|||||||
@@ -25,6 +25,7 @@
|
|||||||
isSlashCommand,
|
isSlashCommand,
|
||||||
type SlashCommand,
|
type SlashCommand,
|
||||||
} from "$lib/commands/slashCommands";
|
} from "$lib/commands/slashCommands";
|
||||||
|
import { configStore, isStreamerMode } from "$lib/stores/config";
|
||||||
import AttachmentPreview from "$lib/components/AttachmentPreview.svelte";
|
import AttachmentPreview from "$lib/components/AttachmentPreview.svelte";
|
||||||
import SnippetLibraryPanel from "$lib/components/SnippetLibraryPanel.svelte";
|
import SnippetLibraryPanel from "$lib/components/SnippetLibraryPanel.svelte";
|
||||||
import QuickActionsPanel from "$lib/components/QuickActionsPanel.svelte";
|
import QuickActionsPanel from "$lib/components/QuickActionsPanel.svelte";
|
||||||
@@ -46,6 +47,11 @@
|
|||||||
let showSnippetLibrary = $state(false);
|
let showSnippetLibrary = $state(false);
|
||||||
let showQuickActions = $state(false);
|
let showQuickActions = $state(false);
|
||||||
let showClipboardHistory = $state(false);
|
let showClipboardHistory = $state(false);
|
||||||
|
let streamerModeActive = $state(false);
|
||||||
|
|
||||||
|
isStreamerMode.subscribe((value) => {
|
||||||
|
streamerModeActive = value;
|
||||||
|
});
|
||||||
|
|
||||||
// Input history state
|
// Input history state
|
||||||
let inputHistory = $state<string[]>([]);
|
let inputHistory = $state<string[]>([]);
|
||||||
@@ -765,6 +771,34 @@ User: ${formattedMessage}`;
|
|||||||
|
|
||||||
<div class="input-controls flex gap-2 mb-2">
|
<div class="input-controls flex gap-2 mb-2">
|
||||||
<MessageModeSelector />
|
<MessageModeSelector />
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onclick={() => configStore.toggleStreamerMode()}
|
||||||
|
class="control-button streamer-toggle"
|
||||||
|
class:streamer-active={streamerModeActive}
|
||||||
|
title="Toggle Streamer Mode (Ctrl+Shift+S)"
|
||||||
|
>
|
||||||
|
{#if streamerModeActive}
|
||||||
|
<div class="live-indicator"></div>
|
||||||
|
<span>LIVE</span>
|
||||||
|
{:else}
|
||||||
|
<svg
|
||||||
|
width="16"
|
||||||
|
height="16"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
fill="none"
|
||||||
|
stroke="currentColor"
|
||||||
|
stroke-width="2"
|
||||||
|
stroke-linecap="round"
|
||||||
|
stroke-linejoin="round"
|
||||||
|
>
|
||||||
|
<path d="M1 12s4-8 11-8 11 8 11 8-4 8-11 8-11-8-11-8z" />
|
||||||
|
<circle cx="12" cy="12" r="3" />
|
||||||
|
<line x1="1" y1="1" x2="23" y2="23" />
|
||||||
|
</svg>
|
||||||
|
<span>Stream</span>
|
||||||
|
{/if}
|
||||||
|
</button>
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
onclick={() => (showQuickActions = true)}
|
onclick={() => (showQuickActions = true)}
|
||||||
@@ -992,6 +1026,46 @@ User: ${formattedMessage}`;
|
|||||||
transform: scale(0.95);
|
transform: scale(0.95);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.streamer-toggle.streamer-active {
|
||||||
|
background: rgba(239, 68, 68, 0.2);
|
||||||
|
border-color: rgb(239, 68, 68);
|
||||||
|
color: rgb(248, 113, 113);
|
||||||
|
animation: pulse-red 2s ease-in-out infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
.streamer-toggle.streamer-active:hover {
|
||||||
|
background: rgba(239, 68, 68, 0.3);
|
||||||
|
border-color: rgb(248, 113, 113);
|
||||||
|
}
|
||||||
|
|
||||||
|
.live-indicator {
|
||||||
|
width: 8px;
|
||||||
|
height: 8px;
|
||||||
|
border-radius: 50%;
|
||||||
|
background: rgb(239, 68, 68);
|
||||||
|
animation: pulse-dot 1.5s ease-in-out infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes pulse-red {
|
||||||
|
0%,
|
||||||
|
100% {
|
||||||
|
box-shadow: 0 0 0 0 rgba(239, 68, 68, 0.4);
|
||||||
|
}
|
||||||
|
50% {
|
||||||
|
box-shadow: 0 0 0 4px rgba(239, 68, 68, 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes pulse-dot {
|
||||||
|
0%,
|
||||||
|
100% {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
50% {
|
||||||
|
opacity: 0.5;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.input-row {
|
.input-row {
|
||||||
display: flex;
|
display: flex;
|
||||||
gap: 12px;
|
gap: 12px;
|
||||||
|
|||||||
@@ -11,7 +11,7 @@
|
|||||||
import { openUrl } from "@tauri-apps/plugin-opener";
|
import { openUrl } from "@tauri-apps/plugin-opener";
|
||||||
import { get } from "svelte/store";
|
import { get } from "svelte/store";
|
||||||
import { claudeStore } from "$lib/stores/claude";
|
import { claudeStore } from "$lib/stores/claude";
|
||||||
import { configStore, type HikariConfig } from "$lib/stores/config";
|
import { configStore, type HikariConfig, isStreamerMode } from "$lib/stores/config";
|
||||||
import type { ConnectionStatus } from "$lib/types/messages";
|
import type { ConnectionStatus } from "$lib/types/messages";
|
||||||
import { onMount } from "svelte";
|
import { onMount } from "svelte";
|
||||||
import StatsDisplay from "./StatsDisplay.svelte";
|
import StatsDisplay from "./StatsDisplay.svelte";
|
||||||
@@ -54,6 +54,13 @@
|
|||||||
character_panel_width: null,
|
character_panel_width: null,
|
||||||
font_size: 14,
|
font_size: 14,
|
||||||
minimize_to_tray: false,
|
minimize_to_tray: false,
|
||||||
|
streamer_mode: false,
|
||||||
|
streamer_hide_paths: false,
|
||||||
|
});
|
||||||
|
|
||||||
|
let streamerModeActive = $state(false);
|
||||||
|
isStreamerMode.subscribe((value) => {
|
||||||
|
streamerModeActive = value;
|
||||||
});
|
});
|
||||||
|
|
||||||
onMount(async () => {
|
onMount(async () => {
|
||||||
@@ -207,6 +214,9 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="flex items-center gap-3">
|
<div class="flex items-center gap-3">
|
||||||
|
{#if streamerModeActive}
|
||||||
|
<div class="w-2.5 h-2.5 rounded-full bg-red-500 animate-pulse" title="Streamer mode active (Ctrl+Shift+S to toggle)"></div>
|
||||||
|
{/if}
|
||||||
<button
|
<button
|
||||||
onclick={toggleAchievements}
|
onclick={toggleAchievements}
|
||||||
class="p-1 text-gray-500 icon-trans-hover relative"
|
class="p-1 text-gray-500 icon-trans-hover relative"
|
||||||
|
|||||||
@@ -6,6 +6,7 @@
|
|||||||
import HighlightedText from "./HighlightedText.svelte";
|
import HighlightedText from "./HighlightedText.svelte";
|
||||||
import { searchState, searchQuery } from "$lib/stores/search";
|
import { searchState, searchQuery } from "$lib/stores/search";
|
||||||
import { clipboardStore } from "$lib/stores/clipboard";
|
import { clipboardStore } from "$lib/stores/clipboard";
|
||||||
|
import { shouldHidePaths, maskPaths } from "$lib/stores/config";
|
||||||
|
|
||||||
let terminalElement: HTMLDivElement;
|
let terminalElement: HTMLDivElement;
|
||||||
let shouldAutoScroll = true;
|
let shouldAutoScroll = true;
|
||||||
@@ -18,6 +19,11 @@
|
|||||||
currentSearchQuery = value;
|
currentSearchQuery = value;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
let hidePaths = false;
|
||||||
|
shouldHidePaths.subscribe((value) => {
|
||||||
|
hidePaths = value;
|
||||||
|
});
|
||||||
|
|
||||||
claudeStore.terminalLines.subscribe((value) => {
|
claudeStore.terminalLines.subscribe((value) => {
|
||||||
lines = value;
|
lines = value;
|
||||||
});
|
});
|
||||||
@@ -188,9 +194,9 @@
|
|||||||
<span class="terminal-tool-name mr-2">[{line.toolName}]</span>
|
<span class="terminal-tool-name mr-2">[{line.toolName}]</span>
|
||||||
{/if}
|
{/if}
|
||||||
{#if line.type === "assistant"}
|
{#if line.type === "assistant"}
|
||||||
<Markdown content={line.content} searchQuery={currentSearchQuery} />
|
<Markdown content={maskPaths(line.content, hidePaths)} searchQuery={currentSearchQuery} />
|
||||||
{:else}
|
{:else}
|
||||||
<HighlightedText content={line.content} searchQuery={currentSearchQuery} />
|
<HighlightedText content={maskPaths(line.content, hidePaths)} searchQuery={currentSearchQuery} />
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
{/each}
|
{/each}
|
||||||
|
|||||||
@@ -19,6 +19,8 @@ export interface HikariConfig {
|
|||||||
update_checks_enabled: boolean;
|
update_checks_enabled: boolean;
|
||||||
character_panel_width: number | null;
|
character_panel_width: number | null;
|
||||||
font_size: number;
|
font_size: number;
|
||||||
|
streamer_mode: boolean;
|
||||||
|
streamer_hide_paths: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
const defaultConfig: HikariConfig = {
|
const defaultConfig: HikariConfig = {
|
||||||
@@ -37,6 +39,8 @@ const defaultConfig: HikariConfig = {
|
|||||||
update_checks_enabled: true,
|
update_checks_enabled: true,
|
||||||
character_panel_width: null,
|
character_panel_width: null,
|
||||||
font_size: 14,
|
font_size: 14,
|
||||||
|
streamer_mode: false,
|
||||||
|
streamer_hide_paths: false,
|
||||||
};
|
};
|
||||||
|
|
||||||
function createConfigStore() {
|
function createConfigStore() {
|
||||||
@@ -145,6 +149,12 @@ function createConfigStore() {
|
|||||||
config.subscribe((c) => (currentConfig = c))();
|
config.subscribe((c) => (currentConfig = c))();
|
||||||
return currentConfig;
|
return currentConfig;
|
||||||
},
|
},
|
||||||
|
|
||||||
|
toggleStreamerMode: async () => {
|
||||||
|
let currentConfig: HikariConfig = defaultConfig;
|
||||||
|
config.subscribe((c) => (currentConfig = c))();
|
||||||
|
await updateConfig({ streamer_mode: !currentConfig.streamer_mode });
|
||||||
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -174,3 +184,25 @@ export { MIN_FONT_SIZE, MAX_FONT_SIZE, DEFAULT_FONT_SIZE };
|
|||||||
export const configStore = createConfigStore();
|
export const configStore = createConfigStore();
|
||||||
|
|
||||||
export const isDarkTheme = derived(configStore.config, ($config) => $config.theme === "dark");
|
export const isDarkTheme = derived(configStore.config, ($config) => $config.theme === "dark");
|
||||||
|
|
||||||
|
export const isStreamerMode = derived(configStore.config, ($config) => $config.streamer_mode);
|
||||||
|
export const shouldHidePaths = derived(
|
||||||
|
configStore.config,
|
||||||
|
($config) => $config.streamer_mode && $config.streamer_hide_paths
|
||||||
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Masks file paths in text when streamer mode with hide paths is enabled.
|
||||||
|
* Replaces username portion of paths with asterisks.
|
||||||
|
*/
|
||||||
|
export function maskPaths(text: string, hidePaths: boolean): string {
|
||||||
|
if (!hidePaths) return text;
|
||||||
|
|
||||||
|
// Match Unix paths like /home/username/... or /Users/username/...
|
||||||
|
// and Windows paths like C:\Users\username\...
|
||||||
|
return text
|
||||||
|
.replace(/\/home\/([^\/\s]+)/g, "/home/****")
|
||||||
|
.replace(/\/Users\/([^\/\s]+)/g, "/Users/****")
|
||||||
|
.replace(/C:\\Users\\([^\\\s]+)/gi, "C:\\Users\\****")
|
||||||
|
.replace(/~\//g, "****/");
|
||||||
|
}
|
||||||
|
|||||||
@@ -149,6 +149,13 @@
|
|||||||
configStore.resetFontSize();
|
configStore.resetFontSize();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Ctrl+Shift+S - Toggle streamer mode
|
||||||
|
if (event.ctrlKey && event.shiftKey && event.key === "S") {
|
||||||
|
event.preventDefault();
|
||||||
|
configStore.toggleStreamerMode();
|
||||||
|
return;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function handleInterrupt() {
|
async function handleInterrupt() {
|
||||||
|
|||||||
Reference in New Issue
Block a user