mbeditor 0.5.3 → 0.5.4

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 31199cd73e4e3321d8851a62ab20decd6e9c8d9ab328b50d9f987f85da1333b2
4
- data.tar.gz: 27de9f3e0641c41e693023b3fcbedbe0d89d35d8d1fd7f3606aaa8095ceeb5b2
3
+ metadata.gz: 6ce79d4cc314cce5975277d04c12a0d7d5fa13629fa0447d66cf742bf05aee3e
4
+ data.tar.gz: 820ef6cac234149c3f3e69069c5255598b27d161dea7c60939d06999d16d9c4a
5
5
  SHA512:
6
- metadata.gz: 5f86f17e33bfd43248d7dc1a122f6ed3d85a0fcf3e745ca805561d7d490c3497e1bfaa53339ed28ed068a401ed91447888eb5f92a89654d89fe3b8ae0fd7c5b1
7
- data.tar.gz: fea980a035f785508e1bc5444fce65c7a8c0824307c48e4db7ea73fcf7218f23889c1c687d999fc252f3ffb97fc732b1aae8292ec65938f40d97c48b95a470c7
6
+ metadata.gz: 87cc5da11a46b54e9399ecb678c450a6f71053807a2f80bb22b788ef7c4c359b2cbcb2cc6bce4e1691763336ecd9c9ef4d9540f6a80f587f013188142a7d0b1c
7
+ data.tar.gz: 3cd29156eda196448f5f31eed27e5b8d7483e7a8f2f91d70ab6adc58a039a3e5cb19568a6b1a7bc94e38bee904cc4a495e9c698428117e8cd5aba4c09f6e9342
@@ -4,6 +4,7 @@
4
4
  //= require mbeditor/file_service
5
5
  //= require mbeditor/websocket_service
6
6
  //= require mbeditor/git_service
7
+ //= require mbeditor/conflict_parser
7
8
  //= require mbeditor/search_service
8
9
  //= require mbeditor/tab_manager
9
10
  //= require mbeditor/editor_plugins
@@ -36,9 +36,17 @@ var EditorPanel = function EditorPanel(_ref) {
36
36
  var monacoRef = useRef(null);
37
37
  var latestContentRef = useRef('');
38
38
  var lastAppliedExternalVersionRef = useRef(0);
39
+ var conflictDecorationsRef = React.useRef([]);
40
+ var conflictBlocksRef = React.useRef([]);
39
41
  var aviBaseRef = useRef(0);
40
42
  var aviMaxRef = useRef(0);
41
43
 
44
+ var _conflictState = React.useState(0);
45
+ var conflictCount = _conflictState[0];
46
+ var setConflictCount = _conflictState[1];
47
+
48
+ var currentConflictIndexRef = React.useRef(0);
49
+
42
50
  var _useState = useState('');
43
51
  var _useState2 = _slicedToArray(_useState, 2);
44
52
  var markup = _useState2[0];
@@ -848,6 +856,54 @@ var EditorPanel = function EditorPanel(_ref) {
848
856
  }
849
857
  }, [tab.content, tab.externalContentVersion]);
850
858
 
