Skip to content

Commit 2804b9a

Browse files
committed
refactor: move panel operations from api.ts to panel.ts
1 parent b5628c5 commit 2804b9a

File tree

2 files changed

+238
-264
lines changed

2 files changed

+238
-264
lines changed

page/api.ts

Lines changed: 7 additions & 264 deletions
Original file line numberDiff line numberDiff line change
@@ -1,72 +1,14 @@
1-
import emojiRegex from 'emoji-regex'
2-
import { HORIZONTAL, SCROLL_NONE, SCROLL_READY, SCROLLING, VERTICAL } from './constant'
1+
import { HORIZONTAL, SCROLLING, VERTICAL } from './constant'
32
import { fcitx } from './distribution'
43
// Must be put after fcitx import.
54
import { setStyle } from './customize' // eslint-disable-line perfectionist/sort-imports
6-
import { getLabelFormatter, setLastLabels } from './format-label'
75
import { fcitxLog } from './log'
8-
import {
9-
loadPlugins,
10-
pluginManager,
11-
unloadPlugins,
12-
} from './plugin'
13-
import {
14-
fetchComplete,
15-
getScrollState,
16-
recalculateScroll,
17-
scrollKeyAction,
18-
setScrollEnd,
19-
setScrollState,
20-
} from './scroll'
21-
import {
22-
auxDown,
23-
auxUp,
24-
hoverables,
25-
panel,
26-
preedit,
27-
theme,
28-
} from './selector'
29-
import {
30-
setAccentColor,
31-
setTheme,
32-
} from './theme'
33-
import {
34-
answerActions,
35-
div,
36-
getHoverBehavior,
37-
getPagingButtonsStyle,
38-
hideContextmenu,
39-
resetMouseMoveState,
40-
resize,
41-
setActions,
42-
setColorTransition,
43-
} from './ux'
44-
45-
const regex = emojiRegex()
46-
const segmenter = new Intl.Segmenter(undefined, { granularity: 'grapheme' })
47-
48-
function isSingleEmoji(text: string) {
49-
regex.lastIndex = 0
50-
return Array.from(segmenter.segment(text)).length === 1 && regex.test(text)
51-
}
52-
53-
function escapeWS(s: string) {
54-
// XXX:   is broken in Safari
55-
return s.replaceAll(' ', '&nbsp;').replaceAll('\n', '<br>').replaceAll('\t', '&emsp;')
56-
}
57-
58-
function divider(paging: boolean = false) {
59-
const e = div('fcitx-divider')
60-
// Is this divider between candidates and paging buttons?
61-
if (paging) {
62-
e.classList.add('fcitx-divider-paging')
63-
}
64-
const dividerStart = div('fcitx-divider-side')
65-
const dividerMiddle = div('fcitx-divider-middle')
66-
const dividerEnd = div('fcitx-divider-side')
67-
e.append(dividerStart, dividerMiddle, dividerEnd)
68-
return e
69-
}
6+
import { hidePanel, moveHighlight, setCandidates, updateInputPanel } from './panel'
7+
import { loadPlugins, pluginManager, unloadPlugins } from './plugin'
8+
import { getScrollState, scrollKeyAction } from './scroll'
9+
import { hoverables, panel } from './selector'
10+
import { setAccentColor, setTheme } from './theme'
11+
import { answerActions, getHoverBehavior, resize } from './ux'
7012

