├── client ├── style.css ├── content │ ├── config.js │ ├── debug.ts │ ├── util.ts │ ├── columns.jsx │ └── itemPane.jsx ├── prefs.js ├── skin │ ├── muted.png │ ├── loading.jpg │ ├── disputing.png │ ├── mentioning.png │ ├── supporting.png │ ├── contrasting.png │ ├── scite_logo_12w.png │ ├── total_publications.png │ ├── scite_logo_header_svg.svg │ └── scite_logo_sidenav_svg.svg ├── locale │ └── en-US │ │ └── scite-zotero-plugin.ftl └── manifest.json ├── .eslintignore ├── .gitignore ├── images ├── plugin.png ├── example-tally.png └── view-scite-report.png ├── tsconfig.json ├── .circleci └── config.yml ├── package.json ├── typings └── global.d.ts ├── esbuild.js ├── .eslintrc.js ├── bootstrap.ts ├── lib.ts └── README.md /client/style.css: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | *.d.ts -------------------------------------------------------------------------------- /client/content/config.js: -------------------------------------------------------------------------------- 1 | export const PLUGIN_ENABLED = true; -------------------------------------------------------------------------------- /client/prefs.js: -------------------------------------------------------------------------------- 1 | pref("extensions.scite-zotero-plugin.intensity", 100); 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | build/ 3 | gen/ 4 | xpi/ 5 | .DS_Store 6 | .eslintcache/ -------------------------------------------------------------------------------- /images/plugin.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scitedotai/scite-zotero-plugin/HEAD/images/plugin.png -------------------------------------------------------------------------------- /client/skin/muted.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scitedotai/scite-zotero-plugin/HEAD/client/skin/muted.png -------------------------------------------------------------------------------- /client/skin/loading.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scitedotai/scite-zotero-plugin/HEAD/client/skin/loading.jpg -------------------------------------------------------------------------------- /client/skin/disputing.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scitedotai/scite-zotero-plugin/HEAD/client/skin/disputing.png -------------------------------------------------------------------------------- /client/skin/mentioning.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scitedotai/scite-zotero-plugin/HEAD/client/skin/mentioning.png -------------------------------------------------------------------------------- /client/skin/supporting.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scitedotai/scite-zotero-plugin/HEAD/client/skin/supporting.png -------------------------------------------------------------------------------- /images/example-tally.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scitedotai/scite-zotero-plugin/HEAD/images/example-tally.png -------------------------------------------------------------------------------- /client/skin/contrasting.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scitedotai/scite-zotero-plugin/HEAD/client/skin/contrasting.png -------------------------------------------------------------------------------- /images/view-scite-report.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scitedotai/scite-zotero-plugin/HEAD/images/view-scite-report.png -------------------------------------------------------------------------------- /client/skin/scite_logo_12w.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scitedotai/scite-zotero-plugin/HEAD/client/skin/scite_logo_12w.png -------------------------------------------------------------------------------- /client/skin/total_publications.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scitedotai/scite-zotero-plugin/HEAD/client/skin/total_publications.png -------------------------------------------------------------------------------- /client/locale/en-US/scite-zotero-plugin.ftl: -------------------------------------------------------------------------------- 1 | scite-zotero-plugin-pane-header = 2 | .label = Scite Smart Citations 3 | 4 | scite-zotero-plugin-pane-sidenav = 5 | .tooltiptext = Scite Smart Citations 6 | -------------------------------------------------------------------------------- /client/content/debug.ts: -------------------------------------------------------------------------------- 1 | import { IZotero } from '../../typings/global' 2 | 3 | declare const Zotero: IZotero 4 | 5 | export function debug(...msg) { 6 | const str = `scite: ${msg.map(s => s.toString()).join(' ')}` 7 | // console.error(str) // tslint:disable-line:no-console 8 | Zotero.debug(str) 9 | } 10 | -------------------------------------------------------------------------------- /client/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "manifest_version": 2, 3 | "name": "Scite Plugin Zotero (V7)", 4 | "version": "0.0.1", 5 | "description": "Scite Plugin Zotero (V7)", 6 | "applications": { 7 | "zotero": { 8 | "id": "scite-zotero-plugin@scite.ai", 9 | "update_url": "https://github.com/u-ashish/scite-zotero-plugin/releases/download/release/update.rdf", 10 | "strict_min_version": "6.999", 11 | "strict_max_version": "7.0.*" 12 | } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "importHelpers": true, 4 | "target": "es2017", 5 | "disableSizeLimit": true, 6 | "module": "commonjs", 7 | "noImplicitAny": false, 8 | "esModuleInterop": true, 9 | "removeComments": false, 10 | "preserveConstEnums": false, 11 | "sourceMap": false, 12 | "downlevelIteration": true, 13 | "lib": [ "es2017", "dom", "dom.iterable" ], 14 | "jsx": "react", 15 | "allowJs": true, 16 | "resolveJsonModule": true, 17 | "typeRoots": [ 18 | "./typings/**/*.d.ts", 19 | "./node_modules/@types" 20 | ] 21 | }, 22 | "include": [ "*.ts", "chrome/content", "client/chrome/content/**/*.ts", "typing", "node_modules/zotero-types", "client/content/debug.ts" ], 23 | "exclude": [ 24 | "node_modules", 25 | "**/*.spec.ts", 26 | "build/*", 27 | "xpi/*" 28 | ] 29 | } 30 | -------------------------------------------------------------------------------- /client/skin/scite_logo_header_svg.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /client/skin/scite_logo_sidenav_svg.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /.circleci/config.yml: -------------------------------------------------------------------------------- 1 | version: '2.1' 2 | orbs: 3 | slack: circleci/slack@4.1.1 4 | jobs: 5 | test: 6 | working_directory: ~/repo 7 | docker: 8 | - image: cimg/node:20.16.0 9 | 10 | steps: 11 | - checkout 12 | - run: npm install 13 | - run: npm run build 14 | - slack/notify: 15 | event: fail 16 | template: SLACK_FAIL_TEST_TEMPLATE 17 | 18 | deploy: 19 | working_directory: ~/repo 20 | docker: 21 | - image: cimg/node:20.16.0 22 | 23 | steps: 24 | - checkout 25 | - run: npm install 26 | - run: npm run build 27 | - run: npm run release 28 | - slack/notify: 29 | event: fail 30 | template: SLACK_FAIL_TEMPLATE 31 | - slack/notify: 32 | event: pass 33 | template: SLACK_PASS_TEMPLATE 34 | 35 | workflows: 36 | version: 2 37 | test_and_deploy: 38 | jobs: 39 | - test: 40 | context: 41 | - slack-secrets 42 | - deploy: 43 | context: 44 | - slack-secrets 45 | filters: 46 | branches: 47 | only: 48 | - master 49 | - /^gh-.*/ 50 | requires: 51 | - test 52 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "scite-zotero-plugin", 3 | "version": "2.0.4", 4 | "description": "Scite Zotero Plugin (V7+)", 5 | "scripts": { 6 | "lint": "eslint . --ext .ts --cache --cache-location .eslintcache/", 7 | "prebuild": "npm run lint", 8 | "build": "tsc --noEmit && node esbuild.js", 9 | "postbuild": "zotero-plugin-zipup build scite-zotero-plugin", 10 | "release": "zotero-plugin-release", 11 | "postversion": "git push --follow-tags" 12 | }, 13 | "repository": { 14 | "type": "git", 15 | "url": "https://github.com/scitedotai/scite-zotero-plugin.git" 16 | }, 17 | "author": { 18 | "name": "ashish uppala", 19 | "email": "hi@scite.ai" 20 | }, 21 | "bugs": { 22 | "url": "https://github.com/scitedotai/scite-zotero-plugin/issues" 23 | }, 24 | "homepage": "https://github.com/scitedotai/scite-zotero-plugin", 25 | "dependencies": { 26 | "@typescript-eslint/eslint-plugin": "^5.59.9", 27 | "@typescript-eslint/parser": "^5.59.9", 28 | "esbuild": "^0.18.1", 29 | "eslint": "^8.42.0", 30 | "eslint-plugin-import": "^2.27.5", 31 | "eslint-plugin-jsdoc": "^46.2.6", 32 | "eslint-plugin-prefer-arrow": "^1.2.3", 33 | "mkdirp": "^3.0.1", 34 | "npm-run-all": "^4.1.5", 35 | "prop-types": "^15.8.1", 36 | "react": "^18.3.1", 37 | "rimraf": "^5.0.1", 38 | "ts-node": "^10.9.1", 39 | "typescript": "^5.1.3", 40 | "zotero-plugin": "^2.0.25", 41 | "zotero-types": "^1.0.15" 42 | }, 43 | "xpi": { 44 | "name": "Scite Zotero Plugin (V7+)", 45 | "updateLink": "https://github.com/scitedotai/scite-zotero-plugin/releases/download/v{version}/scite-zotero-plugin-{version}.xpi", 46 | "releaseURL": "https://github.com/scitedotai/scite-zotero-plugin/releases/download/release/", 47 | "bootstrapped": true 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /typings/global.d.ts: -------------------------------------------------------------------------------- 1 | import { CScite } from '../lib' 2 | 3 | export interface IZotero { 4 | Scite: CScite 5 | 6 | debug(msg: string) 7 | logError(err: Error | string) 8 | 9 | getActiveZoteroPane(): any 10 | 11 | Notifier: { 12 | trigger(event: string, type: string, itemIDs: number[]) 13 | registerObserver(onserver: any, types: string[], id: string, priority?: number) // any => ZoteroObserver 14 | unregisterObserver(id: number) 15 | } 16 | 17 | Prefs: { 18 | get(pref: string) 19 | set(pref: string, value: string | number | boolean) 20 | } 21 | 22 | Items: { 23 | getAsync(ids: number | number[]): Promise 24 | } 25 | 26 | DB: { 27 | queryAsync(query: string): Promise 28 | } 29 | 30 | HTTP: { 31 | request(method: string, url: string, options?: { 32 | body?: string, 33 | responseType?: string, 34 | headers?: Record, 35 | }): Promise 36 | } 37 | 38 | Schema: { 39 | schemaUpdatePromise: Promise 40 | } 41 | 42 | ItemTreeManager: any 43 | ItemPaneManager: any 44 | 45 | Promise: { 46 | new (executor: (resolve: (value: T | PromiseLike) => void, reject: (reason?: any) => void) => void): Promise; 47 | resolve(value: T | PromiseLike): Promise; 48 | reject(reason?: any): Promise; 49 | all(values: Iterable>): Promise; 50 | race(values: Iterable>): Promise; 51 | defer(): { promise: Promise; resolve: (value: T) => void; reject: (reason?: any) => void }; 52 | } 53 | 54 | /** 55 | * NOTE (Ashish): 56 | * ItemTreeView is removed after Zotero 6 57 | * but we include it in the typing and check within 58 | * content/scite.ts if it is undefined to handle patching properly. 59 | */ 60 | ItemTreeView: { 61 | new (): {} 62 | getCellText(row: number, col: number) 63 | } 64 | 65 | Item: { 66 | new (): {} 67 | getField(field: string, unformatted: boolean, includeBaseMapped: boolean): string 68 | } 69 | } 70 | 71 | declare const Components: any 72 | declare const Services: any 73 | declare const rootURI: string 74 | -------------------------------------------------------------------------------- /client/content/util.ts: -------------------------------------------------------------------------------- 1 | declare const Zotero: any 2 | 3 | export const worker = typeof location !== 'undefined' && location.search 4 | export const isZotero7 = worker ? ((new URLSearchParams(location.search)).get('isZotero7') === 'true') : Zotero.platformMajorVersion >= 102 5 | 6 | export function htmlencode(text) { 7 | return `${text}`.replace(/&/g, '&').replace(//g, '>') 8 | } 9 | 10 | export function plaintext(text) { 11 | return `${text}` 12 | } 13 | 14 | export function getField(item, field) { 15 | try { 16 | return item.getField(field) || '' 17 | } 18 | catch (err) { 19 | return '' 20 | } 21 | } 22 | 23 | export function getDOI(doi, extra) { 24 | if (doi) return doi.toLowerCase().trim() 25 | 26 | if (!extra) return '' 27 | 28 | const dois = extra.split('\n').map(line => line.match(/^DOI:\s*(.+)/i)).filter(line => line).map(line => line[1].trim()) 29 | return dois[0]?.toLowerCase().trim() || '' 30 | } 31 | 32 | export function isShortDoi(doi) { 33 | return doi.match(/10\/[^\s]*[^\s\.,]/) 34 | } 35 | 36 | export const fetchTallyDataZotero7 = (item, dataKey) => { 37 | try { 38 | const sciteTallyFieldName = dataKey.includes('zotero-items') ? dataKey.split('-').slice(-1)[0] : dataKey 39 | if (Zotero.Scite.ready.isPending()) return '-' // tslint:disable-line:no-use-before-declare 40 | const doi = getDOI(item.getField('DOI'), item.getField('extra')) 41 | if (!doi || !Zotero.Scite.tallies[doi]) return 0 42 | const tallies = Zotero.Scite.tallies[doi] 43 | return tallies[sciteTallyFieldName] 44 | } 45 | catch (err) { 46 | Zotero.logError(`Error loading ${dataKey} tally: ${err}`) 47 | return 0 48 | } 49 | } 50 | 51 | export const fetchTalliesZotero7 = item => { 52 | try { 53 | if (Zotero.Scite.ready.isPending()) return {} // tslint:disable-line:no-use-before-declare 54 | const doi = getDOI(item.getField('DOI'), item.getField('extra')) 55 | Zotero.logError(`Debugging DOI ${doi} for item ${item.getField('DOI')} and extra ${item.getField('extra')}`) 56 | if (!doi || !Zotero.Scite.tallies[doi]) return {} 57 | const tallies = Zotero.Scite.tallies[doi] 58 | return tallies 59 | } 60 | catch (err) { 61 | Zotero.logError(`Error loading tallies: ${err}`) 62 | return {} 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /esbuild.js: -------------------------------------------------------------------------------- 1 | const path = require('path') 2 | const fs = require('fs') 3 | const esbuild = require('esbuild') 4 | const rmrf = require('rimraf') 5 | rmrf.sync('gen') 6 | 7 | require('zotero-plugin/copy-assets') 8 | require('zotero-plugin/rdf') 9 | require('zotero-plugin/version') 10 | 11 | function js(src) { 12 | return src.replace(/[.]ts$/, '.js') 13 | } 14 | 15 | async function bundle(config) { 16 | config = { 17 | bundle: true, 18 | format: 'iife', 19 | target: ['firefox60'], 20 | external: ['zotero/itemTree'], 21 | inject: [], 22 | treeShaking: true, 23 | keepNames: true, 24 | ...config, 25 | } 26 | 27 | let target 28 | if (config.outfile) { 29 | target = config.outfile 30 | } 31 | else if (config.entryPoints.length === 1 && config.outdir) { 32 | target = path.join(config.outdir, js(path.basename(config.entryPoints[0]))) 33 | } 34 | else { 35 | target = `${config.outdir} [${config.entryPoints.map(js).join(', ')}]` 36 | } 37 | 38 | const exportGlobals = config.exportGlobals 39 | delete config.exportGlobals 40 | if (exportGlobals) { 41 | const esm = await esbuild.build({ ...config, logLevel: 'silent', format: 'esm', metafile: true, write: false }) 42 | if (Object.values(esm.metafile.outputs).length !== 1) throw new Error('exportGlobals not supported for multiple outputs') 43 | 44 | for (const output of Object.values(esm.metafile.outputs)) { 45 | if (output.entryPoint) { 46 | config.globalName = escape(`{ ${output.exports.sort().join(', ')} }`).replace(/%/g, '$') 47 | // make these var, not const, so they get hoisted and are available in the global scope. 48 | } 49 | } 50 | } 51 | 52 | console.log('* bundling', target) 53 | await esbuild.build(config) 54 | if (exportGlobals) { 55 | await fs.promises.writeFile( 56 | target, 57 | (await fs.promises.readFile(target, 'utf-8')).replace(config.globalName, unescape(config.globalName.replace(/[$]/g, '%'))) 58 | ) 59 | } 60 | } 61 | 62 | async function build() { 63 | await bundle({ 64 | exportGlobals: true, 65 | entryPoints: [ 'bootstrap.ts' ], 66 | outdir: 'build', 67 | banner: { js: 'var Zotero;\n' }, 68 | }) 69 | 70 | await bundle({ 71 | entryPoints: [ 'lib.ts' ], 72 | outdir: 'build', 73 | banner: { js: 'var Zotero;\nif (!Zotero.Scite) {\n' }, 74 | footer: { js: '\n}' }, 75 | }) 76 | } 77 | 78 | build().catch(err => { 79 | console.log(err) 80 | process.exit(1) 81 | }) 82 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const config = require('zotero-plugin/.eslintrc') 3 | 4 | // Add this block 5 | config.parserOptions = { 6 | ...config.parserOptions, 7 | ecmaFeatures : { 8 | jsx: true 9 | }, 10 | settings: { 11 | react: { 12 | version: "detect" 13 | } 14 | }, 15 | project: path.resolve(__dirname, './tsconfig.json'), 16 | tsconfigRootDir: __dirname, 17 | } 18 | 19 | config.parser = '@typescript-eslint/parser' 20 | 21 | config.rules['@stylistic/block-spacing'] = 'off' 22 | config.rules['@stylistic/object-curly-spacing'] = 'off' 23 | config.rules['@typescript-eslint/consistent-type-definitions'] = 'off' 24 | config.rules['@typescript-eslint/member-ordering'] = 'off' 25 | config.rules['max-classes-per-file'] = 'off' 26 | config.rules['no-console'] = 'error' 27 | config.rules['no-new-func'] = 'off' 28 | config.rules['@typescript-eslint/semi'] = 'off' 29 | config.rules['no-magic-numbers'] = 'off' 30 | config.rules['@typescript-eslint/semi'] = 'off' 31 | config.rules['no-underscore-dangle'] = [ 'error', { "allowAfterThis": true } ] 32 | 33 | config.rules['@typescript-eslint/no-unsafe-member-access'] = 'off' 34 | config.rules['@typescript-eslint/no-unsafe-call'] = 'off' 35 | config.rules['@typescript-eslint/prefer-regexp-exec'] = 'off' 36 | config.rules['@typescript-eslint/no-implied-eval'] = 'off' 37 | config.rules['@typescript-eslint/no-unsafe-assignment'] = 'off' 38 | config.rules['@typescript-eslint/no-unsafe-argument'] = 'off' 39 | config.rules['@typescript-eslint/no-unsafe-return'] = 'off' 40 | config.rules['@typescript-eslint/restrict-template-expressions'] = 'off' 41 | config.rules['@typescript-eslint/explicit-module-boundary-types'] = 'off' 42 | config.rules['@typescript-eslint/no-unused-vars'] = 'off' 43 | config.rules['brace-style'] = 'off' 44 | config.rules['no-useless-escape'] = 'off' 45 | config.rules['prefer-rest-params'] = 'off' 46 | config.rules['prefer-template'] = 'off' 47 | config.rules['prefer-arrow/prefer-arrow-functions'] = 'off' 48 | config.rules['@typescript-eslint/no-inferrable-types'] = 'off' 49 | config.rules['@typescript-eslint/restrict-plus-operands'] = 'off' 50 | config.rules['@typescript-eslint/require-await'] = 'off' 51 | config.rules['prefer-spread'] = 'off' 52 | config.rules['no-underscore-dangle'] = 'off' 53 | 54 | config.rules['@typescript-eslint/ban-ts-comment'] = 'warn' 55 | config.rules['@typescript-eslint/member-delimiter-style'] = [ 'error', { 56 | multiline: { delimiter: 'none', requireLast: false }, 57 | singleline: { delimiter: 'comma', requireLast: false }, 58 | }] 59 | 60 | config.ignorePatterns = [ 61 | 'webpack.config.ts', 62 | "build/*", 63 | "xpi/*" 64 | ] 65 | 66 | module.exports = config 67 | -------------------------------------------------------------------------------- /client/content/columns.jsx: -------------------------------------------------------------------------------- 1 | const React = require('react'); 2 | const { string } = require('prop-types'); 3 | import { fetchTallyDataZotero7 } from './util'; 4 | 5 | const SciteColumnHeading = (props) => { 6 | props = Object.assign({}, props); 7 | return {props.name}; 8 | }; 9 | 10 | SciteColumnHeading.propTypes = { 11 | iconPath: string, 12 | name: string.isRequired, 13 | } 14 | 15 | 16 | export const sciteColumnsZotero7 = [ 17 | { 18 | dataKey: 'zotero-items-column-supporting', 19 | label: 'Supporting', 20 | pluginID: 'scite-zotero-plugin@scite.ai', 21 | iconPath: 'skin/supporting.png', 22 | flex: 0, 23 | fixedWidth: false, 24 | staticWidth: false, 25 | minWidth: 0, 26 | zoteroPersist: ['width', 'ordinal', 'hidden', 'sortActive', 'sortDirection'], 27 | dataProvider: (item, dataKey) => { 28 | return fetchTallyDataZotero7(item, dataKey) 29 | } 30 | }, 31 | { 32 | dataKey: 'zotero-items-column-contrasting', 33 | label: 'Contrasting', 34 | pluginID: 'scite-zotero-plugin@scite.ai', 35 | iconPath: 'skin/contrasting.png', 36 | flex: 0, 37 | fixedWidth: false, 38 | staticWidth: false, 39 | minWidth: 0, 40 | zoteroPersist: ['width', 'ordinal', 'hidden', 'sortActive', 'sortDirection'], 41 | dataProvider: (item, dataKey) => { 42 | return fetchTallyDataZotero7(item, dataKey) 43 | } 44 | }, 45 | { 46 | dataKey: 'zotero-items-column-mentioning', 47 | label: 'Mentioning', 48 | pluginID: 'scite-zotero-plugin@scite.ai', 49 | iconPath: 'skin/mentioning.png', 50 | flex: 0, 51 | fixedWidth: false, 52 | staticWidth: false, 53 | minWidth: 0, 54 | zoteroPersist: ['width', 'ordinal', 'hidden', 'sortActive', 'sortDirection'], 55 | dataProvider: (item, dataKey) => { 56 | return fetchTallyDataZotero7(item, dataKey) 57 | } 58 | }, 59 | { 60 | dataKey: 'zotero-items-column-total', 61 | label: 'Total Smart Citations', 62 | pluginID: 'scite-zotero-plugin@scite.ai', 63 | iconPath: '', 64 | flex: 0, 65 | fixedWidth: false, 66 | staticWidth: false, 67 | minWidth: 0, 68 | zoteroPersist: ['width', 'ordinal', 'hidden', 'sortActive', 'sortDirection'], 69 | dataProvider: (item, dataKey) => { 70 | return fetchTallyDataZotero7(item, dataKey) 71 | } 72 | }, 73 | { 74 | dataKey: 'zotero-items-column-citingPublications', 75 | label: 'Total Distinct Citing Publications', 76 | pluginID: 'scite-zotero-plugin@scite.ai', 77 | iconPath: 'skin/total_publications.png', 78 | flex: 0, 79 | fixedWidth: false, 80 | staticWidth: false, 81 | minWidth: 0, 82 | zoteroPersist: ['width', 'ordinal', 'hidden', 'sortActive', 'sortDirection'], 83 | dataProvider: (item, dataKey) => { 84 | return fetchTallyDataZotero7(item, dataKey) 85 | } 86 | 87 | } 88 | ] 89 | -------------------------------------------------------------------------------- /client/content/itemPane.jsx: -------------------------------------------------------------------------------- 1 | 2 | import { fetchTalliesZotero7, getDOI } from './util'; 3 | 4 | export const sciteItemPaneZotero7 = { 5 | paneID: "scite-zotero-plugin-pane", 6 | pluginID: "scite-zotero-plugin@scite.ai", 7 | header: { 8 | l10nID: "scite-zotero-plugin-pane-header", 9 | icon: "skin/scite_logo_header_svg.svg", 10 | }, 11 | sidenav: { 12 | l10nID: "scite-zotero-plugin-pane-sidenav", 13 | icon: "skin/scite_logo_sidenav_svg.svg", 14 | }, 15 | bodyXHTML: ` 16 |
17 |
18 | # Supporting 19 | 20 |
21 |
22 | # Contrasting 23 | 24 |
25 |
26 | # Mentioning 27 | 28 |
29 |
30 | # Citing Publications 31 | 32 |
33 | 36 |
37 | `, 38 | onRender: ({ body, item }) => { 39 | const tallies = fetchTalliesZotero7(item) 40 | if (!tallies) { 41 | body.textContent = "No tallies loaded" 42 | } else { 43 | const doi = getDOI(item.getField('DOI'), item.getField('extra')); 44 | 45 | const fields = ['supporting', 'contrasting', 'mentioning', 'citingPublications']; 46 | fields.forEach(field => { 47 | const spanElement = body.querySelector(`#scite-plugin-item-pane-${field}-key`); 48 | if (spanElement) { 49 | spanElement.textContent = tallies[field]?.toLocaleString() || '-'; 50 | spanElement.dataset.itemid = item ? `${item.id}-${field}-${tallies[field]}` : ''; 51 | } 52 | }); 53 | 54 | // Handle the report link 55 | const linkContainer = body.querySelector('#scite-plugin-item-pane-report-link-container'); 56 | const linkElement = body.querySelector('#scite-plugin-item-pane-report-link'); 57 | if (doi && linkContainer && linkElement) { 58 | linkContainer.style.display = 'block'; 59 | linkElement.onclick = (event) => { 60 | event.preventDefault(); 61 | const url = `https://scite.ai/reports/${encodeURIComponent(doi)}?utm_source=zotero&utm_medium=scite-zotero-plugin&utm_campaign=scite`; 62 | Zotero.launchURL(url); 63 | }; 64 | } else if (linkContainer) { 65 | linkContainer.style.display = 'none'; 66 | } 67 | } 68 | } 69 | } -------------------------------------------------------------------------------- /bootstrap.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable prefer-arrow/prefer-arrow-functions, no-var, @typescript-eslint/no-unused-vars, no-caller, @typescript-eslint/explicit-module-boundary-types */ 2 | declare const ChromeUtils: any 3 | declare const Cc: any 4 | declare const Ci: any 5 | 6 | if (typeof Zotero == 'undefined') { 7 | var Zotero 8 | } 9 | 10 | function log(msg) { 11 | msg = `[Scite Zotero] bootstrap: ${msg}` 12 | Zotero.logError(msg) 13 | } 14 | 15 | export function onMainWindowLoad({ window }) { 16 | log('onMainWindowLoad') 17 | window.MozXULElement.insertFTLIfNeeded('scite-zotero-plugin.ftl') 18 | } 19 | 20 | async function waitForZotero() { 21 | if (typeof Zotero != 'undefined') { 22 | await Zotero.initializationPromise 23 | return 24 | } 25 | 26 | // eslint-disable-next-line @typescript-eslint/no-shadow 27 | var { Services } = ChromeUtils.import('resource://gre/modules/Services.jsm') 28 | var windows = Services.wm.getEnumerator('navigator:browser') 29 | var found = false 30 | while (windows.hasMoreElements()) { 31 | const win = windows.getNext() 32 | if (win.Zotero) { 33 | Zotero = win.Zotero 34 | found = true 35 | break 36 | } 37 | } 38 | if (!found) { 39 | await new Promise(resolve => { 40 | var listener = { 41 | onOpenWindow(aWindow) { 42 | // Wait for the window to finish loading 43 | const domWindow = aWindow.QueryInterface(Ci.nsIInterfaceRequestor) 44 | .getInterface(Ci.nsIDOMWindowInternal || Ci.nsIDOMWindow) 45 | domWindow.addEventListener('load', function() { 46 | domWindow.removeEventListener('load', arguments.callee, false) 47 | if (domWindow.Zotero) { 48 | Services.wm.removeListener(listener) 49 | Zotero = domWindow.Zotero 50 | resolve(undefined) 51 | } 52 | }, false) 53 | }, 54 | } 55 | Services.wm.addListener(listener) 56 | }) 57 | } 58 | await Zotero.initializationPromise 59 | } 60 | 61 | async function install() { 62 | await waitForZotero() 63 | log('Installed Scite Plugin') 64 | } 65 | 66 | let chromeHandle 67 | async function startup({ id, version, resourceURI, rootURI = resourceURI?.spec }) { 68 | try { 69 | await waitForZotero() 70 | if (typeof Services == 'undefined') { 71 | var { Services } = ChromeUtils.import('resource://gre/modules/Services.jsm') 72 | } 73 | 74 | var aomStartup = Cc['@mozilla.org/addons/addon-manager-startup;1'].getService(Ci.amIAddonManagerStartup) 75 | var manifestURI = Services.io.newURI(rootURI + 'client/manifest.json') 76 | 77 | // Register chrome resources 78 | chromeHandle = aomStartup.registerChrome(manifestURI, [ 79 | [ 'content', 'scite-zotero-plugin', rootURI + 'content/' ], 80 | [ 'locale', 'scite-zotero-plugin', 'en-US', rootURI + 'locale/en-US/' ], 81 | ]) 82 | 83 | Services.scriptloader.loadSubScript(`${rootURI}lib.js`) 84 | Zotero.Scite.start(rootURI).catch(err => Zotero.logError(err)) 85 | log('Started Zotero Scite') 86 | const $window = Zotero.getMainWindow() 87 | onMainWindowLoad({ window: $window }) 88 | } 89 | catch (err) { 90 | Zotero.logError('[Scite Zotero] Error during startup') 91 | Zotero.logError(err) 92 | } 93 | } 94 | 95 | function shutdown() { 96 | log('Shutting down') 97 | 98 | // Remove stylesheet 99 | var zp = Zotero.getActiveZoteroPane() 100 | 101 | Zotero.Scite.unload().catch(err => Zotero.logError(err)) 102 | 103 | // Deregister chrome 104 | if (chromeHandle) { 105 | chromeHandle.destruct() 106 | chromeHandle = null 107 | } 108 | 109 | Zotero.Scite = undefined 110 | } 111 | 112 | function uninstall() { 113 | log('Uninstalled') 114 | } 115 | 116 | export { install, startup, shutdown, uninstall } 117 | -------------------------------------------------------------------------------- /lib.ts: -------------------------------------------------------------------------------- 1 | import { IZotero } from './typings/global' 2 | 3 | Components.utils.import('resource://gre/modules/AddonManager.jsm') 4 | 5 | declare const Zotero: IZotero 6 | declare const Components: any 7 | 8 | import { debug } from './client/content/debug' 9 | import { htmlencode, plaintext, getField, getDOI, isShortDoi, isZotero7 } from './client/content/util' 10 | import { PLUGIN_ENABLED } from './client/content/config' 11 | import { sciteColumnsZotero7 } from './client/content/columns' 12 | import { sciteItemPaneZotero7 } from './client/content/itemPane' 13 | 14 | interface Tallies { 15 | doi: string 16 | contrasting: number // NOTE: The API returns contradicting, we map this manually 17 | mentioning: number 18 | supporting: number 19 | total: number 20 | unclassified: number 21 | citingPublications: number 22 | } 23 | 24 | const shortToLongDOIMap = {} 25 | const longToShortDOIMap = {} 26 | const MAX_DOI_BATCH_SIZE = 500 // tslint:disable-line:no-magic-numbers 27 | 28 | async function getLongDoi(shortDoi) { 29 | try { 30 | if (!shortDoi) { 31 | return '' 32 | } 33 | // If it starts with 10/, it is short 34 | // otherwise, treat it as long and just return 35 | shortDoi = shortDoi.toLowerCase().trim() 36 | if (!isShortDoi(shortDoi)) { 37 | // This is probably a long DOI then! 38 | return shortDoi 39 | } 40 | if (shortDoi in shortToLongDOIMap) { 41 | debug(`shortToLongDOIMap cache hit ${shortDoi}`) 42 | return shortToLongDOIMap[shortDoi] 43 | } 44 | 45 | const res = await Zotero.HTTP.request('GET', `https://doi.org/api/handles/${shortDoi}`) 46 | const doiRes = res?.response ? JSON.parse(res.response).values : [] 47 | const longDoi = (doiRes && doiRes.length && doiRes.length > 1) ? doiRes[1].data.value.toLowerCase().trim() : '' 48 | if (!longDoi) { 49 | debug(`Unable to resolve shortDoi ${shortDoi} to longDoi`) 50 | // I guess just return the shortDoi for now...? 51 | return shortDoi 52 | } 53 | 54 | // Use these to minimize API calls and easily go back and forth 55 | shortToLongDOIMap[shortDoi] = longDoi 56 | longToShortDOIMap[longDoi] = shortDoi 57 | 58 | debug(`Converted shortDoi (${shortDoi}) to longDoi (${longDoi})`) 59 | return longDoi 60 | } 61 | catch (err) { 62 | Zotero.logError(`ERR_getLongDoi(${shortDoi}): ${err}`) 63 | return shortDoi 64 | } 65 | } 66 | 67 | const ready = Zotero.Promise.defer() 68 | 69 | export class CScite { 70 | public ready: any = ready.promise 71 | public tallies: { [DOI: string]: Tallies } = {} 72 | public uninstalled: boolean = false 73 | 74 | private bundle: any 75 | private started = false 76 | 77 | constructor() { 78 | this.bundle = Components.classes['@mozilla.org/intl/stringbundle;1'].getService(Components.interfaces.nsIStringBundleService).createBundle('chrome://zotero-scite/locale/zotero-scite.properties') 79 | } 80 | 81 | public async start(rootURI: string = '') { 82 | if (!PLUGIN_ENABLED) { 83 | Zotero.logError('Scite Zotero plugin is disabled. Aborting!') 84 | return 85 | } 86 | 87 | if (!isZotero7) { 88 | Zotero.logError('This version of the scite plugin only supports Zotero 7 and after, please upgrade or use an older XPI') 89 | return 90 | } 91 | 92 | if (this.started) return 93 | this.started = true 94 | 95 | const columns = sciteColumnsZotero7.map(column => { 96 | const iconPath = column.iconPath ? rootURI + column.iconPath : null 97 | return { 98 | ...column, 99 | iconPath, 100 | htmlLabel: iconPath 101 | ? ` ${column.label}` 102 | : column.label, 103 | } 104 | }) 105 | 106 | for (const column of columns) { 107 | await Zotero.ItemTreeManager.registerColumns(column) 108 | } 109 | Zotero.logError('Registered columns') 110 | 111 | const updatedSciteItemPaneZotero7 = { 112 | ...sciteItemPaneZotero7, 113 | header: { 114 | ...sciteItemPaneZotero7.header, 115 | icon: sciteItemPaneZotero7.header.icon ? rootURI + sciteItemPaneZotero7.header.icon : null, 116 | }, 117 | sidenav: { 118 | ...sciteItemPaneZotero7.sidenav, 119 | icon: sciteItemPaneZotero7.sidenav.icon ? rootURI + sciteItemPaneZotero7.sidenav.icon : null, 120 | }, 121 | } 122 | const registeredID = Zotero.ItemPaneManager.registerSection(updatedSciteItemPaneZotero7) 123 | Zotero.logError(`Registered Scite section: ${registeredID}`) 124 | 125 | await Zotero.Schema.schemaUpdatePromise 126 | 127 | await this.refresh() 128 | ready.resolve(true) 129 | Zotero.Notifier.registerObserver(this, ['item'], 'Scite', 1) 130 | } 131 | 132 | public getString(name, params = {}, html = false) { 133 | if (!this.bundle || typeof this.bundle.GetStringFromName !== 'function') { 134 | Zotero.logError(`Scite.getString(${name}): getString called before strings were loaded`) 135 | return name 136 | } 137 | 138 | let template = name 139 | 140 | try { 141 | template = this.bundle.GetStringFromName(name) 142 | } 143 | catch (err) { 144 | Zotero.logError(`Scite.getString(${name}): ${err}`) 145 | } 146 | 147 | const encode = html ? htmlencode : plaintext 148 | return template.replace(/{{(.*?)}}/g, (match, param) => encode(params[param] || '')) 149 | } 150 | 151 | public async viewSciteReport(doi) { 152 | try { 153 | if (isShortDoi(doi)) { 154 | doi = await getLongDoi(doi) 155 | } 156 | const zoteroPane = Zotero.getActiveZoteroPane() 157 | zoteroPane.loadURI(`https://scite.ai/reports/${doi}`) 158 | } 159 | catch (err) { 160 | Zotero.logError(`Scite.viewSciteReport(${doi}): ${err}`) 161 | alert(err) 162 | } 163 | } 164 | 165 | public async refreshTallies(doi) { 166 | try { 167 | if (isShortDoi(doi)) { 168 | doi = await getLongDoi(doi) 169 | } 170 | 171 | const data = await Zotero.HTTP.request('GET', `https://api.scite.ai/tallies/${doi.toLowerCase().trim()}`) 172 | const tallies = data?.response 173 | if (!tallies) { 174 | Zotero.logError(`Scite.refreshTallies: No tallies found for: (${doi})`) 175 | return {} 176 | } 177 | const tallyData = JSON.parse(tallies) 178 | this.tallies[doi] = { 179 | ...tallyData, 180 | contrasting: tallyData.contradicting, 181 | } 182 | // Also set it for the short DOI equivalent 183 | const shortDoi = longToShortDOIMap[doi] 184 | if (shortDoi) { 185 | this.tallies[shortDoi] = { 186 | ...tallyData, 187 | contrasting: tallyData.contradicting, 188 | } 189 | } 190 | return tallyData 191 | } 192 | catch (err) { 193 | Zotero.logError(`Scite.refreshTallies(${doi}): ${err}`) 194 | alert(err) 195 | } 196 | } 197 | 198 | public async bulkRefreshDois(doisToFetch) { 199 | if (!doisToFetch) { 200 | return 201 | } 202 | 203 | try { 204 | const res = await Zotero.HTTP.request('POST', 'https://api.scite.ai/tallies', { 205 | body: JSON.stringify(doisToFetch.map(doi => doi.toLowerCase().trim())), 206 | responseType: 'json', 207 | headers: { 'Content-Type': 'application/json;charset=UTF-8' }, 208 | }) 209 | const doiTallies = res?.response ? res.response.tallies : {} 210 | for (const doi of Object.keys(doiTallies)) { 211 | debug(`scite bulk DOI refresh: ${doi}`) 212 | const tallies = doiTallies[doi] 213 | this.tallies[doi] = { 214 | ...tallies, 215 | contrasting: tallies.contradicting, 216 | } 217 | // Also set it for the short DOI equivalent if present 218 | const shortDoi = longToShortDOIMap[doi] 219 | if (shortDoi) { 220 | this.tallies[shortDoi] = { 221 | ...tallies, 222 | contrasting: tallies.contradicting, 223 | } 224 | } 225 | } 226 | } 227 | catch (err) { 228 | Zotero.logError(`Scite.bulkRefreshDois error getting ${doisToFetch.length || 0} DOIs: ${err}`) 229 | } 230 | } 231 | 232 | public async get(dois, options: { refresh?: boolean } = {}) { 233 | let doisToFetch = options.refresh ? dois : dois.filter(doi => !this.tallies[doi]) 234 | 235 | doisToFetch = await Promise.all(doisToFetch.map(async doi => { 236 | const longDoi = await getLongDoi(doi) 237 | return longDoi 238 | })) 239 | const numDois = doisToFetch.length 240 | if (!numDois) { 241 | return 242 | } 243 | if (numDois <= MAX_DOI_BATCH_SIZE) { 244 | await this.bulkRefreshDois(doisToFetch) 245 | } 246 | else { 247 | // Do them in chunks of MAX_DOI_BATCH_SIZE due to server limits 248 | const chunks = [] 249 | let i = 0 250 | while (i < numDois) { 251 | chunks.push(doisToFetch.slice(i, i += MAX_DOI_BATCH_SIZE)) 252 | } 253 | 254 | // Properly wait for each chunk to finish before returning! 255 | await chunks.reduce(async (promise, chunk) => { 256 | await promise 257 | await this.bulkRefreshDois(chunk) 258 | }, Promise.resolve()) 259 | } 260 | return dois.map(doi => this.tallies[doi]) 261 | } 262 | 263 | private async refresh() { 264 | try { 265 | const query = ` 266 | SELECT DISTINCT fields.fieldName, itemDataValues.value 267 | FROM fields 268 | JOIN itemData on fields.fieldID = itemData.fieldID 269 | JOIN itemDataValues on itemData.valueID = itemDataValues.valueID 270 | WHERE fieldname IN ('extra', 'DOI') 271 | `.replace(/[\s\n]+/g, ' ').trim() 272 | 273 | let dois = [] 274 | // eslint-disable-next-line 275 | for (const doi of await Zotero.DB.queryAsync(query)) { 276 | switch (doi.fieldName) { 277 | case 'extra': 278 | dois = dois.concat(doi.value.split('\n').map(line => line.match(/^DOI:\s*(.+)/i)).filter(line => line).map(line => line[1].trim())) 279 | break 280 | case 'DOI': 281 | dois.push(doi.value) 282 | break 283 | } 284 | } 285 | 286 | await this.get(dois, { refresh: true }) 287 | setTimeout(this.refresh.bind(this), 24 * 60 * 60 * 1000) // eslint-disable-line no-magic-numbers 288 | } 289 | catch (err) { 290 | Zotero.logError('[Scite Zotero] Unexpected error refreshing tallies') 291 | Zotero.logError(err) 292 | throw err 293 | } 294 | } 295 | 296 | protected async notify(action, type, ids, extraData) { 297 | if (type !== 'item' || (action !== 'modify' && action !== 'add')) return 298 | 299 | const dois = [] 300 | for (const item of (await Zotero.Items.getAsync(ids))) { 301 | const doi = getDOI(getField(item, 'DOI'), getField(item, 'extra')) 302 | if (doi && !dois.includes(doi)) dois.push(doi) 303 | } 304 | // this list of dois can include a mix of short and long 305 | if (dois.length) await this.get(dois) 306 | } 307 | } 308 | 309 | if (!Zotero.Scite) { 310 | Zotero.Scite = (new CScite) 311 | } 312 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # scite-zotero-plugin 2 | 3 | Welcome! 4 | 5 | This is a Zotero plugin developed by [scite](https://scite.ai) so that you can enrich your library with information from us. 6 | 7 | **Please note that the latest version of this plugin at V2.0 onwards only supports Zotero 7 and above**. If you are looking for a plugin that supports Zotero 6, please use an [earlier release like v.1.11.6](https://github.com/scitedotai/scite-zotero-plugin/releases/tag/v1.11.6). 8 | 9 | 10 | 11 | It currently lets you do two main things: 12 | - See classification tallies for each paper based on our Smart Citation data 13 | - Easily go to the scite report page (see below) 14 | 15 | ### Smart Citation Classification Tallies 16 | 17 | Once you install the plugin (see below section for instructions), each row will have columns for `Supporting`, `Mentioning`, `Contrasting`, `Total Smart Citations`, and `Total Distinct Citing Publications`. The values in this cell tell you, for a given paper, how many citations there are in the scite database with the corresponding classification. 18 | 19 | You may need to right click and enable the columns to see them. 20 | 21 | 22 | 23 | For example, here, the paper titled `Psychometric Properties of the Strengths and Difficulties Questionnaire` has received `207 supporting citations`, `26 contrasting citations`, `4,874 mentioning citations`, and `5,550 traditional citations` from other publications in our database. Note that a traditional citation is what you expect, i.e. a paper to paper level citation. A Smart Citation, like supporting, mentioning, contrasting, is unique to Scite because we index full-text articles from publishers and classify the intent of the citations based on our own deep learning models. You can [read about scite here](https://direct.mit.edu/qss/article/2/3/882/102990/scite-A-smart-citation-index-that-displays-the) or visit our [homepage to learn more](https://scite.ai). 24 | 25 | You can also sort on any of these 3 columns to see which papers are the most supported, contrasted, and so on. 26 | 27 | ### Viewing a scite report 28 | 29 | In order to explore the citation snippets around these classifications, you can view the scite report for the paper of interest through the item pane on the right, which shows the same metrics but in a panel. Simply select `View Scite Report`. It should open the URL directly in your browser. 30 | 31 | 32 | 33 | ## Installation 34 | 35 | Get the XPI file from https://github.com/scitedotai/scite-zotero-plugin/releases and install it in Zotero following the normal plugin procedure. 36 | 37 | To install a plugin in Zotero, download its .xpi file to your computer. Then, in Zotero, click “Tools → Plugins”, then drag the .xpi for the plugin onto the Plugins Manager window that opens. 38 | 39 | ## Changelog 40 | 41 | NOTE: You only need to download once; it will auto update afterwards! 42 | 43 | ### [2.0.2](https://github.com/scitedotai/scite-zotero-plugin/releases/tag/v2.0.2) 44 | 45 | - Fixes bug in Z7 compatible plugin where column sorting broke because the numbers were being converted to strings. 46 | 47 | ### [2.0.1](https://github.com/scitedotai/scite-zotero-plugin/releases/tag/v2.0.1) 48 | 49 | - Adds support for Zotero 7 (not backwards compatible). 50 | 51 | ### [1.11.6](https://github.com/scitedotai/scite-zotero-plugin/releases/tag/v1.11.6) 52 | 53 | - Removes scite icon from each cell; moves it into the header. Fixes a bug where one of the cells had unnecessary padding to the left. Also adds React to support using icons in the column header via `iconLabel`. 54 | 55 | ### [1.11.5](https://github.com/scitedotai/scite-zotero-plugin/releases/tag/v1.11.5) 56 | 57 | - Re-enables scite plugin and fixes bug in patched `getField` function which was raising an exception for `int` fields being passed in, causing non-scite specific columns to go into the exception handler, which swallowed the exception and returned 0. This exception handler now only happens on scite specific columns as intended, and the handling of `field` is more robust to prevent the identified sources of exceptions. 58 | 59 | ### [1.11.4](https://github.com/scitedotai/scite-zotero-plugin/releases/tag/v1.11.4) 60 | 61 | - Disables the scite plugin from doing anything when it loads -- due to intermittent bug found in Zotero 6 upgrade. 62 | 63 | ### [1.11.3](https://github.com/scitedotai/scite-zotero-plugin/releases/tag/v1.11.3) 64 | 65 | - Fix bug where column sorting on scite specific columns did not work in Zotero 6. 66 | 67 | ### [1.11.2](https://github.com/scitedotai/scite-zotero-plugin/releases/tag/v1.11.2) 68 | 69 | - Upgrade `zotero-plugin` dependency with support for Zotero 6 (backwards compatible), and use eslint instead of tslint. 70 | 71 | ### [1.11.1](https://github.com/scitedotai/scite-zotero-plugin/releases/tag/v1.11.1) 72 | 73 | - Properly match scite column during `getField` call in XUL tree version. 74 | 75 | ### [1.11.0](https://github.com/scitedotai/scite-zotero-plugin/releases/tag/v1.11.0) 76 | 77 | - Fix bug in beta build where the tally information in each row was not refreshed after the initial load from the API. 78 | 79 | ### [1.10.0](https://github.com/scitedotai/scite-zotero-plugin/releases/tag/v1.10.0) 80 | 81 | - Add backwards-compatible support for upcoming Zotero release. This specifically makes the plugin work with the build `Zotero-5.0.97-beta.43+c5d89f6d0` but should generally support the new HTML based structure (in addition to the XUL version for any users who do not upgrade). 82 | ### [1.0.9](https://github.com/scitedotai/scite-zotero-plugin/releases/tag/v1.0.9) 83 | 84 | - Fix edge case in how DOI is retrieved from zotero library metadata (prevents a JavaScript exception). 85 | 86 | ### [1.0.8](https://github.com/scitedotai/scite-zotero-plugin/releases/tag/v1.0.8) 87 | 88 | - Add columns for Total Smart Citations and Total Distinct Citing Publications (for example: if `paper X` references `paper Y` a total of 4 times, then the count of Smart Citations for paper Y will be 4, and the count of citing publications will be 1. This is because Smart Citations count each individual in-text reference and are not grouped at the citing publication level.) 89 | 90 | ### [1.0.7](https://github.com/scitedotai/scite-zotero-plugin/releases/tag/v1.0.7) 91 | 92 | - Use DOMParser and XMLSerializer. 93 | 94 | ### [1.0.6](https://github.com/scitedotai/scite-zotero-plugin/releases/tag/v1.0.6) 95 | 96 | - Rename disputed to contrasted, and change the color of the icon from orange to blue. 97 | 98 | ### [1.0.5](https://github.com/scitedotai/scite-zotero-plugin/releases/tag/v1.0.5) 99 | 100 | - Fix auto-update bug. 101 | 102 | ### [1.0.4](https://github.com/scitedotai/scite-zotero-plugin/releases/tag/v1.0.4) 103 | 104 | - Add ability to resize `Supporting`, `Mentioning`, and `Disputing` columns. 105 | ### [1.0.3](https://github.com/scitedotai/scite-zotero-plugin/releases/tag/v1.0.3) 106 | 107 | - Minor logging changes, trim whitespace from DOIs, and properly supports automatic updates. 108 | 109 | ### [1.0.2](https://github.com/scitedotai/scite-zotero-plugin/releases/tag/v1.0.2) 110 | 111 | - Fixes bug where DOIs during bulk refresh were not properly being lower cased, and adds debug logging to triage issues in the future if it persists in other edge cases. 112 | 113 | ### [1.0.1](https://github.com/scitedotai/scite-zotero-plugin/releases/tag/v1.0.1) 114 | 115 | - Fixes bug where DOI wasn't being lowercased properly 116 | 117 | ### [1.0.0](https://github.com/scitedotai/scite-zotero-plugin/releases/tag/v1.0.0) 118 | 119 | Release initial version of plugin that allows you to: 120 | - See the # supporting cites (separate column, sortable) 121 | - See the # mentioning cites (separate column, sortable) 122 | - See the # disputing cites (separate column, sortable) 123 | - Directly view a scite report by right-clicking on a row and clicking 'View scite report') 124 | - Refreshing the tallies whenever you want (right-click row and click the refresh tallies option) 125 | 126 | ## Instructions for local development 127 | 128 | (These were originally from https://www.zotero.org/support/dev/client_coding/plugin_development but replicated here for convenience) 129 | 130 | - Clone the repo 131 | - `npm install` to get any dependencies 132 | - `npm run build` will generate a `build/` folder for you. You should see an `install.rdf` file in this directory. If you open it, find the `` tag and make note of the value. 133 | - Make sure Zotero is closed 134 | - In terminal, navigate to your Zotero profile directory 135 | 136 | | Operating System | Location | 137 | | ----------- | ----------- | 138 | | Mac | /Users//Library/Application Support/Zotero/Profiles/ | 139 | | Windows 10/8/7/Vista | C:\Users\\AppData\Roaming\Zotero\Zotero\Profiles\ | 140 | | Windows XP/2000 | C:\Documents and Settings\\Application Data\Zotero\Zotero\Profiles\ | 141 | | Linux | ~/.zotero/zotero/ | 142 | 143 | NOTE: The above table is from https://www.zotero.org/support/kb/profile_directory 144 | 145 | - Next, go into `extensions/` and create a text file matching the value you saw in the `` tag. e.g. a file called `scite@scite.ai` 146 | - Open this file, and in it, set the contents to be the absolute path to the `install.rdf` file from your `build/` directory 147 | - `cd` back to the profile directory (one level above `extensions/`) 148 | - Open the `prefs.js` file 149 | - Comment out the lines containing `extensions.lastAppVersion` and `extensions.lastPlatformVersion`. Should only be needed once. 150 | - Open Zotero, and you should see the extension get loaded 151 | 152 | Notes: 153 | - Doing `npm run build` will also generate an `xpi/` directory locally that you can directly add as a plugin into your Zotero 154 | - It looks like Zotero has been migrating to Electron (or at least there may be plans for this; it's been discussed for the past 4 years). Due to the lack of support for XUL, clear plugin documentation, and the potential deprecation of this version of Zotero, a lot of this codebase was put together by looking at existing plugins that worked in similar ways. If you're trying to write a plugin, I'd recommend poking around these three excellent plugins: 155 | - https://github.com/PubPeerFoundation/pubpeer_zotero_plugin 156 | - https://github.com/jlegewie/zotfile 157 | - https://github.com/bwiernik/zotero-shortdoi 158 | 159 | ## Release 160 | 161 | We use this package: https://github.com/retorquere/zotero-plugin 162 | 163 | Note that it depends on having a `GITHUB_TOKEN` with a `repo` scope available. This is configured in the `CircleCI Project Settings` for this repo. I have a token I issued with scopes from my account; if it expires, you can always change it by generating your own. 164 | 165 | This is how I do a release: 166 | - If you make changes via pull request, do NOT run `npm version` before your pull request gets merged 167 | - First merge in the pull request 168 | - Then from `master`, pull locally to your machine 169 | - While on `master`, run `npm version `, e.g. `npm version 2.0.2` 170 | - This will create a new tag, commit, and push and that will auto-trigger the CI to release it. You should be able to see the new release at https://github.com/scitedotai/scite-zotero-plugin/releases 171 | - I usually manually update the description after the release is created 172 | - Update README, etc. 173 | 174 | If you run `npm version` before the PR gets merged, then the tagged commit will have a hash different from the commit hash in circle after it gets merged (github will always create a new commit for the merge) 175 | 176 | ## How to disable the plugin 177 | 178 | In the event of a bug that gets released, the easiest way to disable the scite plugin is to: 179 | 180 | - Go to `/client/content/config.js` and set the `PLUGIN_ENABLED` flag to `false` 181 | - Merge this into `master` 182 | - Then, from `master` locally, run `npm version ` to release a new version, e.g. if it was on `2.0.1`, run `npm version 2.0.2`. 183 | 184 | ## Questions 185 | 186 | If you have any questions or have feedback, feel free to write to us at hi@scite.ai, or create an issue here. 187 | --------------------------------------------------------------------------------