diff --git a/website/client/components/Home/TryIt.vue b/website/client/components/Home/TryIt.vue
index c4d15ed7b..4c300ba8b 100644
--- a/website/client/components/Home/TryIt.vue
+++ b/website/client/components/Home/TryIt.vue
@@ -54,6 +54,22 @@
:loading="loading"
:isValid="isSubmitValid"
/>
+
@@ -83,9 +100,10 @@
@@ -248,6 +311,29 @@ onMounted(() => {
align-items: stretch;
align-self: start;
flex-shrink: 0;
+ gap: 8px;
+}
+
+.reset-button {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ width: 50px;
+ height: 50px;
+ background: white;
+ color: var(--vp-c-text-2);
+ border: 1px solid var(--vp-c-border);
+ border-radius: 8px;
+ cursor: pointer;
+ transition: all 0.2s ease;
+ flex-shrink: 0;
+}
+
+.reset-button:hover {
+ color: var(--vp-c-brand-1);
+ border-color: var(--vp-c-brand-1);
+ background: var(--vp-c-bg-soft);
+
}
/* Responsive adjustments */
@@ -263,6 +349,45 @@ onMounted(() => {
.pack-button-wrapper {
width: 100%;
+ gap: 8px;
}
}
+.tooltip-container {
+ position: relative;
+ display: inline-block;
+}
+
+.tooltip-content {
+ position: absolute;
+ bottom: 100%;
+ left: 50%;
+ transform: translateX(-50%);
+ margin-bottom: 8px;
+ padding: 8px 12px;
+ background: #333;
+ color: white;
+ font-size: 0.875rem;
+ white-space: nowrap;
+ border-radius: 4px;
+ opacity: 0;
+ visibility: hidden;
+ transition: opacity 0.2s, visibility 0.2s;
+ z-index: 10;
+}
+
+.tooltip-container:hover .tooltip-content {
+ opacity: 1;
+ visibility: visible;
+}
+
+.tooltip-arrow {
+ position: absolute;
+ top: 100%;
+ left: 50%;
+ transform: translateX(-50%);
+ border-width: 8px;
+ border-style: solid;
+ border-color: #333 transparent transparent transparent;
+}
+
diff --git a/website/client/components/Home/TryItPackOptions.vue b/website/client/components/Home/TryItPackOptions.vue
index 9795ed8b3..dfebb2403 100644
--- a/website/client/components/Home/TryItPackOptions.vue
+++ b/website/client/components/Home/TryItPackOptions.vue
@@ -144,6 +144,7 @@ function handleCompressToggle(enabled: boolean) {
+
Output Format Options
@@ -436,4 +437,5 @@ function handleCompressToggle(enabled: boolean) {
outline: none;
border-color: var(--vp-c-brand-1);
}
+
diff --git a/website/client/composables/usePackOptions.ts b/website/client/composables/usePackOptions.ts
index c8bc9d195..59c6edf65 100644
--- a/website/client/composables/usePackOptions.ts
+++ b/website/client/composables/usePackOptions.ts
@@ -1,4 +1,5 @@
import { computed, reactive } from 'vue';
+import { parseUrlParameters } from '../utils/urlParams';
export interface PackOptions {
format: 'xml' | 'markdown' | 'plain';
@@ -27,7 +28,19 @@ const DEFAULT_PACK_OPTIONS: PackOptions = {
};
export function usePackOptions() {
- const packOptions = reactive
({ ...DEFAULT_PACK_OPTIONS });
+ // Initialize with URL parameters if available
+ const urlParams = parseUrlParameters();
+ const initialOptions = { ...DEFAULT_PACK_OPTIONS };
+
+ // Apply URL parameters to initial options
+ for (const key of Object.keys(initialOptions) as Array) {
+ if (key in urlParams && urlParams[key] !== undefined) {
+ // Type-safe assignment: only assign if the key is a valid PackOptions key
+ initialOptions[key] = urlParams[key] as PackOptions[typeof key];
+ }
+ }
+
+ const packOptions = reactive(initialOptions);
const getPackRequestOptions = computed(() => ({
removeComments: packOptions.removeComments,
@@ -54,5 +67,6 @@ export function usePackOptions() {
getPackRequestOptions,
updateOption,
resetOptions,
+ DEFAULT_PACK_OPTIONS,
};
}
diff --git a/website/client/composables/usePackRequest.ts b/website/client/composables/usePackRequest.ts
index 242906837..6283cdca7 100644
--- a/website/client/composables/usePackRequest.ts
+++ b/website/client/composables/usePackRequest.ts
@@ -2,16 +2,20 @@ import { computed, ref } from 'vue';
import type { PackResult } from '../components/api/client';
import { handlePackRequest } from '../components/utils/requestHandlers';
import { isValidRemoteValue } from '../components/utils/validation';
+import { parseUrlParameters } from '../utils/urlParams';
import { usePackOptions } from './usePackOptions';
export type InputMode = 'url' | 'file' | 'folder';
export function usePackRequest() {
const packOptionsComposable = usePackOptions();
- const { packOptions, getPackRequestOptions } = packOptionsComposable;
+ const { packOptions, getPackRequestOptions, resetOptions, DEFAULT_PACK_OPTIONS } = packOptionsComposable;
+
+ // Initialize with URL parameters if available
+ const urlParams = parseUrlParameters();
// Input states
- const inputUrl = ref('');
+ const inputUrl = ref(urlParams.repo || '');
const inputRepositoryUrl = ref('');
const mode = ref('url');
const uploadedFile = ref(null);
@@ -130,5 +134,9 @@ export function usePackRequest() {
resetRequest,
submitRequest,
cancelRequest,
+
+ // Pack option actions
+ resetOptions,
+ DEFAULT_PACK_OPTIONS,
};
}
diff --git a/website/client/utils/urlParams.ts b/website/client/utils/urlParams.ts
new file mode 100644
index 000000000..6649738e2
--- /dev/null
+++ b/website/client/utils/urlParams.ts
@@ -0,0 +1,206 @@
+import type { PackOptions } from '../composables/usePackOptions';
+
+// URL parameter constants
+const BOOLEAN_PARAMS = [
+ 'removeComments',
+ 'removeEmptyLines',
+ 'showLineNumbers',
+ 'fileSummary',
+ 'directoryStructure',
+ 'outputParsable',
+ 'compress',
+] as const;
+
+const VALID_FORMATS = ['xml', 'markdown', 'plain'] as const;
+
+const URL_PARAM_KEYS = ['repo', 'format', 'style', 'include', 'ignore', ...BOOLEAN_PARAMS] as const;
+
+// Key mapping for internal names to URL parameter names
+const KEY_MAPPING: Record = {
+ includePatterns: 'include',
+ ignorePatterns: 'ignore',
+};
+
+// Helper function to get URL parameter key from internal key
+function getUrlParamKey(internalKey: string): string {
+ return KEY_MAPPING[internalKey] || internalKey;
+}
+
+// Helper function to validate URL parameter values
+export function validateUrlParameters(params: Record): { isValid: boolean; errors: string[] } {
+ const errors: string[] = [];
+
+ // Validate format parameter
+ if (params.format && !(VALID_FORMATS as readonly string[]).includes(params.format as string)) {
+ errors.push(`Invalid format: ${params.format}. Must be one of: ${VALID_FORMATS.join(', ')}`);
+ }
+
+ // Validate URL length to prevent browser limit issues
+ const urlSearchParams = new URLSearchParams();
+ for (const [key, value] of Object.entries(params)) {
+ if (value !== undefined && value !== null) {
+ urlSearchParams.set(key, String(value));
+ }
+ }
+
+ const maxUrlLength = 2000; // Conservative limit for browser compatibility
+ if (urlSearchParams.toString().length > maxUrlLength) {
+ errors.push(
+ `URL parameters too long (${urlSearchParams.toString().length} chars). Maximum allowed: ${maxUrlLength}`,
+ );
+ }
+
+ return {
+ isValid: errors.length === 0,
+ errors,
+ };
+}
+
+// Helper function to check if any options differ from defaults
+export function hasNonDefaultValues(
+ inputUrl: string,
+ packOptions: Record,
+ defaultOptions: Record,
+): boolean {
+ // Check if there's input URL
+ if (inputUrl && inputUrl.trim() !== '') {
+ return true;
+ }
+
+ // Check if any pack option differs from default
+ for (const [key, value] of Object.entries(packOptions)) {
+ const defaultValue = defaultOptions[key];
+ if (typeof value === 'string' && typeof defaultValue === 'string') {
+ if (value.trim() !== defaultValue.trim()) {
+ return true;
+ }
+ } else if (value !== defaultValue) {
+ return true;
+ }
+ }
+
+ return false;
+}
+
+/**
+ * Parses URL query parameters and returns pack options and repository URL.
+ * Supports backward compatibility with 'style' parameter as alias for 'format'.
+ *
+ * @returns Parsed options object with repository URL and pack options
+ */
+export function parseUrlParameters(): Partial {
+ if (typeof window === 'undefined') {
+ return {};
+ }
+
+ const urlParams = new URLSearchParams(window.location.search);
+ const params: Partial = {};
+
+ // Repository URL parameter
+ const repo = urlParams.get('repo');
+ if (repo) {
+ params.repo = repo.trim();
+ }
+
+ // Format and Style parameters (with conflict handling)
+ const format = urlParams.get('format');
+ const style = urlParams.get('style');
+
+ if (
+ format &&
+ (VALID_FORMATS as readonly string[]).includes(format) &&
+ style &&
+ (VALID_FORMATS as readonly string[]).includes(style)
+ ) {
+ // Both present: prefer format, warn about conflict
+ params.format = format as (typeof VALID_FORMATS)[number];
+ console.warn(
+ `Both 'format' and 'style' URL parameters are present. Using 'format=${format}' and ignoring 'style=${style}'.`,
+ );
+ } else if (format && (VALID_FORMATS as readonly string[]).includes(format)) {
+ params.format = format as (typeof VALID_FORMATS)[number];
+ } else if (style && (VALID_FORMATS as readonly string[]).includes(style)) {
+ params.format = style as (typeof VALID_FORMATS)[number];
+ }
+
+ // Include patterns
+ const include = urlParams.get('include');
+ if (include) {
+ params.includePatterns = include;
+ }
+
+ // Ignore patterns
+ const ignore = urlParams.get('ignore');
+ if (ignore) {
+ params.ignorePatterns = ignore;
+ }
+
+ // Boolean parameters
+ for (const param of BOOLEAN_PARAMS) {
+ const value = urlParams.get(param);
+ if (value !== null) {
+ // Accept various truthy values: true, 1, yes, on
+ params[param] = ['true', '1', 'yes', 'on'].includes(value.toLowerCase());
+ }
+ }
+
+ return params;
+}
+
+/**
+ * Updates URL query parameters with the provided options.
+ * Validates parameters and provides error handling for URL length limits.
+ *
+ * @param options - Pack options and repository URL to set in URL
+ * @returns Result object with success status and optional error message
+ */
+export function updateUrlParameters(options: Partial): {
+ success: boolean;
+ error?: string;
+} {
+ if (typeof window === 'undefined') {
+ return { success: false, error: 'Window object not available (SSR environment)' };
+ }
+
+ try {
+ // Validate parameters before updating URL
+ const validation = validateUrlParameters(options);
+ if (!validation.isValid) {
+ // If validation fails due to URL length, return error instead of continuing
+ const hasLengthError = validation.errors.some((error) => error.includes('too long'));
+ if (hasLengthError) {
+ return { success: false, error: validation.errors.join('; ') };
+ }
+ // For other validation errors, just warn and continue
+ console.warn('URL parameter validation failed:', validation.errors);
+ }
+
+ const url = new URL(window.location.href);
+ const params = url.searchParams;
+
+ // Clear existing repomix-related parameters
+ for (const key of URL_PARAM_KEYS) {
+ params.delete(key);
+ }
+
+ // Add new parameters
+ for (const [key, value] of Object.entries(options)) {
+ if (value !== undefined && value !== null) {
+ const urlParamKey = getUrlParamKey(key);
+ if (typeof value === 'boolean') {
+ params.set(urlParamKey, value.toString());
+ } else if (typeof value === 'string' && value.trim() !== '') {
+ params.set(urlParamKey, value.trim());
+ }
+ }
+ }
+
+ // Update URL without reloading the page
+ window.history.replaceState({}, '', url.toString());
+ return { success: true };
+ } catch (error) {
+ const errorMessage = error instanceof Error ? error.message : 'Unknown error occurred while updating URL';
+ console.error('Failed to update URL parameters:', errorMessage);
+ return { success: false, error: errorMessage };
+ }
+}