@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,42 @@
|
|
|
1
|
+
import {InputList} from "fwtoolkit"
|
|
2
|
+
import {RangeFieldForm} from "./range"
|
|
3
|
+
|
|
4
|
+
export class RangeListForm {
|
|
5
|
+
constructor(dom, initialValue = [[]]) {
|
|
6
|
+
this.dom = dom
|
|
7
|
+
this.fieldHandlers = new WeakMap()
|
|
8
|
+
this.inputList = new InputList({
|
|
9
|
+
dom,
|
|
10
|
+
initialValues: initialValue,
|
|
11
|
+
emptyValue: [],
|
|
12
|
+
renderItem: value => ({
|
|
13
|
+
html: `<div class="range-field"></div>`,
|
|
14
|
+
bind: el => {
|
|
15
|
+
const fieldHandler = new RangeFieldForm(
|
|
16
|
+
el.firstElementChild,
|
|
17
|
+
value
|
|
18
|
+
)
|
|
19
|
+
fieldHandler.init()
|
|
20
|
+
this.fieldHandlers.set(el, fieldHandler)
|
|
21
|
+
}
|
|
22
|
+
}),
|
|
23
|
+
getValue: el => this.fieldHandlers.get(el).value
|
|
24
|
+
})
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
init() {
|
|
28
|
+
// InputList is already rendered by the constructor.
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
get value() {
|
|
32
|
+
const formValue = this.inputList.values.filter(value => value !== false)
|
|
33
|
+
if (formValue.length === 0) {
|
|
34
|
+
return false
|
|
35
|
+
}
|
|
36
|
+
return formValue
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
check() {
|
|
40
|
+
return true
|
|
41
|
+
}
|
|
42
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import TokenField from "tokenfield"
|
|
2
|
+
|
|
3
|
+
export class TagListForm {
|
|
4
|
+
constructor(dom, initialValue = []) {
|
|
5
|
+
this.dom = dom
|
|
6
|
+
this.initialValue = initialValue
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
init() {
|
|
10
|
+
this.dom.innerHTML = '<input class="tags" type="text">'
|
|
11
|
+
this.tokenInput = new TokenField({
|
|
12
|
+
el: this.dom.querySelector(".tags"),
|
|
13
|
+
setItems: this.initialValue.map((key, index) => {
|
|
14
|
+
return {id: index, name: key}
|
|
15
|
+
}),
|
|
16
|
+
keys: {
|
|
17
|
+
188: "delimiter"
|
|
18
|
+
}
|
|
19
|
+
})
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
get value() {
|
|
23
|
+
const formValue = this.tokenInput.getItems().map(item => {
|
|
24
|
+
return item.name
|
|
25
|
+
})
|
|
26
|
+
// If the form has not been filled out, don't consider this form
|
|
27
|
+
return formValue.length > 0 ? formValue : false
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
check() {
|
|
31
|
+
return true
|
|
32
|
+
}
|
|
33
|
+
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import {toggleMark} from "prosemirror-commands"
|
|
2
|
+
|
|
3
|
+
import {InlineInput, icon} from "fwtoolkit"
|
|
4
|
+
|
|
5
|
+
import {titleSchema} from "../../schema/title"
|
|
6
|
+
|
|
7
|
+
export class TitleFieldForm {
|
|
8
|
+
constructor(dom, initialValue) {
|
|
9
|
+
this.inlineInput = new InlineInput(dom, {
|
|
10
|
+
schema: titleSchema,
|
|
11
|
+
nodeType: "literal",
|
|
12
|
+
initialValue,
|
|
13
|
+
tools: [
|
|
14
|
+
{
|
|
15
|
+
command: toggleMark(titleSchema.marks.strong),
|
|
16
|
+
dom: icon("strong", gettext("Strong"))
|
|
17
|
+
},
|
|
18
|
+
{
|
|
19
|
+
command: toggleMark(titleSchema.marks.em),
|
|
20
|
+
dom: icon("em", gettext("Emphasis"))
|
|
21
|
+
},
|
|
22
|
+
{
|
|
23
|
+
command: toggleMark(titleSchema.marks.smallcaps),
|
|
24
|
+
dom: icon("smallcaps", gettext("Small caps"))
|
|
25
|
+
},
|
|
26
|
+
{
|
|
27
|
+
command: toggleMark(titleSchema.marks.sub),
|
|
28
|
+
dom: icon("sub", gettext("Subscript₊"))
|
|
29
|
+
},
|
|
30
|
+
{
|
|
31
|
+
command: toggleMark(titleSchema.marks.sup),
|
|
32
|
+
dom: icon("sup", gettext("Supscript²"))
|
|
33
|
+
},
|
|
34
|
+
{
|
|
35
|
+
command: toggleMark(titleSchema.marks.nocase),
|
|
36
|
+
dom: icon("nocase", gettext("CasE ProTecT"))
|
|
37
|
+
}
|
|
38
|
+
]
|
|
39
|
+
})
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
init() {}
|
|
43
|
+
|
|
44
|
+
get value() {
|
|
45
|
+
return this.inlineInput.value
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
check() {
|
|
49
|
+
return this.inlineInput.check()
|
|
50
|
+
}
|
|
51
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
// Simple regex to check for a basic URL structure
|
|
2
|
+
const simpleUrlRegex = /^(https?:\/\/)?[\w.-]+\.[a-z]{2,}(\/\S*)?$/i
|
|
3
|
+
|
|
4
|
+
function isValidUrl(string) {
|
|
5
|
+
if (simpleUrlRegex.test(string)) {
|
|
6
|
+
try {
|
|
7
|
+
new URL(string)
|
|
8
|
+
return true
|
|
9
|
+
} catch (_e) {
|
|
10
|
+
return false
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
return false
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export class URIFieldForm {
|
|
17
|
+
constructor(dom, initialValue = "", placeHolder = "") {
|
|
18
|
+
this.dom = dom
|
|
19
|
+
this.initialValue = initialValue
|
|
20
|
+
this.placeHolder = placeHolder
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
init() {
|
|
24
|
+
this.dom.innerHTML = `<input class="uri" type="text" value="${this.initialValue}" placeholder="${this.placeHolder}">`
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
get value() {
|
|
28
|
+
const formValue = this.dom.querySelector("input.uri").value
|
|
29
|
+
// If the form has not been filled out, don't consider this form
|
|
30
|
+
return formValue.length > 0 ? formValue : false
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
check() {
|
|
34
|
+
const formValue = this.value
|
|
35
|
+
if (formValue) {
|
|
36
|
+
if (!isValidUrl(formValue)) {
|
|
37
|
+
this.dom.classList.add("fw-fomt-error")
|
|
38
|
+
return false
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
return true
|
|
42
|
+
}
|
|
43
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
export class VerbatimFieldForm {
|
|
2
|
+
constructor(dom, initialValue = "", placeHolder = "") {
|
|
3
|
+
this.dom = dom
|
|
4
|
+
this.initialValue = initialValue
|
|
5
|
+
this.placeHolder = placeHolder
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
init() {
|
|
9
|
+
this.dom.innerHTML = `<input class="verbatim" type="text" value="${this.initialValue}" placeholder="${this.placeHolder}">`
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
get value() {
|
|
13
|
+
const formValue = this.dom.querySelector("input.verbatim").value
|
|
14
|
+
// If the form has not been filled out, don't consider this form
|
|
15
|
+
return formValue.length > 0 ? formValue : false
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
check() {
|
|
19
|
+
return true
|
|
20
|
+
}
|
|
21
|
+
}
|
|
@@ -0,0 +1,424 @@
|
|
|
1
|
+
import {BibFieldTypes, BibTypes} from "bibliojson"
|
|
2
|
+
import {Dialog, DialogTabs, InfoRow, addAlert} from "fwtoolkit"
|
|
3
|
+
import {dateToYear, litToText, nameToText} from "../tools"
|
|
4
|
+
import {CatsForm} from "./cats"
|
|
5
|
+
import {DateFieldForm} from "./fields/date"
|
|
6
|
+
import {KeyFieldForm} from "./fields/key"
|
|
7
|
+
import {KeyListForm} from "./fields/key_list"
|
|
8
|
+
import {LiteralFieldForm} from "./fields/literal"
|
|
9
|
+
import {LiteralListForm} from "./fields/literal_list"
|
|
10
|
+
import {LiteralLongFieldForm} from "./fields/literal_long"
|
|
11
|
+
import {NameListForm} from "./fields/name_list"
|
|
12
|
+
import {RangeListForm} from "./fields/range_list"
|
|
13
|
+
import {TagListForm} from "./fields/tag_list"
|
|
14
|
+
import {TitleFieldForm} from "./fields/title"
|
|
15
|
+
import {URIFieldForm} from "./fields/uri"
|
|
16
|
+
import {VerbatimFieldForm} from "./fields/verbatim"
|
|
17
|
+
import {
|
|
18
|
+
BibFieldHelp,
|
|
19
|
+
BibFieldTitles,
|
|
20
|
+
BibTypeTitles,
|
|
21
|
+
getBibFieldTitle
|
|
22
|
+
} from "./strings"
|
|
23
|
+
import {bibDialog} from "./templates"
|
|
24
|
+
|
|
25
|
+
const FIELD_FORMS = {
|
|
26
|
+
f_date: DateFieldForm,
|
|
27
|
+
f_integer: LiteralFieldForm,
|
|
28
|
+
f_literal: LiteralFieldForm,
|
|
29
|
+
l_literal: LiteralListForm,
|
|
30
|
+
f_long_literal: LiteralLongFieldForm,
|
|
31
|
+
f_key: KeyFieldForm,
|
|
32
|
+
l_key: KeyListForm,
|
|
33
|
+
l_name: NameListForm,
|
|
34
|
+
l_range: RangeListForm,
|
|
35
|
+
l_tag: TagListForm,
|
|
36
|
+
f_title: TitleFieldForm,
|
|
37
|
+
f_uri: URIFieldForm,
|
|
38
|
+
f_verbatim: VerbatimFieldForm
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export class BibEntryForm {
|
|
42
|
+
constructor(bibDB, app = false, itemId = false) {
|
|
43
|
+
this.bibDB = bibDB
|
|
44
|
+
this.itemId = itemId
|
|
45
|
+
this.app = app
|
|
46
|
+
this.fields = {}
|
|
47
|
+
this.currentValues = {}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
init() {
|
|
51
|
+
if (this.app && this.app.isOffline()) {
|
|
52
|
+
// Diable the editing of main user bibliography , since Document bibliography is stored in Editor/Document.
|
|
53
|
+
addAlert(
|
|
54
|
+
"info",
|
|
55
|
+
gettext(
|
|
56
|
+
"You are currently offline. Please try again when you are back online."
|
|
57
|
+
)
|
|
58
|
+
)
|
|
59
|
+
return Promise.resolve()
|
|
60
|
+
}
|
|
61
|
+
if (this.itemId !== false) {
|
|
62
|
+
this.dialogHeader = gettext("Edit Source")
|
|
63
|
+
const bibEntry = this.bibDB.db[this.itemId]
|
|
64
|
+
this.currentValues = JSON.parse(JSON.stringify(bibEntry)) // copy current values
|
|
65
|
+
} else {
|
|
66
|
+
this.dialogHeader = gettext("Register New Source")
|
|
67
|
+
this.currentValues = {
|
|
68
|
+
bib_type: false,
|
|
69
|
+
cats: [],
|
|
70
|
+
entry_key: "",
|
|
71
|
+
fields: {}
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
return this.createForm()
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
addDialogToDOM() {
|
|
78
|
+
// Add form to DOM
|
|
79
|
+
const buttons = [
|
|
80
|
+
{
|
|
81
|
+
type: "close"
|
|
82
|
+
}
|
|
83
|
+
]
|
|
84
|
+
|
|
85
|
+
return new Promise(resolve => {
|
|
86
|
+
buttons.push({
|
|
87
|
+
classes: "fw-dark",
|
|
88
|
+
text: gettext("Submit"),
|
|
89
|
+
click: () => {
|
|
90
|
+
if (this.check()) {
|
|
91
|
+
const returnValue = this.save()
|
|
92
|
+
this.dialog.close()
|
|
93
|
+
resolve(returnValue)
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
})
|
|
97
|
+
|
|
98
|
+
this.entryKeyAutoGenerate = !this.currentValues.entry_key
|
|
99
|
+
this.programmaticallySettingKey = false
|
|
100
|
+
this.lastAutoGeneratedKey = null
|
|
101
|
+
|
|
102
|
+
const hasCats = this.bibDB.cats.length > 0
|
|
103
|
+
this.dialog = new Dialog({
|
|
104
|
+
title: this.dialogHeader,
|
|
105
|
+
id: "bib-dialog",
|
|
106
|
+
width: 940,
|
|
107
|
+
body: bibDialog({
|
|
108
|
+
bib_type: this.currentValues.bib_type,
|
|
109
|
+
BibTypes,
|
|
110
|
+
hasCats
|
|
111
|
+
}),
|
|
112
|
+
buttons
|
|
113
|
+
})
|
|
114
|
+
|
|
115
|
+
this.dialog.open()
|
|
116
|
+
|
|
117
|
+
this.dialogTabs = new DialogTabs(
|
|
118
|
+
[
|
|
119
|
+
{
|
|
120
|
+
id: "req-fields-tab",
|
|
121
|
+
title: gettext("Required Fields"),
|
|
122
|
+
template: () => ""
|
|
123
|
+
},
|
|
124
|
+
{
|
|
125
|
+
id: "opt-fields-tab",
|
|
126
|
+
title: gettext("Optional Fields"),
|
|
127
|
+
template: () => ""
|
|
128
|
+
},
|
|
129
|
+
...(hasCats
|
|
130
|
+
? [
|
|
131
|
+
{
|
|
132
|
+
id: "categories-tab",
|
|
133
|
+
title: gettext("Categories"),
|
|
134
|
+
template: () => ""
|
|
135
|
+
}
|
|
136
|
+
]
|
|
137
|
+
: [])
|
|
138
|
+
],
|
|
139
|
+
{containerId: "bib-dialog-tabs"}
|
|
140
|
+
)
|
|
141
|
+
this.dialogTabs.bind(
|
|
142
|
+
this.dialog.dialogEl.querySelector("#bib-dialog-tabs")
|
|
143
|
+
)
|
|
144
|
+
|
|
145
|
+
const entryKeyInput = document.getElementById("entry-key")
|
|
146
|
+
if (entryKeyInput) {
|
|
147
|
+
entryKeyInput.value = this.currentValues.entry_key || ""
|
|
148
|
+
entryKeyInput.addEventListener("input", () => {
|
|
149
|
+
if (this.programmaticallySettingKey) {
|
|
150
|
+
return
|
|
151
|
+
}
|
|
152
|
+
this.entryKeyAutoGenerate = !entryKeyInput.value.trim()
|
|
153
|
+
if (this.entryKeyAutoGenerate) {
|
|
154
|
+
this.lastAutoGeneratedKey = null
|
|
155
|
+
}
|
|
156
|
+
this.checkEntryKey()
|
|
157
|
+
})
|
|
158
|
+
this.checkEntryKey()
|
|
159
|
+
this.dialog.dialogEl.addEventListener("focusout", () => {
|
|
160
|
+
if (this.entryKeyAutoGenerate) {
|
|
161
|
+
this.maybeAutoGenerateEntryKey()
|
|
162
|
+
}
|
|
163
|
+
})
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
document
|
|
167
|
+
.getElementById("select-bibtype")
|
|
168
|
+
.addEventListener("change", () => resolve(this.changeBibType()))
|
|
169
|
+
})
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
addField(fieldName, dom) {
|
|
173
|
+
const fieldType = BibFieldTypes[fieldName]
|
|
174
|
+
const fieldTitleText = getBibFieldTitle(
|
|
175
|
+
fieldName,
|
|
176
|
+
this.currentValues.bib_type
|
|
177
|
+
)
|
|
178
|
+
|
|
179
|
+
const row = new InfoRow({
|
|
180
|
+
label: fieldTitleText,
|
|
181
|
+
helpText: BibFieldHelp[fieldName],
|
|
182
|
+
fieldClass: fieldName
|
|
183
|
+
})
|
|
184
|
+
dom.insertAdjacentHTML("beforeend", row.html())
|
|
185
|
+
const fieldDOM = dom.lastChild.lastChild
|
|
186
|
+
const FieldClass = FIELD_FORMS[fieldType.type]
|
|
187
|
+
if (FieldClass) {
|
|
188
|
+
const fieldHandler = new FieldClass(
|
|
189
|
+
fieldDOM,
|
|
190
|
+
this.currentValues.fields[fieldName],
|
|
191
|
+
undefined,
|
|
192
|
+
fieldType
|
|
193
|
+
)
|
|
194
|
+
fieldHandler.init()
|
|
195
|
+
this.fields[fieldName] = fieldHandler
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
createForm() {
|
|
200
|
+
const dialogPromise = this.addDialogToDOM()
|
|
201
|
+
if (this.currentValues.bib_type !== false) {
|
|
202
|
+
const eitherOrFields = document.getElementById("eo-fields")
|
|
203
|
+
BibTypes[this.currentValues.bib_type].eitheror.forEach(
|
|
204
|
+
fieldName => {
|
|
205
|
+
this.addField(fieldName, eitherOrFields)
|
|
206
|
+
}
|
|
207
|
+
)
|
|
208
|
+
const reqFields = document.getElementById("req-fields")
|
|
209
|
+
BibTypes[this.currentValues.bib_type].required.forEach(
|
|
210
|
+
fieldName => {
|
|
211
|
+
this.addField(fieldName, reqFields)
|
|
212
|
+
}
|
|
213
|
+
)
|
|
214
|
+
const optFields = document.getElementById("opt-fields")
|
|
215
|
+
BibTypes[this.currentValues.bib_type].optional.forEach(
|
|
216
|
+
fieldName => {
|
|
217
|
+
this.addField(fieldName, optFields)
|
|
218
|
+
}
|
|
219
|
+
)
|
|
220
|
+
const catsField = document.getElementById("categories-field")
|
|
221
|
+
if (catsField) {
|
|
222
|
+
this.catsForm = new CatsForm(
|
|
223
|
+
catsField,
|
|
224
|
+
this.currentValues.cats,
|
|
225
|
+
this.bibDB.cats
|
|
226
|
+
)
|
|
227
|
+
this.catsForm.init()
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
return dialogPromise
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
changeBibType() {
|
|
234
|
+
// Add all current values into temporary currentValues, in case the
|
|
235
|
+
// user still wants them.
|
|
236
|
+
const formValue = this.value
|
|
237
|
+
Object.assign(this.currentValues.fields, formValue.fields)
|
|
238
|
+
this.currentValues.cats = formValue.cats
|
|
239
|
+
this.currentValues.bib_type = formValue.bib_type
|
|
240
|
+
// Reset fields and close dialog.
|
|
241
|
+
this.fields = {}
|
|
242
|
+
this.dialog.close()
|
|
243
|
+
return this.createForm()
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
createEntryKey(bibItem) {
|
|
247
|
+
// We attempt to create a biblatex compatible entry key if there is no entry
|
|
248
|
+
// key so far.
|
|
249
|
+
let entryKey = ""
|
|
250
|
+
if (bibItem.fields.author) {
|
|
251
|
+
entryKey += nameToText(bibItem.fields.author).replace(
|
|
252
|
+
/\s|,|=|;|:|{|}/g,
|
|
253
|
+
""
|
|
254
|
+
)
|
|
255
|
+
} else if (bibItem.fields.editor) {
|
|
256
|
+
entryKey += nameToText(bibItem.fields.editor).replace(
|
|
257
|
+
/\s|,|=|;|:|{|}/g,
|
|
258
|
+
""
|
|
259
|
+
)
|
|
260
|
+
}
|
|
261
|
+
if (bibItem.fields.date) {
|
|
262
|
+
entryKey += bibItem.fields.date
|
|
263
|
+
.split("/")[0]
|
|
264
|
+
.replace(/\?|\*|u|~|-/g, "")
|
|
265
|
+
}
|
|
266
|
+
if (entryKey.length) {
|
|
267
|
+
bibItem.entry_key = entryKey
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
maybeAutoGenerateEntryKey() {
|
|
272
|
+
if (!this.entryKeyAutoGenerate) {
|
|
273
|
+
return
|
|
274
|
+
}
|
|
275
|
+
const input = document.getElementById("entry-key")
|
|
276
|
+
if (!input) {
|
|
277
|
+
return
|
|
278
|
+
}
|
|
279
|
+
const currentValue = input.value.trim()
|
|
280
|
+
if (currentValue && currentValue !== this.lastAutoGeneratedKey) {
|
|
281
|
+
return
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
let lastName = ""
|
|
285
|
+
const nameField = this.fields.author || this.fields.editor
|
|
286
|
+
if (nameField && nameField.value && nameField.value.length) {
|
|
287
|
+
const firstAuthor = nameField.value[0]
|
|
288
|
+
if (firstAuthor.family && firstAuthor.family.length) {
|
|
289
|
+
lastName = litToText(firstAuthor.family)
|
|
290
|
+
} else if (firstAuthor.literal && firstAuthor.literal.length) {
|
|
291
|
+
lastName = litToText(firstAuthor.literal)
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
lastName = lastName.replace(/[^a-zA-Z0-9_-]/g, "")
|
|
295
|
+
|
|
296
|
+
let year = ""
|
|
297
|
+
if (this.fields.date && this.fields.date.value) {
|
|
298
|
+
const yearStr = dateToYear(this.fields.date.value)
|
|
299
|
+
const yearMatch = yearStr.match(/-?(\d+)/)
|
|
300
|
+
year = yearMatch ? yearMatch[1] : ""
|
|
301
|
+
}
|
|
302
|
+
year = year.replace(/[^a-zA-Z0-9_-]/g, "")
|
|
303
|
+
|
|
304
|
+
const baseKey = `${lastName}${year}`
|
|
305
|
+
if (!baseKey) {
|
|
306
|
+
return
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
const existingKeys = new Set(
|
|
310
|
+
Object.entries(this.bibDB.db)
|
|
311
|
+
.filter(
|
|
312
|
+
([id]) =>
|
|
313
|
+
Number.parseInt(id) !==
|
|
314
|
+
Number.parseInt(this.itemId || 0)
|
|
315
|
+
)
|
|
316
|
+
.map(([, entry]) => entry.entry_key)
|
|
317
|
+
)
|
|
318
|
+
|
|
319
|
+
let key = baseKey
|
|
320
|
+
let suffix = ""
|
|
321
|
+
while (existingKeys.has(key + suffix)) {
|
|
322
|
+
suffix =
|
|
323
|
+
suffix === ""
|
|
324
|
+
? "a"
|
|
325
|
+
: String.fromCharCode(suffix.charCodeAt(0) + 1)
|
|
326
|
+
}
|
|
327
|
+
key = key + suffix
|
|
328
|
+
|
|
329
|
+
if (key === currentValue) {
|
|
330
|
+
return
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
this.programmaticallySettingKey = true
|
|
334
|
+
input.value = key
|
|
335
|
+
this.programmaticallySettingKey = false
|
|
336
|
+
this.lastAutoGeneratedKey = key
|
|
337
|
+
this.checkEntryKey()
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
checkEntryKey() {
|
|
341
|
+
const input = document.getElementById("entry-key")
|
|
342
|
+
const warningEl = document.getElementById("entry-key-warning")
|
|
343
|
+
if (!input || !warningEl) {
|
|
344
|
+
return
|
|
345
|
+
}
|
|
346
|
+
const value = input.value.trim()
|
|
347
|
+
if (!value) {
|
|
348
|
+
warningEl.textContent = ""
|
|
349
|
+
return
|
|
350
|
+
}
|
|
351
|
+
const slugPattern = /^[a-zA-Z0-9_-]+$/
|
|
352
|
+
if (!slugPattern.test(value)) {
|
|
353
|
+
warningEl.textContent = gettext(
|
|
354
|
+
"Only letters, numbers, hyphens and underscores are allowed."
|
|
355
|
+
)
|
|
356
|
+
return
|
|
357
|
+
}
|
|
358
|
+
// Check for duplicates in the user's library (soft requirement - just warn)
|
|
359
|
+
const duplicate = Object.entries(this.bibDB.db).find(
|
|
360
|
+
([id, entry]) =>
|
|
361
|
+
entry.entry_key === value &&
|
|
362
|
+
Number.parseInt(id) !== Number.parseInt(this.itemId || 0)
|
|
363
|
+
)
|
|
364
|
+
if (duplicate) {
|
|
365
|
+
warningEl.textContent = gettext(
|
|
366
|
+
"Warning: this key is already used in your library."
|
|
367
|
+
)
|
|
368
|
+
} else {
|
|
369
|
+
warningEl.textContent = ""
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
get value() {
|
|
374
|
+
const entryKeyInput = document.querySelector("#entry-key")
|
|
375
|
+
const entryKey = entryKeyInput
|
|
376
|
+
? entryKeyInput.value.trim()
|
|
377
|
+
: this.currentValues.entry_key
|
|
378
|
+
const returnObj = {
|
|
379
|
+
bib_type: document.querySelector("#select-bibtype").value,
|
|
380
|
+
cats: this.catsForm ? this.catsForm.value : [],
|
|
381
|
+
entry_key: entryKey,
|
|
382
|
+
fields: {}
|
|
383
|
+
}
|
|
384
|
+
Object.keys(this.fields).forEach(fieldName => {
|
|
385
|
+
const fieldValue = this.fields[fieldName].value
|
|
386
|
+
if (fieldValue !== false) {
|
|
387
|
+
returnObj["fields"][fieldName] = fieldValue
|
|
388
|
+
}
|
|
389
|
+
})
|
|
390
|
+
return returnObj
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
save() {
|
|
394
|
+
const isNew = this.itemId === false ? true : false,
|
|
395
|
+
itemId = this.itemId === false ? 0 : this.itemId,
|
|
396
|
+
item = this.value
|
|
397
|
+
|
|
398
|
+
if (!item.entry_key) {
|
|
399
|
+
this.createEntryKey(item)
|
|
400
|
+
}
|
|
401
|
+
const saveObj = {}
|
|
402
|
+
saveObj[itemId] = item
|
|
403
|
+
return this.bibDB.saveBibEntries(saveObj, isNew)
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
check() {
|
|
407
|
+
let passed = true
|
|
408
|
+
if (!this.currentValues.bib_type) {
|
|
409
|
+
return false
|
|
410
|
+
}
|
|
411
|
+
Object.keys(this.fields).forEach(fieldName => {
|
|
412
|
+
if (this.fields[fieldName].check() !== true) {
|
|
413
|
+
passed = false
|
|
414
|
+
}
|
|
415
|
+
})
|
|
416
|
+
if (!passed) {
|
|
417
|
+
addAlert(
|
|
418
|
+
"error",
|
|
419
|
+
gettext("Error in form, check highlighted values!")
|
|
420
|
+
)
|
|
421
|
+
}
|
|
422
|
+
return passed
|
|
423
|
+
}
|
|
424
|
+
}
|