master 411b4dec3f78 cached
14 files
19.7 KB
5.6k tokens
6 symbols
1 requests
Download .txt
Repository: retorquere/zotero-storage-scanner
Branch: master
Commit: 411b4dec3f78
Files: 14
Total size: 19.7 KB

Directory structure:
gitextract_t_d6xoq3/

├── .eslintignore
├── .eslintrc.json
├── .github/
│   └── workflows/
│       └── release.yml
├── .gitignore
├── README.md
├── chrome.manifest
├── content/
│   ├── zotero-storage-scanner.ts
│   └── zotero-storage-scanner.xul
├── esbuild.js
├── locale/
│   └── en-US/
│       ├── zotero-storage-scanner.dtd
│       └── zotero-storage-scanner.properties
├── package.json
├── skin/
│   └── default/
│       └── overlay.css
└── tsconfig.json

================================================
FILE CONTENTS
================================================

================================================
FILE: .eslintignore
================================================
node_modules
*.d.ts
generator-temp


================================================
FILE: .eslintrc.json
================================================
{
  "root": true,
  "env": {
    "browser": true,
    "es6": true,
    "node": true
  },
  "extends": [
    "eslint:recommended",
    "plugin:@typescript-eslint/recommended",
    "plugin:@typescript-eslint/eslint-recommended",
    "plugin:@typescript-eslint/recommended-requiring-type-checking"
  ],
  "parser": "@typescript-eslint/parser",
  "parserOptions": {
    "project": "tsconfig.json",
    "sourceType": "module"
  },
  "plugins": [
    "eslint-plugin-import",
    "eslint-plugin-prefer-arrow",
    "@typescript-eslint",
    "@typescript-eslint/eslint-plugin"
  ],
  "rules": {
    "@typescript-eslint/adjacent-overload-signatures": "error",
    "@typescript-eslint/array-type": [
      "error",
      {
        "default": "array"
      }
    ],
    "@typescript-eslint/await-thenable": "error",
    "@typescript-eslint/ban-ts-comment": "warn",
    "@typescript-eslint/ban-types": [
      "warn",
      {
        "types": {
          "Object": {
            "message": "Avoid using the `Object` type. Did you mean `object`?"
          },
          "Function": {
            "message": "Avoid using the `Function` type. Prefer a specific function type, like `() => void`."
          },
          "Boolean": {
            "message": "Avoid using the `Boolean` type. Did you mean `boolean`?"
          },
          "Number": {
            "message": "Avoid using the `Number` type. Did you mean `number`?"
          },
          "String": {
            "message": "Avoid using the `String` type. Did you mean `string`?"
          },
          "Symbol": {
            "message": "Avoid using the `Symbol` type.  Did you mean `symbol`?"
          }
        }
      }
    ],
    "@typescript-eslint/consistent-type-assertions": "error",
    "@typescript-eslint/dot-notation": "error",
    "@typescript-eslint/explicit-module-boundary-types": "warn",
    "@typescript-eslint/indent": [
      "error",
      2
    ],
    "@typescript-eslint/member-delimiter-style": [
      "error",
      {
        "multiline": {
          "delimiter": "none",
          "requireLast": false
        },
        "singleline": {
          "delimiter": "comma",
          "requireLast": false
        }
      }
    ],
    "@typescript-eslint/member-ordering": "off",
    "@typescript-eslint/naming-convention": "off",
    "@typescript-eslint/no-array-constructor": "error",
    "@typescript-eslint/no-empty-function": "error",
    "@typescript-eslint/no-empty-interface": "error",
    "@typescript-eslint/no-explicit-any": "off",
    "@typescript-eslint/no-extra-non-null-assertion": "error",
    "@typescript-eslint/no-extra-semi": "error",
    "@typescript-eslint/no-floating-promises": "error",
    "@typescript-eslint/no-for-in-array": "error",
    "@typescript-eslint/no-implied-eval": "off",
    "@typescript-eslint/no-inferrable-types": "error",
    "@typescript-eslint/no-misused-new": "error",
    "@typescript-eslint/no-misused-promises": "error",
    "@typescript-eslint/no-namespace": "error",
    "@typescript-eslint/no-non-null-asserted-optional-chain": "error",
    "@typescript-eslint/no-non-null-assertion": "warn",
    "@typescript-eslint/no-parameter-properties": "off",
    "@typescript-eslint/no-shadow": [
      "error",
      {
        "hoist": "all"
      }
    ],
    "@typescript-eslint/no-this-alias": "error",
    "@typescript-eslint/no-unnecessary-type-assertion": "error",
    "@typescript-eslint/no-unsafe-assignment": "off",
    "@typescript-eslint/no-unsafe-call": "off",
    "@typescript-eslint/no-unsafe-member-access": "off",
    "@typescript-eslint/no-unsafe-return": "error",
    "@typescript-eslint/no-unused-expressions": "error",
    "@typescript-eslint/no-unused-vars": [
      "error",
      {
        "argsIgnorePattern": "^_"
      }
    ],
    "@typescript-eslint/no-use-before-define": "off",
    "@typescript-eslint/no-var-requires": "off",
    "@typescript-eslint/prefer-as-const": "error",
    "@typescript-eslint/prefer-for-of": "error",
    "@typescript-eslint/prefer-function-type": "error",
    "@typescript-eslint/prefer-namespace-keyword": "error",
    "@typescript-eslint/prefer-regexp-exec": "off",
    "@typescript-eslint/quotes": [
      "error",
      "single",
      {
        "avoidEscape": true
      }
    ],
    "@typescript-eslint/require-await": "error",
    "@typescript-eslint/restrict-plus-operands": "error",
    "@typescript-eslint/restrict-template-expressions": "off",
    "@typescript-eslint/semi": [
      "error",
      "never"
    ],
    "@typescript-eslint/triple-slash-reference": [
      "error",
      {
        "path": "always",
        "types": "prefer-import",
        "lib": "always"
      }
    ],
    "@typescript-eslint/unbound-method": "error",
    "@typescript-eslint/unified-signatures": "error",
    "arrow-body-style": "error",
    "arrow-parens": [
      "error",
      "as-needed"
    ],
    "brace-style": [
      "error",
      "stroustrup",
      {
        "allowSingleLine": true
      }
    ],
    "comma-dangle": [
      "error",
      {
        "objects": "always-multiline",
        "arrays": "always-multiline",
        "functions": "never"
      }
    ],
    "complexity": "off",
    "constructor-super": "error",
    "curly": [
      "error",
      "multi-line"
    ],
    "eol-last": "error",
    "eqeqeq": [
      "error",
      "smart"
    ],
    "guard-for-in": "error",
    "id-blacklist": [
      "error",
      "any",
      "Number",
      "number",
      "String",
      "string",
      "Boolean",
      "boolean",
      "Undefined",
      "undefined"
    ],
    "id-match": "error",
    "import/order": "off",
    "linebreak-style": [
      "error",
      "unix"
    ],
    "max-classes-per-file": "off",
    "max-len": [
      "warn",
      {
        "code": 240
      }
    ],
    "new-parens": "off",
    "no-array-constructor": "off",
    "no-bitwise": "error",
    "no-caller": "error",
    "no-cond-assign": "off",
    "no-console": "error",
    "no-debugger": "error",
    "no-empty": [
      "error",
      {
        "allowEmptyCatch": true
      }
    ],
    "no-empty-function": "off",
    "no-eval": "error",
    "no-extra-semi": "off",
    "no-fallthrough": "off",
    "no-implied-eval": "off",
    "no-invalid-this": "off",
    "no-irregular-whitespace": "error",
    "no-magic-numbers": "off",
    "@typescript-eslint/no-magic-numbers": [ "error", {
      "ignore": [ -1, 0, 1, 2 ],
      "ignoreEnums": true
    }],
    "no-new-wrappers": "error",
    "no-redeclare": "error",
    "no-throw-literal": "error",
    "no-trailing-spaces": "error",
    "no-undef-init": "error",
    "no-underscore-dangle": [
      "error",
      {
        "allowAfterThis": true
      }
    ],
    "no-unsafe-finally": "error",
    "no-unused-labels": "error",
    "no-unused-vars": "off",
    "no-var": "error",
    "object-shorthand": "error",
    "one-var": [
      "off",
      "never"
    ],
    "prefer-arrow/prefer-arrow-functions": [
      "error",
      {
        "allowStandaloneDeclarations": true
      }
    ],
    "prefer-const": [
      "error",
      {
        "destructuring": "all"
      }
    ],
    "prefer-object-spread": "error",
    "prefer-template": "error",
    "quote-props": [
      "error",
      "as-needed"
    ],
    "radix": "off",
    "require-await": "off",
    "space-before-function-paren": [
      "error",
      {
        "anonymous": "never",
        "named": "never",
        "asyncArrow": "always"
      }
    ],
    "spaced-comment": [
      "error",
      "always",
      {
        "markers": [
          "/"
        ]
      }
    ],
    "use-isnan": "error",
    "valid-typeof": "off",
    "yoda": "error",
    "@typescript-eslint/consistent-type-definitions": "off",
    "no-new-func": "off"
  },
  "ignorePatterns": [
    "webpack.config.ts",
    "util/*.ts",
    "minitests/*.ts",
    "content/minitests/*.ts"
  ]
}


