Skip to content

Commit cc7a691

Browse files
committed
refactor: extract StatusBar and ToolBar
Signed-off-by: Yukai Huang <[email protected]>
1 parent 2677d21 commit cc7a691

File tree

3 files changed

+485
-361
lines changed

3 files changed

+485
-361
lines changed

public/js/lib/editor/StatusBar.js

Lines changed: 313 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,313 @@
1+
import Storage from './storage'
2+
import { availableThemes } from './constants'
3+
import { defaultEditorMode, jumpToAddressBarKeymapName } from './editor-config'
4+
import debounce from 'lodash/debounce'
5+
6+
export default class StatusBar {
7+
constructor(editor, config) {
8+
this.editor = editor
9+
this.config = config
10+
this.statusBar = null
11+
this.statusCursor = null
12+
this.statusSelection = null
13+
this.statusFile = null
14+
this.statusIndicators = null
15+
this.statusIndent = null
16+
this.statusKeymap = null
17+
this.statusLength = null
18+
this.statusTheme = null
19+
this.statusSpellcheck = null
20+
this.statusLinter = null
21+
this.statusPreferences = null
22+
this.statusPanel = null
23+
this.jumpToAddressBarKeymapValue = null
24+
}
25+
26+
init(template) {
27+
this.statusBar = $(template)
28+
this.statusCursor = this.statusBar.find('.status-cursor > .status-line-column')
29+
this.statusSelection = this.statusBar.find('.status-cursor > .status-selection')
30+
this.statusFile = this.statusBar.find('.status-file')
31+
this.statusIndicators = this.statusBar.find('.status-indicators')
32+
this.statusIndent = this.statusBar.find('.status-indent')
33+
this.statusKeymap = this.statusBar.find('.status-keymap')
34+
this.statusLength = this.statusBar.find('.status-length')
35+
this.statusTheme = this.statusBar.find('.status-theme')
36+
this.statusSpellcheck = this.statusBar.find('.status-spellcheck')
37+
this.statusLinter = this.statusBar.find('.status-linter')
38+
this.statusPreferences = this.statusBar.find('.status-preferences')
39+
40+
this.setupThemeDropdown()
41+
this.setupIndentation()
42+
this.setupKeymap()
43+
this.setupLinter()
44+
this.setupPreferences()
45+
this.handleResize()
46+
47+
this.statusPanel = this.editor.addPanel(this.statusBar[0], {
48+
position: 'bottom'
49+
})
50+
51+
// Add jQuery-compatible interface
52+
this.outerHeight = () => this.statusBar.outerHeight()
53+
this.height = () => this.statusBar.height()
54+
55+
return this.statusBar
56+
}
57+
58+
handleResize() {
59+
// Resize handling is now managed by Editor class
60+
}
61+
62+
getHeight() {
63+
return this.statusBar ? this.statusBar[0].offsetHeight : 0
64+
}
65+
66+
setDropdownMaxHeight(maxHeight) {
67+
if (this.statusBar) {
68+
this.statusBar.find('.status-theme ul.dropdown-menu').css('max-height', `${maxHeight}px`)
69+
}
70+
}
71+
72+
update() {
73+
if (!this.statusBar) return
74+
75+
const cursor = this.editor.getCursor()
76+
const cursorText = `Line ${cursor.line + 1}, Column ${cursor.ch + 1}`
77+
this.statusCursor.text(cursorText)
78+
79+
const fileText = ` — ${this.editor.lineCount()} Lines`
80+
this.statusFile.text(fileText)
81+
82+
const docLength = this.editor.getValue().length
83+
this.statusLength.text(`Length ${docLength}`)
84+
85+
this.updateLengthIndicator(docLength)
86+
}
87+
88+
updateLengthIndicator(docLength) {
89+
if (docLength > (this.config.docmaxlength * 0.95)) {
90+
this.statusLength.css('color', 'red')
91+
this.statusLength.attr('title', 'You have almost reached the limit for this document.')
92+
} else if (docLength > (this.config.docmaxlength * 0.8)) {
93+
this.statusLength.css('color', 'orange')
94+
this.statusLength.attr('title', 'This document is nearly full, consider splitting it or creating a new one.')
95+
} else {
96+
this.statusLength.css('color', 'white')
97+
this.statusLength.attr('title', `You can write up to ${this.config.docmaxlength} characters in this document.`)
98+
}
99+
}
100+
101+
setupThemeDropdown() {
102+
this.statusIndicators.find('.status-theme ul.dropdown-menu').append(
103+
availableThemes.map(theme =>
104+
$(`<li value="${theme.value}"><a>${theme.name}</a></li>`)
105+
)
106+
)
107+
108+
const storedTheme = Storage.get('theme')
109+
if (storedTheme && availableThemes.find(theme => storedTheme === theme.value)) {
110+
this.setTheme(storedTheme)
111+
}
112+
113+
this.statusIndicators.find('.status-theme li').click(e => {
114+
const theme = $(e.currentTarget).attr('value')
115+
this.setTheme(theme)
116+
})
117+
}
118+
119+
setTheme(theme) {
120+
this.editor.setOption('theme', theme)
121+
Storage.set('theme', theme)
122+
this.statusIndicators.find('.status-theme li').removeClass('active')
123+
this.statusIndicators.find(`.status-theme li[value="${theme}"]`).addClass('active')
124+
}
125+
126+
setupIndentation() {
127+
const storedIndentType = Storage.get('indent_type')
128+
const storedTabSize = parseInt(Storage.get('tab_size'))
129+
const storedSpaceUnits = parseInt(Storage.get('space_units'))
130+
131+
if (storedIndentType === 'tab') {
132+
this.editor.setOption('indentWithTabs', true)
133+
if (storedTabSize) this.editor.setOption('indentUnit', storedTabSize)
134+
} else if (storedIndentType === 'space') {
135+
this.editor.setOption('indentWithTabs', false)
136+
if (storedSpaceUnits) this.editor.setOption('indentUnit', storedSpaceUnits)
137+
}
138+
139+
if (storedTabSize) {
140+
this.editor.setOption('tabSize', storedTabSize)
141+
}
142+
143+
this.setupIndentationHandlers()
144+
}
145+
146+
setupIndentationHandlers() {
147+
const type = this.statusIndicators.find('.indent-type')
148+
const widthLabel = this.statusIndicators.find('.indent-width-label')
149+
const widthInput = this.statusIndicators.find('.indent-width-input')
150+
151+
type.click(() => {
152+
const currentIndentWithTabs = this.editor.getOption('indentWithTabs')
153+
this.editor.setOption('indentWithTabs', !currentIndentWithTabs)
154+
155+
if (!currentIndentWithTabs) {
156+
const storedTabSize = parseInt(Storage.get('tab_size'))
157+
if (storedTabSize) {
158+
this.editor.setOption('indentUnit', storedTabSize)
159+
this.editor.setOption('tabSize', storedTabSize)
160+
}
161+
} else {
162+
const storedSpaceUnits = parseInt(Storage.get('space_units'))
163+
if (storedSpaceUnits) {
164+
this.editor.setOption('indentUnit', storedSpaceUnits)
165+
}
166+
}
167+
168+
this.updateIndentationDisplay()
169+
})
170+
171+
this.setupIndentationWidthHandlers(widthLabel, widthInput)
172+
}
173+
174+
setupIndentationWidthHandlers(widthLabel, widthInput) {
175+
widthLabel.click(() => {
176+
if (widthLabel.is(':visible')) {
177+
widthLabel.addClass('hidden')
178+
widthInput.removeClass('hidden')
179+
widthInput.val(this.editor.getOption('indentUnit'))
180+
widthInput.select()
181+
}
182+
})
183+
184+
widthInput.on('change', () => {
185+
let val = parseInt(widthInput.val()) || this.editor.getOption('indentUnit')
186+
val = Math.min(Math.max(val, 1), 10)
187+
188+
if (this.editor.getOption('indentWithTabs')) {
189+
this.editor.setOption('tabSize', val)
190+
Storage.set('tab_size', val)
191+
}
192+
this.editor.setOption('indentUnit', val)
193+
Storage.set('space_units', val)
194+
195+
this.updateIndentationDisplay()
196+
})
197+
198+
widthInput.on('blur', () => {
199+
widthLabel.removeClass('hidden')
200+
widthInput.addClass('hidden')
201+
})
202+
}
203+
204+
updateIndentationDisplay() {
205+
const type = this.statusIndicators.find('.indent-type')
206+
const widthLabel = this.statusIndicators.find('.indent-width-label')
207+
208+
const indentWithTabs = this.editor.getOption('indentWithTabs')
209+
type.text(indentWithTabs ? 'Tab Size:' : 'Spaces:')
210+
Storage.set('indent_type', indentWithTabs ? 'tab' : 'space')
211+
212+
const unit = this.editor.getOption('indentUnit')
213+
widthLabel.text(unit)
214+
}
215+
216+
setupKeymap() {
217+
const storedKeymap = Storage.get('keymap')
218+
if (storedKeymap) {
219+
this.editor.setOption('keyMap', storedKeymap)
220+
}
221+
222+
const label = this.statusIndicators.find('.ui-keymap-label')
223+
const sublime = this.statusIndicators.find('.ui-keymap-sublime')
224+
const emacs = this.statusIndicators.find('.ui-keymap-emacs')
225+
const vim = this.statusIndicators.find('.ui-keymap-vim')
226+
227+
this.updateKeymapLabel()
228+
229+
sublime.click(() => this.setKeymap('sublime'))
230+
emacs.click(() => this.setKeymap('emacs'))
231+
vim.click(() => this.setKeymap('vim'))
232+
}
233+
234+
setKeymap(keymap) {
235+
this.editor.setOption('keyMap', keymap)
236+
Storage.set('keymap', keymap)
237+
this.updateKeymapLabel()
238+
}
239+
240+
updateKeymapLabel() {
241+
const keymap = this.editor.getOption('keyMap')
242+
this.statusIndicators.find('.ui-keymap-label').text(keymap)
243+
}
244+
245+
setupLinter() {
246+
const linterToggle = this.statusLinter.find('.ui-linter-toggle')
247+
const enable = Storage.get('linter') === 'true'
248+
249+
this.toggleLinter(enable)
250+
linterToggle.toggleClass('active', enable)
251+
252+
linterToggle.click(() => {
253+
const lintEnable = !!this.editor.getOption('lint')
254+
this.toggleLinter(!lintEnable)
255+
linterToggle.toggleClass('active', !lintEnable)
256+
})
257+
}
258+
259+
toggleLinter(enable) {
260+
const gutters = this.editor.getOption('gutters')
261+
const lintGutter = 'CodeMirror-lint-markers'
262+
263+
if (enable) {
264+
if (!gutters.includes(lintGutter)) {
265+
this.editor.setOption('gutters', [lintGutter, ...gutters])
266+
}
267+
Storage.set('linter', 'true')
268+
} else {
269+
this.editor.setOption('gutters', gutters.filter(g => g !== lintGutter))
270+
Storage.remove('linter')
271+
}
272+
this.editor.setOption('lint', enable ? this.config.linterOptions : false)
273+
}
274+
275+
setupPreferences() {
276+
const overrideBrowserKeymap = $('.ui-preferences-override-browser-keymap label > input[type="checkbox"]')
277+
const storedOverrideBrowserKeymap = Storage.get('preferences-override-browser-keymap')
278+
279+
overrideBrowserKeymap.prop('checked', storedOverrideBrowserKeymap === 'true')
280+
this.setOverrideBrowserKeymap()
281+
282+
overrideBrowserKeymap.change(() => {
283+
this.setOverrideBrowserKeymap()
284+
})
285+
}
286+
287+
setOverrideBrowserKeymap() {
288+
const overrideBrowserKeymap = $('.ui-preferences-override-browser-keymap label > input[type="checkbox"]')
289+
if (overrideBrowserKeymap.is(':checked')) {
290+
Storage.set('preferences-override-browser-keymap', 'true')
291+
this.restoreOverrideEditorKeymap()
292+
} else {
293+
Storage.remove('preferences-override-browser-keymap')
294+
this.resetEditorKeymapToBrowserKeymap()
295+
}
296+
}
297+
298+
resetEditorKeymapToBrowserKeymap() {
299+
const keymap = this.editor.getOption('keyMap')
300+
if (!this.jumpToAddressBarKeymapValue) {
301+
this.jumpToAddressBarKeymapValue = CodeMirror.keyMap[keymap][jumpToAddressBarKeymapName]
302+
delete CodeMirror.keyMap[keymap][jumpToAddressBarKeymapName]
303+
}
304+
}
305+
306+
restoreOverrideEditorKeymap() {
307+
const keymap = this.editor.getOption('keyMap')
308+
if (this.jumpToAddressBarKeymapValue) {
309+
CodeMirror.keyMap[keymap][jumpToAddressBarKeymapName] = this.jumpToAddressBarKeymapValue
310+
this.jumpToAddressBarKeymapValue = null
311+
}
312+
}
313+
}

0 commit comments

Comments
 (0)