saby-customizer 0.0.0-pre.27 → 0.0.0-pre.31

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.
@@ -0,0 +1,17 @@
1
+ import { toolbar } from 'saby-customizer/lib.js'
2
+ import { octicons } from 'saby-customizer/octicons.js'
3
+ import { GitBranchCardData } from 'saby-customizer/features/git-branch.js'
4
+
5
+
6
+ toolbar.addButton({
7
+ position: 'top',
8
+ title: 'Скопировать ссылку на карточку',
9
+ iconSVG: octicons.link.toSVG({ width: 18 }),
10
+ onclick: card => {
11
+ const gitCard = new GitBranchCardData(card.data)
12
+
13
+ gitCard.restoreFromHistory()
14
+ .then(() => gitCard.copyLink())
15
+ .catch(console.error)
16
+ }
17
+ })
@@ -3,6 +3,198 @@ import { keysMapping, toolbar, PopupDialog, getListOfOpenCards, snackbar, contex
3
3
  import { octicons } from 'saby-customizer/octicons.js'
4
4
 
5
5
 
6
+ /** Класс для управления данными карточки задачи */
7
+ export class GitBranchCardData {
8
+
9
+ /**
10
+ * @typedef GitCardData
11
+ * @property {string} [branchSubName] Имя ветки указанное пользователем
12
+ */
13
+ /** @param {import('saby-customizer/lib.js').CardData & GitCardData} data Данные о задаче */
14
+ constructor(data) {
15
+ /** @type {import('saby-customizer/lib.js').CardData & GitCardData} Данные о задаче */
16
+ this.raw = {
17
+ id: data.id,
18
+ number: data.number,
19
+ branchSubName: data.branchSubName || data.number,
20
+ date: data.date,
21
+ name: data.name,
22
+ uuid: data.uuid,
23
+ url: data.url,
24
+ author: data.author,
25
+ milestones: data.milestones,
26
+ version: data.version,
27
+ description: data.description
28
+ }
29
+ }
30
+
31
+ /** @returns {string} Название регламента Задача/Ошибка и т.д. */
32
+ get name() {
33
+ return this.raw.name
34
+ }
35
+
36
+ /** @returns {string} Заголовок для описания задачи */
37
+ get branchTitle() {
38
+ const { name } = this.raw
39
+ const docNumber = ' № ' + this.raw.number
40
+ const version = ' веха ' + this.raw.version
41
+ const date = ' от ' + this.raw.date
42
+ const author = ' ' + this.raw.author
43
+
44
+ return name + docNumber + version + date + author
45
+ }
46
+
47
+ /** @returns {string} Версия задачи (веха), выбранная пользователем */
48
+ get version() {
49
+ return this.raw.version
50
+ }
51
+
52
+ /**
53
+ * Обновление выбранной версии и обновление карточки задачи
54
+ *
55
+ * @param {string} value Версия
56
+ */
57
+ set version(value) {
58
+ if (this.raw.milestones && this.raw.milestones.includes(value)) {
59
+ this.raw.version = value
60
+ }
61
+ }
62
+
63
+ /** @returns {string} Часть имени ветки, зависящая от регламента задачи */
64
+ get branchType() {
65
+ return this.raw.name === 'Ошибка' ? 'bugfix' : 'feature'
66
+ }
67
+
68
+ /** @returns {string} Часть имени ветки, определяемая пользователем */
69
+ get branchSubName() {
70
+ return this.raw.branchSubName
71
+ }
72
+
73
+ /**
74
+ * Обновление доп. имени ветки и обновление карточки задачи
75
+ *
76
+ * @param {string} value Доп. имя ветки
77
+ */
78
+ set branchSubName(value) {
79
+ this.raw.branchSubName = value || this.raw.number
80
+ }
81
+
82
+ /** @returns {string} Краткое описание задачи */
83
+ get description() {
84
+ return this.raw.description
85
+ }
86
+
87
+ /**
88
+ * Обновление краткого описания и карточки задачи
89
+ *
90
+ * @param {string} value Доп. имя ветки
91
+ */
92
+ set description(value) {
93
+ this.raw.description = value || ''
94
+ }
95
+
96
+ /** @returns {string[]} Список доступных вех (версий) */
97
+ get milestones() {
98
+ return this.raw.milestones
99
+ }
100
+
101
+ /** @returns {string} Ссылка на документ в отдельной вкладке */
102
+ get url() {
103
+ return this.raw.url
104
+ }
105
+
106
+ /**
107
+ * Получение имени ветки с учетом выбора вехи и доп. имени ветки
108
+ *
109
+ * @returns {Promise<string>} Имя ветки
110
+ */
111
+ async getBranchName() {
112
+ const userLogin = await context.userLogin
113
+
114
+ return `${this.version}/${this.branchType}/${userLogin}/${this.branchSubName}`
115
+ }
116
+
117
+ /** Копирование ссылки на задачу */
118
+ copyLink() {
119
+ navigator.clipboard.writeText(this.raw.url)
120
+ snackbar.show(`Скопирована ссылка: ${this.raw.url}`)
121
+ }
122
+
123
+ /** Копирование имени ветки */
124
+ async copyBranchName() {
125
+ const branchName = await this.getBranchName()
126
+
127
+ navigator.clipboard.writeText(branchName)
128
+ snackbar.show(`Скопировано имя ветки: ${branchName}`)
129
+
130
+ await this.saveToHistory()
131
+ }
132
+
133
+ /** Копирование описания для git commit */
134
+ async copyDescription() {
135
+ const description = this.branchTitle + '\n' +
136
+ this.raw.url + '\n' +
137
+ this.raw.description
138
+
139
+ navigator.clipboard.writeText(description)
140
+ snackbar.show(`Скопировано описание для git commit:\n${description}`)
141
+
142
+ await this.saveToHistory()
143
+ }
144
+
145
+ /** Сохраняет данные в историю по карточкам для которых копировали имя ветки или описание */
146
+ async saveToHistory() {
147
+ const branchName = await this.getBranchName()
148
+ const gitBranchHistory = db.table('gitBranchHistory')
149
+ const historyOldData = await gitBranchHistory.get({
150
+ id: this.raw.id,
151
+ branch: branchName
152
+ })
153
+ const historyData = {
154
+ id: this.raw.id,
155
+ branch: branchName,
156
+ version: this.version,
157
+ useDate: new Date(),
158
+ data: this.raw
159
+ }
160
+
161
+ if (historyOldData) {
162
+ await gitBranchHistory.update(historyOldData.pk, historyData)
163
+ } else {
164
+ await gitBranchHistory.add(historyData)
165
+ }
166
+ }
167
+
168
+ /** Восстановление из истории последнего сохраненного состояния карточки */
169
+ async restoreFromHistory() {
170
+ const gitBranchHistory = db.table('gitBranchHistory')
171
+ const historyData = await gitBranchHistory
172
+ .where({ id: this.raw.id })
173
+ .reverse()
174
+ .sortBy('useDate')
175
+
176
+ if (historyData && historyData.length > 0) {
177
+ const [{ data: { version, branchSubName, description } }] = historyData
178
+
179
+ this.version = version
180
+ this.branchSubName = branchSubName
181
+ this.description = description
182
+ }
183
+ }
184
+
185
+ /**
186
+ * Удаление карточки задачи из истории
187
+ *
188
+ * @param {number} primaryKey Ид записи в истории
189
+ */
190
+ async removeFromHistory(primaryKey) {
191
+ const gitBranchHistory = db.table('gitBranchHistory')
192
+
193
+ await gitBranchHistory.delete(primaryKey)
194
+ }
195
+
196
+ }
197
+
6
198
  /** Карточка со сведениями о задаче для копирования ветки */
7
199
  class GitBranchCard extends oom.extends(HTMLElement) {
8
200
 
@@ -27,6 +219,10 @@ class GitBranchCard extends oom.extends(HTMLElement) {
27
219
  whiteSpace: 'nowrap',
28
220
  textOverflow: 'ellipsis'
29
221
  },
222
+ '.history-info': {
223
+ fontSize: '13px',
224
+ fontStyle: 'italic'
225
+ },
30
226
  '.link': {
31
227
  fontSize: '14px',
32
228
  color: 'var(--mdc-theme-primary, #6200ee)'
@@ -84,6 +280,10 @@ class GitBranchCard extends oom.extends(HTMLElement) {
84
280
  '.description-textarea': {
85
281
  width: '100%'
86
282
  },
283
+ '.icon_16': {
284
+ '--mdc-icon-size': '16px',
285
+ '--mdc-icon-button-size': '24px'
286
+ },
87
287
  '.icon_18': {
88
288
  '--mdc-icon-size': '18px',
89
289
  '--mdc-icon-button-size': '24px'
@@ -105,28 +305,11 @@ class GitBranchCard extends oom.extends(HTMLElement) {
105
305
  return String(Math.min(Math.max(1, rows), 5))
106
306
  }
107
307
 
108
- /**
109
- * @typedef GitCardData
110
- * @property {string} [branchSubName] Имя ветки указанное пользователем
111
- */
112
- /** @type {import('saby-customizer/lib.js').CardData & GitCardData} Данные о задаче */
113
- data = {
114
- id: this.options.data.id,
115
- number: this.options.data.number,
116
- branchSubName: this.options.data.branchSubName || this.options.data.number,
117
- date: this.options.data.date,
118
- name: this.options.data.name,
119
- uuid: this.options.data.uuid,
120
- url: this.options.data.url,
121
- author: this.options.data.author,
122
- milestones: this.options.data.milestones,
123
- version: this.options.data.version,
124
- description: this.options.data.description
125
- }
308
+ data = new GitBranchCardData(this.options.data)
126
309
 
127
310
  elements = {
128
311
  /** @type {HTMLSpanElement} */
129
- cardTitle: oom.span(this.branchTitle, { title: this.branchTitle }).dom,
312
+ cardTitle: oom.span(this.data.branchTitle, { title: this.data.branchTitle }).dom,
130
313
  /** @type {import('@material/mwc-icon-button').IconButton} */// @ts-ignore
131
314
  copyLinkButton: oom.mwcIconButton({
132
315
  title: 'Скопировать ссылку',
@@ -167,7 +350,7 @@ class GitBranchCard extends oom.extends(HTMLElement) {
167
350
  /** @type {HTMLSpanElement} */
168
351
  branchNamePart: oom.span({
169
352
  class: 'branch-part'
170
- }, `/${this.branchType}/***/`).dom,
353
+ }, `/${this.data.branchType}/***/`).dom,
171
354
  /** @type {HTMLSpanElement} */
172
355
  branchSubName: oom.span({
173
356
  title: this.data.branchSubName,
@@ -199,17 +382,6 @@ class GitBranchCard extends oom.extends(HTMLElement) {
199
382
  }).dom
200
383
  }
201
384
 
202
- /** @returns {string} Заголовок для описания задачи */
203
- get branchTitle() {
204
- const { name } = this.data
205
- const docNumber = ' № ' + this.data.number
206
- const version = ' веха ' + this.version
207
- const date = ' от ' + this.data.date
208
- const author = ' ' + this.data.author
209
-
210
- return name + docNumber + version + date + author
211
- }
212
-
213
385
  /** @returns {string} Версия задачи (веха), выбранная пользователем */
214
386
  get version() {
215
387
  return this.data.version
@@ -221,18 +393,13 @@ class GitBranchCard extends oom.extends(HTMLElement) {
221
393
  * @param {string} value Версия
222
394
  */
223
395
  set version(value) {
224
- if (this.data.milestones && this.data.milestones.includes(value)) {
225
- this.data.version = value
396
+ this.data.version = value
397
+ if (this.data.version === value) {
226
398
  this.elements.branchButton.textContent = this.data.version
227
- this.elements.cardTitle.textContent = this.branchTitle
399
+ this.elements.cardTitle.textContent = this.data.branchTitle
228
400
  }
229
401
  }
230
402
 
231
- /** @returns {string} Часть имени ветки, зависящая от регламента задачи */
232
- get branchType() {
233
- return this.data.name === 'Ошибка' ? 'bugfix' : 'feature'
234
- }
235
-
236
403
  /** @returns {string} Часть имени ветки, определяемая пользователем */
237
404
  get branchSubName() {
238
405
  return this.data.branchSubName
@@ -244,7 +411,7 @@ class GitBranchCard extends oom.extends(HTMLElement) {
244
411
  * @param {string} value Доп. имя ветки
245
412
  */
246
413
  set branchSubName(value) {
247
- this.data.branchSubName = value || this.data.number
414
+ this.data.branchSubName = value
248
415
  oom(this.elements.branchSubName, { innerHTML: '', title: this.data.branchSubName }, this.data.branchSubName)
249
416
  if (this.elements.branchSubNameTextfield.value !== this.data.branchSubName) {
250
417
  this.elements.branchSubNameTextfield.value = this.data.branchSubName
@@ -262,7 +429,7 @@ class GitBranchCard extends oom.extends(HTMLElement) {
262
429
  * @param {string} value Доп. имя ветки
263
430
  */
264
431
  set description(value) {
265
- this.data.description = value || ''
432
+ this.data.description = value
266
433
  this.elements.description.title = this.data.description
267
434
  this.elements.description.textContent = this.data.description
268
435
  if (this.elements.descriptionTextarea.value !== this.data.description) {
@@ -279,8 +446,32 @@ class GitBranchCard extends oom.extends(HTMLElement) {
279
446
 
280
447
  this.updateBranchNamePart().catch(console.error)
281
448
 
282
- return oom.div({ class: 'mdc-card' }, oom
283
- .div({ class: 'title' }, this.elements.cardTitle)
449
+ const cardElement = oom.div({ class: 'mdc-card' }, oom
450
+ .div({ class: 'title' }, this.elements.cardTitle))
451
+
452
+ if (this.options.pk) {
453
+ // Добавим элементы для карточки задачи из истории
454
+ const useDate = this.options.useDate.toLocaleString()
455
+
456
+ cardElement(oom.div(oom
457
+ .span({ class: 'history-info' }, `Скопировано: ${useDate}`)
458
+ .mwcIconButton({
459
+ title: 'Удалить из истории',
460
+ class: 'icon_16',
461
+ innerHTML: octicons.x.toSVG({ width: 16 }),
462
+ onclick: () => {
463
+ /** @type {GitBranchCardList} */// @ts-ignore
464
+ const cardList = this.parentElement.parentElement
465
+
466
+ this.remove()
467
+ this.data.removeFromHistory(this.options.pk)
468
+ .then(() => cardList.buildHistoryList())
469
+ .catch(console.error)
470
+ }
471
+ })))
472
+ }
473
+
474
+ return cardElement(oom
284
475
  .div(oom
285
476
  .a({ class: 'link' }, this.data.url, {
286
477
  target: '_blank',
@@ -305,7 +496,7 @@ class GitBranchCard extends oom.extends(HTMLElement) {
305
496
  async updateBranchNamePart() {
306
497
  const userLogin = await context.userLogin
307
498
 
308
- this.elements.branchNamePart.textContent = `/${this.branchType}/${userLogin}/`
499
+ this.elements.branchNamePart.textContent = `/${this.data.branchType}/${userLogin}/`
309
500
  }
310
501
 
311
502
  /** Открывает форму редактирования редактирование описания для коммита */
@@ -344,85 +535,29 @@ class GitBranchCard extends oom.extends(HTMLElement) {
344
535
  }
345
536
  }
346
537
 
347
- /**
348
- * Получение имени ветки с учетом выбора вехи и доп. имени ветки
349
- *
350
- * @returns {Promise<string>} Имя ветки
351
- */
352
- async getBranchName() {
353
- const userLogin = await context.userLogin
354
-
355
- return `${this.version}/${this.branchType}/${userLogin}/${this.branchSubName}`
356
- }
357
-
358
538
  /** Копирование ссылки на задачу */
359
539
  copyLink() {
360
- navigator.clipboard.writeText(this.data.url)
361
- snackbar.show(`Скопирована ссылка: ${this.data.url}`)
540
+ this.data.copyLink()
362
541
  }
363
542
 
364
543
  /** Копирование имени ветки */
365
544
  async copyBranchName() {
366
545
  if (this.elements.branchSubNameTextfield.checkValidity()) {
367
- const branchName = await this.getBranchName()
368
-
369
- navigator.clipboard.writeText(branchName)
370
- snackbar.show(`Скопировано имя ветки: ${branchName}`)
371
-
372
- await this.saveToHistory()
546
+ await this.data.copyBranchName()
373
547
  }
374
548
  }
375
549
 
376
550
  /** Копирование описания для git commit */
377
551
  async copyDescription() {
378
- const description = this.branchTitle + '\n' +
379
- this.data.url + '\n' +
380
- this.description
381
-
382
- navigator.clipboard.writeText(description)
383
- snackbar.show(`Скопировано описание для git commit:\n${description}`)
384
-
385
- await this.saveToHistory()
386
- }
387
-
388
- /** Сохраняет данные в историю по карточкам для которых копировали имя ветки или описание */
389
- async saveToHistory() {
390
- const branchName = await this.getBranchName()
391
- const gitBranchHistory = db.table('gitBranchHistory')
392
- const historyOldData = await gitBranchHistory.get({
393
- id: this.data.id,
394
- branch: branchName
395
- })
396
- const historyData = {
397
- id: this.data.id,
398
- branch: branchName,
399
- version: this.version,
400
- useDate: new Date(),
401
- data: this.data
402
- }
403
-
404
- if (historyOldData) {
405
- await gitBranchHistory.update(historyOldData.pk, historyData)
406
- } else {
407
- await gitBranchHistory.add(historyData)
408
- }
552
+ await this.data.copyDescription()
409
553
  }
410
554
 
411
555
  /** Восстановление из истории последнего сохраненного состояния карточки */
412
556
  async restoreFromHistory() {
413
- const gitBranchHistory = db.table('gitBranchHistory')
414
- const historyData = await gitBranchHistory
415
- .where({ id: this.options.data.id })
416
- .reverse()
417
- .sortBy('useDate')
418
-
419
- if (historyData && historyData.length > 0) {
420
- const [{ data: { version, branchSubName, description } }] = historyData
421
-
422
- this.version = version
423
- this.branchSubName = branchSubName
424
- this.description = description
425
- }
557
+ await this.data.restoreFromHistory()
558
+ this.version = this.data.version
559
+ this.branchSubName = this.data.branchSubName
560
+ this.description = this.data.description
426
561
  }
427
562
 
428
563
  }
@@ -439,8 +574,26 @@ class GitBranchCardList extends oom.extends(HTMLElement) {
439
574
  textAlign: 'center'
440
575
  },
441
576
  '.content': {
442
- overflow: 'auto',
443
- maxHeight: 'calc(var(--mdc-dialog-max-height) - 100px)'
577
+ flexGrow: '1',
578
+ overflowY: 'auto',
579
+ overflowX: 'hidden',
580
+ maxHeight: 'calc(var(--mdc-dialog-max-height) - 100px)',
581
+ background: 'var(--mdc-theme-background)'
582
+ },
583
+ '.hide': {
584
+ display: 'none'
585
+ },
586
+ '.nav-buttons': {
587
+ display: 'flex',
588
+ justifyContent: 'space-between',
589
+ alignItems: 'center',
590
+ padding: '4px 4px 0px'
591
+ },
592
+ '.nav-buttons-space': {
593
+ width: '64px'
594
+ },
595
+ '.nav-buttons-page-label': {
596
+ userSelect: 'none'
444
597
  }
445
598
  })
446
599
 
@@ -450,16 +603,38 @@ class GitBranchCardList extends oom.extends(HTMLElement) {
450
603
  // .mwcTab({ label: 'Список задач' })
451
604
  .mwcTab({ label: 'История задач' }))
452
605
 
453
- list = oom.div({ class: 'content' })
606
+ openTaskList = oom.div({ class: 'content' }, oom.div({ class: 'empty' }, 'Загрузка ...'))
607
+ historyTaskList = oom.div({ class: 'content hide' })
608
+
609
+ // Параметры навигации по истории
610
+ historyListPage = 0
611
+ historyListLimit = 3
454
612
 
455
613
  template = async () => {
456
614
  // if (this.openTaskList.length === 0) {
457
615
  // this.tabs({ activeindex: '1' })
458
616
  // }
459
- oom(this, this.tabs, this.list)
460
- oom(this.list, oom.div({ class: 'empty' }, 'Загрузка ...'))
617
+ oom(this, this.tabs, this.openTaskList, this.historyTaskList)
618
+
619
+ this.tabs.dom.addEventListener('MDCTabBar:activated', (/** @type {CustomEvent<import('@material/mwc-list/mwc-list-foundation').ActionDetail>} */event) => {
620
+ switch (event.detail.index) {
621
+ case 0:
622
+ this.historyTaskList({ innerHTML: '' })
623
+ this.historyTaskList.dom.classList.add('hide')
624
+ this.openTaskList.dom.classList.remove('hide')
625
+ break
626
+ case 1:
627
+ this.historyTaskList({ innerHTML: '' }, oom.div({ class: 'empty' }, 'Загрузка ...'))
628
+ this.openTaskList.dom.classList.add('hide')
629
+ this.historyTaskList.dom.classList.remove('hide')
630
+ this.historyListPage = 0
631
+ this.historyListLimit = 3
632
+ this.buildHistoryList().catch(console.error)
633
+ break
634
+ }
635
+ })
461
636
 
462
- this.list({ innerHTML: '' }, await this.buildTaskList())
637
+ this.openTaskList({ innerHTML: '' }, await this.buildTaskList())
463
638
  }
464
639
 
465
640
  /** @returns {Promise<HTMLElement|DocumentFragment>} Вернет готовый список задач с учетом аинхронной загрузки данных */
@@ -481,6 +656,52 @@ class GitBranchCardList extends oom.extends(HTMLElement) {
481
656
  return taskTist.dom
482
657
  }
483
658
 
659
+ /** Строит компонент списка истории задач */
660
+ async buildHistoryList() {
661
+ const offset = this.historyListPage * this.historyListLimit
662
+ const navButtons = oom.div({ class: 'nav-buttons' })
663
+ const gitBranchHistory = db.table('gitBranchHistory')
664
+ const historyData = await gitBranchHistory
665
+ .orderBy('useDate')
666
+ .reverse()
667
+ .offset(offset)
668
+ .limit(this.historyListLimit + 1)
669
+ .toArray()
670
+
671
+ if (historyData.length > 0) {
672
+ if (this.historyListPage > 0) {
673
+ navButtons(oom.mwcButton({
674
+ innerHTML: octicons['arrow-left'].toSVG({ width: 24 }),
675
+ onclick: () => {
676
+ --this.historyListPage
677
+ this.buildHistoryList().catch(console.error)
678
+ }
679
+ }))
680
+ } else {
681
+ navButtons(oom.span({ class: 'nav-buttons-space' }))
682
+ }
683
+ navButtons(oom.span(`${offset + 1} - ${offset + this.historyListLimit}`, {
684
+ class: 'nav-buttons-page-label'
685
+ }))
686
+ if (historyData.length > this.historyListLimit) {
687
+ historyData.pop()
688
+ navButtons(oom.mwcButton({
689
+ innerHTML: octicons['arrow-right'].toSVG({ width: 24 }),
690
+ onclick: () => {
691
+ ++this.historyListPage
692
+ this.buildHistoryList().catch(console.error)
693
+ }
694
+ }))
695
+ } else {
696
+ navButtons(oom.span({ class: 'nav-buttons-space' }))
697
+ }
698
+
699
+ this.historyTaskList({ innerHTML: '' }, navButtons, ...historyData.map(card => new GitBranchCard(card)))
700
+ } else {
701
+ this.historyTaskList({ innerHTML: '' }, oom.div({ class: 'empty' }, 'Задач в истории не найдено'))
702
+ }
703
+ }
704
+
484
705
  }
485
706
 
486
707
 
@@ -493,7 +714,35 @@ keysMapping['alt-KeyB'] = () => PopupDialog.toggle({
493
714
  })
494
715
 
495
716
  toolbar.addButton({
717
+ position: 'right',
496
718
  title: 'Ветки для Git',
497
719
  iconSVG: octicons['git-branch'].toSVG({ width: 24 }),
498
720
  onclick: keysMapping['alt-KeyB']
499
721
  })
722
+
723
+ toolbar.addButton({
724
+ position: 'top',
725
+ filter: { names: ['Ошибка', 'Задача'] },
726
+ title: 'Скопировать имя ветки для Git',
727
+ iconSVG: octicons['git-branch'].toSVG({ width: 18 }),
728
+ onclick: card => {
729
+ const gitCard = new GitBranchCardData(card.data)
730
+
731
+ gitCard.restoreFromHistory()
732
+ .then(() => gitCard.copyBranchName())
733
+ .catch(console.error)
734
+ }
735
+ })
736
+ toolbar.addButton({
737
+ position: 'top',
738
+ filter: { names: ['Ошибка', 'Задача'] },
739
+ title: 'Скопировать описание для Git коммита',
740
+ iconSVG: octicons['git-commit'].toSVG({ width: 18 }),
741
+ onclick: card => {
742
+ const gitCard = new GitBranchCardData(card.data)
743
+
744
+ gitCard.restoreFromHistory()
745
+ .then(() => gitCard.copyDescription())
746
+ .catch(console.error)
747
+ }
748
+ })