Skip to content

Commit

Permalink
Merge branch 'master' into gh-2104
Browse files Browse the repository at this point in the history
  • Loading branch information
retorquere committed May 11, 2022
2 parents 5cda7e3 + 82b1530 commit d822d36
Show file tree
Hide file tree
Showing 8 changed files with 2,395 additions and 72 deletions.
49 changes: 29 additions & 20 deletions content/auto-export.ts
Original file line number Diff line number Diff line change
Expand Up @@ -165,19 +165,17 @@ if (Preference.autoExportIdleWait < 1) Preference.autoExportIdleWait = 1
const queue = new class TaskQueue {
private scheduler = new Scheduler('autoExportDelay', 1000)
private autoexports: any
private started = false
private idleService = Components.classes['@mozilla.org/widget/idleservice;1'].getService(Components.interfaces.nsIIdleService)

constructor() {
this.pause('startup')
}

public start() {
if (this.started) return
this.started = true
if (Preference.autoExport === 'immediate') this.resume('startup')

this.idleService.addIdleObserver(this, Preference.autoExportIdleWait * 1000)
// really dumb but the idle service deals with msecs wverywhere -- except add, which is in seconds
this.idleService.addIdleObserver(this, Preference.autoExportIdleWait)

Zotero.Notifier.registerObserver(this, ['sync'], 'BetterBibTeX', 1)
}
Expand All @@ -186,30 +184,30 @@ const queue = new class TaskQueue {
this.autoexports = autoexports
}

public pause(reason: 'startup' | 'end-of-idle' | 'start-of-sync' | 'trigger-change') {
log.debug('idle?: queue paused:', reason)
public pause(reason: 'startup' | 'end-of-idle' | 'start-of-sync' | 'preference-change') {
log.debug('on-idle: queue.pause:', reason)
this.scheduler.paused = true
}

public resume(reason: 'startup' | 'end-of-sync' | 'start-of-idle' | 'trigger-change') {
log.debug('idle?: queue resume request:', reason)
public resume(reason: 'startup' | 'end-of-sync' | 'start-of-idle' | 'preference-change') {
log.debug('on-idle: queue.resume:', reason)
if (Zotero.Sync.Runner.syncInProgress) {
log.debug('idle?: queue not resumed: sync in progress, end-of-sync will trigger resume')
log.debug('on-idle: queue not resumed: sync in progress, end-of-sync will trigger resume')
this.scheduler.paused = true
return
}

const is_idle = this.idleService.idleTime >= Preference.autoExportIdleWait * 1000
switch (Preference.autoExport) {
case 'off':
log.debug('idle?: queue not resumed: auto-export is off')
log.debug('on-idle: queue not resumed: auto-export is off')
this.scheduler.paused = true
return

case 'idle':
// don't re-schedule idle for end-of-sync / should never happen?
if (!is_idle) {
log.debug('idle?: queue not resumed:', reason, "but we're not actually idle")
log.debug('on-idle: queue not resumed:', reason, "but we're not actually idle")
this.scheduler.paused = true
return
}
Expand All @@ -219,7 +217,7 @@ const queue = new class TaskQueue {
break
}

log.debug('idle?: queue resumed:', reason)
log.debug('on-idle: queue resumed:', reason)
this.scheduler.paused = false
}

Expand Down Expand Up @@ -308,6 +306,7 @@ const queue = new class TaskQueue {
}
}

log.debug('on-idle: starting auto-export')
await Promise.all(jobs.map(job => Translators.exportItems(ae.translatorID, displayOptions, job.scope, job.path)))

await repo.push(l10n.localize('Preferences.auto-export.git.message', { type: Translators.byId[ae.translatorID].label.replace('Better ', '') }))
Expand All @@ -330,17 +329,23 @@ const queue = new class TaskQueue {
}

// idle observer
protected observe(subject, topic, data) {
log.debug('idle?: observer:', { subject, topic, data })
if (!this.started || Preference.autoExport === 'off') return
protected observe(_subject, topic, data) {
log.debug('on-idle: idle.observe:', { topic, data })
if (Preference.autoExport === 'off') {
log.debug('on-idle: idle.observe: auto-export is off')
this.pause('preference-change')
return
}

switch (topic) {
case 'back':
case 'active':
log.debug('on-idle: idle.observe: => pause', topic)
this.pause('end-of-idle')
break

case 'idle':
log.debug('on-idle: idle.observe: => resume', topic)
this.resume('start-of-idle')
break

Expand All @@ -354,16 +359,20 @@ const queue = new class TaskQueue {
// It is theoretically possible that auto-export is paused because Zotero is idle and then restarted when the sync finishes, but
// I can't see how a system can be considered idle when Zotero is syncing.
protected notify(action, type) {
if (!this.started || Preference.autoExport === 'off') return
if (Preference.autoExport === 'off') {
log.debug('on-idle: sync.notify: auto-export is off')
this.pause('preference-change')
return
}

switch(`${type}.${action}`) {
case 'sync.start':
log.debug('idle?: sync started => pausing queue')
log.debug('on-idle: sync.notify: started => pausing queue')
this.pause('start-of-sync')
break

case 'sync.finish':
log.debug('idle?: sync finished => resuming queue')
log.debug('on-idle: sync.notify: finished => resuming queue')
this.resume('end-of-sync')
break

Expand Down Expand Up @@ -512,9 +521,9 @@ Events.on('preference-changed', pref => {

switch (Preference.autoExport) {
case 'immediate':
queue.resume('trigger-change')
queue.resume('preference-change')
break
default: // off / idle
queue.pause('trigger-change')
queue.pause('preference-change')
}
})
7 changes: 5 additions & 2 deletions pandoc/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -11,18 +11,21 @@ count:
bundle:
ifeq ($(shell grep '^local pl =' *.lua), )
echo "print('zotero-live-citations $(shell git rev-parse --short HEAD)')" > $(DEPLOYED)
~/.luarocks/bin/amalg.lua -o $(BUNDLED) -s $(MAIN) lunajson lunajson.decoder lunajson.encoder lunajson.sax locator utils zotero
/usr/local/bin/amalg.lua -o $(BUNDLED) -s $(MAIN) lunajson lunajson.decoder lunajson.encoder lunajson.sax locator utils zotero
cat $(BUNDLED) >> $(DEPLOYED)
rm $(BUNDLED)
else
@echo $(MAIN) contains debugging code
@exit 1
endif

test-deployed:
@rm -f *.docx *.odt *.json
pandoc -s --lua-filter=$(DEPLOYED) -o paper$(TIMESTAMP).odt main.md
test:
@rm -f *.docx *.odt *.json
#@pandoc -s --lua-filter=$(MAIN) -o paper$(TIMESTAMP).docx main.md
@pandoc -s --lua-filter=$(MAIN) -o paper$(TIMESTAMP).odt main.md
pandoc -s --lua-filter=$(MAIN) -o paper$(TIMESTAMP).odt main.md
pandoc -s --metadata=zotero_scannable_cite:true --lua-filter=$(MAIN) -o paper$(TIMESTAMP)-scannable-cite.odt main.md

jurism:
Expand Down
33 changes: 23 additions & 10 deletions pandoc/zotero.lua
Original file line number Diff line number Diff line change
Expand Up @@ -10,23 +10,36 @@ local state = {

module.citekeys = {}

function module.authors(csl)
if csl.author == nil then
return nil
end

function module.authors(csl_or_item)
local authors = {}
local author
for _, author in ipairs(csl.author) do
if author.literal ~= nil then
table.insert(authors, author.literal)
elseif author.family ~= nil then
table.insert(authors, author.family)

if csl_or_item.author ~= nil then
for _, author in ipairs(csl_or_item.author) do
if author.literal ~= nil then
table.insert(authors, author.literal)
elseif author.family ~= nil then
table.insert(authors, author.family)
end
end

elseif csl_or_item.creators ~= nil then
for _, author in ipairs(csl_or_item.creators) do
if author.name ~= nil then
table.insert(authors, author.name)
elseif author.lastName ~= nil then
table.insert(authors, author.lastName)
end
end

elseif csl_or_item.reporter ~= nil then
table.insert(authors, csl_or_item.reporter)
end

if utils.tablelength(authors) == 0 then
return nil
end

local last = table.remove(authors)
if utils.tablelength(authors) == 0 then
return last
Expand Down
112 changes: 112 additions & 0 deletions site/content/citing/_index.md.orig
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
---
title: Citation Keys
weight: 3
aliases:
- /Citation-Keys
- /citation-keys
tags:
- citation keys
---

## Generating citekeys for your items

The BibTeX citations keys generated by the standard Zotero exporters are always generated at time of export, using an algorithm that usually generates unique keys. For serious LaTeX users, "usually" presents the following problems:

* If a non-unique key is generated, which one gets postfixed with a distinguishing character is essentially
non-deterministic.
* The keys are *always* auto-generated, so if you correct a typo in the author name or title, the key will change
* You can't see the citation keys until you export them

For a LaTeX author, the citation keys have their own meaning, fully separate from the other entry data, even if
people usually pick a naming scheme related to them. As the citation key is *the* piece of data that connects your
bibliography, this is a piece of data you want to have control over. BBT offers you this control:

* Stable citation keys, without key clashes. BBT generates citation keys that take into account other existing keys in your library in a deterministic way, regardless of what part of your library you export, or the order in which you do it.
* BBT is conservative about citation key changes, and allows you to fix keys to any value of your choosing.
* Generate citation keys from JabRef(-ish) patterns.

You can also

* Drag and drop LaTeX citations using these keys to your favorite LaTeX editor
* Show your citation keys in the item list view.

## Set your own, fixed citation keys

By default, BBT generates the citation key from the item information, and this key may change when you edit the item. Such keys are called `dynamic` keys, which are marked with a pushpin the item list view and in the item details to distinguish them from dynamic keys.

You can fix the citation key (called `pinning` in BBT) for an item by adding the text `Citation Key: <your citekey>` anywhere in the
`extra` field of the item on a line of its own. You can generate a pinned citation key by selecting one or more items, right-clicking, and selecting `Generate BibTeX key`, which will add the current citation key to the `extra` field, thereby pinning it.

## Drag and drop/hotkey citations

You can drag and drop citations into your LaTeX/Markdown/Orgmode editor, and it will add a proper `\cite{citekey}`/`[@citekey]`/`[[zotero://select...][@citekey]`. The `cite` command is
configurable for LaTeX by setting the config option in the [preferences]({{< ref "installation/preferences" >}}). Do not include the leading backslash.

This feature requires a one-time setup: choose the Quick Copy format under the `Citation keys` preferences for BBT, and go to Zotero preferences, tab Export, under Default Output Format, select "Better BibTeX Quick Copy: [format you just selected]".

## Find duplicate keys through integration with [Report Customizer](https://github.com/retorquere/zotero-report-customizer)

The plugin will generate BibTeX comments to show whether a key conflicts and with which entry. BBT integrates with
[Zotero: Report Customizer](https://github.com/retorquere/zotero-report-customizer) to display the BibTeX key plus any
conflicts between them in the zotero report.

## Configurable citekey generator

BBT also implements a citekey generator for those entries that don't have a citekey set explicitly; the formatter pattern language used to follow
the [JabRef key formatting syntax](https://help.jabref.org/en/BibtexKeyPatterns), but now uses a javascript-ish format. You can set your generator pattern in the Better BibTeX
preferences (you can get there via the Zotero preferences, or by clicking the Better BibTeX "Preferences" button in the addons pane.

The default key pattern is `auth.lower + shorttitle(3,3) + year`; if you have papers that use keys which were generated by the key generator of the standard Bib(La)TeX exporters of Zotero you may want to use `zotero.clean` instead in order to ease migration from existing exports for people who previously used the standard Zotero Bib(La)TeX exports. You will be offered this choice on first run of BBT.

A common pattern is `auth.lower + year`, which means

1. last name of first author without spaces, in lowercase
2. year of publication if any,
3. a letter postfix (a, b, c, etc) in case of a clash (this part is always added, you can't disable it, although you can change it to Zotero-style numeric)

Changing a pattern will only affect items created/changed after you changed the pattern; existing keys are not automatically regenerated when you change the pattern. If you want your keys to update after a pattern change you will have to select your items, right-click, and select `Refresh`. This will not affect keys you have pinned.

If you want to get fancy, you can set multiple patterns separated by a vertical bar, of which the first will be applied
that yields a non-empty string. If all return a empty string, a random key will be generated.

An example application for this behavior is to use the `tex.shortauthor` from the [extra field]({{< ref "../exporting/extra-fields" >}}) when defined to generate short citation keys for entries with long group author names, but to default to `auth.lower` otherwise:

```text
extra('tex.shortauthor').transliterate.clean.lower.len + year | auth.lower + year
```

You can add a verbatim text by just including it in single or double quotes:

```text
extra('tex.shortauthor').transliterate.clean.lower.len + year | 'default' + auth.lower + year
```

### Generating citekeys

To generate your citekeys, you use a formula composed of functions and filters. Broadly, functions grab text from your item, and filters transform that text. **Note that the formula syntax has changed from a bracketed format to a javascript-ish format**. The old syntax was getting harder to maintain and its inflexibility prevented new extensions to the functions being implemented cleanly. **The old syntax still works** and will be translated to the new format automatically and displayed below the old format if you use it. At some point not too far away, BBT will automatically upgrade old patterns and use those directly. But for now you can choose which format you use.

Below you will find a full list of functions and filters you can use, in the new format only, sorry. You can till use these in the old syntax, but they support only positional parameters, where I would recommend generally to use the new syntax with named parameters.

#### Functions

{{< citekey-formatters/functions >}}

**Note**: All `auth...` functions will fall back to editors if no authors are present on the item.

**Note**: the functions above all have the `clean` filter (see below) applied to them by default. You can turn that off by passying `clean=false`.

### Direct access to unprocessed fields

The above functions all retrieve information stored in the item's fields and process it in some way. If you don't want this, you can instead call field contents without any processing. To access Zotero fields, refer to them as given in the table below:

{{< citekey-formatters/fields >}}

(fields marked <sup>JM</sup> are only available in Juris-M).

#### Filters

{{< citekey-formatters/filters >}}

*Usage note*: the functions `condense`, `skipwords`, `capitalize` and `select` rely on whitespaces for word handling. Most functions strip
whitespace and thereby make these filter functions sort of useless. You will in general want to use the fields from the
table above, which give you the values from Zotero without any changes. The fields with `**` are only available in Juris-M.
35 changes: 24 additions & 11 deletions site/content/exporting/zotero.lua
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
print('zotero-live-citations 993af1f79')
print('zotero-live-citations f34bfc770')
do
local _ENV = _ENV
package.preload[ "locator" ] = function( ... ) local arg = _G.arg;
Expand Down Expand Up @@ -1680,23 +1680,36 @@ local state = {
module.citekeys = {}
function module.authors(csl)
if csl.author == nil then
return nil
end
function module.authors(csl_or_item)
local authors = {}
local author
for _, author in ipairs(csl.author) do
if author.literal ~= nil then
table.insert(authors, author.literal)
elseif author.family ~= nil then
table.insert(authors, author.family)
if csl_or_item.author ~= nil then
for _, author in ipairs(csl_or_item.author) do
if author.literal ~= nil then
table.insert(authors, author.literal)
elseif author.family ~= nil then
table.insert(authors, author.family)
end
end
elseif csl_or_item.creators ~= nil then
for _, author in ipairs(csl_or_item.creators) do
if author.name ~= nil then
table.insert(authors, author.name)
elseif author.lastName ~= nil then
table.insert(authors, author.lastName)
end
end
elseif csl_or_item.reporter ~= nil then
table.insert(authors, csl_or_item.reporter)
end
if utils.tablelength(authors) == 0 then
return nil
end
local last = table.remove(authors)
if utils.tablelength(authors) == 0 then
return last
Expand Down

0 comments on commit d822d36

Please sign in to comment.