_utils.js 4.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130
  1. // Utility functions for tag autocomplete
  2. // Parse the CSV file into a 2D array. Doesn't use regex, so it is very lightweight.
  3. function parseCSV(str) {
  4. var arr = [];
  5. var quote = false; // 'true' means we're inside a quoted field
  6. // Iterate over each character, keep track of current row and column (of the returned array)
  7. for (var row = 0, col = 0, c = 0; c < str.length; c++) {
  8. var cc = str[c], nc = str[c + 1]; // Current character, next character
  9. arr[row] = arr[row] || []; // Create a new row if necessary
  10. arr[row][col] = arr[row][col] || ''; // Create a new column (start with empty string) if necessary
  11. // If the current character is a quotation mark, and we're inside a
  12. // quoted field, and the next character is also a quotation mark,
  13. // add a quotation mark to the current column and skip the next character
  14. if (cc == '"' && quote && nc == '"') { arr[row][col] += cc; ++c; continue; }
  15. // If it's just one quotation mark, begin/end quoted field
  16. if (cc == '"') { quote = !quote; continue; }
  17. // If it's a comma and we're not in a quoted field, move on to the next column
  18. if (cc == ',' && !quote) { ++col; continue; }
  19. // If it's a newline (CRLF) and we're not in a quoted field, skip the next character
  20. // and move on to the next row and move to column 0 of that new row
  21. if (cc == '\r' && nc == '\n' && !quote) { ++row; col = 0; ++c; continue; }
  22. // If it's a newline (LF or CR) and we're not in a quoted field,
  23. // move on to the next row and move to column 0 of that new row
  24. if (cc == '\n' && !quote) { ++row; col = 0; continue; }
  25. if (cc == '\r' && !quote) { ++row; col = 0; continue; }
  26. // Otherwise, append the current character to the current column
  27. arr[row][col] += cc;
  28. }
  29. return arr;
  30. }
  31. // Load file
  32. async function readFile(filePath, json = false, cache = false) {
  33. if (!cache)
  34. filePath += `?${new Date().getTime()}`;
  35. let response = await fetch(`file=${filePath}`);
  36. if (response.status != 200) {
  37. console.error(`Error loading file "${filePath}": ` + response.status, response.statusText);
  38. return null;
  39. }
  40. if (json)
  41. return await response.json();
  42. else
  43. return await response.text();
  44. }
  45. // Load CSV
  46. async function loadCSV(path) {
  47. let text = await readFile(path);
  48. return parseCSV(text);
  49. }
  50. // Debounce function to prevent spamming the autocomplete function
  51. var dbTimeOut;
  52. const debounce = (func, wait = 300) => {
  53. return function (...args) {
  54. if (dbTimeOut) {
  55. clearTimeout(dbTimeOut);
  56. }
  57. dbTimeOut = setTimeout(() => {
  58. func.apply(this, args);
  59. }, wait);
  60. }
  61. }
  62. // Difference function to fix duplicates not being seen as changes in normal filter
  63. function difference(a, b) {
  64. if (a.length == 0) {
  65. return b;
  66. }
  67. if (b.length == 0) {
  68. return a;
  69. }
  70. return [...b.reduce((acc, v) => acc.set(v, (acc.get(v) || 0) - 1),
  71. a.reduce((acc, v) => acc.set(v, (acc.get(v) || 0) + 1), new Map())
  72. )].reduce((acc, [v, count]) => acc.concat(Array(Math.abs(count)).fill(v)), []);
  73. }
  74. function escapeRegExp(string) {
  75. return string.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); // $& means the whole matched string
  76. }
  77. function escapeHTML(unsafeText) {
  78. let div = document.createElement('div');
  79. div.textContent = unsafeText;
  80. return div.innerHTML;
  81. }
  82. // Queue calling function to process global queues
  83. async function processQueue(queue, context, ...args) {
  84. for (let i = 0; i < queue.length; i++) {
  85. await queue[i].call(context, ...args);
  86. }
  87. }
  88. // The same but with return values
  89. async function processQueueReturn(queue, context, ...args)
  90. {
  91. let qeueueReturns = [];
  92. for (let i = 0; i < queue.length; i++) {
  93. let returnValue = await queue[i].call(context, ...args);
  94. if (returnValue)
  95. qeueueReturns.push(returnValue);
  96. }
  97. return qeueueReturns;
  98. }
  99. // Specific to tag completion parsers
  100. async function processParsers(textArea, prompt) {
  101. // Get all parsers that have a successful trigger condition
  102. let matchingParsers = PARSERS.filter(parser => parser.triggerCondition());
  103. // Guard condition
  104. if (matchingParsers.length === 0) {
  105. return null;
  106. }
  107. let parseFunctions = matchingParsers.map(parser => parser.parse);
  108. // Process them and return the results
  109. return await processQueueReturn(parseFunctions, null, textArea, prompt);
  110. }