@electerm/electerm-react 5.5.15 → 5.5.35

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.
Files changed (64) hide show
  1. package/README.md +14 -4
  2. package/client/common/bookmark-schemas.js +3 -0
  3. package/client/common/constants.js +16 -0
  4. package/client/common/create-title.jsx +5 -1
  5. package/client/common/default-setting.js +4 -2
  6. package/client/common/parse-vv.js +407 -0
  7. package/client/common/theme-defaults.js +5 -2
  8. package/client/common/update-check.js +5 -4
  9. package/client/common/vv-to-tab.js +120 -0
  10. package/client/components/ai/agent-tool-call-card.jsx +19 -4
  11. package/client/components/ai/agent.js +33 -16
  12. package/client/components/ai/ai-chat-history-item.jsx +12 -48
  13. package/client/components/ai/ai-chat.jsx +82 -25
  14. package/client/components/ai/ai-config-props.js +12 -1
  15. package/client/components/ai/ai-config.jsx +14 -0
  16. package/client/components/ai/ai-context-indicator.jsx +83 -0
  17. package/client/components/ai/ai-context.js +272 -0
  18. package/client/components/ai/ai-output.jsx +4 -0
  19. package/client/components/ai/ai-presets.js +13 -2
  20. package/client/components/ai/ai.styl +105 -6
  21. package/client/components/ai/copy-icon.jsx +31 -0
  22. package/client/components/bg/css-overwrite.jsx +16 -1
  23. package/client/components/bookmark-form/common/fields.jsx +3 -0
  24. package/client/components/bookmark-form/common/vv-file-field.jsx +138 -0
  25. package/client/components/bookmark-form/config/spice.js +22 -0
  26. package/client/components/footer/cmd-history.jsx +279 -65
  27. package/client/components/footer/cmd-history.styl +68 -1
  28. package/client/components/footer/footer-entry.jsx +46 -15
  29. package/client/components/icons/ai-icon.styl +4 -1
  30. package/client/components/layout/layout.jsx +12 -13
  31. package/client/components/main/main.jsx +23 -3
  32. package/client/components/main/term-fullscreen.styl +7 -0
  33. package/client/components/quick-commands/qm.styl +58 -0
  34. package/client/components/quick-commands/quick-command-ai-editor.jsx +255 -0
  35. package/client/components/quick-commands/quick-command-ai-editor.styl +13 -0
  36. package/client/components/quick-commands/quick-commands-box.jsx +197 -82
  37. package/client/components/quick-commands/quick-commands-form-elem.jsx +129 -65
  38. package/client/components/quick-commands/quick-commands-select.jsx +11 -0
  39. package/client/components/setting-panel/list.jsx +2 -2
  40. package/client/components/setting-panel/setting-common.jsx +2 -0
  41. package/client/components/side-panel-r/right-side-panel.styl +11 -1
  42. package/client/components/side-panel-r/side-panel-r.jsx +56 -13
  43. package/client/components/sidebar/bookmark-select.jsx +17 -3
  44. package/client/components/sidebar/history-item.jsx +20 -4
  45. package/client/components/sidebar/history.jsx +7 -0
  46. package/client/components/sidebar/index.jsx +13 -4
  47. package/client/components/sidebar/side-panel.jsx +8 -3
  48. package/client/components/sidebar/sidebar-panel.jsx +20 -7
  49. package/client/components/terminal/shortcut-bar.styl +14 -0
  50. package/client/components/terminal/unix-timestamp-tooltip.jsx +27 -1
  51. package/client/components/terminal-info/terminal-info.styl +3 -0
  52. package/client/components/theme/terminal-theme-list.styl +3 -0
  53. package/client/components/theme/theme-ai-editor.jsx +2 -2
  54. package/client/components/theme/theme-form.jsx +2 -2
  55. package/client/components/theme/theme-list-item.jsx +59 -25
  56. package/client/components/tree-list/tree-list.jsx +31 -3
  57. package/client/css/mobile.styl +28 -0
  58. package/client/store/common.js +164 -32
  59. package/client/store/init-state.js +10 -1
  60. package/client/store/load-data.js +72 -0
  61. package/client/store/sidebar.js +5 -1
  62. package/client/store/store.js +15 -2
  63. package/client/store/watch.js +3 -1
  64. package/package.json +1 -1
