@miketromba/issy-core 0.1.1 → 0.1.3

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@miketromba/issy-core",
3
- "version": "0.1.1",
3
+ "version": "0.1.3",
4
4
  "description": "Issue storage, search, and parsing for issy",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -9,12 +9,12 @@
9
9
  * A suggestion for autocomplete
10
10
  */
11
11
  export interface Suggestion {
12
- /** The text to insert when selected */
13
- text: string
14
- /** The text to display in the dropdown */
15
- displayText: string
16
- /** Optional helper text describing the suggestion */
17
- description?: string
12
+ /** The text to insert when selected */
13
+ text: string
14
+ /** The text to display in the dropdown */
15
+ displayText: string
16
+ /** Optional helper text describing the suggestion */
17
+ description?: string
18
18
  }
19
19
 
20
20
  /**
@@ -26,11 +26,11 @@ const QUALIFIER_KEYS = ['is', 'priority', 'type', 'label', 'sort'] as const
26
26
  * Valid values for each qualifier
27
27
  */
28
28
  const QUALIFIER_VALUES: Record<string, readonly string[]> = {
29
- is: ['open', 'closed'] as const,
30
- priority: ['high', 'medium', 'low'] as const,
31
- type: ['bug', 'improvement'] as const,
32
- sort: ['priority', 'created', 'updated', 'id'] as const
33
- // label values are dynamic and provided via existingLabels parameter
29
+ is: ['open', 'closed'] as const,
30
+ priority: ['high', 'medium', 'low'] as const,
31
+ type: ['bug', 'improvement'] as const,
32
+ sort: ['priority', 'created', 'updated', 'id'] as const,
33
+ // label values are dynamic and provided via existingLabels parameter
34
34
  }
35
35
 
36
36
  /**
@@ -50,185 +50,181 @@ const QUALIFIER_VALUES: Record<string, readonly string[]> = {
50
50
  * // [{ text: "priority:", displayText: "priority:", description: "Filter by priority" }, ...]
51
51
  */
52
52
  export function getQuerySuggestions(
53
- query: string,
54
- cursorPosition?: number,
55
- existingLabels?: string[]
53
+ query: string,
54
+ cursorPosition?: number,
55
+ existingLabels?: string[],
56
56
  ): Suggestion[] {
57
- // Default cursor position to end of string
58
- const cursor = cursorPosition ?? query.length
59
-
60
- // Get the text up to the cursor
61
- const textBeforeCursor = query.substring(0, cursor)
62
-
63
- // Find the current token being typed
64
- const { currentToken } = findCurrentToken(textBeforeCursor)
65
-
66
- // If we're in the middle of a qualifier (key:value)
67
- if (currentToken.includes(':')) {
68
- const colonIndex = currentToken.indexOf(':')
69
- const qualifierKey = currentToken.substring(0, colonIndex)
70
- const partialValue = currentToken.substring(colonIndex + 1)
71
-
72
- // Check if it's a supported qualifier
73
- if ((QUALIFIER_KEYS as readonly string[]).includes(qualifierKey)) {
74
- return getValueSuggestions(
75
- qualifierKey,
76
- partialValue,
77
- existingLabels
78
- )
79
- }
80
- }
81
-
82
- // Check if we're typing a qualifier key (with or without colon)
83
- const partialQualifier = currentToken
84
- if (partialQualifier && !partialQualifier.includes(':')) {
85
- const matchingKeys = QUALIFIER_KEYS.filter(key =>
86
- key.startsWith(partialQualifier.toLowerCase())
87
- )
88
-
89
- if (matchingKeys.length > 0) {
90
- return matchingKeys.map(key => ({
91
- text: `${key}:`,
92
- displayText: `${key}:`,
93
- description: getQualifierDescription(key)
94
- }))
95
- }
96
- }
97
-
98
- // If we're at the start of a new token (space or start of string)
99
- // and the previous token doesn't end with a colon, suggest qualifier keys
100
- if (currentToken === '' || currentToken.trim() === '') {
101
- const previousToken = getPreviousToken(textBeforeCursor)
102
- if (!previousToken || !previousToken.endsWith(':')) {
103
- return QUALIFIER_KEYS.map(key => ({
104
- text: `${key}:`,
105
- displayText: `${key}:`,
106
- description: getQualifierDescription(key)
107
- }))
108
- }
109
- }
110
-
111
- return []
57
+ // Default cursor position to end of string
58
+ const cursor = cursorPosition ?? query.length
59
+
60
+ // Get the text up to the cursor
61
+ const textBeforeCursor = query.substring(0, cursor)
62
+
63
+ // Find the current token being typed
64
+ const { currentToken } = findCurrentToken(textBeforeCursor)
65
+
66
+ // If we're in the middle of a qualifier (key:value)
67
+ if (currentToken.includes(':')) {
68
+ const colonIndex = currentToken.indexOf(':')
69
+ const qualifierKey = currentToken.substring(0, colonIndex)
70
+ const partialValue = currentToken.substring(colonIndex + 1)
71
+
72
+ // Check if it's a supported qualifier
73
+ if ((QUALIFIER_KEYS as readonly string[]).includes(qualifierKey)) {
74
+ return getValueSuggestions(qualifierKey, partialValue, existingLabels)
75
+ }
76
+ }
77
+
78
+ // Check if we're typing a qualifier key (with or without colon)
79
+ const partialQualifier = currentToken
80
+ if (partialQualifier && !partialQualifier.includes(':')) {
81
+ const matchingKeys = QUALIFIER_KEYS.filter((key) =>
82
+ key.startsWith(partialQualifier.toLowerCase()),
83
+ )
84
+
85
+ if (matchingKeys.length > 0) {
86
+ return matchingKeys.map((key) => ({
87
+ text: `${key}:`,
88
+ displayText: `${key}:`,
89
+ description: getQualifierDescription(key),
90
+ }))
91
+ }
92
+ }
93
+
94
+ // If we're at the start of a new token (space or start of string)
95
+ // and the previous token doesn't end with a colon, suggest qualifier keys
96
+ if (currentToken === '' || currentToken.trim() === '') {
97
+ const previousToken = getPreviousToken(textBeforeCursor)
98
+ if (!previousToken || !previousToken.endsWith(':')) {
99
+ return QUALIFIER_KEYS.map((key) => ({
100
+ text: `${key}:`,
101
+ displayText: `${key}:`,
102
+ description: getQualifierDescription(key),
103
+ }))
104
+ }
105
+ }
106
+
107
+ return []
112
108
  }
