@bsky.app/tapper 0.3.0 → 0.3.1
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/CHANGELOG.md +6 -0
- package/build/util.d.ts.map +1 -1
- package/build/util.js +23 -16
- package/build/util.js.map +1 -1
- package/package.json +1 -1
- package/src/util.ts +26 -14
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
# @bsky.app/tapper
|
|
2
2
|
|
|
3
|
+
## 0.3.1
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- [`27b4748`](https://github.com/bluesky-social/toolbox/commit/27b4748ea3c9b2ed45b7979f0431b6eed2b908d3) Thanks [@estrattonbailey](https://github.com/estrattonbailey)! - Misc fixes
|
|
8
|
+
|
|
3
9
|
## 0.3.0
|
|
4
10
|
|
|
5
11
|
### Minor Changes
|
package/build/util.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"util.d.ts","sourceRoot":"","sources":["../src/util.ts"],"names":[],"mappings":"AAAA,OAAO,EAAC,iBAAiB,EAAE,UAAU,EAAE,WAAW,EAAC,MAAM,SAAS,CAAA;AAIlE,MAAM,MAAM,oBAAoB,GAAG,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;AAEtD;;;;;GAKG;AACH,wBAAgB,mBAAmB,CACjC,MAAM,EAAE,iBAAiB,GACxB,oBAAoB,CAMtB;AAED,wBAAgB,kBAAkB,CAChC,IAAI,EAAE,MAAM,EACZ,OAAO,EAAE,oBAAoB,EAC7B,SAAS,CAAC,EAAE,UAAU,EAAE,EACxB,MAAM,CAAC,EAAE,MAAM,EACf,QAAQ,CAAC,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,GAC7B,UAAU,EAAE,CAqJd;AAED,wBAAgB,cAAc,CAAC,MAAM,EAAE,iBAAiB,GAAG,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAO7E;AAED,wBAAgB,WAAW,CAAC,IAAI,EAAE,UAAU,GAAG,WAAW,CAOzD;AAED,wBAAgB,iBAAiB,CAC/B,KAAK,EAAE,UAAU,EAAE,EACnB,IAAI,EAAE,MAAM,EACZ,MAAM,EAAE,MAAM,EACd,QAAQ,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,GAC5B,WAAW,GAAG,IAAI,
|
|
1
|
+
{"version":3,"file":"util.d.ts","sourceRoot":"","sources":["../src/util.ts"],"names":[],"mappings":"AAAA,OAAO,EAAC,iBAAiB,EAAE,UAAU,EAAE,WAAW,EAAC,MAAM,SAAS,CAAA;AAIlE,MAAM,MAAM,oBAAoB,GAAG,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;AAEtD;;;;;GAKG;AACH,wBAAgB,mBAAmB,CACjC,MAAM,EAAE,iBAAiB,GACxB,oBAAoB,CAMtB;AAED,wBAAgB,kBAAkB,CAChC,IAAI,EAAE,MAAM,EACZ,OAAO,EAAE,oBAAoB,EAC7B,SAAS,CAAC,EAAE,UAAU,EAAE,EACxB,MAAM,CAAC,EAAE,MAAM,EACf,QAAQ,CAAC,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,GAC7B,UAAU,EAAE,CAqJd;AAED,wBAAgB,cAAc,CAAC,MAAM,EAAE,iBAAiB,GAAG,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAO7E;AAED,wBAAgB,WAAW,CAAC,IAAI,EAAE,UAAU,GAAG,WAAW,CAOzD;AAED,wBAAgB,iBAAiB,CAC/B,KAAK,EAAE,UAAU,EAAE,EACnB,IAAI,EAAE,MAAM,EACZ,MAAM,EAAE,MAAM,EACd,QAAQ,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,GAC5B,WAAW,GAAG,IAAI,CAkDpB"}
|
package/build/util.js
CHANGED
|
@@ -172,7 +172,10 @@ export function detectActiveFacet(nodes, text, cursor, triggers) {
|
|
|
172
172
|
range: { start: node.start, end: node.end },
|
|
173
173
|
};
|
|
174
174
|
}
|
|
175
|
-
if (node.type === 'facet' &&
|
|
175
|
+
if (node.type === 'facet' &&
|
|
176
|
+
!node.committed &&
|
|
177
|
+
node.start < cursor &&
|
|
178
|
+
cursor <= node.end) {
|
|
176
179
|
return {
|
|
177
180
|
type: node.facetType,
|
|
178
181
|
raw: node.raw,
|
|
@@ -181,21 +184,25 @@ export function detectActiveFacet(nodes, text, cursor, triggers) {
|
|
|
181
184
|
};
|
|
182
185
|
}
|
|
183
186
|
}
|
|
184
|
-
// Scan backward from cursor for a trigger char (partial facet not yet matched by regex)
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
raw,
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
187
|
+
// Scan backward from cursor for a trigger char (partial facet not yet matched by regex).
|
|
188
|
+
// Skip if the cursor is inside or at the end of a committed facet.
|
|
189
|
+
const inCommitted = nodes.some(n => n.type === 'facet' && n.committed && n.start < cursor && cursor <= n.end);
|
|
190
|
+
if (!inCommitted) {
|
|
191
|
+
for (let i = cursor - 1; i >= 0; i--) {
|
|
192
|
+
const ch = text[i];
|
|
193
|
+
// Stop at whitespace — triggers don't span across words
|
|
194
|
+
if (WHITESPACE.test(ch))
|
|
195
|
+
break;
|
|
196
|
+
const type = triggers.get(ch);
|
|
197
|
+
if (type) {
|
|
198
|
+
const raw = text.slice(i, cursor);
|
|
199
|
+
return {
|
|
200
|
+
type,
|
|
201
|
+
raw,
|
|
202
|
+
value: text.slice(i + 1, cursor),
|
|
203
|
+
range: { start: i, end: cursor },
|
|
204
|
+
};
|
|
205
|
+
}
|
|
199
206
|
}
|
|
200
207
|
}
|
|
201
208
|
return null;
|
package/build/util.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"util.js","sourceRoot":"","sources":["../src/util.ts"],"names":[],"mappings":"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","sourcesContent":["import {TapperFacetConfig, TapperNode, TapperFacet} from './types'\n\nconst WHITESPACE = /\\s/\n\nexport type CompiledFacetRegexes = Map<string, RegExp>\n\n/**\n * Pre-compile facet regexes once at init time. This avoids re-creating\n * RegExp objects on every keystroke in parseNodesFromText. Each Tapper\n * instance gets its own compiled copy so lastIndex state can't leak\n * between instances.\n */\nexport function compileFacetRegexes(\n config: TapperFacetConfig,\n): CompiledFacetRegexes {\n const compiled = new Map<string, RegExp>()\n for (const [name, re] of Object.entries(config)) {\n compiled.set(name, new RegExp(re.source, re.flags))\n }\n return compiled\n}\n\nexport function parseNodesFromText(\n text: string,\n regexes: CompiledFacetRegexes,\n prevNodes?: TapperNode[],\n cursor?: number,\n triggers?: Map<string, string>,\n): TapperNode[] {\n const allMatches: {\n facetName: string\n fullMatch: string\n capture: string\n index: number\n }[] = []\n\n for (const [name, re] of regexes) {\n // Reset lastIndex so stateful (global) regexes don't carry over\n // match positions from the previous parse call.\n re.lastIndex = 0\n for (const m of text.matchAll(re)) {\n allMatches.push({\n facetName: name,\n fullMatch: m[0],\n capture: m[1] ?? m[0],\n index: m.index,\n })\n }\n }\n\n allMatches.sort(\n (a, b) => a.index - b.index || b.fullMatch.length - a.fullMatch.length,\n )\n\n const accepted: typeof allMatches = []\n let lastEnd = 0\n for (const m of allMatches) {\n if (m.index >= lastEnd) {\n accepted.push(m)\n lastEnd = m.index + m.fullMatch.length\n }\n }\n\n const nodes: TapperNode[] = []\n let pos = 0\n\n for (const m of accepted) {\n if (m.index > pos) {\n const raw = text.slice(pos, m.index)\n nodes.push({\n type: 'text',\n raw,\n value: raw,\n start: pos,\n end: m.index,\n })\n }\n nodes.push({\n type: 'facet',\n facetType: m.facetName,\n raw: m.fullMatch,\n value: m.capture,\n start: m.index,\n end: m.index + m.fullMatch.length,\n })\n pos = m.index + m.fullMatch.length\n }\n\n if (pos < text.length) {\n const raw = text.slice(pos)\n nodes.push({\n type: 'text',\n raw,\n value: raw,\n start: pos,\n end: text.length,\n })\n }\n\n // If the cursor is right after a trigger char that the regex didn't match,\n // splice a 'trigger' node out of the containing text node.\n if (cursor != null && triggers) {\n for (let i = cursor - 1; i >= 0; i--) {\n const ch = text[i]\n if (WHITESPACE.test(ch)) break\n const facetType = triggers.get(ch)\n if (facetType) {\n // Only create a trigger node if the trigger is inside a text node\n // (i.e. the regex didn't already match it as a facet)\n const textNodeIdx = nodes.findIndex(\n n => n.type === 'text' && n.start <= i && n.end > i,\n )\n if (textNodeIdx !== -1) {\n const node = nodes[textNodeIdx]\n const spliced: TapperNode[] = []\n if (node.start < i) {\n const raw = text.slice(node.start, i)\n spliced.push({\n type: 'text',\n raw,\n value: raw,\n start: node.start,\n end: i,\n })\n }\n const triggerRaw = text.slice(i, cursor)\n spliced.push({\n type: 'trigger',\n facetType,\n raw: triggerRaw,\n value: text.slice(i + ch.length, cursor),\n start: i,\n end: cursor,\n })\n if (cursor < node.end) {\n const raw = text.slice(cursor, node.end)\n spliced.push({\n type: 'text',\n raw,\n value: raw,\n start: cursor,\n end: node.end,\n })\n }\n nodes.splice(textNodeIdx, 1, ...spliced)\n }\n break\n }\n }\n }\n\n // Transfer committed flags from previous nodes by facetType + occurrence index\n if (prevNodes) {\n const counts = new Map<string, number>()\n const committedByTypeIndex = new Set<string>()\n\n for (const node of prevNodes) {\n if (node.type !== 'facet') continue\n const idx = counts.get(node.facetType!) ?? 0\n counts.set(node.facetType!, idx + 1)\n if (node.committed) {\n committedByTypeIndex.add(`${node.facetType}:${idx}`)\n }\n }\n\n counts.clear()\n for (const node of nodes) {\n if (node.type !== 'facet') continue\n const idx = counts.get(node.facetType!) ?? 0\n counts.set(node.facetType!, idx + 1)\n if (committedByTypeIndex.has(`${node.facetType}:${idx}`)) {\n node.committed = true\n }\n }\n }\n\n return nodes\n}\n\nexport function deriveTriggers(config: TapperFacetConfig): Map<string, string> {\n const triggers = new Map<string, string>()\n for (const [name, re] of Object.entries(config)) {\n const m = re.source.match(/^[^\\\\([\\]{}.*+?^$|]+/)\n if (m) triggers.set(m[0], name)\n }\n return triggers\n}\n\nexport function nodeToFacet(node: TapperNode): TapperFacet {\n return {\n type: node.facetType!,\n raw: node.raw,\n value: node.value,\n range: {start: node.start, end: node.end},\n }\n}\n\nexport function detectActiveFacet(\n nodes: TapperNode[],\n text: string,\n cursor: number,\n triggers: Map<string, string>,\n): TapperFacet | null {\n for (const node of nodes) {\n if (node.type === 'trigger' && node.start < cursor && cursor <= node.end) {\n return {\n type: node.facetType,\n raw: node.raw,\n value: node.value,\n range: {start: node.start, end: node.end},\n }\n }\n if (node.type === 'facet' && node.start < cursor && cursor <= node.end) {\n return {\n type: node.facetType,\n raw: node.raw,\n value: node.value,\n range: {start: node.start, end: node.end},\n }\n }\n }\n\n // Scan backward from cursor for a trigger char (partial facet not yet matched by regex)\n for (let i = cursor - 1; i >= 0; i--) {\n const ch = text[i]\n // Stop at whitespace — triggers don't span across words\n if (WHITESPACE.test(ch)) break\n const type = triggers.get(ch)\n if (type) {\n const raw = text.slice(i, cursor)\n return {\n type,\n raw,\n value: text.slice(i + 1, cursor),\n range: {start: i, end: cursor},\n }\n }\n }\n\n return null\n}\n"]}
|
|
1
|
+
{"version":3,"file":"util.js","sourceRoot":"","sources":["../src/util.ts"],"names":[],"mappings":"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","sourcesContent":["import {TapperFacetConfig, TapperNode, TapperFacet} from './types'\n\nconst WHITESPACE = /\\s/\n\nexport type CompiledFacetRegexes = Map<string, RegExp>\n\n/**\n * Pre-compile facet regexes once at init time. This avoids re-creating\n * RegExp objects on every keystroke in parseNodesFromText. Each Tapper\n * instance gets its own compiled copy so lastIndex state can't leak\n * between instances.\n */\nexport function compileFacetRegexes(\n config: TapperFacetConfig,\n): CompiledFacetRegexes {\n const compiled = new Map<string, RegExp>()\n for (const [name, re] of Object.entries(config)) {\n compiled.set(name, new RegExp(re.source, re.flags))\n }\n return compiled\n}\n\nexport function parseNodesFromText(\n text: string,\n regexes: CompiledFacetRegexes,\n prevNodes?: TapperNode[],\n cursor?: number,\n triggers?: Map<string, string>,\n): TapperNode[] {\n const allMatches: {\n facetName: string\n fullMatch: string\n capture: string\n index: number\n }[] = []\n\n for (const [name, re] of regexes) {\n // Reset lastIndex so stateful (global) regexes don't carry over\n // match positions from the previous parse call.\n re.lastIndex = 0\n for (const m of text.matchAll(re)) {\n allMatches.push({\n facetName: name,\n fullMatch: m[0],\n capture: m[1] ?? m[0],\n index: m.index,\n })\n }\n }\n\n allMatches.sort(\n (a, b) => a.index - b.index || b.fullMatch.length - a.fullMatch.length,\n )\n\n const accepted: typeof allMatches = []\n let lastEnd = 0\n for (const m of allMatches) {\n if (m.index >= lastEnd) {\n accepted.push(m)\n lastEnd = m.index + m.fullMatch.length\n }\n }\n\n const nodes: TapperNode[] = []\n let pos = 0\n\n for (const m of accepted) {\n if (m.index > pos) {\n const raw = text.slice(pos, m.index)\n nodes.push({\n type: 'text',\n raw,\n value: raw,\n start: pos,\n end: m.index,\n })\n }\n nodes.push({\n type: 'facet',\n facetType: m.facetName,\n raw: m.fullMatch,\n value: m.capture,\n start: m.index,\n end: m.index + m.fullMatch.length,\n })\n pos = m.index + m.fullMatch.length\n }\n\n if (pos < text.length) {\n const raw = text.slice(pos)\n nodes.push({\n type: 'text',\n raw,\n value: raw,\n start: pos,\n end: text.length,\n })\n }\n\n // If the cursor is right after a trigger char that the regex didn't match,\n // splice a 'trigger' node out of the containing text node.\n if (cursor != null && triggers) {\n for (let i = cursor - 1; i >= 0; i--) {\n const ch = text[i]\n if (WHITESPACE.test(ch)) break\n const facetType = triggers.get(ch)\n if (facetType) {\n // Only create a trigger node if the trigger is inside a text node\n // (i.e. the regex didn't already match it as a facet)\n const textNodeIdx = nodes.findIndex(\n n => n.type === 'text' && n.start <= i && n.end > i,\n )\n if (textNodeIdx !== -1) {\n const node = nodes[textNodeIdx]\n const spliced: TapperNode[] = []\n if (node.start < i) {\n const raw = text.slice(node.start, i)\n spliced.push({\n type: 'text',\n raw,\n value: raw,\n start: node.start,\n end: i,\n })\n }\n const triggerRaw = text.slice(i, cursor)\n spliced.push({\n type: 'trigger',\n facetType,\n raw: triggerRaw,\n value: text.slice(i + ch.length, cursor),\n start: i,\n end: cursor,\n })\n if (cursor < node.end) {\n const raw = text.slice(cursor, node.end)\n spliced.push({\n type: 'text',\n raw,\n value: raw,\n start: cursor,\n end: node.end,\n })\n }\n nodes.splice(textNodeIdx, 1, ...spliced)\n }\n break\n }\n }\n }\n\n // Transfer committed flags from previous nodes by facetType + occurrence index\n if (prevNodes) {\n const counts = new Map<string, number>()\n const committedByTypeIndex = new Set<string>()\n\n for (const node of prevNodes) {\n if (node.type !== 'facet') continue\n const idx = counts.get(node.facetType!) ?? 0\n counts.set(node.facetType!, idx + 1)\n if (node.committed) {\n committedByTypeIndex.add(`${node.facetType}:${idx}`)\n }\n }\n\n counts.clear()\n for (const node of nodes) {\n if (node.type !== 'facet') continue\n const idx = counts.get(node.facetType!) ?? 0\n counts.set(node.facetType!, idx + 1)\n if (committedByTypeIndex.has(`${node.facetType}:${idx}`)) {\n node.committed = true\n }\n }\n }\n\n return nodes\n}\n\nexport function deriveTriggers(config: TapperFacetConfig): Map<string, string> {\n const triggers = new Map<string, string>()\n for (const [name, re] of Object.entries(config)) {\n const m = re.source.match(/^[^\\\\([\\]{}.*+?^$|]+/)\n if (m) triggers.set(m[0], name)\n }\n return triggers\n}\n\nexport function nodeToFacet(node: TapperNode): TapperFacet {\n return {\n type: node.facetType!,\n raw: node.raw,\n value: node.value,\n range: {start: node.start, end: node.end},\n }\n}\n\nexport function detectActiveFacet(\n nodes: TapperNode[],\n text: string,\n cursor: number,\n triggers: Map<string, string>,\n): TapperFacet | null {\n for (const node of nodes) {\n if (node.type === 'trigger' && node.start < cursor && cursor <= node.end) {\n return {\n type: node.facetType,\n raw: node.raw,\n value: node.value,\n range: {start: node.start, end: node.end},\n }\n }\n if (\n node.type === 'facet' &&\n !node.committed &&\n node.start < cursor &&\n cursor <= node.end\n ) {\n return {\n type: node.facetType,\n raw: node.raw,\n value: node.value,\n range: {start: node.start, end: node.end},\n }\n }\n }\n\n // Scan backward from cursor for a trigger char (partial facet not yet matched by regex).\n // Skip if the cursor is inside or at the end of a committed facet.\n const inCommitted = nodes.some(\n n =>\n n.type === 'facet' && n.committed && n.start < cursor && cursor <= n.end,\n )\n if (!inCommitted) {\n for (let i = cursor - 1; i >= 0; i--) {\n const ch = text[i]\n // Stop at whitespace — triggers don't span across words\n if (WHITESPACE.test(ch)) break\n const type = triggers.get(ch)\n if (type) {\n const raw = text.slice(i, cursor)\n return {\n type,\n raw,\n value: text.slice(i + 1, cursor),\n range: {start: i, end: cursor},\n }\n }\n }\n }\n\n return null\n}\n"]}
|
package/package.json
CHANGED
package/src/util.ts
CHANGED
|
@@ -210,7 +210,12 @@ export function detectActiveFacet(
|
|
|
210
210
|
range: {start: node.start, end: node.end},
|
|
211
211
|
}
|
|
212
212
|
}
|
|
213
|
-
if (
|
|
213
|
+
if (
|
|
214
|
+
node.type === 'facet' &&
|
|
215
|
+
!node.committed &&
|
|
216
|
+
node.start < cursor &&
|
|
217
|
+
cursor <= node.end
|
|
218
|
+
) {
|
|
214
219
|
return {
|
|
215
220
|
type: node.facetType,
|
|
216
221
|
raw: node.raw,
|
|
@@ -220,19 +225,26 @@ export function detectActiveFacet(
|
|
|
220
225
|
}
|
|
221
226
|
}
|
|
222
227
|
|
|
223
|
-
// Scan backward from cursor for a trigger char (partial facet not yet matched by regex)
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
228
|
+
// Scan backward from cursor for a trigger char (partial facet not yet matched by regex).
|
|
229
|
+
// Skip if the cursor is inside or at the end of a committed facet.
|
|
230
|
+
const inCommitted = nodes.some(
|
|
231
|
+
n =>
|
|
232
|
+
n.type === 'facet' && n.committed && n.start < cursor && cursor <= n.end,
|
|
233
|
+
)
|
|
234
|
+
if (!inCommitted) {
|
|
235
|
+
for (let i = cursor - 1; i >= 0; i--) {
|
|
236
|
+
const ch = text[i]
|
|
237
|
+
// Stop at whitespace — triggers don't span across words
|
|
238
|
+
if (WHITESPACE.test(ch)) break
|
|
239
|
+
const type = triggers.get(ch)
|
|
240
|
+
if (type) {
|
|
241
|
+
const raw = text.slice(i, cursor)
|
|
242
|
+
return {
|
|
243
|
+
type,
|
|
244
|
+
raw,
|
|
245
|
+
value: text.slice(i + 1, cursor),
|
|
246
|
+
range: {start: i, end: cursor},
|
|
247
|
+
}
|
|
236
248
|
}
|
|
237
249
|
}
|
|
238
250
|
}
|