@@ -1,17 +1,29 @@
1
1
  /**
2
- * quick commands footer selection wrap
2
+ * quick commands panel
3
+ *
4
+ * The panel has two homes: the floating footer box (default) and the right side
5
+ * panel. Which one it is in is a stored preference
6
+ * (store.quickCommandsInRightPanel), so the same component renders both —
7
+ * `inline` drops the floating box and lets the right panel's own container wrap
8
+ * the panel body.
3
9
  */
4
10
 
5
11
  import { useState } from 'react'
6
- import { quickCommandLabelsLsKey, pinnedQuickCommandBarKey } from '../../common/constants'
7
- import { sortBy } from 'lodash-es'
12
+ import { auto } from 'manate/react'
13
+ import {
14
+ quickCommandLabelsLsKey,
15
+ pinnedQuickCommandBarKey
16
+ } from '../../common/constants'
17
+ import { sortBy, pick } from 'lodash-es'
8
18
  import { Button, Input, Select, Space, Flex } from 'antd'
9
19
  import * as ls from '../../common/safe-local-storage'
10
20
  import CmdItem from './quick-command-item'
11
21
  import {
12
22
  EditOutlined,
13
23
  CloseCircleOutlined,
14
- PushpinOutlined
24
+ PushpinOutlined,
25
+ VerticalLeftOutlined,
26
+ VerticalAlignBottomOutlined
15
27
  } from '@ant-design/icons'
16
28
  import classNames from 'classnames'
17
29
  import onDropFunc from './on-drop'
@@ -22,9 +34,42 @@ const e = window.translate
22
34
  const addQuickCommands = 'addQuickCommands'
23
35
  const { Option } = Select
24
36
 