113
109
 
114
110
  /**
115
111
  * Get suggestions for a qualifier value
116
112
  */
117
113
  function getValueSuggestions(
118
- qualifierKey: string,
119
- partialValue: string,
120
- existingLabels?: string[]
114
+ qualifierKey: string,
115
+ partialValue: string,
116
+ existingLabels?: string[],
121
117
  ): Suggestion[] {
122
- const _suggestions: Suggestion[] = []
123
-
124
- if (qualifierKey === 'label') {
125
- // For labels, use existing labels if provided
126
- if (existingLabels && existingLabels.length > 0) {
127
- const matchingLabels = existingLabels
128
- .filter(label =>
129
- label.toLowerCase().includes(partialValue.toLowerCase())
130
- )
131
- .slice(0, 10) // Limit to 10 suggestions
132
-
133
- return matchingLabels.map(label => ({
134
- text: label,
135
- displayText: label,
136
- description: 'Label'
137
- }))
138
- }
139
- return []
140
- }
141
-
142
- // For other qualifiers, use predefined values
143
- const validValues = QUALIFIER_VALUES[qualifierKey]
144
- if (!validValues) {
145
- return []
146
- }
147
-
148
- const matchingValues = validValues.filter(value =>
149
- value.toLowerCase().startsWith(partialValue.toLowerCase())
150
- )
151
-
152
- return matchingValues.map(value => ({
153
- text: value,
154
- displayText: value,
155
- description: getValueDescription(qualifierKey, value)
156
- }))
118
+ const _suggestions: Suggestion[] = []
119
+
120
+ if (qualifierKey === 'label') {
121
+ // For labels, use existing labels if provided
122
+ if (existingLabels && existingLabels.length > 0) {
123
+ const matchingLabels = existingLabels
124
+ .filter((label) =>
125
+ label.toLowerCase().includes(partialValue.toLowerCase()),
126
+ )
127
+ .slice(0, 10) // Limit to 10 suggestions
128
+
129
+ return matchingLabels.map((label) => ({
130
+ text: label,
131
+ displayText: label,
132
+ description: 'Label',
133
+ }))
134
+ }
135
+ return []
136
+ }
137
+
138
+ // For other qualifiers, use predefined values
139
+ const validValues = QUALIFIER_VALUES[qualifierKey]
140
+ if (!validValues) {
141
+ return []
142
+ }
143
+
144
+ const matchingValues = validValues.filter((value) =>
145
+ value.toLowerCase().startsWith(partialValue.toLowerCase()),
146
+ )
147
+
148
+ return matchingValues.map((value) => ({
149
+ text: value,
150
+ displayText: value,
151
+ description: getValueDescription(qualifierKey, value),
152
+ }))
157
153
  }
158
154
 
159
155
  /**
160
156
  * Find the current token being typed at the cursor position
161
157
  */
