@getdom/studio 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE.md +109 -0
- package/README.md +44 -0
- package/SKILL.md +83 -0
- package/package.json +73 -0
- package/public/llms.txt +57 -0
- package/public/skills.sh +11 -0
- package/src/pages/_layout/componentCatalog.generated.js +4514 -0
- package/src/pages/_layout/componentInspector.js +60 -0
- package/src/pages/_layout/componentManager.js +192 -0
- package/src/pages/_layout/componentRuntimeRecords.js +49 -0
- package/src/pages/_layout/inspector/componentRegistry.js +639 -0
- package/src/pages/_layout/inspector/serialize.js +134 -0
- package/src/pages/_layout/inspector/useInspector.js +293 -0
- package/src/pages/components/accordion/DomAccordion.vue +39 -0
- package/src/pages/components/breadcrumbs/DomBreadcrumbs.vue +347 -0
- package/src/pages/components/button/DomButton.vue +132 -0
- package/src/pages/components/command-palette/DomCommandPalette.vue +482 -0
- package/src/pages/components/context-menu/DomContextMenu.vue +178 -0
- package/src/pages/components/dialog/DomDialog.vue +271 -0
- package/src/pages/components/dialog/DomDialogSchemaForm.vue +72 -0
- package/src/pages/components/dialog/DomDialogStack.vue +289 -0
- package/src/pages/components/dialog/useDialogs.js +253 -0
- package/src/pages/components/drawer/DomDrawer.vue +159 -0
- package/src/pages/components/dropdown/DomDropdown.vue +261 -0
- package/src/pages/components/icon-button/DomIconButton.vue +155 -0
- package/src/pages/components/lightbox/DomLightbox.vue +1561 -0
- package/src/pages/components/menu/DomMenu.vue +359 -0
- package/src/pages/components/menu/DomMenuItem.vue +233 -0
- package/src/pages/components/menu/DomMenuLabel.vue +19 -0
- package/src/pages/components/menu/DomMenuSeparator.vue +14 -0
- package/src/pages/components/menu/menuContext.js +11 -0
- package/src/pages/components/menu/menuPlacement.js +26 -0
- package/src/pages/components/menu/submenu.js +58 -0
- package/src/pages/components/pagination/DomPagination.vue +265 -0
- package/src/pages/components/playground/DomRenderer.vue +48 -0
- package/src/pages/components/popover/DomPopover.vue +244 -0
- package/src/pages/components/profile-menu/DomProfileMenu.vue +408 -0
- package/src/pages/components/profile-menu/DomProfileMenuItemContent.vue +45 -0
- package/src/pages/components/scroll-menu/DomScrollMenu.vue +430 -0
- package/src/pages/components/scroll-menu/DomScrollMenuItem.vue +212 -0
- package/src/pages/components/scroll-menu/scrollMenuContext.js +22 -0
- package/src/pages/components/splitter-panel/DomSplitterPanel.vue +283 -0
- package/src/pages/components/stepper/DomStepper.vue +386 -0
- package/src/pages/components/tabs/DomTabs.vue +149 -0
- package/src/pages/components/theme-switcher/DomThemeSwitcher.vue +226 -0
- package/src/pages/components/toast/DomToastItem.vue +52 -0
- package/src/pages/components/toast/DomToastStack.vue +768 -0
- package/src/pages/components/toast/useToasts.js +237 -0
- package/src/pages/components/toolbar/DomToolbar.vue +158 -0
- package/src/pages/components/tooltip/DomTooltip.vue +69 -0
- package/src/pages/components/tooltip/vDomTooltip.js +106 -0
- package/src/pages/components/tree-view/DomTreeView.vue +961 -0
- package/src/pages/components/tree-view/treeSchemas.js +129 -0
- package/src/pages/components/tree-view/treeUtils.js +228 -0
- package/src/pages/data/_shared/calculateVirtualWindow.js +101 -0
- package/src/pages/data/_shared/resourceRecord.js +29 -0
- package/src/pages/data/activity-timeline/DomActivityTimeline.vue +417 -0
- package/src/pages/data/agent-chat/DomAgentChatShell.vue +565 -0
- package/src/pages/data/agent-chat/DomAgentConversation.vue +737 -0
- package/src/pages/data/agent-chat/DomAgentConversationList.vue +319 -0
- package/src/pages/data/agent-chat/examples/agentChatMockApi.js +620 -0
- package/src/pages/data/agent-chat/examples/useAgentChatDemoStore.js +730 -0
- package/src/pages/data/data-grid/DataGridVirtualRows.vue +220 -0
- package/src/pages/data/data-grid/DomDataGrid.vue +1643 -0
- package/src/pages/data/document-editor/DomDocumentEditor.vue +3030 -0
- package/src/pages/data/document-editor/documentEditorCodeBlock.js +282 -0
- package/src/pages/data/document-editor/examples/documentEditorMockApi.js +272 -0
- package/src/pages/data/document-editor/examples/useDocumentEditorDemoStore.js +284 -0
- package/src/pages/data/filter-bar/DomFilterBar.vue +533 -0
- package/src/pages/data/kanban/DomKanban.vue +1703 -0
- package/src/pages/data/kanban/kanbanApi.js +80 -0
- package/src/pages/data/media-browser/DomMediaBrowser.vue +2344 -0
- package/src/pages/data/media-browser/examples/mediaBrowserMockApi.js +653 -0
- package/src/pages/data/media-browser/examples/useMediaBrowserDemoStore.js +1157 -0
- package/src/pages/data/month-calendar/DomMonthCalendar.vue +1718 -0
- package/src/pages/data/week-planner/DomWeekPlanner.vue +2308 -0
- package/src/pages/dev/code-block/DomCodeBlock.vue +263 -0
- package/src/pages/dev/json-viewer/DomJsonViewer.vue +962 -0
- package/src/pages/forms/_shared/DomFieldLoadingSpinner.vue +12 -0
- package/src/pages/forms/_shared/options.js +61 -0
- package/src/pages/forms/_shared/useOptionMenu.js +59 -0
- package/src/pages/forms/autocomplete/DomAutocomplete.vue +180 -0
- package/src/pages/forms/calendar/DomCalendar.vue +442 -0
- package/src/pages/forms/checkbox/DomCheckbox.vue +69 -0
- package/src/pages/forms/class-toggle-input/DomClassToggleInput.vue +316 -0
- package/src/pages/forms/code-input/DomCodeInput.vue +202 -0
- package/src/pages/forms/color-input/DomColorInput.vue +118 -0
- package/src/pages/forms/combobox/DomCombobox.vue +237 -0
- package/src/pages/forms/confirmation-code-input/DomConfirmationCodeInput.vue +345 -0
- package/src/pages/forms/date-picker/DomDatePicker.vue +299 -0
- package/src/pages/forms/date-time-picker/DomDateTimePicker.vue +630 -0
- package/src/pages/forms/email-input/DomEmailInput.vue +55 -0
- package/src/pages/forms/field/DomField.vue +267 -0
- package/src/pages/forms/field/fieldLayout.js +1 -0
- package/src/pages/forms/field/fieldProps.js +114 -0
- package/src/pages/forms/field/layouts/DomGridFieldLayout.vue +38 -0
- package/src/pages/forms/field/useField.js +343 -0
- package/src/pages/forms/field/validators.js +364 -0
- package/src/pages/forms/file-upload/DomFileUpload.vue +361 -0
- package/src/pages/forms/form/DomForm.vue +985 -0
- package/src/pages/forms/form/adapters/fieldBuilder.js +45 -0
- package/src/pages/forms/form/adapters/jsonSchema.js +264 -0
- package/src/pages/forms/form/adapters/shared.js +53 -0
- package/src/pages/forms/form/adapters/zod.js +235 -0
- package/src/pages/forms/form/formApi.js +38 -0
- package/src/pages/forms/form/formDefinition.js +392 -0
- package/src/pages/forms/form/schemaAdapter.js +54 -0
- package/src/pages/forms/icon-selector/DomIconSelector.vue +460 -0
- package/src/pages/forms/icon-selector/iconSelectorItems.js +490 -0
- package/src/pages/forms/input-group/DomInputGroup.vue +232 -0
- package/src/pages/forms/json-input/DomJsonInput.vue +227 -0
- package/src/pages/forms/json-list-input/DomJsonListInput.vue +206 -0
- package/src/pages/forms/listbox/DomListbox.vue +105 -0
- package/src/pages/forms/money-input/DomMoneyInput.vue +147 -0
- package/src/pages/forms/native-select/DomNativeSelect.vue +67 -0
- package/src/pages/forms/number-input/DomNumberInput.vue +47 -0
- package/src/pages/forms/password-input/DomPasswordInput.vue +218 -0
- package/src/pages/forms/password-input/pwnedPasswords.js +47 -0
- package/src/pages/forms/password-strength/DomPasswordStrength.vue +128 -0
- package/src/pages/forms/password-strength/passwordStrength.js +92 -0
- package/src/pages/forms/position-input/DomPositionInput.vue +83 -0
- package/src/pages/forms/radio-group/DomRadioGroup.vue +102 -0
- package/src/pages/forms/range-input/DomRangeInput.vue +296 -0
- package/src/pages/forms/select/DomSelect.vue +479 -0
- package/src/pages/forms/select-input/DomSelectInput.vue +155 -0
- package/src/pages/forms/tag-combobox/DomTagCombobox.vue +358 -0
- package/src/pages/forms/text-input/DomTextInput.vue +48 -0
- package/src/pages/forms/textarea-composer/DomTextareaComposer.vue +234 -0
- package/src/pages/forms/textarea-input/DomTextareaInput.vue +86 -0
- package/src/pages/forms/textarea-input/useTextareaAutosize.js +147 -0
- package/src/pages/forms/time-selector/DomTimeSelector.vue +389 -0
- package/src/pages/forms/time-selector/timeUtils.js +194 -0
- package/src/pages/forms/toggle/DomToggle.vue +158 -0
- package/src/pages/forms/toggle-button/DomToggleButton.vue +88 -0
- package/src/pages/forms/toggle-button-group/DomToggleButtonGroup.vue +340 -0
- package/src/pages/forms/url-input/DomUrlInput.vue +55 -0
- package/src/pages/forms/wysiwyg/DomWysiwyg.vue +204 -0
- package/src/pages/lib/css/color.js +245 -0
- package/src/pages/lib/headless/accordion.d.ts +7 -0
- package/src/pages/lib/headless/accordion.js +246 -0
- package/src/pages/lib/headless/autocomplete.d.ts +4 -0
- package/src/pages/lib/headless/autocomplete.js +33 -0
- package/src/pages/lib/headless/base.d.ts +25 -0
- package/src/pages/lib/headless/base.js +127 -0
- package/src/pages/lib/headless/checkbox.d.ts +19 -0
- package/src/pages/lib/headless/checkbox.js +99 -0
- package/src/pages/lib/headless/combobox.d.ts +7 -0
- package/src/pages/lib/headless/combobox.js +488 -0
- package/src/pages/lib/headless/dialog.d.ts +23 -0
- package/src/pages/lib/headless/dialog.js +384 -0
- package/src/pages/lib/headless/drawer.d.ts +21 -0
- package/src/pages/lib/headless/drawer.js +682 -0
- package/src/pages/lib/headless/dropdown.d.ts +19 -0
- package/src/pages/lib/headless/dropdown.js +412 -0
- package/src/pages/lib/headless/floating-effect.js +118 -0
- package/src/pages/lib/headless/floating.d.ts +34 -0
- package/src/pages/lib/headless/floating.js +382 -0
- package/src/pages/lib/headless/index.d.ts +19 -0
- package/src/pages/lib/headless/index.js +19 -0
- package/src/pages/lib/headless/listbox.d.ts +19 -0
- package/src/pages/lib/headless/listbox.js +126 -0
- package/src/pages/lib/headless/menu.d.ts +18 -0
- package/src/pages/lib/headless/menu.js +117 -0
- package/src/pages/lib/headless/popover-panel.d.ts +18 -0
- package/src/pages/lib/headless/popover-panel.js +309 -0
- package/src/pages/lib/headless/popover.d.ts +19 -0
- package/src/pages/lib/headless/popover.js +391 -0
- package/src/pages/lib/headless/radio-group.d.ts +19 -0
- package/src/pages/lib/headless/radio-group.js +100 -0
- package/src/pages/lib/headless/tabs.d.ts +18 -0
- package/src/pages/lib/headless/tabs.js +119 -0
- package/src/pages/lib/headless/toast.d.ts +13 -0
- package/src/pages/lib/headless/toast.js +65 -0
- package/src/pages/lib/headless/toggle.d.ts +19 -0
- package/src/pages/lib/headless/toggle.js +162 -0
- package/src/pages/lib/headless/tooltip-controller.d.ts +22 -0
- package/src/pages/lib/headless/tooltip-controller.js +549 -0
- package/src/pages/lib/headless/tooltip.d.ts +19 -0
- package/src/pages/lib/headless/tooltip.js +153 -0
- package/src/pages/lib/headless/transitions.d.ts +5 -0
- package/src/pages/lib/headless/transitions.js +89 -0
- package/src/pages/lib/vue/_internal/DomLiquidGlassSurface.vue +310 -0
- package/src/pages/lib/vue/_internal/liquidGlass.js +112 -0
- package/src/pages/lib/vue/_internal/liquidGlassConfig.js +10 -0
- package/src/pages/lib/vue/codeHighlighter.js +132 -0
- package/src/pages/lib/vue/floatingTeleportTarget.js +23 -0
- package/src/pages/lib/vue/index.d.ts +6806 -0
- package/src/pages/lib/vue/index.js +218 -0
- package/src/pages/lib/vue/useTheme.js +116 -0
- package/src/pages/mobile/action-sheet/DomActionSheet.vue +186 -0
- package/src/pages/mobile/bottom-nav/DomAppBottomNav.vue +94 -0
- package/src/pages/mobile/list-item/DomAppListItem.vue +90 -0
- package/src/pages/mobile/safe-area/DomSafeArea.vue +39 -0
- package/src/pages/mobile/shell/DomAppShell.vue +51 -0
- package/src/pages/mobile/stack/AppStackScreen.vue +63 -0
- package/src/pages/mobile/stack/DomAppStack.vue +711 -0
- package/src/pages/mobile/top-bar/DomAppTopBar.vue +67 -0
- package/src/pages/visual/alert/DomAlert.vue +363 -0
- package/src/pages/visual/avatar/DomAvatar.vue +110 -0
- package/src/pages/visual/avatar-stack/DomAvatarStack.vue +89 -0
- package/src/pages/visual/badge/DomBadge.vue +105 -0
- package/src/pages/visual/card/DomCard.vue +65 -0
- package/src/pages/visual/card-rail/DomCardRail.vue +99 -0
- package/src/pages/visual/empty-state/DomEmptyState.vue +88 -0
- package/src/pages/visual/progress/DomProgress.vue +212 -0
- package/src/pages/visual/progress/DomRadialProgress.vue +186 -0
- package/src/pages/visual/progress/DomSegmentedProgress.vue +350 -0
- package/src/pages/visual/skeleton/DomSkeleton.vue +174 -0
- package/src/pages/visual/spinner/DomSpinner.vue +122 -0
- package/src/pages/visual/status-pill/DomStatusPill.vue +75 -0
- package/src/pages/visual/text-diff/DomTextDiff.vue +577 -0
- package/src/pages/visual/text-diff/textDiff.js +300 -0
- package/src/style.css +446 -0
|
@@ -0,0 +1,300 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Creates a word-level diff between original and proposed text.
|
|
3
|
+
*
|
|
4
|
+
* @param {string} original Original text.
|
|
5
|
+
* @param {string} proposed Proposed replacement text.
|
|
6
|
+
* @returns {Array<{ id: string, type: 'same'|'added'|'removed', value: string }>} Ordered inline diff segments.
|
|
7
|
+
*/
|
|
8
|
+
export function createInlineDiff(original = '', proposed = '') {
|
|
9
|
+
return createSequenceDiff(tokenizeWords(original), tokenizeWords(proposed));
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Creates unified line rows with word-level emphasis inside paired replacements.
|
|
14
|
+
*
|
|
15
|
+
* @param {string} original Original text.
|
|
16
|
+
* @param {string} proposed Proposed replacement text.
|
|
17
|
+
* @returns {Array<Record<string, unknown>>} Unified diff rows.
|
|
18
|
+
*/
|
|
19
|
+
export function createUnifiedDiff(original = '', proposed = '') {
|
|
20
|
+
const operations = createSequenceDiff(splitLines(original), splitLines(proposed), false);
|
|
21
|
+
const rows = createLineRows(operations);
|
|
22
|
+
return addReplacementSegments(rows);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Creates aligned side-by-side rows from a unified line diff.
|
|
27
|
+
*
|
|
28
|
+
* @param {string} original Original text.
|
|
29
|
+
* @param {string} proposed Proposed replacement text.
|
|
30
|
+
* @returns {Array<Record<string, unknown>>} Split diff rows.
|
|
31
|
+
*/
|
|
32
|
+
export function createSplitDiff(original = '', proposed = '') {
|
|
33
|
+
const unifiedRows = createUnifiedDiff(original, proposed);
|
|
34
|
+
const rows = [];
|
|
35
|
+
let index = 0;
|
|
36
|
+
|
|
37
|
+
while (index < unifiedRows.length) {
|
|
38
|
+
const row = unifiedRows[index];
|
|
39
|
+
if (row.type === 'same') {
|
|
40
|
+
rows.push(createSplitRow(row, row));
|
|
41
|
+
index += 1;
|
|
42
|
+
continue;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const changeRows = [];
|
|
46
|
+
while (index < unifiedRows.length && unifiedRows[index].type !== 'same') {
|
|
47
|
+
changeRows.push(unifiedRows[index]);
|
|
48
|
+
index += 1;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
const removedRows = changeRows.filter(isRemovedRow);
|
|
52
|
+
const addedRows = changeRows.filter(isAddedRow);
|
|
53
|
+
const pairCount = Math.max(removedRows.length, addedRows.length);
|
|
54
|
+
for (let pairIndex = 0; pairIndex < pairCount; pairIndex += 1) {
|
|
55
|
+
rows.push(createSplitRow(removedRows[pairIndex], addedRows[pairIndex]));
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
return rows;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Tokenizes prose while preserving whitespace and punctuation for faithful rendering.
|
|
64
|
+
*
|
|
65
|
+
* @param {string} value Text to tokenize.
|
|
66
|
+
* @returns {Array<string>} Word, punctuation, and whitespace tokens.
|
|
67
|
+
*/
|
|
68
|
+
function tokenizeWords(value) {
|
|
69
|
+
return String(value ?? '').match(/\s+|[\p{L}\p{N}_]+|[^\s\p{L}\p{N}_]+/gu) || [];
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Splits text into normalized lines while preserving blank and trailing lines.
|
|
74
|
+
*
|
|
75
|
+
* @param {string} value Text to split.
|
|
76
|
+
* @returns {Array<string>} Normalized lines.
|
|
77
|
+
*/
|
|
78
|
+
function splitLines(value) {
|
|
79
|
+
return String(value ?? '').replace(/\r\n?/g, '\n').split('\n');
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Creates a longest-common-subsequence diff for two token sequences.
|
|
84
|
+
*
|
|
85
|
+
* @param {Array<string>} originalTokens Original sequence.
|
|
86
|
+
* @param {Array<string>} proposedTokens Proposed sequence.
|
|
87
|
+
* @param {boolean} mergeAdjacent Whether neighbouring operations should be merged.
|
|
88
|
+
* @returns {Array<{ id: string, type: 'same'|'added'|'removed', value: string }>} Ordered diff operations.
|
|
89
|
+
*/
|
|
90
|
+
function createSequenceDiff(originalTokens, proposedTokens, mergeAdjacent = true) {
|
|
91
|
+
const table = buildDiffTable(originalTokens, proposedTokens);
|
|
92
|
+
const operations = [];
|
|
93
|
+
let originalIndex = originalTokens.length;
|
|
94
|
+
let proposedIndex = proposedTokens.length;
|
|
95
|
+
|
|
96
|
+
while (originalIndex > 0 || proposedIndex > 0) {
|
|
97
|
+
if (
|
|
98
|
+
originalIndex > 0
|
|
99
|
+
&& proposedIndex > 0
|
|
100
|
+
&& originalTokens[originalIndex - 1] === proposedTokens[proposedIndex - 1]
|
|
101
|
+
) {
|
|
102
|
+
operations.unshift(createDiffOperation('same', originalTokens[originalIndex - 1], originalIndex, proposedIndex));
|
|
103
|
+
originalIndex -= 1;
|
|
104
|
+
proposedIndex -= 1;
|
|
105
|
+
continue;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
if (
|
|
109
|
+
proposedIndex > 0
|
|
110
|
+
&& (originalIndex === 0 || table[originalIndex][proposedIndex - 1] >= table[originalIndex - 1][proposedIndex])
|
|
111
|
+
) {
|
|
112
|
+
operations.unshift(createDiffOperation('added', proposedTokens[proposedIndex - 1], originalIndex, proposedIndex));
|
|
113
|
+
proposedIndex -= 1;
|
|
114
|
+
continue;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
operations.unshift(createDiffOperation('removed', originalTokens[originalIndex - 1], originalIndex, proposedIndex));
|
|
118
|
+
originalIndex -= 1;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
return mergeAdjacent ? mergeAdjacentOperations(operations) : operations;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* Builds the longest-common-subsequence table used by the diff backtracker.
|
|
126
|
+
*
|
|
127
|
+
* @param {Array<string>} originalTokens Original sequence.
|
|
128
|
+
* @param {Array<string>} proposedTokens Proposed sequence.
|
|
129
|
+
* @returns {Array<Uint32Array>} LCS length table.
|
|
130
|
+
*/
|
|
131
|
+
function buildDiffTable(originalTokens, proposedTokens) {
|
|
132
|
+
const table = [];
|
|
133
|
+
for (let rowIndex = 0; rowIndex <= originalTokens.length; rowIndex += 1) {
|
|
134
|
+
table.push(new Uint32Array(proposedTokens.length + 1));
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
for (let originalIndex = 1; originalIndex <= originalTokens.length; originalIndex += 1) {
|
|
138
|
+
for (let proposedIndex = 1; proposedIndex <= proposedTokens.length; proposedIndex += 1) {
|
|
139
|
+
if (originalTokens[originalIndex - 1] === proposedTokens[proposedIndex - 1]) {
|
|
140
|
+
table[originalIndex][proposedIndex] = table[originalIndex - 1][proposedIndex - 1] + 1;
|
|
141
|
+
} else {
|
|
142
|
+
table[originalIndex][proposedIndex] = Math.max(
|
|
143
|
+
table[originalIndex - 1][proposedIndex],
|
|
144
|
+
table[originalIndex][proposedIndex - 1],
|
|
145
|
+
);
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
return table;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
/**
|
|
154
|
+
* Creates one diff operation with a deterministic rendering key.
|
|
155
|
+
*
|
|
156
|
+
* @param {'same'|'added'|'removed'} type Operation type.
|
|
157
|
+
* @param {string} value Operation text.
|
|
158
|
+
* @param {number} originalIndex Original backtrack index.
|
|
159
|
+
* @param {number} proposedIndex Proposed backtrack index.
|
|
160
|
+
* @returns {{ id: string, type: 'same'|'added'|'removed', value: string }} Diff operation.
|
|
161
|
+
*/
|
|
162
|
+
function createDiffOperation(type, value, originalIndex, proposedIndex) {
|
|
163
|
+
return {
|
|
164
|
+
id: `${type}-${originalIndex}-${proposedIndex}-${String(value).length}`,
|
|
165
|
+
type,
|
|
166
|
+
value,
|
|
167
|
+
};
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
/**
|
|
171
|
+
* Merges neighbouring operations of the same type for compact inline rendering.
|
|
172
|
+
*
|
|
173
|
+
* @param {Array<{ id: string, type: 'same'|'added'|'removed', value: string }>} operations Diff operations.
|
|
174
|
+
* @returns {Array<{ id: string, type: 'same'|'added'|'removed', value: string }>} Merged operations.
|
|
175
|
+
*/
|
|
176
|
+
function mergeAdjacentOperations(operations) {
|
|
177
|
+
const merged = [];
|
|
178
|
+
for (const operation of operations) {
|
|
179
|
+
const previous = merged[merged.length - 1];
|
|
180
|
+
if (previous && previous.type === operation.type) {
|
|
181
|
+
previous.value += operation.value;
|
|
182
|
+
previous.id = `${previous.id}-${operation.id}`;
|
|
183
|
+
continue;
|
|
184
|
+
}
|
|
185
|
+
merged.push({ ...operation });
|
|
186
|
+
}
|
|
187
|
+
return merged;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
/**
|
|
191
|
+
* Converts line diff operations into numbered unified rows.
|
|
192
|
+
*
|
|
193
|
+
* @param {Array<Record<string, unknown>>} operations All line operations.
|
|
194
|
+
* @returns {Array<Record<string, unknown>>} Numbered line rows.
|
|
195
|
+
*/
|
|
196
|
+
function createLineRows(operations) {
|
|
197
|
+
const rows = [];
|
|
198
|
+
let originalNumber = 0;
|
|
199
|
+
let proposedNumber = 0;
|
|
200
|
+
|
|
201
|
+
for (const operation of operations) {
|
|
202
|
+
if (operation.type !== 'added') originalNumber += 1;
|
|
203
|
+
if (operation.type !== 'removed') proposedNumber += 1;
|
|
204
|
+
rows.push({
|
|
205
|
+
...operation,
|
|
206
|
+
originalNumber: operation.type === 'added' ? null : originalNumber,
|
|
207
|
+
proposedNumber: operation.type === 'removed' ? null : proposedNumber,
|
|
208
|
+
segments: [{ id: `${operation.id}-same`, type: 'same', value: operation.value }],
|
|
209
|
+
});
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
return rows;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
/**
|
|
216
|
+
* Adds word-level segments to corresponding removed and added rows.
|
|
217
|
+
*
|
|
218
|
+
* @param {Array<Record<string, unknown>>} rows Unified line rows.
|
|
219
|
+
* @returns {Array<Record<string, unknown>>} Rows with replacement emphasis.
|
|
220
|
+
*/
|
|
221
|
+
function addReplacementSegments(rows) {
|
|
222
|
+
let index = 0;
|
|
223
|
+
while (index < rows.length) {
|
|
224
|
+
if (rows[index].type === 'same') {
|
|
225
|
+
index += 1;
|
|
226
|
+
continue;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
const changeRows = [];
|
|
230
|
+
while (index < rows.length && rows[index].type !== 'same') {
|
|
231
|
+
changeRows.push(rows[index]);
|
|
232
|
+
index += 1;
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
const removedRows = changeRows.filter(isRemovedRow);
|
|
236
|
+
const addedRows = changeRows.filter(isAddedRow);
|
|
237
|
+
const pairCount = Math.min(removedRows.length, addedRows.length);
|
|
238
|
+
for (let pairIndex = 0; pairIndex < pairCount; pairIndex += 1) {
|
|
239
|
+
const segments = createInlineDiff(removedRows[pairIndex].value, addedRows[pairIndex].value);
|
|
240
|
+
removedRows[pairIndex].segments = segments.filter(isVisibleOnOriginalSide);
|
|
241
|
+
addedRows[pairIndex].segments = segments.filter(isVisibleOnProposedSide);
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
return rows;
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
/**
|
|
248
|
+
* Checks whether a row represents removed content.
|
|
249
|
+
*
|
|
250
|
+
* @param {Record<string, unknown>} row Diff row.
|
|
251
|
+
* @returns {boolean} Whether the row is removed.
|
|
252
|
+
*/
|
|
253
|
+
function isRemovedRow(row) {
|
|
254
|
+
return row?.type === 'removed';
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
/**
|
|
258
|
+
* Checks whether a row represents added content.
|
|
259
|
+
*
|
|
260
|
+
* @param {Record<string, unknown>} row Diff row.
|
|
261
|
+
* @returns {boolean} Whether the row is added.
|
|
262
|
+
*/
|
|
263
|
+
function isAddedRow(row) {
|
|
264
|
+
return row?.type === 'added';
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
/**
|
|
268
|
+
* Checks whether an inline segment belongs on the original side.
|
|
269
|
+
*
|
|
270
|
+
* @param {Record<string, unknown>} segment Inline segment.
|
|
271
|
+
* @returns {boolean} Whether the segment should be shown.
|
|
272
|
+
*/
|
|
273
|
+
function isVisibleOnOriginalSide(segment) {
|
|
274
|
+
return segment.type !== 'added';
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
/**
|
|
278
|
+
* Checks whether an inline segment belongs on the proposed side.
|
|
279
|
+
*
|
|
280
|
+
* @param {Record<string, unknown>} segment Inline segment.
|
|
281
|
+
* @returns {boolean} Whether the segment should be shown.
|
|
282
|
+
*/
|
|
283
|
+
function isVisibleOnProposedSide(segment) {
|
|
284
|
+
return segment.type !== 'removed';
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
/**
|
|
288
|
+
* Creates one aligned split-view row.
|
|
289
|
+
*
|
|
290
|
+
* @param {Record<string, unknown>|undefined} original Original-side row.
|
|
291
|
+
* @param {Record<string, unknown>|undefined} proposed Proposed-side row.
|
|
292
|
+
* @returns {Record<string, unknown>} Split-view row.
|
|
293
|
+
*/
|
|
294
|
+
function createSplitRow(original, proposed) {
|
|
295
|
+
return {
|
|
296
|
+
id: `split-${original?.id || 'blank'}-${proposed?.id || 'blank'}`,
|
|
297
|
+
original: original || null,
|
|
298
|
+
proposed: proposed || null,
|
|
299
|
+
};
|
|
300
|
+
}
|