859
+ useEffect(function () {
860
+ var editor = monacoRef.current;
861
+ if (!editor || !window.monaco || typeof tab.content !== 'string') return;
862
+ var model = editor.getModel();
863
+ if (!model) return;
864
+
865
+ if (!ConflictParser.hasConflicts(tab.content)) {
866
+ conflictDecorationsRef.current = model.deltaDecorations(conflictDecorationsRef.current, []);
867
+ conflictBlocksRef.current = [];
868
+ setConflictCount(0);
869
+ return;
870
+ }
871
+
872
+ var blocks = ConflictParser.parse(tab.content);
873
+ conflictBlocksRef.current = blocks;
874
+ setConflictCount(blocks.length);
875
+
876
+ var decorations = [];
877
+ blocks.forEach(function (block) {
878
+ decorations.push({
879
+ range: new window.monaco.Range(block.startLine + 1, 1, block.startLine + 1, 1),
880
+ options: { isWholeLine: true, className: 'mb-conflict-marker-line' }
881
+ });
882
+ if (block.headEnd > block.startLine + 1) {
883
+ decorations.push({
884
+ range: new window.monaco.Range(block.startLine + 2, 1, block.headEnd, 1),
885
+ options: { isWholeLine: true, className: 'mb-conflict-head' }
886
+ });
887
+ }
888
+ decorations.push({
889
+ range: new window.monaco.Range(block.dividerLine + 1, 1, block.dividerLine + 1, 1),
890
+ options: { isWholeLine: true, className: 'mb-conflict-marker-line' }
891
+ });
892
+ if (block.endLine > block.dividerLine + 1) {
893
+ decorations.push({
894
+ range: new window.monaco.Range(block.dividerLine + 2, 1, block.endLine, 1),
895
+ options: { isWholeLine: true, className: 'mb-conflict-incoming' }
896
+ });
897
+ }
898
+ decorations.push({
899
+ range: new window.monaco.Range(block.endLine + 1, 1, block.endLine + 1, 1),
900
+ options: { isWholeLine: true, className: 'mb-conflict-marker-line' }
901
+ });
902
+ });
903
+
904
+ conflictDecorationsRef.current = model.deltaDecorations(conflictDecorationsRef.current, decorations);
905
+ }, [tab.content, tab.externalContentVersion]);
906
+
851
907
  // Apply editorPrefs changes to a running editor without remounting
852
908
  useEffect(function () {
853
909
  if (!window.monaco) return;
@@ -1458,6 +1514,57 @@ var EditorPanel = function EditorPanel(_ref) {
1458
1514
  );
1459
1515
  }
1460
1516
 
1517
+ function resolveConflict(blockIndex, resolution) {
1518
+ var editor = monacoRef.current;
1519
+ if (!editor || !window.monaco) return;
1520
+ var blocks = conflictBlocksRef.current;
1521
+ if (blockIndex < 0 || blockIndex >= blocks.length) return;
1522
+ var block = blocks[blockIndex];
1523
+ var model = editor.getModel();
1524
+ if (!model) return;
1525
+
1526
+ var resolvedContent;
1527
+ if (resolution === 'head') {
1528
+ resolvedContent = block.headContent;
1529
+ } else if (resolution === 'incoming') {
1530
+ resolvedContent = block.incomingContent;
1531
+ } else {
1532
+ resolvedContent = block.headContent +
1533
+ (block.headContent && block.incomingContent ? '\n' : '') +
1534
+ block.incomingContent;
1535
+ }
1536
+
1537
+ editor.pushUndoStop();
1538
+ editor.executeEdits('conflict-resolve', [{
1539
+ range: new window.monaco.Range(
1540
+ block.startLine + 1, 1,
1541
+ block.endLine + 1,
1542
+ model.getLineMaxColumn(block.endLine + 1)
1543
+ ),
1544
+ text: resolvedContent
1545
+ }]);
1546
+ editor.pushUndoStop();
1547
+
1548
+ var newContent = editor.getValue();
1549
+ EditorStore.setState({
1550
+ panes: EditorStore.getState().panes.map(function (p) {
1551
+ return Object.assign({}, p, {
1552
+ tabs: p.tabs.map(function (t) {
1553
+ if (t.path !== tab.path) return t;
1554
+ return Object.assign({}, t, {
1555
+ content: newContent,
1556
+ dirty: true,
1557
+ externalContentVersion: (t.externalContentVersion || 0) + 1
1558
+ });
1559
+ })
1560
+ });
1561
+ })
1562
+ });
1563
+
1564
+ var remaining = conflictBlocksRef.current.length - 1;
1565
+ currentConflictIndexRef.current = Math.min(currentConflictIndexRef.current, Math.max(0, remaining - 1));
1566
+ }
1567
+
1461
1568
  // Always render the same wrapper structure so the editorRef div is never
1462
1569
  // unmounted when gitAvailable changes (e.g. loaded async after workspace
1463
1570
  // call returns). The toolbar is conditionally included inside the wrapper.
@@ -1532,6 +1639,65 @@ var EditorPanel = function EditorPanel(_ref) {
1532
1639
  !editorPrefs.toolbarIconOnly && !testLoading && React.createElement('span', { className: 'ide-toolbar-label' }, 'Test')
1533
1640
  )
1534
1641
  ),