================================================
FILE: .github/workflows/release.yml
================================================
name: release

on:
  push:

jobs:
  release:
    runs-on: ubuntu-latest
    steps:
    - uses: actions/checkout@v2
    - name: install node
      uses: actions/setup-node@v1
      with:
        node-version: 14.x
    - name: Cache node dependencies
      uses: actions/cache@v2
      env:
        cache-name: cache-dependencies
      with:
        path: |
          ~/.npm
        key: ${{ runner.os }}-build-${{ env.cache-name }}-${{ hashFiles('package-lock.json') }}
        restore-keys: |
          ${{ runner.os }}-build-${{ env.cache-name }}-
          ${{ runner.os }}-build-
          ${{ runner.os }}-
    - name: install node dependencies
      run: npm install
    - name: build
      run: npm run build
    - name: release
      run: npm run release
      env:
        GITHUB_TOKEN: ${{ github.token }}


================================================
FILE: .gitignore
================================================
wiki
gen
build
*~
*.swp
*.debug
*.cache
*.status
*.js.map
*.tmp
*.xpi
node_modules
.env
.eslintcache


================================================
FILE: README.md
================================================
# This plugin has been superceeded by Zotero Attachment Scanner

which can be found at https://github.com/SciImage/zotero-attachment-scanner

## Zotero Storage Scanner

