@lvce-editor/output-view 2.10.0 → 2.11.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/README.md CHANGED
@@ -10,7 +10,3 @@ cd output-view &&
10
10
  npm ci &&
11
11
  npm test
12
12
  ```
13
-
14
- ## Gitpod
15
-
16
- [![Open in Gitpod](https://gitpod.io/button/open-in-gitpod.svg)](https://gitpod.io/#https://github.com/lvce-editor/output-view)
@@ -991,6 +991,7 @@ const ToolBar = 'toolbar';
991
991
  const Button = 1;
992
992
  const Div = 4;
993
993
  const Input = 6;
994
+ const Span = 8;
994
995
  const Text$1 = 12;
995
996
  const A = 53;
996
997
  const Select$1 = 63;
@@ -1241,10 +1242,18 @@ const terminate = () => {
1241
1242
  globalThis.close();
1242
1243
  };
1243
1244
 
1245
+ const Text = 1;
1246
+ const Link = 2;
1247
+ const LogLevel = 3;
1248
+ const RepeatCount = 4;
1249
+
1244
1250
  const getTextFromParts = parts => {
1245
1251
  let result = '';
1246
1252
  for (const part of parts) {
1247
- result += part.value;
1253
+ if (part.type === RepeatCount) {
1254
+ continue;
1255
+ }
1256
+ result += part.type === Link ? part.label || part.value : part.value;
1248
1257
  }
1249
1258
  return result;
1250
1259
  };
@@ -1252,7 +1261,37 @@ const filterItems = (items, filterValue) => {
1252
1261
  if (!filterValue) {
1253
1262
  return items;
1254
1263
  }
1255
- return items.filter(parts => getTextFromParts(parts).includes(filterValue));
1264
+ const normalizedFilterValue = filterValue.toLowerCase();
1265
+ return items.filter(parts => getTextFromParts(parts).toLowerCase().includes(normalizedFilterValue));
1266
+ };
1267
+
1268
+ const getAggregationKey = line => {
1269
+ return JSON.stringify(line.filter(part => part.type !== RepeatCount));
1270
+ };
1271
+ const withRepeatCount = (line, count) => {
1272
+ const parts = line.filter(part => part.type !== RepeatCount);
1273
+ return [{
1274
+ type: RepeatCount,
1275
+ value: String(count)
1276
+ }, ...parts];
1277
+ };
1278
+ const aggregateLines = lines => {
1279
+ const result = [];
1280
+ let previousKey = '';
1281
+ let repeatCount = 0;
1282
+ for (const line of lines) {
1283
+ const key = getAggregationKey(line);
1284
+ if (result.length > 0 && key === previousKey) {
1285
+ repeatCount++;
1286
+ const previousIndex = result.length - 1;
1287
+ result[previousIndex] = withRepeatCount(result.at(-1), repeatCount);
1288
+ continue;
1289
+ }
1290
+ result.push(line);
1291
+ previousKey = key;
1292
+ repeatCount = 1;
1293
+ }
1294
+ return result;
1256
1295
  };
1257
1296
 
1258
1297
  const isFileNotFoundError = error => {
@@ -1265,10 +1304,75 @@ const getLinkMatch = text => {
1265
1304
  return match ? match[0] : null;
1266
1305
  };
1267
1306
 
1268
- const Text = 1;
1269
- const Link = 2;
1307
+ const Filter$2 = 'Filter';
1308
+ const IconButton = 'IconButton';
1309
+ const InputBox = 'InputBox';
1310
+ const MaskIcon = 'MaskIcon';
1311
+ const FilterInput = 'FilterInput';
1312
+ const OutputContent = 'OutputContent';
1313
+ const Actions = 'Actions';
1314
+ const Message = 'Message';
1315
+ const Viewlet = 'Viewlet';
1316
+ const Output$1 = 'Output';
1317
+ const Option = 'Option';
1318
+ const Select = 'Select';
1319
+ const Error$1 = 'Error';
1320
+ const Line = 'Line';
1321
+ const OutputRepeatCount = 'OutputRepeatCount';
1322
+ const OutputSourceLink = 'OutputSourceLink';
1323
+
1324
+ const isStructuredLogEntry = value => {
1325
+ return value?.category === 'Window' && typeof value.level === 'string' && typeof value.line === 'number' && typeof value.message === 'string' && typeof value.source === 'string' && typeof value.timestamp === 'string';
1326
+ };
1327
+ const getSourceLabel = (source, line) => {
1328
+ const withoutQueryOrFragment = source.split(/[?#]/, 1)[0];
1329
+ const slashIndex = withoutQueryOrFragment.lastIndexOf('/');
1330
+ const name = slashIndex === -1 ? withoutQueryOrFragment : withoutQueryOrFragment.slice(slashIndex + 1);
1331
+ return line > 0 ? `${name}:${line}` : name;
1332
+ };
1333
+ const normalizeLevel = level => {
1334
+ return level === 'warn' ? 'warning' : level;
1335
+ };
1336
+ const parseStructuredLogLine = line => {
1337
+ if (!line.startsWith('{')) {
1338
+ return undefined;
1339
+ }
1340
+ let parsed;
1341
+ try {
1342
+ parsed = JSON.parse(line);
1343
+ } catch {
1344
+ return undefined;
1345
+ }
1346
+ if (!isStructuredLogEntry(parsed)) {
1347
+ return undefined;
1348
+ }
1349
+ const parts = [{
1350
+ type: LogLevel,
1351
+ value: normalizeLevel(parsed.level)
1352
+ }, {
1353
+ type: Text,
1354
+ value: parsed.message
1355
+ }];
1356
+ if (!parsed.source) {
1357
+ return parts;
1358
+ }
1359
+ const href = parsed.line > 0 ? `${parsed.source}:${parsed.line}` : parsed.source;
1360
+ return [...parts, {
1361
+ type: Text,
1362
+ value: ' '
1363
+ }, {
1364
+ className: OutputSourceLink,
1365
+ label: getSourceLabel(parsed.source, parsed.line),
1366
+ type: Link,
1367
+ value: href
1368
+ }];
1369
+ };
1270
1370
 
1271
1371
  const parseLine = line => {
1372
+ const structuredLine = parseStructuredLogLine(line);
1373
+ if (structuredLine) {
1374
+ return structuredLine;
1375
+ }
1272
1376
  const parts = [];
1273
1377
  let rest = line;
1274
1378
  while (rest.length > 0) {
@@ -1308,7 +1412,7 @@ const loadLines = async uri => {
1308
1412
  try {
1309
1413
  // TODO use log stream, updating the output when the file is changed
1310
1414
  const content = await readFile(uri);
1311
- const lines = content.split('\n').map(parseLine);
1415
+ const lines = aggregateLines(content.split('\n').map(parseLine));
1312
1416
  return {
1313
1417
  code: 0,
1314
1418
  error: '',
@@ -1549,8 +1653,8 @@ const getKeyBindings = () => {
1549
1653
  }];
1550
1654
  };
1551
1655
 
1552
- const Filter$2 = 'filter';
1553
- const Output$1 = 'output';
1656
+ const Filter$1 = 'filter';
1657
+ const Output = 'output';
1554
1658
  const MainProcess = 'MainProcess';
1555
1659
  const SharedProcess = 'SharedProcess';
1556
1660
  const Window = 'Window';
@@ -1761,7 +1865,7 @@ const i18nString = (key, placeholders = emptyObject) => {
1761
1865
  };
1762
1866
 
1763
1867
  const ClearOutput = 'clear output';
1764
- const Filter$1 = 'Filter';
1868
+ const Filter = 'Filter';
1765
1869
  const LogFileNotFound$1 = 'The log file was not found.';
1766
1870
  const Settings = 'Settings';
1767
1871
  const TurnOffAutoScroll = 'Turn auto scrolling off';
@@ -1779,7 +1883,7 @@ const logFileNotFound = () => {
1779
1883
  return i18nString(LogFileNotFound$1);
1780
1884
  };
1781
1885
  const filter = () => {
1782
- return i18nString(Filter$1);
1886
+ return i18nString(Filter);
1783
1887
  };
1784
1888
 
1785
1889
  const loadButtons = () => {
@@ -1971,47 +2075,69 @@ const refresh = async state => {
1971
2075
  };
1972
2076
 
1973
2077
  const renderFilterValue = (oldState, newState) => {
1974
- return ['Viewlet.setValueByName', Filter$2, newState.filterValue];
2078
+ return ['Viewlet.setValueByName', Filter$1, newState.filterValue];
1975
2079
  };
1976
2080
 
1977
- const Filter = 'Filter';
1978
- const IconButton = 'IconButton';
1979
- const InputBox = 'InputBox';
1980
- const MaskIcon = 'MaskIcon';
1981
- const FilterInput = 'FilterInput';
1982
- const OutputContent = 'OutputContent';
1983
- const Actions = 'Actions';
1984
- const Message = 'Message';
1985
- const Viewlet = 'Viewlet';
1986
- const Output = 'Output';
1987
- const Option = 'Option';
1988
- const Select = 'Select';
1989
- const Error$1 = 'Error';
1990
- const Line = 'Line';
1991
-
1992
- const parentNode = {
2081
+ const noNodes = [];
2082
+ const repeatCountNode = {
1993
2083
  childCount: 1,
1994
- className: Line,
1995
- type: Div
2084
+ className: OutputRepeatCount,
2085
+ type: Span
2086
+ };
2087
+ const spaceNode = text(' ');
2088
+ const getLevel = parts => {
2089
+ return parts.find(part => part.type === LogLevel)?.value || '';
2090
+ };
2091
+ const getLinePartDom = part => {
2092
+ switch (part.type) {
2093
+ case Link:
2094
+ {
2095
+ const linkNode = {
2096
+ childCount: 1,
2097
+ href: part.value,
2098
+ rel: 'noopener noreferrer',
2099
+ target: '_blank',
2100
+ type: A,
2101
+ ...(part.className && {
2102
+ className: part.className
2103
+ })
2104
+ };
2105
+ return {
2106
+ childCount: 1,
2107
+ nodes: [linkNode, text(part.label || part.value)]
2108
+ };
2109
+ }
2110
+ case RepeatCount:
2111
+ return {
2112
+ childCount: 2,
2113
+ nodes: [repeatCountNode, text(part.value), spaceNode]
2114
+ };
2115
+ case Text:
2116
+ return {
2117
+ childCount: 1,
2118
+ nodes: [text(part.value)]
2119
+ };
2120
+ default:
2121
+ return {
2122
+ childCount: 0,
2123
+ nodes: noNodes
2124
+ };
2125
+ }
1996
2126
  };
1997
2127
  const getLineDom = parts => {
1998
2128
  const children = [];
2129
+ let childCount = 0;
1999
2130
  for (const part of parts) {
2000
- if (part.type === Text) {
2001
- children.push(text(part.value));
2002
- } else {
2003
- children.push({
2004
- childCount: 1,
2005
- href: part.value,
2006
- rel: 'noopener noreferrer',
2007
- target: '_blank',
2008
- type: A
2009
- }, text(part.value));
2010
- }
2131
+ const partDom = getLinePartDom(part);
2132
+ children.push(...partDom.nodes);
2133
+ childCount += partDom.childCount;
2011
2134
  }
2135
+ const level = getLevel(parts);
2136
+ const className = level ? mergeClassNames(Line, level) : Line;
2012
2137
  const lineNode = {
2013
- ...parentNode,
2014
- childCount: children.length
2138
+ childCount,
2139
+ className,
2140
+ type: Div
2015
2141
  };
2016
2142
  return [lineNode, ...children];
2017
2143
  };
@@ -2062,7 +2188,7 @@ const getErrorDom = (errorCode, error) => {
2062
2188
  const getOutputVirtualDom = (lines, errorCode, error) => {
2063
2189
  return [{
2064
2190
  childCount: 1,
2065
- className: mergeClassNames(Viewlet, Output),
2191
+ className: mergeClassNames(Viewlet, Output$1),
2066
2192
  type: Div
2067
2193
  }, ...getContentDom(lines, error), ...getErrorDom(errorCode, error)];
2068
2194
  };
@@ -2074,7 +2200,7 @@ const renderItems = (oldState, newState) => {
2074
2200
 
2075
2201
  const renderSelectedItem = (oldState, newState) => {
2076
2202
  const value = newState.selectedOption;
2077
- const name = Output$1;
2203
+ const name = Output;
2078
2204
  return ['Viewlet.setValueByName', name, value];
2079
2205
  };
2080
2206
 
@@ -2143,7 +2269,7 @@ const getActionButtonsVirtualDom = buttons => {
2143
2269
 
2144
2270
  const filterNode = {
2145
2271
  childCount: 1,
2146
- className: Filter,
2272
+ className: Filter$2,
2147
2273
  type: Div
2148
2274
  };
2149
2275
  const getFilterVirtualDom = () => {
@@ -2151,7 +2277,7 @@ const getFilterVirtualDom = () => {
2151
2277
  return [filterNode, {
2152
2278
  childCount: 0,
2153
2279
  className: mergeClassNames(InputBox, FilterInput),
2154
- name: Filter$2,
2280
+ name: Filter$1,
2155
2281
  onInput: HandleFilterInput,
2156
2282
  placeholder,
2157
2283
  type: Input
@@ -2175,7 +2301,7 @@ const getSelectVirtualDom = options => {
2175
2301
  return [{
2176
2302
  childCount: options.length,
2177
2303
  className: Select,
2178
- name: Output$1,
2304
+ name: Output,
2179
2305
  onChange: HandleSelect,
2180
2306
  type: Select$1
2181
2307
  }, ...options.flatMap(getOptionVirtualDom)];
@@ -2240,7 +2366,10 @@ const resize = (state, dimensions) => {
2240
2366
  };
2241
2367
 
2242
2368
  const serializeLinePart = part => {
2243
- return part.value;
2369
+ if (part.type === LogLevel || part.type === RepeatCount) {
2370
+ return '';
2371
+ }
2372
+ return part.type === Link ? part.label || part.value : part.value;
2244
2373
  };
2245
2374
  const serializeLineParts = parts => {
2246
2375
  return parts.map(serializeLinePart).join('');
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lvce-editor/output-view",
3
- "version": "2.10.0",
3
+ "version": "2.11.0",
4
4
  "description": "Output View Worker",
5
5
  "repository": {
6
6
  "type": "git",