1642
+ conflictCount > 0 && React.createElement(
1643
+ 'div', { className: 'mb-conflict-banner' },
1644
+ React.createElement(
1645
+ 'span', { className: 'mb-conflict-count' },
1646
+ React.createElement('i', { className: 'fas fa-code-merge' }),
1647
+ ' ',
1648
+ conflictCount,
1649
+ ' merge conflict',
1650
+ conflictCount !== 1 ? 's' : ''
1651
+ ),
1652
+ React.createElement(
1653
+ 'div', { className: 'mb-conflict-nav' },
1654
+ React.createElement('button', {
1655
+ className: 'mb-btn mb-btn-sm',
1656
+ title: 'Previous conflict',
1657
+ onClick: function () {
1658
+ var idx = Math.max(0, currentConflictIndexRef.current - 1);
1659
+ currentConflictIndexRef.current = idx;
1660
+ var b = conflictBlocksRef.current[idx];
1661
+ if (b && monacoRef.current) {
1662
+ monacoRef.current.revealLineInCenter(b.startLine + 1);
1663
+ monacoRef.current.setPosition({ lineNumber: b.startLine + 1, column: 1 });
1664
+ }
1665
+ }
1666
+ }, '↑ Prev'),
1667
+ React.createElement('button', {
1668
+ className: 'mb-btn mb-btn-sm',
1669
+ title: 'Next conflict',
1670
+ onClick: function () {
1671
+ var blocks = conflictBlocksRef.current;
1672
+ var idx = Math.min(blocks.length - 1, currentConflictIndexRef.current + 1);
1673
+ currentConflictIndexRef.current = idx;
1674
+ var b = blocks[idx];
1675
+ if (b && monacoRef.current) {
1676
+ monacoRef.current.revealLineInCenter(b.startLine + 1);
1677
+ monacoRef.current.setPosition({ lineNumber: b.startLine + 1, column: 1 });
1678
+ }
1679
+ }
1680
+ }, '↓ Next')
1681
+ ),
1682
+ React.createElement(
1683
+ 'div', { className: 'mb-conflict-actions' },
1684
+ React.createElement('button', {
1685
+ className: 'mb-btn mb-btn-sm mb-btn-success',
1686
+ title: 'Accept current (HEAD) — keep your local changes',
1687
+ onClick: function () { resolveConflict(currentConflictIndexRef.current, 'head'); }
1688
+ }, 'Accept Current'),
1689
+ React.createElement('button', {
1690
+ className: 'mb-btn mb-btn-sm mb-btn-incoming',
1691
+ title: 'Accept incoming — take the changes being merged in',
1692
+ onClick: function () { resolveConflict(currentConflictIndexRef.current, 'incoming'); }
1693
+ }, 'Accept Incoming'),
1694
+ React.createElement('button', {
1695
+ className: 'mb-btn mb-btn-sm',
1696
+ title: 'Accept both — keep current above incoming',
1697
+ onClick: function () { resolveConflict(currentConflictIndexRef.current, 'both'); }
1698
+ }, 'Accept Both')
1699
+ )
1700
+ ),
1535
1701
  tab.truncated && React.createElement(
1536
1702
  'div',
1537
1703
  {
@@ -111,6 +111,44 @@ var SectionActionGroup = function SectionActionGroup(_ref2) {
111
111
  );
112
112
  };
113
113
 
114
+ function FileReloadBanner(_ref) {
115
+ var pendingReloads = _ref.pendingReloads;
116
+ var onSaveAndReload = _ref.onSaveAndReload;
117
+ var onDiscardAndReload = _ref.onDiscardAndReload;
118
+ var onKeepMine = _ref.onKeepMine;
119
+ if (!pendingReloads || pendingReloads.length === 0) return null;
120
+ return React.createElement(
121
+ 'div', { className: 'mb-file-reload-banner' },
122
+ pendingReloads.map(function (r) {
123
+ return React.createElement(
124
+ 'div', { key: r.paneId + ':' + r.tabId, className: 'mb-file-reload-item' },
125
+ React.createElement(
126
+ 'span', { className: 'mb-file-reload-msg' },
127
+ React.createElement('i', { className: 'fas fa-sync-alt' }),
128
+ ' ',
129
+ React.createElement('strong', null, r.name),
130
+ ' was updated externally'
131
+ ),
132
+ React.createElement(
133
+ 'div', { className: 'mb-file-reload-actions' },
134
+ React.createElement('button', {
135
+ className: 'mb-btn mb-btn-sm mb-btn-primary',
136
+ onClick: function () { onSaveAndReload(r); }
137
+ }, 'Save & Reload'),
138
+ React.createElement('button', {
139
+ className: 'mb-btn mb-btn-sm mb-btn-warning',
140
+ onClick: function () { onDiscardAndReload(r); }
141
+ }, 'Discard & Reload'),
142
+ React.createElement('button', {
143
+ className: 'mb-btn mb-btn-sm',
144
+ onClick: function () { onKeepMine(r); }
145
+ }, 'Keep Mine')
146
+ )
147
+ );
148
+ })
149
+ );
150
+ }
151
+
114
152
  var MbeditorApp = function MbeditorApp() {
115
153
  var _useState = useState(EditorStore.getState());
116
154
 
@@ -1099,12 +1137,77 @@ var MbeditorApp = function MbeditorApp() {
1099
1137
  SearchService.buildIndex(newData);
1100
1138
  return newData;
1101
1139
  });
1140
+ checkOpenTabsForExternalChanges();
1102
1141
  })["catch"](function () {});