7113
function setLayout(layout: LAYOUT) {
7214
switch (layout) {
@@ -93,205 +35,6 @@ function setWritingMode(mode: WRITING_MODE) {
9335
}
9436
}
9537

96-
function moveHighlight(from: Element | null, to: Element | null) {
97-
from?.classList.remove('fcitx-highlighted')
98-
to?.classList.add('fcitx-highlighted')
99-
// In vertical or scroll mode, there are multiple marks,
100-
// but either toMark exists (for candidate) or candidateInner doesn't exist (for paging button).
101-
// In horizontal mode, there is a unique mark.
102-
// Don't get mark from "from" because when mouse moves from paging button to outside,
103-
// we need to move highlight from the last hovered candidate (not paging button) to original.
104-
const mark = hoverables?.querySelector('.fcitx-mark')
105-
const toMark = to?.querySelector('.fcitx-mark')
106-
const candidateInner = to?.querySelector('.fcitx-candidate-inner') // not paging button
107-
if (mark && !toMark && candidateInner) {
108-
candidateInner.prepend(mark)
109-
}
110-
}
111-
112-
// Use 2 icons instead of flipping one to avoid 1-pixel shift bug.
113-
const common = '<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="{}"><path d="{}" fill="currentColor"></path></svg>'
114-
// font-awesome
115-
const caretLeft = common.replace('{}', '0 0 192 512').replace('{}', 'M192 127.338v257.324c0 17.818-21.543 26.741-34.142 14.142L29.196 270.142c-7.81-7.81-7.81-20.474 0-28.284l128.662-128.662c12.599-12.6 34.142-3.676 34.142 14.142z')
116-
const caretRight = common.replace('{}', '0 0 192 512').replace('{}', 'M0 384.662V127.338c0-17.818 21.543-26.741 34.142-14.142l128.662 128.662c7.81 7.81 7.81 20.474 0 28.284L34.142 398.804C21.543 411.404 0 402.48 0 384.662z')
117-
// material
118-
const arrowBack = common.replace('{}', '0 0 24 24').replace('{}', 'M16.62 2.99a1.25 1.25 0 0 0-1.77 0L6.54 11.3a.996.996 0 0 0 0 1.41l8.31 8.31c.49.49 1.28.49 1.77 0s.49-1.28 0-1.77L9.38 12l7.25-7.25c.48-.48.48-1.28-.01-1.76z')
119-
const arrowForward = common.replace('{}', '0 0 24 24').replace('{}', 'M7.38 21.01c.49.49 1.28.49 1.77 0l8.31-8.31a.996.996 0 0 0 0-1.41L9.15 2.98c-.49-.49-1.28-.49-1.77 0s-.49 1.28 0 1.77L14.62 12l-7.25 7.25c-.48.48-.48 1.28.01 1.76z')
120-
121-
function setCandidates(cands: Candidate[], highlighted: number, markText: string, pageable: boolean, hasPrev: boolean, hasNext: boolean, scrollState: SCROLL_STATE, scrollStart: boolean, scrollEnd: boolean) {
122-
if (cands.length) {
123-
// Auto layout requires display: not none so that getBoundingClientRect works.
124-
theme.classList.remove('fcitx-hidden')
125-
}
126-
const isVertical = hoverables.classList.contains('fcitx-vertical')
127-
resetMouseMoveState()
128-
hideContextmenu()
129-
setScrollState(scrollState)
130-
// Clear existing candidates when scroll continues.
131-
if (scrollState !== SCROLLING || scrollStart) {
132-
hoverables.innerHTML = ''
133-
hoverables.scrollTop = 0 // Otherwise last scroll position will be kept.
134-
}
135-
else {
136-
fetchComplete()
137-
}
138-
if (scrollState === SCROLLING) {
139-
hoverables.classList.add('fcitx-horizontal-scroll')
140-
hoverables.style.maxBlockSize = '' // Fallback to non-inline larger max-block-size.
141-
setScrollEnd(scrollEnd)
142-
// Disable transition to avoid flash on scroll expand and highlight move under light theme.
143-
setColorTransition(false)
144-
}
145-
else {
146-
hoverables.classList.remove('fcitx-horizontal-scroll')
147-
if (scrollState === SCROLL_READY) {
148-
setLastLabels(cands.map(c => c.label))
149-
}
150-
else {
151-
// Cleanup all leftovers.
152-
hoverables.style.maxBlockSize = ''
153-
}
154-
setColorTransition(true)
155-
}
156-
const label0 = getLabelFormatter()(0)
157-
for (let i = 0; i < cands.length; ++i) {
158-
const candidate = div('fcitx-candidate', 'fcitx-hoverable')
159-
if (i === 0 && scrollState !== SCROLLING) {
160-
candidate.classList.add('fcitx-candidate-first')
161-
}
162-
if (i === highlighted) {
163-
candidate.classList.add('fcitx-highlighted', 'fcitx-highlighted-original')
164-
}
165-
if (i === cands.length - 1 && scrollState !== SCROLLING) {
166-
candidate.classList.add('fcitx-candidate-last')
167-
}
168-
169-
const candidateInner = div('fcitx-candidate-inner', 'fcitx-hoverable-inner')
170-
171-
// Render placeholder for vertical/scroll non-highlighted candidates
172-
if (isVertical || hoverables.classList.contains('fcitx-horizontal-scroll') || i === highlighted) {
173-
const mark = div('fcitx-mark')
174-
if (markText === '') {
175-
mark.classList.add('fcitx-no-text')
176-
}
177-
else {
178-
mark.innerHTML = markText
179-
}
180-
candidateInner.append(mark)
181-
}
182-
183-
if (cands[i].label || scrollState === SCROLLING) {
184-
const label = div('fcitx-label')
185-
label.innerHTML = escapeWS(cands[i].label || label0)
186-
candidateInner.append(label)
187-
}
188-
189-
const text = div('fcitx-text')
190-
text.innerHTML = escapeWS(cands[i].text)
191-
if (isSingleEmoji(cands[i].text)) {
192-
// Hack: for vertical-lr writing mode, 🙅‍♂️ is rotated on Safari and split to 🙅 and ♂ on Chrome.
193-
// Can't find a way that works for text that contains not only emoji (e.g. for preedit) but
194-
// it's a rare case for candidates so should be acceptable.
195-
text.style.writingMode = 'horizontal-tb'
196-
}
197-
candidateInner.append(text)
198-
199-
if (cands[i].comment) {
200-
const comment = div('fcitx-comment')
201-
comment.innerHTML = escapeWS(cands[i].comment)
202-
candidateInner.append(comment)
203-
}
204-
205-
candidate.append(candidateInner)
206-
hoverables.append(candidate)
207-
208-
// For horizontal/scroll mode it needs to fill the row when candidates are not enough.
209-
// For vertical mode, this last divider is hidden.
210-
hoverables.append(divider())
211-
}
212-
213-
setActions(cands.map(c => c.actions))
214-
215-
if (scrollState === SCROLL_READY && getPagingButtonsStyle() !== 'None') {
216-
hoverables.append(divider(true))
217-
const expand = div('fcitx-expand', 'fcitx-paging-inner', 'fcitx-hoverable-inner')
218-
expand.innerHTML = arrowForward
219-
const paging = div('fcitx-paging', 'fcitx-scroll', 'fcitx-hoverable')
220-
paging.append(expand)
221-
hoverables.append(paging)
222-
}
223-
else if (scrollState === SCROLL_NONE && pageable) {
224-
const isArrow = getPagingButtonsStyle() === 'Arrow'
225-
hoverables.append(divider(true))
226-
227-
const prev = div('fcitx-prev', 'fcitx-hoverable')
228-
const prevInner = div('fcitx-paging-inner')
229-
if (hasPrev) {
230-
prevInner.classList.add('fcitx-hoverable-inner')
231-
}
232-
prevInner.innerHTML = isArrow ? arrowBack : caretLeft
233-
prev.appendChild(prevInner)
234-
235-
const next = div('fcitx-next', 'fcitx-hoverable')
236-
const nextInner = div('fcitx-paging-inner')
237-
if (hasNext) {
238-
nextInner.classList.add('fcitx-hoverable-inner')
239-
}
240-
nextInner.innerHTML = isArrow ? arrowForward : caretRight
241-
next.appendChild(nextInner)
242-
243-
const paging = div('fcitx-paging')
244-
if (isArrow) {
245-
paging.classList.add('fcitx-arrow')
246-
}
247-
else {
248-
paging.classList.add('fcitx-triangle')
249-
}
250-
paging.appendChild(prev)
251-
paging.appendChild(next)
252-
hoverables.appendChild(paging)
253-
}
254-
else if (scrollState === SCROLLING) {
255-
recalculateScroll(scrollStart)
256-
}
257-
258-
for (const hoverable of hoverables.querySelectorAll('.fcitx-hoverable')) {
259-
hoverable.addEventListener('mousemove', () => {
260-
const hoverBehavior = getHoverBehavior()
261-
if (hoverBehavior === 'Move' && hoverables.classList.contains('fcitx-mousemoved')) {
262-
const lastHighlighted = hoverables.querySelector('.fcitx-highlighted')
263-
moveHighlight(lastHighlighted, hoverable)
264-
}
265-
})
266-
}
267-
}
268-
269-
function updateElement(element: Element, innerHTML: string) {
270-
if (innerHTML === '') {
271-
element.classList.add('fcitx-hidden')
272-
}
273-
else {
274-
element.innerHTML = innerHTML
275-
element.classList.remove('fcitx-hidden')
276-
}
277-
}
278-
279-
function updateInputPanel(preeditHTML: string, auxUpHTML: string, auxDownHTML: string) {
280-
if (preeditHTML || auxUpHTML || auxDownHTML) {
281-
theme.classList.remove('fcitx-hidden')
282-
}
283-
hideContextmenu()
284-
updateElement(preedit, preeditHTML)
285-
updateElement(auxUp, auxUpHTML)
286-
updateElement(auxDown, auxDownHTML)
287-
}
288-
289-
function hidePanel() {
290-
updateInputPanel('', '', '')
291-
setCandidates([], -1, '', false, false, false, SCROLL_NONE, false, false)
292-
theme.classList.add('fcitx-hidden')
293-
}
294-
29538
function copyHTML() {
29639
const html = document.documentElement.outerHTML
29740
fcitx._copyHTML(html)

0 commit comments

Comments
 (0)