tecitheme 1.1.1 → 1.1.2

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.
@@ -16,7 +16,12 @@
16
16
 
17
17
  const { searchClient, setMeiliSearchParams } = instantMeiliSearch (
18
18
  "https://ms-6689ffd3d866-29089.nyc.meilisearch.io",
19
- "d9f20886cbbbe171f3933dc93a1ec38e365d53f072854e5d9f92a9b130fa1f7d"
19
+ "d9f20886cbbbe171f3933dc93a1ec38e365d53f072854e5d9f92a9b130fa1f7d",
20
+ {
21
+ meiliSearchParams: {
22
+ showMatchesPosition: true
23
+ }
24
+ }
20
25
  )
21
26
 
22
27
  onMount(() => {
@@ -90,7 +90,106 @@
90
90
  }
91
91
  }
92
92
 
93
+ function tokenizeHTML(html) {
94
+ const tokens = [];
95
+ let buffer = "";
96
+ let insideTag = false;
97
+
98
+ for (let i = 0; i < html.length; i++) {
99
+ const c = html[i];
100
+
101
+ if (c === "<") {
102
+ // flush text buffer
103
+ if (buffer) tokens.push({ type: "text", value: buffer });
104
+ buffer = "<";
105
+ insideTag = true;
106
+ } else if (c === ">" && insideTag) {
107
+ buffer += ">";
108
+ tokens.push({ type: "tag", value: buffer });
109
+ buffer = "";
110
+ insideTag = false;
111
+ } else {
112
+ buffer += c;
113
+ }
114
+ }
115
+
116
+ if (buffer) {
117
+ tokens.push({ type: insideTag ? "tag" : "text", value: buffer });
118
+ }
119
+
120
+ return tokens;
121
+ }
122
+
123
+ function buildOffsetMap(tokens) {
124
+ let global = 0;
125
+
126
+ return tokens.map(t => {
127
+ const start = global;
128
+ const end = start + t.value.length;
129
+
130
+ global = end;
131
+
132
+ return {
133
+ ...t,
134
+ globalStart: start,
135
+ globalEnd: end
136
+ };
137
+ });
138
+ }
139
+
140
+ function applyHighlights(tokens, matches) {
141
+ // Process matches from last → first to prevent index shifting
142
+ matches.sort((a, b) => b.start - a.start);
143
+
144
+ matches.forEach(({ start, length }) => {
145
+ const end = start + length;
146
+
147
+ tokens.forEach(token => {
148
+ if (token.type === "tag") return;
93
149
 
150
+ // skip if match does not touch this token
151
+ if (end <= token.globalStart || start >= token.globalEnd) return;
152
+
153
+ // ----- compute localStart -----
154
+ let localStart;
155
+ if (start < token.globalStart) {
156
+ localStart = 0;
157
+ } else {
158
+ localStart = start - token.globalStart;
159
+ }
160
+
161
+ // ----- compute localEnd -----
162
+ let localEnd;
163
+ const tokenLength = token.value.length;
164
+
165
+ if (end > token.globalEnd) {
166
+ localEnd = tokenLength;
167
+ } else {
168
+ localEnd = end - token.globalStart;
169
+ }
170
+
171
+ // apply the highlight
172
+ const text = token.value;
173
+
174
+ token.value =
175
+ text.slice(0, localStart) +
176
+ "<mark>" +
177
+ text.slice(localStart, localEnd) +
178
+ "</mark>" +
179
+ text.slice(localEnd);
180
+ });
181
+ });
182
+
183
+ return tokens.map(t => t.value).join("");
184
+ }
185
+
186
+ function highlightHTMLWithMatches(html, matches) {
187
+ if (!matches || matches.length === 0) return html
188
+
189
+ const tokens = tokenizeHTML(html);
190
+ const mapped = buildOffsetMap(tokens);
191
+ return applyHighlights(mapped, matches);
192
+ }
94
193
  </script>
95
194
 
96
195
  <svelte:window on:keydown={(e) => handleKeyPress(e)} />
@@ -209,7 +308,7 @@
209
308
  <!-- Content Preview -->
210
309
  <div class="flex flex-col">
211
310
  <h4 class="text-lg font-bold text-black uppercase">Preview</h4>
212
- <p class="line-clamp-4 mb-4"><Highlight {hit} attribute="content" /></p>
311
+ <p class="highlightedSearchContent line-clamp-4 mb-4">{@html highlightHTMLWithMatches(hit.content, hit._matchesPosition.content)}</p>
213
312
  </div>
214
313
 
215
314
  <!-- On This Page headings -->
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "tecitheme",
3
- "version": "1.1.1",
3
+ "version": "1.1.2",
4
4
  "license": "MIT",
5
5
  "scripts": {
6
6
  "dev": "vite dev",