mirror of
https://github.com/router-for-me/Cli-Proxy-API-Management-Center.git
synced 2026-02-19 11:10:49 +08:00
fix(config): show diff chunks with line numbers and context
This commit is contained in:
@@ -70,7 +70,7 @@
|
|||||||
|
|
||||||
.diffColumnHeader {
|
.diffColumnHeader {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: baseline;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
gap: $spacing-sm;
|
gap: $spacing-sm;
|
||||||
padding: 8px 10px;
|
padding: 8px 10px;
|
||||||
@@ -81,23 +81,66 @@
|
|||||||
background: var(--bg-secondary);
|
background: var(--bg-secondary);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.lineMeta {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
.lineRange {
|
.lineRange {
|
||||||
|
font-size: 11px;
|
||||||
|
color: var(--text-secondary);
|
||||||
|
font-family: 'Consolas', 'Monaco', 'Menlo', monospace;
|
||||||
|
}
|
||||||
|
|
||||||
|
.contextRange {
|
||||||
font-size: 11px;
|
font-size: 11px;
|
||||||
color: var(--text-tertiary);
|
color: var(--text-tertiary);
|
||||||
font-family: 'Consolas', 'Monaco', 'Menlo', monospace;
|
font-family: 'Consolas', 'Monaco', 'Menlo', monospace;
|
||||||
}
|
}
|
||||||
|
|
||||||
.codeBlock {
|
.codeList {
|
||||||
margin: 0;
|
overflow: auto;
|
||||||
padding: 10px;
|
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;
|
font-size: 12px;
|
||||||
line-height: 1.5;
|
line-height: 1.45;
|
||||||
color: var(--text-primary);
|
color: var(--text-primary);
|
||||||
white-space: pre-wrap;
|
white-space: pre-wrap;
|
||||||
word-break: break-word;
|
word-break: break-word;
|
||||||
font-family: 'Consolas', 'Monaco', 'Menlo', monospace;
|
display: block;
|
||||||
overflow: auto;
|
box-sizing: border-box;
|
||||||
max-height: 240px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@include mobile {
|
@include mobile {
|
||||||
@@ -109,4 +152,25 @@
|
|||||||
.diffColumns {
|
.diffColumns {
|
||||||
grid-template-columns: 1fr;
|
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 = {
|
type DiffChunkCard = {
|
||||||
id: string;
|
id: string;
|
||||||
currentLines: string;
|
current: DiffSide;
|
||||||
modifiedLines: string;
|
modified: DiffSide;
|
||||||
currentText: string;
|
|
||||||
modifiedText: string;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
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 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 start = clampPos(doc, from);
|
||||||
const end = clampPos(doc, to);
|
const end = clampPos(doc, to);
|
||||||
if (start === end) {
|
if (start === end) {
|
||||||
const linePos = Math.min(start, doc.length);
|
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 startLine = doc.lineAt(start).number;
|
||||||
const endLine = doc.lineAt(Math.max(start, end - 1)).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 expandContextRange = (doc: Text, range: LineRange): LineRange => ({
|
||||||
const start = clampPos(doc, from);
|
start: Math.max(1, range.start - DIFF_CONTEXT_LINES),
|
||||||
const end = clampPos(doc, to);
|
end: Math.min(doc.lines, range.end + DIFF_CONTEXT_LINES)
|
||||||
if (start >= end) return '';
|
});
|
||||||
return doc.sliceString(start, end).trimEnd();
|
|
||||||
|
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({
|
export function DiffModal({
|
||||||
@@ -59,13 +91,26 @@ export function DiffModal({
|
|||||||
const modifiedDoc = Text.of(modified.split('\n'));
|
const modifiedDoc = Text.of(modified.split('\n'));
|
||||||
const chunks = Chunk.build(currentDoc, modifiedDoc);
|
const chunks = Chunk.build(currentDoc, modifiedDoc);
|
||||||
|
|
||||||
return chunks.map((chunk, index) => ({
|
return chunks.map((chunk, index) => {
|
||||||
id: `${index}-${chunk.fromA}-${chunk.toA}-${chunk.fromB}-${chunk.toB}`,
|
const currentChangedRange = getChangedLineRange(currentDoc, chunk.fromA, chunk.toA);
|
||||||
currentLines: getLineRangeLabel(currentDoc, chunk.fromA, chunk.toA),
|
const modifiedChangedRange = getChangedLineRange(modifiedDoc, chunk.fromB, chunk.toB);
|
||||||
modifiedLines: getLineRangeLabel(modifiedDoc, chunk.fromB, chunk.toB),
|
const currentContextRange = expandContextRange(currentDoc, currentChangedRange);
|
||||||
currentText: getChunkText(currentDoc, chunk.fromA, chunk.toA),
|
const modifiedContextRange = expandContextRange(modifiedDoc, modifiedChangedRange);
|
||||||
modifiedText: getChunkText(modifiedDoc, chunk.fromB, chunk.toB)
|
|
||||||
}));
|
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]);
|
}, [modified, original]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -99,16 +144,46 @@ export function DiffModal({
|
|||||||
<section className={styles.diffColumn}>
|
<section className={styles.diffColumn}>
|
||||||
<header className={styles.diffColumnHeader}>
|
<header className={styles.diffColumnHeader}>
|
||||||
<span>{t('config_management.diff.current')}</span>
|
<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>
|
</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>
|
||||||
<section className={styles.diffColumn}>
|
<section className={styles.diffColumn}>
|
||||||
<header className={styles.diffColumnHeader}>
|
<header className={styles.diffColumnHeader}>
|
||||||
<span>{t('config_management.diff.modified')}</span>
|
<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>
|
</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>
|
</section>
|
||||||
</div>
|
</div>
|
||||||
</article>
|
</article>
|
||||||
|
|||||||
Reference in New Issue
Block a user