mirror of
https://github.com/router-for-me/Cli-Proxy-API-Management-Center.git
synced 2026-02-18 02:30:51 +08:00
fix(config): show diff chunks with line numbers and context
This commit is contained in:
@@ -70,7 +70,7 @@
|
||||
|
||||
.diffColumnHeader {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
align-items: baseline;
|
||||
justify-content: space-between;
|
||||
gap: $spacing-sm;
|
||||
padding: 8px 10px;
|
||||
@@ -81,23 +81,66 @@
|
||||
background: var(--bg-secondary);
|
||||
}
|
||||
|
||||
.lineMeta {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.lineRange {
|
||||
font-size: 11px;
|
||||
color: var(--text-secondary);
|
||||
font-family: 'Consolas', 'Monaco', 'Menlo', monospace;
|
||||
}
|
||||
|
||||
.contextRange {
|
||||
font-size: 11px;
|
||||
color: var(--text-tertiary);
|
||||
font-family: 'Consolas', 'Monaco', 'Menlo', monospace;
|
||||
}
|
||||
|
||||
.codeBlock {
|
||||
margin: 0;
|
||||
padding: 10px;
|
||||
.codeList {
|
||||
overflow: auto;
|
||||
max-height: 280px;
|
||||
font-family: 'Consolas', 'Monaco', 'Menlo', monospace;
|
||||
}
|
||||
|
||||
.codeLine {
|
||||
display: grid;
|
||||
grid-template-columns: 52px minmax(0, 1fr);
|
||||
align-items: start;
|
||||
border-top: 1px solid color-mix(in srgb, var(--border-color) 55%, transparent);
|
||||
}
|
||||
|
||||
.codeLine:first-child {
|
||||
border-top: none;
|
||||
}
|
||||
|
||||
.codeLineChanged {
|
||||
background: color-mix(in srgb, var(--primary-color) 8%, transparent);
|
||||
}
|
||||
|
||||
.codeLineNumber {
|
||||
padding: 7px 10px 7px 8px;
|
||||
text-align: right;
|
||||
font-size: 11px;
|
||||
color: var(--text-tertiary);
|
||||
border-right: 1px solid color-mix(in srgb, var(--border-color) 55%, transparent);
|
||||
background: color-mix(in srgb, var(--bg-secondary) 90%, transparent);
|
||||
font-variant-numeric: tabular-nums;
|
||||
user-select: none;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.codeLineText {
|
||||
padding: 7px 10px;
|
||||
font-size: 12px;
|
||||
line-height: 1.5;
|
||||
line-height: 1.45;
|
||||
color: var(--text-primary);
|
||||
white-space: pre-wrap;
|
||||
word-break: break-word;
|
||||
font-family: 'Consolas', 'Monaco', 'Menlo', monospace;
|
||||
overflow: auto;
|
||||
max-height: 240px;
|
||||
display: block;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
@include mobile {
|
||||
@@ -109,4 +152,25 @@
|
||||
.diffColumns {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
.lineMeta {
|
||||
flex-direction: column;
|
||||
align-items: flex-end;
|
||||
gap: 1px;
|
||||
}
|
||||
|
||||
.codeLine {
|
||||
grid-template-columns: 44px minmax(0, 1fr);
|
||||
}
|
||||
|
||||
.codeLineNumber {
|
||||
padding: 6px 6px 6px 4px;
|
||||
font-size: 10px;
|
||||
}
|
||||
|
||||
.codeLineText {
|
||||
padding: 6px 8px;
|
||||
font-size: 11px;
|
||||
line-height: 1.4;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,31 +17,63 @@ type DiffModalProps = {
|
||||
|
||||
type DiffChunkCard = {
|
||||
id: string;
|
||||
currentLines: string;
|
||||
modifiedLines: string;
|
||||
currentText: string;
|
||||
modifiedText: string;
|
||||
current: DiffSide;
|
||||
modified: DiffSide;
|
||||
};
|
||||
|
||||
type LineRange = {
|
||||
start: number;
|
||||
end: number;
|
||||
};
|
||||
|
||||
type DiffSideLine = {
|
||||
lineNumber: number;
|
||||
text: string;
|
||||
changed: boolean;
|
||||
};
|
||||
|
||||
type DiffSide = {
|
||||
changedRangeLabel: string;
|
||||
contextRangeLabel: string;
|
||||
lines: DiffSideLine[];
|
||||
};
|
||||
|
||||
const DIFF_CONTEXT_LINES = 2;
|
||||
|
||||
const clampPos = (doc: Text, pos: number) => Math.max(0, Math.min(pos, doc.length));
|
||||
|
||||
const getLineRangeLabel = (doc: Text, from: number, to: number): string => {
|
||||
const getLineRangeLabel = (range: LineRange): string => {
|
||||
return range.start === range.end ? String(range.start) : `${range.start}-${range.end}`;
|
||||
};
|
||||
|
||||
const getChangedLineRange = (doc: Text, from: number, to: number): LineRange => {
|
||||
const start = clampPos(doc, from);
|
||||
const end = clampPos(doc, to);
|
||||
if (start === end) {
|
||||
const linePos = Math.min(start, doc.length);
|
||||
return String(doc.lineAt(linePos).number);
|
||||
const anchorLine = doc.lineAt(linePos).number;
|
||||
return { start: anchorLine, end: anchorLine };
|
||||
}
|
||||
const startLine = doc.lineAt(start).number;
|
||||
const endLine = doc.lineAt(Math.max(start, end - 1)).number;
|
||||
return startLine === endLine ? String(startLine) : `${startLine}-${endLine}`;
|
||||
return { start: startLine, end: endLine };
|
||||
};
|
||||
|
||||
const getChunkText = (doc: Text, from: number, to: number): string => {
|
||||
const start = clampPos(doc, from);
|
||||
const end = clampPos(doc, to);
|
||||
if (start >= end) return '';
|
||||
return doc.sliceString(start, end).trimEnd();
|
||||
const expandContextRange = (doc: Text, range: LineRange): LineRange => ({
|
||||
start: Math.max(1, range.start - DIFF_CONTEXT_LINES),
|
||||
end: Math.min(doc.lines, range.end + DIFF_CONTEXT_LINES)
|
||||
});
|
||||
|
||||
const buildSideLines = (doc: Text, contextRange: LineRange, changedRange: LineRange): DiffSideLine[] => {
|
||||
const lines: DiffSideLine[] = [];
|
||||
for (let lineNumber = contextRange.start; lineNumber <= contextRange.end; lineNumber += 1) {
|
||||
lines.push({
|
||||
lineNumber,
|
||||
text: doc.line(lineNumber).text,
|
||||
changed: lineNumber >= changedRange.start && lineNumber <= changedRange.end
|
||||
});
|
||||
}
|
||||
return lines;
|
||||
};
|
||||
|
||||
export function DiffModal({
|
||||
@@ -59,13 +91,26 @@ export function DiffModal({
|
||||
const modifiedDoc = Text.of(modified.split('\n'));
|
||||
const chunks = Chunk.build(currentDoc, modifiedDoc);
|
||||
|
||||
return chunks.map((chunk, index) => ({
|
||||
id: `${index}-${chunk.fromA}-${chunk.toA}-${chunk.fromB}-${chunk.toB}`,
|
||||
currentLines: getLineRangeLabel(currentDoc, chunk.fromA, chunk.toA),
|
||||
modifiedLines: getLineRangeLabel(modifiedDoc, chunk.fromB, chunk.toB),
|
||||
currentText: getChunkText(currentDoc, chunk.fromA, chunk.toA),
|
||||
modifiedText: getChunkText(modifiedDoc, chunk.fromB, chunk.toB)
|
||||
}));
|
||||
return chunks.map((chunk, index) => {
|
||||
const currentChangedRange = getChangedLineRange(currentDoc, chunk.fromA, chunk.toA);
|
||||
const modifiedChangedRange = getChangedLineRange(modifiedDoc, chunk.fromB, chunk.toB);
|
||||
const currentContextRange = expandContextRange(currentDoc, currentChangedRange);
|
||||
const modifiedContextRange = expandContextRange(modifiedDoc, modifiedChangedRange);
|
||||
|
||||
return {
|
||||
id: `${index}-${chunk.fromA}-${chunk.toA}-${chunk.fromB}-${chunk.toB}`,
|
||||
current: {
|
||||
changedRangeLabel: getLineRangeLabel(currentChangedRange),
|
||||
contextRangeLabel: getLineRangeLabel(currentContextRange),
|
||||
lines: buildSideLines(currentDoc, currentContextRange, currentChangedRange)
|
||||
},
|
||||
modified: {
|
||||
changedRangeLabel: getLineRangeLabel(modifiedChangedRange),
|
||||
contextRangeLabel: getLineRangeLabel(modifiedContextRange),
|
||||
lines: buildSideLines(modifiedDoc, modifiedContextRange, modifiedChangedRange)
|
||||
}
|
||||
};
|
||||
});
|
||||
}, [modified, original]);
|
||||
|
||||
return (
|
||||
@@ -99,16 +144,46 @@ export function DiffModal({
|
||||
<section className={styles.diffColumn}>
|
||||
<header className={styles.diffColumnHeader}>
|
||||
<span>{t('config_management.diff.current')}</span>
|
||||
<span className={styles.lineRange}>L{card.currentLines}</span>
|
||||
<span className={styles.lineMeta}>
|
||||
<span className={styles.lineRange}>L{card.current.changedRangeLabel}</span>
|
||||
<span className={styles.contextRange}>
|
||||
±{DIFF_CONTEXT_LINES}: L{card.current.contextRangeLabel}
|
||||
</span>
|
||||
</span>
|
||||
</header>
|
||||
<pre className={styles.codeBlock}>{card.currentText || '-'}</pre>
|
||||
<div className={styles.codeList}>
|
||||
{card.current.lines.map((line) => (
|
||||
<div
|
||||
key={`${card.id}-a-${line.lineNumber}`}
|
||||
className={`${styles.codeLine} ${line.changed ? styles.codeLineChanged : ''}`}
|
||||
>
|
||||
<span className={styles.codeLineNumber}>{line.lineNumber}</span>
|
||||
<code className={styles.codeLineText}>{line.text || ' '}</code>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</section>
|
||||
<section className={styles.diffColumn}>
|
||||
<header className={styles.diffColumnHeader}>
|
||||
<span>{t('config_management.diff.modified')}</span>
|
||||
<span className={styles.lineRange}>L{card.modifiedLines}</span>
|
||||
<span className={styles.lineMeta}>
|
||||
<span className={styles.lineRange}>L{card.modified.changedRangeLabel}</span>
|
||||
<span className={styles.contextRange}>
|
||||
±{DIFF_CONTEXT_LINES}: L{card.modified.contextRangeLabel}
|
||||
</span>
|
||||
</span>
|
||||
</header>
|
||||
<pre className={styles.codeBlock}>{card.modifiedText || '-'}</pre>
|
||||
<div className={styles.codeList}>
|
||||
{card.modified.lines.map((line) => (
|
||||
<div
|
||||
key={`${card.id}-b-${line.lineNumber}`}
|
||||
className={`${styles.codeLine} ${line.changed ? styles.codeLineChanged : ''}`}
|
||||
>
|
||||
<span className={styles.codeLineNumber}>{line.lineNumber}</span>
|
||||
<code className={styles.codeLineText}>{line.text || ' '}</code>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
</article>
|
||||
|
||||
Reference in New Issue
Block a user