1103
1142
  }
1104
1143
  WebSocketService.onFilesChanged(handleFilesChanged);
1105
1144
  return function () { WebSocketService.offFilesChanged(handleFilesChanged); };
1106
1145
  }, []);
1107
1146
 
1147
+ function checkOpenTabsForExternalChanges() {
1148
+ var st = EditorStore.getState();
1149
+ var allTabs = st.panes.reduce(function (acc, p) {
1150
+ return acc.concat(p.tabs.map(function (t) { return { paneId: p.id, tab: t }; }));
1151
+ }, []);
1152
+ var fileTabs = allTabs.filter(function (pt) {
1153
+ var path = pt.tab.path || '';
1154
+ return path &&
1155
+ !path.startsWith('mbeditor://') &&
1156
+ !path.startsWith('diff://') &&
1157
+ !path.startsWith('combined-diff://') &&
1158
+ !pt.tab.isCombinedDiff &&
1159
+ !pt.tab.isSettings &&
1160
+ !pt.tab.isImage &&
1161
+ !pt.tab.isDiff &&
1162
+ typeof pt.tab.content === 'string';
1163
+ });
1164
+ fileTabs.forEach(function (pt) {
1165
+ FileService.getFile(pt.tab.path, { allowMissing: true }).then(function (data) {
1166
+ if (!data || typeof data.content !== 'string') return;
1167
+ var serverNorm = data.content.replace(/\r\n/g, '\n');
1168
+ var tabNorm = (pt.tab.content || '').replace(/\r\n/g, '\n');
1169
+ if (serverNorm === tabNorm) return;
1170
+ if (!pt.tab.dirty) {
1171
+ EditorStore.setState({
1172
+ panes: EditorStore.getState().panes.map(function (p) {
1173
+ if (p.id !== pt.paneId) return p;
1174
+ return Object.assign({}, p, {
1175
+ tabs: p.tabs.map(function (t) {
1176
+ if (t.id !== pt.tab.id) return t;
1177
+ return Object.assign({}, t, {
1178
+ content: data.content,
1179
+ externalContentVersion: (t.externalContentVersion || 0) + 1
1180
+ });
1181
+ })
1182
+ });
1183
+ })
1184
+ });
1185
+ } else {
1186
+ // Re-verify the tab still exists before queuing
1187
+ var currentState = EditorStore.getState();
1188
+ var stillExists = currentState.panes.some(function (p) {
1189
+ return p.id === pt.paneId && p.tabs.some(function (t) { return t.id === pt.tab.id; });
1190
+ });
1191
+ if (!stillExists) return;
1192
+ var existing = EditorStore.getState().pendingReloads.find(function (r) {
1193
+ return r.paneId === pt.paneId && r.tabId === pt.tab.id;
1194
+ });
1195
+ if (!existing) {
1196
+ EditorStore.setState({
1197
+ pendingReloads: EditorStore.getState().pendingReloads.concat([{
1198
+ paneId: pt.paneId,
1199
+ tabId: pt.tab.id,
1200
+ path: pt.tab.path,
1201
+ name: pt.tab.name,
1202
+ serverContent: data.content
1203
+ }])
1204
+ });
1205
+ }
1206
+ }
1207
+ })["catch"](function () {});
1208
+ });
1209
+ }
1210
+
1108
1211
  // Auto-refresh the file tree every 10s to pick up external changes (new files, deletions, etc.)
