@fiduswriter/bibliography-manager 0.1.0
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.
- package/LICENSE +661 -0
- package/README.md +20 -0
- package/dist/database/index.js +172 -0
- package/dist/database/index.js.map +1 -0
- package/dist/database/server_connector.js +47 -0
- package/dist/database/server_connector.js.map +1 -0
- package/dist/export/index.js +23 -0
- package/dist/export/index.js.map +1 -0
- package/dist/form/cats.js +22 -0
- package/dist/form/cats.js.map +1 -0
- package/dist/form/fields/date.js +28 -0
- package/dist/form/fields/date.js.map +1 -0
- package/dist/form/fields/key.js +100 -0
- package/dist/form/fields/key.js.map +1 -0
- package/dist/form/fields/key_list.js +37 -0
- package/dist/form/fields/key_list.js.map +1 -0
- package/dist/form/fields/literal.js +43 -0
- package/dist/form/fields/literal.js.map +1 -0
- package/dist/form/fields/literal_list.js +36 -0
- package/dist/form/fields/literal_list.js.map +1 -0
- package/dist/form/fields/literal_long.js +42 -0
- package/dist/form/fields/literal_long.js.map +1 -0
- package/dist/form/fields/name.js +121 -0
- package/dist/form/fields/name.js.map +1 -0
- package/dist/form/fields/name_list.js +36 -0
- package/dist/form/fields/name_list.js.map +1 -0
- package/dist/form/fields/range.js +89 -0
- package/dist/form/fields/range.js.map +1 -0
- package/dist/form/fields/range_list.js +36 -0
- package/dist/form/fields/range_list.js.map +1 -0
- package/dist/form/fields/tag_list.js +30 -0
- package/dist/form/fields/tag_list.js.map +1 -0
- package/dist/form/fields/title.js +46 -0
- package/dist/form/fields/title.js.map +1 -0
- package/dist/form/fields/uri.js +40 -0
- package/dist/form/fields/uri.js.map +1 -0
- package/dist/form/fields/verbatim.js +19 -0
- package/dist/form/fields/verbatim.js.map +1 -0
- package/dist/form/index.js +344 -0
- package/dist/form/index.js.map +1 -0
- package/dist/form/strings.js +89 -0
- package/dist/form/strings.js.map +1 -0
- package/dist/form/templates.js +59 -0
- package/dist/form/templates.js.map +1 -0
- package/dist/import/bibliography_import.js +83 -0
- package/dist/import/bibliography_import.js.map +1 -0
- package/dist/import/dialog.js +71 -0
- package/dist/import/dialog.js.map +1 -0
- package/dist/import/index.js +3 -0
- package/dist/import/index.js.map +1 -0
- package/dist/import/templates.js +12 -0
- package/dist/import/templates.js.map +1 -0
- package/dist/index.js +6 -0
- package/dist/index.js.map +1 -0
- package/dist/overview/index.js +453 -0
- package/dist/overview/index.js.map +1 -0
- package/dist/overview/menu.js +91 -0
- package/dist/overview/menu.js.map +1 -0
- package/dist/overview/templates.js +24 -0
- package/dist/overview/templates.js.map +1 -0
- package/dist/plugins/bibliography_overview/index.js +2 -0
- package/dist/plugins/bibliography_overview/index.js.map +1 -0
- package/dist/schema/literal.js +24 -0
- package/dist/schema/literal.js.map +1 -0
- package/dist/schema/literal_long.js +40 -0
- package/dist/schema/literal_long.js.map +1 -0
- package/dist/schema/title.js +31 -0
- package/dist/schema/title.js.map +1 -0
- package/dist/tools.js +53 -0
- package/dist/tools.js.map +1 -0
- package/package.json +55 -0
- package/src/database/index.js +234 -0
- package/src/database/server_connector.js +57 -0
- package/src/export/index.js +27 -0
- package/src/form/cats.js +24 -0
- package/src/form/fields/date.js +31 -0
- package/src/form/fields/key.js +113 -0
- package/src/form/fields/key_list.js +45 -0
- package/src/form/fields/literal.js +48 -0
- package/src/form/fields/literal_list.js +42 -0
- package/src/form/fields/literal_long.js +47 -0
- package/src/form/fields/name.js +152 -0
- package/src/form/fields/name_list.js +42 -0
- package/src/form/fields/range.js +104 -0
- package/src/form/fields/range_list.js +42 -0
- package/src/form/fields/tag_list.js +33 -0
- package/src/form/fields/title.js +51 -0
- package/src/form/fields/uri.js +43 -0
- package/src/form/fields/verbatim.js +21 -0
- package/src/form/index.js +424 -0
- package/src/form/strings.js +122 -0
- package/src/form/templates.js +70 -0
- package/src/import/bibliography_import.js +104 -0
- package/src/import/dialog.js +95 -0
- package/src/import/index.js +2 -0
- package/src/import/templates.js +12 -0
- package/src/index.js +5 -0
- package/src/overview/index.js +548 -0
- package/src/overview/menu.js +106 -0
- package/src/overview/templates.js +30 -0
- package/src/plugins/bibliography_overview/index.js +1 -0
- package/src/schema/literal.js +34 -0
- package/src/schema/literal_long.js +50 -0
- package/src/schema/title.js +42 -0
- package/src/tools.js +58 -0
|
@@ -0,0 +1,548 @@
|
|
|
1
|
+
import fixUTF8 from "fix-utf8"
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
Dialog,
|
|
5
|
+
OverviewDataTable,
|
|
6
|
+
OverviewMenuView,
|
|
7
|
+
addAlert,
|
|
8
|
+
ensureCSS,
|
|
9
|
+
escapeText,
|
|
10
|
+
findTarget,
|
|
11
|
+
isActivationEvent,
|
|
12
|
+
setDocTitle,
|
|
13
|
+
whenReady
|
|
14
|
+
} from "fwtoolkit"
|
|
15
|
+
import {baseBodyTemplate, FeedbackTab, SiteMenu} from "@fiduswriter/common"
|
|
16
|
+
import {plugins} from "../../../plugins/bibliography_overview"
|
|
17
|
+
import {getBibTypeTitle} from "../form/strings"
|
|
18
|
+
import {litToText, nameToText} from "../tools"
|
|
19
|
+
import {bulkMenuModel, menuModel} from "./menu"
|
|
20
|
+
import {editCategoriesTemplate} from "./templates"
|
|
21
|
+
|
|
22
|
+
export class BibliographyOverview {
|
|
23
|
+
constructor({app, user}) {
|
|
24
|
+
this.app = app
|
|
25
|
+
this.user = user
|
|
26
|
+
|
|
27
|
+
this.lastSort = {column: 0, dir: "asc"}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/** Bind the init function to doc loading.
|
|
31
|
+
* @function bind
|
|
32
|
+
*/
|
|
33
|
+
init() {
|
|
34
|
+
return whenReady().then(() => {
|
|
35
|
+
this.render()
|
|
36
|
+
const smenu = new SiteMenu(this.app, "bibliography")
|
|
37
|
+
smenu.init()
|
|
38
|
+
this.menu = new OverviewMenuView(this, menuModel)
|
|
39
|
+
this.menu.init()
|
|
40
|
+
this.setBibCategoryList(this.app.bibDB.cats)
|
|
41
|
+
this.initTable(Object.keys(this.app.bibDB.db))
|
|
42
|
+
// Reset scroll position to top to prevent Safari from auto-scrolling
|
|
43
|
+
// to the focused table element, which would hide the header/menu
|
|
44
|
+
window.scrollTo(0, 0)
|
|
45
|
+
this.activatePlugins()
|
|
46
|
+
this.bindEvents()
|
|
47
|
+
})
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
render() {
|
|
51
|
+
this.dom = document.createElement("body")
|
|
52
|
+
this.dom.innerHTML = baseBodyTemplate({
|
|
53
|
+
contents: "",
|
|
54
|
+
user: this.user,
|
|
55
|
+
hasOverview: true,
|
|
56
|
+
app: this.app
|
|
57
|
+
})
|
|
58
|
+
document.body = this.dom
|
|
59
|
+
ensureCSS([
|
|
60
|
+
staticUrl("css/bibliography.css"),
|
|
61
|
+
staticUrl("css/prosemirror.css"),
|
|
62
|
+
staticUrl("css/inline_tools.css")
|
|
63
|
+
])
|
|
64
|
+
setDocTitle(gettext("Bibliography Manager"), this.app)
|
|
65
|
+
const feedbackTab = new FeedbackTab()
|
|
66
|
+
feedbackTab.init()
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
onResize() {
|
|
70
|
+
if (!this.table) {
|
|
71
|
+
return
|
|
72
|
+
}
|
|
73
|
+
this.initTable(Object.keys(this.app.bibDB.db))
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/* Initialize the overview table */
|
|
77
|
+
initTable(ids) {
|
|
78
|
+
if (this.overviewTable) {
|
|
79
|
+
this.overviewTable.destroy()
|
|
80
|
+
this.overviewTable = null
|
|
81
|
+
}
|
|
82
|
+
this.table = null
|
|
83
|
+
this.dtBulk = null
|
|
84
|
+
|
|
85
|
+
const contentsEl = this.dom.querySelector(".fw-contents")
|
|
86
|
+
contentsEl.innerHTML = ""
|
|
87
|
+
|
|
88
|
+
const hiddenCols = [0]
|
|
89
|
+
|
|
90
|
+
if (window.innerWidth < 500) {
|
|
91
|
+
hiddenCols.push(1)
|
|
92
|
+
if (window.innerWidth < 450) {
|
|
93
|
+
hiddenCols.push(3)
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
this.overviewTable = new OverviewDataTable({
|
|
98
|
+
dom: contentsEl,
|
|
99
|
+
classes: ["fw-data-table", "fw-large"],
|
|
100
|
+
columns: [
|
|
101
|
+
{
|
|
102
|
+
select: 0,
|
|
103
|
+
type: "number"
|
|
104
|
+
},
|
|
105
|
+
{
|
|
106
|
+
select: 1,
|
|
107
|
+
type: "boolean",
|
|
108
|
+
sortable: false
|
|
109
|
+
},
|
|
110
|
+
{
|
|
111
|
+
select: hiddenCols,
|
|
112
|
+
hidden: true
|
|
113
|
+
},
|
|
114
|
+
{
|
|
115
|
+
select: 6,
|
|
116
|
+
sortable: false
|
|
117
|
+
},
|
|
118
|
+
{
|
|
119
|
+
select: [this.lastSort.column],
|
|
120
|
+
sort: this.lastSort.dir
|
|
121
|
+
}
|
|
122
|
+
],
|
|
123
|
+
data: ids.map(id => this.createTableRow(id)),
|
|
124
|
+
idColumn: 0,
|
|
125
|
+
checkboxColumn: 1,
|
|
126
|
+
bulkMenu: bulkMenuModel(),
|
|
127
|
+
bulkMenuPage: this,
|
|
128
|
+
searchable: true,
|
|
129
|
+
scrollY: `${Math.max(window.innerHeight - 360, 100)}px`,
|
|
130
|
+
tabIndex: 1,
|
|
131
|
+
labels: {
|
|
132
|
+
noRows: gettext("No sources registered"),
|
|
133
|
+
noResults: gettext("No sources found") // Message shown when there are no search results
|
|
134
|
+
},
|
|
135
|
+
headings: [
|
|
136
|
+
"",
|
|
137
|
+
"",
|
|
138
|
+
gettext("Title"),
|
|
139
|
+
gettext("Sourcetype"),
|
|
140
|
+
gettext("Author"),
|
|
141
|
+
gettext("Published"),
|
|
142
|
+
""
|
|
143
|
+
],
|
|
144
|
+
template: (options, _dom) =>
|
|
145
|
+
`<div class='${options.classes.container}'${options.scrollY.length ? ` style='height: ${options.scrollY}; overflow-Y: auto;'` : ""}></div>`,
|
|
146
|
+
rowRender: (row, tr, _index) => {
|
|
147
|
+
const id = row.cells[0].data
|
|
148
|
+
const inputNode = {
|
|
149
|
+
nodeName: "input",
|
|
150
|
+
attributes: {
|
|
151
|
+
type: "checkbox",
|
|
152
|
+
class: "entry-select fw-check",
|
|
153
|
+
"data-id": id,
|
|
154
|
+
id: `bib-${id}`
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
if (row.cells[1].data) {
|
|
158
|
+
inputNode.attributes.checked = true
|
|
159
|
+
}
|
|
160
|
+
tr.childNodes[0].childNodes = [
|
|
161
|
+
inputNode,
|
|
162
|
+
{
|
|
163
|
+
nodeName: "label",
|
|
164
|
+
attributes: {
|
|
165
|
+
for: `bib-${id}`
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
]
|
|
169
|
+
},
|
|
170
|
+
onEnter: (row, _event) => {
|
|
171
|
+
if (this.getSelected().length > 0) {
|
|
172
|
+
return
|
|
173
|
+
}
|
|
174
|
+
const rowIndex = this.table.data.data.indexOf(row)
|
|
175
|
+
const editButton = this.table.dom.querySelector(
|
|
176
|
+
`tr[data-index="${rowIndex}"] span.edit-bib`
|
|
177
|
+
)
|
|
178
|
+
if (editButton) {
|
|
179
|
+
editButton.click()
|
|
180
|
+
}
|
|
181
|
+
},
|
|
182
|
+
onDelete: row => {
|
|
183
|
+
const bibId = row.cells[0].data
|
|
184
|
+
this.deleteBibEntryDialog([bibId])
|
|
185
|
+
}
|
|
186
|
+
})
|
|
187
|
+
this.overviewTable.init()
|
|
188
|
+
this.table = this.overviewTable.table
|
|
189
|
+
this.table.id = "bibliography"
|
|
190
|
+
this.dtBulk = this.overviewTable.dtBulk
|
|
191
|
+
|
|
192
|
+
this.table.on("datatable.sort", (column, dir) => {
|
|
193
|
+
this.lastSort = {column, dir}
|
|
194
|
+
})
|
|
195
|
+
|
|
196
|
+
this.table.dom.focus()
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
/** Adds a list of bibliography categories to current list of bibliography categories.
|
|
200
|
+
* @function setBibCategoryList
|
|
201
|
+
* @param newBibCategories The new categories which will be added to the existing ones.
|
|
202
|
+
*/
|
|
203
|
+
setBibCategoryList(bibCategories) {
|
|
204
|
+
const catSelector = this.menu.model.content.find(
|
|
205
|
+
menuItem => menuItem.id === "cat_selector"
|
|
206
|
+
)
|
|
207
|
+
catSelector.content = catSelector.content.filter(
|
|
208
|
+
cat => cat.type !== "category"
|
|
209
|
+
)
|
|
210
|
+
|
|
211
|
+
catSelector.content = catSelector.content.concat(
|
|
212
|
+
bibCategories.map(cat => ({
|
|
213
|
+
title: cat.category_title,
|
|
214
|
+
type: "category",
|
|
215
|
+
action: _overview => {
|
|
216
|
+
const trs = this.dom.querySelectorAll(
|
|
217
|
+
"#bibliography > tbody > tr"
|
|
218
|
+
)
|
|
219
|
+
trs.forEach(tr => {
|
|
220
|
+
if (
|
|
221
|
+
tr
|
|
222
|
+
.querySelector(".fw-data-table-title")
|
|
223
|
+
.classList.contains(`cat_${cat.id}`)
|
|
224
|
+
) {
|
|
225
|
+
tr.style.display = ""
|
|
226
|
+
} else {
|
|
227
|
+
tr.style.display = "none"
|
|
228
|
+
}
|
|
229
|
+
})
|
|
230
|
+
}
|
|
231
|
+
}))
|
|
232
|
+
)
|
|
233
|
+
this.menu.update()
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
/** This takes a list of new bib entries and adds them to BibDB and the bibliography table
|
|
237
|
+
* @function updateTable
|
|
238
|
+
*/
|
|
239
|
+
updateTable(ids) {
|
|
240
|
+
// Remove items that already exist
|
|
241
|
+
this.removeTableRows(ids)
|
|
242
|
+
this.table.insert({data: ids.map(id => this.createTableRow(id))})
|
|
243
|
+
// Redo last sort
|
|
244
|
+
this.table.columns.sort(this.lastSort.column, this.lastSort.dir)
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
createTableRow(id) {
|
|
248
|
+
const bibInfo = this.app.bibDB.db[id]
|
|
249
|
+
const bibauthors = bibInfo.fields.author || bibInfo.fields.editor
|
|
250
|
+
const cats = bibInfo.cats.map(cat => `cat_${cat}`)
|
|
251
|
+
return [
|
|
252
|
+
id,
|
|
253
|
+
false, // checkbox
|
|
254
|
+
`<span class="fw-data-table-title ${cats.join(" ")}">
|
|
255
|
+
<i class="fa fa-book"></i>
|
|
256
|
+
<span class="edit-bib fw-link-text fw-searchable" data-id="${id}">
|
|
257
|
+
${bibInfo.fields.title?.length ? escapeText(litToText(bibInfo.fields.title)) : gettext("Untitled")}
|
|
258
|
+
</span>
|
|
259
|
+
${bibInfo.entry_key ? `<small class="bib-entry-key">${escapeText(bibInfo.entry_key)}</small>` : ""}
|
|
260
|
+
</span>`, // title
|
|
261
|
+
getBibTypeTitle(bibInfo.bib_type), // sourcetype
|
|
262
|
+
bibauthors ? nameToText(bibauthors) : "", // author
|
|
263
|
+
`<span class="fw-date">${bibInfo.fields.date ? bibInfo.fields.date.replace("/", " ") : ""}</span>`, // published,
|
|
264
|
+
`<span class="delete-bib fw-link-text" data-id="${id}"><i class="fa fa-trash-alt"> </i></span>` // delete icon
|
|
265
|
+
]
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
removeTableRows(ids) {
|
|
269
|
+
const existingRows = this.table.data.data
|
|
270
|
+
.map((row, index) => {
|
|
271
|
+
const id = row.cells[0].data
|
|
272
|
+
if (ids.includes(id)) {
|
|
273
|
+
return index
|
|
274
|
+
} else {
|
|
275
|
+
return false
|
|
276
|
+
}
|
|
277
|
+
})
|
|
278
|
+
.filter(rowIndex => rowIndex !== false)
|
|
279
|
+
|
|
280
|
+
if (existingRows.length) {
|
|
281
|
+
this.table.rows.remove(existingRows)
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
/** Opens a dialog for editing categories.
|
|
286
|
+
* @function editCategoriesDialog
|
|
287
|
+
*/
|
|
288
|
+
editCategoriesDialog() {
|
|
289
|
+
if (this.app.isOffline()) {
|
|
290
|
+
addAlert(
|
|
291
|
+
"info",
|
|
292
|
+
gettext(
|
|
293
|
+
"You are currently offline. Please try again when you are back online."
|
|
294
|
+
)
|
|
295
|
+
)
|
|
296
|
+
return
|
|
297
|
+
}
|
|
298
|
+
const buttons = [
|
|
299
|
+
{
|
|
300
|
+
text: gettext("Submit"),
|
|
301
|
+
classes: "fw-dark",
|
|
302
|
+
click: () => {
|
|
303
|
+
const cats = {ids: [], titles: []}
|
|
304
|
+
this.dom
|
|
305
|
+
.querySelectorAll("#edit-categories .category-form")
|
|
306
|
+
.forEach(el => {
|
|
307
|
+
const title = el.value.trim()
|
|
308
|
+
if (title.length) {
|
|
309
|
+
cats.ids.push(
|
|
310
|
+
Number.parseInt(
|
|
311
|
+
el.getAttribute("data-id") || 0
|
|
312
|
+
)
|
|
313
|
+
)
|
|
314
|
+
cats.titles.push(title)
|
|
315
|
+
}
|
|
316
|
+
})
|
|
317
|
+
if (this.app.isOffline()) {
|
|
318
|
+
addAlert(
|
|
319
|
+
"info",
|
|
320
|
+
gettext(
|
|
321
|
+
"You are currently offline. Please try again when you are back online."
|
|
322
|
+
)
|
|
323
|
+
)
|
|
324
|
+
} else {
|
|
325
|
+
this.saveCategories(cats)
|
|
326
|
+
}
|
|
327
|
+
dialog.close()
|
|
328
|
+
}
|
|
329
|
+
},
|
|
330
|
+
{
|
|
331
|
+
type: "cancel"
|
|
332
|
+
}
|
|
333
|
+
]
|
|
334
|
+
|
|
335
|
+
const dialog = new Dialog({
|
|
336
|
+
id: "edit-categories",
|
|
337
|
+
width: 350,
|
|
338
|
+
height: 350,
|
|
339
|
+
title: gettext("Edit Categories"),
|
|
340
|
+
body: editCategoriesTemplate({
|
|
341
|
+
categories: this.app.bibDB.cats
|
|
342
|
+
}),
|
|
343
|
+
buttons
|
|
344
|
+
})
|
|
345
|
+
dialog.open()
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
/** Dialog to confirm deletion of bibliography items.
|
|
349
|
+
* @function deleteBibEntryDialog
|
|
350
|
+
* @param ids Ids of items that are to be deleted.
|
|
351
|
+
*/
|
|
352
|
+
deleteBibEntryDialog(ids) {
|
|
353
|
+
const buttons = [
|
|
354
|
+
{
|
|
355
|
+
text: gettext("Delete"),
|
|
356
|
+
classes: "fw-dark",
|
|
357
|
+
click: () => {
|
|
358
|
+
this.deleteBibEntries(ids)
|
|
359
|
+
dialog.close()
|
|
360
|
+
}
|
|
361
|
+
},
|
|
362
|
+
{
|
|
363
|
+
type: "cancel"
|
|
364
|
+
}
|
|
365
|
+
]
|
|
366
|
+
|
|
367
|
+
const dialog = new Dialog({
|
|
368
|
+
id: "confirmdeletion",
|
|
369
|
+
title: gettext("Confirm deletion"),
|
|
370
|
+
body: `<p>${gettext("Delete the bibliography item(s)")}?</p>`,
|
|
371
|
+
buttons,
|
|
372
|
+
icon: "exclamation-triangle"
|
|
373
|
+
})
|
|
374
|
+
dialog.open()
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
// get IDs of selected bib entries
|
|
378
|
+
getSelected() {
|
|
379
|
+
return Array.from(
|
|
380
|
+
this.dom.querySelectorAll(".entry-select:checked:not(:disabled)")
|
|
381
|
+
).map(el => Number.parseInt(el.getAttribute("data-id")))
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
activatePlugins() {
|
|
385
|
+
if (this.plugins) {
|
|
386
|
+
// Plugins have been activated already
|
|
387
|
+
return
|
|
388
|
+
}
|
|
389
|
+
// Add plugins.
|
|
390
|
+
this.plugins = {}
|
|
391
|
+
|
|
392
|
+
return Promise.all(
|
|
393
|
+
plugins.map(([app, plugin]) => {
|
|
394
|
+
if (!this.app.settings.APPS.includes(app)) {
|
|
395
|
+
return Promise.resolve()
|
|
396
|
+
}
|
|
397
|
+
return Promise.all(
|
|
398
|
+
Object.values(plugin).map(pluginExport => {
|
|
399
|
+
if (typeof pluginExport === "function") {
|
|
400
|
+
this.plugins[pluginExport.name] = new pluginExport(
|
|
401
|
+
this
|
|
402
|
+
)
|
|
403
|
+
return (
|
|
404
|
+
this.plugins[pluginExport.name].init() ||
|
|
405
|
+
Promise.resolve()
|
|
406
|
+
)
|
|
407
|
+
}
|
|
408
|
+
return Promise.resolve()
|
|
409
|
+
})
|
|
410
|
+
)
|
|
411
|
+
})
|
|
412
|
+
)
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
/** Initialize the bibliography table and bind interactive parts.
|
|
416
|
+
* @function bibEvents
|
|
417
|
+
*/
|
|
418
|
+
bindEvents() {
|
|
419
|
+
this.dom.addEventListener("click", event =>
|
|
420
|
+
this.handleActivation(event)
|
|
421
|
+
)
|
|
422
|
+
this.dom.addEventListener("keydown", event =>
|
|
423
|
+
this.handleActivation(event)
|
|
424
|
+
)
|
|
425
|
+
|
|
426
|
+
// Allow pasting of bibtex data.
|
|
427
|
+
this.dom.addEventListener("paste", event => {
|
|
428
|
+
if (event.target.nodeName === "INPUT") {
|
|
429
|
+
// We are inside of an input element, cancel.
|
|
430
|
+
return false
|
|
431
|
+
}
|
|
432
|
+
const text = event.clipboardData.getData("text")
|
|
433
|
+
return this.getBibtex(text)
|
|
434
|
+
})
|
|
435
|
+
|
|
436
|
+
// The two drag events are needed to allow dropping
|
|
437
|
+
this.dom.addEventListener("dragover", event => {
|
|
438
|
+
if (event.dataTransfer.types.includes("text/plain")) {
|
|
439
|
+
event.preventDefault()
|
|
440
|
+
}
|
|
441
|
+
})
|
|
442
|
+
|
|
443
|
+
this.dom.addEventListener("dragenter", event => {
|
|
444
|
+
if (event.dataTransfer.types.includes("text/plain")) {
|
|
445
|
+
event.preventDefault()
|
|
446
|
+
}
|
|
447
|
+
})
|
|
448
|
+
|
|
449
|
+
// Allow dropping of bibtex data
|
|
450
|
+
this.dom.addEventListener("drop", event => {
|
|
451
|
+
if (event.target.nodeName === "INPUT") {
|
|
452
|
+
// We are inside of an input element, cancel.
|
|
453
|
+
return false
|
|
454
|
+
}
|
|
455
|
+
const text = fixUTF8(event.dataTransfer.getData("text"))
|
|
456
|
+
return this.getBibtex(text)
|
|
457
|
+
})
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
handleActivation(event) {
|
|
461
|
+
if (!isActivationEvent(event)) {
|
|
462
|
+
return
|
|
463
|
+
}
|
|
464
|
+
const el = {}
|
|
465
|
+
switch (true) {
|
|
466
|
+
case findTarget(event, ".delete-bib", el): {
|
|
467
|
+
const bibId = Number.parseInt(el.target.dataset.id)
|
|
468
|
+
this.deleteBibEntryDialog([bibId])
|
|
469
|
+
break
|
|
470
|
+
}
|
|
471
|
+
case findTarget(event, ".edit-bib", el): {
|
|
472
|
+
const bibId = Number.parseInt(el.target.dataset.id)
|
|
473
|
+
import("../form").then(({BibEntryForm}) => {
|
|
474
|
+
const form = new BibEntryForm(
|
|
475
|
+
this.app.bibDB,
|
|
476
|
+
this.app,
|
|
477
|
+
bibId
|
|
478
|
+
)
|
|
479
|
+
form.init().then(idTranslations => {
|
|
480
|
+
const ids = idTranslations.map(idTrans => idTrans[1])
|
|
481
|
+
return this.updateTable(ids)
|
|
482
|
+
})
|
|
483
|
+
})
|
|
484
|
+
break
|
|
485
|
+
}
|
|
486
|
+
case findTarget(event, ".fw-add-input", el): {
|
|
487
|
+
const itemEl = el.target.closest(".fw-list-input")
|
|
488
|
+
if (!itemEl.nextElementSibling) {
|
|
489
|
+
itemEl.insertAdjacentHTML(
|
|
490
|
+
"afterend",
|
|
491
|
+
`<tr class="fw-list-input">
|
|
492
|
+
<td>
|
|
493
|
+
<input type="text" class="category-form">
|
|
494
|
+
<span class="fw-add-input icon-addremove" tabindex="0"></span>
|
|
495
|
+
</td>
|
|
496
|
+
</tr>`
|
|
497
|
+
)
|
|
498
|
+
} else {
|
|
499
|
+
itemEl.parentElement.removeChild(itemEl)
|
|
500
|
+
}
|
|
501
|
+
break
|
|
502
|
+
}
|
|
503
|
+
default:
|
|
504
|
+
break
|
|
505
|
+
}
|
|
506
|
+
}
|
|
507
|
+
|
|
508
|
+
// find bibtex in pasted or dropped data.
|
|
509
|
+
getBibtex(text) {
|
|
510
|
+
import("../import").then(({BibliographyImporter}) => {
|
|
511
|
+
const importer = new BibliographyImporter(
|
|
512
|
+
text,
|
|
513
|
+
this.app.bibDB,
|
|
514
|
+
newIds => this.updateTable(newIds),
|
|
515
|
+
false
|
|
516
|
+
)
|
|
517
|
+
importer.init()
|
|
518
|
+
})
|
|
519
|
+
return true
|
|
520
|
+
}
|
|
521
|
+
|
|
522
|
+
saveCategories(cats) {
|
|
523
|
+
this.app.bibDB
|
|
524
|
+
.saveCategories(cats)
|
|
525
|
+
.then(bibCats => this.setBibCategoryList(bibCats))
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
deleteBibEntries(ids) {
|
|
529
|
+
this.app.bibDB
|
|
530
|
+
.deleteBibEntries(ids)
|
|
531
|
+
.then(ids => this.removeTableRows(ids))
|
|
532
|
+
}
|
|
533
|
+
|
|
534
|
+
close() {
|
|
535
|
+
if (this.table) {
|
|
536
|
+
this.table.destroy()
|
|
537
|
+
this.table = null
|
|
538
|
+
}
|
|
539
|
+
if (this.dtBulk) {
|
|
540
|
+
this.dtBulk.destroy()
|
|
541
|
+
this.dtBulk = null
|
|
542
|
+
}
|
|
543
|
+
if (this.menu) {
|
|
544
|
+
this.menu.destroy()
|
|
545
|
+
this.menu = null
|
|
546
|
+
}
|
|
547
|
+
}
|
|
548
|
+
}
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import {BibLatexFileExporter} from "../export"
|
|
2
|
+
import {BibliographyFileImportDialog} from "../import"
|
|
3
|
+
|
|
4
|
+
export const bulkMenuModel = () => ({
|
|
5
|
+
content: [
|
|
6
|
+
{
|
|
7
|
+
title: gettext("Delete selected"),
|
|
8
|
+
tooltip: gettext("Delete selected bibliography entries."),
|
|
9
|
+
action: overview => {
|
|
10
|
+
const ids = overview
|
|
11
|
+
.getSelected()
|
|
12
|
+
.map(id => Number.parseInt(id))
|
|
13
|
+
if (ids.length) {
|
|
14
|
+
overview.deleteBibEntryDialog(ids)
|
|
15
|
+
}
|
|
16
|
+
},
|
|
17
|
+
disabled: overview =>
|
|
18
|
+
!overview.getSelected().length || overview.app.isOffline()
|
|
19
|
+
},
|
|
20
|
+
{
|
|
21
|
+
title: gettext("Export selected"),
|
|
22
|
+
tooltip: gettext("Export selected bibliography entries."),
|
|
23
|
+
action: overview => {
|
|
24
|
+
const ids = overview.getSelected()
|
|
25
|
+
if (ids.length) {
|
|
26
|
+
const exporter = new BibLatexFileExporter(
|
|
27
|
+
overview.app.bibDB,
|
|
28
|
+
ids
|
|
29
|
+
)
|
|
30
|
+
exporter.init()
|
|
31
|
+
}
|
|
32
|
+
},
|
|
33
|
+
disabled: overview =>
|
|
34
|
+
!overview.getSelected().length || overview.app.isOffline()
|
|
35
|
+
}
|
|
36
|
+
]
|
|
37
|
+
})
|
|
38
|
+
|
|
39
|
+
export const menuModel = () => ({
|
|
40
|
+
content: [
|
|
41
|
+
{
|
|
42
|
+
type: "dropdown",
|
|
43
|
+
id: "cat_selector",
|
|
44
|
+
keys: "Alt-c",
|
|
45
|
+
content: [
|
|
46
|
+
{
|
|
47
|
+
title: gettext("All categories"),
|
|
48
|
+
action: _overview => {
|
|
49
|
+
const trs = document.querySelectorAll(
|
|
50
|
+
"#bibliography > tbody > tr"
|
|
51
|
+
)
|
|
52
|
+
trs.forEach(tr => (tr.style.display = ""))
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
],
|
|
56
|
+
order: 1
|
|
57
|
+
},
|
|
58
|
+
{
|
|
59
|
+
type: "text",
|
|
60
|
+
title: gettext("Edit categories"),
|
|
61
|
+
keys: "Alt-e",
|
|
62
|
+
action: overview => overview.editCategoriesDialog(),
|
|
63
|
+
order: 2
|
|
64
|
+
},
|
|
65
|
+
{
|
|
66
|
+
type: "text",
|
|
67
|
+
title: gettext("Register new source"),
|
|
68
|
+
keys: "Alt-n",
|
|
69
|
+
action: overview => {
|
|
70
|
+
import("../form").then(({BibEntryForm}) => {
|
|
71
|
+
const form = new BibEntryForm(
|
|
72
|
+
overview.app.bibDB,
|
|
73
|
+
overview.app
|
|
74
|
+
)
|
|
75
|
+
form.init().then(idTranslations => {
|
|
76
|
+
const ids = idTranslations.map(idTrans => idTrans[1])
|
|
77
|
+
return overview.updateTable(ids)
|
|
78
|
+
})
|
|
79
|
+
})
|
|
80
|
+
},
|
|
81
|
+
order: 3
|
|
82
|
+
},
|
|
83
|
+
{
|
|
84
|
+
type: "text",
|
|
85
|
+
title: gettext("Import bibliography"),
|
|
86
|
+
keys: "Alt-u",
|
|
87
|
+
action: overview => {
|
|
88
|
+
const fileImporter = new BibliographyFileImportDialog(
|
|
89
|
+
overview.app.bibDB,
|
|
90
|
+
ids => overview.updateTable(ids),
|
|
91
|
+
overview.app
|
|
92
|
+
)
|
|
93
|
+
fileImporter.init()
|
|
94
|
+
},
|
|
95
|
+
order: 4
|
|
96
|
+
},
|
|
97
|
+
{
|
|
98
|
+
type: "search",
|
|
99
|
+
icon: "search",
|
|
100
|
+
title: gettext("Search bibliography"),
|
|
101
|
+
keys: "Alt-s",
|
|
102
|
+
input: (overview, text) => overview.table.search(text),
|
|
103
|
+
order: 5
|
|
104
|
+
}
|
|
105
|
+
]
|
|
106
|
+
})
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import {escapeText} from "fwtoolkit"
|
|
2
|
+
|
|
3
|
+
/** A template for each category in the category list edit of the bibliography categories list. */
|
|
4
|
+
const categoryFormsTemplate = ({categories}) =>
|
|
5
|
+
`${categories
|
|
6
|
+
.map(
|
|
7
|
+
cat =>
|
|
8
|
+
`<tr id="categoryTr_${cat.id}" class="fw-list-input">
|
|
9
|
+
<td>
|
|
10
|
+
<input type="text" class="category-form" id="categoryTitle_${cat.id}"
|
|
11
|
+
value="${escapeText(cat.category_title)}" data-id="${cat.id}" />
|
|
12
|
+
<span class="fw-add-input icon-addremove" tabindex="0"></span>
|
|
13
|
+
</td>
|
|
14
|
+
</tr>`
|
|
15
|
+
)
|
|
16
|
+
.join("")}
|
|
17
|
+
<tr class="fw-list-input">
|
|
18
|
+
<td>
|
|
19
|
+
<input type="text" class="category-form" />
|
|
20
|
+
<span class="fw-add-input icon-addremove" tabindex="0"></span>
|
|
21
|
+
</td>
|
|
22
|
+
</tr>`
|
|
23
|
+
|
|
24
|
+
/** A template for the editing of bibliography categories list. */
|
|
25
|
+
export const editCategoriesTemplate = ({categories}) =>
|
|
26
|
+
`<table id="editCategoryList" class="fw-dialog-table">
|
|
27
|
+
<tbody>
|
|
28
|
+
${categoryFormsTemplate({categories})}
|
|
29
|
+
</tbody>
|
|
30
|
+
</table>`
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export const plugins = []
|