full PC config setup
shifting towards manual symlinks for more complex setup than laptop, allows for GUI settings changes to actually apply, and is much easier than nix-ifying every config
This commit is contained in:
149 files changed
+9539
-478
No files matched your search
+561
@@ -0,0 +1,561 @@
|
||||
body {
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
font-family: var(--vscode-font-family);
|
||||
color: var(--vscode-foreground);
|
||||
}
|
||||
|
||||
.container {
|
||||
display: flex;
|
||||
height: 100vh;
|
||||
gap: 0;
|
||||
background: var(--vscode-panel-border);
|
||||
position: relative;
|
||||
}
|
||||
|
||||
/* Left panel - Pattern list */
|
||||
.pattern-list-panel {
|
||||
flex: 0 0 300px;
|
||||
min-width: 200px;
|
||||
max-width: 600px;
|
||||
background: var(--vscode-sideBar-background);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
/* Resize Handle */
|
||||
.resize-handle {
|
||||
flex: 0 0 4px;
|
||||
background: var(--vscode-panel-border);
|
||||
cursor: ew-resize;
|
||||
position: relative;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.resize-handle:hover {
|
||||
background: var(--vscode-focusBorder);
|
||||
}
|
||||
|
||||
.resize-handle.resizing {
|
||||
background: var(--vscode-focusBorder);
|
||||
}
|
||||
|
||||
.search-box {
|
||||
padding: 8px;
|
||||
background: var(--vscode-sideBar-background);
|
||||
border-bottom: 1px solid var(--vscode-panel-border);
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.search-wrapper {
|
||||
position: relative;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.search-icon {
|
||||
position: absolute;
|
||||
left: 8px;
|
||||
pointer-events: none;
|
||||
color: var(--vscode-input-placeholderForeground);
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.search-input {
|
||||
width: 100%;
|
||||
padding: 6px 30px 6px 30px;
|
||||
background: var(--vscode-input-background);
|
||||
color: var(--vscode-input-foreground);
|
||||
border: 1px solid var(--vscode-input-border);
|
||||
border-radius: 2px;
|
||||
font-size: 13px;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.search-input:focus {
|
||||
outline: none;
|
||||
border-color: var(--vscode-focusBorder);
|
||||
}
|
||||
|
||||
.search-clear {
|
||||
position: absolute;
|
||||
right: 6px;
|
||||
padding: 4px;
|
||||
background: transparent;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
color: var(--vscode-input-placeholderForeground);
|
||||
border-radius: 3px;
|
||||
display: none;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
transition: all 0.15s ease;
|
||||
}
|
||||
|
||||
.search-clear:hover {
|
||||
background: var(--vscode-toolbar-hoverBackground);
|
||||
color: var(--vscode-foreground);
|
||||
}
|
||||
|
||||
.search-clear.visible {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.search-clear .codicon {
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.pattern-list {
|
||||
flex: 1;
|
||||
overflow-y: auto;
|
||||
padding: 4px;
|
||||
}
|
||||
|
||||
.section-header {
|
||||
padding: 8px 12px;
|
||||
font-weight: 600;
|
||||
font-size: 11px;
|
||||
text-transform: uppercase;
|
||||
color: var(--vscode-foreground);
|
||||
opacity: 0.6;
|
||||
letter-spacing: 0.5px;
|
||||
margin-top: 8px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.section-header-left {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
cursor: pointer;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.add-pattern-btn {
|
||||
padding: 4px;
|
||||
background: transparent;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
color: var(--vscode-foreground);
|
||||
opacity: 0.6;
|
||||
border-radius: 3px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
transition: all 0.15s ease;
|
||||
}
|
||||
|
||||
.add-pattern-btn:hover {
|
||||
background: var(--vscode-toolbar-hoverBackground);
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.add-pattern-btn .codicon {
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.section-header:hover {
|
||||
opacity: 0.8;
|
||||
}
|
||||
|
||||
.section-header:first-child {
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
.section-header .codicon {
|
||||
font-size: 14px;
|
||||
transition: transform 0.1s ease;
|
||||
}
|
||||
|
||||
.section-header.collapsed .codicon {
|
||||
transform: rotate(-90deg);
|
||||
}
|
||||
|
||||
.section-content {
|
||||
max-height: 10000px;
|
||||
overflow: hidden;
|
||||
transition: max-height 0.15s ease;
|
||||
}
|
||||
|
||||
.section-content.collapsed {
|
||||
max-height: 0;
|
||||
}
|
||||
|
||||
.empty-state {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 48px 24px;
|
||||
color: var(--vscode-descriptionForeground);
|
||||
text-align: center;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.empty-state-section {
|
||||
padding: 16px;
|
||||
text-align: center;
|
||||
color: var(--vscode-descriptionForeground);
|
||||
font-size: 13px;
|
||||
opacity: 0.8;
|
||||
}
|
||||
|
||||
.pattern-item {
|
||||
padding: 8px 12px;
|
||||
cursor: pointer;
|
||||
border-radius: 3px;
|
||||
margin-bottom: 2px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.pattern-item:hover {
|
||||
background: var(--vscode-list-hoverBackground);
|
||||
}
|
||||
|
||||
.pattern-item:hover .pattern-actions {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.pattern-item.selected {
|
||||
background: var(--vscode-list-activeSelectionBackground);
|
||||
color: var(--vscode-list-activeSelectionForeground);
|
||||
}
|
||||
|
||||
.pattern-info {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.pattern-name {
|
||||
font-weight: 500;
|
||||
margin-bottom: 2px;
|
||||
}
|
||||
|
||||
.pattern-preview {
|
||||
font-size: 12px;
|
||||
opacity: 0.8;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.pattern-item-invalid {
|
||||
border-left: 2px solid var(--vscode-errorForeground);
|
||||
opacity: 0.8;
|
||||
}
|
||||
|
||||
.pattern-item-invalid .pattern-name .codicon-warning {
|
||||
color: var(--vscode-errorForeground);
|
||||
margin-left: 4px;
|
||||
font-size: 13px;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
.pattern-item-invalid .pattern-preview {
|
||||
color: var(--vscode-errorForeground);
|
||||
}
|
||||
|
||||
.pattern-item-invalid .pattern-preview em {
|
||||
font-style: italic;
|
||||
opacity: 0.7;
|
||||
}
|
||||
|
||||
/* Action buttons */
|
||||
.pattern-actions {
|
||||
display: flex;
|
||||
gap: 4px;
|
||||
opacity: 0;
|
||||
transition: opacity 0.15s ease;
|
||||
}
|
||||
|
||||
.action-btn {
|
||||
padding: 4px;
|
||||
background: transparent;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
color: var(--vscode-foreground);
|
||||
opacity: 0.7;
|
||||
border-radius: 3px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
}
|
||||
|
||||
.action-btn:hover {
|
||||
background: var(--vscode-toolbar-hoverBackground);
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.action-btn .codicon {
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.action-btn.delete:hover {
|
||||
color: var(--vscode-errorForeground);
|
||||
}
|
||||
|
||||
.action-btn.load:hover {
|
||||
color: var(--vscode-textLink-foreground);
|
||||
}
|
||||
|
||||
/* Right panel - Edit form */
|
||||
.edit-panel {
|
||||
flex: 1;
|
||||
background: var(--vscode-editor-background);
|
||||
overflow-y: auto;
|
||||
padding: 16px;
|
||||
}
|
||||
|
||||
.edit-empty-state {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
height: 100%;
|
||||
opacity: 0.5;
|
||||
text-align: center;
|
||||
padding: 40px 20px;
|
||||
}
|
||||
|
||||
.edit-empty-state .codicon {
|
||||
font-size: 64px;
|
||||
margin-bottom: 16px;
|
||||
opacity: 0.6;
|
||||
}
|
||||
|
||||
.edit-empty-state p {
|
||||
margin: 4px 0;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.edit-empty-state .hint {
|
||||
font-size: 12px;
|
||||
opacity: 0.7;
|
||||
}
|
||||
|
||||
.edit-form {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.edit-form.active {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.edit-form-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.edit-form h2 {
|
||||
font-size: 13px;
|
||||
font-weight: 600;
|
||||
margin: 0;
|
||||
text-transform: uppercase;
|
||||
opacity: 0.8;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.form-input-inline {
|
||||
flex: 1;
|
||||
padding: 4px 6px;
|
||||
background: var(--vscode-input-background);
|
||||
color: var(--vscode-input-foreground);
|
||||
border: 1px solid var(--vscode-input-border);
|
||||
border-radius: 2px;
|
||||
font-size: 13px;
|
||||
font-family: var(--vscode-editor-font-family);
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.form-input-inline:focus {
|
||||
outline: none;
|
||||
border-color: var(--vscode-focusBorder);
|
||||
}
|
||||
|
||||
.form-group {
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.form-label {
|
||||
display: block;
|
||||
margin-bottom: 3px;
|
||||
font-weight: 500;
|
||||
font-size: 11px;
|
||||
opacity: 0.8;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
.form-label-with-options {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
margin-bottom: 3px;
|
||||
}
|
||||
|
||||
.form-label-with-options .form-label {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.form-input,
|
||||
.form-textarea {
|
||||
width: 100%;
|
||||
padding: 4px 6px;
|
||||
background: var(--vscode-input-background);
|
||||
color: var(--vscode-input-foreground);
|
||||
border: 1px solid var(--vscode-input-border);
|
||||
border-radius: 2px;
|
||||
font-size: 13px;
|
||||
font-family: var(--vscode-editor-font-family);
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.form-input:focus,
|
||||
.form-textarea:focus {
|
||||
outline: none;
|
||||
border-color: var(--vscode-focusBorder);
|
||||
}
|
||||
|
||||
.form-input.error,
|
||||
.form-textarea.error {
|
||||
border-color: var(--vscode-errorForeground);
|
||||
}
|
||||
|
||||
.form-input.dirty,
|
||||
.form-input-inline.dirty,
|
||||
.form-textarea.dirty {
|
||||
box-shadow: 0 0 0 1px rgba(106, 153, 85, 0.6);
|
||||
}
|
||||
|
||||
.form-textarea {
|
||||
min-height: 60px;
|
||||
resize: vertical;
|
||||
font-family: var(--vscode-editor-font-family);
|
||||
}
|
||||
|
||||
.validation-error {
|
||||
display: none;
|
||||
color: var(--vscode-errorForeground);
|
||||
font-size: 11px;
|
||||
margin-top: 3px;
|
||||
}
|
||||
|
||||
.validation-error.visible {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.checkbox-group-icons {
|
||||
display: flex;
|
||||
gap: 4px;
|
||||
}
|
||||
|
||||
.toggle-btn {
|
||||
padding: 4px 8px;
|
||||
background: var(--vscode-button-secondaryBackground);
|
||||
color: var(--vscode-button-secondaryForeground);
|
||||
border: 1px solid transparent;
|
||||
border-radius: 2px;
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
transition: all 0.15s ease;
|
||||
min-width: 28px;
|
||||
height: 24px;
|
||||
}
|
||||
|
||||
.toggle-btn:hover {
|
||||
background: var(--vscode-button-secondaryHoverBackground);
|
||||
}
|
||||
|
||||
.toggle-btn.active {
|
||||
background: var(--vscode-button-background);
|
||||
color: var(--vscode-button-foreground);
|
||||
border-color: var(--vscode-focusBorder);
|
||||
}
|
||||
|
||||
.toggle-btn.dirty {
|
||||
border-color: #6a9955;
|
||||
}
|
||||
|
||||
.toggle-btn.active.dirty {
|
||||
background: var(--vscode-button-background);
|
||||
color: var(--vscode-button-foreground);
|
||||
border-color: #6a9955;
|
||||
}
|
||||
|
||||
.toggle-btn .codicon {
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.button-group {
|
||||
display: flex;
|
||||
gap: 6px;
|
||||
margin-top: 16px;
|
||||
padding-top: 12px;
|
||||
border-top: 1px solid var(--vscode-panel-border);
|
||||
}
|
||||
|
||||
button {
|
||||
padding: 5px 12px;
|
||||
background: var(--vscode-button-background);
|
||||
color: var(--vscode-button-foreground);
|
||||
border: none;
|
||||
border-radius: 2px;
|
||||
cursor: pointer;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
button:hover {
|
||||
background: var(--vscode-button-hoverBackground);
|
||||
}
|
||||
|
||||
button.secondary {
|
||||
background: var(--vscode-button-secondaryBackground);
|
||||
color: var(--vscode-button-secondaryForeground);
|
||||
}
|
||||
|
||||
button.secondary:hover {
|
||||
background: var(--vscode-button-secondaryHoverBackground);
|
||||
}
|
||||
|
||||
.dirty-indicator {
|
||||
margin-left: 6px;
|
||||
color: var(--vscode-editorWarning-foreground);
|
||||
font-size: 8px;
|
||||
animation: pulse 2s ease-in-out infinite;
|
||||
width: 8px;
|
||||
display: inline-block;
|
||||
visibility: hidden;
|
||||
}
|
||||
|
||||
@keyframes pulse {
|
||||
0%, 100% {
|
||||
opacity: 1;
|
||||
}
|
||||
50% {
|
||||
opacity: 0.5;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes pulseBorder {
|
||||
0%, 100% {
|
||||
border-color: #4ec9b0;
|
||||
}
|
||||
50% {
|
||||
border-color: #dcdcaa;
|
||||
}
|
||||
}
|
||||
+106
@@ -0,0 +1,106 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<meta http-equiv="Content-Security-Policy"
|
||||
content="default-src 'none'; style-src {{cspSource}} https://unpkg.com 'nonce-{{nonce}}'; font-src https://unpkg.com; script-src 'nonce-{{nonce}}';">
|
||||
<title>Manage Patterns</title>
|
||||
<link href="https://unpkg.com/@vscode/codicons/dist/codicon.css" rel="stylesheet" />
|
||||
{{styleTag}}
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div class="container">
|
||||
<!-- Left: Pattern List -->
|
||||
<div class="pattern-list-panel">
|
||||
<div class="search-box">
|
||||
<div class="search-wrapper">
|
||||
<i class="codicon codicon-search search-icon"></i>
|
||||
<input type="text" class="search-input" placeholder="Search patterns..." id="searchInput" />
|
||||
<button class="search-clear" id="searchClear" title="Clear search">
|
||||
<i class="codicon codicon-close"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="pattern-list" id="patternList">
|
||||
<!-- Patterns will be inserted here -->
|
||||
<div class="empty-state">No patterns yet</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Resize Handle -->
|
||||
<div class="resize-handle" id="resizeHandle"></div>
|
||||
|
||||
<div class="edit-panel" id="editPanel">
|
||||
<div class="edit-empty-state" id="editEmptyState">
|
||||
<i class="codicon codicon-list-selection"></i>
|
||||
<p>Select a pattern to edit</p>
|
||||
<p class="hint">or create a new one with the + button</p>
|
||||
</div>
|
||||
|
||||
<div class="edit-form" id="editForm">
|
||||
<div class="edit-form-header">
|
||||
<h2>Pattern</h2>
|
||||
<input type="text" class="form-input-inline" id="labelInput" placeholder="Pattern name" />
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<div class="form-label-with-options">
|
||||
<label class="form-label" for="findInput">Find</label>
|
||||
<div class="checkbox-group-icons">
|
||||
|
||||
<button type="button" class="toggle-btn" id="isCaseSensitiveBtn" title="Match Case">
|
||||
<i class="codicon codicon-case-sensitive"></i>
|
||||
</button>
|
||||
<button type="button" class="toggle-btn" id="matchWholeWordBtn" title="Match Whole Word">
|
||||
<i class="codicon codicon-whole-word"></i>
|
||||
</button>
|
||||
<button type="button" class="toggle-btn" id="isRegexBtn" title="Use Regular Expression">
|
||||
<i class="codicon codicon-regex"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<textarea class="form-textarea" id="findInput" placeholder="Search pattern or regex"></textarea>
|
||||
<div class="validation-error" id="findValidationError"></div>
|
||||
<!-- Hidden checkboxes to maintain state -->
|
||||
<input type="checkbox" id="isCaseSensitive" hidden />
|
||||
<input type="checkbox" id="matchWholeWord" hidden />
|
||||
<input type="checkbox" id="isRegex" hidden />
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label class="form-label" for="replaceInput">Replace (optional)</label>
|
||||
<textarea class="form-textarea" id="replaceInput" placeholder="Replacement text"></textarea>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label class="form-label" for="filesToInclude">Files to Include</label>
|
||||
<input type="text" class="form-input" id="filesToInclude" placeholder="*.ts,*.js or src/**" />
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label class="form-label" for="filesToExclude">Files to Exclude</label>
|
||||
<input type="text" class="form-input" id="filesToExclude" placeholder="node_modules/**,dist/**" />
|
||||
</div>
|
||||
|
||||
<div class="button-group">
|
||||
<button id="saveBtn">
|
||||
Save
|
||||
<span id="dirtyIndicator" class="dirty-indicator">
|
||||
<i class="codicon codicon-circle-filled"></i>
|
||||
</span>
|
||||
</button>
|
||||
<button id="loadBtn" class="secondary">
|
||||
<i class="codicon codicon-arrow-right"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script nonce="{{nonce}}">{{script}}</script>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
+857
@@ -0,0 +1,857 @@
|
||||
const vscode = acquireVsCodeApi();
|
||||
|
||||
// Store patterns data
|
||||
let workspacePatterns = [];
|
||||
let userPatterns = [];
|
||||
let workspacePatternsMap = new Map(); // id -> pattern
|
||||
let userPatternsMap = new Map(); // id -> pattern
|
||||
let searchQuery = '';
|
||||
|
||||
// Dirty state tracking
|
||||
let savedPatternState = null;
|
||||
|
||||
// Cache DOM elements for performance
|
||||
const domCache = {
|
||||
labelInput: null,
|
||||
findInput: null,
|
||||
replaceInput: null,
|
||||
filesToInclude: null,
|
||||
filesToExclude: null,
|
||||
isRegex: null,
|
||||
isCaseSensitive: null,
|
||||
matchWholeWord: null,
|
||||
isRegexBtn: null,
|
||||
isCaseSensitiveBtn: null,
|
||||
matchWholeWordBtn: null,
|
||||
dirtyIndicator: null,
|
||||
patternList: null,
|
||||
editForm: null,
|
||||
editEmptyState: null,
|
||||
findValidationError: null
|
||||
};
|
||||
|
||||
function initDomCache() {
|
||||
domCache.labelInput = document.getElementById('labelInput');
|
||||
domCache.findInput = document.getElementById('findInput');
|
||||
domCache.replaceInput = document.getElementById('replaceInput');
|
||||
domCache.filesToInclude = document.getElementById('filesToInclude');
|
||||
domCache.filesToExclude = document.getElementById('filesToExclude');
|
||||
domCache.isRegex = document.getElementById('isRegex');
|
||||
domCache.isCaseSensitive = document.getElementById('isCaseSensitive');
|
||||
domCache.matchWholeWord = document.getElementById('matchWholeWord');
|
||||
domCache.isRegexBtn = document.getElementById('isRegexBtn');
|
||||
domCache.isCaseSensitiveBtn = document.getElementById('isCaseSensitiveBtn');
|
||||
domCache.matchWholeWordBtn = document.getElementById('matchWholeWordBtn');
|
||||
domCache.dirtyIndicator = document.getElementById('dirtyIndicator');
|
||||
domCache.patternList = document.getElementById('patternList');
|
||||
domCache.editForm = document.getElementById('editForm');
|
||||
domCache.editEmptyState = document.getElementById('editEmptyState');
|
||||
domCache.findValidationError = document.getElementById('findValidationError');
|
||||
}
|
||||
|
||||
function checkIfDirty() {
|
||||
const state = vscode.getState() || {};
|
||||
const currentPattern = state.currentPattern;
|
||||
|
||||
if (!currentPattern || !savedPatternState) {
|
||||
updateDirtyIndicator(false);
|
||||
clearAllDirtyClasses();
|
||||
return;
|
||||
}
|
||||
|
||||
// Compare current form values with saved state
|
||||
const isDirty =
|
||||
domCache.labelInput.value.trim() !== savedPatternState.label ||
|
||||
domCache.findInput.value !== savedPatternState.find ||
|
||||
domCache.replaceInput.value !== savedPatternState.replace ||
|
||||
domCache.isRegex.checked !== savedPatternState.flags.isRegex ||
|
||||
domCache.isCaseSensitive.checked !== savedPatternState.flags.isCaseSensitive ||
|
||||
domCache.matchWholeWord.checked !== savedPatternState.flags.matchWholeWord ||
|
||||
(domCache.filesToInclude.value.trim() || '') !== (savedPatternState.filesToInclude || '') ||
|
||||
(domCache.filesToExclude.value.trim() || '') !== (savedPatternState.filesToExclude || '');
|
||||
|
||||
updateDirtyIndicator(isDirty);
|
||||
updateFieldDirtyStates();
|
||||
}
|
||||
|
||||
function updateDirtyIndicator(isDirty) {
|
||||
if (domCache.dirtyIndicator) {
|
||||
domCache.dirtyIndicator.style.visibility = isDirty ? 'visible' : 'hidden';
|
||||
}
|
||||
}
|
||||
|
||||
function updateFieldDirtyStates() {
|
||||
if (!savedPatternState) {
|
||||
clearAllDirtyClasses();
|
||||
return;
|
||||
}
|
||||
|
||||
// Check text inputs using cached DOM references
|
||||
if (domCache.labelInput) {
|
||||
toggleDirtyClass(domCache.labelInput, domCache.labelInput.value.trim() !== savedPatternState.label);
|
||||
}
|
||||
if (domCache.findInput) {
|
||||
toggleDirtyClass(domCache.findInput, domCache.findInput.value !== savedPatternState.find);
|
||||
}
|
||||
if (domCache.replaceInput) {
|
||||
toggleDirtyClass(domCache.replaceInput, domCache.replaceInput.value !== savedPatternState.replace);
|
||||
}
|
||||
if (domCache.filesToInclude) {
|
||||
toggleDirtyClass(domCache.filesToInclude, (domCache.filesToInclude.value.trim() || '') !== (savedPatternState.filesToInclude || ''));
|
||||
}
|
||||
if (domCache.filesToExclude) {
|
||||
toggleDirtyClass(domCache.filesToExclude, (domCache.filesToExclude.value.trim() || '') !== (savedPatternState.filesToExclude || ''));
|
||||
}
|
||||
|
||||
// Check toggle buttons using cached DOM references
|
||||
if (domCache.isRegexBtn && domCache.isRegex) {
|
||||
toggleDirtyClass(domCache.isRegexBtn, domCache.isRegex.checked !== savedPatternState.flags.isRegex);
|
||||
}
|
||||
if (domCache.isCaseSensitiveBtn && domCache.isCaseSensitive) {
|
||||
toggleDirtyClass(domCache.isCaseSensitiveBtn, domCache.isCaseSensitive.checked !== savedPatternState.flags.isCaseSensitive);
|
||||
}
|
||||
if (domCache.matchWholeWordBtn && domCache.matchWholeWord) {
|
||||
toggleDirtyClass(domCache.matchWholeWordBtn, domCache.matchWholeWord.checked !== savedPatternState.flags.matchWholeWord);
|
||||
}
|
||||
}
|
||||
|
||||
function toggleDirtyClass(element, isDirty) {
|
||||
if (isDirty) {
|
||||
element.classList.add('dirty');
|
||||
} else {
|
||||
element.classList.remove('dirty');
|
||||
}
|
||||
}
|
||||
|
||||
function clearAllDirtyClasses() {
|
||||
const elements = [
|
||||
domCache.labelInput, domCache.findInput, domCache.replaceInput,
|
||||
domCache.filesToInclude, domCache.filesToExclude,
|
||||
domCache.isRegexBtn, domCache.isCaseSensitiveBtn, domCache.matchWholeWordBtn
|
||||
];
|
||||
|
||||
elements.forEach(element => {
|
||||
if (element) {
|
||||
element.classList.remove('dirty');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function saveDirtyState(pattern) {
|
||||
savedPatternState = {
|
||||
label: pattern.label,
|
||||
find: pattern.find || '',
|
||||
replace: pattern.replace || '',
|
||||
flags: { ...pattern.flags },
|
||||
filesToInclude: pattern.filesToInclude || '',
|
||||
filesToExclude: pattern.filesToExclude || ''
|
||||
};
|
||||
updateDirtyIndicator(false);
|
||||
clearAllDirtyClasses();
|
||||
}
|
||||
|
||||
function findPatternById(id, scope) {
|
||||
const map = scope === 'workspace' ? workspacePatternsMap : userPatternsMap;
|
||||
return map.get(id);
|
||||
}
|
||||
|
||||
function rebuildPatternMaps() {
|
||||
workspacePatternsMap.clear();
|
||||
userPatternsMap.clear();
|
||||
|
||||
workspacePatterns.forEach(p => workspacePatternsMap.set(p.id, p));
|
||||
userPatterns.forEach(p => userPatternsMap.set(p.id, p));
|
||||
}
|
||||
|
||||
// Get collapse state from localStorage
|
||||
const getCollapseState = (section) => {
|
||||
const state = vscode.getState() || {};
|
||||
return state[section + '_collapsed'] === true;
|
||||
};
|
||||
|
||||
// Get collapse state with a default value if not previously set
|
||||
const getCollapseStateWithDefault = (section, defaultValue) => {
|
||||
const state = vscode.getState() || {};
|
||||
const key = section + '_collapsed';
|
||||
// If user has explicitly set a preference, use it; otherwise use default
|
||||
return state[key] !== undefined ? state[key] : defaultValue;
|
||||
};
|
||||
|
||||
// Save collapse state to localStorage
|
||||
const setCollapseState = (section, collapsed) => {
|
||||
const state = vscode.getState() || {};
|
||||
state[section + '_collapsed'] = collapsed;
|
||||
vscode.setState(state);
|
||||
};
|
||||
|
||||
window.addEventListener('message', event => {
|
||||
const message = event.data;
|
||||
|
||||
if (message.type === 'patterns') {
|
||||
|
||||
disablePatternList(false);
|
||||
document.body.style.cursor = '';
|
||||
|
||||
workspacePatterns = message.workspace;
|
||||
userPatterns = message.user;
|
||||
rebuildPatternMaps(); // Rebuild Maps for O(1) lookups
|
||||
renderPatternList();
|
||||
setupSearchInput();
|
||||
|
||||
// Force clear selection if requested (e.g., on external config changes)
|
||||
if (message.clearSelection) {
|
||||
clearDetailsView();
|
||||
} else if (message.selectPattern) {
|
||||
selectPattern(message.selectPattern.id, message.selectPattern.scope);
|
||||
} else {
|
||||
const state = vscode.getState() || {};
|
||||
if (state.currentPattern) {
|
||||
const allPatterns = state.currentPattern.scope === 'workspace' ? workspacePatterns : userPatterns;
|
||||
const stillExists = allPatterns.some(p => p.id === state.currentPattern.id);
|
||||
|
||||
if (!stillExists) {
|
||||
clearDetailsView();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Focus search input when webview opens
|
||||
const searchInput = document.getElementById('searchInput');
|
||||
if (searchInput) {
|
||||
searchInput.focus();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
function setupSearchInput() {
|
||||
const searchInput = document.getElementById('searchInput');
|
||||
const searchClear = document.getElementById('searchClear');
|
||||
|
||||
if (!searchInput || !searchClear) return;
|
||||
|
||||
// if (searchInput.dataset.initialized === 'true') {
|
||||
// return;
|
||||
// }
|
||||
|
||||
// searchInput.dataset.initialized = 'true';
|
||||
|
||||
searchInput.addEventListener('input', (e) => {
|
||||
searchQuery = e.target.value.toLowerCase();
|
||||
|
||||
if (searchQuery) {
|
||||
searchClear.classList.add('visible');
|
||||
} else {
|
||||
searchClear.classList.remove('visible');
|
||||
}
|
||||
|
||||
renderPatternList();
|
||||
});
|
||||
|
||||
searchClear.addEventListener('click', () => {
|
||||
searchInput.value = '';
|
||||
searchQuery = '';
|
||||
searchClear.classList.remove('visible');
|
||||
renderPatternList();
|
||||
searchInput.focus();
|
||||
});
|
||||
|
||||
searchInput.addEventListener('keydown', (e) => {
|
||||
if (e.key === 'Escape' && searchQuery) {
|
||||
searchInput.value = '';
|
||||
searchQuery = '';
|
||||
searchClear.classList.remove('visible');
|
||||
renderPatternList();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function filterPatterns(patterns) {
|
||||
if (!searchQuery) return patterns;
|
||||
|
||||
return patterns.filter(pattern => {
|
||||
const nameMatch = pattern.label.toLowerCase().includes(searchQuery);
|
||||
const findMatch = pattern.find.toLowerCase().includes(searchQuery);
|
||||
const replaceMatch = pattern.replace && pattern.replace.toLowerCase().includes(searchQuery);
|
||||
return nameMatch || findMatch || replaceMatch;
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
function renderPatternList() {
|
||||
const listContainer = document.getElementById('patternList');
|
||||
|
||||
const filteredWorkspace = filterPatterns(workspacePatterns);
|
||||
const filteredUser = filterPatterns(userPatterns);
|
||||
let html = '';
|
||||
|
||||
if (searchQuery && filteredWorkspace.length === 0 && filteredUser.length === 0) {
|
||||
listContainer.innerHTML = '<div class="empty-state">No patterns found</div>';
|
||||
return;
|
||||
}
|
||||
|
||||
// Collapse empty sections by default, but respect user's saved preference
|
||||
const workspaceCollapsed = searchQuery ? false : getCollapseStateWithDefault('workspace', filteredWorkspace.length === 0);
|
||||
html += renderSection('workspace', 'Workspace', filteredWorkspace, workspaceCollapsed);
|
||||
|
||||
const userCollapsed = searchQuery ? false : getCollapseStateWithDefault('user', filteredUser.length === 0);
|
||||
html += renderSection('user', 'User', filteredUser, userCollapsed);
|
||||
|
||||
listContainer.innerHTML = html;
|
||||
|
||||
document.querySelectorAll('.section-header').forEach(header => {
|
||||
header.addEventListener('click', toggleSection);
|
||||
});
|
||||
|
||||
// Event delegation handles all pattern list interactions, no need to re-attach
|
||||
}
|
||||
|
||||
|
||||
function renderSection(id, title, patterns, collapsed) {
|
||||
const chevron = collapsed ? 'codicon-chevron-right' : 'codicon-chevron-down';
|
||||
const contentClass = collapsed ? 'section-content collapsed' : 'section-content';
|
||||
|
||||
let html = `
|
||||
<div class="section-header ${collapsed ? 'collapsed' : ''}" data-section="${id}">
|
||||
<div class="section-header-left">
|
||||
<i class="codicon ${chevron}"></i>
|
||||
<span>${title} (${patterns.length})</span>
|
||||
</div>
|
||||
<button class="add-pattern-btn" data-scope="${id}" title="Add new pattern">
|
||||
<i class="codicon codicon-add"></i>
|
||||
</button>
|
||||
</div>
|
||||
<div class="${contentClass}" data-section-content="${id}">
|
||||
`;
|
||||
|
||||
if (patterns.length === 0) {
|
||||
html += '<div class="empty-state-section">No patterns in this scope</div>';
|
||||
} else {
|
||||
patterns.forEach(pattern => {
|
||||
html += renderPatternItem(pattern);
|
||||
});
|
||||
}
|
||||
|
||||
html += '</div>';
|
||||
return html;
|
||||
}
|
||||
|
||||
|
||||
function toggleSection(event) {
|
||||
if (event.target.closest('.add-pattern-btn')) {
|
||||
return;
|
||||
}
|
||||
|
||||
const header = event.currentTarget;
|
||||
const sectionId = header.dataset.section;
|
||||
const content = document.querySelector('[data-section-content="' + sectionId + '"]');
|
||||
const chevron = header.querySelector('.codicon');
|
||||
|
||||
const isCollapsed = header.classList.contains('collapsed');
|
||||
|
||||
if (isCollapsed) {
|
||||
header.classList.remove('collapsed');
|
||||
content.classList.remove('collapsed');
|
||||
chevron.classList.remove('codicon-chevron-right');
|
||||
chevron.classList.add('codicon-chevron-down');
|
||||
setCollapseState(sectionId, false);
|
||||
} else {
|
||||
header.classList.add('collapsed');
|
||||
content.classList.add('collapsed');
|
||||
chevron.classList.remove('codicon-chevron-down');
|
||||
chevron.classList.add('codicon-chevron-right');
|
||||
setCollapseState(sectionId, true);
|
||||
}
|
||||
}
|
||||
|
||||
function renderPatternItem(pattern) {
|
||||
const preview = pattern.find.substring(0, 40);
|
||||
const truncated = pattern.find.length > 40 ? '...' : '';
|
||||
const isInvalid = !pattern.find || pattern.find.trim() === '';
|
||||
const invalidClass = isInvalid ? 'pattern-item-invalid' : '';
|
||||
|
||||
return `
|
||||
<div class="pattern-item ${invalidClass}" data-id="${pattern.id}" data-label="${pattern.label}" data-scope="${pattern.scope}"> <div class="pattern-info">
|
||||
<div class="pattern-name">
|
||||
${pattern.label}
|
||||
${isInvalid ? '<i class="codicon codicon-warning" title="Find field is empty"></i>' : ''}
|
||||
</div>
|
||||
<div class="pattern-preview">${isInvalid ? '<em>Empty find pattern</em>' : preview + truncated}</div>
|
||||
</div>
|
||||
<div class="pattern-actions">
|
||||
${renderActionButton('trash', 'delete', 'Delete pattern')}
|
||||
${renderActionButton('arrow-right', 'load', 'Load to search')}
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
function renderActionButton(icon, action, title) {
|
||||
return `
|
||||
<button class="action-btn ${action}"
|
||||
data-action="${action}"
|
||||
title="${title}"
|
||||
onclick="event.stopPropagation()">
|
||||
<i class="codicon codicon-${icon}"></i>
|
||||
</button>
|
||||
`;
|
||||
}
|
||||
|
||||
// Event delegation for pattern list - set up once, works for all items
|
||||
function setupEventDelegation() {
|
||||
if (!domCache.patternList) return;
|
||||
|
||||
// Delegate all pattern list events to the parent container
|
||||
domCache.patternList.addEventListener('click', (event) => {
|
||||
// Handle pattern item click
|
||||
const patternItem = event.target.closest('.pattern-item');
|
||||
if (patternItem && !event.target.closest('.action-btn') && !event.target.closest('.add-pattern-btn')) {
|
||||
handlePatternClick(patternItem);
|
||||
return;
|
||||
}
|
||||
|
||||
// Handle action button clicks (load, delete)
|
||||
const actionBtn = event.target.closest('.action-btn');
|
||||
if (actionBtn) {
|
||||
event.stopPropagation();
|
||||
handleActionClick(actionBtn);
|
||||
return;
|
||||
}
|
||||
|
||||
// Handle add pattern button clicks
|
||||
const addBtn = event.target.closest('.add-pattern-btn');
|
||||
if (addBtn) {
|
||||
event.stopPropagation();
|
||||
handleAddPattern(addBtn);
|
||||
return;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function handlePatternClick(item) {
|
||||
const id = item.dataset.id;
|
||||
const scope = item.dataset.scope;
|
||||
|
||||
document.querySelectorAll('.pattern-item').forEach(i => {
|
||||
i.classList.remove('selected');
|
||||
});
|
||||
|
||||
item.classList.add('selected');
|
||||
|
||||
const pattern = findPatternById(id, scope);
|
||||
|
||||
if (pattern) {
|
||||
populateDetailsView(pattern);
|
||||
}
|
||||
}
|
||||
|
||||
function populateDetailsView(pattern) {
|
||||
domCache.editEmptyState.style.display = 'none';
|
||||
domCache.editForm.classList.add('active');
|
||||
|
||||
domCache.labelInput.value = pattern.label;
|
||||
domCache.findInput.value = pattern.find || '';
|
||||
domCache.replaceInput.value = pattern.replace || '';
|
||||
|
||||
domCache.isRegex.checked = pattern.flags.isRegex;
|
||||
domCache.isCaseSensitive.checked = pattern.flags.isCaseSensitive;
|
||||
domCache.matchWholeWord.checked = pattern.flags.matchWholeWord;
|
||||
|
||||
updateToggleButtonState(domCache.isRegexBtn, pattern.flags.isRegex);
|
||||
updateToggleButtonState(domCache.isCaseSensitiveBtn, pattern.flags.isCaseSensitive);
|
||||
updateToggleButtonState(domCache.matchWholeWordBtn, pattern.flags.matchWholeWord);
|
||||
|
||||
domCache.findInput.classList.remove('error');
|
||||
domCache.findValidationError.classList.remove('visible');
|
||||
validateRegexPattern();
|
||||
|
||||
domCache.filesToInclude.value = pattern.filesToInclude || '';
|
||||
domCache.filesToExclude.value = pattern.filesToExclude || '';
|
||||
|
||||
const state = vscode.getState() || {};
|
||||
state.currentPattern = {
|
||||
id: pattern.id,
|
||||
label: pattern.label,
|
||||
scope: pattern.scope
|
||||
};
|
||||
vscode.setState(state);
|
||||
|
||||
// Save the clean state for dirty tracking
|
||||
saveDirtyState(pattern);
|
||||
}
|
||||
|
||||
function selectPattern(id, scope) {
|
||||
const item = document.querySelector(
|
||||
`.pattern-item[data-id="${id}"][data-scope="${scope}"]`
|
||||
);
|
||||
|
||||
if (item) {
|
||||
item.click();
|
||||
}
|
||||
}
|
||||
|
||||
function handleSavePattern() {
|
||||
const state = vscode.getState() || {};
|
||||
const currentPattern = state.currentPattern;
|
||||
|
||||
if (!currentPattern) {
|
||||
console.error('No pattern selected to save');
|
||||
return;
|
||||
}
|
||||
|
||||
const label = domCache.labelInput.value.trim();
|
||||
const find = domCache.findInput.value;
|
||||
const replace = domCache.replaceInput.value;
|
||||
|
||||
if (!label) {
|
||||
alert('Pattern name cannot be empty');
|
||||
return;
|
||||
}
|
||||
|
||||
disablePatternList(true);
|
||||
document.body.style.cursor = 'wait';
|
||||
|
||||
const pattern = {
|
||||
id: currentPattern.id,
|
||||
label: label,
|
||||
find: find,
|
||||
replace: replace,
|
||||
flags: {
|
||||
isRegex: domCache.isRegex.checked,
|
||||
isCaseSensitive: domCache.isCaseSensitive.checked,
|
||||
matchWholeWord: domCache.matchWholeWord.checked,
|
||||
isMultiline: false
|
||||
},
|
||||
filesToInclude: domCache.filesToInclude.value.trim() || undefined,
|
||||
filesToExclude: domCache.filesToExclude.value.trim() || undefined,
|
||||
scope: currentPattern.scope
|
||||
};
|
||||
|
||||
vscode.postMessage({
|
||||
type: 'save',
|
||||
pattern: pattern
|
||||
});
|
||||
}
|
||||
|
||||
function disablePatternList(disabled) {
|
||||
const listPanel = document.querySelector('.pattern-list-panel');
|
||||
if (disabled) {
|
||||
listPanel.style.pointerEvents = 'none';
|
||||
listPanel.style.opacity = '0.6';
|
||||
} else {
|
||||
listPanel.style.pointerEvents = '';
|
||||
listPanel.style.opacity = '';
|
||||
}
|
||||
}
|
||||
|
||||
function handleDeletePattern() {
|
||||
const state = vscode.getState() || {};
|
||||
const currentPattern = state.currentPattern;
|
||||
|
||||
if (!currentPattern) {
|
||||
console.error('No pattern selected to delete');
|
||||
return;
|
||||
}
|
||||
|
||||
vscode.postMessage({
|
||||
type: 'delete',
|
||||
id: currentPattern.id,
|
||||
scope: currentPattern.scope
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
function handleLoadPatternFromDetails() {
|
||||
const state = vscode.getState() || {};
|
||||
const currentPattern = state.currentPattern;
|
||||
|
||||
if (!currentPattern) {
|
||||
console.error('No pattern selected to load');
|
||||
return;
|
||||
}
|
||||
|
||||
// Build pattern from current form values (may be dirty)
|
||||
const pattern = {
|
||||
id: currentPattern.id,
|
||||
label: domCache.labelInput.value.trim(),
|
||||
find: domCache.findInput.value,
|
||||
replace: domCache.replaceInput.value,
|
||||
flags: {
|
||||
isRegex: domCache.isRegex.checked,
|
||||
isCaseSensitive: domCache.isCaseSensitive.checked,
|
||||
matchWholeWord: domCache.matchWholeWord.checked,
|
||||
isMultiline: false
|
||||
},
|
||||
filesToInclude: domCache.filesToInclude.value.trim() || undefined,
|
||||
filesToExclude: domCache.filesToExclude.value.trim() || undefined,
|
||||
scope: currentPattern.scope
|
||||
};
|
||||
|
||||
vscode.postMessage({
|
||||
type: 'load',
|
||||
id: currentPattern.id,
|
||||
scope: currentPattern.scope,
|
||||
pattern: pattern
|
||||
});
|
||||
}
|
||||
|
||||
function clearDetailsView() {
|
||||
domCache.editForm.classList.remove('active');
|
||||
domCache.editEmptyState.style.display = 'flex';
|
||||
|
||||
domCache.labelInput.value = '';
|
||||
domCache.findInput.value = '';
|
||||
domCache.replaceInput.value = '';
|
||||
domCache.isRegex.checked = false;
|
||||
domCache.isCaseSensitive.checked = false;
|
||||
domCache.matchWholeWord.checked = false;
|
||||
domCache.filesToInclude.value = '';
|
||||
domCache.filesToExclude.value = '';
|
||||
|
||||
const state = vscode.getState() || {};
|
||||
delete state.currentPattern;
|
||||
vscode.setState(state);
|
||||
|
||||
document.querySelectorAll('.pattern-item').forEach(i => {
|
||||
i.classList.remove('selected');
|
||||
});
|
||||
|
||||
// Clear dirty state
|
||||
savedPatternState = null;
|
||||
updateDirtyIndicator(false);
|
||||
clearAllDirtyClasses();
|
||||
}
|
||||
|
||||
/**
|
||||
* Setup toggle buttons for flags
|
||||
*/
|
||||
function setupToggleButtons() {
|
||||
const buttons = [
|
||||
{ btnId: 'isRegexBtn', checkboxId: 'isRegex' },
|
||||
{ btnId: 'isCaseSensitiveBtn', checkboxId: 'isCaseSensitive' },
|
||||
{ btnId: 'matchWholeWordBtn', checkboxId: 'matchWholeWord' }
|
||||
];
|
||||
|
||||
buttons.forEach(({ btnId, checkboxId }) => {
|
||||
const btn = document.getElementById(btnId);
|
||||
const checkbox = document.getElementById(checkboxId);
|
||||
|
||||
if (!btn || !checkbox) return;
|
||||
|
||||
btn.addEventListener('click', () => {
|
||||
checkbox.checked = !checkbox.checked;
|
||||
updateToggleButtonState(btn, checkbox.checked);
|
||||
|
||||
// Validate regex when regex flag changes
|
||||
if (checkboxId === 'isRegex') {
|
||||
validateRegexPattern();
|
||||
}
|
||||
|
||||
// Check if field is dirty
|
||||
checkIfDirty();
|
||||
});
|
||||
|
||||
// Initialize button state
|
||||
updateToggleButtonState(btn, checkbox.checked);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Update visual state of toggle button
|
||||
*/
|
||||
function updateToggleButtonState(button, isActive) {
|
||||
if (isActive) {
|
||||
button.classList.add('active');
|
||||
} else {
|
||||
button.classList.remove('active');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate regex pattern
|
||||
*/
|
||||
function validateRegexPattern() {
|
||||
const isRegex = domCache.isRegex.checked;
|
||||
|
||||
if (!isRegex) {
|
||||
// Not using regex, clear any errors
|
||||
domCache.findInput.classList.remove('error');
|
||||
domCache.findValidationError.classList.remove('visible');
|
||||
domCache.findValidationError.textContent = '';
|
||||
return true;
|
||||
}
|
||||
|
||||
const pattern = domCache.findInput.value;
|
||||
|
||||
if (!pattern) {
|
||||
// Empty pattern is okay
|
||||
domCache.findInput.classList.remove('error');
|
||||
domCache.findValidationError.classList.remove('visible');
|
||||
domCache.findValidationError.textContent = '';
|
||||
return true;
|
||||
}
|
||||
|
||||
try {
|
||||
new RegExp(pattern);
|
||||
// Valid regex
|
||||
domCache.findInput.classList.remove('error');
|
||||
domCache.findValidationError.classList.remove('visible');
|
||||
domCache.findValidationError.textContent = '';
|
||||
return true;
|
||||
} catch (e) {
|
||||
// Invalid regex
|
||||
domCache.findInput.classList.add('error');
|
||||
domCache.findValidationError.classList.add('visible');
|
||||
domCache.findValidationError.textContent = `Invalid regular expression: ${e.message}`;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
function handleAddPattern(button) {
|
||||
const scope = button.dataset.scope;
|
||||
|
||||
// Visual feedback: disable button temporarily
|
||||
button.disabled = true;
|
||||
button.style.opacity = '0.5';
|
||||
button.style.cursor = 'wait';
|
||||
|
||||
vscode.postMessage({
|
||||
type: 'create',
|
||||
scope: scope === 'workspace' ? 'workspace' : 'global'
|
||||
});
|
||||
|
||||
// Re-enable after 2 seconds (fallback in case of error)
|
||||
setTimeout(() => {
|
||||
button.disabled = false;
|
||||
button.style.opacity = '';
|
||||
button.style.cursor = '';
|
||||
}, 2000);
|
||||
}
|
||||
|
||||
function handleActionClick(button) {
|
||||
const action = button.dataset.action;
|
||||
const item = button.closest('.pattern-item');
|
||||
const id = item.dataset.id;
|
||||
const scope = item.dataset.scope;
|
||||
|
||||
if (action === 'delete') {
|
||||
handleDelete(id, scope);
|
||||
} else if (action === 'load') {
|
||||
handleLoad(id, scope);
|
||||
}
|
||||
}
|
||||
|
||||
function handleDelete(id, scope) {
|
||||
vscode.postMessage({
|
||||
type: 'delete',
|
||||
id: id,
|
||||
scope: scope
|
||||
});
|
||||
}
|
||||
|
||||
function handleLoad(id, scope) {
|
||||
if (!id || !scope) {
|
||||
console.error('Pattern not found');
|
||||
return;
|
||||
}
|
||||
|
||||
vscode.postMessage({
|
||||
type: 'load',
|
||||
id: id,
|
||||
scope: scope
|
||||
});
|
||||
}
|
||||
|
||||
document.getElementById('saveBtn').addEventListener('click', handleSavePattern);
|
||||
document.getElementById('loadBtn').addEventListener('click', handleLoadPatternFromDetails);
|
||||
|
||||
setupToggleButtons();
|
||||
|
||||
// Add event listeners to all form inputs for dirty tracking
|
||||
const formInputs = [
|
||||
'labelInput', 'findInput', 'replaceInput',
|
||||
'isRegex', 'isCaseSensitive', 'matchWholeWord',
|
||||
'filesToInclude', 'filesToExclude'
|
||||
];
|
||||
|
||||
formInputs.forEach(id => {
|
||||
const element = document.getElementById(id);
|
||||
if (element) {
|
||||
element.addEventListener('input', checkIfDirty);
|
||||
element.addEventListener('change', checkIfDirty);
|
||||
}
|
||||
});
|
||||
|
||||
// Initialize DOM cache for performance
|
||||
initDomCache();
|
||||
|
||||
// Set up event delegation for pattern list (once, not per render)
|
||||
setupEventDelegation();
|
||||
|
||||
// Resizable panels functionality
|
||||
function setupResizablePanels() {
|
||||
const resizeHandle = document.getElementById('resizeHandle');
|
||||
const patternListPanel = document.querySelector('.pattern-list-panel');
|
||||
|
||||
if (!resizeHandle || !patternListPanel) return;
|
||||
|
||||
let isResizing = false;
|
||||
let startX = 0;
|
||||
let startWidth = 0;
|
||||
|
||||
// Load saved width from state
|
||||
const state = vscode.getState() || {};
|
||||
if (state.panelWidth) {
|
||||
patternListPanel.style.flexBasis = `${state.panelWidth}px`;
|
||||
}
|
||||
|
||||
resizeHandle.addEventListener('mousedown', (e) => {
|
||||
isResizing = true;
|
||||
startX = e.clientX;
|
||||
startWidth = patternListPanel.offsetWidth;
|
||||
resizeHandle.classList.add('resizing');
|
||||
document.body.style.cursor = 'ew-resize';
|
||||
document.body.style.userSelect = 'none';
|
||||
e.preventDefault();
|
||||
});
|
||||
|
||||
document.addEventListener('mousemove', (e) => {
|
||||
if (!isResizing) return;
|
||||
|
||||
const deltaX = e.clientX - startX;
|
||||
const newWidth = startWidth + deltaX;
|
||||
|
||||
// Respect min/max constraints
|
||||
const minWidth = 200;
|
||||
const maxWidth = 600;
|
||||
const clampedWidth = Math.max(minWidth, Math.min(maxWidth, newWidth));
|
||||
|
||||
patternListPanel.style.flexBasis = `${clampedWidth}px`;
|
||||
});
|
||||
|
||||
document.addEventListener('mouseup', () => {
|
||||
if (isResizing) {
|
||||
isResizing = false;
|
||||
resizeHandle.classList.remove('resizing');
|
||||
document.body.style.cursor = '';
|
||||
document.body.style.userSelect = '';
|
||||
|
||||
// Save width to state
|
||||
const currentWidth = patternListPanel.offsetWidth;
|
||||
const state = vscode.getState() || {};
|
||||
state.panelWidth = currentWidth;
|
||||
vscode.setState(state);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Set up resizable panels
|
||||
setupResizablePanels();
|
||||
|
||||
// Add validation listener to find input
|
||||
if (domCache.findInput) {
|
||||
domCache.findInput.addEventListener('input', () => {
|
||||
validateRegexPattern();
|
||||
});
|
||||
}
|
||||
|
||||
vscode.postMessage({ type: 'ready' });
|
||||
Reference in new issue
Block a user