tecitheme 1.1.0 → 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.
@@ -7,7 +7,7 @@
7
7
  <section {id} class="{data.bgColor?'p-8':''} {getColorStyles('background', data.bgColor)}
8
8
  {data.fullwidth?'w-full mx-auto':'max-w-3xl mx-auto'}
9
9
  {data.justifyText?(data.justifyText=='left'?'text-left':(data.justifyText=='right'?'text-right':'')):'text-center'}
10
- not-prose
10
+ not-prose print:hidden
11
11
  "
12
12
  >
13
13
  {#if data.title}
@@ -5,7 +5,7 @@
5
5
  let id = makeIdString(data.name);
6
6
  </script>
7
7
 
8
- <section {id} class="relative {getColorStyles('background', data.color)}">
8
+ <section {id} class="relative {getColorStyles('background', data.color)} print:hidden">
9
9
  <div class="h-56 sm:h-72 md:absolute md:left-0 md:h-full md:w-1/2">
10
10
  <img class="h-full w-full object-cover" src="https://files.thunderheadeng.com/www/images/{data.image}?w=608&fit=clip&auto=compress&auto=format" alt="Image for {data.preheading}">
11
11
  </div>
@@ -4,7 +4,7 @@ import Button from './Button.svelte'
4
4
  let d = new Date();
5
5
  </script>
6
6
 
7
- <footer id="bottom" class="bg-gray-800" aria-labelledby="footerHeading">
7
+ <footer id="bottom" class="bg-gray-800 print:hidden" aria-labelledby="footerHeading">
8
8
  <h2 id="footerHeading" class="sr-only">Footer</h2>
9
9
  <div class="mx-auto max-w-7xl py-12 px-4 sm:px-6 lg:py-16 lg:px-8">
10
10
  <div class="xl:grid xl:grid-cols-3 xl:gap-8">
@@ -36,7 +36,7 @@
36
36
 
37
37
  <svelte:window on:keyup={handleEscape} />
38
38
 
39
- <header id="menu" class="relative">
39
+ <header id="menu" class="relative print:hidden">
40
40
  <div
41
41
  aria-hidden="true"
42
42
  class="pointer-events-none absolute inset-0 z-50 shadow"
@@ -38,7 +38,7 @@
38
38
 
39
39
  <svelte:window on:keyup={handleEscape} />
40
40
 
41
- <header {id} class="sticky top-0 w-full z-40 bg-white">
41
+ <header {id} class="sticky top-0 w-full z-40 bg-white print:hidden">
42
42
  <div
43
43
  aria-hidden="true"
44
44
  class="pointer-events-none absolute inset-0 z-30 shadow"
@@ -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.0",
3
+ "version": "1.1.2",
4
4
  "license": "MIT",
5
5
  "scripts": {
6
6
  "dev": "vite dev",