A Zotero plugin to remove the broken & duplicate attachment link of the bibliography

Install by downloading the [latest version](https://github.com/retorquere/zotero-storage-scanner/releases/latest).

This plugin scans your attachments of storage to remove the missing & duplicate attachment link. The combination of the duplicated bibliography in Zotero could create a lot of duplicate attachments. Also when you use a plugin/ third-party software to directly remove the PDF of the bibliography, the attachment link becomes broken in Zotero. This plugin is designed to solve the problem.

Install by downloading the latest version and installing it from the Zotero add-ons screen.

## A little background on how the plugin works

There is no UI, this plugin scans your library after being launched from tools->storage scanner in the background.

If you run zotero-storage-scanner you will see a zotero process (name dependand on OS) kick off in your proccess manager (top, activity monitor, Task Manager), however as it works through your library it live updates two tags `#duplicates` and `#broken` as it goes. If those tags have no items attached to them after some time (variable depending on size of library) then you are golden, if there are entries tagged in either then your likely have duplicate articles or a file/DB has issues been identified with the most likely cause being a "missing" PDF sometimes caused by incomplete syncing.


================================================
FILE: chrome.manifest
================================================
content	zotero-storage-scanner				content/
locale	zotero-storage-scanner	en-US		locale/en-US/
skin	zotero-storage-scanner	default		skin/

overlay	chrome://zotero/content/zoteroPane.xul	chrome://zotero-storage-scanner/content/zotero-storage-scanner.xul


================================================
FILE: content/zotero-storage-scanner.ts
================================================
declare const Zotero: any
// declare const Components: any

const monkey_patch_marker = 'StorageScannerMonkeyPatched'

// eslint-disable-next-line @typescript-eslint/no-unused-vars
function patch(object, method, patcher) {
  if (object[method][monkey_patch_marker]) return
  object[method] = patcher(object[method])
  object[method][monkey_patch_marker] = true
}

class StorageScanner {
  private duplicates: string
  private noattachments: string
  private globals: Record<string, any>

  // eslint-disable-next-line @typescript-eslint/require-await
  public async load(globals: Record<string, any>) {
    this.globals = globals
    if (!this.duplicates) {
      // https://groups.google.com/d/msg/zotero-dev/QYNGxqTSpaQ/uvGObVNlCgAJ
      await Zotero.Schema.schemaUpdatePromise

      let attachmentTypeID
      for (const type of await Zotero.DB.queryAsync("select itemTypeID from itemTypes where typeName = 'attachment'")) {
        attachmentTypeID = type.itemTypeID
      }

      this.duplicates = `
        SELECT
          item.itemID,
          attachment.path,
          CASE attachment.linkMode WHEN ${Zotero.Attachments.LINK_MODE_LINKED_URL} THEN 1 ELSE 0 END AS isLinkedURL,
          COALESCE(duplicates.duplicates, 1) as duplicates
        FROM items item
        LEFT JOIN itemAttachments attachment ON attachment.itemID = item.itemID
        LEFT JOIN (
          SELECT parentItemID, contentType, COUNT(*) as duplicates
          FROM itemAttachments
          WHERE linkMode <> ${Zotero.Attachments.LINK_MODE_LINKED_URL} AND itemID NOT IN (select itemID from deletedItems)
          GROUP BY parentItemID, contentType
        ) duplicates ON attachment.parentItemID = duplicates.parentItemID AND attachment.contentType = duplicates.contentType
        WHERE
            item.itemTypeID = ${attachmentTypeID}
          AND
            item.itemID NOT IN (select itemID from deletedItems)
          AND
            (
                attachment.path IS NULL
              OR
                (attachment.linkMode IS NOT NULL AND attachment.linkMode <> ${Zotero.Attachments.LINK_MODE_LINKED_URL})
            )
      `.replace(/\n/g, ' ').trim()
      Zotero.debug(`StorageScanner: duplicates = ${this.duplicates}`)

      this.noattachments = `
	      SELECT item.itemID, COUNT(attachment.itemID) AS attachments
        FROM items item
        JOIN itemTypes ON item.itemTypeID = itemTypes.itemTypeID AND itemTypes.typeName NOT IN ('note', 'attachment')
        LEFT JOIN itemAttachments attachment ON attachment.parentItemID = item.itemID AND attachment.itemID NOT IN (select itemID from deletedItems)
        WHERE item.itemID NOT IN (select itemID from deletedItems)
        GROUP BY item.itemID

        UNION

        SELECT itemID, CASE WHEN parentItemID IS NULL THEN 0 ELSE 1 END as attachments
        FROM itemAttachments attachment
        WHERE attachment.itemID NOT IN (select itemID from deletedItems)
      `.replace(/\n/g, ' ').trim()
    }
  }

  public async scan() {
    if (!this.duplicates) return

    // Zotero.Attachments.LINK_MODE_LINKED_URL // ignore this
    // Zotero.Attachments.LINK_MODE_IMPORTED_URL // snapshot
    // Zotero.Attachments.LINK_MODE_IMPORTED_FILE
    // LINK_MODE_LINKED_FILE

    const attachments = (await Zotero.DB.queryAsync(this.duplicates)) || [] // apparently 'no results' gets me 'null', not an empty list. Sure, ok.

    Zotero.debug(`StorageScanner.attachments: ${attachments.length}`)

    for (const attachment of attachments) {
      Zotero.debug(`StorageScanner.attachment: ${JSON.stringify({itemID: attachment.itemID, path: attachment.path, duplicates: attachment.duplicates})}`)
      const item = await Zotero.Items.getAsync(attachment.itemID)
      /*
        because getAsync isn't "same as get but asynchronously" but "sort
        of same as get but asynchronously, however if the object was not
        already loaded by some user interaction you're out of luck". BTW,
        if you could know at this point that getAsync would get you a loaded
        object, you could just have called "get". Nice.
        https://groups.google.com/d/msg/zotero-dev/QYNGxqTSpaQ/nGKJakGnBAAJ
        https://groups.google.com/d/msg/zotero-dev/naAxXIbpDhU/iSLpXo-UBQAJ
      */
      await item.loadAllData()

      let save = false

      if (this.updateTag(item, '#broken_attachments', !attachment.isLinkedURL && !(await item.getFilePathAsync()))) save = true
      if (this.updateTag(item, '#multiple_attachments_of_same_type', attachment.duplicates > 1)) save = true
      Zotero.debug(`StorageScanner.save: ${save}`)

      if (save) await item.saveTx()
    }

    const items = (await Zotero.DB.queryAsync(this.noattachments)) || []
    Zotero.debug(`StorageScanner.items: ${items.length}`)
    for (const status of items) {
      const item = await Zotero.Items.getAsync(status.itemID)
      await item.loadAllData()

      let save = false
      if (this.updateTag(item, '#nosource ', !status.attachments)) save = true
      if (save) await item.saveTx()
    }
  }

  private updateTag(item, tag, add) {
    Zotero.debug(`StorageScanner.updateTag('${item.id}.${tag}', ist: ${item.hasTag(tag)}, soll: ${add})`)
    if (add) {
      if (item.hasTag(tag)) return false
      item.addTag(tag)
    }
    else {
      if (!item.hasTag(tag)) return false
      item.removeTag(tag)
    }

    return true
  }
}

Zotero.StorageScanner = new StorageScanner


================================================
FILE: content/zotero-storage-scanner.xul
================================================
<?xml version="1.0"?>
<?xml-stylesheet href="chrome://zotero-storage-scanner/skin/overlay.css" type="text/css"?>
<!DOCTYPE window SYSTEM "chrome://zotero-storage-scanner/locale/zotero-storage-scanner.dtd">

<overlay id="zotero-storage-scanner-overlay" xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
  <script>
    var globals = Function('return this')();
    var zotero_storage_scanner_zoteropane = null;
  </script>
  <stringbundleset>
    <stringbundle id="zotero-storage-scanner-strings" src="chrome://zotero-storage-scanner/locale/zotero-storage-scanner.properties"/>
  </stringbundleset>

  <menupopup id="menu_ToolsPopup">
    <menuitem label="&zotero-storage-scanner.name;" oncommand="Zotero.StorageScanner.scan()"/>
  </menupopup>
	
	<!-- Include the main extension logic -->
  <script src="chrome://zotero-storage-scanner/content/zotero-storage-scanner.js"/>
  <script>
    window.addEventListener('load', async function() {
      try {
        Zotero.debug('zotero-storage-scanner startup')
        await Zotero.StorageScanner.load(globals)
        Zotero.debug('zotero-storage-scanner started')
      } catch (err) {
        Zotero.debug(`zotero-storage-scanner ZoteroPane overlay error: ${err.message}\n${err.stack ||''}`)
      }
    })
  </script>
</overlay>


================================================
FILE: esbuild.js
================================================
const path = require('path')
const fs = require('fs')
const esbuild = require('esbuild')
const rmrf = require('rimraf')

require('zotero-plugin/copy-assets')
require('zotero-plugin/rdf')
require('zotero-plugin/version')

async function build() {
  rmrf.sync('gen')
  await esbuild.build({
    bundle: true,
    format: 'iife',
    target: ['firefox60'],
    entryPoints: [ 'content/zotero-storage-scanner.ts' ],
    outdir: 'build/content',
    banner: { js: 'if (!Zotero.StorageScanner) {\n' },
    footer: { js: '\n}' },
  })
}

build().catch(err => {
  console.log(err)
  process.exit(1)
})


================================================
FILE: locale/en-US/zotero-storage-scanner.dtd
================================================
<!ENTITY zotero-storage-scanner.name "Storage Scanner">


================================================
FILE: locale/en-US/zotero-storage-scanner.properties
================================================


================================================
FILE: package.json
================================================
{
  "name": "zotero-storage-scanner",
  "version": "5.0.12",
  "description": "Scan attachments for duplicates",
  "scripts": {
    "lint": "eslint . --ext .ts --cache --cache-location .eslintcache/",
    "prebuild": "npm run lint",
    "build": "tsc --noEmit && node esbuild.js",
    "postbuild": "zotero-plugin-zipup build zotero-storage-scanner",
    "release": "zotero-plugin-release",
    "postversion": "git push --follow-tags"
  },
  "repository": {
    "type": "git",
    "url": "https://github.com/retorquere/zotero-storage-scanner.git"
  },
  "author": {
    "name": "Emiliano Heyns",
    "email": "emiliano.heyns@iris-advies.com"
  },
  "bugs": {
    "url": "https://github.com/retorquere/zotero-storage-scanner/issues"
  },
  "homepage": "https://github.com/retorquere/zotero-storage-scanner",
  "dependencies": {
    "@types/mocha": "^9.1.1",
    "@typescript-eslint/eslint-plugin": "^5.25.0",
    "@typescript-eslint/parser": "^5.25.0",
    "esbuild": "^0.14.39",
    "eslint": "^8.16.0",
    "eslint-plugin-import": "^2.26.0",
    "eslint-plugin-jsdoc": "^39.3.0",
    "eslint-plugin-prefer-arrow": "^1.2.3",
    "mkdirp": "^1.0.4",
    "rimraf": "^3.0.2",
    "ts-node": "^10.8.0",
    "typescript": "^4.6.4",
    "zotero-plugin": "^1.4.2"
  },
  "xpi": {
    "name": "Storage Scanner for Zotero",
    "updateLink": "https://github.com/retorquere/zotero-storage-scanner/releases/download/v{version}/zotero-storage-scanner-{version}.xpi",
    "releaseURL": "https://github.com/retorquere/zotero-storage-scanner/releases/download/release/"
  }
}


================================================
FILE: skin/default/overlay.css
================================================


================================================
FILE: tsconfig.json
================================================
{
  "compilerOptions": {
    "importHelpers": true,
    "target": "es2017",
    "disableSizeLimit": true,
    "module": "commonjs",
    "noImplicitAny": false,
    "esModuleInterop": true,
    "removeComments": false,
    "preserveConstEnums": false,
    "sourceMap": false,
    "downlevelIteration": true,
    "lib": [ "es2017", "dom" ],
    "typeRoots": [
      "./typings",
      "./node_modules/@types"
    ]
  },
  "include": [
    "content/**/*",
    "resource/**/*",
    "*.ts"
  ],
  "exclude": [
    "node_modules",
    "**/*.spec.ts",
    "typings"
  ]
}
Download .txt
gitextract_t_d6xoq3/

├── .eslintignore
├── .eslintrc.json
├── .github/
│   └── workflows/
│       └── release.yml
├── .gitignore
├── README.md
├── chrome.manifest
├── content/
│   ├── zotero-storage-scanner.ts
│   └── zotero-storage-scanner.xul
├── esbuild.js
├── locale/
│   └── en-US/
│       ├── zotero-storage-scanner.dtd
│       └── zotero-storage-scanner.properties
├── package.json
├── skin/
│   └── default/
│       └── overlay.css
└── tsconfig.json
Download .txt
SYMBOL INDEX (6 symbols across 2 files)

FILE: content/zotero-storage-scanner.ts
  function patch (line 7) | function patch(object, method, patcher) {
  class StorageScanner (line 13) | class StorageScanner {
    method load (line 19) | public async load(globals: Record<string, any>) {
    method scan (line 74) | public async scan() {
    method updateTag (line 121) | private updateTag(item, tag, add) {

FILE: esbuild.js
  function build (line 10) | async function build() {
Condensed preview — 14 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (22K chars).
[
  {
    "path": ".eslintignore",
    "chars": 35,
    "preview": "node_modules\n*.d.ts\ngenerator-temp\n"
  },
  {
    "path": ".eslintrc.json",
    "chars": 7857,
    "preview": "{\n  \"root\": true,\n  \"env\": {\n    \"browser\": true,\n    \"es6\": true,\n    \"node\": true\n  },\n  \"extends\": [\n    \"eslint:reco"
  },
  {
    "path": ".github/workflows/release.yml",
    "chars": 815,
    "preview": "name: release\n\non:\n  push:\n\njobs:\n  release:\n    runs-on: ubuntu-latest\n    steps:\n    - uses: actions/checkout@v2\n    -"
  },
  {
    "path": ".gitignore",
    "chars": 101,
    "preview": "wiki\ngen\nbuild\n*~\n*.swp\n*.debug\n*.cache\n*.status\n*.js.map\n*.tmp\n*.xpi\nnode_modules\n.env\n.eslintcache\n"
  },
  {
    "path": "README.md",
    "chars": 1613,
    "preview": "# This plugin has been superceeded by Zotero Attachment Scanner\n\nwhich can be found at https://github.com/SciImage/zoter"
  },
  {
    "path": "chrome.manifest",
    "chars": 252,
    "preview": "content\tzotero-storage-scanner\t\t\t\tcontent/\nlocale\tzotero-storage-scanner\ten-US\t\tlocale/en-US/\nskin\tzotero-storage-scanne"
  },
  {
    "path": "content/zotero-storage-scanner.ts",
    "chars": 5424,
    "preview": "declare const Zotero: any\n// declare const Components: any\n\nconst monkey_patch_marker = 'StorageScannerMonkeyPatched'\n\n/"
  },
  {
    "path": "content/zotero-storage-scanner.xul",
    "chars": 1297,
    "preview": "<?xml version=\"1.0\"?>\n<?xml-stylesheet href=\"chrome://zotero-storage-scanner/skin/overlay.css\" type=\"text/css\"?>\n<!DOCTY"
  },
  {
    "path": "esbuild.js",
    "chars": 594,
    "preview": "const path = require('path')\nconst fs = require('fs')\nconst esbuild = require('esbuild')\nconst rmrf = require('rimraf')\n"
  },
  {
    "path": "locale/en-US/zotero-storage-scanner.dtd",
    "chars": 56,
    "preview": "<!ENTITY zotero-storage-scanner.name \"Storage Scanner\">\n"
  },
  {
    "path": "locale/en-US/zotero-storage-scanner.properties",
    "chars": 0,
    "preview": ""
  },
  {
    "path": "package.json",
    "chars": 1560,
    "preview": "{\n  \"name\": \"zotero-storage-scanner\",\n  \"version\": \"5.0.12\",\n  \"description\": \"Scan attachments for duplicates\",\n  \"scri"
  },
  {
    "path": "skin/default/overlay.css",
    "chars": 0,
    "preview": ""
  },
  {
    "path": "tsconfig.json",
    "chars": 565,
    "preview": "{\n  \"compilerOptions\": {\n    \"importHelpers\": true,\n    \"target\": \"es2017\",\n    \"disableSizeLimit\": true,\n    \"module\": "
  }
]

About this extraction

This page contains the full source code of the retorquere/zotero-storage-scanner GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 14 files (19.7 KB), approximately 5.6k tokens, and a symbol index with 6 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!