refactor: reorganize quick add magic into focused modules
Split the monolithic parseTaskText.ts into a parseTaskText/ directory with separate files for types, prefixes, prefix parsing, priority parsing, repeat parsing, date parsing, and text cleanup. Moved parseDate.ts from helpers/time/ into the module since it's only consumed by the task text parser. Barrel export in index.ts maintains backward compatibility — no consumer import changes needed. https://claude.ai/code/session_01Aeo1ZunQUGKbWx2watMFdW
This commit is contained in:
parent
c760a9bf72
commit
cb81cf1aa8
|
|
@ -1,306 +0,0 @@
|
|||
import {parseDate} from '../helpers/time/parseDate'
|
||||
import {PRIORITIES} from '@/constants/priorities'
|
||||
import {REPEAT_TYPES, type IRepeatAfter, type IRepeatType} from '@/types/IRepeatAfter'
|
||||
|
||||
const VIKUNJA_PREFIXES: Prefixes = {
|
||||
label: '*',
|
||||
project: '+',
|
||||
priority: '!',
|
||||
assignee: '@',
|
||||
}
|
||||
|
||||
const TODOIST_PREFIXES: Prefixes = {
|
||||
label: '@',
|
||||
project: '#',
|
||||
priority: '!',
|
||||
assignee: '+',
|
||||
}
|
||||
|
||||
export enum PrefixMode {
|
||||
Disabled = 'disabled',
|
||||
Default = 'vikunja',
|
||||
Todoist = 'todoist',
|
||||
}
|
||||
|
||||
export const PREFIXES = {
|
||||
[PrefixMode.Disabled]: undefined,
|
||||
[PrefixMode.Default]: VIKUNJA_PREFIXES,
|
||||
[PrefixMode.Todoist]: TODOIST_PREFIXES,
|
||||
}
|
||||
|
||||
interface repeatParsedResult {
|
||||
textWithoutMatched: string,
|
||||
repeats: IRepeatAfter | null,
|
||||
}
|
||||
|
||||
export interface ParsedTaskText {
|
||||
text: string,
|
||||
date: Date | null,
|
||||
labels: string[],
|
||||
project: string | null,
|
||||
priority: number | null,
|
||||
assignees: string[],
|
||||
repeats: IRepeatAfter | null,
|
||||
}
|
||||
|
||||
interface Prefixes {
|
||||
label: string,
|
||||
project: string,
|
||||
priority: string,
|
||||
assignee: string,
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses task text for dates, assignees, labels, projects, priorities and returns an object with all found intents.
|
||||
*
|
||||
* @param text
|
||||
*/
|
||||
export const parseTaskText = (text: string, prefixesMode: PrefixMode = PrefixMode.Default, now: Date = new Date()): ParsedTaskText => {
|
||||
const result: ParsedTaskText = {
|
||||
text: text,
|
||||
date: null,
|
||||
labels: [],
|
||||
project: null,
|
||||
priority: null,
|
||||
assignees: [],
|
||||
repeats: null,
|
||||
}
|
||||
|
||||
const prefixes = PREFIXES[prefixesMode]
|
||||
if (prefixes === undefined) {
|
||||
return result
|
||||
}
|
||||
|
||||
result.labels = getLabelsFromPrefix(text, prefixesMode) ?? []
|
||||
result.text = cleanupItemText(result.text, result.labels, prefixes.label)
|
||||
|
||||
result.project = getProjectFromPrefix(result.text, prefixesMode)
|
||||
result.text = result.project !== null ? cleanupItemText(result.text, [result.project], prefixes.project) : result.text
|
||||
|
||||
result.priority = getPriority(result.text, prefixes.priority)
|
||||
result.text = result.priority !== null ? cleanupItemText(result.text, [String(result.priority)], prefixes.priority) : result.text
|
||||
|
||||
result.assignees = getItemsFromPrefix(result.text, prefixes.assignee)
|
||||
|
||||
const {textWithoutMatched, repeats} = getRepeats(result.text)
|
||||
result.text = textWithoutMatched
|
||||
result.repeats = repeats
|
||||
|
||||
const {newText, date} = parseDate(result.text, now)
|
||||
result.text = newText
|
||||
result.date = date
|
||||
|
||||
return cleanupResult(result, prefixes)
|
||||
}
|
||||
|
||||
const getItemsFromPrefix = (text: string, prefix: string): string[] => {
|
||||
const items: string[] = []
|
||||
|
||||
const itemParts = text.split(' ' + prefix)
|
||||
if (text.startsWith(prefix)) {
|
||||
const firstItem = text.split(prefix)[1]
|
||||
itemParts.unshift(firstItem)
|
||||
}
|
||||
|
||||
itemParts.forEach((p, index) => {
|
||||
// First part contains the rest
|
||||
if (index < 1) {
|
||||
return
|
||||
}
|
||||
|
||||
if (p.startsWith(prefix)) {
|
||||
p = p.substring(1)
|
||||
}
|
||||
|
||||
let itemText
|
||||
if (p.charAt(0) === '\'') {
|
||||
itemText = p.split('\'')[1]
|
||||
} else if (p.charAt(0) === '"') {
|
||||
itemText = p.split('"')[1]
|
||||
} else {
|
||||
// Only until the next space
|
||||
itemText = p.split(' ')[0]
|
||||
}
|
||||
|
||||
if (itemText !== '') {
|
||||
items.push(itemText)
|
||||
}
|
||||
})
|
||||
|
||||
return Array.from(new Set(items))
|
||||
}
|
||||
|
||||
export const getProjectFromPrefix = (text: string, prefixMode: PrefixMode): string | null => {
|
||||
const projectPrefix = PREFIXES[prefixMode]?.project
|
||||
if(typeof projectPrefix === 'undefined') {
|
||||
return null
|
||||
}
|
||||
const projects: string[] = getItemsFromPrefix(text, projectPrefix)
|
||||
return projects.length > 0 ? projects[0] : null
|
||||
}
|
||||
|
||||
export const getLabelsFromPrefix = (text: string, prefixMode: PrefixMode): string[] | null => {
|
||||
const labelsPrefix = PREFIXES[prefixMode]?.label
|
||||
if(typeof labelsPrefix === 'undefined') {
|
||||
return null
|
||||
}
|
||||
return getItemsFromPrefix(text, labelsPrefix)
|
||||
}
|
||||
|
||||
const getPriority = (text: string, prefix: string): number | null => {
|
||||
const ps = getItemsFromPrefix(text, prefix)
|
||||
if (ps.length === 0) {
|
||||
return null
|
||||
}
|
||||
|
||||
for (const p of ps) {
|
||||
for (const pi of Object.values(PRIORITIES)) {
|
||||
if (pi === parseInt(p)) {
|
||||
return parseInt(p)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
const getRepeats = (text: string): repeatParsedResult => {
|
||||
const regex = /(^| )(((every|each) (([0-9]+|one|two|three|four|five|six|seven|eight|nine|ten) )?(hours?|days?|weeks?|months?|years?))|(annually|biannually|semiannually|biennially|daily|hourly|monthly|weekly|yearly))($| )/ig
|
||||
const results = regex.exec(text)
|
||||
if (results === null) {
|
||||
return {
|
||||
textWithoutMatched: text,
|
||||
repeats: null,
|
||||
}
|
||||
}
|
||||
|
||||
let amount = 1
|
||||
switch (results[5] ? results[5].trim() : undefined) {
|
||||
case 'one':
|
||||
amount = 1
|
||||
break
|
||||
case 'two':
|
||||
amount = 2
|
||||
break
|
||||
case 'three':
|
||||
amount = 3
|
||||
break
|
||||
case 'four':
|
||||
amount = 4
|
||||
break
|
||||
case 'five':
|
||||
amount = 5
|
||||
break
|
||||
case 'six':
|
||||
amount = 6
|
||||
break
|
||||
case 'seven':
|
||||
amount = 7
|
||||
break
|
||||
case 'eight':
|
||||
amount = 8
|
||||
break
|
||||
case 'nine':
|
||||
amount = 9
|
||||
break
|
||||
case 'ten':
|
||||
amount = 10
|
||||
break
|
||||
default:
|
||||
amount = results[5] ? parseInt(results[5]) : 1
|
||||
}
|
||||
let type: IRepeatType = REPEAT_TYPES.Hours
|
||||
|
||||
switch (results[2]) {
|
||||
case 'biennially':
|
||||
type = REPEAT_TYPES.Years
|
||||
amount = 2
|
||||
break
|
||||
case 'biannually':
|
||||
case 'semiannually':
|
||||
type = REPEAT_TYPES.Months
|
||||
amount = 6
|
||||
break
|
||||
case 'yearly':
|
||||
case 'annually':
|
||||
type = REPEAT_TYPES.Years
|
||||
break
|
||||
case 'daily':
|
||||
type = REPEAT_TYPES.Days
|
||||
break
|
||||
case 'hourly':
|
||||
type = REPEAT_TYPES.Hours
|
||||
break
|
||||
case 'monthly':
|
||||
type = REPEAT_TYPES.Months
|
||||
break
|
||||
case 'weekly':
|
||||
type = REPEAT_TYPES.Weeks
|
||||
break
|
||||
default:
|
||||
switch (results[7]) {
|
||||
case 'hour':
|
||||
case 'hours':
|
||||
type = REPEAT_TYPES.Hours
|
||||
break
|
||||
case 'day':
|
||||
case 'days':
|
||||
type = REPEAT_TYPES.Days
|
||||
break
|
||||
case 'week':
|
||||
case 'weeks':
|
||||
type = REPEAT_TYPES.Weeks
|
||||
break
|
||||
case 'month':
|
||||
case 'months':
|
||||
type = REPEAT_TYPES.Months
|
||||
break
|
||||
case 'year':
|
||||
case 'years':
|
||||
type = REPEAT_TYPES.Years
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
let matchedText = results[0]
|
||||
if(matchedText.endsWith(' ')) {
|
||||
matchedText = matchedText.substring(0, matchedText.length - 1)
|
||||
}
|
||||
|
||||
return {
|
||||
textWithoutMatched: text.replace(matchedText, ''),
|
||||
repeats: {
|
||||
amount,
|
||||
type,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
const escapeRegExp = (s: string): string => s.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')
|
||||
|
||||
export const cleanupItemText = (text: string, items: string[], prefix: string): string => {
|
||||
items.forEach(l => {
|
||||
if (l === '') {
|
||||
return
|
||||
}
|
||||
const escaped = escapeRegExp(l)
|
||||
text = text
|
||||
.replace(new RegExp(`\\${prefix}'${escaped}' `, 'ig'), '')
|
||||
.replace(new RegExp(`\\${prefix}'${escaped}'`, 'ig'), '')
|
||||
.replace(new RegExp(`\\${prefix}"${escaped}" `, 'ig'), '')
|
||||
.replace(new RegExp(`\\${prefix}"${escaped}"`, 'ig'), '')
|
||||
.replace(new RegExp(`\\${prefix}${escaped} `, 'ig'), '')
|
||||
.replace(new RegExp(`\\${prefix}${escaped}`, 'ig'), '')
|
||||
})
|
||||
return text
|
||||
}
|
||||
|
||||
const cleanupResult = (result: ParsedTaskText, prefixes: Prefixes): ParsedTaskText => {
|
||||
result.text = cleanupItemText(result.text, result.labels, prefixes.label)
|
||||
result.text = result.project !== null ? cleanupItemText(result.text, [result.project], prefixes.project) : result.text
|
||||
result.text = result.priority !== null ? cleanupItemText(result.text, [String(result.priority)], prefixes.priority) : result.text
|
||||
// Not removing assignees to avoid removing @text where the user does not exist
|
||||
result.text = result.text.trim()
|
||||
|
||||
return result
|
||||
}
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
import {calculateDayInterval} from './calculateDayInterval'
|
||||
import {calculateNearestHours} from './calculateNearestHours'
|
||||
import {replaceAll} from '../replaceAll'
|
||||
import {calculateDayInterval} from '@/helpers/time/calculateDayInterval'
|
||||
import {calculateNearestHours} from '@/helpers/time/calculateNearestHours'
|
||||
import {replaceAll} from '@/helpers/replaceAll'
|
||||
|
||||
export interface dateParseResult {
|
||||
newText: string,
|
||||
|
|
@ -196,7 +196,7 @@ export const getDateFromText = (text: string, now: Date = new Date()) => {
|
|||
result = `${month}/${day}/${tmp_year}`
|
||||
result = !isNaN(new Date(result).getTime()) ? result : `${day}/${month}/${tmp_year}`
|
||||
result = !isNaN(new Date(result).getTime()) ? result : null
|
||||
|
||||
|
||||
if(result !== null){
|
||||
foundText = found
|
||||
break
|
||||
|
|
@ -212,7 +212,7 @@ export const getDateFromText = (text: string, now: Date = new Date()) => {
|
|||
foundText = results === null ? '' : results[0].trim()
|
||||
containsYear = false
|
||||
}
|
||||
|
||||
|
||||
if (result === null) {
|
||||
return {
|
||||
foundText,
|
||||
|
|
@ -328,7 +328,7 @@ const getDateFromWeekday = (text: string, date: Date = new Date()): dateFoundRes
|
|||
const distance: number = (day + 7 - currentDay) % 7
|
||||
date.setDate(date.getDate() + distance)
|
||||
|
||||
// This a space at the end of the found text to not break parsing suffix strings like "at 14:00" in cases where the
|
||||
// This a space at the end of the found text to not break parsing suffix strings like "at 14:00" in cases where the
|
||||
// matched string comes with a space at the end (last part of the regex).
|
||||
let foundText = results[0]
|
||||
if (foundText.endsWith(' ')) {
|
||||
|
|
@ -357,9 +357,9 @@ const getDayFromText = (text: string, now: Date = new Date()) => {
|
|||
const day = parseInt(results[0])
|
||||
date.setDate(day)
|
||||
|
||||
// If the parsed day is the 31st (or 29+ and the next month is february) but the next month only has 30 days,
|
||||
// If the parsed day is the 31st (or 29+ and the next month is february) but the next month only has 30 days,
|
||||
// setting the day to 31 will "overflow" the date to the next month, but the first.
|
||||
// This would look like a very weired bug. Now, to prevent that, we check if the day is the same as parsed after
|
||||
// This would look like a very weired bug. Now, to prevent that, we check if the day is the same as parsed after
|
||||
// setting it for the first time and set it again if it isn't - that would mean the month overflowed.
|
||||
while (date < now) {
|
||||
date.setMonth(date.getMonth() + 1)
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
export {parseTaskText} from './parseTaskText'
|
||||
export {PrefixMode, PREFIXES} from './prefixes'
|
||||
export {getLabelsFromPrefix, getProjectFromPrefix} from './prefixParser'
|
||||
export {cleanupItemText} from './textCleanup'
|
||||
export type {ParsedTaskText} from './types'
|
||||
|
|
@ -1,8 +1,9 @@
|
|||
import {afterEach, beforeEach, describe, expect, it, vi} from 'vitest'
|
||||
|
||||
import {ParsedTaskText, parseTaskText, PrefixMode} from './parseTaskText'
|
||||
import {parseDate} from '../helpers/time/parseDate'
|
||||
import {calculateDayInterval} from '../helpers/time/calculateDayInterval'
|
||||
import {parseTaskText, PrefixMode} from '.'
|
||||
import type {ParsedTaskText} from '.'
|
||||
import {parseDate} from './dateParser'
|
||||
import {calculateDayInterval} from '@/helpers/time/calculateDayInterval'
|
||||
import {PRIORITIES} from '@/constants/priorities'
|
||||
import {MILLISECONDS_A_DAY} from '@/constants/date'
|
||||
import type {IRepeatAfter} from '@/types/IRepeatAfter'
|
||||
|
|
@ -0,0 +1,50 @@
|
|||
import {parseDate} from './dateParser'
|
||||
import {PREFIXES, PrefixMode} from './prefixes'
|
||||
import {getItemsFromPrefix, getLabelsFromPrefix, getProjectFromPrefix} from './prefixParser'
|
||||
import {getPriority} from './priorityParser'
|
||||
import {getRepeats} from './repeatParser'
|
||||
import {cleanupItemText, cleanupResult} from './textCleanup'
|
||||
import type {ParsedTaskText} from './types'
|
||||
|
||||
/**
|
||||
* Parses task text for dates, assignees, labels, projects, priorities and returns an object with all found intents.
|
||||
*
|
||||
* @param text
|
||||
*/
|
||||
export const parseTaskText = (text: string, prefixesMode: PrefixMode = PrefixMode.Default, now: Date = new Date()): ParsedTaskText => {
|
||||
const result: ParsedTaskText = {
|
||||
text: text,
|
||||
date: null,
|
||||
labels: [],
|
||||
project: null,
|
||||
priority: null,
|
||||
assignees: [],
|
||||
repeats: null,
|
||||
}
|
||||
|
||||
const prefixes = PREFIXES[prefixesMode]
|
||||
if (prefixes === undefined) {
|
||||
return result
|
||||
}
|
||||
|
||||
result.labels = getLabelsFromPrefix(text, prefixesMode) ?? []
|
||||
result.text = cleanupItemText(result.text, result.labels, prefixes.label)
|
||||
|
||||
result.project = getProjectFromPrefix(result.text, prefixesMode)
|
||||
result.text = result.project !== null ? cleanupItemText(result.text, [result.project], prefixes.project) : result.text
|
||||
|
||||
result.priority = getPriority(result.text, prefixes.priority)
|
||||
result.text = result.priority !== null ? cleanupItemText(result.text, [String(result.priority)], prefixes.priority) : result.text
|
||||
|
||||
result.assignees = getItemsFromPrefix(result.text, prefixes.assignee)
|
||||
|
||||
const {textWithoutMatched, repeats} = getRepeats(result.text)
|
||||
result.text = textWithoutMatched
|
||||
result.repeats = repeats
|
||||
|
||||
const {newText, date} = parseDate(result.text, now)
|
||||
result.text = newText
|
||||
result.date = date
|
||||
|
||||
return cleanupResult(result, prefixes)
|
||||
}
|
||||
|
|
@ -0,0 +1,55 @@
|
|||
import {PREFIXES, PrefixMode} from './prefixes'
|
||||
|
||||
export const getItemsFromPrefix = (text: string, prefix: string): string[] => {
|
||||
const items: string[] = []
|
||||
|
||||
const itemParts = text.split(' ' + prefix)
|
||||
if (text.startsWith(prefix)) {
|
||||
const firstItem = text.split(prefix)[1]
|
||||
itemParts.unshift(firstItem)
|
||||
}
|
||||
|
||||
itemParts.forEach((p, index) => {
|
||||
// First part contains the rest
|
||||
if (index < 1) {
|
||||
return
|
||||
}
|
||||
|
||||
if (p.startsWith(prefix)) {
|
||||
p = p.substring(1)
|
||||
}
|
||||
|
||||
let itemText
|
||||
if (p.charAt(0) === '\'') {
|
||||
itemText = p.split('\'')[1]
|
||||
} else if (p.charAt(0) === '"') {
|
||||
itemText = p.split('"')[1]
|
||||
} else {
|
||||
// Only until the next space
|
||||
itemText = p.split(' ')[0]
|
||||
}
|
||||
|
||||
if (itemText !== '') {
|
||||
items.push(itemText)
|
||||
}
|
||||
})
|
||||
|
||||
return Array.from(new Set(items))
|
||||
}
|
||||
|
||||
export const getProjectFromPrefix = (text: string, prefixMode: PrefixMode): string | null => {
|
||||
const projectPrefix = PREFIXES[prefixMode]?.project
|
||||
if(typeof projectPrefix === 'undefined') {
|
||||
return null
|
||||
}
|
||||
const projects: string[] = getItemsFromPrefix(text, projectPrefix)
|
||||
return projects.length > 0 ? projects[0] : null
|
||||
}
|
||||
|
||||
export const getLabelsFromPrefix = (text: string, prefixMode: PrefixMode): string[] | null => {
|
||||
const labelsPrefix = PREFIXES[prefixMode]?.label
|
||||
if(typeof labelsPrefix === 'undefined') {
|
||||
return null
|
||||
}
|
||||
return getItemsFromPrefix(text, labelsPrefix)
|
||||
}
|
||||
|
|
@ -0,0 +1,27 @@
|
|||
import type {Prefixes} from './types'
|
||||
|
||||
const VIKUNJA_PREFIXES: Prefixes = {
|
||||
label: '*',
|
||||
project: '+',
|
||||
priority: '!',
|
||||
assignee: '@',
|
||||
}
|
||||
|
||||
const TODOIST_PREFIXES: Prefixes = {
|
||||
label: '@',
|
||||
project: '#',
|
||||
priority: '!',
|
||||
assignee: '+',
|
||||
}
|
||||
|
||||
export enum PrefixMode {
|
||||
Disabled = 'disabled',
|
||||
Default = 'vikunja',
|
||||
Todoist = 'todoist',
|
||||
}
|
||||
|
||||
export const PREFIXES = {
|
||||
[PrefixMode.Disabled]: undefined,
|
||||
[PrefixMode.Default]: VIKUNJA_PREFIXES,
|
||||
[PrefixMode.Todoist]: TODOIST_PREFIXES,
|
||||
}
|
||||
|
|
@ -0,0 +1,19 @@
|
|||
import {PRIORITIES} from '@/constants/priorities'
|
||||
import {getItemsFromPrefix} from './prefixParser'
|
||||
|
||||
export const getPriority = (text: string, prefix: string): number | null => {
|
||||
const ps = getItemsFromPrefix(text, prefix)
|
||||
if (ps.length === 0) {
|
||||
return null
|
||||
}
|
||||
|
||||
for (const p of ps) {
|
||||
for (const pi of Object.values(PRIORITIES)) {
|
||||
if (pi === parseInt(p)) {
|
||||
return parseInt(p)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
|
|
@ -0,0 +1,114 @@
|
|||
import {REPEAT_TYPES, type IRepeatType} from '@/types/IRepeatAfter'
|
||||
import type {repeatParsedResult} from './types'
|
||||
|
||||
export const getRepeats = (text: string): repeatParsedResult => {
|
||||
const regex = /(^| )(((every|each) (([0-9]+|one|two|three|four|five|six|seven|eight|nine|ten) )?(hours?|days?|weeks?|months?|years?))|(annually|biannually|semiannually|biennially|daily|hourly|monthly|weekly|yearly))($| )/ig
|
||||
const results = regex.exec(text)
|
||||
if (results === null) {
|
||||
return {
|
||||
textWithoutMatched: text,
|
||||
repeats: null,
|
||||
}
|
||||
}
|
||||
|
||||
let amount = 1
|
||||
switch (results[5] ? results[5].trim() : undefined) {
|
||||
case 'one':
|
||||
amount = 1
|
||||
break
|
||||
case 'two':
|
||||
amount = 2
|
||||
break
|
||||
case 'three':
|
||||
amount = 3
|
||||
break
|
||||
case 'four':
|
||||
amount = 4
|
||||
break
|
||||
case 'five':
|
||||
amount = 5
|
||||
break
|
||||
case 'six':
|
||||
amount = 6
|
||||
break
|
||||
case 'seven':
|
||||
amount = 7
|
||||
break
|
||||
case 'eight':
|
||||
amount = 8
|
||||
break
|
||||
case 'nine':
|
||||
amount = 9
|
||||
break
|
||||
case 'ten':
|
||||
amount = 10
|
||||
break
|
||||
default:
|
||||
amount = results[5] ? parseInt(results[5]) : 1
|
||||
}
|
||||
let type: IRepeatType = REPEAT_TYPES.Hours
|
||||
|
||||
switch (results[2]) {
|
||||
case 'biennially':
|
||||
type = REPEAT_TYPES.Years
|
||||
amount = 2
|
||||
break
|
||||
case 'biannually':
|
||||
case 'semiannually':
|
||||
type = REPEAT_TYPES.Months
|
||||
amount = 6
|
||||
break
|
||||
case 'yearly':
|
||||
case 'annually':
|
||||
type = REPEAT_TYPES.Years
|
||||
break
|
||||
case 'daily':
|
||||
type = REPEAT_TYPES.Days
|
||||
break
|
||||
case 'hourly':
|
||||
type = REPEAT_TYPES.Hours
|
||||
break
|
||||
case 'monthly':
|
||||
type = REPEAT_TYPES.Months
|
||||
break
|
||||
case 'weekly':
|
||||
type = REPEAT_TYPES.Weeks
|
||||
break
|
||||
default:
|
||||
switch (results[7]) {
|
||||
case 'hour':
|
||||
case 'hours':
|
||||
type = REPEAT_TYPES.Hours
|
||||
break
|
||||
case 'day':
|
||||
case 'days':
|
||||
type = REPEAT_TYPES.Days
|
||||
break
|
||||
case 'week':
|
||||
case 'weeks':
|
||||
type = REPEAT_TYPES.Weeks
|
||||
break
|
||||
case 'month':
|
||||
case 'months':
|
||||
type = REPEAT_TYPES.Months
|
||||
break
|
||||
case 'year':
|
||||
case 'years':
|
||||
type = REPEAT_TYPES.Years
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
let matchedText = results[0]
|
||||
if(matchedText.endsWith(' ')) {
|
||||
matchedText = matchedText.substring(0, matchedText.length - 1)
|
||||
}
|
||||
|
||||
return {
|
||||
textWithoutMatched: text.replace(matchedText, ''),
|
||||
repeats: {
|
||||
amount,
|
||||
type,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,30 @@
|
|||
import type {ParsedTaskText, Prefixes} from './types'
|
||||
|
||||
const escapeRegExp = (s: string): string => s.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')
|
||||
|
||||
export const cleanupItemText = (text: string, items: string[], prefix: string): string => {
|
||||
items.forEach(l => {
|
||||
if (l === '') {
|
||||
return
|
||||
}
|
||||
const escaped = escapeRegExp(l)
|
||||
text = text
|
||||
.replace(new RegExp(`\\${prefix}'${escaped}' `, 'ig'), '')
|
||||
.replace(new RegExp(`\\${prefix}'${escaped}'`, 'ig'), '')
|
||||
.replace(new RegExp(`\\${prefix}"${escaped}" `, 'ig'), '')
|
||||
.replace(new RegExp(`\\${prefix}"${escaped}"`, 'ig'), '')
|
||||
.replace(new RegExp(`\\${prefix}${escaped} `, 'ig'), '')
|
||||
.replace(new RegExp(`\\${prefix}${escaped}`, 'ig'), '')
|
||||
})
|
||||
return text
|
||||
}
|
||||
|
||||
export const cleanupResult = (result: ParsedTaskText, prefixes: Prefixes): ParsedTaskText => {
|
||||
result.text = cleanupItemText(result.text, result.labels, prefixes.label)
|
||||
result.text = result.project !== null ? cleanupItemText(result.text, [result.project], prefixes.project) : result.text
|
||||
result.text = result.priority !== null ? cleanupItemText(result.text, [String(result.priority)], prefixes.priority) : result.text
|
||||
// Not removing assignees to avoid removing @text where the user does not exist
|
||||
result.text = result.text.trim()
|
||||
|
||||
return result
|
||||
}
|
||||
|
|
@ -0,0 +1,23 @@
|
|||
import type {IRepeatAfter} from '@/types/IRepeatAfter'
|
||||
|
||||
export interface repeatParsedResult {
|
||||
textWithoutMatched: string,
|
||||
repeats: IRepeatAfter | null,
|
||||
}
|
||||
|
||||
export interface ParsedTaskText {
|
||||
text: string,
|
||||
date: Date | null,
|
||||
labels: string[],
|
||||
project: string | null,
|
||||
priority: number | null,
|
||||
assignees: string[],
|
||||
repeats: IRepeatAfter | null,
|
||||
}
|
||||
|
||||
export interface Prefixes {
|
||||
label: string,
|
||||
project: string,
|
||||
priority: string,
|
||||
assignee: string,
|
||||
}
|
||||
Loading…
Reference in New Issue