(input: string)
| 1240 | ): Promise<string | string[]>; |
| 1241 | |
| 1242 | protected async replaceDateVariableInString(input: string) { |
| 1243 | // Scan with a GLOBAL regex + accumulator (mirrors replaceFieldVarInString) |
| 1244 | // so a malformed/empty-name {{VDATE:}} token is skipped (left literal) and |
| 1245 | // every LATER valid {{VDATE}} is still prompted. The previous in-place |
| 1246 | // while-loop `break`ed the entire scan on the first empty-name match, which |
| 1247 | // silently dropped all subsequent date prompts. The accumulator also never |
| 1248 | // re-scans a replacement, so a stored value re-containing the token can't |
| 1249 | // loop. |
| 1250 | const regex = new RegExp(DATE_VARIABLE_REGEX.source, "gi"); |
| 1251 | let output = ""; |
| 1252 | let lastIndex = 0; |
| 1253 | let match: RegExpExecArray | null; |
| 1254 | |
| 1255 | while ((match = regex.exec(input)) !== null) { |
| 1256 | output += input.slice(lastIndex, match.index); |
| 1257 | lastIndex = match.index + match[0].length; |
| 1258 | |
| 1259 | const variableName = match[1]?.trim(); |
| 1260 | // Empty/incomplete token (e.g. {{VDATE:}} or {{VDATE:,YYYY}}): leave it |
| 1261 | // literal and keep scanning so a later valid token still resolves. |
| 1262 | if (!variableName) { |
| 1263 | output += match[0]; |
| 1264 | continue; |
| 1265 | } |
| 1266 | |
| 1267 | const { defaultValue, optional, withTime, snap } = parseVDateOptions(match[3]); |
| 1268 | // A |time/|datetime token with no explicit format gets a datetime |
| 1269 | // default so the rendered value carries the picked time. |
| 1270 | const dateFormat = |
| 1271 | match[2]?.trim() || (withTime ? "YYYY-MM-DD HH:mm" : "YYYY-MM-DD"); |
| 1272 | |
| 1273 | const existingValue = this.variables.get(variableName); |
| 1274 | |
| 1275 | // Check if we already have this date variable stored. |
| 1276 | // Only `undefined` counts as unset — null and "" are intentional |
| 1277 | // values (same contract as VALUE variables, see #872), so a |
| 1278 | // script-set "" renders empty instead of re-prompting. |
| 1279 | if (existingValue === undefined) { |
| 1280 | // Prompt for date input with VDATE context |
| 1281 | const dateInput = await this.promptForVariable( |
| 1282 | variableName, |
| 1283 | { type: "VDATE", dateFormat, defaultValue, optional, withTime } |
| 1284 | ); |
| 1285 | if (optional && !dateInput?.trim()) { |
| 1286 | // Optional date left blank or skipped: answered-empty. |
| 1287 | this.variables.set(variableName, ""); |
| 1288 | } else if (dateInput?.startsWith("@date:")) { |
| 1289 | this.variables.set(variableName, dateInput); |
| 1290 | } else { |
| 1291 | if (!this.dateParser) |
| 1292 | throw new Error("Date parser is not available"); |
| 1293 | |
| 1294 | const aliasMap = settingsStore.getState().dateAliases; |
| 1295 | const normalizedInput = normalizeDateInput( |
| 1296 | dateInput, |
| 1297 | aliasMap, |
| 1298 | ); |
| 1299 | const parseAttempt = this.dateParser.parseDate(normalizedInput); |
nothing calls this directly
no test coverage detected