1- import emojiRegex from 'emoji-regex'
2- import { HORIZONTAL , SCROLL_NONE , SCROLL_READY , SCROLLING , VERTICAL } from './constant'
1+ import { HORIZONTAL , SCROLLING , VERTICAL } from './constant'
32import { fcitx } from './distribution'
43// Must be put after fcitx import.
54import { setStyle } from './customize' // eslint-disable-line perfectionist/sort-imports
6- import { getLabelFormatter , setLastLabels } from './format-label'
75import { 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 ( ' ' , ' ' ) . replaceAll ( '\n' , '<br>' ) . replaceAll ( '\t' , ' ' )
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
7113function 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-
29538function copyHTML ( ) {
29639 const html = document . documentElement . outerHTML
29740 fcitx . _copyHTML ( html )
0 commit comments