1109
1212
  // When an ActionCable WebSocket is connected this acts only as a safety-net fallback —
1110
1213
  // the WebSocket push above handles immediate invalidation after mbeditor mutations.
@@ -1168,6 +1271,13 @@ var MbeditorApp = function MbeditorApp() {
1168
1271
  setClosingTabId(id);
1169
1272
  } else {
1170
1273
  TabManager.closeTab(paneId, id);
1274
+ EditorStore.setState({
1275
+ pendingReloads: EditorStore.getState().pendingReloads.filter(function (r) {
1276
+ return EditorStore.getState().panes.some(function (p) {
1277
+ return p.tabs.some(function (t) { return t.id === r.tabId; });
1278
+ });
1279
+ })
1280
+ });
1171
1281
  }
1172
1282
  };
1173
1283
 
@@ -1199,6 +1309,13 @@ var MbeditorApp = function MbeditorApp() {
1199
1309
  _closeEntry.cleanVersionId = _closeEntry.model.getAlternativeVersionId();
1200
1310
  }
1201
1311
  TabManager.closeTab(closingPaneId, tab.id);
1312
+ EditorStore.setState({
1313
+ pendingReloads: EditorStore.getState().pendingReloads.filter(function (r) {
1314
+ return EditorStore.getState().panes.some(function (p) {
1315
+ return p.tabs.some(function (t) { return t.id === r.tabId; });
1316
+ });
1317
+ })
1318
+ });
1202
1319
  })["catch"](function (err) {
1203
1320
  EditorStore.setStatus("Save failed: " + err.message, "error");
1204
1321
  })["finally"](function () {
@@ -1210,6 +1327,13 @@ var MbeditorApp = function MbeditorApp() {
1210
1327
  });
1211
1328
  } else {
1212
1329
  TabManager.closeTab(closingPaneId, tab.id);
1330
+ EditorStore.setState({
1331
+ pendingReloads: EditorStore.getState().pendingReloads.filter(function (r) {
1332
+ return EditorStore.getState().panes.some(function (p) {
1333
+ return p.tabs.some(function (t) { return t.id === r.tabId; });
1334
+ });
1335
+ })
1336
+ });
1213
1337
  setClosingTabId(null);
1214
1338
  setClosingPaneId(null);
1215
1339
  }
@@ -1472,6 +1596,64 @@ var MbeditorApp = function MbeditorApp() {
1472
1596
  });
1473
1597
  };
1474
1598
 