162
158
  function findCurrentToken(text: string): {
163
- currentToken: string
164
- tokenStart: number
159
+ currentToken: string
160
+ tokenStart: number
165
161
  } {
166
- if (!text) {
167
- return { currentToken: '', tokenStart: 0 }
168
- }
169
-
170
- // Find the start of the current token (last space or start of string)
171
- let tokenStart = text.length
172
- for (let i = text.length - 1; i >= 0; i--) {
173
- if (text[i] === ' ') {
174
- tokenStart = i + 1
175
- break
176
- }
177
- if (i === 0) {
178
- tokenStart = 0
179
- }
180
- }
181
-
182
- const currentToken = text.substring(tokenStart)
183
- return { currentToken, tokenStart }
162
+ if (!text) {
163
+ return { currentToken: '', tokenStart: 0 }
164
+ }
165
+
166
+ // Find the start of the current token (last space or start of string)
167
+ let tokenStart = text.length
168
+ for (let i = text.length - 1; i >= 0; i--) {
169
+ if (text[i] === ' ') {
170
+ tokenStart = i + 1
171
+ break
172
+ }
173
+ if (i === 0) {
174
+ tokenStart = 0
175
+ }
176
+ }
177
+
178
+ const currentToken = text.substring(tokenStart)
179
+ return { currentToken, tokenStart }
184
180
  }
185
181
 
186
182
  /**
187
183
  * Get the previous token before the cursor
188
184
  */
189
185
  function getPreviousToken(text: string): string | null {
190
- if (!text || text.trim() === '') {
191
- return null
192
- }
186
+ if (!text || text.trim() === '') {
187
+ return null
188
+ }
193
189
 
194
- const tokens = text.trim().split(/\s+/)
195
- if (tokens.length < 2) {
196
- return null
197
- }
190
+ const tokens = text.trim().split(/\s+/)
191
+ if (tokens.length < 2) {
192
+ return null
193
+ }
198
194
 
199
- // Get the second-to-last token
200
- return tokens[tokens.length - 2]
195
+ // Get the second-to-last token
196
+ return tokens[tokens.length - 2]
201
197
  }
202
198
 
203
199
  /**
204
200
  * Get a human-readable description for a qualifier key
205
201
  */
206
202
  function getQualifierDescription(key: string): string {
207
- const descriptions: Record<string, string> = {
208
- is: 'Filter by status',
209
- priority: 'Filter by priority',
210
- type: 'Filter by type',
211
- label: 'Filter by label',
212
- sort: 'Sort results'
213
- }
214
- return descriptions[key] || ''
203
+ const descriptions: Record<string, string> = {
204
+ is: 'Filter by status',
205
+ priority: 'Filter by priority',
206
+ type: 'Filter by type',
207
+ label: 'Filter by label',
208
+ sort: 'Sort results',
209
+ }
210
+ return descriptions[key] || ''
215
211
  }
216
212
 
217
213
  /**
218
214
  * Get a human-readable description for a qualifier value
219
215
  */
220
216
  function getValueDescription(key: string, value: string): string {
221
- if (key === 'is') {
222
- return value === 'open' ? 'Open issues' : 'Closed issues'
223
- }
224
- if (key === 'priority') {
225
- return `Priority: ${value}`
226
- }
227
- if (key === 'type') {
228
- return value === 'bug' ? 'Bug report' : 'Improvement'
229
- }
230
- if (key === 'sort') {
231
- return `Sort by ${value}`
232
- }
233
- return ''
217
+ if (key === 'is') {
218
+ return value === 'open' ? 'Open issues' : 'Closed issues'
219
+ }
220
+ if (key === 'priority') {
221
+ return `Priority: ${value}`
222
+ }
223
+ if (key === 'type') {
224
+ return value === 'bug' ? 'Bug report' : 'Improvement'
225
+ }
226
+ if (key === 'sort') {
227
+ return `Sort by ${value}`
228
+ }
229
+ return ''
234
230
  }
@@ -0,0 +1,23 @@
1
+ /**
2
+ * Browser-safe exports from issy-core
3
+ * These functions work in both browser and Node.js environments
4
+ */
5
+
6
+ // Date formatting helpers
7
+ export { formatDisplayDate, formatFullDate } from './formatDate'
8
+
9
+ // Query parser
10
+ export type { ParsedQuery } from './query-parser'
11
+ export { parseQuery } from './query-parser'
12
+
13
+ // Search functionality (pure functions)
14
+ export { filterByQuery, filterIssues } from './search'
15
+
16
+ // Types
17
+ export type {
18
+ CreateIssueInput,
19
+ Issue,
20
+ IssueFilters,
21
+ IssueFrontmatter,
22
+ UpdateIssueInput,
23
+ } from './types'