Skip to content

Commit 2a1e29c

Browse files
committed
format fixes, social media, and try to fix mobile dropdown
1 parent 563ab5e commit 2a1e29c

File tree

4 files changed

+96
-85
lines changed

4 files changed

+96
-85
lines changed

src/components/IMEField.tsx

Lines changed: 69 additions & 83 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
// @refresh reload
12
import * as wanakana from "wanakana";
23
import {
34
createSignal,
@@ -74,18 +75,18 @@ export function IMEField() {
7475
const unconfirmedText = createMemo(() => input().slice(confirmedIndex()));
7576

7677
onMount(() => {
77-
if (ta) {
78-
wanakana.bind(ta);
79-
ta.value = "";
80-
setInput("");
81-
ta.focus();
82-
}
78+
if (!ta) return;
79+
wanakana.bind(ta);
80+
ta.value = "";
81+
setInput("");
82+
ta.focus();
8383
});
8484

8585
onCleanup(() => {
8686
if (ta) wanakana.unbind(ta);
8787
});
8888

89+
// reset menu when lookupReading changes
8990
createEffect(() => {
9091
suggestions();
9192
itemRefs = [];
@@ -122,78 +123,55 @@ export function IMEField() {
122123
setTimeout(() => ta?.focus(), 0);
123124
}
124125

125-
function handleKeyDown(
126-
e: KeyboardEvent & {
127-
currentTarget: HTMLTextAreaElement;
128-
}
129-
) {
126+
function handleKeyDown(e: KeyboardEvent & { currentTarget: HTMLTextAreaElement }) {
130127
if (isMenuOpen()) {
131128
const len = suggestions().length;
132-
if (len > 0) {
133-
if (e.key === "ArrowDown") {
134-
e.preventDefault();
135-
const newIndex = (selectedIndex() + 1) % len;
136-
setSelectedIndex(newIndex);
137-
const item = itemRefs[newIndex];
138-
const container = listRef;
139-
if (item && container) {
140-
const itop = item.offsetTop;
141-
const ibot = itop + item.offsetHeight;
142-
const ctop = container.scrollTop;
143-
const cheight = container.clientHeight;
144-
if (ibot > ctop + cheight) {
145-
container.scrollTop = ibot - cheight;
146-
} else if (itop < ctop) {
147-
container.scrollTop = itop;
148-
}
149-
}
150-
return;
151-
}
152-
if (e.key === "ArrowUp") {
153-
e.preventDefault();
154-
const newIndex = (selectedIndex() - 1 + len) % len;
155-
setSelectedIndex(newIndex);
156-
const item = itemRefs[newIndex];
157-
const container = listRef;
158-
if (item && container) {
159-
const itop = item.offsetTop;
160-
const ibot = itop + item.offsetHeight;
161-
const ctop = container.scrollTop;
162-
const cheight = container.clientHeight;
163-
if (itop < ctop) {
164-
container.scrollTop = itop;
165-
} else if (ibot > ctop + cheight) {
166-
container.scrollTop = ibot - cheight;
167-
}
129+
if (len > 0 && (e.key === "ArrowDown" || e.key === "ArrowUp")) {
130+
e.preventDefault();
131+
const delta = e.key === "ArrowDown" ? 1 : -1;
132+
const newIndex = (selectedIndex() + delta + len) % len;
133+
setSelectedIndex(newIndex);
134+
const item = itemRefs[newIndex];
135+
const container = listRef;
136+
if (item && container) {
137+
const itop = item.offsetTop;
138+
const ibot = itop + item.offsetHeight;
139+
const ctop = container.scrollTop;
140+
const cheight = container.clientHeight;
141+
if (ibot > ctop + cheight) {
142+
container.scrollTop = ibot - cheight;
143+
} else if (itop < ctop) {
144+
container.scrollTop = itop;
168145
}
169-
return;
170146
}
147+
return;
171148
}
172149
if (e.key === "Enter" || e.key === " ") {
173150
e.preventDefault();
174151
commitSuggestion(selectedIndex());
175-
return;
176152
}
153+
return;
177154
}
178155

156+
// backspace to undo last conversion
157+
const lc = lastConversion();
179158
if (
180159
e.key === "Backspace" &&
181-
lastConversion() &&
182-
e.currentTarget.selectionStart === lastConversion()!.end &&
183-
e.currentTarget.selectionEnd === lastConversion()!.end
160+
lc &&
161+
e.currentTarget.selectionStart === lc.end &&
162+
e.currentTarget.selectionEnd === lc.end
184163
) {
185164
e.preventDefault();
186-
const conv = lastConversion()!;
187-
const before = input().slice(0, conv.start);
188-
const after = input().slice(conv.end);
189-
const newVal = before + conv.reading + after;
165+
const before = input().slice(0, lc.start);
166+
const after = input().slice(lc.end);
167+
const newVal = before + lc.reading + after;
190168
setInput(newVal);
191-
const newPos = conv.start + conv.reading.length;
169+
const newPos = lc.start + lc.reading.length;
192170
if (ta) {
193171
ta.value = newVal;
194172
ta.setSelectionRange(newPos, newPos);
195173
}
196-
setConfirmedIndex(conv.start);
174+
setConfirmedIndex(lc.start);
197175
setIsComposing(true);
198176
setLastConversion(null);
199177
return;
@@ -213,18 +191,18 @@ export function IMEField() {
213191
const pos = e.currentTarget.selectionStart;
214192
const reading = input().slice(start, pos);
215193
if (wanakana.isHiragana(reading)) {
216-
const katakana = wanakana.toKatakana(reading);
194+
const kata = wanakana.toKatakana(reading);
217195
const before = input().slice(0, start);
218196
const after = input().slice(pos);
219-
const newVal = before + katakana + after;
197+
const newVal = before + kata + after;
220198
setInput(newVal);
221-
const end = before.length + katakana.length;
199+
const end = before.length + kata.length;
222200
if (ta) {
223201
ta.value = newVal;
224202
ta.setSelectionRange(end, end);
225203
}
226204
setLastConversion({
227-
confirmed: katakana,
205+
confirmed: kata,
228206
reading,
229207
start,
230208
end,
@@ -234,33 +212,41 @@ export function IMEField() {
234212
}
235213
return;
236214
}
215+
}
216+
217+
function handleInput(e: InputEvent & { currentTarget: HTMLTextAreaElement }) {
218+
const val = e.currentTarget.value;
219+
setInput(val);
220+
setIsComposing(val.length > confirmedIndex());
221+
setLastConversion(null);
237222

238-
if (e.key === " ") {
223+
if (e.inputType === "insertText" && e.data === " " && isComposing()) {
224+
const start = confirmedIndex();
239225
const pos = e.currentTarget.selectionStart;
240-
const reading = input().slice(confirmedIndex(), pos);
226+
const reading = val.slice(start, pos - 1);
241227
if (wanakana.isHiragana(reading) && reading.length) {
242-
e.preventDefault();
243-
setCompositionStart(confirmedIndex());
228+
setCompositionStart(start);
244229
setLookupReading(reading);
245230
setSelectedIndex(0);
246231
setIsMenuOpen(true);
247-
return;
248232
}
249233
}
250234
}
251235

252-
function handleInput(
253-
e: InputEvent & {
254-
currentTarget: HTMLTextAreaElement;
255-
}
256-
) {
257-
const val = e.currentTarget.value;
258-
setInput(val);
259-
setIsComposing(val.length > confirmedIndex());
260-
setLastConversion(null);
261-
if (isMenuOpen()) {
262-
setIsMenuOpen(false);
263-
setLookupReading(null);
236+
function handleCompositionStart(e: CompositionEvent & { currentTarget: HTMLTextAreaElement }) {
237+
setIsComposing(true);
238+
setCompositionStart(e.currentTarget.selectionStart);
239+
}
240+
241+
function handleCompositionEnd(e: CompositionEvent & { currentTarget: HTMLTextAreaElement }) {
242+
setIsComposing(false);
243+
const start = compositionStart();
244+
const pos = e.currentTarget.selectionStart;
245+
const reading = input().slice(start, pos);
246+
if (wanakana.isHiragana(reading) && reading.length) {
247+
setLookupReading(reading);
248+
setSelectedIndex(0);
249+
setIsMenuOpen(true);
264250
}
265251
}
266252

@@ -283,6 +269,8 @@ export function IMEField() {
283269
value={input()}
284270
onInput={handleInput}
285271
onKeyDown={handleKeyDown}
272+
onCompositionStart={handleCompositionStart}
273+
onCompositionEnd={handleCompositionEnd}
286274
class="caret-foreground bg-transparent text-transparent"
287275
/>
288276
</div>
@@ -305,7 +293,7 @@ export function IMEField() {
305293
<For each={suggestions()}>
306294
{(s, idx) => (
307295
<DropdownMenuItem
308-
ref={(el) => (itemRefs[idx()] = el)}
296+
ref={(el) => (itemRefs[idx()] = el!)}
309297
onSelect={() => commitSuggestion(idx())}
310298
onFocus={() => setSelectedIndex(idx())}
311299
data-highlighted={selectedIndex() === idx()}
@@ -316,9 +304,7 @@ export function IMEField() {
316304
</For>
317305
</div>
318306
<div class="text-muted-foreground flex items-center justify-end border-t px-2 py-1.5 text-xs">
319-
<span>
320-
{selectedIndex() + 1} / {suggestions().length}
321-
</span>
307+
{selectedIndex() + 1} / {suggestions().length}
322308
</div>
323309
</>
324310
</Show>

src/components/Info.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ export default function Info(props: {
88
<>
99
<Show when={props.showTitle()}>
1010
<h1 class="flex flex-col items-center text-3xl font-semibold sm:text-4xl">
11-
<img src="icon.png" alt="" class="h-16 w-16" />
11+
<img src="icon.png" alt="" class="mb-2 h-10 w-10 lg:h-16 lg:w-16" />
1212
<p>
1313
QuickIME – An Input Method Editor that <span class="text-primary">doesn't suck</span>
1414
</p>

src/entry-server.tsx

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,32 @@ export default createHandler(() => (
88
<head>
99
<meta charset="utf-8" />
1010
<meta name="viewport" content="width=device-width, initial-scale=1" />
11+
<title>QuickIME – An Input Method Editor that doesn't suck</title>
1112
<link rel="icon" href="icon.png" />
13+
<meta name="author" content="xlcdev" />
14+
<meta
15+
name="description"
16+
content="A local-first Input Method Editor (IME) for Japanese, aiming for a fast and frustration-free experience. Since all IME's on Linux/Windows/Android are annoying or slow."
17+
/>
18+
19+
<meta property="og:type" content="website" />
20+
<meta
21+
property="og:title"
22+
content="A local-first Input Method Editor (IME) for Japanese, aiming for a fast and frustration-free experience. Since all IME's on Linux/Windows/Android are annoying or slow."
23+
/>
24+
<meta
25+
property="og:description"
26+
content="A local-first Input Method Editor (IME) for Japanese, aiming for a fast and frustration-free experience. Since all IME's on Linux/Windows/Android are annoying or slow."
27+
/>
28+
<meta property="og:image" content="https://xlc-dev.github.io/QuickIME/icon.png" />
29+
<meta property="og:url" content="https://xlc-dev.github.io/QuickIME/" />
30+
31+
<meta name="twitter:title" content="QuickIME" />
32+
<meta
33+
name="twitter:description"
34+
content="A local-first Input Method Editor (IME) for Japanese, aiming for a fast and frustration-free experience. Since all IME's on Linux/Windows/Android are annoying or slow."
35+
/>
36+
<meta name="twitter:image" content="https://xlc-dev.github.io/QuickIME/icon.png" />
1237
{assets}
1338
</head>
1439
<body>

src/routes/index.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ export default function Home() {
4545
<IMEField />
4646

4747
<footer class="text-muted-foreground w-full text-sm">
48-
<Flex justifyContent="center" alignItems="center" class="gap-3">
48+
<Flex justifyContent="center" alignItems="center" class="gap-2">
4949
<span>
5050
Made by{" "}
5151
<a

0 commit comments

Comments
 (0)