1599
+ function dismissPendingReload(reload) {
1600
+ EditorStore.setState({
1601
+ pendingReloads: EditorStore.getState().pendingReloads.filter(function (r) {
1602
+ return !(r.paneId === reload.paneId && r.tabId === reload.tabId);
1603
+ })
1604
+ });
1605
+ }
1606
+
1607
+ function handleSaveAndReload(reload) {
1608
+ var st = EditorStore.getState();
1609
+ var pane = st.panes.find(function (p) { return p.id === reload.paneId; });
1610
+ var tab = pane && pane.tabs.find(function (t) { return t.id === reload.tabId; });
1611
+ if (!tab) { dismissPendingReload(reload); return; }
1612
+ FileService.saveFile(tab.path, tab.content).then(function () {
1613
+ EditorStore.setState({
1614
+ panes: EditorStore.getState().panes.map(function (p) {
1615
+ if (p.id !== reload.paneId) return p;
1616
+ return Object.assign({}, p, {
1617
+ tabs: p.tabs.map(function (t) {
1618
+ if (t.id !== reload.tabId) return t;
1619
+ return Object.assign({}, t, {
1620
+ content: tab.content,
1621
+ dirty: false,
1622
+ externalContentVersion: (t.externalContentVersion || 0) + 1
1623
+ });
1624
+ })
1625
+ });
1626
+ })
1627
+ });
1628
+ dismissPendingReload(reload);
1629
+ })["catch"](function () {
1630
+ EditorStore.setStatus('Save failed — cannot reload', 'error');
1631
+ });
1632
+ }
1633
+
1634
+ function handleDiscardAndReload(reload) {
1635
+ EditorStore.setState({
1636
+ panes: EditorStore.getState().panes.map(function (p) {
1637
+ if (p.id !== reload.paneId) return p;
1638
+ return Object.assign({}, p, {
1639
+ tabs: p.tabs.map(function (t) {
1640
+ if (t.id !== reload.tabId) return t;
1641
+ return Object.assign({}, t, {
1642
+ content: reload.serverContent,
1643
+ dirty: false,
1644
+ externalContentVersion: (t.externalContentVersion || 0) + 1
1645
+ });
1646
+ })
1647
+ });
1648
+ })
1649
+ });
1650
+ dismissPendingReload(reload);
1651
+ }
1652
+
1653
+ function handleKeepMine(reload) {
1654
+ dismissPendingReload(reload);
1655
+ }
1656
+
1475
1657
  var handleSaveAll = function handleSaveAll() {
1476
1658
  var dirtyTabs = state.panes.flatMap(function (p) {
1477
1659
  return p.tabs;
@@ -3775,6 +3957,12 @@ var MbeditorApp = function MbeditorApp() {
3775
3957
  React.Fragment,
3776
3958
  null,
3777
3959
  renderTabBar(pane.id, pane.tabs, pane.activeTabId),
3960
+ React.createElement(FileReloadBanner, {
3961
+ pendingReloads: (state.pendingReloads || []).filter(function (r) { return r.paneId === pane.id; }),
3962
+ onSaveAndReload: handleSaveAndReload,
3963
+ onDiscardAndReload: handleDiscardAndReload,
3964
+ onKeepMine: handleKeepMine
3965
+ }),
3778
3966
  React.createElement(
3779
3967
  "div",
3780
3968
  { style: { flex: 1, minHeight: 0, display: 'flex', flexDirection: 'column', visibility: activeResizeMode === 'pane' ? 'hidden' : 'visible' } },
@@ -0,0 +1,48 @@
1
+ var ConflictParser = (function () {
2
+ function hasConflicts(content) {
3
+ return /^<<<<<<< /m.test(content);
4
+ }
5
+
6
+ function parse(content) {
7
+ var blocks = [];
8
+ var lines = content.split('\n');
9
+ var state = null;
10
+ var headStart = -1, headEnd = -1, dividerLine = -1;
11
+ var headLines = [], incomingLines = [];
12
+ var marker = null;
13
+
14
+ for (var i = 0; i < lines.length; i++) {
15
+ var line = lines[i];
16
+ if (/^<<<<<<< /.test(line) && state === null) {
17
+ state = 'head';
18
+ headStart = i;
19
+ headLines = [];
20
+ marker = line;
21
+ } else if (/^=======\s*$/.test(line) && state === 'head') {
22
+ state = 'incoming';
23
+ headEnd = i;
24
+ dividerLine = i;
25
+ incomingLines = [];
26
+ } else if (/^>>>>>>> /.test(line) && state === 'incoming') {
27
+ blocks.push({
28
+ startLine: headStart,
29
+ headEnd: headEnd,
30
+ dividerLine: dividerLine,
31
+ endLine: i,
32
+ headContent: headLines.join('\n'),
33
+ incomingContent: incomingLines.join('\n'),
34
+ marker: marker,
35
+ endMarker: line
36
+ });
37
+ state = null;
38
+ } else if (state === 'head') {
39
+ headLines.push(line);
40
+ } else if (state === 'incoming') {
41
+ incomingLines.push(line);
42
+ }
43
+ }
44
+ return blocks;
45
+ }
46
+
47
+ return { hasConflicts: hasConflicts, parse: parse };
48
+ })();
@@ -15,6 +15,7 @@ var EditorStore = (function () {
15
15
  searchResults: [],
16
16
  searchCapped: false,
17
17
  statusMessage: { text: "", kind: "info" },
18
+ pendingReloads: [],
18
19
  canUndo: false,
19
20
  canRedo: false,
20
21
  };
@@ -861,3 +861,115 @@
861
861
  }
862
862
 
863
863
  .cdiff-ctx { color: var(--ide-text-muted); }
864
+
865
+ /* ── File Reload Banner ─────────────────────────────────────────── */
866
+ .mb-file-reload-banner {
867
+ display: flex;
868
+ flex-direction: column;
869
+ gap: 0;
870
+ flex-shrink: 0;
871
+ }
872
+
873
+ .mb-file-reload-item {
874
+ display: flex;
875
+ align-items: center;
876
+ justify-content: space-between;
877
+ padding: 5px 12px;
878
+ background: #2a2600;
879
+ border-bottom: 1px solid #5a5000;
880
+ font-size: 12px;
881
+ color: #e3d286;
882
+ gap: 12px;
883
+ }
884
+
885
+ .mb-file-reload-msg {
886
+ display: flex;
887
+ align-items: center;
888
+ gap: 6px;
889
+ flex: 1;
890
+ min-width: 0;
891
+ overflow: hidden;
892
+ text-overflow: ellipsis;
893
+ white-space: nowrap;
894
+ }
895
+
896
+ .mb-file-reload-actions {
897
+ display: flex;
898
+ gap: 5px;
899
+ flex-shrink: 0;
900
+ }
901
+
902
+ .mb-btn-warning {
903
+ background: #6b4500;
904
+ color: #ffd88a;
905
+ border: 1px solid #8a5900;
906
+ }
907
+
908
+ .mb-btn-warning:hover {
909
+ background: #8a5900;
910
+ }
911
+
912
+ /* ── Merge Conflict Decorations ─────────────────────────────────── */
913
+ .mb-conflict-marker-line {
914
+ background: rgba(120, 80, 0, 0.35) !important;
915
+ }
916
+
917
+ .mb-conflict-head {
918
+ background: rgba(180, 60, 60, 0.18) !important;
919
+ border-left: 2px solid rgba(200, 80, 80, 0.6);
920
+ }
921
+
922
+ .mb-conflict-incoming {
923
+ background: rgba(60, 130, 60, 0.18) !important;
924
+ border-left: 2px solid rgba(80, 160, 80, 0.6);
925
+ }
926
+
927
+ /* ── Conflict Banner ─────────────────────────────────────────────── */
928
+ .mb-conflict-banner {
929
+ display: flex;
930
+ align-items: center;
931
+ gap: 10px;
932
+ padding: 5px 12px;
933
+ background: #1e1a00;
934
+ border-bottom: 1px solid #5a5000;
935
+ font-size: 12px;
936
+ color: #e3d286;
937
+ flex-shrink: 0;
938
+ }
939
+
940
+ .mb-conflict-count {
941
+ font-weight: bold;
942
+ color: #f5a623;
943
+ white-space: nowrap;
944
+ }
945
+
946
+ .mb-conflict-nav {
947
+ display: flex;
948
+ gap: 4px;
949
+ }
950
+
951
+ .mb-conflict-actions {
952
+ display: flex;
953
+ gap: 5px;
954
+ margin-left: auto;
955
+ }
956
+
957
+ .mb-btn-success {
958
+ background: #1a5c2a;
959
+ color: #aef;
960
+ border: 1px solid #2a7a3a;
961
+ }
962
+
963
+ .mb-btn-success:hover {
964
+ background: #1e7a38;
965
+ }
966
+
967
+ .mb-btn-incoming {
968
+ background: #1a3060;
969
+ color: #aef;
970
+ border: 1px solid #1a4080;
971
+ }
972
+
973
+ .mb-btn-incoming:hover {
974
+ background: #1a4080;
975
+ }
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Mbeditor
4
- VERSION = "0.5.3"
4
+ VERSION = "0.5.4"
5
5
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: mbeditor
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.5.3
4
+ version: 0.5.4
5
5
  platform: ruby
6
6
  authors:
7
7
  - Oliver Noonan
@@ -71,6 +71,7 @@ files:
71
71
  - app/assets/javascripts/mbeditor/components/ShortcutHelp.js
72
72
  - app/assets/javascripts/mbeditor/components/TabBar.js
73
73
  - app/assets/javascripts/mbeditor/components/TestResultsPanel.js
74
+ - app/assets/javascripts/mbeditor/conflict_parser.js
74
75
  - app/assets/javascripts/mbeditor/editor_plugins.js
75
76
  - app/assets/javascripts/mbeditor/editor_store.js
76
77
  - app/assets/javascripts/mbeditor/file_icon.js