25
- export default function QuickCommandsFooterBox (props) {
37
+ export default auto(function QuickCommandsFooterBox (props) {
38
+ const { store, inline } = props
26
39
  const [keyword, setKeyword] = useState('')
27
40
  const [label, setLabel] = useState(ls.getItem(quickCommandLabelsLsKey, ''))
41
+ // read through the store so the panel re-renders on its own: it is rendered
42
+ // in two different places (footer box and right panel), and only one of them
43
+ // gets props from the layout
44
+ const {
45
+ openQuickCommandBar,
46
+ pinnedQuickCommandBar,
47
+ qmSortByFrequency,
48
+ quickCommandsInRightPanel,
49
+ inActiveTerminal,
50
+ leftSidePanelWidth,
51
+ leftSideBarWidth,
52
+ openedSideBar,
53
+ rightPanelVisible,
54
+ rightPanelPinned,
55
+ rightPanelWidth,
56
+ isMobile
57
+ } = pick(store, [
58
+ 'openQuickCommandBar',
59
+ 'pinnedQuickCommandBar',
60
+ 'qmSortByFrequency',
61
+ 'quickCommandsInRightPanel',
62
+ 'inActiveTerminal',
63
+ 'leftSidePanelWidth',
64
+ 'leftSideBarWidth',
65
+ 'openedSideBar',
66
+ 'rightPanelVisible',
67
+ 'rightPanelPinned',
68
+ 'rightPanelWidth',
69
+ 'isMobile'
70
+ ])
71
+ const all = store.currentQuickCommands
72
+ const quickCommandTags = store.quickCommandTags
28
73
 
29
74
  function handleTogglePinned () {
30
75
  const current = !window.store.pinnedQuickCommandBar
@@ -58,12 +103,12 @@ export default function QuickCommandsFooterBox (props) {
58
103
  setLabel(v)
59
104
  }
60
105
 
61
- // function filterFunc (v, opt) {
62
- // const c = opt.props.children.toLowerCase()
63
- // const m = opt.props.cmd.toLowerCase()
64
- // const vv = v.toLowerCase()
65
- // return c.includes(vv) || m.includes(vv)
66
- // }
106
+ // Dock the panel into the right side panel. The footer box unmounts on the
107
+ // way out (it is gated on quickCommandsInRightPanel), so there is nothing to
108
+ // clean up here — unlike the cmd history popover, which has to be closed.
109
+ function handleMoveToRightPanel () {
110
+ window.store.moveQuickCommandsToRightPanel()
111
+ }
67
112
 
68
113
  function onDragOver (e) {
69
114
  e.preventDefault()
@@ -114,9 +159,6 @@ export default function QuickCommandsFooterBox (props) {
114
159
  }
115
160
 
116
161
  function renderItem (item) {
117
- const {
118
- qmSortByFrequency
119
- } = props
120
162
  return (
121
163
  <CmdItem
122
164
  item={item}
@@ -156,22 +198,69 @@ export default function QuickCommandsFooterBox (props) {
156
198
  })
157
199
  }
158
200
 
159
- const {
160
- openQuickCommandBar,
161
- pinnedQuickCommandBar,
162
- qmSortByFrequency,
163
- inActiveTerminal,
164
- leftSidePanelWidth,
165
- leftSideBarWidth,
166
- openedSideBar
167
- } = props
168
- if ((!openQuickCommandBar && !pinnedQuickCommandBar) || !inActiveTerminal) {
169
- return null
201
+ // The two "move" icons are mirror images and sit in the same slot: the footer
202
+ // box offers to dock the panel into the right side panel, the docked panel
203
+ // offers to hand it back to the footer. Same glyph pair as the cmd history
204
+ // panel (cmd-history.jsx), so the two panels read the same way.
205
+ // Note VerticalLeftOutlined is the one whose bar is on the right (arrow
206
+ // pointing right); VerticalAlignBottomOutlined points at the footer.
207
+ function renderMoveIcon () {
208
+ if (inline) {
209
+ return (
210
+ <VerticalAlignBottomOutlined
211
+ className='qm-move-icon pointer'
212
+ title={e('moveToFooter')}
213
+ onClick={() => window.store.moveQuickCommandsToFooter()}
214
+ />
215
+ )
216
+ }
217
+ return (
218
+ <VerticalLeftOutlined
219
+ className='qm-move-icon pointer'
220
+ title={e('moveToRightPanel')}
221
+ onClick={handleMoveToRightPanel}
222
+ />
223
+ )
224
+ }
225
+
226
+ // Docked, the right panel owns the pin and the close button (both live in its
227
+ // title bar), so the panel keeps only the actions that are about the quick
228
+ // commands themselves. A pin left over from the footer is kept in the store
229
+ // and comes back with the panel, so nothing is silently lost.
230
+ function renderActions () {
231
+ const tp = pinnedQuickCommandBar
232
+ ? 'primary'
233
+ : 'text'
234
+ return (
235
+ <div className='qm-panel-actions mg2l'>
236
+ {renderMoveIcon()}
237
+ <Space.Compact>
238
+ {
239
+ !inline && (
240
+ <Button
241
+ onClick={handleTogglePinned}
242
+ icon={<PushpinOutlined />}
243
+ type={tp}
244
+ />
245
+ )
246
+ }
247
+ <Button
248
+ onClick={window.store.handleOpenQuickCommandsSetting}
249
+ icon={<EditOutlined />}
250
+ />
251
+ {
252
+ !inline && (
253
+ <Button
254
+ onClick={handleClose}
255
+ icon={<CloseCircleOutlined />}
256
+ />
257
+ )
258
+ }
259
+ </Space.Compact>
260
+ </div>
261
+ )
170
262
  }
171
- const all = props.currentQuickCommands
172
- // if (!all.length) {
173
- // return renderNoCmd()
174
- // }
263
+
175
264
  const keyword0 = keyword.toLowerCase()
176
265
  const filtered = filterArray(all, keyword0, label)
177
266
  const sorted = qmSortByFrequency
@@ -184,68 +273,94 @@ export default function QuickCommandsFooterBox (props) {
184
273
  className: 'qm-label-select',
185
274
  allowClear: true
186
275
  }
187
- const tp = pinnedQuickCommandBar
188
- ? 'primary'
189
- : 'text'
190
276
  const cls = classNames('qm-list-wrap')
191
277
  const type = qmSortByFrequency ? 'primary' : 'default'
192
- const w = openedSideBar ? leftSideBarWidth + leftSidePanelWidth : leftSideBarWidth
278
+ // Mirrors the footer's left offset. Mobile reserves nothing for the side
279
+ // panel — it is a full-screen drawer there (store.leftSidePanelWidth returns
280
+ // the viewport width), so adding it would push the popup off-screen.
281
+ const w = openedSideBar && !isMobile
282
+ ? leftSideBarWidth + leftSidePanelWidth
283
+ : leftSideBarWidth
284
+ // Keep the popup's horizontal extent in step with the footer (same `w`
285
+ // expression above). A pinned right panel is a dock that reserves its width
286
+ // in layout.jsx, so give that width up here too — otherwise the popup's right
287
+ // end slides under the dock. Unpinned is an overlay and reserves nothing, and
288
+ // mobile is excluded to match layout.jsx.
289
+ const qmStyle = {
290
+ left: w
291
+ }
292
+ if (rightPanelVisible && rightPanelPinned && !isMobile) {
293
+ qmStyle.right = rightPanelWidth
294
+ }
193
295
  const qmProps = {
194
296
  className: 'qm-wrap-tooltip',
195
- style: {
196
- left: w
297
+ style: qmStyle
298
+ }
299
+
300
+ const content = (
301
+ <div className='pd2'>
302
+ <Flex justify='space-between' className='qm-flex'>
303
+ <Input.Search
304
+ value={keyword}
305
+ onChange={handleChange}
306
+ placeholder=''
307
+ className='qm-search-input'
308
+ />
309
+ <Flex gap='small'>
310
+ <Select
311
+ {...sprops}
312
+ >
313
+ {quickCommandTags.map(
314
+ renderTag
315
+ )}
316
+ </Select>
317
+ <Button
318
+ type={type}
319
+ onClick={window.store.handleSortByFrequency}
320
+ >
321
+ {e('sortByFrequency')}
322
+ </Button>
323
+ </Flex>
324
+ {renderActions()}
325
+ </Flex>
326
+ <div className={cls}>
327
+ {sorted.map(renderItem)}
328
+ {
329
+ !sorted.length && renderNoCmd()
330
+ }
331
+ </div>
332
+ </div>
333
+ )
334
+
335
+ // docked in the right side panel: no floating box, no pin/close of its own —
336
+ // the right panel decides when this is on screen, via store.rightPanelTab
337
+ if (inline) {
338
+ if (store.rightPanelTab !== 'quickCommands') {
339
+ return null
197
340
  }
341
+ return (
342
+ <div className='qm-panel-in-right-panel'>
343
+ {content}
344
+ </div>
345
+ )
346
+ }
347
+
348
+ // Dormant while the panel is docked: the box would otherwise float over the
349
+ // terminal with nothing to show for it, and a pin left on would keep
350
+ // reserving the strip of height it occupies (see layout.jsx).
351
+ if (
352
+ quickCommandsInRightPanel ||
353
+ (!openQuickCommandBar && !pinnedQuickCommandBar) ||
354
+ !inActiveTerminal
355
+ ) {
356
+ return null
198
357
  }
358
+
199
359
  return (
200
360
  <div
201
361
  {...qmProps}
202
362
  >
203
- <div className='pd2'>
204
- <Flex justify='space-between' className='qm-flex'>
205
- <Input.Search
206
- value={keyword}
207
- onChange={handleChange}
208
- placeholder=''
209
- className='qm-search-input'
210
- />
211
- <Flex gap='small'>
212
- <Select
213
- {...sprops}
214
- >
215
- {props.quickCommandTags.map(
216
- renderTag
217
- )}
218
- </Select>
219
- <Button
220
- type={type}
221
- onClick={window.store.handleSortByFrequency}
222
- >
223
- {e('sortByFrequency')}
224
- </Button>
225
- </Flex>
226
- <Space.Compact className='mg2l'>
227
- <Button
228
- onClick={handleTogglePinned}
229
- icon={<PushpinOutlined />}
230
- type={tp}
231
- />
232
- <Button
233
- onClick={window.store.handleOpenQuickCommandsSetting}
234
- icon={<EditOutlined />}
235
- />
236
- <Button
237
- onClick={handleClose}
238
- icon={<CloseCircleOutlined />}
239
- />
240
- </Space.Compact>
241
- </Flex>
242
- <div className={cls}>
243
- {sorted.map(renderItem)}
244
- {
245
- !sorted.length && renderNoCmd()
246
- }
247
- </div>
248
- </div>
363
+ {content}
249
364
  </div>
250
365
  )
251
- }
366
+ })
@@ -4,12 +4,16 @@ import {
4
4
  Select,
5
5
  Input
6
6
  } from 'antd'
7
+ import { RobotOutlined } from '@ant-design/icons'
7
8
  import message from '../common/message'
8
9
  import SwitchLabel from '../common/switch'
9
10
  import { useState } from 'react'
10
11
  import generate from '../../common/uid'
11
12
  import InputAutoFocus from '../common/input-auto-focus'
12
13
  import renderQm from './quick-commands-list-form'
14
+ import QuickCommandAiEditor from './quick-command-ai-editor'
15
+ import ResponsiveTabs from '../common/responsive-tabs'
16
+ import { isAIDisabled } from '../../common/ai-feature'
13
17
  import ShortcutEdit from '../shortcuts/shortcut-editor'
14
18
  import { getKeysTakenData } from '../shortcuts/shortcut-utils'
15
19
  import deepCopy from 'json-deep-copy'
@@ -18,6 +22,8 @@ import HelpIcon from '../common/help-icon'
18
22
 
19
23
  const FormItem = Form.Item
20
24
  const { Option } = Select
25
+ const manualTab = 'qm-form-manual'
26
+ const aiTab = 'qm-form-ai'
21
27
  const e = window.translate
22
28
 
23
29
  export default function QuickCommandForm (props) {
@@ -25,6 +31,7 @@ export default function QuickCommandForm (props) {
25
31
  const { store, formData } = props
26
32
  const { quickCommandTags = [] } = store
27
33
  const [shortcut, setShortcut] = useState(formData.shortcut || '')
34
+ const [tab, setTab] = useState(manualTab)
28
35
  const uid = formData.id || generate()
29
36
  const updateConfig = (name, value) => {
30
37
  form.setFieldsValue({
@@ -86,6 +93,22 @@ export default function QuickCommandForm (props) {
86
93
  message.success(e('saved'))
87
94
  props.onSaved?.()
88
95
  }
96
+
97
+ // AI generated quick command: write it into the same form (so the user can
98
+ // review/edit it) and jump back to the manual tab, where the save button is
99
+ function handleAiGenerated (data) {
100
+ const update = {
101
+ name: data.name,
102
+ commands: data.commands,
103
+ inputOnly: data.inputOnly
104
+ }
105
+ if (data.labels.length) {
106
+ update.labels = data.labels
107
+ }
108
+ form.setFieldsValue(update)
109
+ setTab(manualTab)
110
+ }
111
+
89
112
  const initialValues = formData
90
113
  if (!initialValues.labels) {
91
114
  initialValues.labels = []
@@ -112,6 +135,111 @@ export default function QuickCommandForm (props) {
112
135
  return `{{${t}}}`
113
136
  }).join(', ')
114
137
  const wiki = 'https://github.com/electerm/electerm/wiki/quick-command-templates'
138
+ const aiDisabled = isAIDisabled()
139
+ // renderQm() uses hooks internally, so it must run on every render
140
+ const qmItem = renderQm(form)
141
+ const manualPane = (
142
+ <>
143
+ <FormItem
144
+ label={e('quickCommandName')}
145
+ rules={[{
146
+ max: 60, message: '60 chars max'
147
+ }, {
148
+ required: true, message: 'Name required'
149
+ }]}
150
+ hasFeedback
151
+ name='name'
152
+ >
153
+ <InputAutoFocus />
154
+ </FormItem>
155
+ {qmItem}
156
+ <FormItem
157
+ name='labels'
158
+ label={e('label')}
159
+ >
160
+ <Select
161
+ mode='tags'
162
+ >
163
+ {
164
+ quickCommandTags.map(q => {
165
+ return (
166
+ <Option value={q} key={'qmt-' + q}>
167
+ {q}
168
+ </Option>
169
+ )
170
+ })
171
+ }
172
+ </Select>
173
+ </FormItem>
174
+ <FormItem
175
+ label={e('settingShortcuts')}
176
+ name='shortcut'
177
+ >
178
+ <div>
179
+ <Input className='hide' />
180
+ <ShortcutEdit
181
+ {...editorProps}
182
+ />
183
+ </div>
184
+ </FormItem>
185
+ <FormItem
186
+ label={e('inputOnly')}
187
+ name='inputOnly'
188
+ valuePropName='checked'
189
+ >
190
+ <SwitchLabel />
191
+ </FormItem>
192
+ <FormItem>
193
+ <p>
194
+ <Button
195
+ type='primary'
196
+ htmlType='submit'
197
+ >{e('save')}
198
+ </Button>
199
+ </p>
200
+ </FormItem>
201
+ <p>
202
+ <b className='mg1r'>{e('templates')}:</b>
203
+ <span className='mg1r'>{templatesStr}</span>
204
+ <HelpIcon
205
+ link={wiki}
206
+ />
207
+ </p>
208
+ </>
209
+ )
210
+ const tabsProps = {
211
+ activeKey: tab,
212
+ onChange: setTab,
213
+ className: 'qm-form-tabs',
214
+ size: 'small',
215
+ items: [
216
+ {
217
+ key: manualTab,
218
+ label: e('quickCommand')
219
+ },
220
+ ...(!aiDisabled
221
+ ? [{
222
+ key: aiTab,
223
+ label: (
224
+ <span className='qm-form-tab-ai'>
225
+ <RobotOutlined className='mg1r' />
226
+ AI
227
+ </span>
228
+ )
229
+ }]
230
+ : [])
231
+ ]
232
+ }
233
+ const aiPane = (
234
+ <>
235
+ <ResponsiveTabs {...tabsProps} />
236
+ {
237
+ tab === aiTab
238
+ ? <QuickCommandAiEditor onChange={handleAiGenerated} />
239
+ : manualPane
240
+ }
241
+ </>
242
+ )
115
243
  return (
116
244
  <>
117
245
  <Form
@@ -121,71 +249,7 @@ export default function QuickCommandForm (props) {
121
249
  layout='vertical'
122
250
  initialValues={initialValues}
123
251
  >
124
- <FormItem
125
- label={e('quickCommandName')}
126
- rules={[{
127
- max: 60, message: '60 chars max'
128
- }, {
129
- required: true, message: 'Name required'
130
- }]}
131
- hasFeedback
132
- name='name'
133
- >
134
- <InputAutoFocus />
135
- </FormItem>
136
- {renderQm(form)}
137
- <FormItem
138
- name='labels'
139
- label={e('label')}
140
- >
141
- <Select
142
- mode='tags'
143
- >
144
- {
145
- quickCommandTags.map(q => {
146
- return (
147
- <Option value={q} key={'qmt-' + q}>
148
- {q}
149
- </Option>
150
- )
151
- })
152
- }
153
- </Select>
154
- </FormItem>
155
- <FormItem
156
- label={e('settingShortcuts')}
157
- name='shortcut'
158
- >
159
- <div>
160
- <Input className='hide' />
161
- <ShortcutEdit
162
- {...editorProps}
163
- />
164
- </div>
165
- </FormItem>
166
- <FormItem
167
- label={e('inputOnly')}
168
- name='inputOnly'
169
- valuePropName='checked'
170
- >
171
- <SwitchLabel />
172
- </FormItem>
173
- <FormItem>
174
- <p>
175
- <Button
176
- type='primary'
177
- htmlType='submit'
178
- >{e('save')}
179
- </Button>
180
- </p>
181
- </FormItem>
182
- <p>
183
- <b className='mg1r'>{e('templates')}:</b>
184
- <span className='mg1r'>{templatesStr}</span>
185
- <HelpIcon
186
- link={wiki}
187
- />
188
- </p>
252
+ {aiDisabled ? manualPane : aiPane}
189
253
  </Form>
190
254
  </>
191
255
  )
@@ -5,6 +5,11 @@
5
5
  * on touch (mobile) and mouse (desktop) — the old hover-to-open model was
6
6
  * unreliable on Android, where a tap synthesizes mouseleave and cancels the
7
7
  * open timer.
8
+ *
9
+ * The panel has two homes (see quick-commands-box.jsx): the footer box and the
10
+ * right side panel. While it is docked, this button is repurposed to open and
11
+ * close that panel, exactly like the info and AI icons do — and `active` (fed
12
+ * by the footer, which is already reactive) keeps it lit while it is showing.
8
13
  */
9
14
 
10
15
  import { PureComponent } from 'react'
@@ -14,15 +19,21 @@ import './qm.styl'
14
19
  export default class QuickCommandsFooter extends PureComponent {
15
20
  handleClick = () => {
16
21
  const { store } = window
22
+ if (store.quickCommandsInRightPanel) {
23
+ store.toggleQuickCommandsPanel()
24
+ return
25
+ }
17
26
  store.openQuickCommandBar = !store.openQuickCommandBar
18
27
  }
19
28
 
20
29
  render () {
30
+ const { active } = this.props
21
31
  return (
22
32
  <div className='fleft relative quick-command-trigger-wrap'>
23
33
  <Button
24
34
  size='small'
25
35
  type='text'
36
+ className={'quick-command-trigger' + (active ? ' active' : '')}
26
37
  onClick={this.handleClick}
27
38
  >
28
39
  Q
@@ -9,7 +9,7 @@ import createName, { createTitleTag } from '../../common/create-title'
9
9
  import classnames from 'classnames'
10
10
  import { noop } from 'lodash-es'
11
11
  import highlight from '../common/highlight'
12
- import { settingSyncId, settingCommonId, staticNewItemTabs } from '../../common/constants'
12
+ import { staticNewItemTabs, undeletableIds } from '../../common/constants'
13
13
  import getInitItem from '../../common/init-setting-item'
14
14
 
15
15
  const e = window.translate
@@ -64,7 +64,7 @@ export default class ItemList extends React.PureComponent {
64
64
  }
65
65
 
66
66
  renderDelBtn = item => {
67
- if (!item.id || [settingSyncId, settingCommonId].includes(item.id) || item.id.startsWith('default')) {
67
+ if (!item.id || undeletableIds.has(item.id)) {
68
68
  return null
69
69
  }
70
70
  const { shouldConfirmDel } = this.props
@@ -574,7 +574,9 @@ export default class SettingCommon extends Component {
574
574
  'allowMultiInstance',
575
575
  'disableDeveloperTool',
576
576
  'switchTabOnHover',
577
+ 'doubleClickToOpenBookmark',
577
578
  'disableTabIndex',
579
+ 'onlyShowTitleInTab',
578
580
  'disableShortcutBar',
579
581
  'debug'
580
582
  ]
@@ -2,13 +2,23 @@
2
2
  position absolute
3
3
  right 0
4
4
  top 36px
5
- bottom 0
5
+ // Unpinned is an overlay, so stop above the footer: the footer is app chrome
6
+ // and holds the very icon that toggles this panel, so a full-height overlay
7
+ // would bury its own trigger. 36px is footerHeight (common/constants.js).
8
+ // Two cases move or remove the footer, and are handled where their sibling
9
+ // rules already live — see shortcut-bar.styl (.shortcut-bar-on lifts the
10
+ // footer) and term-fullscreen.styl (rdp/vnc/spice fullscreen hides it).
11
+ bottom 36px
6
12
  z-index 200
7
13
  background var(--main)
8
14
  color var(--text)
9
15
  border-left 1px solid var(--main-darker)
16
+ // Pinned is a dock, not an overlay: it owns the whole right column, top to
17
+ // bottom. The layout squeezes the terminal out of the way instead
18
+ // (layout.jsx reserves rightPanelWidth), so covering the footer is intended.
10
19
  &.right-side-panel-pinned
11
20
  top 0
21
+ bottom 0
12
22
 
13
23
  .drag-handle
14
24
  left 0