Zotero Import Script #421
Replies: 16 comments 26 replies
-
Hi Christian, thank you for your step-by-step guide. I just figured out that the script does not work if a paper does not have an attached PDF in Zotero. Below is the error message:
|
Beta Was this translation helpful? Give feedback.
-
Thanks Christian, was excited to try this as I'm mobile only, but couldn't get it to work. Can get the list of documents in Obsidian, but the file is not created. Help? |
Beta Was this translation helpful? Give feedback.
-
I am wondering if it's restricted by Zotero that only the 25 recently added articles are accessible after running this script. I have some 'old' papers saved before your guide but they're not in the list. |
Beta Was this translation helpful? Give feedback.
-
Huge new updateThe script now grabs annotations from every PDF you have with a given item. This means you are no longer limited to annotating just 1 PDF per research item. const API_KEY_OPTION = "Zotero API Key";
const USER_ID_OPTION = "Zotero User ID";
const ANNOTATION_FORMAT_OPTION = "Annotation Format";
const AUTHOR_FORMAT_OPTION = "Author Format";
const API_URL = "https://api.zotero.org";
module.exports = {
entry: start,
settings: {
author: "Christian B. B. Houmann",
name: "Zotero Importer",
options: {
[API_KEY_OPTION]: {
type: "text",
defaultValue: "",
placeholder: "Zotero API Key",
secret: true,
},
[USER_ID_OPTION]: {
type: "text",
defaultValue: "",
placeholder: "Zotero User ID",
},
[ANNOTATION_FORMAT_OPTION]: {
type: "format",
defaultValue: `> {{VALUE:quote}}\n\n{{VALUE:note}}`,
placeholder: "Annotation Format",
},
[AUTHOR_FORMAT_OPTION]: {
type: "format",
defaultValue: "[[{{VALUE:firstName}} {{VALUE:lastName}}]]",
placeholder: "Author Format",
},
},
},
};
let QuickAdd;
let Settings;
async function start(params, settings) {
QuickAdd = params;
Settings = settings;
if (
!safeInvariant(
Settings[API_KEY_OPTION],
"Please set your Zotero API Key and User ID in the settings for this script."
) ||
!safeInvariant(
Settings[USER_ID_OPTION],
"Please set your Zotero User ID in the settings for this script."
)
) {
return;
}
const topLevelItems = (await fetchTopLevelItems()).filter(
(item) => item.data.title
);
const targetItem = await QuickAdd.quickAddApi.suggester(
topLevelItems.map((item) => item.data.title),
topLevelItems
);
const pdfChildren = await fetchItemPDFChildren(targetItem);
const annotations = pdfChildren.flatMap(getAnnotations);
const markdownAnnotations = addMarkdownAttributesToAnnotations(annotations);
const variables = {
fileName: replaceIllegalFileNameCharactersInString(
targetItem.data.title
),
...targetItem,
...targetItem.data,
abstract: targetItem.data.abstractNote ?? "",
shortAuthor: targetItem.meta.creatorSummary,
doi: targetItem.data.DOI ?? "",
year: targetItem.data.date?.split("-")[0] ?? "",
issn: targetItem.data.ISSN ?? "",
};
const formattedAnnotations = [];
for (const annotation of markdownAnnotations) {
const formattedAnnotation = await QuickAdd.quickAddApi.format(
Settings[ANNOTATION_FORMAT_OPTION],
{
...annotation,
...variables,
note: annotation.note.length === 0 ? "-" : annotation.note,
quote: annotation.quote.length === 0 ? "-" : annotation.quote,
}
);
formattedAnnotations.push(formattedAnnotation);
}
const authors = [...(targetItem.data.creators ?? [])];
const formattedAuthors = [];
for (const author of authors) {
const formattedAuthor = await QuickAdd.quickAddApi.format(
Settings[AUTHOR_FORMAT_OPTION],
{
...author,
firstName: author.firstName ?? "",
lastName: author.lastName ?? "",
}
);
formattedAuthors.push(formattedAuthor);
}
Object.assign(QuickAdd.variables, variables, {
formattedAnnotations: formattedAnnotations.join("\n\n"),
authors: formattedAuthors.join(", "),
});
}
function safeInvariant(condition, message) {
if (!condition) {
new Notice(message);
return false;
}
return true;
}
function addMarkdownAttributesToAnnotations(annotations) {
return annotations.map((annotation) => {
const note = annotation.data.annotationComment ?? "";
annotation["note"] = QuickAdd.obsidian.htmlToMarkdown(note);
const text = annotation.data.annotationText ?? "";
annotation["quote"] = QuickAdd.obsidian.htmlToMarkdown(text);
return annotation;
});
}
function getAnnotations(pdfChildren) {
const annotations = pdfChildren.filter(
(child) => child.data.itemType === "annotation"
);
return annotations;
}
async function fetchItemPDFChildren(item) {
const children = await fetchItemChildren(item);
if (!children) return [];
const pdfChildren = children.filter(child => child?.data?.contentType === "application/pdf");
const resArr = (await Promise.all(pdfChildren.map(child => {
const url = `${child.links.self.href}/children`;
return makeRequest(url);
}))).map(res => res.json);
return resArr;
}
async function fetchItemChildren(item) {
const url = `${API_URL}/users/${Settings[USER_ID_OPTION]}/items/${item.key}/children?format=json&limit=100`;
const response = await makeRequest(url);
return response.json;
}
async function fetchTopLevelItems() {
const route = new URL(`${API_URL}/users/${Settings[USER_ID_OPTION]}/items/top`);
route.searchParams.set("limit", 10000);
const response = await makeRequest(route.toString());
return response.json;
}
async function makeRequest(url, options = {}) {
const headers = {
"Zotero-API-Version": 3,
"Zotero-API-Key": Settings[API_KEY_OPTION],
...options.headers,
};
return requestUrl({
...options,
url,
headers,
});
}
function replaceIllegalFileNameCharactersInString(string) {
return string.replace(/[\\,#%&\{\}\/*<>$\'\":@]*/g, "");
} |
Beta Was this translation helpful? Give feedback.
-
Hi there, really love that this workflow exists. Thank you so much for taking the time to flesh this out. However, I'm running into an issue, everything runs great except the annotations/highlights/notes on my PDFs are not being imported into Obsidian. The PDFs are stored using Zotero sync so I think that should work right? |
Beta Was this translation helpful? Give feedback.
-
Hi @chhoumann, there is a small error in the generation of the filename: |
Beta Was this translation helpful? Give feedback.
-
Hi, I'm noticing that not all of my papers are appearing from Zotero, even though I have the limit set to 10000 as you fixed in the above discussion. I have multiple directories and subdirectories in Zotero, and I checked with my Web Library and it is up to date and contains everything. I'm wondering if anyone else has had issues with accessing papers and possible solutions. I can't really estimate how many papers are missing from the import search function, other than the fact that not every single one is there. BTW, I love the plugin, excited to get it working for my workflow :) |
Beta Was this translation helpful? Give feedback.
-
Hi All, I was importing a pdf with several annotations but only getting 25 in the md. This is the fix I did: async function fetchItemPDFChildren(item) {
const children = await fetchItemChildren(item);
if (!children) return [];
const pdfChildren = children.filter(
(child) => child?.data?.contentType === 'application/pdf'
);
const resArr = (
await Promise.all(
pdfChildren.map((child) => {
// const url = `${child.links.self.href}/children`;
// the api defaults limit all multi-element responses to 25
const url = `${child.links.self.href}/children?limit=100`;
return makeRequest(url);
})
)
).map((res) => res.json);
return resArr;
} |
Beta Was this translation helpful? Give feedback.
-
I have found that the script grabs all people (entities, whatever) mentioned as "creators" of an entry as authors. That can be rather annoying when dealing with editors. I would the script to only grab "real" authors - those listed as "Author" in Zotero, not "Editor" or "Translator" etc. Is this possible? |
Beta Was this translation helpful? Give feedback.
-
Awesome script! Note to others: The VALUE.firstName will pull in both author name and middle initial. |
Beta Was this translation helpful? Give feedback.
-
Beta Was this translation helpful? Give feedback.
-
Hi, just trying to set this up but keep getting a Request Failed Status 500 error.. any pointers gratefully received! Thanks.. |
Beta Was this translation helpful? Give feedback.
-
Another update! I've added handling for when you have more than 100 annotations in a single PDF. It happens 🤷♂️ const API_KEY_OPTION = "Zotero API Key";
const USER_ID_OPTION = "Zotero User ID";
const ANNOTATION_FORMAT_OPTION = "Annotation Format";
const AUTHOR_FORMAT_OPTION = "Author Format";
const API_URL = "https://api.zotero.org";
module.exports = {
entry: start,
settings: {
author: "Christian B. B. Houmann",
name: "Zotero Importer",
options: {
[API_KEY_OPTION]: {
type: "text",
defaultValue: "",
placeholder: "Zotero API Key",
secret: true,
description:
"You can find your API key at https://www.zotero.org/settings/keys",
},
[USER_ID_OPTION]: {
type: "text",
defaultValue: "",
placeholder: "Zotero User ID",
},
[ANNOTATION_FORMAT_OPTION]: {
type: "format",
defaultValue: "> {{VALUE:quote}}\n\n{{VALUE:note}}",
placeholder: "Annotation Format",
},
[AUTHOR_FORMAT_OPTION]: {
type: "format",
defaultValue: "[[{{VALUE:firstName}} {{VALUE:lastName}}]]",
placeholder: "Author Format",
},
},
},
};
let QuickAdd;
let Settings;
async function start(params, settings) {
QuickAdd = params;
Settings = settings;
if (
!safeInvariant(
Settings[API_KEY_OPTION],
"Please set your Zotero API Key and User ID in the settings for this script."
) ||
!safeInvariant(
Settings[USER_ID_OPTION],
"Please set your Zotero User ID in the settings for this script."
)
) {
return;
}
const topLevelItems = (await fetchTopLevelItems()).filter(
(item) => item.data.title
);
const targetItem = await QuickAdd.quickAddApi.suggester(
topLevelItems.map((item) => item.data.title),
topLevelItems
);
const annotations = await fetchItemPDFChildren(targetItem);
const markdownAnnotations = addMarkdownAttributesToAnnotations(annotations);
const variables = {
fileName: replaceIllegalFileNameCharactersInString(
targetItem.data.title
),
...targetItem,
...targetItem.data,
abstract: targetItem.data.abstractNote ?? "",
shortAuthor: targetItem.meta.creatorSummary,
doi: targetItem.data.DOI ?? "",
year: targetItem.data.date?.split("-")[0] ?? "",
issn: targetItem.data.ISSN ?? "",
};
const formattedAnnotations = [];
for (const annotation of markdownAnnotations) {
const formattedAnnotation = await QuickAdd.quickAddApi.format(
Settings[ANNOTATION_FORMAT_OPTION],
{
...annotation,
...variables,
note: annotation.note.length === 0 ? "-" : annotation.note,
quote: annotation.quote.length === 0 ? "-" : annotation.quote,
color: annotation.data.annotationColor ?? "",
}
);
formattedAnnotations.push(formattedAnnotation);
}
formattedAnnotations.reverse();
const authors = [...(targetItem.data.creators ?? [])];
const formattedAuthors = [];
for (const author of authors) {
const formattedAuthor = await QuickAdd.quickAddApi.format(
Settings[AUTHOR_FORMAT_OPTION],
{
...author,
firstName: author.firstName ?? "",
lastName: author.lastName ?? "",
}
);
formattedAuthors.push(formattedAuthor);
}
Object.assign(QuickAdd.variables, variables, {
formattedAnnotations: formattedAnnotations.join("\n\n"),
authors: formattedAuthors.join(", "),
});
}
function safeInvariant(condition, message) {
if (!condition) {
new Notice(message);
return false;
}
return true;
}
function addMarkdownAttributesToAnnotations(annotations) {
return annotations.map((annotation) => {
const note = annotation.data.annotationComment ?? "";
const quote = annotation.data.annotationText ?? "";
annotation.note = QuickAdd.obsidian.htmlToMarkdown(note);
annotation.quote = QuickAdd.obsidian.htmlToMarkdown(quote);
return annotation;
});
}
async function fetchItemPDFChildren(item) {
const children = await fetchItemChildren(item);
if (!children) return [];
const pdfChildren = children.filter(
(child) => child?.data?.contentType === "application/pdf"
);
const resArr = (
await Promise.all(
pdfChildren.map(async (child) => {
const results = [];
let nextPage = `${child.links.self.href}/children?format=json&limit=100&start=0&itemType=annotation`;
while (true) {
const response = await makeRequest(nextPage);
const data = await response.json;
if (data.length === 0) break; // No more data to paginate through
results.push(...data);
nextPage = `${child.links.self.href}/children?format=json&limit=100&itemType=annotation&start=${results.length}`;
}
return results;
})
)
).flat();
return resArr;
}
async function fetchItemChildren(item) {
const url = `${API_URL}/users/${Settings[USER_ID_OPTION]}/items/${item.key}/children?format=json&limit=100`;
const response = await makeRequest(url);
return response.json;
}
async function fetchTopLevelItems() {
const route = new URL(
`${API_URL}/users/${Settings[USER_ID_OPTION]}/items/top`
);
route.searchParams.set("limit", 10000);
const response = await makeRequest(route.toString());
return response.json;
}
async function makeRequest(url, options = {}) {
const headers = {
"Zotero-API-Version": 3,
"Zotero-API-Key": Settings[API_KEY_OPTION],
...options.headers,
};
return requestUrl({
...options,
url,
headers,
});
}
function replaceIllegalFileNameCharactersInString(string) {
return string.replace(/[\\,#%&\{\}\/*<>$\'\":@]*/g, "");
} |
Beta Was this translation helpful? Give feedback.
-
Hi. Is there a way that the script inserts annotation to my note with citations (the same way it is done when i drag and drop from zotero) |
Beta Was this translation helpful? Give feedback.
-
Thank you so much for this script. However, the script has stopped working for me for some reason, I don't know if its the new update or something I did haha. Also, I was wondering whether its possible to sort the annotations from zotero according to their positions instead of the creation time? I have to read my papers twice or thrice to understand and highlight the important parts, which messes with the order of the imported annotations into obsidian. I tried tinkering around but havent been able to find a solution. Cheers! |
Beta Was this translation helpful? Give feedback.
-
Zotero Importer UpdateNew Features
The following fields are available, each retrievable with the corresponding key using the format General Item Metadata
Annotation Metadata
Notes and Quotes
Updated scriptconst API_KEY_OPTION = "Zotero API Key";
const USER_ID_OPTION = "Zotero User ID";
const ANNOTATION_FORMAT_OPTION = "Annotation Format";
const AUTHOR_FORMAT_OPTION = "Author Format";
const SORT_ANNOTATIONS_OPTION = "Sort Annotations";
const SORT_DIRECTION_OPTION = "Sort Direction";
const API_URL = "https://api.zotero.org";
const SORT_OPTIONS = Object.freeze({
NONE: "none",
PAGE: "page",
MODIFIED: "modified",
ADDED: "added",
SORT_INDEX: "sort-index",
});
module.exports = {
entry: wrap,
settings: {
author: "Christian B. B. Houmann",
name: "Zotero Importer",
options: {
[API_KEY_OPTION]: {
type: "text",
defaultValue: "",
placeholder: "Zotero API Key",
secret: true,
description:
"You can find your API key at https://www.zotero.org/settings/keys",
},
[USER_ID_OPTION]: {
type: "text",
defaultValue: "",
placeholder: "Zotero User ID",
},
[ANNOTATION_FORMAT_OPTION]: {
type: "format",
defaultValue: "> {{VALUE:quote}}\n\n{{VALUE:note}}",
placeholder: "Annotation Format",
},
[AUTHOR_FORMAT_OPTION]: {
type: "format",
defaultValue: "[[{{VALUE:firstName}} {{VALUE:lastName}}]]",
placeholder: "Author Format",
},
[SORT_ANNOTATIONS_OPTION]: {
type: "dropdown",
defaultValue: SORT_OPTIONS.SORT_INDEX,
placeholder: "Sort Annotations",
options: Object.values(SORT_OPTIONS),
},
[SORT_DIRECTION_OPTION]: {
type: "dropdown",
defaultValue: "ASC",
placeholder: "Sort Direction",
options: ["ASC", "DESC"],
},
},
},
};
let QuickAdd;
let Settings;
class ImporterNotice extends Notice {
prefix = "[Zotero Importer] ";
constructor() {
super("", 99999999);
this.noticeEl.style.flexDirection = "column";
this.noticeEl.style.alignItems = "flex-start";
this.titleEl = this.noticeEl.createDiv();
this.titleEl.style.display = "flex";
this.titleEl.style.justifyContent = "space-between";
this.titleEl.style.width = "100%";
this.titleTextEl = this.titleEl.createEl("strong", {
text: "Zotero Importer",
});
this.timerEl = this.titleEl.createSpan({ text: "0.00s" });
this.timerEl.style.marginLeft = "10px"; // Add a small margin to the timer
this.messageEl = this.noticeEl.createDiv();
this.messageEl.textContent = "Starting...";
this.lastMessageTime = Date.now();
this.startTimer();
}
startTimer() {
this.timerInterval = setInterval(() => {
const now = Date.now();
const timeSinceLastMessage = (
(now - this.lastMessageTime) /
1000
).toFixed(2);
this.timerEl.textContent = `${timeSinceLastMessage}s`;
}, 100);
}
setMessage(message) {
this.messageEl.textContent = message;
this.lastMessageTime = Date.now();
}
hide() {
clearInterval(this.timerInterval);
super.hide();
}
}
let notice;
async function wrap(...args) {
try {
await start(...args);
} catch (error) {
if (notice) {
notice.hide();
}
throw error;
}
}
async function start(params, settings) {
QuickAdd = params;
Settings = settings;
notice = new ImporterNotice();
if (
!safeInvariant(
Settings[API_KEY_OPTION],
"Please set your Zotero API Key and User ID in the settings for this script.",
) ||
!safeInvariant(
Settings[USER_ID_OPTION],
"Please set your Zotero User ID in the settings for this script.",
)
) {
return;
}
notice.setMessage("Fetching papers...");
const topLevelItems = (await fetchTopLevelItems()).filter(
(item) => item.data.title,
);
notice.setMessage(`Found ${topLevelItems.length} items.`);
const targetItem = await QuickAdd.quickAddApi.suggester(
topLevelItems.map((item) => item.data.title),
topLevelItems,
);
notice.setMessage(`Importing ${targetItem.data.title}.`);
notice.setMessage("Fetching annotations...");
const annotations = await fetchItemPDFChildren(targetItem);
notice.setMessage(`Found ${annotations.length} annotations.`);
notice.setMessage("Sorting annotations...");
const sortedAnnotations = (() => {
switch (Settings[SORT_ANNOTATIONS_OPTION]) {
case SORT_OPTIONS.PAGE:
return sortAnnotationsByPage(annotations);
case SORT_OPTIONS.DATE:
return sortAnnotationsByDate(annotations);
case SORT_OPTIONS.COLOR:
return sortAnnotationsByColor(annotations);
case SORT_OPTIONS.SORT_INDEX:
return sortAnnotationsBySortIndex(annotations);
case SORT_OPTIONS.NONE:
return annotations;
default:
return annotations;
}
})();
notice.setMessage("Formatting annotations...");
const markdownAnnotations =
addMarkdownAttributesToAnnotations(sortedAnnotations);
const variables = {
fileName: replaceIllegalFileNameCharactersInString(targetItem.data.title),
...targetItem,
...targetItem.data,
...(targetItem.meta ?? {}),
abstract: targetItem.data.abstractNote ?? "",
shortAuthor: targetItem.meta.creatorSummary,
doi: targetItem.data.DOI ?? "",
year: targetItem.data.date?.split("-")[0] ?? "",
issn: targetItem.data.ISSN ?? "",
};
const formattedAnnotations = [];
for (const annotation of markdownAnnotations) {
const formattedAnnotation = await QuickAdd.quickAddApi.format(
Settings[ANNOTATION_FORMAT_OPTION],
{
...annotation,
...annotation.data,
...variables,
dateAdded: annotation.data.dateAdded ?? "",
dateModified: annotation.data.dateModified ?? "",
note: annotation.note.length === 0 ? "-" : annotation.note,
quote: annotation.quote.length === 0 ? "-" : annotation.quote,
color: annotation.data.annotationColor ?? "",
annotationType: annotation.data.annotationType ?? "",
},
);
formattedAnnotations.push(formattedAnnotation);
}
notice.setMessage("Formatting authors...");
const authors = [...(targetItem.data.creators ?? [])];
const formattedAuthors = [];
for (const author of authors) {
const formattedAuthor = await QuickAdd.quickAddApi.format(
Settings[AUTHOR_FORMAT_OPTION],
{
...author,
firstName: author.firstName ?? "",
lastName: author.lastName ?? "",
},
);
formattedAuthors.push(formattedAuthor);
}
Object.assign(QuickAdd.variables, variables, {
formattedAnnotations: formattedAnnotations.join("\n\n"),
authors: formattedAuthors.join(", "),
});
notice.setMessage(`🎉 Finished importing ${targetItem.data.title}.`);
setTimeout(() => {
notice.hide();
}, 10000);
}
function safeInvariant(condition, message) {
if (!condition) {
new Notice(message);
return false;
}
return true;
}
function addMarkdownAttributesToAnnotations(annotations) {
return annotations.map((annotation) => {
const note = annotation.data.annotationComment ?? "";
const quote = annotation.data.annotationText ?? "";
annotation.note = QuickAdd.obsidian.htmlToMarkdown(note);
annotation.quote = QuickAdd.obsidian.htmlToMarkdown(quote);
return annotation;
});
}
function sortAnnotationsByPage(annotations) {
const direction = Settings[SORT_DIRECTION_OPTION] === "ASC" ? 1 : -1;
return annotations.sort((a, b) => {
if (a.pageIndex !== b.pageIndex) {
return (a.pageIndex - b.pageIndex) * direction;
}
// If page indices are the same, sort by the top-left corner of the rects
const aRect = a.rects?.[0] || [];
const bRect = b.rects?.[0] || [];
const aTop = aRect[1] || Number.POSITIVE_INFINITY;
const bTop = bRect[1] || Number.POSITIVE_INFINITY;
if (aTop !== bTop) {
return (aTop - bTop) * direction;
}
const aLeft = aRect[0] || Number.POSITIVE_INFINITY;
const bLeft = bRect[0] || Number.POSITIVE_INFINITY;
return (aLeft - bLeft) * direction;
});
}
/**
* Sorts annotations by date.
* @param {Array} annotations - The annotations to sort.
* @param {"dateModified" | "dateAdded"} sortBy - The property to sort by. Defaults to 'dateModified'.
* @returns {Array} The sorted annotations.
*/
function sortAnnotationsByDate(annotations, sortBy = "dateModified") {
const direction = Settings[SORT_DIRECTION_OPTION] === "ASC" ? 1 : -1;
return annotations.sort((a, b) => {
const aDate = new Date(a.data[sortBy]);
const bDate = new Date(b.data[sortBy]);
return (aDate - bDate) * direction;
});
}
function sortAnnotationsBySortIndex(annotations) {
const direction = Settings[SORT_DIRECTION_OPTION] === "ASC" ? 1 : -1;
return annotations.sort((a, b) => {
const aSortIndex = a.data.annotationSortIndex.split("|").map(Number);
const bSortIndex = b.data.annotationSortIndex.split("|").map(Number);
for (let i = 0; i < aSortIndex.length; i++) {
if (aSortIndex[i] !== bSortIndex[i]) {
return (aSortIndex[i] - bSortIndex[i]) * direction;
}
}
return 0;
});
}
async function fetchItemPDFChildren(item) {
const children = await fetchItemChildren(item);
if (!children) return [];
const pdfChildren = children.filter(
(child) => child?.data?.contentType === "application/pdf",
);
const resArr = (
await Promise.all(
pdfChildren.map(async (child) => {
const results = [];
let nextPage = child.links?.self?.href
? `${child.links.self.href}/children?format=json&limit=100&start=0&itemType=annotation`
: null;
if (!nextPage) {
console.error('Invalid child link structure:', child);
return results;
}
while (true) {
const response = await makeRequest(nextPage);
const data = await response.json;
if (data.length === 0) break; // No more data to paginate through
results.push(...data);
nextPage = `${child.links.self.href}/children?format=json&limit=100&itemType=annotation&start=${results.length}`;
}
return results;
}),
)
).flat();
return resArr;
}
async function fetchItemChildren(item) {
const url = `${API_URL}/users/${Settings[USER_ID_OPTION]}/items/${item.key}/children?format=json&limit=100`;
const response = await makeRequest(url);
return response.json;
}
async function fetchTopLevelItems() {
const route = new URL(
`${API_URL}/users/${Settings[USER_ID_OPTION]}/items/top`,
);
route.searchParams.set("limit", 100);
const allItems = [];
let start = 0;
while (true) {
route.searchParams.set("start", start);
const response = await makeRequest(route.toString());
const items = await response.json;
if (items.length === 0) break;
allItems.push(...items);
start += items.length;
const totalResults = Number.parseInt(response.headers["Total-Results"]);
if (allItems.length >= totalResults) break;
}
return allItems;
}
async function makeRequest(url, options = {}) {
const headers = {
"Zotero-API-Version": 3,
"Zotero-API-Key": Settings[API_KEY_OPTION],
...options.headers,
};
return requestUrl({
...options,
url,
headers,
});
}
function replaceIllegalFileNameCharactersInString(string) {
return string.replace(/[\\,#%&\{\}\/*<>$\'\":@]*/g, "");
} Recommended settingsAnnotation formatI'm personally using this version, but you can modify it to your own liking.
```js quickadd
const note = this.variables.note;
let quote = this.variables.quote;
if (this.variables.annotationType === "image") {
return "==Images not supported yet.=="
}
const headingMatch = note.match(/\.h(\d+)/);
if (headingMatch) {
const headingLevel = Number.parseInt(headingMatch[1], 10) + 2;
const hashtags = '#'.repeat(headingLevel);
quote = `${hashtags} ${quote}`;
}
if (quote.trim().startsWith("#")) return quote;
return `> ${quote}`
```
{{VALUE:note}} SortingI'm using ascending |
Beta Was this translation helpful? Give feedback.
-
This is a script to import Zotero papers into Obsidian. Opening here for discussion regarding the script.
How to use it: https://bagerbach.com/blog/how-i-read-research-papers-with-obsidian-and-zotero
Beta Was this translation helpful? Give feedback.
All reactions