• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

caleb531 / workday-time-calculator / 6983834262

24 Nov 2023 06:17PM UTC coverage: 93.789% (-0.009%) from 93.798%
6983834262

push

github

caleb531
Replace onReceiveCompletions gandler with Tiny Emitter

355 of 390 branches covered (0.0%)

Branch coverage included in aggregate %.

9 of 9 new or added lines in 2 files covered. (100.0%)

13 existing lines in 2 files now uncovered.

2182 of 2315 relevant lines covered (94.25%)

415.37 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

78.6
/scripts/components/editor.js
1
import { debounce } from 'lodash-es';
3✔
2
import m from 'mithril';
3✔
3
import Quill from 'quill';
3✔
4
import 'quill/dist/quill.snow.css';
3✔
5
import appStorage from '../models/app-storage.js';
3✔
6
import EditorAutocompleter from '../models/editor-autocompleter.js';
3✔
7
import EditorAutocompleterComponent from './editor-autocompleter.js';
3✔
8

3✔
9
class EditorComponent {
3✔
10
  oninit({ attrs: { preferences, selectedDate, onSetLogContents } }) {
3✔
11
    this.preferences = preferences;
180✔
12
    this.selectedDate = selectedDate.clone();
180✔
13
    this.onSetLogContents = onSetLogContents;
180✔
14
    this.autocompleter = new EditorAutocompleter({
180✔
15
      autocompleteMode: this.preferences.autocompleteMode
180✔
16
    });
180✔
17
    this.preferences.on('change:autocompleteMode', (key, newMode) => {
180✔
18
      this.autocompleter.setMode(newMode);
6✔
19
    });
180✔
20
  }
180✔
21

3✔
22
  onupdate({ attrs: { selectedDate } }) {
3✔
23
    if (!selectedDate.isSame(this.selectedDate)) {
573✔
24
      this.selectedDate = selectedDate.clone();
12✔
25
      this.getLogContentsForSelectedDate().then((logContents) => {
12✔
26
        this.setEditorText(logContents);
12✔
27
      });
12✔
28
    }
12✔
29
  }
573✔
30

3✔
31
  // Autocomplete the shown completion, if there is one; if not, run the
3✔
32
  // designated callback as a fallback
3✔
33
  autocomplete(range, options = {}) {
3✔
34
    const completionPlaceholder = this.autocompleter.getCompletionPlaceholder();
×
35
    if (completionPlaceholder) {
×
36
      this.editor.insertText(range.index, completionPlaceholder + ' ', 'user');
×
37
      this.editor.setSelection(
×
38
        range.index + completionPlaceholder.length + 1,
×
39
        0,
×
40
        'user'
×
41
      );
×
42
      this.autocompleter.cancel();
×
43
    } else if (options.fallbackBehavior) {
×
44
      options.fallbackBehavior();
×
45
    }
×
UNCOV
46
  }
×
47

3✔
48
  async initializeEditor(editorContainer) {
3✔
49
    this.editor = new Quill(editorContainer, {
180✔
50
      theme: 'snow',
180✔
51
      placeholder:
180✔
52
        '1. Category One\n\t\ta. 9 to 12:15\n\t\t\t\ti. Did this\n2. Category Two\n\t\ta. 12:45 to 5\n\t\t\t\ti. Did that',
180✔
53
      formats: ['list', 'indent'],
180✔
54
      modules: {
180✔
55
        toolbar: [
180✔
56
          [{ list: 'bullet' }, { list: 'ordered' }],
180✔
57
          [{ indent: '-1' }, { indent: '+1' }]
180✔
58
        ],
180✔
59
        history: {
180✔
60
          // Do not add the editor contents to the Undo history when the app
180✔
61
          // initially loads, or when the selected date changes
180✔
62
          userOnly: true
180✔
63
        },
180✔
64
        keyboard: {
180✔
65
          bindings: {
180✔
66
            // Use <tab> and shift-<tab> to indent/un-indent; these must be
180✔
67
            // defined on editor initialization rather than via
180✔
68
            // keyboard.addBinding (see
180✔
69
            // <https://github.com/quilljs/quill/issues/1647>)
180✔
70
            tab: {
180✔
71
              key: 9,
180✔
72
              handler: (range) => {
180✔
73
                this.autocomplete(range, {
×
74
                  fallbackBehavior: () => {
×
75
                    this.editor.formatLine(range, { indent: '+1' }, 'user');
×
76
                    this.autocompleter.cancel();
×
77
                  }
×
78
                });
×
UNCOV
79
              }
×
80
            },
180✔
81
            arrowRight: {
180✔
82
              key: 39,
180✔
83
              handler: (range) => {
180✔
84
                this.autocomplete(range, {
×
85
                  fallbackBehavior: () => {
×
86
                    if (range.length) {
×
87
                      this.editor.setSelection(
×
88
                        range.index + range.length,
×
89
                        0,
×
90
                        'user'
×
91
                      );
×
92
                    } else {
×
93
                      this.editor.setSelection(
×
94
                        range.index + range.length + 1,
×
95
                        0,
×
96
                        'user'
×
97
                      );
×
98
                    }
×
99
                  }
×
100
                });
×
UNCOV
101
              }
×
102
            },
180✔
103
            shiftTab: {
180✔
104
              key: 9,
180✔
105
              shiftKey: true,
180✔
106
              handler: (range) => {
180✔
107
                this.editor.formatLine(range, { indent: '-1' }, 'user');
×
108
                this.autocompleter.cancel();
×
UNCOV
109
              }
×
110
            },
180✔
111
            indent: {
180✔
112
              // 221 corresponds to right bracket (']')
180✔
113
              key: 221,
180✔
114
              shortKey: true,
180✔
115
              handler: (range) => {
180✔
116
                this.editor.formatLine(range, { indent: '+1' }, 'user');
×
117
                this.autocompleter.cancel();
×
UNCOV
118
              }
×
119
            },
180✔
120
            unIndent: {
180✔
121
              // 219 corresponds to left bracket ('[')
180✔
122
              key: 219,
180✔
123
              shortKey: true,
180✔
124
              handler: (range) => {
180✔
125
                this.editor.formatLine(range, { indent: '-1' }, 'user');
×
126
                this.autocompleter.cancel();
×
UNCOV
127
              }
×
128
            },
180✔
129
            escape: {
180✔
130
              key: 27,
180✔
131
              handler: () => {
180✔
132
                if (this.autocompleter.getCompletionPlaceholder()) {
×
133
                  this.autocompleter.cancel();
×
134
                  m.redraw();
×
135
                }
×
UNCOV
136
              }
×
137
            }
180✔
138
          }
180✔
139
        }
180✔
140
      }
180✔
141
    });
180✔
142
    this.editor.on('selection-change', () => {
180✔
143
      this.autocompleter.cancel();
224✔
144
      m.redraw();
224✔
145
    });
180✔
146
    // Ensure that autocomplete placeholder text remains fixed relative to the
180✔
147
    // text being completed
180✔
148
    this.editor.container
180✔
149
      .querySelector('.ql-editor')
180✔
150
      .addEventListener('scroll', () => {
180✔
151
        if (this.autocompleter.getCompletionPlaceholder()) {
×
152
          m.redraw();
×
UNCOV
153
        }
×
154
      });
180✔
155
    // We need to asynchronously defer our text-change handler below with
180✔
156
    // _.debounce() so that the earlier tab-completion handler (which cannot be
180✔
157
    // performed in any less than two atomic operations) can complete before we
180✔
158
    // attempt to fetch completions again; in the case of the tab-completion,
180✔
159
    // this gives time for the selection to be set, just after inserting the
180✔
160
    // completed text
180✔
161
    this.editor.on(
180✔
162
      'text-change',
180✔
163
      debounce((delta, oldDelta, source) => {
180✔
164
        if (source === 'user') {
453✔
165
          let logContents = this.editor.getContents();
261✔
166
          this.onSetLogContents(logContents);
261✔
167
          this.saveTextLog(logContents);
261✔
168
          this.autocompleter.fetchCompletions();
261✔
169
          m.redraw();
261✔
170
        }
261✔
171
        this.editor.focus();
453✔
172
      })
180✔
173
    );
180✔
174
    this.autocompleter.on('receive-completions', () => {
180✔
175
      m.redraw();
249✔
176
    });
180✔
177
    const logContents = await this.getLogContentsForSelectedDate();
180✔
178
    this.setEditorText(logContents);
180✔
179
    this.autocompleter.setEditor(this.editor);
180✔
180
  }
180✔
181

3✔
182
  async getLogContentsForSelectedDate() {
3✔
183
    let dateStorageId = this.getSelectedDateStorageId();
192✔
184
    let logContentsPromise = appStorage.get(dateStorageId);
192✔
185
    try {
192✔
186
      return (await logContentsPromise) || this.getDefaultLogContents();
192✔
187
    } catch (error) {
192!
UNCOV
188
      return this.getDefaultLogContents();
×
UNCOV
189
    }
×
190
  }
192✔
191

3✔
192
  getSelectedDateStorageId() {
3✔
193
    return `wtc-date-${this.selectedDate.format('l')}`;
453✔
194
  }
453✔
195

3✔
196
  getDefaultLogContents() {
3✔
197
    return {
102✔
198
      ops: [
102✔
199
        {
102✔
200
          insert: '\n'
102✔
201
        }
102✔
202
      ]
102✔
203
    };
102✔
204
  }
102✔
205

3✔
206
  setEditorText(logContents, source = 'api') {
3✔
207
    this.editor.setContents(logContents, source);
192✔
208
    this.onSetLogContents(logContents);
192✔
209
    m.redraw();
192✔
210
  }
192✔
211

3✔
212
  saveTextLog(logContents) {
3✔
213
    if (logContents.ops.length === 1 && logContents.ops[0].insert === '\n') {
261✔
214
      // If the contents of the current log are empty, delete the entry from
12✔
215
      // localStorage to conserve space
12✔
216
      appStorage.remove(this.getSelectedDateStorageId(this.selectedDate));
12✔
217
    } else {
261✔
218
      appStorage.set(
249✔
219
        this.getSelectedDateStorageId(this.selectedDate),
249✔
220
        logContents
249✔
221
      );
249✔
222
    }
249✔
223
  }
261✔
224

3✔
225
  view() {
3✔
226
    return m('div.log-editor-area', [
753✔
227
      m(
753✔
228
        'div.log-editor[data-testid="log-editor"]',
753✔
229
        {
753✔
230
          oncreate: (vnode) => {
753✔
231
            this.initializeEditor(vnode.dom);
180✔
232
          }
180✔
233
        },
753✔
234
        [
753✔
235
          this.editor && this.autocompleter.isEnabled
753✔
236
            ? m(EditorAutocompleterComponent, {
753✔
237
                editorContainer: this.editor.container,
561✔
238
                autocompleter: this.autocompleter
561✔
239
              })
561✔
240
            : null
753✔
241
        ]
753✔
242
      )
753✔
243
    ]);
753✔
244
  }
753✔
245
}
3✔
246

3✔
247
export default EditorComponent;
3✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc