├── .editorconfig ├── .gitattributes ├── .github ├── SECURITY.md └── workflows │ ├── bad-link-reporter.yml │ ├── markdown-linter.yml │ └── yaml-linter.yml ├── .gitignore ├── .jsonlintrc.json ├── .markdownlint.json ├── .yamllint.yml ├── CITATION.cff ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── SUPPORT.md ├── codemeta.json ├── devonthink-config-scripts ├── README.md └── print-hidden-preferences │ ├── README.md │ └── print-devonthink-preferences ├── devonthink-smart-rule-scripts ├── Makefile ├── auto-convert-web-page-to-PDF │ ├── .graphics │ │ └── auto-convert-smart-rule.png │ ├── Auto convert web page to PDF.applescript │ ├── README.md │ └── bookmarklet.js ├── copy-metadata-to-annotation-docs │ ├── Copy metadata to annotation docs.applescript │ └── Makefile ├── create-image-file │ ├── Create image file for transclusion.applescript │ ├── Makefile │ └── Test image file exists.applescript ├── pause │ ├── Makefile │ ├── Pause 10 seconds.applescript │ └── Pause 5 seconds.applescript ├── remove-pdf-keywords │ ├── .graphics │ │ ├── smart-rule-screenshot.png │ │ └── zotfile.png │ ├── Makefile │ ├── README.md │ └── Remove PDF keywords.applescript ├── replace-custom-placeholders │ ├── Makefile │ └── Replace custom placeholders in document.applescript ├── send-url-to-internet-archive │ ├── Makefile │ └── Send URL to Internet Archive.applescript ├── set-annotation-url │ ├── Makefile │ └── Set annotation document URL.applescript ├── unset-common-zotero-field-values │ ├── Makefile │ └── Unset common Zotero field values.applescript ├── write-uri-into-comments │ ├── Makefile │ ├── README.md │ ├── Write URI into Finder comment using Urial.applescript │ └── smart-rule-screenshot.png ├── zoinks-scripts │ ├── Add Zotero abstract.applescript │ ├── Add Zotero citekey.applescript │ ├── Makefile │ ├── README.md │ └── t.py ├── zotero-scripts │ ├── Makefile │ ├── Set reference field using BBT.applescript │ ├── Start Zotero + BBT if needed.applescript │ └── tests │ │ ├── test-framework-failure.applescript │ │ ├── test-ping.applescript │ │ ├── test-without-framework.applescript │ │ └── testapi.applescript └── zowie-scripts │ ├── Makefile │ ├── README.md │ └── Run Zowie on newly indexed PDF.applescript ├── devonthink-templates ├── Annotations.noindex │ └── Reading notes.md ├── README.md ├── Templates.noindex │ ├── .gitignore │ ├── Brief note.ooutline │ ├── Card.md │ ├── Clipboard to markdown.md │ ├── Code.md │ ├── Diary.ooutline │ ├── Empty markdown.md │ ├── Glossary.md │ ├── Goal plan.ooutline │ ├── Markdown.md │ ├── Meeting.ooutline │ ├── Notes.ooutline │ ├── Quote.md │ ├── Records markdown.md │ ├── Spreadsheet.numbers │ └── Term definition.md └── makelinks.sh ├── devonthink-toolbar-scripts ├── .gitignore ├── Makefile ├── copy-location-paths-of-selected-items │ ├── Copy location paths of selected items.applescript │ ├── Makefile │ ├── README.md │ └── icon.svg ├── copy-markdown-links-to-selected-items │ ├── Copy Markdown links to selected items.applescript │ ├── Makefile │ ├── README.md │ └── icon.svg ├── copy-paths-to-files-on-disk │ ├── Copy disk paths of selected files.applescript │ └── Makefile ├── create-document-from-template │ ├── .graphics │ │ └── km-shortcut-screenshot.png │ ├── Create document from template.applescript │ ├── Makefile │ └── README.md ├── open-in-new-window │ ├── Makefile │ ├── Open in new window.applescript │ ├── README.md │ └── icon.svg ├── set-icon-to-parent-group-icon │ ├── .graphics │ │ ├── folder-icons-dark.png │ │ └── folder-icons-light.png │ ├── Makefile │ ├── README.md │ ├── Set icon to parent group icon.applescript │ └── icon.svg ├── switch-workspace │ ├── Makefile │ ├── README.md │ └── Switch workspace.applescript └── unset-field-values │ ├── Makefile │ ├── README.md │ ├── Unset Aliases.applescript │ ├── Unset Finder comment.applescript │ ├── Unset URL.applescript │ └── Unset common Zotero field values.applescript ├── finder-folder-actions ├── Makefile └── import-to-devonthink │ ├── Import to DEVONthink with selector.applescript │ ├── Makefile │ └── README.md ├── global-scripts ├── Makefile ├── README.md ├── create-markdown-note-for-quote │ ├── Create note in DEVONthink for code.applescript │ ├── Create note in DEVONthink for quote.applescript │ └── Makefile └── open-url-in-devonthink │ ├── Makefile │ ├── Open current browser URL in DEVONthink.applescript │ └── README.md └── obsolete-no-longer-used ├── README.md ├── insert-tags-into-markdown ├── README.md └── insert-tags-into-markdown.applescript └── open-annotation-file ├── README.md ├── annotations-drop-down.png └── open-annotation-file.applescript /.editorconfig: -------------------------------------------------------------------------------- 1 | # Summary: EditorConfig file for this project. -*- conf -*- 2 | # 3 | # For more information, see https://EditorConfig.org 4 | # 5 | # Copyright 2024 Michael Hucka. 6 | # License: MIT License – see file "LICENSE" in the project website. 7 | # Website: https://github.com/mhucka/urial 8 | 9 | root = true 10 | 11 | [*] 12 | charset = utf-8 13 | end_of_line = lf 14 | indent_size = 4 15 | indent_style = space 16 | insert_final_newline = true 17 | max_line_length = 90 18 | tab_width = 4 19 | trim_trailing_whitespace = true 20 | 21 | [*.cfg] 22 | indent_size = 2 23 | 24 | [*.json] 25 | indent_size = 2 26 | 27 | [*.{yml, yaml}] 28 | indent_size = 2 29 | 30 | # Shell scripts on Windows. 31 | [*.{cmd, bat}] 32 | end_of_line = crlf 33 | 34 | [Makefile, makefile] 35 | indent_size = 4 36 | indent_style = tab 37 | tab_width = 8 38 | 39 | [.applescript] 40 | indent_size = 4 41 | indent_style = tab 42 | tab_width = 4 43 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # Summary: repository-specific file attributes assignments for git. 2 | # 3 | # Copyright 2024 Michael Hucka. 4 | # License: MIT License – see file "LICENSE" in the project website. 5 | # Website: https://github.com/mhucka/devonthink-hacks 6 | 7 | # Set default interpretation of line endings ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 8 | 9 | * text=auto 10 | 11 | # Interpretation of common text files ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 12 | 13 | *.bat text eol=crlf 14 | *.cff text 15 | *.cfg text 16 | *.css text diff=css 17 | *.env text 18 | *.html text diff=html 19 | *.ini text 20 | *.ipynb text eol=lf 21 | *.js text 22 | *.json text 23 | *.md text diff=markdown 24 | *.py text diff=python 25 | *.rst text 26 | *.sh text 27 | *.sql text 28 | *.svg text 29 | *.toml text 30 | *.tex text diff=tex 31 | *.txt text 32 | *.yaml text merge=yaml 33 | *.yml text merge=yaml 34 | 35 | # RC files like .babelrc, .eslintrc, etc. 36 | *.*rc text 37 | 38 | # This avoids compatibility issues between Windows and Mac. 39 | *.csv text eol=crlf 40 | 41 | LICENSE text 42 | Makefile text 43 | 44 | *.*ignore text 45 | *.gitattributes text 46 | .gitconfig text 47 | 48 | # Interpretation of common binary files ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 49 | 50 | *.bz binary 51 | *.DOC binary 52 | *.doc binary 53 | *.DOCX binary 54 | *.docx binary 55 | *.DOT binary 56 | *.dot binary 57 | *.gz binary 58 | *.jpeg binary 59 | *.jpg binary 60 | *.PDF binary 61 | *.pdf binary 62 | *.RTF binary 63 | *.rtf binary 64 | *.tar binary 65 | *.tgz binary 66 | *.tif binary 67 | *.tiff binary 68 | *.xls binary 69 | *.zip binary 70 | 71 | # Interpretation of Mac-specific files ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 72 | 73 | *.shortcut binary 74 | -------------------------------------------------------------------------------- /.github/SECURITY.md: -------------------------------------------------------------------------------- 1 | # Reporting security issues 2 | 3 | Please report security issues using the [issue tracker](https://github.com/mhucka/template/issues) for this repository. 4 | -------------------------------------------------------------------------------- /.github/workflows/bad-link-reporter.yml: -------------------------------------------------------------------------------- 1 | # GitHub Actions workflow for Baler (BAd Link reportER) version 2.0.2. 2 | # This is available as the file "sample-workflow.yml" from the source 3 | # code repository for Baler: https://github.com/caltechlibrary/baler 4 | 5 | name: Bad Link Reporter 6 | 7 | # Configure this section ───────────────────────────────────────────── 8 | 9 | env: 10 | # Files to check. (Put patterns on separate lines, no leading dash.) 11 | files: | 12 | **/*.md 13 | 14 | # Label assigned to issues created by this workflow: 15 | labels: bug 16 | 17 | # Number of previous issues to check for duplicate reports. 18 | lookback: 10 19 | 20 | # Time (sec) to wait on an unresponsive URL before trying once more. 21 | timeout: 15 22 | 23 | # Optional file containing a list of URLs to ignore, one per line: 24 | ignore: .github/workflows/ignored-urls.txt 25 | 26 | on: 27 | schedule: # Cron syntax is: "min hr day-of-month month day-of-week" 28 | - cron: 00 04 * * 1 29 | push: 30 | paths: ['**.md'] 31 | workflow_dispatch: 32 | 33 | # The rest of this file should be left as-is ───────────────────────── 34 | 35 | run-name: Test links in Markdown files 36 | jobs: 37 | Baler: 38 | name: Link checker and reporter 39 | runs-on: ubuntu-latest 40 | permissions: 41 | issues: write 42 | steps: 43 | - uses: caltechlibrary/baler@v2 44 | with: 45 | files: ${{github.event.inputs.files || env.files}} 46 | labels: ${{github.event.inputs.labels || env.labels}} 47 | ignore: ${{github.event.inputs.ignore || env.ignore}} 48 | timeout: ${{github.event.inputs.timeout || env.timeout}} 49 | lookback: ${{github.event.inputs.lookback || env.lookback}} 50 | -------------------------------------------------------------------------------- /.github/workflows/markdown-linter.yml: -------------------------------------------------------------------------------- 1 | # Summary: GitHub Actions workflow to run a Markdown linter on .md files. 2 | # 3 | # Copyright 2024 California Institute of Technology. 4 | # License: Modified BSD 3-clause – see file "LICENSE" in the project website. 5 | # Website: https://github.com/caltechlibrary/devonthink-hacks 6 | 7 | name: Markdown file linter 8 | run-name: Lint Markdown files after ${{github.event_name}} by ${{github.actor}} 9 | 10 | on: 11 | push: 12 | branches: 13 | - main 14 | paths: 15 | - '*.md' 16 | pull_request: 17 | branches: 18 | - main 19 | paths: 20 | - '*.md' 21 | workflow_dispatch: 22 | paths: 23 | - '*.md' 24 | 25 | jobs: 26 | lint: 27 | name: Run Markdown linter 28 | runs-on: ubuntu-latest 29 | steps: 30 | - name: Check out copy of git repository. 31 | uses: actions/checkout@v4 32 | 33 | - name: Run Markdownlint on .md files. 34 | uses: DavidAnson/markdownlint-cli2-action@v15 35 | with: 36 | config: .markdownlint.json 37 | globs: | 38 | *.md 39 | -------------------------------------------------------------------------------- /.github/workflows/yaml-linter.yml: -------------------------------------------------------------------------------- 1 | # Summary: GitHub Actions workflow to run a YAML linter on .yml files. 2 | # 3 | # Copyright 2024 California Institute of Technology. 4 | # License: Modified BSD 3-clause – see file "LICENSE" in the project website. 5 | # Website: https://github.com/caltechlibrary/devonthink-hacks 6 | 7 | name: YAML file linter 8 | 9 | on: 10 | pull_request: 11 | types: [opened, synchronize] 12 | paths: 13 | - '**.yml' 14 | - '**.yaml' 15 | push: 16 | branches: 17 | - main 18 | paths: 19 | - '**.yml' 20 | - '**.yaml' 21 | 22 | run-name: Run linter on YAML files 23 | jobs: 24 | Yamllint: 25 | name: GitHub YAMLlint 26 | runs-on: ubuntu-latest 27 | steps: 28 | - name: Check out copy of git repository 29 | uses: actions/checkout@v4 30 | 31 | - name: Run YAMLlint 32 | uses: ibiqlik/action-yamllint@v3.1.1 33 | with: 34 | config_file: .yamllint.yml 35 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Summary: rules for files and subdirectories to be ignored by git. 2 | # 3 | # Copyright 2024 Michael Hucka. 4 | # License: MIT License – see file "LICENSE" in the project website. 5 | # Website: https://github.com/mhucka/devonthink-hacks 6 | # 7 | # Note: the recommended approach for using this file is to add only project- 8 | # specific rules to the .gitignore of a repository. Each user should put rules 9 | # that apply to their tools and ways of working into a global git ignore file 10 | # using (e.g.) "git config --global core.excludesfile ~/.gitignore_global". 11 | # The rationale for this is that a number of file types, such as Emacs 12 | # checkpoint and backup files, are things that are not specific to a given 13 | # project; rather, Emacs creates them everywhere, in all projects, because 14 | # they're a byproduct of how Emacs works. Thus, rules to ignore them belong 15 | # in a user's own global .gitignore file, not in a project's .gitignore. 16 | # A useful starting point for global .gitignore file contents can be found 17 | # at https://github.com/github/gitignore/tree/main/Global (as of 2022-07-14) 18 | 19 | # AppleScript-specific things to ignore 20 | # ............................................................................. 21 | 22 | *.scpt 23 | 24 | # Python-specific things to ignore 25 | # ............................................................................. 26 | 27 | __pycache__/ 28 | *.py[cod] 29 | *$py.class 30 | *.egg-info/ 31 | .eggs/ 32 | .pytest_cache 33 | .coverage 34 | 35 | 36 | # Project-specific things to ignore: 37 | # ............................................................................. 38 | 39 | build 40 | dist 41 | write-uri-into-comments/*.scpt 42 | zowie-scripts/*.scpt 43 | set-icon-to-parent-icon/*.scpt 44 | unset-url/*.scpt 45 | unset-finder-comment/*.scpt 46 | zoinks-scripts/*.scpt 47 | create-document-from-template/*.scpt 48 | switch-workspace/*.scpt 49 | create-markdown-note-for-quote/*.scpt 50 | -------------------------------------------------------------------------------- /.jsonlintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "comments": false, 3 | "trailing-commas": false, 4 | "duplicate-keys": false, 5 | "log-files": false, 6 | "compact": true, 7 | "continue": true, 8 | "patterns": ["**/*.json"] 9 | } 10 | -------------------------------------------------------------------------------- /.markdownlint.json: -------------------------------------------------------------------------------- 1 | { 2 | "blank_lines": { 3 | "maximum": 2 4 | }, 5 | "html": { 6 | "allowed_elements": [ 7 | "a", 8 | "b", 9 | "br", 10 | "code", 11 | "details", 12 | "div", 13 | "em", 14 | "i", 15 | "img", 16 | "ins", 17 | "kbd", 18 | "p", 19 | "span", 20 | "sup", 21 | "summary" 22 | ] 23 | }, 24 | "line-length": { 25 | "line_length": 10000 26 | }, 27 | "no-alt-text": true, 28 | "no-duplicate-heading": { 29 | "allow_different_nesting": true 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /.yamllint.yml: -------------------------------------------------------------------------------- 1 | # Summary: configuration file for .github/workflows/yaml-linter.yml. 2 | # 3 | # Copyright 2024 California Institute of Technology. 4 | # License: Modified BSD 3-clause – see file "LICENSE" in the project website. 5 | # Website: https://github.com/caltechlibrary/baler 6 | 7 | rules: 8 | colons: 9 | max-spaces-after: -1 10 | quoted-strings: 11 | required: only-when-needed 12 | document-start: 13 | present: false 14 | document-end: 15 | present: false 16 | -------------------------------------------------------------------------------- /CITATION.cff: -------------------------------------------------------------------------------- 1 | cff-version: 1.2.0 2 | message: "If you use this software, please cite it using the metadata from this file." 3 | authors: 4 | - family-names: Hucka 5 | given-names: Michael 6 | email: mhucka@caltech.edu 7 | orcid: https://orcid.org/0000-0001-9105-5960 8 | title: Mike's DEVONthink hacks 9 | abstract: Scripts and programs I use with DEVONthink 10 | date-released: 2024-01-17 11 | repository-code: "https://github.com/mhucka/devonthink-hacks" 12 | license: MIT 13 | type: software 14 | keywords: 15 | - software 16 | - DEVONthink 17 | - personal information management 18 | - AppleScript 19 | - Python 20 | - macOS 21 | - automation 22 | - scripting 23 | - archiving 24 | - PDF 25 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and orientation. 6 | 7 | ## Our Standards 8 | 9 | Examples of behavior that contributes to creating a positive environment include: 10 | 11 | * Using welcoming and inclusive language 12 | * Being respectful of differing viewpoints and experiences 13 | * Gracefully accepting constructive criticism 14 | * Focusing on what is best for the community 15 | * Showing empathy towards other community members 16 | 17 | Examples of unacceptable behavior by participants include: 18 | 19 | * The use of sexualized language or imagery and unwelcome sexual attention or advances 20 | * Trolling, insulting/derogatory comments, and personal or political attacks 21 | * Public or private harassment 22 | * Publishing others' private information, such as a physical or electronic address, without explicit permission 23 | * Other conduct which could reasonably be considered inappropriate in a professional setting 24 | 25 | ## Our Responsibilities 26 | 27 | Project contributors are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior. 28 | 29 | Project contributors have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful. 30 | 31 | ## Scope 32 | 33 | This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project contributors. 34 | 35 | ## Enforcement 36 | 37 | If a contributor engages in harassing behaviour, the project organizer(s) may take any action they deem appropriate, including warning the offender or expelling them from online forums, online project resources, face-to-face meetings, or any other project-related activity or resource. 38 | 39 | If you are being harassed, notice that someone else is being harassed, or have any other concerns, please contact a member of the project team immediately. Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team. All complaints will be reviewed and investigated and will result in a response that is deemed necessary and appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately. 40 | 41 | ## Attribution 42 | 43 | Portions of this Code of Conduct were adapted from Electron's [Contributor Covenant Code of Conduct](https://github.com/electron/electron/blob/main/CODE_OF_CONDUCT.md), which itself was adapted from the [Contributor Covenant](http://contributor-covenant.org/version/1/4), version 1.4. 44 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Guidelines for contributing to this project 2 | 3 | Any constructive contributions – bug reports, pull requests (code or documentation), suggestions for improvements, and more – are welcome. 4 | 5 | ## Conduct 6 | 7 | Everyone is asked to read and respect the [code of conduct](CODE_OF_CONDUCT.md) before participating in this project. 8 | 9 | ## Coordinating work 10 | 11 | A quick way to find out what is currently in the near-term plans for this project is to look at the [GitHub issue tracker](https://github.com/mhucka/devonthink-hacks/issues), but the possibilities are not limited to what you see there – if you have ideas for new features and enhancements, please feel free to write them up as a new issue or contact the developers directly! 12 | 13 | ## Submitting contributions 14 | 15 | Please feel free to contact the author directly, or even better, jump right in and use the standard GitHub approach of forking the repo and creating a pull request. When committing code changes and submitting pull requests, please write a clear log message for your commits. 16 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2024 by Michael Hucka. 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 4 | 5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 6 | 7 | THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 8 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Mike's DEVONthink Hacks 2 | 3 | These are scripts and programs I developed to work with [DEVONthink](https://www.devontechnologies.com/apps/devonthink), a powerful personal database and information management system. 4 | 5 | [![License](https://img.shields.io/badge/MIT-purple.svg?style=flat-square)](https://github.com/mhucka/devonthink-hacks/blob/main/LICENSE) 6 | [![DEVONthink 3](https://img.shields.io/badge/DEVONthink%203-blue.svg?style=flat-square)](https://www.devontechnologies.com/apps/devonthink) 7 | [![AppleScript](https://img.shields.io/badge/AppleScript-green.svg?style=flat-square)](https://developer.apple.com/library/archive/documentation/AppleScript/Conceptual/AppleScriptLangGuide/introduction/ASLR_intro.html) 8 | 9 | 10 | ## Table of contents 11 | 12 | * [Introduction](#introduction) 13 | * [Installation and usage](#installation-and-usage) 14 | * [Known issues and limitations](#known-issues-and-limitations) 15 | * [Getting help](#getting-help) 16 | * [Contributing](#contributing) 17 | * [License](#license) 18 | 19 | 20 | ## Introduction 21 | 22 | In the process of using [DEVONthink](https://www.devontechnologies.com/apps/devonthink) more fully, I've been developing scripts to automate various procedures. This repository contains the results. 23 | 24 | 25 | ## Installation and usage 26 | 27 | There is no one-shot installation procedure nor a single usage procedure for all of these hacks. Please check each subdirectory separately; there is a README file that explains what needs to be done for each case. 28 | 29 | 30 | ## Known issues and limitations 31 | 32 | The solutions and code in this repository were developed over a period of years spanning multiple releases of [DEVONthink](https://www.devontechnologies.com/apps/devonthink) in my sometimes idiosyncratic macOS environment. While they work for me (or did at the time I put them here), it is possible they will not work in your environment or in the version of DEVONthink that you are using. 33 | 34 | 35 | ## Getting help 36 | 37 | If you find an issue, please submit it in [the GitHub issue tracker](https://github.com/mhucka/devonthink-hacks/issues) for this repository. 38 | 39 | 40 | ## Contributing 41 | 42 | I would be happy to receive your help and participation with enhancing the solutions and code contained here. Please visit the [guidelines for contributing](CONTRIBUTING.md) for some tips on getting started. 43 | 44 | 45 | ## License 46 | 47 | Unless otherwise noted, the software in this repository is licensed under the [MIT](https://github.com/mhucka/devonthink-hacks/blob/main/LICENSE) license. 48 | 49 | 50 | ## Do you like it? 51 | 52 | If you like this software, don't forget to give this repo a star on GitHub to show your support! 53 | -------------------------------------------------------------------------------- /SUPPORT.md: -------------------------------------------------------------------------------- 1 | Support 2 | ======= 3 | 4 | Thank you for your interest in this project. If you are experiencing problems or have questions, the following are the preferred methods of reaching someone: 5 | 6 | 1. Report a new issue using the [issue tracker](https://github.com/mhucka/devonthink-hacks/issues). 7 | 2. Send email to the author: [mhucka@caltech.edu](mailto:mhucka@caltech.edu). 8 | 3. Send email to an individual involved in the project. People's names appear in the top-level `README.md` file in the source code repository. 9 | -------------------------------------------------------------------------------- /codemeta.json: -------------------------------------------------------------------------------- 1 | { 2 | "@context": "https://doi.org/10.5063/schema/codemeta-2.0", 3 | "@type": "SoftwareSourceCode", 4 | "name": "DEVONthink Hacks", 5 | "identifier": "devonthink-hacks", 6 | "description": "Random hacks for working with DEVONthink", 7 | "datePublished": "2024-01-17", 8 | "dateCreated": "2023-12-11", 9 | "author": [ 10 | { 11 | "@type": "Person", 12 | "givenName": "Michael", 13 | "familyName": "Hucka", 14 | "affiliation": { 15 | "@type": "Organization", 16 | "name": "California Institute of Technology Library" 17 | }, 18 | "email": "mhucka@caltech.edu", 19 | "@id": "https://orcid.org/0000-0001-9105-5960" 20 | } 21 | ], 22 | "maintainer": [ 23 | { 24 | "@type": "Person", 25 | "givenName": "Michael", 26 | "familyName": "Hucka", 27 | "affiliation": { 28 | "@type": "Organization", 29 | "name": "California Institute of Technology Library" 30 | }, 31 | "email": "mhucka@caltech.edu", 32 | "@id": "https://orcid.org/0000-0001-9105-5960" 33 | } 34 | ], 35 | "copyrightHolder": [ 36 | { 37 | "@type": "Person", 38 | "givenName": "Michael", 39 | "familyName": "Hucka", 40 | "affiliation": { 41 | "@type": "Organization", 42 | "name": "California Institute of Technology Library" 43 | }, 44 | "email": "mhucka@caltech.edu", 45 | "@id": "https://orcid.org/0000-0001-9105-5960" 46 | } 47 | ], 48 | "copyrightYear": 2024, 49 | "license": "https://github.com/mhucka/devonthink-hacks/blob/main/LICENSE", 50 | "isAccessibleForFree": true, 51 | "codeRepository": "https://github.com/mhucka/devonthink-hacks", 52 | "readme": "https://github.com/mhucka/devonthink-hacks/blob/main/README.md", 53 | "issueTracker": "https://github.com/mhucka/devonthink-hacks/issues", 54 | "downloadUrl": "https://github.com/mhucka/devonthink-hacks/archive/main.zip", 55 | "keywords": [ 56 | "software", 57 | "command line", 58 | "macOS", 59 | "shell", 60 | "AppleScript", 61 | "Bash" 62 | ], 63 | "programmingLanguage": [ 64 | { 65 | "@type": "ComputerLanguage", 66 | "name": "AppleScript", 67 | "version": "2.8", 68 | "url": "https://developer.apple.com/library/archive/documentation/AppleScript/Conceptual/AppleScriptLangGuide/" 69 | } 70 | ], 71 | "developmentStatus": "active" 72 | } 73 | -------------------------------------------------------------------------------- /devonthink-config-scripts/README.md: -------------------------------------------------------------------------------- 1 | # Config scripts 2 | 3 | In this directory are things I use to configure DEVONthink or do other configuration-related activities. 4 | -------------------------------------------------------------------------------- /devonthink-config-scripts/print-hidden-preferences/README.md: -------------------------------------------------------------------------------- 1 | # Config hidden preferences 2 | 3 | This is a simple shell script I use to both print and set DEVONthink hidden preferences, for installing DEVONthink on a new machine. The script is designed to output the values in a way that makes it possible to take the output and run it as a shell script to set the values. Example: 4 | 5 | ```sh 6 | # Get the values on one machine 7 | ./print-devonthink-preferences > prefs.sh 8 | 9 | # Copy prefs.sh to another machine 10 | # Set the values on the other machine 11 | sh prefs.sh 12 | ``` 13 | -------------------------------------------------------------------------------- /devonthink-config-scripts/print-hidden-preferences/print-devonthink-preferences: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | # Summary: print the current values of all DEVONthink hidden preferences. 3 | # 4 | # Warning: this contains an explicit list of hidden preferences that were 5 | # documented as of version 3.9.4 of DEVONthink in the DEVONthink user manual. 6 | # The list needs to be kept up-to-date manually. 7 | # 8 | # Copyright 2024 Michael Hucka. 9 | # License: MIT License – see file "LICENSE" in the project website. 10 | # Website: https://github.com/mhucka/devonthink-hacks 11 | 12 | year=$(date +%Y) 13 | key="com.devon-technologies.think3" 14 | 15 | print_value() { 16 | value=$(defaults read $key $1 2>&1 /dev/null | egrep -v ^$year) 17 | if [[ $value =~ 'does not exist' ]] ; then 18 | echo \# $1 not set 19 | else 20 | type=$(defaults read-type $key $1 2>&1 /dev/null | egrep -v ^$year) 21 | if [[ $type =~ 'string' ]]; then 22 | echo defaults write $key $1 -string $value 23 | elif [[ $type =~ 'boolean' ]]; then 24 | if [[ $value -eq 1 ]] ; then 25 | echo defaults write $key $1 -bool TRUE 26 | else 27 | echo defaults write $key $1 -bool FALSE 28 | fi 29 | else 30 | echo defaults write $key value is $value 31 | fi 32 | fi 33 | } 34 | 35 | print_value AVSkippingInterval 36 | print_value AdditionalPlainTextExtensions 37 | print_value AdditionalXMLExtensions 38 | print_value BatesNumberDigits 39 | print_value CounterDigits 40 | print_value DisableActivityWindow 41 | print_value DisableAutomaticDeconsolidation 42 | print_value DisableAutomaticUpdatingOfIndexedItems 43 | print_value DisableBadgeLabel 44 | print_value DisableFileCoordination 45 | print_value DisableFileSystemEvents 46 | print_value DisableFinderTags 47 | print_value DisableHighlightColorMapping 48 | print_value DisablePDFValidation 49 | print_value DisablePreprocessedClipping 50 | print_value DisableRelativeDates 51 | print_value DisableTagAutocompletion 52 | print_value DisplayGroupsInPreviewPane 53 | print_value DontAutomaticallyEnableOperatorsOptionOfSearchInspector 54 | print_value EnableApplicationFiles 55 | print_value EnableAutomaticConsolidation 56 | print_value EnableEvernoteRTFDImport 57 | print_value EnableFSEventLogging 58 | print_value EnableSearchFieldAutocompletion 59 | print_value ForceEditablePDFs 60 | print_value IndexRawMarkdownSource 61 | print_value MaximumNumberOfRecentDestinations 62 | print_value MaximumNumberOfRecentSearches 63 | print_value MonospacedSidebarFont 64 | print_value MonospacedViewFont 65 | print_value PersistentSortingOfSearchResults 66 | print_value PlainTextIsMarkdown 67 | print_value RawMarkdownPasting 68 | print_value RawOPMLImport 69 | print_value RichNotesWithoutAttachments 70 | print_value ShowAdditionalInfoInPathBar 71 | print_value SyncDebugLog 72 | print_value UsePDFDocumentDates 73 | print_value WindowToolbarStyleExpanded 74 | -------------------------------------------------------------------------------- /devonthink-smart-rule-scripts/Makefile: -------------------------------------------------------------------------------- 1 | # Summary: my generic Makefile for compiling AppleScript files. 2 | # 3 | # Copyright 2024 Michael Hucka. 4 | # License: MIT License – see file "LICENSE" in the project website. 5 | # Website: https://github.com/mhucka/devonthink-hacks 6 | 7 | # Preliminary settings and tests ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 8 | 9 | SHELL=/bin/bash 10 | .ONESHELL: # Run all commands in the same shell. 11 | .SHELLFLAGS += -e # Exit at the first error. 12 | 13 | thisdir := $(shell basename $(CURDIR)) 14 | 15 | # When I run M-x compile using this Makefile, the compile target works but the 16 | # install target fails. It works outside Emacs in a regular shell terminal. I 17 | # haven't figured out the reason, so for now, this test reminds me to avoid it. 18 | 19 | ifeq ($(origin INSIDE_EMACS),environment) 20 | $(error "Do not run make from inside Emacs with this Makefile.") 21 | endif 22 | 23 | 24 | # Print help if no command is given ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 25 | 26 | # The help scheme works by looking for lines that begin with "#:" above the 27 | # definitions of commands. Originally based on code posted to Stack Overflow on 28 | # 2019-11-28 by Richard Kiefer at https://stackoverflow.com/a/59087509/743730 29 | 30 | #: Print a summary of available commands. 31 | help: 32 | @echo "This is the Makefile for $(bright)$(thisdir)$(reset)." 33 | @echo "Available commands:" 34 | @echo 35 | @grep -B1 -E "^[a-zA-Z0-9_-]+\:([^=]|$$)" $(MAKEFILE_LIST) \ 36 | | grep -v -- -- \ 37 | | sed 'N;s/\n/###/' \ 38 | | sed -n 's/^#: \(.*\)###\([a-zA-Z0-9_-]*\):.*/$(color)\2$(reset):###\1/p' \ 39 | | column -t -s '###' 40 | 41 | 42 | # Compile source files ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 43 | 44 | # Getting Make to handle path names with embedded spaces is insanely hard. 45 | # The following incredibly obscure workarounds are the combination of two 46 | # methods: (1) by user "mathematical.coffee" posted on 2014-02-11 47 | # at https://stackoverflow.com/a/21694624/743730, and (2) by user Yann-Gaël 48 | # Guéhéneuc posted on 2016-02-25 at https://stackoverflow.com/a/35617603/743730 49 | 50 | # We first create a list of source files in a way that bypasses the regular 51 | # Make file pattern matching, and then replace every space character in 52 | # each pathname with a question mark character. 53 | 54 | sources = $(shell find . -iname '*.applescript' -maxdepth 1 | cut -d/ -f2- | tr ' ' '?') 55 | 56 | # Next, define a function named "q2s" to replace question marks by spaces, 57 | # using an absolutely bonkers trick to assign a space character to "space". 58 | 59 | space := 60 | space += 61 | q2s = $(subst ?, $(space),$1) 62 | 63 | # Now define the pattern rule to create compile .scpt files from .applescript 64 | # files. A rule of the form %.scpt would not work if the file names had space 65 | # characters in them (because the outcome of Make expanding %.scpt would have 66 | # spaces in the result, and those spaces would act as file name delimiters). 67 | # But if the file names used with this pattern rule have question marks 68 | # instead of spaces, they're atomic units as far as the pattern matching 69 | # mechanism is concerned, and so the rule works. We just have to do some 70 | # substitution to reconstruct the real file names before we run commands that 71 | # act on the files themselves. (That's where q2s comes in.) 72 | 73 | .SECONDEXPANSION: 74 | %.scpt: %.applescript 75 | osacompile -o "$(call q2s,$*.scpt)" "$<" 76 | 77 | # The final ingredient is to define the target "compile" such that it depends 78 | # on a list of .scpt files. This is simply done by taking the list of source 79 | # files (names such as "Some?file?name.applescript") and replacing the 80 | # extensions. The resulting list (names like "Some?file?name.scpt") then 81 | # satisfies the %.scpt rule above. 82 | 83 | #: Compile the AppleScript files in this directory. 84 | compile: $(sources:.applescript=.scpt) 85 | 86 | 87 | # Install binaries ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 88 | 89 | # Note the use of ? in the directory name below, instead of the space character 90 | # (i.e. ~/Library/Application Scripts/com.devon-technologies.think3/Smart Rules). 91 | 92 | destdir = "$(HOME)/Library/Application?Scripts/com.devon-technologies.think3/Smart?Rules" 93 | 94 | .SECONDEXPANSION: 95 | $(destdir)/%.scpt: %.applescript 96 | install -bpS "$(call q2s,$*.scpt)" $(call q2s,$(destdir)) 97 | 98 | #: Install the compiled scripts in DEVONthink's script directory. 99 | install: $(addprefix $(destdir)/,$(sources:.applescript=.scpt)) 100 | 101 | 102 | # Cleanup ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 103 | 104 | #: Clean this directory. 105 | clean: 106 | rm -rf *.scpt 107 | 108 | 109 | # Miscellaneous directives ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 110 | 111 | #: Print a random joke from https://icanhazdadjoke.com/. 112 | joke: 113 | @echo "$(shell curl -s https://icanhazdadjoke.com/)" 114 | 115 | # Color codes used in messages. 116 | color := $(shell tput bold; tput setaf 6) 117 | bright := $(shell tput bold; tput setaf 15) 118 | dim := $(shell tput setaf 66) 119 | link := $(shell tput setaf 111) 120 | reset := $(shell tput sgr0) 121 | 122 | .PHONY: help clean joke 123 | -------------------------------------------------------------------------------- /devonthink-smart-rule-scripts/auto-convert-web-page-to-PDF/.graphics/auto-convert-smart-rule.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mhucka/devonthink-hacks/9a4211aa29f9163ce625b9fb9ede2dccdb6546f1/devonthink-smart-rule-scripts/auto-convert-web-page-to-PDF/.graphics/auto-convert-smart-rule.png -------------------------------------------------------------------------------- /devonthink-smart-rule-scripts/auto-convert-web-page-to-PDF/Auto convert web page to PDF.applescript: -------------------------------------------------------------------------------- 1 | -- Script for DEVONthink to convert a webpage bookmark to PDF 2 | -- 3 | -- Copyright 2024 Michael Hucka. 4 | -- License: MIT License – see file "LICENSE" in the project website. 5 | -- Website: https://github.com/mhucka/devonthink-hacks 6 | -- 7 | -- This is an AppleScript fragment that will only work as the script 8 | -- executed by a Smart Rule in DEVONthink. For more information, see 9 | -- https://github.com/mhucka/devonthink-hacks/auto-convert-web-page-to-PDF/ 10 | 11 | -- This script will add a tag to the PDF document it creates. Set the 12 | -- next property value to the tag name that you want it to use. 13 | 14 | property tagForPDF : "π-archived-page" 15 | 16 | -- The companion bookmarklet can be used on regular web pages as well 17 | -- as pages that are open on PDF files. In the case of web pages, this 18 | -- script uses heuristics to try to force page content to be loaded 19 | -- fully before creating a full-page PDF snapshot of the page using the 20 | -- PDF conversion facilityin DEVONthink. Beware that this takes 10+ 21 | -- seconds to finish (possibly longer, depending on page contents). 22 | 23 | on performSmartRule(theRecords) 24 | repeat with selected in theRecords 25 | set recordURL to (URL of selected) as string 26 | set recordWindow to open window for record selected 27 | delay 2 28 | 29 | -- Wait until it's finished loading. 30 | repeat while loading of recordWindow 31 | delay 0.5 32 | end repeat 33 | 34 | -- If it's already a PDF, don't need to do more. 35 | if (recordURL ends with ".pdf") is not true then 36 | -- Some pages load content dynamically, with elements not 37 | -- displayed until they come into view. This is a hopeless 38 | -- situation in general but the following heuristic improves 39 | -- outcomes for some cases. It scrolls the window by 40 | -- quarters to try to trigger loading of more page elements. 41 | repeat with n from 1 to 4 42 | set scroll to "window.scrollTo(0," & n ¬ 43 | & "*document.body.scrollHeight/4)" 44 | do JavaScript scroll in current tab of recordWindow 45 | delay 0.75 46 | end repeat 47 | 48 | -- Return to the top. Do it twice because sometimes on some 49 | -- pages (notably Twitter), the first attempt gets stuck in 50 | -- some random location. (Ugh, what an utter hack this is.) 51 | do JavaScript "window.scrollTo(0,0)" ¬ 52 | in current tab of recordWindow 53 | delay 0.5 54 | do JavaScript "window.scrollTo(0,0)" ¬ 55 | in current tab of recordWindow 56 | delay 0.25 57 | end if 58 | 59 | -- Get the content of our current viewer window, in PDF form. 60 | -- This approach instead of using DEVONthink's "convert record 61 | -- to single page PDF document" is critical to getting the 62 | -- version of the page that is produced with the user's 63 | -- login/session state. 64 | set contentAsPDF to get PDF of recordWindow 65 | 66 | set recordName to (name of selected) 67 | set recordParents to (parents of selected) 68 | set recordGroup to item 1 of recordParents 69 | 70 | -- Create the new record in the 1st parent group of the selected 71 | -- item. (FIXME: that's an arbitrary choice of groups if item is 72 | -- replicated. Should do something smarter here.) 73 | set newDoc to create record with ¬ 74 | {name:recordName, URL:recordURL, type:PDF document} ¬ 75 | in recordGroup 76 | set data of newDoc to contentAsPDF 77 | set creation date of newDoc to creation date of selected 78 | set modification date of newDoc to modification date of selected 79 | set comment of newDoc to comment of selected 80 | set label of newDoc to label of selected 81 | 82 | -- Copy the tags of the original document and add tagForPDF. 83 | set {od, AppleScript's text item delimiters} ¬ 84 | to {AppleScript's text item delimiters, ","} 85 | set the tags of newDoc to (tags of selected & tagForPDF) 86 | set AppleScript's text item delimiters to od 87 | 88 | close recordWindow 89 | end repeat 90 | end performSmartRule 91 | -------------------------------------------------------------------------------- /devonthink-smart-rule-scripts/auto-convert-web-page-to-PDF/README.md: -------------------------------------------------------------------------------- 1 | Auto convert web page to PDF 2 | ============================ 3 | 4 | I find that DEVONthink is one of the best tools for saving snapshots of web pages as full-page PDF files, because its embedded web browser is interactive and lets you log into websites that use sessions. Once logged in, you can save pages that would otherwise be inaccessible; moreover, the login sessions persist just as they do in a regular browser, so that you don't always have to log back in every time. 5 | 6 | However, I'm not always browsing the web from within DEVONthink, and if I want to save a page encountered while using a normal web browser like Safari, I need to send it to DEVONthink and tell it to save it as a full-page PDF. This could be done using the standard clipper extension or bookmarklet provided with DEVONthink, but there is a catch: some web pages are generated dynamically and sent incrementally, with content not revealed until you scroll further down on the page. DEVONthink's default capture method will not always get the full page in those cases. 7 | 8 | In general, capturing dynamically-generated pages is a hopelessly difficult task. However, for my purposes, I've solved this for most cases using a combination of mechanisms: 9 | 10 | 1. A Smart Rule in DEVONthink that is triggered when a new web page bookmark is created if the bookmark contains a certain tag; 11 | 2. An AppleScript program that is executed by the Smart Rule; it opens the page, scrolls down the page a few times, and tells DEVONthink to convert the resulting window content to PDF. 12 | 3. A custom bookmarklet that can be used from Safari that, when invoked, sends a URL to DEVONthink with the required tag. 13 | 14 | The following image shows the configuration of my Smart Rule in DEVONthink: 15 | 16 |

17 | 18 |

19 | 20 | The second action of this rule is to execute an embedded script. The script in question is contained in this directory in the file `Auto convert web page to PDF.applescript`. To use it with the Smart Rule above, open the AppleScript file in a text editor, copy the entire file contents, and paste the contents in as the embedded script in the Smart Rule. 21 | 22 | Note that the rule removes the tag _before_ creating the PDF, because the PDF file will inherit any tags on the bookmark when DEVONthink creates it, and you don't want the tag on the resulting PDF file. The rule also moves the original bookmark to the trash because for my purposes, it's no longer needed after the PDF conversion is done. 23 | 24 | Finally, the following is the code for the bookmarklet: 25 | 26 | ``` 27 | javascript:window.location='x-devonthink://createBookmark?title='+encodeURIComponent(document.title)+'&location='+encodeURIComponent(window.location)+'&referrer='+encodeURIComponent(document.referrer)+'&tags=convert-to-pdf'; 28 | ``` 29 | 30 | Incidentally, the AppleScript code copies the DEVONthink item link (of the form `x-devonthink-item://...`) to the macOS clipboard. This can be handy if the next thing you do after clipping the page is to write something about it and you want to link to the item in DEVONthink. 31 | 32 | 37 | -------------------------------------------------------------------------------- /devonthink-smart-rule-scripts/auto-convert-web-page-to-PDF/bookmarklet.js: -------------------------------------------------------------------------------- 1 | javascript:window.location='x-devonthink://createBookmark?title='+encodeURIComponent(document.title)+'&location='+encodeURIComponent(window.location)+'&referrer='+encodeURIComponent(document.referrer)+'&tags=convert-to-pdf'; 2 | -------------------------------------------------------------------------------- /devonthink-smart-rule-scripts/copy-metadata-to-annotation-docs/Copy metadata to annotation docs.applescript: -------------------------------------------------------------------------------- 1 | -- Summary: copy specific metadata from source doc to annotation doc. 2 | -- 3 | -- This AppleScript code will only work as a script executed by a Smart 4 | -- Rule in DEVONthink. It also assumes that the Smart Rule is set to 5 | -- search specifically in the database group containing annotations. 6 | -- 7 | -- ╭───────────────────────────── WARNING ─────────────────────────────╮ 8 | -- │ This assumes custom metadata fields that I set in my copy of │ 9 | -- │ DEVONthink. It will almost certainly not work in anyone else's │ 10 | -- │ copy of DEVONthink. │ 11 | -- ╰───────────────────────────────────────────────────────────────────╯ 12 | -- 13 | -- Copyright 2024 Michael Hucka. 14 | -- License: MIT license – see file "LICENSE" in the project website. 15 | -- Website: https://github.com/mhucka/devonthink-hacks 16 | 17 | use AppleScript version "2.4" -- Yosemite (10.10) or later 18 | use scripting additions 19 | 20 | -- Config variables ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 21 | 22 | -- The following are specific to my DEVONthink configuration. 23 | property custom_fields: {"Citekey", "Type", "Year", "Abstract", "Reference"} 24 | 25 | -- Helper functions ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 26 | 27 | on copy_meta(source_record, annotation_record) 28 | tell application id "DNtp" 29 | tell annotation_record 30 | set tags to tags of source_record 31 | set URL to reference URL of source_record 32 | set rating to rating of source_record 33 | set label to label of source_record 34 | repeat with _field in custom_fields 35 | set value to get custom meta data for _field from source_record 36 | add custom meta data value for _field to annotation_record 37 | end repeat 38 | end tell 39 | end tell 40 | end copy_meta 41 | 42 | -- Main body ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 43 | 44 | on performSmartRule(selected_records) 45 | tell application id "DNtp" 46 | repeat with this_record in selected_records 47 | -- We assume that these are annotation records already. We 48 | -- need to get the thing they're annotating. 49 | repeat with _incoming in incoming references of this_record 50 | if (exists annotation of _incoming) then 51 | set other_annot to get annotation of _incoming 52 | if uuid of other_annot = uuid of this_record then 53 | my copy_meta(_incoming, this_record) 54 | end if 55 | end if 56 | end repeat 57 | end repeat 58 | end tell 59 | end performSmartRule 60 | -------------------------------------------------------------------------------- /devonthink-smart-rule-scripts/copy-metadata-to-annotation-docs/Makefile: -------------------------------------------------------------------------------- 1 | ../Makefile -------------------------------------------------------------------------------- /devonthink-smart-rule-scripts/create-image-file/Create image file for transclusion.applescript: -------------------------------------------------------------------------------- 1 | -- Create an image file to use for transcluding an image in Zotero. 2 | -- This is specific to my way of doing things in DEVONthink & Zotero. 3 | -- 4 | -- This is an AppleScript fragment that will only work as the script 5 | -- executed by a Smart Rule in DEVONthink. 6 | -- 7 | -- Copyright 2024 Michael Hucka. 8 | -- License: MIT License – see file "LICENSE" in the project website. 9 | -- Website: https://github.com/mhucka/devonthink-hacks 10 | 11 | property groupPath: "/Sources/Zotero/Images" 12 | 13 | on performSmartRule(selectedRecords) 14 | tell application id "DNtp" 15 | set grp to create location groupPath in the current database 16 | try 17 | repeat with rec in selectedRecords 18 | set citekey to get custom meta data for "citekey" from rec 19 | if citekey = "" then 20 | set recName to name of rec 21 | display notification "No citekey found for " & recName 22 | return 23 | end if 24 | if (exists record at groupPath & "/" & citekey) then 25 | return 26 | end if 27 | 28 | set u to get reference URL of rec 29 | set caption to "Source: [@" & citekey & "](" & u & ")" 30 | set body to "![" & caption & "](" & u & ")" 31 | create record with ¬ 32 | {name:citekey, type:markdown, plain text:body} ¬ 33 | in grp 34 | end repeat 35 | on error msg number code 36 | -- Code -128 signifies the user cancelled an operation. 37 | if the code is not -128 then 38 | display alert "DEVONthink" message msg as warning 39 | end if 40 | end try 41 | end tell 42 | end performSmartRule 43 | -------------------------------------------------------------------------------- /devonthink-smart-rule-scripts/create-image-file/Makefile: -------------------------------------------------------------------------------- 1 | ../Makefile -------------------------------------------------------------------------------- /devonthink-smart-rule-scripts/create-image-file/Test image file exists.applescript: -------------------------------------------------------------------------------- 1 | -- Test if image file exists. 2 | -- 3 | -- This is an AppleScript fragment that will only work as the script 4 | -- executed by a Smart Rule in DEVONthink. 5 | -- 6 | -- Copyright 2024 Michael Hucka. 7 | -- License: MIT License – see file "LICENSE" in the project website. 8 | -- Website: https://github.com/mhucka/devonthink-hacks 9 | 10 | property imageGroup : "/Sources/Zotero/Images" 11 | 12 | on performSmartRule(selectedRecords) 13 | tell application id "DNtp" 14 | repeat with rec in selectedRecords 15 | set citekey to get custom meta data for "citekey" from rec 16 | if citekey = "" then 17 | set recName to name of rec 18 | display notification "No citekey found for " & recName 19 | return 20 | end if 21 | set rec to exists record at imageGroup & "/" & "foo" 22 | if not rec then 23 | display dialog rec 24 | error -128 25 | end if 26 | end repeat 27 | end tell 28 | end performSmartRule 29 | -------------------------------------------------------------------------------- /devonthink-smart-rule-scripts/pause/Makefile: -------------------------------------------------------------------------------- 1 | ../Makefile -------------------------------------------------------------------------------- /devonthink-smart-rule-scripts/pause/Pause 10 seconds.applescript: -------------------------------------------------------------------------------- 1 | -- Summary: sleep for 10 seconds. 2 | -- 3 | -- When Smart Rule actions invoke other Smart Rules, sometimes it doesn't 4 | -- work if the subsequent rules are invoked immediately. This script can 5 | -- be used to insert a 5 second delay in the sequence of actions in a 6 | -- Smart Rule. 7 | -- 8 | -- Copyright 2024 Michael Hucka. 9 | -- License: MIT license – see file "LICENSE" in the project website. 10 | -- Website: https://github.com/mhucka/devonthink-hacks 11 | 12 | use AppleScript version "2.5" 13 | use scripting additions 14 | 15 | on performSmartRule(selected_records) 16 | tell application "System Events" 17 | delay 10 18 | end tell 19 | end performSmartRule 20 | 21 | -- Scaffolding for execution outside of a Smart Rule (e.g., in a debugger). 22 | tell application id "DNtp" 23 | my performSmartRule(selection as list) 24 | end tell 25 | -------------------------------------------------------------------------------- /devonthink-smart-rule-scripts/pause/Pause 5 seconds.applescript: -------------------------------------------------------------------------------- 1 | -- Summary: sleep for 5 seconds. 2 | -- 3 | -- When Smart Rule actions invoke other Smart Rules, sometimes it doesn't 4 | -- work if the subsequent rules are invoked immediately. This script can 5 | -- be used to insert a 5 second delay in the sequence of actions in a 6 | -- Smart Rule. 7 | -- 8 | -- Copyright 2024 Michael Hucka. 9 | -- License: MIT license – see file "LICENSE" in the project website. 10 | -- Website: https://github.com/mhucka/devonthink-hacks 11 | 12 | use AppleScript version "2.5" 13 | use scripting additions 14 | 15 | on performSmartRule(selected_records) 16 | tell application "System Events" 17 | delay 5 18 | end tell 19 | end performSmartRule 20 | 21 | -- Scaffolding for execution outside of a Smart Rule (e.g., in a debugger). 22 | tell application id "DNtp" 23 | my performSmartRule(selection as list) 24 | end tell 25 | -------------------------------------------------------------------------------- /devonthink-smart-rule-scripts/remove-pdf-keywords/.graphics/smart-rule-screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mhucka/devonthink-hacks/9a4211aa29f9163ce625b9fb9ede2dccdb6546f1/devonthink-smart-rule-scripts/remove-pdf-keywords/.graphics/smart-rule-screenshot.png -------------------------------------------------------------------------------- /devonthink-smart-rule-scripts/remove-pdf-keywords/.graphics/zotfile.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mhucka/devonthink-hacks/9a4211aa29f9163ce625b9fb9ede2dccdb6546f1/devonthink-smart-rule-scripts/remove-pdf-keywords/.graphics/zotfile.png -------------------------------------------------------------------------------- /devonthink-smart-rule-scripts/remove-pdf-keywords/Makefile: -------------------------------------------------------------------------------- 1 | ../Makefile -------------------------------------------------------------------------------- /devonthink-smart-rule-scripts/remove-pdf-keywords/README.md: -------------------------------------------------------------------------------- 1 | # Remove PDF keywords in PDF files 2 | 3 | Many of my PDF files are articles from academic publications such as research journals. Most journals and other sources "helpfully" add keywords to the PDF files, typically for subject tags. I find these keywords entirely _unhelpful_: 4 | 5 | - They use someone else's tagging scheme, which has no relation to my own tagging scheme. 6 | - Every journal and source seems to use a _different_ tagging scheme, which leads to a tag mess when you collect articles from multiple sources. 7 | 8 | Although DEVONthink can be configured to skip turning PDF keywords into tags when you import files, I still find it confusing to see the keywords in property inspectors or when opening the PDF files in other applications. 9 | 10 | I wrote this small script to remove keywords from PDF files automatically when I import PDF files into DEVONthink. The script uses the venerable [ExifTool](https://exiftool.org) command line tool, which I installed using [HomeBrew](https://brew.sh). 11 | 12 | In my DEVONthink configuration, the script is run by a very simple smart rule. The trigger in this case is "on demand" because in my configuration, I have another smart rule invoke this one. (If you want to use this for yourself, you may want to change the trigger condition.) 13 | 14 |

15 | 16 |

17 | 18 | Be careful about the length of your PDF file names. The maximum file name length on macOS is 256 characters. The way that [ExifTool](https://exiftool.org) works when it replaces keywords in PDF files is that it creates a temporary file named after the original file with a suffix of `.pdf_exiftool_tmp`. That's an additional 13 characters. If your file name is already close to 256 characters in length, the new suffix may push it past 256, and this script and the DEVONthink smart rule will fail with an error. 19 | 20 | If you are a Zotero user, and you use the [ZotFile](http://zotfile.com) plugin for Zotero to rename files that you store in your Zotero database, you may be tempted to configure ZotFile to use a title length of 256 to handle the occasional very long article title. However, the "maximum length of title" setting in ZotFile refers _only_ to the title of a Zotero entry, not the other parts (such as author names, year, etc.) that may be used to construct the actual file name of the PDF file. When all the parts are put together by Zotfile and it creates the PDF file name, a high value for the maximum title length makes it more likely you will occasionally exceed the 256 character limit. If that happens when this script runs, it can be difficult to attribute the cause of those errors. 21 | 22 |

23 | 24 |

25 | -------------------------------------------------------------------------------- /devonthink-smart-rule-scripts/remove-pdf-keywords/Remove PDF keywords.applescript: -------------------------------------------------------------------------------- 1 | # Summary: remove all PDF subject keywords from selected PDF files. 2 | # This calls "exiftool" as an external command-line program. 3 | # 4 | # This is an AppleScript fragment that will only work as the script 5 | # executed by a Smart Rule in DEVONthink. 6 | # 7 | # Copyright 2024 Michael Hucka. 8 | # License: MIT license – see file "LICENSE" in the project website. 9 | # Website: https://github.com/mhucka/devonthink-hacks 10 | 11 | use AppleScript version "2.5" 12 | use scripting additions 13 | 14 | # ~~~~ Config variables ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 15 | 16 | # List used to set the shell's command search $PATH. The values cover the most 17 | # likely places where ExifTool may be found. If your copy of ExifTool is not 18 | # found in one of these locations, add the appropriate directory to this list. 19 | property shell_paths: { ¬ 20 | "$PATH" , ¬ 21 | "$HOME/.local/bin" , ¬ 22 | "$HOME/bin" , ¬ 23 | "/usr/local/bin" , ¬ 24 | "/opt/homebrew/bin" ¬ 25 | } 26 | 27 | 28 | # ~~~~ Helper functions ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 29 | 30 | # Log a message in DEVONthink's log and include the name of this script. 31 | on report(error_text) 32 | local script_path 33 | tell application "System Events" 34 | set script_path to POSIX path of (path to me as alias) 35 | end tell 36 | tell application id "DNtp" 37 | # Note: DEVONthink's "log" function is not the same as AppleScript's. 38 | log message script_path info error_text 39 | end tell 40 | log error_text # Useful when running in an AppleScript debugger. 41 | end report 42 | 43 | on concat(string_list, separator) 44 | local output 45 | set output to "" 46 | repeat with i from 1 to count of string_list 47 | set output to output & item i of string_list 48 | if i < count of string_list then 49 | set output to output & separator 50 | end if 51 | end repeat 52 | return output 53 | end concat 54 | 55 | on sh(shell_search_path, command) 56 | local output, path_env 57 | set output to "" 58 | set path_env to "PATH=" & shell_search_path 59 | try 60 | set output to do shell script (path_env & " " & command) 61 | on error msg number code 62 | if the code is not -128 then 63 | my report(msg) 64 | end if 65 | end try 66 | return output 67 | end sh 68 | 69 | on command_found(shell_search_path, command) 70 | local path_env 71 | set path_env to "PATH=" & shell_search_path 72 | try 73 | do shell script (path_env & " command -V " & command) 74 | return true 75 | on error 76 | return false 77 | end try 78 | end command_found 79 | 80 | 81 | # ~~~~ Main body ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 82 | 83 | property search_path: my concat(shell_paths, ":") 84 | 85 | on performSmartRule(selected_records) 86 | local rec_path, quoted_path 87 | if not my command_found(search_path, "exiftool") then 88 | my report("Could not find program '" & command & "' on this " ¬ 89 | & "computer. The directories search were the following: " ¬ 90 | & concat(shell_paths, ", ")) 91 | return 92 | end if 93 | tell application id "DNtp" 94 | repeat with rec in selected_records 95 | try 96 | if type of rec is PDF document then 97 | # Embedded single quotes cause problems. Combo of changing 98 | # delims & using "quoted form" (below) seems to solve it. 99 | set AppleScript's text item delimiters to "\\\\" 100 | # Don't combine the next 2 stmts b/c that will result in 101 | # type coercion will go wrong and cause an error. 102 | set rec_path to path of rec 103 | set quoted_path to quoted form of rec_path 104 | my sh(search_path, "exiftool -q -Keywords= " ¬ 105 | & "-overwrite_original_in_place " & quoted_path) 106 | synchronize record rec 107 | end if 108 | on error msg number code 109 | if the code is not -128 then 110 | my report(msg & " (error " & code & ")") 111 | end if 112 | end try 113 | end repeat 114 | 115 | end tell 116 | end performSmartRule 117 | 118 | # Scaffolding for execution outside of a Smart Rule (e.g., in a debugger). 119 | tell application id "DNtp" 120 | my performSmartRule(selection as list) 121 | end tell 122 | -------------------------------------------------------------------------------- /devonthink-smart-rule-scripts/replace-custom-placeholders/Makefile: -------------------------------------------------------------------------------- 1 | ../Makefile -------------------------------------------------------------------------------- /devonthink-smart-rule-scripts/replace-custom-placeholders/Replace custom placeholders in document.applescript: -------------------------------------------------------------------------------- 1 | -- Summary: replace placeholders in the text of a Markdown document. 2 | -- 3 | -- ╭───────────────────────────── WARNING ─────────────────────────────╮ 4 | -- │ This assumes custom metadata fields that I set in my copy of │ 5 | -- │ DEVONthink. It will almost certainly not work in anyone else's │ 6 | -- │ copy of DEVONthink without modification. │ 7 | -- ╰───────────────────────────────────────────────────────────────────╯ 8 | -- 9 | -- This AppleScript program is meant to be invoked by a DEVONthink Smart Rule. 10 | -- It searches the contents of a selected document for placeholders of the form 11 | -- %placeholder% and for each one, replaces the placeholder text with the value 12 | -- of the appropriate metadata field found in the DEVONthink record for that 13 | -- document. It preprocesses the value to escape characters that have special 14 | -- meaning in Markdown (e.g., the pound sign) so that the result is less likely 15 | -- to be misinterpreted by the Markdown processor. 16 | -- 17 | -- This script looks for BOTH standard placeholders defined by DEVONthink, and 18 | -- a set of custom placeholders named in property "custom_fields" below. The 19 | -- reason this replaces standard placeholders is because template placeholder 20 | -- substitution is not performed by DEVONthink To Go on iOS when you create an 21 | -- annotation document there. Now, despite that DTTG does not directly support 22 | -- templates for annotations, it doesn't mean you can't insert template text 23 | -- into the annotation property field of a record. For example, you could 24 | -- create an iOS Shortcut to insert your desired template. That works well, 25 | -- except that DTTG won't perform placeholder substitution. However, if you 26 | -- synchronize your database with DEVONthink Pro on a Mac, and set up a 27 | -- suitable Smart Rule, you can get placeholder substitution in annotation 28 | -- documents after all (after a delay to sync documents and trigger the 29 | -- Smart Rule). That is the reason this script exists. 30 | -- 31 | -- The custom placeholder names that this script replaces will be prefixed with 32 | -- a string (defined by property "custom_prefix" below), to avoid name 33 | -- collisions with built-in DEVONthink placeholders. For example, in my 34 | -- configuration, I defined a custom metadata field named "Year", which I use 35 | -- to store the value of the year field in bibliographic items that I keep in 36 | -- Zotero; unfortunately, it turns out that one of the standard DEVONthink 37 | -- template placeholders is %year% (for the current year). If this script 38 | -- didn't do something to distinguish custom year field names, then a %year% 39 | -- appearing in a template acted on by this script would be ambiguous. 40 | -- 41 | -- Copyright 2024 Michael Hucka. 42 | -- License: MIT License – see file "LICENSE" in the project website. 43 | -- Website: https://github.com/mhucka/devonthink-hacks 44 | 45 | use AppleScript version "2.5" 46 | use scripting additions 47 | 48 | -- Config variables ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 49 | 50 | -- Set of characters (expressed as a regex) with special meaning in Markdown. 51 | property chars_escape_regex: "[`*_{}()\\[\\]<>#+!|-]" 52 | 53 | -- List of custom metadata fields to replace (in addition to regular fields). 54 | property custom_fields: {"Citekey", "Year", "Type", "Abstract", "Reference"} 55 | 56 | -- Prefix for custom placeholders (to avoid name collisions with DEVONthink). 57 | property custom_prefix: "bib" 58 | 59 | 60 | -- Helper functions ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 61 | 62 | -- Log a message in DEVONthink's log and include the name of this script. 63 | on report(error_text) 64 | local script_path 65 | tell application "System Events" 66 | set script_path to POSIX path of (path to me as alias) 67 | end tell 68 | tell application id "DNtp" 69 | log message script_path info error_text 70 | end tell 71 | log error_text -- Useful when running in a debugger. 72 | end report 73 | 74 | -- Do literal substitution of placeholder in string oldstr & return the result. 75 | -- The value of "replacement" can be an empty string. If placeholder is an 76 | -- empty string, this does nothing and returns "oldstr" unchanged. 77 | on substitute(placeholder, oldstr, replacement) 78 | script wrapperScript 79 | property ca: a reference to current application 80 | use framework "Foundation" 81 | on substitute(placeholder, oldstr, replacement) 82 | if (placeholder is missing value or placeholder = "") ¬ 83 | or (oldstr is missing value or oldstr = "") then 84 | return oldstr 85 | end if 86 | try 87 | set opts to ca's NSCaseInsensitiveSearch 88 | set newstr to ca's NSString's stringWithString:oldstr 89 | set newstr to (newstr's ¬ 90 | stringByReplacingOccurrencesOfString:placeholder ¬ 91 | withString:(replacement) options:(opts) ¬ 92 | range:{0, newstr's |length|()}) 93 | return newstr as string 94 | on error msg number code 95 | my report(msg & "(error " & number & ")") 96 | return oldstr 97 | end try 98 | end substitute 99 | end script 100 | return wrapperScript's substitute(placeholder, oldstr, replacement) 101 | end substitute 102 | 103 | -- Filter the given string to quote characters that have meaning in Markdown. 104 | on escape(oldstr) 105 | script wrapperScript 106 | property ca : a reference to current application 107 | use framework "Foundation" 108 | on escape(oldstr) 109 | if oldstr is missing value or oldstr = "" then 110 | return oldstr 111 | end if 112 | try 113 | set newstr to ca's NSString's stringWithString:oldstr 114 | set {regex, err} to (ca's NSRegularExpression's ¬ 115 | regularExpressionWithPattern:chars_escape_regex ¬ 116 | options:0 |error|:(reference)) 117 | if err is not missing value then 118 | set err_text to err's localizedDescription() as text 119 | error ("Invalid definition of the set of characters to " ¬ 120 | & "escape in replaced text: " & err_text) number -128 121 | end if 122 | set newstr to (regex's stringByReplacingMatchesInString:newstr ¬ 123 | options:0 range:{0, newstr's |length|()} withTemplate:"\\\\$0") 124 | return newstr as string 125 | on error msg number code 126 | my report(msg & "(error " & number & ")") 127 | return oldstr 128 | end try 129 | end escape 130 | end script 131 | return wrapperScript's escape(oldstr) 132 | end escape 133 | 134 | -- Get the record that this annotation document is annotating. 135 | on get_annotated_record(this_record) 136 | tell application id "DNtp" 137 | repeat with incoming_record in incoming references of this_record 138 | if (exists annotation of incoming_record) then 139 | set annot_of_incoming to get annotation of incoming_record 140 | if uuid of annot_of_incoming = uuid of this_record then 141 | return incoming_record 142 | end if 143 | end if 144 | end repeat 145 | return missing value 146 | end tell 147 | end get_annotated_record 148 | 149 | -- Replace occurrences of "placeholder" in the "body" string, except if either 150 | -- the placeholder or replacement are empty, in which case, nothing is done. 151 | on replace(placeholder, body, replacement, escape_replacement) 152 | if replacement is missing value or replacement = "" then 153 | return body 154 | end if 155 | if escape_replacement is true then 156 | set replacement to my escape(replacement) 157 | end if 158 | return my substitute(placeholder, body, replacement) 159 | end replace 160 | 161 | 162 | -- Main body ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 163 | 164 | on performSmartRule(selected_records) 165 | tell application id "DNtp" 166 | try 167 | -- Create a list of just the Markdown documents in the selection. 168 | set md_records to {} 169 | repeat with rec in selected_records 170 | set rectype to (type of rec) as string 171 | if rectype is in {"markdown", "«constant Ctypmkdn»", ¬ 172 | "«constant ****mkdn»"} then 173 | set end of md_records to rec 174 | end if 175 | end repeat 176 | 177 | -- Iterate over our subset of records. 178 | repeat with rec in md_records 179 | local body 180 | set body to plain text of rec 181 | set ref_url to reference URL of rec 182 | set group_url to reference URL of current group 183 | 184 | -- Info about the current record (the annotation document). 185 | set body to my replace("%UUID%", body, uuid of rec, false) 186 | set body to my replace("%name%", body, name of rec, true) 187 | set body to my replace("%fileName%", body, filename of rec, true) 188 | set body to my replace("%referenceURL%", body, ref_url, false) 189 | set body to my replace("%groupURL%", body, group_url, false) 190 | 191 | -- When an annotation document is created on a Mac, DEVONthink 192 | -- replaces placeholders %document%Link% and %documentName% 193 | -- with the corresponding info about the *original* document 194 | -- (i.e., what is being annotated), not the current document, 195 | -- as described on p. 127 of the user manual for vers. 3.9.4. 196 | set source_record to my get_annotated_record(rec) 197 | if source_record is missing value then 198 | my report("could not find source record for " & name of rec) 199 | else 200 | set source_name to name of source_record 201 | set source_url to reference URL of source_record as string 202 | set body to my replace("%documentName%", body, source_name, true) 203 | set body to my replace("%documentLink%", body, source_url, false) 204 | 205 | -- Iterate over the list of custom placeholders. 206 | repeat with field_name in custom_fields 207 | local placeholder, value 208 | set placeholder to "%" & custom_prefix & field_name & "%" 209 | set value to get custom meta data for field_name from rec 210 | set body to my replace(placeholder, body, value, true) 211 | end repeat 212 | end if 213 | 214 | -- Replace the record body if we made it this far. 215 | set plain text of rec to body 216 | end repeat 217 | on error msg number code 218 | if the code is not -128 then 219 | my report(msg & " (error " & code & ")") 220 | display alert "DEVONthink" message msg as warning 221 | end if 222 | end try 223 | end tell 224 | end performSmartRule 225 | 226 | -- Scaffolding for execution outside of a Smart Rule (e.g., in a debugger). 227 | tell application id "DNtp" 228 | my performSmartRule(selection as list) 229 | end tell 230 | -------------------------------------------------------------------------------- /devonthink-smart-rule-scripts/send-url-to-internet-archive/Makefile: -------------------------------------------------------------------------------- 1 | ../Makefile -------------------------------------------------------------------------------- /devonthink-smart-rule-scripts/send-url-to-internet-archive/Send URL to Internet Archive.applescript: -------------------------------------------------------------------------------- 1 | -- Send the URL of the selected items to the Internet Archive 2 | -- 3 | -- This is meant to be executed by a smart rule in DEVONthink. 4 | -- This script assumes a metadata field named "archiveurl" exists on the 5 | -- records of your database. 6 | -- 7 | -- Copyright 2024 Michael Hucka. 8 | -- License: MIT License – see file "LICENSE" in the project website. 9 | -- Website: https://github.com/mhucka/devonthink-hacks 10 | 11 | -- Set to the name of the smart rule, so notifications can mention it. 12 | property smartRuleName : "send URL to IA" 13 | 14 | on performSmartRule(selectedRecords) 15 | repeat with _record in selectedRecords 16 | set theURL to get URL of _record 17 | if theURL ≠ "" then 18 | if "discourse" is in theURL or "forum" is in theURL then 19 | if theURL ends with "/print" then 20 | -- Strip off /print part before sending these URLs. 21 | set theURL to (characters 1 thru -7 of theURL) as string 22 | end if 23 | end if 24 | try 25 | set result to do shell script ¬ 26 | "curl -sSI 'https://web.archive.org/save/" & theURL & "'" 27 | set result_lines to every paragraph of result 28 | set status_line to item 1 of result_lines as string 29 | set status to ((characters -4 thru -1 of status_line) as string) as number 30 | if status < 400 then 31 | set location_line to item 6 of result_lines 32 | set archiveURL to (characters 11 thru -1 of location_line) as string 33 | add custom meta data archiveURL for "archiveurl" to _record 34 | else if status = 404 then 35 | -- There's something wrong with the URL. 36 | add custom meta data "NOT FOUND" for "archiveurl" to _record 37 | else if status = 451 then 38 | -- Unavailable for legal reasons. 39 | add custom meta data "UNAVAILABLE" for "archiveurl" to _record 40 | else if status = 429 or status = 410 or status > 500 then 41 | -- Can't save a copy right now, possibly due to rate 42 | -- limits or the site is offline or an error. Did 43 | -- IA ever archive it? If so, use the last copy. 44 | set available_result to do shell script ¬ 45 | "curl -sSI 'https://web.archive.org/wayback/available?url=" & theURL & "'" 46 | set result_lines to every paragraph of available_result 47 | set location_line to item 8 of result_lines as string 48 | if location_line contains "memento-location" then 49 | set archiveURL to (characters 19 thru -1 of location_line) as string 50 | add custom meta data archiveURL for "archiveurl" to _record 51 | -- Some of the following repeat tests done above, 52 | -- because we may have gotten here due to the 429 53 | -- when attempting to save the URL, and also, these 54 | -- cases test the results of a different API call. 55 | else if status = 404 then 56 | -- There's something wrong with the URL. 57 | add custom meta data "NOT FOUND" for "archiveurl" to _record 58 | else if status = 410 or status = 451 then 59 | -- Gone or unavailable for legal reasons. 60 | add custom meta data "UNAVAILABLE" for "archiveurl" to _record 61 | else if status ≥ 525 then 62 | add custom meta data "NO ARCHIVED COPY" for "archiveurl" to _record 63 | end if 64 | else 65 | -- Got some other failure status code. Let other rules 66 | -- handle retrying some other time. 67 | add custom meta data "RETRY LATER" for "archiveurl" to _record 68 | end if 69 | on error msg number code 70 | if the code is not -128 then 71 | my notify(msg, "leaving item in queue") 72 | end if 73 | end try 74 | end if 75 | end repeat 76 | end performSmartRule 77 | 78 | on notify(headline, body) 79 | display notification body with title headline ¬ 80 | subtitle "(Smart rule '" & smartRuleName & "')" 81 | end notify 82 | 83 | 84 | -- Reminder about HTTP codes that IA may return: 85 | -- 86 | -- # Description 87 | -- --- ----------- 88 | -- 404 HTTP Not Found 89 | -- 408 HTTP Request Timeout 90 | -- 410 HTTP Gone 91 | -- 429 Rate limit reached; try later 92 | -- 451 Unavailable for Legal Reasons 93 | -- 500 Internal Server Error 94 | -- 502 Bad Gateway 95 | -- 503 Gateway Timeout 96 | -- 509 Bandwidth Limit Exceeded 97 | -- 520 Server Returned an Unknown Error 98 | -- 521 Web Server is Down 99 | -- 523 Origin is Unreachable 100 | -- 524 A Timeout Occurred 101 | -- 525 SSL Handshake Failed 102 | -- 526 Invalid SSL Certificate 103 | -------------------------------------------------------------------------------- /devonthink-smart-rule-scripts/set-annotation-url/Makefile: -------------------------------------------------------------------------------- 1 | ../Makefile -------------------------------------------------------------------------------- /devonthink-smart-rule-scripts/set-annotation-url/Set annotation document URL.applescript: -------------------------------------------------------------------------------- 1 | -- Summary: set the URL of an annotation document to the source doc. 2 | -- 3 | -- This AppleScript code will only work as a script executed by a Smart 4 | -- Rule in DEVONthink. It also assumes that the Smart Rule is set to 5 | -- search specifically in the database group containing annotations. 6 | -- 7 | -- As of DEVONthink 3.9.4, annotations are stored in such a way that the 8 | -- record for the document being annotated (call it the source) points to 9 | -- the annotation record, but the annotation record does NOT store a link 10 | -- back to the source. This is problematic when you need to access the 11 | -- source record. So, in my DEVONthink configuration, I set the URL field 12 | -- of annotation documents to point to the source document. I find this 13 | -- makes sense conceptually, and is convenient because then you use the 14 | -- keyboard shortcut control-command-u to jump to the source document. 15 | -- The script here is used in combination with a Smart Rule to set the 16 | -- field when an annotation document is first created. 17 | -- 18 | -- Copyright 2024 Michael Hucka. 19 | -- License: MIT license – see file "LICENSE" in the project website. 20 | -- Website: https://github.com/mhucka/devonthink-hacks 21 | 22 | use AppleScript version "2.4" -- Yosemite (10.10) or later 23 | 24 | -- Main body ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 25 | 26 | on performSmartRule(selected_annotation_records) 27 | tell application id "DNtp" 28 | -- This assumes the Smart Rule searches the group containing 29 | -- annotation documents. For efficiency, it doesn't test that. 30 | repeat with this_record in selected_annotation_records 31 | -- Look at each record that points to this record. 32 | repeat with _incoming in incoming references of this_record 33 | -- Does this other record have an annotation? 34 | if (exists annotation of _incoming) then 35 | set incoming_annot to get annotation of _incoming 36 | -- Is that annotation doc *this* annotation doc? 37 | if uuid of incoming_annot = uuid of this_record then 38 | set URL of this_record to reference URL of _incoming 39 | end if 40 | end if 41 | end repeat 42 | end repeat 43 | end tell 44 | end performSmartRule 45 | -------------------------------------------------------------------------------- /devonthink-smart-rule-scripts/unset-common-zotero-field-values/Makefile: -------------------------------------------------------------------------------- 1 | ../Makefile -------------------------------------------------------------------------------- /devonthink-smart-rule-scripts/unset-common-zotero-field-values/Unset common Zotero field values.applescript: -------------------------------------------------------------------------------- 1 | -- Summary: unset record fields I use in conjunction with Zotero. 2 | -- 3 | -- ╭───────────────────────────── WARNING ─────────────────────────────╮ 4 | -- │ This assumes custom metadata fields that I set in my copy of │ 5 | -- │ DEVONthink. It will almost certainly not work in anyone else's │ 6 | -- │ copy of DEVONthink. │ 7 | -- ╰───────────────────────────────────────────────────────────────────╯ 8 | -- 9 | -- Copyright 2024 Michael Hucka. 10 | -- License: MIT License – see file "LICENSE" in the project website. 11 | -- Website: https://github.com/mhucka/devonthink-hacks 12 | 13 | use AppleScript version "2.4" -- Yosemite (10.10) or later 14 | use scripting additions 15 | 16 | on performSmartRule(selectedRecords) 17 | tell application id "DNtp" 18 | try 19 | repeat with _record in selectedRecords 20 | set URL of _record to "" 21 | set aliases of _record to "" 22 | set comment of _record to "" 23 | add custom meta data "" for "year" to _record 24 | add custom meta data "" for "type" to _record 25 | add custom meta data "" for "citekey" to _record 26 | add custom meta data "" for "abstract" to _record 27 | add custom meta data "" for "reference" to _record 28 | end repeat 29 | on error msg number code 30 | if the code is not -128 then 31 | display alert "Unset common Zotero field values" ¬ 32 | message msg as warning 33 | end if 34 | end try 35 | end tell 36 | end performSmartRule 37 | -------------------------------------------------------------------------------- /devonthink-smart-rule-scripts/write-uri-into-comments/Makefile: -------------------------------------------------------------------------------- 1 | ../Makefile -------------------------------------------------------------------------------- /devonthink-smart-rule-scripts/write-uri-into-comments/README.md: -------------------------------------------------------------------------------- 1 | # Write DEVONthink URI into Finder comment 2 | 3 | I use the AppleScript code in this directory as part of a Smart Rule definition in DEVONthink to write the `x-devonthink-item` URI of a Markdown document into that document's Finder comments. This makes it possible for me to find the document's record more easily in a DEVONthink database when I'm editing the Markdown file in an external editor. 4 | 5 | First, here a screenshot of the DEVONthink Smart Rule definition: 6 | 7 |

8 | 9 |

10 | 11 | The rule's conditions match files that do not contain an `x-devonthink-item` URI in the Finder comment, and trigger on certain events such as creating a document in DEVONthink. In the bottom part of the rule, there are two actions. First, it writes the `x-devonthink-item` URI into the Finder comment in DEVONthink (which DEVONthink shows to the user, but beware of some implications noted below); second, it runs a short AppleScript that invokes [Urial](https://github.com/mhucka/urial). The AppleScript code is the file `Write URI into Finder comment using Urial.applescript` in this directory. In order to make the script accessible as an external AppleScript file within DEVONthink, I compiled it and copied it to my `~/Library/Application Scripts/com.devon-technologies.think3/Smart Rules` directory (and called it "Write URI into Finder comment using Urial.scpt", which is the name you see in the rule definition above). 12 | 13 | The need for both actions is due to how DEVONthink behaves (at least up to version 3.8). When you modify the metadata field called "Finder Comments" in the DEVONthink user interface, it [does not actually write the field value to the macOS file](https://discourse.devontechnologies.com/t/how-can-i-make-finder-comments-added-in-dt-show-up-in-finder-get-info-box/68186) at that point. In fact, it never writes the metadata to the file properties unless the file is an indexed file or you export the file. Thus, merely updating the comment in DEVONthink is not enough to make it visible outside of DEVONthink, and an additional action is needed. 14 | 15 | I wrote [Urial](https://github.com/mhucka/urial) to help with this task. The program intelligently updates URIs in Finder comments rather than blindly replacing the comment text. 16 | -------------------------------------------------------------------------------- /devonthink-smart-rule-scripts/write-uri-into-comments/Write URI into Finder comment using Urial.applescript: -------------------------------------------------------------------------------- 1 | -- Summary: write the ref. URL of each record to its Finder comment. 2 | -- 3 | -- ╭─────────────────── Notice ── Notice ── Notice ───────────────────╮ 4 | -- │ This AppleScript code will only work as a script executed by a │ 5 | -- │ Smart Rule in DEVONthink. It won't work as a standalone script. │ 6 | -- ╰──────────────────────────────────────────────────────────────────╯ 7 | -- 8 | -- A DEVONthink "reference URL" is a URI that begins with the URI scheme 9 | -- "x-devonthink-item" and contains the UUID of an item in DEVONthink. 10 | -- This script reads the DEVONthink reference URL of each selected 11 | -- document in DEVONthink, and runs an external program (Urial) to set 12 | -- the document's Finder comment to that URI. The reason this is useful 13 | -- is that when you are editing a document in an external editor 14 | -- (something that is permitted by DEVONthink), there is no inherent 15 | -- information linking the file on disk to the database record that 16 | -- corresponds to it. Using the reference URL is convenient on macOS 17 | -- because of macOS's built-in URI scheme handler mechanisms, but the 18 | -- reference URL is not a property of the file on disk. If you want to 19 | -- know the reference URL of a given file, you have to come up with an 20 | -- approach to storing it yourself in such a way that external programs 21 | -- can find it. The approach taken here is to write it into the Finder 22 | -- comment. This makes it possible to find the DEVONthink document by 23 | -- reading the Finder comment (something that can be done in various 24 | -- ways, such as with the use of AppleScript) and asking macOS to open 25 | -- the document using the reference URI. For example, you can do 26 | -- "open x-devonthink-item://474AB439-369E-429D-A856-924DDABC" from a 27 | -- shell terminal and macOS will tell DEVONthink to open the document, 28 | -- effectively letting you jump directly to the document from outside 29 | -- of DEVONthink. 30 | -- 31 | -- Copyright 2024 Michael Hucka. 32 | -- License: MIT License – see file "LICENSE" in the project website. 33 | -- Website: https://github.com/mhucka/devonthink-hacks 34 | 35 | use AppleScript version "2.4" -- Yosemite (10.10) or later 36 | use scripting additions 37 | 38 | -- Config variables ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 39 | 40 | -- This list is used to set the shell's command search $PATH. The values 41 | -- cover the most likely places where Urial may be installed. If your 42 | -- copy of Urial is not found in one of these locations, add the 43 | -- appropriate directory to this list. 44 | 45 | property shell_paths: { ¬ 46 | "$PATH" , ¬ 47 | "$HOME/.local/bin" , ¬ 48 | "$HOME/.pyenv/shims" , ¬ 49 | "$HOME/.pyenv/bin" , ¬ 50 | "$HOME/bin" , ¬ 51 | "/usr/local/bin" , ¬ 52 | "/opt/homebrew/bin" ¬ 53 | } 54 | 55 | -- Helper functions ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 56 | 57 | on concat(string_list, separator) 58 | set output to "" 59 | repeat with i from 1 to count of string_list 60 | set output to output & item i of string_list 61 | if i < count of string_list then 62 | set output to output & separator 63 | end if 64 | end repeat 65 | return output 66 | end concat 67 | 68 | on sh(shell_command) 69 | set paths to "PATH=" & my concat(shell_paths, ":") 70 | set result to do shell script (paths & " " & shell_command) 71 | end shell_cmd 72 | 73 | -- The following function is based on code posted by user "mb21" on 74 | -- 2016-06-26 at https://stackoverflow.com/a/38042023/743730 75 | 76 | on substituted(search_string, replacement_string, this_text) 77 | set AppleScript's text item delimiters to the search_string 78 | set the item_list to every text item of this_text 79 | set AppleScript's text item delimiters to the replacement_string 80 | set this_text to the item_list as string 81 | set AppleScript's text item delimiters to "" 82 | return this_text 83 | end substituted 84 | 85 | -- Main body ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 86 | 87 | on performSmartRule(selected_records) 88 | -- The "tell" here isn't needed for DEVONthink (this whole script is 89 | -- executed by DEVONthink), but osacompile gives errors without it. 90 | tell application id "DNtp" 91 | try 92 | repeat with _record in selected_records 93 | set uri to reference URL of the _record as string 94 | set file_path to the path of _record 95 | 96 | -- Some chars in file names are problematic due to having 97 | -- special meaning to the shell. Need to quote them, but 98 | -- here, need to use 2 blackslashes, b/c the 1st backslash 99 | -- will be removed when the string is handed to the shell. 100 | set file_path to my substituted("&", "\\\\&", file_path) 101 | 102 | -- Another problem for shell strings is embedded single 103 | -- quotes. Combo of changing the text delimiter & using 104 | -- the AS "quoted form of" below seems to do the trick. 105 | set AppleScript's text item delimiters to "\\\\" 106 | set fp to quoted form of file_path 107 | 108 | set out to my sh("urial -m update -U " & uri & " " & fp) 109 | 110 | -- Display a notification if urial returned a msg. 111 | if out is not equal to "" then 112 | display alert "Urial" message out as warning 113 | end if 114 | end repeat 115 | on error msg number code 116 | if the code is not -128 then 117 | display alert "DEVONthink" message msg as warning 118 | end if 119 | end try 120 | end tell 121 | end performSmartRule 122 | -------------------------------------------------------------------------------- /devonthink-smart-rule-scripts/write-uri-into-comments/smart-rule-screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mhucka/devonthink-hacks/9a4211aa29f9163ce625b9fb9ede2dccdb6546f1/devonthink-smart-rule-scripts/write-uri-into-comments/smart-rule-screenshot.png -------------------------------------------------------------------------------- /devonthink-smart-rule-scripts/zoinks-scripts/Add Zotero abstract.applescript: -------------------------------------------------------------------------------- 1 | -- Set custom metadata "abstract" using Zonks 2 | -- 3 | -- Copyright 2024 Michael Hucka. 4 | -- License: MIT License – see file "LICENSE" in the project website. 5 | -- Website: https://github.com/mhucka/devonthink-hacks 6 | -- 7 | -- This is an AppleScript fragment that will only work as the script 8 | -- executed by a Smart Rule in DEVONthink. It runs Zoinks to get the 9 | -- abstract for a Zotero record (technically, what Better BibTeX alls 10 | -- the "abstractNote" field), and sets a metadata field in DEVONthink 11 | -- for storing the abstract. If there is no abstract in the Zotero 12 | -- record, this sets the metadata field value to "(No abstract.)". 13 | -- 14 | -- This expects to be passed a record that has a zotero://select/... 15 | -- link in its "URL" metadata field. This Zotero link value is set by 16 | -- a separate DEVONthink Smart Rule that runs another program, Zowie. 17 | -- (C.f. https://github.com/mhucka/devonthink-hacks/zowie-scripts) 18 | 19 | on performSmartRule(selectedRecords) 20 | tell application id "DNtp" 21 | try 22 | repeat with _record in selectedRecords 23 | set _abstract to do shell script ¬ 24 | "echo " & (URL of _record) & " | " & ¬ 25 | "PATH=$PATH:$HOME/.local/bin:$HOME/.pyenv/shims:$HOME/.pyenv/bin:/usr/local/bin:/opt/homebrew/bin" ¬ 26 | & " zoinks -U abstractNote" 27 | if _abstract ≠ "" then 28 | -- Remove embedded newlines in the abstract. 29 | set _abstract to do shell script ¬ 30 | "echo " & quoted form of _abstract & "| tr -s '\\r\\n' ' '" 31 | else 32 | set _abstract to "(No abstract.)" 33 | end if 34 | add custom meta data _abstract for "abstract" to _record 35 | end repeat 36 | on error msg number code 37 | if the code is not -128 then 38 | display alert "Zoinks" message msg as warning 39 | end if 40 | end try 41 | end tell 42 | end performSmartRule 43 | -------------------------------------------------------------------------------- /devonthink-smart-rule-scripts/zoinks-scripts/Add Zotero citekey.applescript: -------------------------------------------------------------------------------- 1 | -- Set custom metadata "abstract" using Zonks 2 | -- 3 | -- Copyright 2024 Michael Hucka. 4 | -- License: MIT License – see file "LICENSE" in the project website. 5 | -- Website: https://github.com/mhucka/devonthink-hacks 6 | -- 7 | -- This is an AppleScript fragment that will only work as the script 8 | -- executed by a Smart Rule in DEVONthink. It runs Zoinks to get the 9 | -- Better BibTeX citation key for a Zotero record, and sets a custom 10 | -- metadata field in DEVONthink for storing the citation key. 11 | -- 12 | -- This expects to be passed a record that has a zotero://select/... link 13 | -- in its "URL" metadata field. This Zotero link value is set by a 14 | -- separate DEVONthink Smart Rule that runs another program, Zowie. 15 | -- (C.f. https://github.com/mhucka/devonthink-hacks/zowie-scripts) 16 | 17 | on performSmartRule(selectedRecords) 18 | tell application id "DNtp" 19 | try 20 | repeat with _record in selectedRecords 21 | set _citekey to do shell script ¬ 22 | "echo " & (URL of _record) & " | " & ¬ 23 | "PATH=$PATH:$HOME/.local/bin:$HOME/.pyenv/shims:$HOME/.pyenv/bin:/usr/local/bin:/opt/homebrew/bin" ¬ 24 | & " zoinks -U citekey" 25 | if _citekey ≠ "" then 26 | add custom meta data _citekey for "citekey" to _record 27 | else 28 | display notification ¬ 29 | "Could not get citekey for " & (name of _record) 30 | end if 31 | end repeat 32 | on error msg number code 33 | if the code is not -128 then 34 | display alert "Zoinks" message msg as warning 35 | end if 36 | end try 37 | end tell 38 | end performSmartRule 39 | -------------------------------------------------------------------------------- /devonthink-smart-rule-scripts/zoinks-scripts/Makefile: -------------------------------------------------------------------------------- 1 | ../Makefile -------------------------------------------------------------------------------- /devonthink-smart-rule-scripts/zoinks-scripts/README.md: -------------------------------------------------------------------------------- 1 | Run Zoinks 2 | ========= 3 | 4 | Scripts for running [Zoinks](https://mhucka.github.io/zoinks). 5 | -------------------------------------------------------------------------------- /devonthink-smart-rule-scripts/zoinks-scripts/t.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | # See https://github.com/tobywf/pasteboard 4 | 5 | from appscript import app 6 | from commonpy.network_utils import net 7 | import json 8 | import pasteboard 9 | import sys 10 | 11 | bbt_url = 'http://localhost:23119/better-bibtex/json-rpc' 12 | 13 | # pb = pasteboard.Pasteboard() 14 | # text = pb.get_contents() 15 | 16 | app = app('DEVONthink 3') 17 | if not app.isrunning(): 18 | exit('DEVONthink is not running') 19 | 20 | windows = app.think_window() 21 | front_window = windows[0] 22 | selected_docs = front_window.selection() 23 | 24 | selected = selected_docs[0] 25 | url = selected.URL() 26 | 27 | # zotero://select/library/items/5A94F2PP 28 | if url.startswith('zotero:'): 29 | key = url.split('/')[-1] 30 | 31 | headers = {'Content-Type': 'application/json', 'Accept' : 'application/json'} 32 | data = ('{"jsonrpc": "2.0", "method": "items.citationkey", ' 33 | + '"params": [["' + key + '"]] }') 34 | 35 | (response, error) = net('post', bbt_url, headers = headers, data = data) 36 | if not error: 37 | bbt_output = json.loads(response.text) 38 | if 'result' in bbt_output and key in bbt_output['result']: 39 | print(bbt_output['result'][key]) 40 | sys.exit(0) 41 | 42 | print('no zotero URL in clipboard') 43 | -------------------------------------------------------------------------------- /devonthink-smart-rule-scripts/zotero-scripts/Makefile: -------------------------------------------------------------------------------- 1 | ../Makefile -------------------------------------------------------------------------------- /devonthink-smart-rule-scripts/zotero-scripts/Start Zotero + BBT if needed.applescript: -------------------------------------------------------------------------------- 1 | # Summary: if Zotero & BBT are not running, launch & wait until they respond. 2 | # 3 | # This assumes that the Better BibTeX plugin has been installed in Zotero 4 | # (see https://retorque.re/zotero-better-bibtex/) but it should be safe 5 | # to use even if the plugin is not installed. (If it's not installed, this 6 | # program will end up waiting the full duration of the wait_time value.) 7 | # 8 | # Copyright 2024 Michael Hucka. 9 | # License: MIT License – see file "LICENSE" in the project website. 10 | # Website: https://github.com/mhucka/devonthink-hacks 11 | 12 | use AppleScript version "2.5" 13 | use scripting additions 14 | 15 | # Config variables ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 16 | 17 | # URL of the Zotero Connector endpoint for Better BibTeX. 18 | property bbt_api_endpoint: "http://localhost:23119/better-bibtex/json-rpc" 19 | 20 | # Approximate duration to wait for Zotero + BBT to start, in seconds. 21 | property wait_time: 20 22 | 23 | 24 | # Helper functions ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 25 | 26 | # Log a message in DEVONthink's log and include the name of this script. 27 | on report(error_text) 28 | local script_path 29 | tell application "System Events" 30 | set script_path to POSIX path of (path to me as alias) 31 | end tell 32 | tell application id "DNtp" 33 | log message script_path info error_text 34 | end tell 35 | log error_text # Useful when running in a debugger. 36 | end report 37 | 38 | # Launch application, wait to see it launched, and bring it to the front. 39 | # Only waits for a limited time, in case something is wrong. 40 | on launch_application(app_name, activate_app) 41 | launch application app_name 42 | tell application "System Events" 43 | set times_left to 30 # 15 sec in 0.5 sec iterations 44 | # This roundabout approach of testing process names is because the more 45 | # direct "repeat until application app_name is running" causes errors. 46 | repeat while times_left > 0 47 | if count of (every process whose name is app_name) > 0 then 48 | exit repeat 49 | end if 50 | delay 0.5 51 | set times_left to (times_left - 1) 52 | end repeat 53 | if times_left ≤ 0 then 54 | error "Timed out waiting for " & app_name & " to launch." 55 | end if 56 | end tell 57 | if activate_app then 58 | tell application app_name to activate 59 | end if 60 | end launch_application 61 | 62 | # Return true if we connected to the BBT endpoint URL in < max_time seconds, 63 | # false if there was no response, and an integer HTTP status code if there was 64 | # a response but it was an HTTP code indicating a problem. 65 | on connect_to_bbt(max_time) 66 | script wrapperScript 67 | property ca: a reference to current application 68 | use framework "Foundation" 69 | on connect_to_bbt(max_time) 70 | # Create the request object with a timeout. 71 | set url_str to ca's NSURL's URLWithString:bbt_api_endpoint 72 | set ignore_cache to ca's NSURLRequestReloadIgnoringCacheData 73 | set request to ca's NSURLRequest's alloc()'s initWithURL:url_str ¬ 74 | cachePolicy:(ignore_cache) timeoutInterval:max_time 75 | 76 | # Try to connect. 77 | set {body, response, err} to ca's NSURLConnection's ¬ 78 | sendSynchronousRequest:request ¬ 79 | returningResponse:(reference) |error|:(reference) 80 | 81 | # Interpret the outcome. No err doesn't necessarily mean success. 82 | if (err is not missing value) or (response is missing value) then 83 | return false 84 | else if response's statusCode() >= 400 then 85 | # This shouldn't happen. Something is wrong with the endpoint. 86 | return response's statusCode() 87 | else 88 | return true 89 | end if 90 | end connect_to_bbt 91 | end script 92 | return wrapperScript's connect_to_bbt(max_time) 93 | end connect_to_bbt 94 | 95 | 96 | # Main body ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 97 | 98 | on performSmartRule(selected_records) 99 | my launch_application("Zotero", false) 100 | tell application "Zotero" 101 | # Better BibTeX takes time to start up after Zotero is running. 102 | # Wait until we can connect to the JSON-RPC endpoint. Note: it's 103 | # okay to modify wait_time directly b/c every execution of this 104 | # script from a Smart Rule will reload the file and thus reset it. 105 | set logged_error to false 106 | repeat while wait_time > 0 107 | set bbt_connection to my connect_to_bbt(1) 108 | if bbt_connection is true then 109 | return 110 | else if (bbt_connection is not false) and not logged_error then 111 | my report("Unable to connect to the Zotero Connector " ¬ 112 | & "endpoint for Better BibTeX (HTTP code " ¬ 113 | & bbt_connection & ") at " & bbt_api_endpoint) 114 | set logged_error to true 115 | end if 116 | set wait_time to (wait_time - 1) 117 | delay 1 118 | end repeat 119 | my report("Wait time exceeded for starting Zotero & Better BibTeX") 120 | end tell 121 | end performSmartRule 122 | 123 | # Scaffolding for execution outside of a Smart Rule (e.g., in a debugger). 124 | tell application id "DNtp" 125 | my performSmartRule(selection as list) 126 | end tell 127 | -------------------------------------------------------------------------------- /devonthink-smart-rule-scripts/zotero-scripts/tests/test-framework-failure.applescript: -------------------------------------------------------------------------------- 1 | -- This script will produce an error if used as the 2 | 3 | use AppleScript version "2.5" 4 | use framework "Foundation" 5 | use scripting additions 6 | 7 | on performSmartRule(selectedRecords) 8 | tell application id "DNtp" 9 | try 10 | repeat with theRecord in selectedRecords 11 | set theRecord to item 1 of selectedRecords 12 | display dialog (name of theRecord) as text 13 | end repeat 14 | on error msg number code 15 | if the code is not -128 then 16 | display alert "DEVONthink" message msg as warning 17 | end if 18 | end try 19 | end tell 20 | end performSmartRule 21 | -------------------------------------------------------------------------------- /devonthink-smart-rule-scripts/zotero-scripts/tests/test-ping.applescript: -------------------------------------------------------------------------------- 1 | use AppleScript version "2.5" 2 | use framework "Foundation" 3 | 4 | on ping(api_url, max_time) 5 | -- Create the request object. 6 | set ca to current application 7 | set url_string to ca's NSURL's URLWithString:api_url 8 | set ignore_cache to ca's NSURLRequestReloadIgnoringCacheData 9 | set request to ca's NSURLRequest's alloc()'s initWithURL:url_string ¬ 10 | cachePolicy:(ignore_cache) timeoutInterval:max_time 11 | 12 | -- Try to connect. 13 | set {body, response, err} to ca's NSURLConnection's ¬ 14 | sendSynchronousRequest:request ¬ 15 | returningResponse:(reference) |error|:(reference) 16 | 17 | -- Interpret the outcome. No error does not necessarily mean success. 18 | if (err is not missing value) or (response is missing value) then 19 | return false 20 | else if {404, 410} contains response's statusCode() then 21 | return false 22 | else 23 | return true 24 | end if 25 | end ping 26 | 27 | if ping("https://httpstat.us/504?sleep=3000", 5) then 28 | log "yes (expected)" 29 | else 30 | log "no (unexpected)" 31 | end if 32 | 33 | if ping("https://httpstat.us/504?sleep=3000", 2) then 34 | log "yes (unexpected)" 35 | else 36 | log "no (expected)" 37 | end if 38 | -------------------------------------------------------------------------------- /devonthink-smart-rule-scripts/zotero-scripts/tests/test-without-framework.applescript: -------------------------------------------------------------------------------- 1 | -- This script will produce an error if used as the 2 | 3 | use AppleScript version "2.5" 4 | use scripting additions 5 | 6 | on performSmartRule(selectedRecords) 7 | tell application id "DNtp" 8 | try 9 | repeat with theRecord in selectedRecords 10 | set theRecord to item 1 of selectedRecords 11 | display dialog (name of theRecord) as text 12 | end repeat 13 | on error msg number code 14 | if the code is not -128 then 15 | display alert "DEVONthink" message msg as warning 16 | end if 17 | end try 18 | end tell 19 | end performSmartRule 20 | -------------------------------------------------------------------------------- /devonthink-smart-rule-scripts/zotero-scripts/tests/testapi.applescript: -------------------------------------------------------------------------------- 1 | use AppleScript version "2.4" 2 | use framework "Foundation" 3 | 4 | -- Function to check if an API endpoint responds. 5 | on ping(service_url) 6 | -- Create the request object. 7 | set ca to current application 8 | set _url to ca's |NSURL|'s URLWithString:service_url 9 | set request to ca's |NSURLRequest|'s requestWithURL:_url 10 | 11 | -- Try to connect to the endpoint. 12 | set responseData to missing value 13 | set status to missing value 14 | 15 | set _response to ca's NSHTTPURLResponse's alloc()'s init() 16 | set {_data, _error} to ca's NSURLConnection's sendSynchronousRequest:request ¬ 17 | returningResponse:_response |error|:(reference) 18 | 19 | if _error is missing value then 20 | -- No error => connected. 21 | return true 22 | else if class of _error is ca's NSURLError then 23 | -- Could not connect to the URL 24 | return false 25 | else 26 | log "Unexpected error code received by ping()." 27 | return false 28 | end if 29 | end ping 30 | 31 | -- Example usage 32 | set apiURL to "http://localhost:23119/better-bibtex/json-rpc" 33 | set responseStatus to ping(apiURL) 34 | 35 | -------------------------------------------------------------------------------- /devonthink-smart-rule-scripts/zowie-scripts/Makefile: -------------------------------------------------------------------------------- 1 | ../Makefile -------------------------------------------------------------------------------- /devonthink-smart-rule-scripts/zowie-scripts/README.md: -------------------------------------------------------------------------------- 1 | Run Zowie 2 | ========= 3 | 4 | Scripts for running [Zowie](https://mhucka.github.io/zowie). 5 | -------------------------------------------------------------------------------- /devonthink-smart-rule-scripts/zowie-scripts/Run Zowie on newly indexed PDF.applescript: -------------------------------------------------------------------------------- 1 | -- Script for DEVONthink smart rule to run Zowie on new additions 2 | -- 3 | -- This is an AppleScript fragment that will only work as the script 4 | -- executed by a Smart Rule in DEVONthink. 5 | -- 6 | -- Copyright 2024 Michael Hucka. 7 | -- License: MIT License – see file "LICENSE" in the project website. 8 | -- Website: https://github.com/mhucka/devonthink-hacks 9 | 10 | on performSmartRule(selectedRecords) 11 | tell application "System Events" 12 | -- In my environment, Zotero takes time to upload a newly-added 13 | -- PDF to the cloud. The following delay is needed to give time 14 | -- for the upload to take place, so that when Zowie runs and 15 | -- queries Zotero via the network API, the data will be there. 16 | delay 15 17 | end tell 18 | tell application id "DNtp" 19 | try 20 | repeat with _record in selectedRecords 21 | set raw_path to the path of _record 22 | 23 | -- A problem for shell strings is embedded single quotes. 24 | -- Combo of changing text delimiters & using AppleScript 25 | -- "quoted form of" seems to do the trick. 26 | set AppleScript's text item delimiters to "\\\\" 27 | set quoted_path to quoted form of raw_path 28 | 29 | -- Now run Zowie. The PATH setting adds common locations 30 | -- where Zowie may be installed on the user's computer. 31 | set result to do shell script ¬ 32 | "PATH=$PATH:$HOME/.local/bin:$HOME/.pyenv/shims:$HOME/.pyenv/bin:/usr/local/bin:/opt/homebrew/bin" ¬ 33 | & " zowie -s -q " & quoted_path 34 | 35 | -- If Zowie returned a msg, something went wrong. 36 | if result ≠ "" then 37 | display notification result 38 | else 39 | -- Finish by telling DT explicitly to set the comment, 40 | -- because it doesn't consistently "notice" addition 41 | -- of a comment after a file has already been indexed. 42 | set comment of _record to my finderComment(raw_path) 43 | end if 44 | end repeat 45 | on error msg number code 46 | if the code is not -128 then 47 | display alert "DEVONthink" message msg as warning 48 | end if 49 | end try 50 | end tell 51 | end performSmartRule 52 | 53 | -- Function to ask the Finder to read the comment. 54 | 55 | on finderComment(f) 56 | tell application "Finder" 57 | return comment of (POSIX file f as alias) 58 | end tell 59 | end finderComment 60 | 61 | -- Function to make literal text substitutions. 62 | -- The following function is based on code posted by user "mb21" on 63 | -- 2016-06-26 at https://stackoverflow.com/a/38042023/743730 64 | 65 | on substituted(search_string, replacement_string, this_text) 66 | set AppleScript's text item delimiters to the search_string 67 | set the item_list to every text item of this_text 68 | set AppleScript's text item delimiters to the replacement_string 69 | set this_text to the item_list as string 70 | set AppleScript's text item delimiters to "" 71 | return this_text 72 | end substituted 73 | -------------------------------------------------------------------------------- /devonthink-templates/Annotations.noindex/Reading notes.md: -------------------------------------------------------------------------------- 1 | CSS: x-devonthink-item://C1043FB7-A957-4CCA-848D-D3FDB79B5C62 2 | Backlink: %documentLink% 3 | 4 | %documentName% 5 | 6 | {{firstheading}}Full reference{{_heading}} 7 | 8 | %bibReference% 9 | 10 | {{heading}}Topic area{{_heading}} 11 | 12 | _(Briefly, what's the subject? Use some common identifying keywords)_ 13 | 14 | {{heading}}Summary{{_heading}} 15 | 16 | _(Put myself in the mind of summarizing the work in an email to a colleague)_ 17 | 18 | - [ ] Who are the authors? 19 | - [ ] What did they do? 20 | - [ ] How did they do it? 21 | - [ ] What were their results? 22 | 23 | {{heading}}Evaluation and reflection{{_heading}} 24 | 25 | - [ ] Clear aims and objectives? 26 | - [ ] Well-defined hypothesis? 27 | - [ ] Reasonable methods? 28 | - [ ] Reasonable analyses? 29 | - [ ] Reasonable conclusions? 30 | - [ ] Strengths? 31 | - [ ] Weaknesses/limitations? 32 | - [ ] Credibility of this work? 33 | 34 | {{heading}}Important outcomes or ideas{{_heading}} 35 | 36 | _(What are notable, new, interesting, significant points about this work?)_ 37 | 38 | {{heading}}What are related works?{{_heading}} 39 | 40 | _(Link to other papers)_ 41 | 42 | {{heading}}Checklist{{_heading}} 43 | 44 | _(Delete this section & heading when done)_ 45 | 46 | - [ ] subject tags 47 | - [ ] result type tag 48 | - [ ] value rating 49 | - [ ] confidence tags 50 | -------------------------------------------------------------------------------- /devonthink-templates/README.md: -------------------------------------------------------------------------------- 1 | # Template files I use in DEVONthink 2 | 3 | This contains template files that I use for creating certain kinds of new documents from within DEVONthink. Beware that these are extremely idiosyncratic to my way of doing things, and moreover, make a lot of assumptions about other software I use, and other things I set up in DEVONthink (like smart rules), so they are unlikely to be useful as-is for anyone else. Still, I need to keep them in version control _somewhere_, so they're here. 4 | 5 | Note that some of these may be old and no longer in active use. 6 | 7 | ## Setup 8 | 9 | DEVONthink doesn't provide a way for users to store their templates in a user-defined location. To be accessible in DEVONthink, template files _must_ be located in these locations: 10 | 11 | * `~/Library/Application Support/DEVONthink 3/Annotations.noindex` (for creating annotation files from the _Annotations & Reminders_ inspector) 12 | * `~/Library/Application Support/DEVONthink 3/Templates.noindex` (for creating new documents using the menu item DataNew From Template) 13 | 14 | Those folders must exist in those locations, but at the file system level, DEVONthink can't distinguish between an actual directory and a symbolic link to a directory. Thus, what we can do is move those directories elsewhere, and replace the locations above with symbolic links. 15 | 16 | ```sh 17 | cd "~Library/Application Support/DEVONthink 3" 18 | mv Annotations.noindex ~/projects/software/repos/devonthink-hacks/devonthink-templates/ 19 | mv Templates.noindex ~/projects/software/repos/devonthink-hacks/devonthink-templates/ 20 | ln -s ~/projects/software/repos/devonthink-hacks/devonthink-templates/Annotations.noindex 21 | ln -s ~/projects/software/repos/devonthink-hacks/devonthink-templates/Templates.noindex 22 | ``` 23 | 24 | DEVONthink includes a lot of default templates, and when you upgrade DEVONthink, it rewrites its default templates in `Templates.noindex`. Thankfully all of those DEVONthink-provided templates are in subdirectories, so we can tell them apart from personal additions. I don't keep their defaults in version control and there's a `.gitignore` file inside `Templates.noindex` here to leave them out of git. 25 | 26 | (I personally don't use any of the default templates, and would rather that DEVONthink didn't keep adding them back to my `Templates.noindex` directory. It would be possible to prevent DEVONthink from overwriting `Templates.noindex` by locking the directory, but then it would be a pain to update my own templates. So, I live with the current situation.) 27 | 28 | To make the templates available on my iPad and iPhone, I create hard links in an iCloud storage folder. On the Mac, at leat in macOS Ventura, the location of a user's iCloud drive is 29 | 30 | ``` 31 | ~/Library/Mobile Documents/com~apple~CloudDocs/ 32 | ``` 33 | 34 | Inside that, I create a `DEVONthink templates` directory, then inside _that_, I put hard links to all the template files folder. The files are in a flat organization, without subdirectories, because I don't have enough templates to be worth making it more complicated than that. 35 | -------------------------------------------------------------------------------- /devonthink-templates/Templates.noindex/.gitignore: -------------------------------------------------------------------------------- 1 | Classifications/ 2 | Education/ 3 | Productivity/ 4 | Registers/ 5 | Smart\ Groups/ 6 | Accounts\ &\ Passwords/ 7 | -------------------------------------------------------------------------------- /devonthink-templates/Templates.noindex/Brief note.ooutline: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mhucka/devonthink-hacks/9a4211aa29f9163ce625b9fb9ede2dccdb6546f1/devonthink-templates/Templates.noindex/Brief note.ooutline -------------------------------------------------------------------------------- /devonthink-templates/Templates.noindex/Card.md: -------------------------------------------------------------------------------- 1 | {{cardtitle}}%title%{{_cardtitle}} 2 | -------------------------------------------------------------------------------- /devonthink-templates/Templates.noindex/Clipboard to markdown.md: -------------------------------------------------------------------------------- 1 | Title: %@ 2 | Creation date: %shortDate% 3 | DEVONthink database: %databaseName% 4 | 5 | %clipboard% 6 | -------------------------------------------------------------------------------- /devonthink-templates/Templates.noindex/Code.md: -------------------------------------------------------------------------------- 1 | ``` 2 | %clipboard% 3 | ``` 4 | 5 | Source: [%sourceTitle%](%sourceURL%) (copied on %shortDate% at %time%). 6 | -------------------------------------------------------------------------------- /devonthink-templates/Templates.noindex/Diary.ooutline: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mhucka/devonthink-hacks/9a4211aa29f9163ce625b9fb9ede2dccdb6546f1/devonthink-templates/Templates.noindex/Diary.ooutline -------------------------------------------------------------------------------- /devonthink-templates/Templates.noindex/Empty markdown.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mhucka/devonthink-hacks/9a4211aa29f9163ce625b9fb9ede2dccdb6546f1/devonthink-templates/Templates.noindex/Empty markdown.md -------------------------------------------------------------------------------- /devonthink-templates/Templates.noindex/Glossary.md: -------------------------------------------------------------------------------- 1 | Base Header Level: 0 2 | 3 | ### %title% 4 | -------------------------------------------------------------------------------- /devonthink-templates/Templates.noindex/Goal plan.ooutline: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mhucka/devonthink-hacks/9a4211aa29f9163ce625b9fb9ede2dccdb6546f1/devonthink-templates/Templates.noindex/Goal plan.ooutline -------------------------------------------------------------------------------- /devonthink-templates/Templates.noindex/Markdown.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mhucka/devonthink-hacks/9a4211aa29f9163ce625b9fb9ede2dccdb6546f1/devonthink-templates/Templates.noindex/Markdown.md -------------------------------------------------------------------------------- /devonthink-templates/Templates.noindex/Meeting.ooutline: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mhucka/devonthink-hacks/9a4211aa29f9163ce625b9fb9ede2dccdb6546f1/devonthink-templates/Templates.noindex/Meeting.ooutline -------------------------------------------------------------------------------- /devonthink-templates/Templates.noindex/Notes.ooutline: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mhucka/devonthink-hacks/9a4211aa29f9163ce625b9fb9ede2dccdb6546f1/devonthink-templates/Templates.noindex/Notes.ooutline -------------------------------------------------------------------------------- /devonthink-templates/Templates.noindex/Quote.md: -------------------------------------------------------------------------------- 1 | > %clipboard% 2 | 3 | Source: [%sourceTitle%](%sourceURL%) (copied on %shortDate% at %time%). 4 | 5 | -------------------------------------------------------------------------------- /devonthink-templates/Templates.noindex/Records markdown.md: -------------------------------------------------------------------------------- 1 | --- 2 | File: "%fileName%" 3 | Creation-date: %sortableDate% 4 | DEVONthink-database: "%databaseName%" 5 | --- 6 | -------------------------------------------------------------------------------- /devonthink-templates/Templates.noindex/Spreadsheet.numbers: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mhucka/devonthink-hacks/9a4211aa29f9163ce625b9fb9ede2dccdb6546f1/devonthink-templates/Templates.noindex/Spreadsheet.numbers -------------------------------------------------------------------------------- /devonthink-templates/Templates.noindex/Term definition.md: -------------------------------------------------------------------------------- 1 | Base Header Level: 0 2 | 3 | ### %title% 4 | 5 | #### Synonyms 6 | 7 | #### Related 8 | -------------------------------------------------------------------------------- /devonthink-templates/makelinks.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | # Summary: create hard links to these template files in an iCloud folder. 3 | # 4 | # Copyright 2024 Michael Hucka. 5 | # License: MIT license – see file "LICENSE" in the project website. 6 | # Website: https://github.com/mhucka/devonthink-hacks 7 | 8 | set -e 9 | 10 | thisdir="$(cd -- "$(dirname "$0")" >/dev/null 2>&1 ; pwd -P)" 11 | clouddir="${HOME}/Library/Mobile Documents/com~apple~CloudDocs/DEVONthink templates" 12 | 13 | # Dealing with spaces in file names is such a PITA. 14 | IFS=$'\n' 15 | for path in `find -E *.noindex -maxdepth 2 -iregex '.*\.(md|ooutline|numbers)'`; do 16 | filename=$(basename "$path") 17 | ln "$thisdir/$path" "$clouddir/" 18 | done 19 | -------------------------------------------------------------------------------- /devonthink-toolbar-scripts/.gitignore: -------------------------------------------------------------------------------- 1 | */icon.png 2 | -------------------------------------------------------------------------------- /devonthink-toolbar-scripts/Makefile: -------------------------------------------------------------------------------- 1 | # Summary: my generic Makefile for compiling AppleScript files. 2 | # 3 | # Copyright 2024 Michael Hucka. 4 | # License: MIT License – see file "LICENSE" in the project website. 5 | # Website: https://github.com/mhucka/devonthink-hacks 6 | 7 | # Preliminary settings and tests ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 8 | 9 | SHELL=/bin/bash 10 | .ONESHELL: # Run all commands in the same shell. 11 | .SHELLFLAGS += -e # Exit at the first error. 12 | 13 | thisdir := $(shell basename $(CURDIR)) 14 | 15 | # When I run M-x compile using this Makefile, the compile target works but the 16 | # install target fails. It works outside Emacs in a regular shell terminal. I 17 | # haven't figured out the reason, so for now, this test reminds me to avoid it. 18 | 19 | ifeq ($(origin INSIDE_EMACS),environment) 20 | $(error "Do not run make from inside Emacs with this Makefile.") 21 | endif 22 | 23 | # This Makefile uses syntax that needs at least GNU Make version 3.82. 24 | # The following test is based on the approach posted by Eldar Abusalimov to 25 | # Stack Overflow in 2012 at https://stackoverflow.com/a/12231321/743730 26 | 27 | ifeq ($(filter undefine,$(value .FEATURES)),) 28 | $(error Unsupported version of Make. \ 29 | This Makefile does not work properly with GNU Make $(MAKE_VERSION); \ 30 | it needs GNU Make version 3.82 or later) 31 | endif 32 | 33 | # Before we go any further, test if certain programs are available. 34 | # The following is based on the approach posted by Jonathan Ben-Avraham to 35 | # Stack Overflow in 2014 at https://stackoverflow.com/a/25668869 36 | 37 | programs_needed = osacompile yq fileicon convert 38 | TEST := $(foreach p,$(programs_needed),\ 39 | $(if $(shell which $(p)),_,$(error Cannot find program "$(p)"))) 40 | 41 | 42 | # Print help if no command is given ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 43 | 44 | # The help scheme works by looking for lines that begin with "#:" above the 45 | # definitions of commands. Originally based on code posted to Stack Overflow on 46 | # 2019-11-28 by Richard Kiefer at https://stackoverflow.com/a/59087509/743730 47 | 48 | #: Print a summary of available commands. 49 | help: 50 | @echo "This is the Makefile for $(bright)$(thisdir)$(reset)." 51 | @echo "Available commands:" 52 | @echo 53 | @grep -B1 -E "^[a-zA-Z0-9_-]+\:([^=]|$$)" $(MAKEFILE_LIST) \ 54 | | grep -v -- -- \ 55 | | sed 'N;s/\n/###/' \ 56 | | sed -n 's/^#: \(.*\)###\([a-zA-Z0-9_-]*\):.*/$(color)\2$(reset):###\1/p' \ 57 | | column -t -s '###' 58 | 59 | 60 | # Create icon file ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 61 | 62 | # This assumes the file is an SVG file from Noun Project. The steps here are: 63 | # 64 | # 1. Filter the SVG file to remove the text. Yes, this removes the author and 65 | # source attributions, but I think that's okay because (a) this is for my 66 | # personal use on my own computer -- I'm not distributing the result -- and 67 | # (b) I attribute the icon author in the README.md file in this directory. 68 | # 2. Filter the SVG file to change the color from black to a green-gray, so 69 | # that when they're added to the toolbar, I can tell which icons are mine. 70 | # 3. Convert SVG to PNG. 71 | # 4. Add some whitespace padding around the icon, based on the proportions of 72 | # that I see in icons in DEVONthink (vers. 3.9.4 on macOS 10.13.6). 73 | # 5. Turn white background to transparent. 74 | 75 | icon.png: icon.svg 76 | yq -p xml -o xml 'del(.svg.text)' icon.svg > cleaned.svg 77 | sed 's/#000000/#7fc43b/' < cleaned.svg > green.svg 78 | convert -trim -size "400x400>" green.svg green.png 79 | convert -background white -gravity center -extent 512x512 \ 80 | -alpha remove -alpha off green.png tmp.png 81 | convert -fuzz 25% -fill "#7fc43b" -opaque "#000000" tmp.png green.png 82 | convert green.png -fuzz 10% -transparent white icon.png 83 | rm -f cleaned.svg green.svg green.png tmp.png 84 | 85 | 86 | # Compile source files ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 87 | 88 | # Getting Make to handle path names with embedded spaces is insanely hard. 89 | # The following incredibly obscure workarounds are the combination of two 90 | # methods: (1) by user "mathematical.coffee" posted on 2014-02-11 91 | # at https://stackoverflow.com/a/21694624/743730, and (2) by user Yann-Gaël 92 | # Guéhéneuc posted on 2016-02-25 at https://stackoverflow.com/a/35617603/743730 93 | 94 | # We first create a list of source files in a way that bypasses the regular 95 | # Make file pattern matching, and then replace every space character in 96 | # each pathname with a question mark character. 97 | 98 | sources = $(shell find . -iname '*.applescript' -maxdepth 1 | cut -d/ -f2- | tr ' ' '?') 99 | 100 | # Next, define a function named "q2s" to replace question marks by spaces, 101 | # using an absolutely bonkers trick to assign a space character to "space". 102 | 103 | space := 104 | space += 105 | q2s = $(subst ?, $(space),$1) 106 | 107 | # Now define the pattern rule to create compile .scpt files from .applescript 108 | # files. A rule of the form %.scpt would not work if the file names had space 109 | # characters in them (because the outcome of Make expanding %.scpt would have 110 | # spaces in the result, and those spaces would act as file name delimiters). 111 | # But if the file names used with this pattern rule have question marks 112 | # instead of spaces, they're atomic units as far as the pattern matching 113 | # mechanism is concerned, and so the rule works. We just have to do some 114 | # substitution to reconstruct the real file names before we run commands that 115 | # act on the files themselves. (That's where q2s comes in.) 116 | 117 | .SECONDEXPANSION: 118 | %.scpt: %.applescript 119 | osacompile -o "$(call q2s,$*.scpt)" "$<" 120 | 121 | # The final ingredient is to define the target "compile" such that it depends 122 | # on a list of .scpt files. This is simply done by taking the list of source 123 | # files (names such as "Some?file?name.applescript") and replacing the 124 | # extensions. The resulting list (names like "Some?file?name.scpt") then 125 | # satisfies the %.scpt rule above. 126 | 127 | #: Compile the AppleScript files in this directory. 128 | compile: $(sources:.applescript=.scpt) icon.png 129 | fileicon set "$(call q2s,$<)" icon.png 130 | 131 | 132 | # Install binaries ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 133 | 134 | # The /Menu location is to add items to the "Scripts" menu that appears in some 135 | # DEVONthink windows such as group/folder lists. The /Toolbar location is for 136 | # making scripts available in the menubar of document windows. For the latter, 137 | # you need to quit and relaunch DEVONthink because the items are cached. 138 | 139 | # Note the use of ? in the directory name below, instead of the space character 140 | # (i.e. ~/Library/Application Scripts/com.devon-technologies.think3/Menu). 141 | 142 | destdir1 = "$(HOME)/Library/Application?Scripts/com.devon-technologies.think3/Menu" 143 | destdir2 = "$(HOME)/Library/Application?Scripts/com.devon-technologies.think3/Toolbar" 144 | 145 | .SECONDEXPANSION: 146 | $(destdir)/%.scpt: %.applescript 147 | install -bpS "$(call q2s,$*.scpt)" $(call q2s,$(destdir1)) 148 | install -bpS "$(call q2s,$*.scpt)" $(call q2s,$(destdir2)) 149 | 150 | #: Install the compiled scripts in DEVONthink's script directory. 151 | install: $(addprefix $(destdir)/,$(sources:.applescript=.scpt)) 152 | @echo "$(bright)Restart DEVONthink to use additions to the Toolbar.$(reset)" 153 | 154 | 155 | # Cleanup ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 156 | 157 | #: Clean this directory. 158 | clean: 159 | rm -rf *.scpt *.png 160 | 161 | 162 | # Miscellaneous directives ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 163 | 164 | #: Print a random joke from https://icanhazdadjoke.com/. 165 | joke: 166 | @echo "$(shell curl -s https://icanhazdadjoke.com/)" 167 | 168 | # Color codes used in messages. 169 | color := $(shell tput bold; tput setaf 6) 170 | bright := $(shell tput bold; tput setaf 15) 171 | dim := $(shell tput setaf 66) 172 | link := $(shell tput setaf 111) 173 | reset := $(shell tput sgr0) 174 | 175 | .PHONY: help clean joke 176 | -------------------------------------------------------------------------------- /devonthink-toolbar-scripts/copy-location-paths-of-selected-items/Copy location paths of selected items.applescript: -------------------------------------------------------------------------------- 1 | -- Summary: copy the absolute database paths of the selected items. 2 | -- 3 | -- Copyright 2024 Michael Hucka. 4 | -- License: MIT License – see file "LICENSE" in the project website. 5 | -- Website: https://github.com/mhucka/devonthink-hacks 6 | 7 | use AppleScript version "2.4" -- Yosemite (10.10) or later 8 | use scripting additions 9 | 10 | tell application id "DNtp" 11 | try 12 | set paths to "" 13 | -- Start with no linebreak for the first time through the loop. 14 | set linebreak to "" 15 | repeat with _record in (selected records) 16 | set p to (location of _record) & (name of _record) 17 | set paths to paths & linebreak & p 18 | -- Separate subsequent paths with linebreaks. This repeated 19 | -- assignment may seem inefficient, but the alternative is 20 | -- adding a conditinal in addition to having an assignment. 21 | set linebreak to character id 10 22 | end repeat 23 | set the clipboard to paths 24 | on error msg number err 25 | if err is not -128 then 26 | display alert "DEVONthink error" message msg as warning 27 | end if 28 | end try 29 | end tell 30 | -------------------------------------------------------------------------------- /devonthink-toolbar-scripts/copy-location-paths-of-selected-items/Makefile: -------------------------------------------------------------------------------- 1 | ../Makefile -------------------------------------------------------------------------------- /devonthink-toolbar-scripts/copy-location-paths-of-selected-items/README.md: -------------------------------------------------------------------------------- 1 | # Copy location paths of selected items 2 | 3 | For each item selected in the frontmost window of DEVONthink, this copies to the clipboard a link to the absolute path to the item in the DEVONthink database. This is a concatenation of the "location" property value and the document name. The result is a string that looks like, for example, `/path/to/the/itemname.md`. 4 | 5 | ## Compilation and installation 6 | 7 | The make procedures require the programs [ImageMagick](https://imagemagick.org/index.php) and [fileicon](https://github.com/mklement0/fileicon). Running `make compile` followed by `make install` in this directory will compile the AppleScript file, give it an icon, and copy the results to two locations: 8 | 9 | ```txt 10 | ~/Library/Application Scripts/com.devon-technologies.think3/Menu/ 11 | ~/Library/Application Scripts/com.devon-technologies.think3/Toolbar/ 12 | ``` 13 | 14 | By putting it in both locations, the program becomes accessible from both the _Scripts_ menu item that appears in certain window contexts in DEVONthink, and as individual toolbar items you can add to the toolbar in certain other contexts. (For example, in windows showing single documents like Markdown documents, there is no _Scripts_ toolbar item available, but programs you install in the `~/Library/.../Toolbar/` location become available as individual toolbar items after you restart DEVONthink.) 15 | 16 | 17 | ## About the icon 18 | 19 | The [vector artwork](https://thenounproject.com/icon/pointer-330358/) used as a starting point for the icon was created by [Shital Patel](https://thenounproject.com/shital777/) for the Noun Project. It is licensed under the Creative Commons [Attribution 3.0 Unported](https://creativecommons.org/licenses/by/3.0/deed.en) license. 20 | -------------------------------------------------------------------------------- /devonthink-toolbar-scripts/copy-location-paths-of-selected-items/icon.svg: -------------------------------------------------------------------------------- 1 | Created by Shital Patelfrom the Noun Project -------------------------------------------------------------------------------- /devonthink-toolbar-scripts/copy-markdown-links-to-selected-items/Copy Markdown links to selected items.applescript: -------------------------------------------------------------------------------- 1 | -- Summary: create Markdown-formatted links to the selected items. 2 | -- The links use the DEVONthink reference URLs of the items. 3 | -- 4 | -- Copyright 2024 Michael Hucka. 5 | -- License: MIT License – see file "LICENSE" in the project website. 6 | -- Website: https://github.com/mhucka/devonthink-hacks 7 | 8 | use AppleScript version "2.4" -- Yosemite (10.10) or later 9 | use scripting additions 10 | 11 | tell application id "DNtp" 12 | try 13 | set links to "" 14 | -- Start with no linebreak for the first time through the loop. 15 | set linebreak to "" 16 | repeat with _record in (selected records) 17 | set _name to name of _record 18 | set _ref_url to reference URL of _record 19 | set _link to "[" & _name & "](" & _ref_url & ")" 20 | set links to links & linebreak & _link 21 | -- Separate subsequent items with linebreaks. This repeated 22 | -- assignment may seem inefficient, but the alternative is 23 | -- adding a conditinal in addition to having an assignment. 24 | set linebreak to character id 10 25 | end repeat 26 | set the clipboard to links 27 | on error msg number err 28 | if err is not -128 then 29 | display alert "DEVONthink error" message msg as warning 30 | end if 31 | end try 32 | end tell 33 | -------------------------------------------------------------------------------- /devonthink-toolbar-scripts/copy-markdown-links-to-selected-items/Makefile: -------------------------------------------------------------------------------- 1 | ../Makefile -------------------------------------------------------------------------------- /devonthink-toolbar-scripts/copy-markdown-links-to-selected-items/README.md: -------------------------------------------------------------------------------- 1 | # Copy Markdown links to selected items 2 | 3 | For each item selected in the frontmost window of DEVONthink, this copies to the clipboard a link to the item in Markdown format. The name of the link is the name of the item in DEVONthink, and the link URL has the form `x-devonthink-item://UUID`. For example, if you select a document named "Some document" and run this script, it will create a link that looks something like this: 4 | 5 | ```md 6 | [Some document](x-devonthink-item://6C764-014F-4DDC-B074-054637F5) 7 | ``` 8 | 9 | If multiple items are selected in DEVONthink, the links put in the clipboard are separated by line breaks. 10 | 11 | ## Compilation and installation 12 | 13 | The make procedures require the programs [ImageMagick](https://imagemagick.org/index.php) and [fileicon](https://github.com/mklement0/fileicon). Running `make compile` followed by `make install` in this directory will compile the AppleScript file, give it an icon, and copy the results to two locations: 14 | 15 | ```txt 16 | ~/Library/Application Scripts/com.devon-technologies.think3/Menu/ 17 | ~/Library/Application Scripts/com.devon-technologies.think3/Toolbar/ 18 | ``` 19 | 20 | By putting it in both locations, the program becomes accessible from both the _Scripts_ menu item that appears in certain window contexts in DEVONthink, and as individual toolbar items you can add to the toolbar in certain other contexts. (For example, in windows showing single documents like Markdown documents, there is no _Scripts_ toolbar item available, but programs you install in the `~/Library/.../Toolbar/` location become available as individual toolbar items after you restart DEVONthink.) 21 | 22 | 23 | ## About the icon 24 | 25 | The [vector artwork](https://thenounproject.com/icon/share-link-875784/) used as a starting point for the icon created by [Arunkumar](https://thenounproject.com/arun122/) for the [Noun Project](https://thenounproject.com). It is licensed under the Creative Commons [Attribution 3.0 Unported](https://creativecommons.org/licenses/by/3.0/deed.en) license. 26 | -------------------------------------------------------------------------------- /devonthink-toolbar-scripts/copy-markdown-links-to-selected-items/icon.svg: -------------------------------------------------------------------------------- 1 | Share LinkCreated with Sketch.Created by Arunkumarfrom the Noun Project -------------------------------------------------------------------------------- /devonthink-toolbar-scripts/copy-paths-to-files-on-disk/Copy disk paths of selected files.applescript: -------------------------------------------------------------------------------- 1 | -- Summary: copy the absolute file paths of the selected documents. 2 | -- 3 | -- Copyright 2024 Michael Hucka. 4 | -- License: MIT License – see file "LICENSE" in the project website. 5 | -- Website: https://github.com/mhucka/devonthink-hacks 6 | 7 | use AppleScript version "2.4" -- Yosemite (10.10) or later 8 | use scripting additions 9 | 10 | tell application id "DNtp" 11 | try 12 | set paths to "" 13 | -- Start with no linebreak for the first time through the loop. 14 | set linebreak to "" 15 | repeat with _record in (selected records) 16 | set _type to (type of _record) as string 17 | -- Skip groups in DEVONthink b/c they don't exist on disk. 18 | if _type is not in {"group", "«constant ****DTgr»", ¬ 19 | "smart group", "«constant ****DTsg»"} then 20 | set paths to paths & linebreak & (path of _record) 21 | end if 22 | -- Separate subsequent paths with linebreaks. This repeated 23 | -- assignment may seem inefficient, but the alternative is 24 | -- adding a conditinal in addition to having an assignment. 25 | set linebreak to character id 10 26 | end repeat 27 | set the clipboard to paths 28 | on error msg number err 29 | if err is not -128 then 30 | display alert "DEVONthink error" message msg as warning 31 | end if 32 | end try 33 | end tell 34 | -------------------------------------------------------------------------------- /devonthink-toolbar-scripts/copy-paths-to-files-on-disk/Makefile: -------------------------------------------------------------------------------- 1 | ../Makefile -------------------------------------------------------------------------------- /devonthink-toolbar-scripts/create-document-from-template/.graphics/km-shortcut-screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mhucka/devonthink-hacks/9a4211aa29f9163ce625b9fb9ede2dccdb6546f1/devonthink-toolbar-scripts/create-document-from-template/.graphics/km-shortcut-screenshot.png -------------------------------------------------------------------------------- /devonthink-toolbar-scripts/create-document-from-template/Create document from template.applescript: -------------------------------------------------------------------------------- 1 | -- Summary: create a doc from a template file, then substitute keywords. 2 | -- 3 | -- Copyright 2024 Michael Hucka. 4 | -- License: MIT License – see file "LICENSE" in the project website. 5 | -- Website: https://github.com/mhucka/devonthink-hacks 6 | 7 | -- List of template files -- MUST BE UPDATED MANUALLY ~~~~~~~~~~~~~~~~~~ 8 | 9 | set templates to { ¬ 10 | "Card.md", ¬ 11 | "Code.md", ¬ 12 | "Diary.ooutline", ¬ 13 | "Empty markdown.md", ¬ 14 | "Goal plan.ooutline", ¬ 15 | "Markdown.md", ¬ 16 | "Meeting.ooutline", ¬ 17 | "Notes.ooutline", ¬ 18 | "Records markdown.md", ¬ 19 | "Spreadsheet.numbers", ¬ 20 | "Term definition.md" ¬ 21 | } 22 | 23 | -- Helper functions ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 24 | 25 | -- The following code is based on a function posted by "jobu" on 2004-08-11 26 | -- at https://macscripter.net/viewtopic.php?pid=32191--p32191 27 | 28 | on withoutExtension(name) 29 | if name contains "." then 30 | set delim to AppleScript's text item delimiters 31 | set AppleScript's text item delimiters to "." 32 | set basename to (text items 1 through -2 of (name as string) as list) as string 33 | set AppleScript's text item delimiters to delim 34 | return basename 35 | else 36 | return name 37 | end if 38 | end withoutExtension 39 | 40 | on replace(theText, placeholder, value) 41 | if theText contains placeholder then 42 | local od 43 | set {od, text item delimiters of AppleScript} to ¬ 44 | {text item delimiters of AppleScript, placeholder} 45 | set theText to text items of theText 46 | set text item delimiters of AppleScript to value 47 | set theText to "" & theText 48 | set text item delimiters of AppleScript to od 49 | end if 50 | return theText 51 | end replace 52 | 53 | -- Main body ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 54 | 55 | try 56 | tell application id "DNtp" 57 | set templateNames to {} 58 | repeat with t from 1 to length of templates 59 | copy my withoutExtension(item t of templates) to the end of templateNames 60 | end repeat 61 | 62 | set userSelection to (choose from list templateNames ¬ 63 | with prompt "Template to use for new document:" default items {"Notes"}) 64 | if userSelection is false then 65 | error number -128 66 | end if 67 | 68 | set chosenTemplate to first item of userSelection 69 | set prompt to "Name for the new " & chosenTemplate & " document:" 70 | set docName to display name editor "New document" info prompt as string 71 | if docName is false then 72 | error number -128 73 | end if 74 | 75 | set supportDir to path to application support from user domain as text 76 | set templateDir to POSIX path of (supportDir & "DEVONthink 3:Templates.noindex:") 77 | 78 | repeat with n from 1 to (count of templateNames) 79 | if templates's item n starts with chosenTemplate then 80 | set templatePath to (POSIX path of templateDir & templates's item n) 81 | set templateFullName to templates's item n 82 | end if 83 | end repeat 84 | 85 | set theGroup to current group 86 | set groupURL to reference URL of theGroup as string 87 | -- If you don't use the "placeholders" option, DEVONthink will not 88 | -- substitute its built-in placeholders like sortableDate. So, use 89 | -- at least one, just to trigger the built-in replacements. 90 | set newRecord to import templatePath placeholders {|%title%|:docName} to theGroup 91 | set creation date of newRecord to current date 92 | set modification date of newRecord to current date 93 | set the name of newRecord to docName 94 | set filePath to the path of the first item of newRecord 95 | set recordURL to reference URL of newRecord as string 96 | set docUUID to uuid of newRecord 97 | set docRevealURL to recordURL & "?reveal=1" 98 | set docFileName to filename of newRecord 99 | 100 | if templateFullName ends with "ooutline" then 101 | do shell script "/opt/homebrew/bin/ottoman -m -r -i '" & filePath ¬ 102 | & "' description=" & recordURL & " subject=" & groupURL 103 | else if templateFullName ends with "md" then 104 | set body to plain text of newRecord 105 | set body to my replace(body, "%UUID%", docUUID) 106 | set body to my replace(body, "%fileName%", docFileName) 107 | set body to my replace(body, "%groupURL%", groupURL) 108 | set body to my replace(body, "%documentURL%", recordURL) 109 | set body to my replace(body, "%documentRevealURL%", docRevealURL) 110 | set body to my replace(body, "%documentName%", docName) 111 | set plain text of newRecord to body 112 | end if 113 | 114 | -- Execute all smart rules as the final step. This ignores the 115 | -- result, because the value is either true/false, and there's no 116 | -- point to showing a dialog that says "a smart rule failed but I 117 | -- can't tell you which one". 118 | perform smart rule record newRecord trigger creation event 119 | end tell 120 | on error error_message number error_number 121 | if the error_number is not -128 then 122 | display alert "DEVONthink error" message error_message as warning 123 | end if 124 | end try 125 | -------------------------------------------------------------------------------- /devonthink-toolbar-scripts/create-document-from-template/Makefile: -------------------------------------------------------------------------------- 1 | ../Makefile -------------------------------------------------------------------------------- /devonthink-toolbar-scripts/create-document-from-template/README.md: -------------------------------------------------------------------------------- 1 | Create document from template 2 | ============================= 3 | 4 | In my quest to do as much as possible via keyboard shortcuts rather than having to reach for the trackpad, I had previously been using a Keyboard Maestro macro to invoke DEVONthink's _Data_ ➜ _New from Template_ menu item. However, the first thing I always do after creating a new document that way is to rename it, since the items' initial name in DEVONthink is the name of the template file. I had code in the Keyboard Maestro macro to create a name, but the interaction between Keyboard Maestro and DEVONthink was such that sometimes something would go wrong with item selection and keyboard focus, and the name change wouldn't happen. This got annoying enough that I wrote some AppleScript code to create new documents from templates and do the naming all in one go. 5 | 6 | Note that if you wish to use this script, **make sure to adapt it for your DEVONthink installation**. The script uses hardwired values for the template names, as well the name of a Smart Rule that is specific to my DEVONthink installation, and these will need to be changed. 7 | 8 | I use a simple Keyboard Maestro shortcut to invoke this script. (I also compile the script using `osacompile` first.) 9 | 10 |

11 | 12 |

13 | 14 | -------------------------------------------------------------------------------- /devonthink-toolbar-scripts/open-in-new-window/Makefile: -------------------------------------------------------------------------------- 1 | ../Makefile -------------------------------------------------------------------------------- /devonthink-toolbar-scripts/open-in-new-window/Open in new window.applescript: -------------------------------------------------------------------------------- 1 | # Summary: force open item in a new window or its default application. 2 | # 3 | # This script exists because I want shift-command-o to behave in a certain 4 | # way. (1) If the selected item is a group, I want DEVONthink to open a new 5 | # window on the group even if there is already a window open on the group 6 | # somewhere. (2) If the selected item is a document, I want it shift-command-o 7 | # to open it in the default application (which is the default action in 8 | # DEVONthink for shift-command-o). 9 | # 10 | # Copyright 2024 Michael Hucka. 11 | # License: MIT license – see file "LICENSE" in the project website. 12 | # Website: https://github.com/mhucka/devonthink-hacks 13 | 14 | use AppleScript version "2.5" 15 | use scripting additions 16 | 17 | # ~~~~ Helper functions ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 18 | 19 | # Log a message in DEVONthink's log and include the name of this script. 20 | on report(error_text) 21 | local script_path 22 | tell application "System Events" 23 | set script_path to POSIX path of (path to me as alias) 24 | end tell 25 | tell application id "DNtp" 26 | log message script_path info error_text 27 | end tell 28 | log error_text # Useful when running in a debugger. 29 | end report 30 | 31 | # Open a file in the defaut application. 32 | # This code is based on a 2022-03-22 forum posting by Shane Stanley at 33 | # https://forum.latenightsw.com/t/macos-12-3-introduces-serious-fundamental-applescript-bug/3666/2 34 | on open_in_default_app(file_path) 35 | script wrapperScript 36 | property ca: a reference to current application 37 | use framework "Foundation" 38 | on open_in_default_app(file_path) 39 | set ws to ca's NSWorkspace's sharedWorkspace() 40 | set file_url to ca's |NSURL|'s fileURLWithPath:file_path 41 | ws's openURL:file_url 42 | end open_in_default_app 43 | end script 44 | return wrapperScript's open_in_default_app(file_path) 45 | end open_in_default_app 46 | 47 | # ~~~~ Main body ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 48 | 49 | on act_on_record(rec) 50 | tell application id "DNtp" 51 | set rec_type to (type of rec) as string 52 | if rec_type is in {"group", "«constant ****DTgr»", ¬ 53 | "smart group", "«constant ****DTsg»"} then 54 | open window for record rec with force 55 | else 56 | my open_in_default_app(path of rec) 57 | end if 58 | end tell 59 | end act_on_record 60 | 61 | # ~~~~ Interfaces to DEVONthink ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 62 | 63 | # Allow execution as part of a Smart Rule. 64 | on performSmartRule(selected_records) 65 | tell application id "DNtp" 66 | try 67 | repeat with rec in (selected records) 68 | my act_on_record(rec) 69 | end repeat 70 | on error msg number err 71 | if the code is not -128 then 72 | my report(msg & " (error " & code & ")") 73 | display alert "DEVONthink" message msg as warning 74 | end if 75 | end try 76 | end tell 77 | end performSmartRule 78 | 79 | # Allow execution outside of a Smart Rule (e.g., in a debugger). 80 | tell application id "DNtp" 81 | my performSmartRule(selection as list) 82 | end tell 83 | -------------------------------------------------------------------------------- /devonthink-toolbar-scripts/open-in-new-window/README.md: -------------------------------------------------------------------------------- 1 | # Open in new window 2 | 3 | For each item selected in the frontmost window of DEVONthink, if the item is a group then this force-opens a new window on the group (even if there is a window open on it somewhere); otherwise, this opens the item in the default application. I wrote this because I wanted to change the behavior of o. I bind this to a keystroke using [Keyboard Maestro](https://www.keyboardmaestro.com). 4 | 5 | 6 | ## Compilation and installation 7 | 8 | The make procedures require the programs [ImageMagick](https://imagemagick.org/index.php) and [fileicon](https://github.com/mklement0/fileicon). Running `make compile` followed by `make install` in this directory will compile the AppleScript file, give it an icon, and copy the results to two locations: 9 | 10 | ```txt 11 | ~/Library/Application Scripts/com.devon-technologies.think3/Menu/ 12 | ~/Library/Application Scripts/com.devon-technologies.think3/Toolbar/ 13 | ``` 14 | 15 | By putting it in both locations, the program becomes accessible from both the _Scripts_ menu item that appears in certain window contexts in DEVONthink, and as individual toolbar items you can add to the toolbar in certain other contexts. (For example, in windows showing single documents like Markdown documents, there is no _Scripts_ toolbar item available, but programs you install in the `~/Library/.../Toolbar/` location become available as individual toolbar items after you restart DEVONthink.) 16 | 17 | 18 | ## About the icon 19 | 20 | The [vector artwork](https://thenounproject.com/icon/folder-2062718/) used as a starting point for the icon created by [iejank](https://thenounproject.com/iejankzonks/) for the Noun Project. It is licensed under the Creative Commons [Attribution 3.0 Unported](https://creativecommons.org/licenses/by/3.0/deed.en) license. 21 | -------------------------------------------------------------------------------- /devonthink-toolbar-scripts/open-in-new-window/icon.svg: -------------------------------------------------------------------------------- 1 | Created by iejankfrom the Noun Project 6 | -------------------------------------------------------------------------------- /devonthink-toolbar-scripts/set-icon-to-parent-group-icon/.graphics/folder-icons-dark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mhucka/devonthink-hacks/9a4211aa29f9163ce625b9fb9ede2dccdb6546f1/devonthink-toolbar-scripts/set-icon-to-parent-group-icon/.graphics/folder-icons-dark.png -------------------------------------------------------------------------------- /devonthink-toolbar-scripts/set-icon-to-parent-group-icon/.graphics/folder-icons-light.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mhucka/devonthink-hacks/9a4211aa29f9163ce625b9fb9ede2dccdb6546f1/devonthink-toolbar-scripts/set-icon-to-parent-group-icon/.graphics/folder-icons-light.png -------------------------------------------------------------------------------- /devonthink-toolbar-scripts/set-icon-to-parent-group-icon/Makefile: -------------------------------------------------------------------------------- 1 | ../Makefile -------------------------------------------------------------------------------- /devonthink-toolbar-scripts/set-icon-to-parent-group-icon/README.md: -------------------------------------------------------------------------------- 1 | # Set the icon of a selected group to that of its parent 2 | 3 | In my way of working with DEVONthink, I set DEVONthink group icons to colorful custom icons, and set all subgroups of a given group to the same icon as the parent group. The figure below shows an actual example. 4 | 5 |

6 | 7 | 8 | 9 | Fragment of one of my databases, showing the use of custom icons and colors. 10 | 11 |

12 | 13 | I also do something analogous with tags: certain tags get custom colors, and subtags of those tags are set to the same color. This approach helps make my database structure easier to notice more quickly. 14 | 15 | Copy-pasting icons using the DEVONthink inspector becomes tedious and time-consuming when changing a lot of items, so I wrote the script [`Set icon to parent group icon`](Set%20icon%20to%20parent%20group%20icon.applescript) to set the icon of a selected item to that of its parent. Its operation is very simple: (1) select one or more items in DEVONthink, and (2) run the script. The icons of the subgroups will be changed to those of the parent, and the colors of tags will be changed to the color of their parent tag. 16 | 17 | Personally, I only use this approach for groups, tags, and bookmarks that point to other groups in my databases. However, this behavior is easily configurable. At the top of the script, there are three properties: 18 | 19 | * `allowed_kinds`: a list of DEVONthink item kinds (as strings); for items of these kinds, the script will set the icons of those kinds without asking. 20 | 21 | `disallowed_kinds`: a list of DEVONthink item kinds; the script will ignore these kinds of items. 22 | 23 | `consider_bookmark_destination`: If an item is a bookmark, it may be desirable to change its icon not based on the fact that it's a bookmark, but rather based on whether it points to an allowed or disallowed item. For example, if `allowed_kinds` contains `"Group"`, you may want a bookmark that points to a DEVONthink group to be changed as if it were itself a group. (Note: the icon of the bookmark will still be set to its parent's icon, _not_ the destination group's icon.) 24 | 25 | The two properties `allowed_kinds` and `disallowed_kinds` together control which kinds of items can have their icons changed automatically. There are two lists because, depending on how many kinds of things you want to allow changing, it may be easier to express the condition by inclusion or exclusion. The code that sets icons tests both; i.e., an item's kind has to be in the allow list and not be in the disallow list. If an item's kind is not listed as allowed, this script will ask if an exception should be made. 26 | 27 | 28 | ## Compilation and installation 29 | 30 | The procedure for compiling the script using the Makefile in this directory requires the programs [ImageMagick](https://imagemagick.org/index.php) and [fileicon](https://github.com/mklement0/fileicon). (Note: they are only used for creating the compiled script; they are _not_ used by the script itself.) Running `make compile` followed by `make install` in this directory will compile the AppleScript file, give it an icon, and copy the results to two locations: 31 | 32 | ```text 33 | ~/Library/Application Scripts/com.devon-technologies.think3/Menu/ 34 | ~/Library/Application Scripts/com.devon-technologies.think3/Toolbar/ 35 | ``` 36 | 37 | By putting it in both locations, the program becomes accessible from both the _Scripts_ menu item that appears in certain window contexts in DEVONthink, and as individual toolbar items you can add to the toolbar in certain other contexts. 38 | 39 | 40 | ## Creating custom folder/group icons in DEVONthink 41 | 42 | My process for creating custom icons in DEVONthink is simple, albeit a bit roundabout. First, I use a separate general-purpose Mac app called [FolderMaker](https://foldermarker.com/en/) to create a custom folder icon for a temporary folder in the Finder. Next, I open the Info panel on the folder in the Finder (menu item _File_ ▹ _Get Info_), click on the icon in the Info panel, and copy it to the clipboard (_Edit_ ▹ _Copy_). Then I switch to DEVONthink, select the group I want to change, and open the Inspector on it. Finally, I click on the icon in the Inspector, and use _Edit_ ▹ _Paste_ to replace the group icon with the one copied to the clipboard. 43 | 44 | 45 | ## Relationship to other work 46 | 47 | This script is very similar in purpose to one [posted by user "pete31" to the DEVONthink Discourse forums in May, 2020](https://discourse.devontechnologies.com/t/script-colorize-devonthink-icons-color-code-magick/56180). That script is more elaborate and can do things like invoke ImageMagick to manipulate icons. I wrote mine independently; it's simpler, and has differences in the way it lets the user control its behavior, but it's also less powerful, so if you are exploring the use of [`Set icon to parent group icon`](Set%20icon%20to%20parent%20group%20icon.applescript), make sure to try pete31's script too. 48 | 49 | 50 | ## Acknowledgments 51 | 52 | The [vector artwork](https://thenounproject.com/icon/copy-308007/) used as a starting point for the icon created by [Tony Wallström](https://thenounproject.com/tonywallstrom/) It is licensed under the Creative Commons [CC BY 3.0](https://creativecommons.org/licenses/by/3.0/) license. 53 | 54 | The AppleScript code in this directory was made possible thanks to helpful examples and comments from users "pete31" and "chrillek" on the DEVONthink user forums, in these postings: 55 | 56 | * [AppleScript to set thumbnail of group to same as parent group?](https://discourse.devontechnologies.com/t/applescript-to-set-thumbnail-of-group-to-same-as-parent-group/69114/3) (code to set thumbnails of child items) 57 | * [AppleScript to set thumbnail of group to same as parent group?](https://discourse.devontechnologies.com/t/applescript-to-set-thumbnail-of-group-to-same-as-parent-group/69114/11) (reminder about `location group`) 58 | * [How to set thumbnails as raw data?](https://discourse.devontechnologies.com/t/how-to-set-thumbnails-as-raw-data/55696) 59 | -------------------------------------------------------------------------------- /devonthink-toolbar-scripts/set-icon-to-parent-group-icon/Set icon to parent group icon.applescript: -------------------------------------------------------------------------------- 1 | # Summary: set the icon of selected items to the icon of their parent group. 2 | # 3 | # Copyright 2024 Michael Hucka. 4 | # License: MIT License – see file "LICENSE" in the project website. 5 | # Website: https://github.com/mhucka/devonthink-hacks 6 | 7 | use AppleScript version "2.5" 8 | use scripting additions 9 | 10 | 11 | # ~~~~ Config variables ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 12 | 13 | # The next two properties together control which kinds of items can have their 14 | # icons changed automatically. There are two lists because, depending on how 15 | # many kinds of things you want to allow changing, it may be easier to express 16 | # the condition by inclusion or exclusion. The code that sets icons tests 17 | # both; i.e., an item's kind has to be in the allow list and not be in the 18 | # disallow list. If an item's kind is not listed as allowed, this script will 19 | # ask if an exception should be made. 20 | 21 | property allowed_kinds: {"Group", "Smart Group", "Tag"} 22 | property disallowed_kinds: {} 23 | 24 | # If an item is a bookmark, it may be desirable to change its icon not based 25 | # on the fact that it's a bookmark, but rather based on whether it points to 26 | # an allowed or disallowed item. For example, if allowed_kinds contains 27 | # "Group", you may want a bookmark that points to a DEVONthink group to be 28 | # changed as if it were itself a group. (Note: the icon of the bookmark will 29 | # still be set to its parent's icon, not the destination icon.) 30 | 31 | property consider_bookmark_destination: true 32 | 33 | 34 | # ~~~~ Helper functions ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 35 | 36 | # Log a message in DEVONthink's log and include the path to this script. 37 | on report(error_text) 38 | local script_path 39 | tell application "System Events" 40 | set script_path to POSIX path of (path to me as alias) 41 | end tell 42 | tell application id "DNtp" 43 | log message script_path info error_text # DEVONthink's log. 44 | end tell 45 | log error_text # Debugger & osascript log. 46 | end report 47 | 48 | # Return the given file name without its file name extension, if any. 49 | on remove_ext(file_name) 50 | script wrapperScript 51 | property ca: a reference to current application 52 | use framework "Foundation" 53 | on remove_ext(file_name) 54 | set u to ca's NSURL's fileURLWithPath:file_name 55 | return u's URLByDeletingPathExtension()'s lastPathComponent() as text 56 | end remove_ext 57 | end script 58 | return wrapperScript's remove_ext(file_name) 59 | end remove_ext 60 | 61 | # Return the file name of this script as a string, minus the extension. 62 | on get_script_name() 63 | tell application "System Events" 64 | set path_alias to path to me 65 | set file_name to name of path_alias 66 | return my remove_ext(file_name) 67 | end tell 68 | end get_script_name 69 | 70 | # Return true if the given item is a tag. 71 | on is_tag(rec) 72 | tell application id "DNtp" 73 | return (tag type of rec) is in {group tag, ordinary tag} 74 | end tell 75 | end is_tag 76 | 77 | # Return true if the given item has a custom color. (Only possible for tags.) 78 | on has_color(rec) 79 | tell application id "DNtp" 80 | try 81 | color of rec as anything 82 | on error 83 | return false 84 | end try 85 | return true 86 | end tell 87 | end has_color 88 | 89 | # Return true if the given item has an icon/thumbnail. 90 | on has_thumbnail(rec) 91 | tell application id "DNtp" 92 | try 93 | thumbnail of rec as anything 94 | on error 95 | return false 96 | end try 97 | return true 98 | end tell 99 | end has_thumbnail 100 | 101 | # Return true if the given kind matches the allow/disallow conditions. 102 | on is_acceptable_kind(kname) 103 | return (kname is in allowed_kinds and not kname is in disallowed_kinds) 104 | end is_acceptable_kind 105 | 106 | # Return true if the given record has a kind that we want to act on. 107 | on is_allowed_kind(rec) 108 | tell application id "DNtp" 109 | set rkind to (kind of rec) as string 110 | if my is_acceptable_kind(rkind) then 111 | return true 112 | else if consider_bookmark_destination then 113 | set dest_url to url of rec 114 | if dest_url starts with "x-devonthink-item" then 115 | # It points to a DEVONthink destination, so get its kind. 116 | set dest_uuid to text 21 thru -1 of dest_url 117 | set dest_rec to get record with UUID dest_uuid 118 | set dest_kind to (kind of dest_rec) as string 119 | return my is_acceptable_kind(dest_kind) 120 | end if 121 | end if 122 | return false 123 | end tell 124 | end is_allowed_kind 125 | 126 | 127 | # ~~~~ Main body ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 128 | 129 | on act_on_record(rec) 130 | tell application id "DNtp" 131 | # For items at the root level, there's no parent to get an icon from. 132 | if (location of rec) = "/" then 133 | set msg to "The icon of this item cannot be set because the " ¬ 134 | & "item is at the root level of the database:" & ¬ 135 | linefeed & linefeed & (name of rec) 136 | display dialog msg buttons {"Skip", "Cancel"} ¬ 137 | with title my get_script_name() with icon 1 ¬ 138 | default button 1 giving up after 60 139 | if button returned of result = "Cancel" then 140 | error "User cancelled operation" 141 | end if 142 | return 143 | end if 144 | 145 | # If this is not the desired kind, ask the user what to do. 146 | if not my is_allowed_kind(rec) then 147 | set msg to "This item is of kind \"" & kind of rec & "\", which " ¬ 148 | & "is not one of the expected kinds. Should its icon " ¬ 149 | & "be changed anyway? " & linefeed & linefeed & (name of rec) 150 | display dialog msg buttons {"Set icon", "Skip", "Cancel"} ¬ 151 | with title my get_script_name() with icon 1 ¬ 152 | default button 1 giving up after 60 153 | if button returned of result = "Cancel" then 154 | error "User cancelled operation" 155 | else if button returned of result = "Skip" then 156 | return 157 | else if gave up of result then 158 | error "Timed out waiting for user input" 159 | end if 160 | end if 161 | 162 | # Either it's the right kind, or the user said to go ahead anyway. 163 | set parent_group to location group of rec 164 | if my has_thumbnail(parent_group) then 165 | # Note: *must* set this directly; can't use intermediate var. 166 | set thumbnail of rec to thumbnail of parent_group 167 | else if my is_tag(rec) and my has_color(parent_group) then 168 | # Special case: if the parent doesn't have an icon but this is 169 | # a tag, we can still change its color to its parent's color. 170 | set color of rec to color of parent_group 171 | else 172 | set msg to "The parent of this item does not have a custom " ¬ 173 | & "icon, and therefore, the item's icon cannot be changed: " ¬ 174 | & linefeed & linefeed & (name of rec) 175 | display dialog msg buttons {"Skip", "Cancel"} ¬ 176 | with title my get_script_name() with icon 1 ¬ 177 | default button 1 giving up after 60 178 | if button returned of result = "Cancel" then 179 | error "User cancelled operation" 180 | end if 181 | end if 182 | end tell 183 | end act_on_record 184 | 185 | # ~~~~ Interfaces to DEVONthink ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 186 | 187 | # Allow execution as part of a Smart Rule. 188 | on performSmartRule(selected_records) 189 | tell application id "DNtp" 190 | try 191 | repeat with rec in (selected records) 192 | my act_on_record(rec) 193 | end repeat 194 | on error msg number err 195 | if the err is not -128 then # (Code -128 means user cancelled.) 196 | my report(msg & " (error " & err & ")") 197 | display alert "DEVONthink" message msg as warning 198 | end if 199 | end try 200 | end tell 201 | end performSmartRule 202 | 203 | # Allow execution outside of a Smart Rule (e.g., in a debugger). 204 | on run 205 | tell application id "DNtp" 206 | my performSmartRule(selection as list) 207 | end tell 208 | end run 209 | -------------------------------------------------------------------------------- /devonthink-toolbar-scripts/set-icon-to-parent-group-icon/icon.svg: -------------------------------------------------------------------------------- 1 | copyCreated with Sketch Beta.Created by Napoleonfrom the Noun Project -------------------------------------------------------------------------------- /devonthink-toolbar-scripts/switch-workspace/Makefile: -------------------------------------------------------------------------------- 1 | ../Makefile -------------------------------------------------------------------------------- /devonthink-toolbar-scripts/switch-workspace/README.md: -------------------------------------------------------------------------------- 1 | # Switch workspace 2 | 3 | I've learned to take advantage of DEVONthink's workspaces to organize windows and documents for different projects and activities. It's been a great feature – one that I didn’t appreciate when I first started to use DEVONthink. But, with the default DEVONthink commands for changing workspaces, a source of friction for me is remembering to update the current configuration before switching. In a typical day, I switch workspaces multiple time, and I often forget to use _Go_ ▹ _Workspaces_ ▹ _Update workspace_ to save the current configuration before switching. The result is that I lose any changes made since the last time I used that workspace. For my way of working, the windows currently visible on screen capture a lot of context about what I was in the middle of doing, and losing that context is a problem. 4 | 5 | In October 2022, I finally sent a request to the DEVONthink developers for a preference setting that would, when turned on, automatically update the current workspace before switching to another one. The DEVONthink developers said they would consider it. They also sent me a script that, when invoked, asks the user to select a workspace from a list, saves the current workspace, and switches to the selected workspace. This is good enough for me. 6 | 7 | A slightly modified version of the script is in this directory. I put the compiled script in the folder for DEVONthink menubar scripts (which is `~/Library/Application Scripts/com.devon-technologies.think3/Menu/`) and I bind a keystroke (in [Keyboard Maestro](https://www.keyboardmaestro.com/main/)) to invoke the script. 8 | -------------------------------------------------------------------------------- /devonthink-toolbar-scripts/switch-workspace/Switch workspace.applescript: -------------------------------------------------------------------------------- 1 | -- Summary: save the current workspace config and switch to another. 2 | -- 3 | -- The original version of this code was sent to me by Jim Neumann of 4 | -- DEVONtechnologies via email on 2022-10-13 in response to my request 5 | -- for a feature addition to DEVONthink. I have modified the original. 6 | 7 | use AppleScript version "2.4" -- Yosemite (10.10) or later 8 | use scripting additions 9 | 10 | tell application "System Events" 11 | tell application process "DEVONthink 3" 12 | set frontmost to true 13 | end tell 14 | end tell 15 | 16 | tell application "DEVONthink 3" 17 | try 18 | set chosenWorkspace to ¬ 19 | (choose from list (workspaces as list) ¬ 20 | with prompt ¬ 21 | "Choose workspace" without multiple selections allowed and empty selection allowed) 22 | 23 | if chosenWorkspace ≠ false then -- User chose, not cancelled 24 | if exists (current workspace) then 25 | save workspace (get current workspace) 26 | end if 27 | close every think window 28 | load workspace (item 1 of chosenWorkspace) 29 | end if 30 | on error msg number err 31 | if err is not -128 then ¬ 32 | display alert "DEVONthink" message msg as warning 33 | end try 34 | end tell 35 | -------------------------------------------------------------------------------- /devonthink-toolbar-scripts/unset-field-values/Makefile: -------------------------------------------------------------------------------- 1 | ../Makefile -------------------------------------------------------------------------------- /devonthink-toolbar-scripts/unset-field-values/README.md: -------------------------------------------------------------------------------- 1 | # Unset field values 2 | 3 | These small programs are meant to be installed in DEVONthink's AppleScripts menu. They are meant to be invoked by selecting records, then running the desired command. 4 | 5 | * [`Unset Finder comment`](): I use the `Finder comment` metadata field value in specific ways in DEVONthink, and sometimes need to unset the value from a bunch of records at once. This script does that for the selected records. 6 | * [`Unset URL`](): unsets the _URL_ field on the selected records. 7 | * [`Unset Aliases`](): Ditto for the _Aliases_ field. 8 | 9 | In my approach to using DEVONthink, I set a variety of custom fields to store data retrieved from Zotero, a research bibliography management system. Since these fields are not part of DEVONthink by default, and must be defined by the user, and most people probably don't use Zotero or my program [Zowie](https://github.com/mhucka/zowie), the next program is probably not relevant to anyone but me. 10 | 11 | * [`Unset common Zotero field values`](): does the same as the programs above, and in addition, unsets custom fields such as `citekey`, `abstract`, `year`, and more. 12 | -------------------------------------------------------------------------------- /devonthink-toolbar-scripts/unset-field-values/Unset Aliases.applescript: -------------------------------------------------------------------------------- 1 | -- Summary: unset the Aliases field value of select records. 2 | -- 3 | -- Copyright 2024 Michael Hucka. 4 | -- License: MIT License – see file "LICENSE" in the project website. 5 | -- Website: https://github.com/mhucka/devonthink-hacks 6 | 7 | use AppleScript version "2.4" -- Yosemite (10.10) or later 8 | use scripting additions 9 | 10 | tell application id "DNtp" 11 | try 12 | repeat with _record in (selected records) 13 | set aliases of _record to "" 14 | end repeat 15 | on error msg number code 16 | if the code is not -128 then 17 | display alert "Unset Aliases" message msg as warning 18 | end if 19 | end try 20 | end tell 21 | -------------------------------------------------------------------------------- /devonthink-toolbar-scripts/unset-field-values/Unset Finder comment.applescript: -------------------------------------------------------------------------------- 1 | -- Summary: unset the Finder comment of select records. 2 | -- 3 | -- Copyright 2024 Michael Hucka. 4 | -- License: MIT License – see file "LICENSE" in the project website. 5 | -- Website: https://github.com/mhucka/devonthink-hacks 6 | 7 | use AppleScript version "2.4" -- Yosemite (10.10) or later 8 | use scripting additions 9 | 10 | tell application id "DNtp" 11 | try 12 | repeat with _record in (selected records) 13 | set comment of _record to "" 14 | end repeat 15 | on error msg number code 16 | if the code is not -128 then 17 | display alert "Unset Finder comment" message msg as warning 18 | end if 19 | end try 20 | end tell 21 | -------------------------------------------------------------------------------- /devonthink-toolbar-scripts/unset-field-values/Unset URL.applescript: -------------------------------------------------------------------------------- 1 | -- Summary: unset the URL field value of select records. 2 | -- 3 | -- Copyright 2024 Michael Hucka. 4 | -- License: MIT License – see file "LICENSE" in the project website. 5 | -- Website: https://github.com/mhucka/devonthink-hacks 6 | 7 | use AppleScript version "2.4" -- Yosemite (10.10) or later 8 | use scripting additions 9 | 10 | tell application id "DNtp" 11 | try 12 | repeat with _record in (selected records) 13 | set URL of _record to "" 14 | end repeat 15 | on error msg number code 16 | if the code is not -128 then 17 | display alert "Unset URL" message msg as warning 18 | end if 19 | end try 20 | end tell 21 | -------------------------------------------------------------------------------- /devonthink-toolbar-scripts/unset-field-values/Unset common Zotero field values.applescript: -------------------------------------------------------------------------------- 1 | -- Summary: unset record fields I use in conjunction with Zotero. 2 | -- 3 | -- ╭───────────────────────────── WARNING ─────────────────────────────╮ 4 | -- │ This assumes custom metadata fields that I set in my copy of │ 5 | -- │ DEVONthink. It will almost certainly not work in anyone else's │ 6 | -- │ copy of DEVONthink. │ 7 | -- ╰───────────────────────────────────────────────────────────────────╯ 8 | -- 9 | -- Copyright 2024 Michael Hucka. 10 | -- License: MIT License – see file "LICENSE" in the project website. 11 | -- Website: https://github.com/mhucka/devonthink-hacks 12 | 13 | use AppleScript version "2.4" -- Yosemite (10.10) or later 14 | use scripting additions 15 | 16 | tell application id "DNtp" 17 | try 18 | repeat with _record in (selected records) 19 | set URL of _record to "" 20 | set aliases of _record to "" 21 | set comment of _record to "" 22 | add custom meta data "" for "year" to _record 23 | add custom meta data "" for "type" to _record 24 | add custom meta data "" for "citekey" to _record 25 | add custom meta data "" for "abstract" to _record 26 | add custom meta data "" for "reference" to _record 27 | end repeat 28 | on error msg number code 29 | if the code is not -128 then 30 | display alert "Unset URL" message msg as warning 31 | end if 32 | end try 33 | end tell 34 | -------------------------------------------------------------------------------- /finder-folder-actions/Makefile: -------------------------------------------------------------------------------- 1 | # Summary: my generic Makefile for compiling AppleScript files. 2 | # 3 | # Copyright 2024 Michael Hucka. 4 | # License: MIT License – see file "LICENSE" in the project website. 5 | # Website: https://github.com/mhucka/devonthink-hacks 6 | 7 | # Preliminary settings and tests ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 8 | 9 | SHELL=/bin/bash 10 | .ONESHELL: # Run all commands in the same shell. 11 | .SHELLFLAGS += -e # Exit at the first error. 12 | 13 | thisdir := $(shell basename $(CURDIR)) 14 | 15 | # When I run M-x compile using this Makefile, the compile target works but the 16 | # install target fails. It works outside Emacs in a regular shell terminal. I 17 | # haven't figured out the reason, so for now, this test reminds me to avoid it. 18 | 19 | ifeq ($(origin INSIDE_EMACS),environment) 20 | $(error "Do not run make from inside Emacs with this Makefile.") 21 | endif 22 | 23 | 24 | # Print help if no command is given ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 25 | 26 | # The help scheme works by looking for lines that begin with "#:" above the 27 | # definitions of commands. Originally based on code posted to Stack Overflow on 28 | # 2019-11-28 by Richard Kiefer at https://stackoverflow.com/a/59087509/743730 29 | 30 | #: Print a summary of available commands. 31 | help: 32 | @echo "This is the Makefile for $(bright)$(thisdir)$(reset)." 33 | @echo "Available commands:" 34 | @echo 35 | @grep -B1 -E "^[a-zA-Z0-9_-]+\:([^=]|$$)" $(MAKEFILE_LIST) \ 36 | | grep -v -- -- \ 37 | | sed 'N;s/\n/###/' \ 38 | | sed -n 's/^#: \(.*\)###\([a-zA-Z0-9_-]*\):.*/$(color)\2$(reset):###\1/p' \ 39 | | column -t -s '###' 40 | 41 | 42 | # Compile source files ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 43 | 44 | # Getting Make to handle path names with embedded spaces is insanely hard. 45 | # The following incredibly obscure workarounds are the combination of two 46 | # methods: (1) by user "mathematical.coffee" posted on 2014-02-11 47 | # at https://stackoverflow.com/a/21694624/743730, and (2) by user Yann-Gaël 48 | # Guéhéneuc posted on 2016-02-25 at https://stackoverflow.com/a/35617603/743730 49 | 50 | # We first create a list of source files in a way that bypasses the regular 51 | # Make file pattern matching, and then replace every space character in 52 | # each pathname with a question mark character. 53 | 54 | sources = $(shell find . -iname '*.applescript' -maxdepth 1 | cut -d/ -f2- | tr ' ' '?') 55 | 56 | # Next, define a function named "q2s" to replace question marks by spaces, 57 | # using an absolutely bonkers trick to assign a space character to "space". 58 | 59 | space := 60 | space += 61 | q2s = $(subst ?, $(space),$1) 62 | 63 | # Now define the pattern rule to create compile .scpt files from .applescript 64 | # files. A rule of the form %.scpt would not work if the file names had space 65 | # characters in them (because the outcome of Make expanding %.scpt would have 66 | # spaces in the result, and those spaces would act as file name delimiters). 67 | # But if the file names used with this pattern rule have question marks 68 | # instead of spaces, they're atomic units as far as the pattern matching 69 | # mechanism is concerned, and so the rule works. We just have to do some 70 | # substitution to reconstruct the real file names before we run commands that 71 | # act on the files themselves. (That's where q2s comes in.) 72 | 73 | .SECONDEXPANSION: 74 | %.scpt: %.applescript 75 | osacompile -o "$(call q2s,$*.scpt)" "$<" 76 | 77 | # The final ingredient is to define the target "compile" such that it depends 78 | # on a list of .scpt files. This is simply done by taking the list of source 79 | # files (names such as "Some?file?name.applescript") and replacing the 80 | # extensions. The resulting list (names like "Some?file?name.scpt") then 81 | # satisfies the %.scpt rule above. 82 | 83 | #: Compile the AppleScript files in this directory. 84 | compile: $(sources:.applescript=.scpt) 85 | 86 | 87 | # Install binaries ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 88 | 89 | # Note the use of ? in the directory name below, instead of the space character 90 | # (i.e., "~/Library/Scripts/Folder Action Scripts"). 91 | 92 | destdir = "$(HOME)/Library/Scripts/Folder?Action?Scripts" 93 | 94 | .SECONDEXPANSION: 95 | $(destdir)/%.scpt: %.applescript 96 | install -bpS "$(call q2s,$*.scpt)" $(call q2s,$(destdir)) 97 | 98 | #: Install the compiled scripts in DEVONthink's script directory. 99 | install: $(addprefix $(destdir)/,$(sources:.applescript=.scpt)) 100 | 101 | 102 | # Cleanup ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 103 | 104 | #: Clean this directory. 105 | clean: 106 | rm -rf *.scpt 107 | 108 | 109 | # Miscellaneous directives ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 110 | 111 | #: Print a random joke from https://icanhazdadjoke.com/. 112 | joke: 113 | @echo "$(shell curl -s https://icanhazdadjoke.com/)" 114 | 115 | # Color codes used in messages. 116 | color := $(shell tput bold; tput setaf 6) 117 | bright := $(shell tput bold; tput setaf 15) 118 | dim := $(shell tput setaf 66) 119 | link := $(shell tput setaf 111) 120 | reset := $(shell tput sgr0) 121 | 122 | .PHONY: help clean joke 123 | 124 | .SILENT: clean 125 | -------------------------------------------------------------------------------- /finder-folder-actions/import-to-devonthink/Import to DEVONthink with selector.applescript: -------------------------------------------------------------------------------- 1 | # Summary: Folder Action script to import items to a selected location. 2 | # 3 | # This is intended to be installed as a Folder Action script on a folder in 4 | # the Finder. It differs from a similar folder action script provided by 5 | # DEVONthink ("DEVONthink - Import & Delete") in the following ways: 6 | # 7 | # * If given a single item, asks for new name, tags, and destination group. 8 | # 9 | # * If given multiple items, asks if each item should be considered 10 | # separately or all should be treated as one group, and then either 11 | # 12 | # a) asks for the name, tags, & destination of each item one at a time, or 13 | # 14 | # b) else asks for tags & destination, and tags & moves all the items. 15 | # 16 | # * Catches more errors and provides meaningful error dialogs. 17 | # 18 | # * Puts the original item in the trash unless the user cancels at any time 19 | # or an error occurs. 20 | # 21 | # Copyright 2024 Michael Hucka. 22 | # License: MIT license – see file "LICENSE" in the project website. 23 | # Website: https://github.com/mhucka/devonthink-hacks 24 | 25 | use AppleScript version "2.5" 26 | use scripting additions 27 | 28 | # ~~~~ Helper functions ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 29 | 30 | # Return the Unix-style path of the given Finder path object or alias. 31 | on get_path(finder_alias) 32 | tell application "Finder" 33 | return POSIX path of finder_alias 34 | end tell 35 | end get_path 36 | 37 | # Return the name of the file, without the directory components. 38 | on get_basename(file_path) 39 | tell application "Finder" 40 | return name of (info for file_path) 41 | end tell 42 | end get_basename 43 | 44 | # Return the file name of *this* script as a string, minus the file extension. 45 | on get_script_name() 46 | tell application "System Events" 47 | set path_alias to path to me 48 | set file_name to name of path_alias 49 | return my remove_ext(file_name) 50 | end tell 51 | end get_script_name 52 | 53 | # Return the file extension of the given file name or path, minus the dot. 54 | on get_ext(file_name) 55 | script wrapperScript 56 | property ca: a reference to current application 57 | use framework "Foundation" 58 | on get_ext(file_name) 59 | set fname to ca's NSString's stringWithString:file_name 60 | return fname's pathExtension() as string 61 | end get_ext 62 | end script 63 | return wrapperScript's get_ext(file_name) 64 | end get_ext 65 | 66 | # Return the given file name without its file name extension, if any. 67 | on remove_ext(file_name) 68 | script wrapperScript 69 | property ca: a reference to current application 70 | use framework "Foundation" 71 | on remove_ext(file_name) 72 | set u to ca's NSURL's fileURLWithPath:file_name 73 | return u's URLByDeletingPathExtension()'s lastPathComponent() as text 74 | end remove_ext 75 | end script 76 | return wrapperScript's remove_ext(file_name) 77 | end remove_ext 78 | 79 | # Tell DEVONthink to import a single item, asking the user for a name, tags, 80 | # and destination group. 81 | on import_item(finder_alias) 82 | set item_path to my get_path(finder_alias) 83 | set item_basename to my get_basename(item_path) 84 | tell application "DEVONthink 3" 85 | set msg to "Importing file '" & item_basename & "'" 86 | set user_input to display group selector msg tags true name true 87 | set new_record to import item_path to |group| of user_input 88 | set new_name to |name| of user_input 89 | if new_name ≠ "" then 90 | set name of new_record to new_name 91 | else 92 | set name of new_record to item_basename 93 | end if 94 | set tags of new_record to |tags| of user_input 95 | end tell 96 | end import_item 97 | 98 | # Tell DEVONthink to import a list of items. It will only ask for the 99 | # destination and tags once, then move all items into the destination and 100 | # tag them all with the same set of tags. (It will not ask for item names.) 101 | on import_all_items(item_list) 102 | tell application "DEVONthink 3" 103 | set msg to "Importing " & (count of item_list) & " files" 104 | set user_input to display group selector msg tags true 105 | set dest_group to |group| of user_input 106 | set tag_list to |tags| of user_input 107 | repeat with thing in item_list 108 | set item_path to my get_path(thing) 109 | set new_record to import item_path to dest_group 110 | set tags of new_record to tag_list 111 | end repeat 112 | end tell 113 | end import_all_items 114 | 115 | # Move the item or items to the trash. 116 | on move_to_trash(item_list) 117 | if class of item_list is not list then 118 | set item_list to {item_list} 119 | end if 120 | tell application "Finder" 121 | repeat with thing in item_list 122 | move thing to trash 123 | end repeat 124 | end tell 125 | end move_to_trash 126 | 127 | # Launch application, wait to see it launched, and bring it to the front. 128 | # Only waits for a limited time, in case something is wrong. 129 | on launch_application(app_name, activate_app) 130 | launch application app_name 131 | tell application "System Events" 132 | set times_left to 30 # 15 sec in 0.5 sec iterations 133 | # This roundabout approach of testing process names is because the more 134 | # direct "repeat until application app_name is running" causes errors. 135 | repeat while times_left > 0 136 | if count of (every process whose name is app_name) > 0 then 137 | exit repeat 138 | end if 139 | delay 0.5 140 | set times_left to (times_left - 1) 141 | end repeat 142 | if times_left ≤ 0 then 143 | error ("Folder action " & my get_script_name() & " timed " & ¬ 144 | "out waiting for " & app_name & " to launch.") 145 | end if 146 | end tell 147 | if activate_app then 148 | tell application app_name to activate 149 | end if 150 | end launch_application 151 | 152 | # ~~~~ Main body ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 153 | 154 | on adding folder items to this_folder after receiving added_items 155 | try 156 | # Filter the list of items to remove in-progress downloads. 157 | set item_list to {} 158 | repeat with thing in added_items 159 | set item_path to my get_path(thing) 160 | if my get_ext(item_path) is not in {"download", "crdownload"} then 161 | set end of item_list to thing 162 | end if 163 | end repeat 164 | if (count of item_list) = 0 then 165 | return 166 | end if 167 | 168 | # Make sure DEVONthink is running, and then do the imports. 169 | my launch_application("DEVONthink 3", true) 170 | if (count of item_list) = 1 then 171 | my import_item(first item of item_list) 172 | else 173 | tell application "DEVONthink 3" 174 | set answer to display dialog ¬ 175 | "Multiple items received. Handle individually, or as a group?" ¬ 176 | with title my get_script_name() with icon 1 ¬ 177 | buttons {"Individually", "As a group", "Cancel"} ¬ 178 | default button "Individually" cancel button "Cancel" ¬ 179 | giving up after 30 180 | if button returned of answer = "Cancel" then 181 | return 182 | else if button returned of answer = "Individually" then 183 | repeat with thing in item_list 184 | my import_item(thing) 185 | end repeat 186 | else 187 | my import_all_items(item_list) 188 | end if 189 | end tell 190 | end if 191 | 192 | # Do this last, so that nothing is deleted if the user cancels. 193 | my move_to_trash(item_list) 194 | on error msg number err 195 | if the err is not -128 then # (Code -128 means user cancelled.) 196 | set txt to "The following error occurred when running " & ¬ 197 | "the folder action: " & linefeed & linefeed & msg 198 | display dialog txt buttons {"OK"} ¬ 199 | with title "Folder action '" & my get_script_name() & "'" ¬ 200 | with icon 0 default button 1 giving up after 60 201 | end if 202 | end try 203 | end adding folder items to 204 | -------------------------------------------------------------------------------- /finder-folder-actions/import-to-devonthink/Makefile: -------------------------------------------------------------------------------- 1 | ../Makefile -------------------------------------------------------------------------------- /finder-folder-actions/import-to-devonthink/README.md: -------------------------------------------------------------------------------- 1 | # Auto-import to DEVONthink 2 | 3 | This is a script meant to be used as a [Folder Actions](https://developer.apple.com/library/archive/documentation/AppleScript/Conceptual/AppleScriptLangGuide/reference/ASLR_folder_actions.html) script attached to a folder in the Finder. Its job is to watch for additions to the folder, and when items are added, it will tell DEVONthink to import it with a dialog. It differs from a similar folder action script provided as part of DEVONthink (that one is named _DEVONthink - Import & Delete_) in the following ways: 4 | 5 | * If a single item is dropped in the folder, this script will bring up a DEVONthink dialog to ask the user for the destination group, tags to be added, and (optionally) a new name for the item. If the name field is left empty, the script will use the name of the file (minus the path/folder components). 6 | * If multiple items are dropped in the folder, this script will first bring up a dialog to ask the user whether the items should be considered individually or as one group. Depending on the user's answer, the script will then 7 | * Ask for the name, tags, & destination of each item one at a time, or 8 | * Ask for tags and destination once, then move all items to that destination and give them all the same tag. 9 | * It catches more errors and provides (hopefully) more meaningful error dialogs. 10 | * It puts original items in the trash unless the user cancels at any time or an error occurs. 11 | -------------------------------------------------------------------------------- /global-scripts/Makefile: -------------------------------------------------------------------------------- 1 | # Summary: my generic Makefile for compiling AppleScript files. 2 | # 3 | # Copyright 2024 Michael Hucka. 4 | # License: MIT License – see file "LICENSE" in the project website. 5 | # Website: https://github.com/mhucka/devonthink-hacks 6 | 7 | # Preliminary settings and tests ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 8 | 9 | SHELL=/bin/bash 10 | .ONESHELL: # Run all commands in the same shell. 11 | .SHELLFLAGS += -e # Exit at the first error. 12 | 13 | thisdir := $(shell basename $(CURDIR)) 14 | 15 | # When I run M-x compile using this Makefile, the compile target works but the 16 | # install target fails. It works outside Emacs in a regular shell terminal. I 17 | # haven't figured out the reason, so for now, this test reminds me to avoid it. 18 | 19 | ifeq ($(origin INSIDE_EMACS),environment) 20 | $(error "Do not run make from inside Emacs with this Makefile.") 21 | endif 22 | 23 | 24 | # Print help if no command is given ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 25 | 26 | # The help scheme works by looking for lines that begin with "#:" above the 27 | # definitions of commands. Originally based on code posted to Stack Overflow on 28 | # 2019-11-28 by Richard Kiefer at https://stackoverflow.com/a/59087509/743730 29 | 30 | #: Print a summary of available commands. 31 | help: 32 | @echo "This is the Makefile for $(bright)$(thisdir)$(reset)." 33 | @echo "Available commands:" 34 | @echo 35 | @grep -B1 -E "^[a-zA-Z0-9_-]+\:([^=]|$$)" $(MAKEFILE_LIST) \ 36 | | grep -v -- -- \ 37 | | sed 'N;s/\n/###/' \ 38 | | sed -n 's/^#: \(.*\)###\([a-zA-Z0-9_-]*\):.*/$(color)\2$(reset):###\1/p' \ 39 | | column -t -s '###' 40 | 41 | 42 | # Compile source files ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 43 | 44 | # Getting Make to handle path names with embedded spaces is insanely hard. 45 | # The following incredibly obscure workarounds are the combination of two 46 | # methods: (1) by user "mathematical.coffee" posted on 2014-02-11 47 | # at https://stackoverflow.com/a/21694624/743730, and (2) by user Yann-Gaël 48 | # Guéhéneuc posted on 2016-02-25 at https://stackoverflow.com/a/35617603/743730 49 | 50 | # We first create a list of source files in a way that bypasses the regular 51 | # Make file pattern matching, and then replace every space character in 52 | # each pathname with a question mark character. 53 | 54 | sources = $(shell find . -iname '*.applescript' -maxdepth 1 | cut -d/ -f2- | tr ' ' '?') 55 | 56 | # Next, define a function named "q2s" to replace question marks by spaces, 57 | # using an absolutely bonkers trick to assign a space character to "space". 58 | 59 | space := 60 | space += 61 | q2s = $(subst ?, $(space),$1) 62 | 63 | # Now define the pattern rule to create compile .scpt files from .applescript 64 | # files. A rule of the form %.scpt would not work if the file names had space 65 | # characters in them (because the outcome of Make expanding %.scpt would have 66 | # spaces in the result, and those spaces would act as file name delimiters). 67 | # But if the file names used with this pattern rule have question marks 68 | # instead of spaces, they're atomic units as far as the pattern matching 69 | # mechanism is concerned, and so the rule works. We just have to do some 70 | # substitution to reconstruct the real file names before we run commands that 71 | # act on the files themselves. (That's where q2s comes in.) 72 | 73 | .SECONDEXPANSION: 74 | %.scpt: %.applescript 75 | osacompile -o "$(call q2s,$*.scpt)" "$<" 76 | 77 | # The final ingredient is to define the target "compile" such that it depends 78 | # on a list of .scpt files. This is simply done by taking the list of source 79 | # files (names such as "Some?file?name.applescript") and replacing the 80 | # extensions. The resulting list (names like "Some?file?name.scpt") then 81 | # satisfies the %.scpt rule above. 82 | 83 | #: Compile the AppleScript files in this directory. 84 | compile: $(sources:.applescript=.scpt) 85 | 86 | 87 | # Install binaries ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 88 | 89 | destdir = "$(HOME)/Library/Scripts" 90 | 91 | .SECONDEXPANSION: 92 | $(destdir)/%.scpt: %.applescript 93 | install -bpS "$(call q2s,$*.scpt)" $(call q2s,$(destdir)) 94 | 95 | #: Install the compiled scripts in DEVONthink's script directory. 96 | install: $(addprefix $(destdir)/,$(sources:.applescript=.scpt)) 97 | 98 | 99 | # Cleanup ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 100 | 101 | #: Clean this directory. 102 | clean: 103 | rm -rf *.scpt 104 | 105 | 106 | # Miscellaneous directives ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 107 | 108 | #: Print a random joke from https://icanhazdadjoke.com/. 109 | joke: 110 | @echo "$(shell curl -s https://icanhazdadjoke.com/)" 111 | 112 | # Color codes used in messages. 113 | color := $(shell tput bold; tput setaf 6) 114 | bright := $(shell tput bold; tput setaf 15) 115 | dim := $(shell tput setaf 66) 116 | link := $(shell tput setaf 111) 117 | reset := $(shell tput sgr0) 118 | 119 | .PHONY: help clean joke 120 | 121 | .SILENT: clean 122 | -------------------------------------------------------------------------------- /global-scripts/README.md: -------------------------------------------------------------------------------- 1 | # Global scripts for working with DEVONthink 2 | 3 | These are scripts that I use to work with DEVONthink from _other_ applications. I put these in `~/Library/Scripts`. 4 | -------------------------------------------------------------------------------- /global-scripts/create-markdown-note-for-quote/Create note in DEVONthink for code.applescript: -------------------------------------------------------------------------------- 1 | -- Create a Markdown note, treating the highlighted text as code. 2 | -- 3 | -- This assumes there is a template file in the DEVONthink template. 4 | -- dir. The file name is set by the "templateFileName" parameter below. 5 | -- 6 | -- Copyright 2024 Michael Hucka. 7 | -- License: MIT License – see file "LICENSE" in the project website. 8 | -- Website: https://github.com/mhucka/devonthink-hacks 9 | 10 | -- Configuration variables ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 11 | 12 | -- Name of the template file used to create the Markdown document. 13 | property templateFileName : "Code.md" 14 | 15 | -- Truncate the name of the document if it's longer than this. 16 | property maxDocTitleLength : 255 17 | 18 | 19 | -- Main logic ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 20 | 21 | tell application id "com.apple.systemevents" 22 | try 23 | -- Get a URL and title to refer back to the open document. 24 | set theApp to first application process whose frontmost is true 25 | set {sourceURL, sourceTitle} to my sourceInfo for theApp 26 | 27 | -- Copy the text highlighted in the current application. I couldn't 28 | -- find a more direct way of doing this than to use GUI scripting. 29 | keystroke "c" using {command down} 30 | on error msg number err 31 | display alert "Create note for quote" message msg as warning 32 | end try 33 | end tell 34 | 35 | tell application id "DNtp" 36 | try 37 | -- Get the path of the template to be used to create the new doc. 38 | set supDir to path to application support from user domain as text 39 | set tpDir to POSIX path of (supDir & "DEVONthink 3:Templates.noindex:") 40 | set templateFile to (POSIX path of tpDir & templateFileName) 41 | 42 | set selectedText to the clipboard as text 43 | 44 | -- Make a default title based on a truncated version of the text 45 | if length of selectedText is less than maxDocTitleLength then 46 | set docTitle to selectedText 47 | else 48 | set docTitle to text 1 thru maxDocTitleLength of selectedText 49 | end if 50 | 51 | -- If you don't use the "placeholders" option, DEVONthink will not 52 | -- substitute its built-in placeholders. So, use at least one. 53 | set newDoc to import templateFile placeholders {name:docTitle} name docTitle 54 | set creation date of newDoc to current date 55 | set modification date of newDoc to current date 56 | set URL of newDoc to sourceURL 57 | 58 | -- Replace additional custom placeholders. DEVONthink can't do 59 | -- these itself because the document has to be created first. 60 | set docURL to reference URL of newDoc as string 61 | set docRevealURL to docURL & "?reveal=1" 62 | if templateFileName ends with "md" then 63 | set body to plain text of newDoc 64 | set body to my replace(body, "%sourceURL%", sourceURL) 65 | set body to my replace(body, "%sourceTitle%", sourceTitle) 66 | set body to my replace(body, "%documentURL%", docURL) 67 | set body to my replace(body, "%documentRevealURL%", docRevealURL) 68 | set body to my replace(body, "%UUID%", uuid of newDoc) 69 | set plain text of newDoc to body 70 | end if 71 | on error msg number err 72 | if err is not -128 then ¬ 73 | display alert "DEVONthink" message msg as warning 74 | end try 75 | end tell 76 | 77 | 78 | -- Miscellaneous handlers ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 79 | 80 | -- Get the URL or path of the frontmost document. 81 | -- The code to get the page URL from DEVONthink was modified from a post 82 | -- by user "pete31" to the DEVONthink forums on 2021-07-28 at https:// 83 | -- discourse.devontechnologies.com/t/link-to-specific-page-of-a-pdf/65898/3 84 | on sourceInfo for (theApp) 85 | set appName to name of theApp 86 | try 87 | -- First, check for common cases in which we know exactly what to do. 88 | if appName = "DEVONthink 3" then 89 | tell application id "DNtp" 90 | if exists think window 1 then 91 | set theWindow to think window 1 92 | else 93 | display notification "No DEVONthink window." ¬ 94 | with title "DEVONthink" 95 | return {"", "Document"} 96 | end if 97 | set rec to content record of theWindow 98 | if rec = missing value then 99 | display notification "Could not get window contents." ¬ 100 | with title "DEVONthink" 101 | return {"", "Document"} 102 | end if 103 | set sourceTitle to name of rec 104 | set sourceURL to reference URL of rec 105 | set recType to (type of rec) as string 106 | if recType is in {"PDF document", "«constant ****pdf »"} then 107 | set thePage to current page of theWindow 108 | if thePage ≠ -1 then 109 | set sourceURL to reference URL of rec & "?page=" & thePage 110 | end if 111 | end if 112 | return {sourceURL, sourceTitle} 113 | end tell 114 | else if appName = "Safari" or appName = "Webkit" then 115 | using terms from application "Safari" 116 | tell application "Safari" 117 | return {URL of front document, name of front document} 118 | end tell 119 | end using terms from 120 | else if (appName contains "Chrome") or (appName = "Chromium") then 121 | using terms from application "Google Chrome" 122 | tell application appName 123 | set sourceURL to URL of active tab of front window 124 | set sourceTitle to title of active tab of front window 125 | return {sourceURL, sourceTitle} 126 | end tell 127 | end using terms from 128 | else if appName = "Preview" then 129 | tell application id "com.apple.Preview" 130 | return {path of document 1, name of document 1} 131 | end tell 132 | else if appName = "TextEdit" then 133 | tell application "TextEdit" 134 | set theDoc to document of window 1 135 | return {path of theDoc, name of theDoc} 136 | end tell 137 | else if appName = "OmniOutliner" then 138 | tell application "OmniOutliner" 139 | set theFile to file of document 1 140 | return {POSIX path of theFile, name of document 1} 141 | end tell 142 | end if 143 | 144 | -- If we get here, it means we didn't recognize the application. Assume 145 | -- the source is a document, and use a generic approach to try get the 146 | -- path to the file in the current app's window. The next bit of code 147 | -- is based in part on a posting by user "user2330514" on 2016-11-01 148 | -- to Stack Overflow at https://stackoverflow.com/a/58145535/743730 149 | -- Note: the "tell systemevents" part is needed to avoid error -2471. 150 | tell application id "com.apple.systemevents" 151 | tell process appName 152 | if exists (1st window whose value of attribute "AXMain" is true) then 153 | tell (1st window whose value of attribute "AXMain" is true) 154 | set sourceURL to value of attribute "AXDocument" 155 | set sourceTitle to value of attribute "AXTitle" 156 | return {sourceURL, sourceTitle} 157 | end tell 158 | end if 159 | end tell 160 | end tell 161 | 162 | -- Fall-through default case, if the code above doesn't return a value. 163 | return {"", "Document"} 164 | on error msg number err 165 | if err is not -128 then ¬ 166 | display alert appName message msg as warning 167 | end try 168 | end sourceInfo 169 | 170 | 171 | -- Replace text inside a DEVONthink document. 172 | on replace(theText, placeholder, value) 173 | if theText contains placeholder then 174 | local od 175 | set {od, text item delimiters of AppleScript} to ¬ 176 | {text item delimiters of AppleScript, placeholder} 177 | set theText to text items of theText 178 | set text item delimiters of AppleScript to value 179 | set theText to "" & theText 180 | set text item delimiters of AppleScript to od 181 | end if 182 | return theText 183 | end replace 184 | -------------------------------------------------------------------------------- /global-scripts/create-markdown-note-for-quote/Create note in DEVONthink for quote.applescript: -------------------------------------------------------------------------------- 1 | -- Create a Markdown note, treating the highlighted text as a quote. 2 | -- 3 | -- This assumes there is a template file in DEVONthink template diretory. 4 | -- The file name is set by the "templateFileName" parameter below. 5 | -- 6 | -- Copyright 2024 Michael Hucka. 7 | -- License: MIT License – see file "LICENSE" in the project website. 8 | -- Website: https://github.com/mhucka/devonthink-hacks 9 | 10 | -- Configuration variables ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 11 | 12 | -- Name of the template file used to create the Markdown document. (Tip: add 13 | -- tags to this file in the Finder and the DEVONthink doc will inherit them.) 14 | property templateFileName : "Quote.md" 15 | 16 | -- Truncate the name of the document if it's longer than this. 17 | property maxDocTitleLength : 100 18 | 19 | 20 | -- Main logic ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 21 | 22 | tell application id "com.apple.systemevents" 23 | try 24 | -- Get a URL and title to refer back to the open document. 25 | set theApp to first application process whose frontmost is true 26 | set {sourceURL, sourceTitle} to my sourceInfo for theApp 27 | 28 | -- Copy the text highlighted in the current application. I couldn't 29 | -- find a more direct way of doing this than to use GUI scripting. 30 | keystroke "c" using {command down} 31 | -- Critical: need this delay, or the clipboard may have stale data. 32 | delay 0.5 33 | on error msg number err 34 | display alert "Create note for quote" message msg as warning 35 | end try 36 | end tell 37 | 38 | tell application id "DNtp" 39 | try 40 | -- Get the path of the template to be used to create the new doc. 41 | set supDir to path to application support from user domain as text 42 | set tpDir to POSIX path of (supDir & "DEVONthink 3:Templates.noindex:") 43 | set templateFile to (POSIX path of tpDir & templateFileName) 44 | 45 | -- Unwrap the text in the clipboard. 46 | set selectedText to the clipboard as text 47 | set AppleScript's text item delimiters to {return, linefeed} 48 | set itemList to every text item of selectedText 49 | set AppleScript's text item delimiters to " " 50 | set the clipboard to the itemList as string 51 | 52 | -- Make a default title based on a truncated version of the text 53 | if length of selectedText is less than maxDocTitleLength then 54 | set docTitle to selectedText 55 | else 56 | set docTitle to text 1 thru maxDocTitleLength of selectedText 57 | set docTitle to docTitle & "…" 58 | end if 59 | set shortDate to (short date string of (current date)) 60 | set docTitle to shortDate & " clipped: \"" & docTitle & "\"" 61 | 62 | -- If you don't use the "placeholders" option, DEVONthink will not 63 | -- substitute its built-in placeholders. So, use at least one. 64 | set newDoc to import templateFile placeholders {name:docTitle} name docTitle 65 | set creation date of newDoc to current date 66 | set modification date of newDoc to current date 67 | set URL of newDoc to sourceURL 68 | 69 | -- Replace additional custom placeholders. DEVONthink can't do 70 | -- these itself because the document has to be created first. 71 | set docURL to reference URL of newDoc as string 72 | set docRevealURL to docURL & "?reveal=1" 73 | if templateFileName ends with "md" then 74 | set body to plain text of newDoc 75 | set body to my replace(body, "%sourceURL%", sourceURL) 76 | set body to my replace(body, "%sourceTitle%", sourceTitle) 77 | set body to my replace(body, "%documentURL%", docURL) 78 | set body to my replace(body, "%documentRevealURL%", docRevealURL) 79 | set body to my replace(body, "%UUID%", uuid of newDoc) 80 | set plain text of newDoc to body 81 | end if 82 | on error msg number err 83 | if err is not -128 then ¬ 84 | display alert "DEVONthink" message msg as warning 85 | end try 86 | end tell 87 | 88 | 89 | -- Miscellaneous handlers ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 90 | 91 | -- Get the URL or path of the frontmost document. 92 | -- The code to get the page URL from DEVONthink was modified from a post 93 | -- by user "pete31" to the DEVONthink forums on 2021-07-28 at https:// 94 | -- discourse.devontechnologies.com/t/link-to-specific-page-of-a-pdf/65898/3 95 | on sourceInfo for (theApp) 96 | set appName to name of theApp 97 | try 98 | -- First, check for common cases in which we know exactly what to do. 99 | if appName = "DEVONthink 3" then 100 | tell application id "DNtp" 101 | if exists think window 1 then 102 | set theWindow to think window 1 103 | else 104 | display notification "No DEVONthink window." ¬ 105 | with title "DEVONthink" 106 | return {"", "Document"} 107 | end if 108 | set rec to content record of theWindow 109 | if rec = missing value then 110 | display notification "Could not get window contents." ¬ 111 | with title "DEVONthink" 112 | return {"", "Document"} 113 | end if 114 | set sourceTitle to name of rec 115 | set sourceURL to reference URL of rec 116 | set recType to (type of rec) as string 117 | if recType is in {"PDF document", "«constant ****pdf »"} then 118 | set thePage to current page of theWindow 119 | if thePage ≠ -1 then 120 | set sourceURL to reference URL of rec & "?page=" & thePage 121 | end if 122 | end if 123 | return {sourceURL, sourceTitle} 124 | end tell 125 | else if appName = "Safari" or appName = "Webkit" then 126 | using terms from application "Safari" 127 | tell application "Safari" 128 | return {URL of front document, name of front document} 129 | end tell 130 | end using terms from 131 | else if (appName contains "Chrome") or (appName = "Chromium") then 132 | using terms from application "Google Chrome" 133 | tell application appName 134 | set sourceURL to URL of active tab of front window 135 | set sourceTitle to title of active tab of front window 136 | return {sourceURL, sourceTitle} 137 | end tell 138 | end using terms from 139 | else if appName = "Preview" then 140 | tell application id "com.apple.Preview" 141 | return {path of document 1, name of document 1} 142 | end tell 143 | else if appName = "TextEdit" then 144 | tell application "TextEdit" 145 | set theDoc to document of window 1 146 | return {path of theDoc, name of theDoc} 147 | end tell 148 | else if appName = "OmniOutliner" then 149 | tell application "OmniOutliner" 150 | set theFile to file of document 1 151 | return {POSIX path of theFile, name of document 1} 152 | end tell 153 | end if 154 | 155 | -- If we get here, it means we didn't recognize the application. Assume 156 | -- the source is a document, and use a generic approach to try get the 157 | -- path to the file in the current app's window. The next bit of code 158 | -- is based in part on a posting by user "user2330514" on 2016-11-01 159 | -- to Stack Overflow at https://stackoverflow.com/a/58145535/743730 160 | -- Note: the "tell systemevents" part is needed to avoid error -2471. 161 | tell application id "com.apple.systemevents" 162 | tell process appName 163 | if exists (1st window whose value of attribute "AXMain" is true) then 164 | tell (1st window whose value of attribute "AXMain" is true) 165 | set sourceURL to value of attribute "AXDocument" 166 | set sourceTitle to value of attribute "AXTitle" 167 | return {sourceURL, sourceTitle} 168 | end tell 169 | end if 170 | end tell 171 | end tell 172 | 173 | -- Fall-through default case, if the code above doesn't return a value. 174 | return {"", "Document"} 175 | on error msg number err 176 | if err is not -128 then ¬ 177 | display alert appName message msg as warning 178 | end try 179 | end sourceInfo 180 | 181 | 182 | -- Replace text inside a DEVONthink document. 183 | on replace(theText, placeholder, value) 184 | if theText contains placeholder then 185 | local od 186 | set {od, text item delimiters of AppleScript} to ¬ 187 | {text item delimiters of AppleScript, placeholder} 188 | set theText to text items of theText 189 | set text item delimiters of AppleScript to value 190 | set theText to "" & theText 191 | set text item delimiters of AppleScript to od 192 | end if 193 | return theText 194 | end replace 195 | -------------------------------------------------------------------------------- /global-scripts/create-markdown-note-for-quote/Makefile: -------------------------------------------------------------------------------- 1 | ../Makefile -------------------------------------------------------------------------------- /global-scripts/open-url-in-devonthink/Makefile: -------------------------------------------------------------------------------- 1 | ../Makefile -------------------------------------------------------------------------------- /global-scripts/open-url-in-devonthink/Open current browser URL in DEVONthink.applescript: -------------------------------------------------------------------------------- 1 | -- Open current web browser location in DEVONthink 2 | -- 3 | -- Copyright 2024 Michael Hucka. 4 | -- License: MIT License – see file "LICENSE" in the project website. 5 | -- Website: https://github.com/mhucka/devonthink-hacks 6 | 7 | use AppleScript version "2.4" -- Yosemite (10.10) or later 8 | use scripting additions 9 | 10 | -- Helper functions ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 11 | 12 | on showError(msg) 13 | display dialog msg buttons {"OK"} default button 1 with icon 0 14 | end showError 15 | 16 | on getWebPageData() 17 | set status to "OK" 18 | set theURL to "" 19 | set theTitle to "" 20 | 21 | tell application "System Events" 22 | set frontApp to item 1 of (get name of processes whose frontmost is true) 23 | end tell 24 | 25 | try 26 | if (frontApp = "Safari") then 27 | tell application "Safari" 28 | set theTitle to get name of the current tab of the front window 29 | set theURL to get URL of the current tab of the front window 30 | end tell 31 | else if (frontApp = "Google Chrome") then 32 | tell application "Google Chrome" 33 | set theTitle to get title of the active tab of the front window 34 | set theURL to get URL of the active tab of the front window 35 | end tell 36 | else 37 | set status to "Don't know how to get the URL from " & frontApp & "." 38 | end if 39 | on error 40 | set status to "Could not obtain info from " & frontApp & "." 41 | end try 42 | 43 | return {status, theURL, theTitle} 44 | end getWebPageData 45 | 46 | -- Main body ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 47 | 48 | tell application "DEVONthink 3" to launch 49 | 50 | set {status, theURL, theTitle} to my getWebPageData() 51 | if status as string is equal to "OK" then 52 | tell application id "DNtp" 53 | set theGroup to preferred import destination 54 | set theBookmark to create record with {name:theTitle, type:bookmark, URL:theURL} in theGroup 55 | open window for record theBookmark 56 | activate 57 | end tell 58 | else 59 | my showError(status) 60 | error number -128 61 | end if 62 | -------------------------------------------------------------------------------- /global-scripts/open-url-in-devonthink/README.md: -------------------------------------------------------------------------------- 1 | # Open current Safari location in DEVONthink 2 | 3 | This is a small script to open in DEVONthink the page that is currently visible in Safari. It is basically identical to creating a bookmark for the current URL then opening the bookmark in a separate DEVONthink window. This is handy when you want to convert a page to PDF in DEVONthink but the site requires a login session: if you log into the website _within DEVONthink itself_, you can then navigate normally and save web pages while having the benefit of having a normal login session active. 4 | 5 | This short AppleScript program is currently written specifically to work with Safari and Google Chrome, but it could easily be made more general to support other browsers too. 6 | -------------------------------------------------------------------------------- /obsolete-no-longer-used/README.md: -------------------------------------------------------------------------------- 1 | # Obsolete scripts, no longer used 2 | 3 | This directory contains stuff that is no longer needed. In some cases, later versions of DEVONthink introduced features that obviated the need for the scripts; in other cases, I just stopped using them. They are left here in case it's ever useful to refer to them. 4 | -------------------------------------------------------------------------------- /obsolete-no-longer-used/insert-tags-into-markdown/README.md: -------------------------------------------------------------------------------- 1 | # Insert tags into Markdown document 2 | 3 | The original code for this script came from a [posting by Christian Grunenberg during 2020-04](https://discourse.devontechnologies.com/t/help-with-a-way-script-to-copy-dt-tags-to-first-line-of-md-file/55057/2) to the DEVONthink user forums. 4 | 5 | To use this, I copy the AppleScript into the action of a smart rule, as an embedded script. 6 | 7 | -------------------------------------------------------------------------------- /obsolete-no-longer-used/insert-tags-into-markdown/insert-tags-into-markdown.applescript: -------------------------------------------------------------------------------- 1 | -- Summary: find a line that begins with "tags:" & append document tags. 2 | -- 3 | -- This code is based on code posted by Christian Grunenberg to a 4 | -- DEVONthink forum in April 2020 at 5 | -- https://discourse.devontechnologies.com/t/help-with-a-way-script-to-copy-dt-tags-to-first-line-of-md-file/55057/2 6 | 7 | tell application id "DNtp" 8 | repeat with theRecord in (selection as list) 9 | if type of theRecord is markdown then 10 | set theText to plain text of theRecord 11 | set theTags to "" 12 | set theTagList to tags of theRecord 13 | if (count of theTagList) is greater than 0 and theText begins with "Tag:" then 14 | repeat with theTag in theTagList 15 | if theTags is not "" then set theTags to theTags & ", " 16 | set theTags to theTags & "#" & theTag 17 | end repeat 18 | set theText to "tag: " & theTags & return & return & theText 19 | set plain text of theRecord to theText 20 | end if 21 | end if 22 | end repeat 23 | end tell 24 | -------------------------------------------------------------------------------- /obsolete-no-longer-used/open-annotation-file/README.md: -------------------------------------------------------------------------------- 1 | # Open annotation file 2 | 3 | DEVONthink 3 has a mechanism that allows you to create a document that stores comments and other content about _another_ document. In DEVONthink, this is called an "annotation file" or sometimes just "annotation" (not to be confused with other kinds of annotations, such as annotations in PDF files). The facility is accessed from the _Annotations & Reminders_ inspector panel, via a drop-down menu accessible by clicking on _Annotations ▾_ in the lower half. 4 | 5 |

6 | 7 |

8 | 9 | Editing annotations is much more conveniently done in a separate window. If a document has an annotation file associated with it, the "Open" command is available in the pull-down menu (as shown above). I wanted to have a keyboard shortcut to open annotation files in separate windows, but unfortunately, DEVONthink does not provide a keyboard shortcut for this command; more unfortunately, you can't target this command via macOS's built-in shortcuts facility, and I haven't found a way to invoke it directly via [Keyboard Maestro](https://www.keyboardmaestro.com/main/). 10 | 11 | Thankfully, it _is_ possible to write a short AppleScript program to tell DEVONthink to open the annotation file in a new window, and this in turn _can_ be set up as an action in [Keyboard Maestro](https://www.keyboardmaestro.com/main/) with a keyboard shortcut. 12 | 13 | The script in this directory tells DEVONthink to open a new window on the annotation file for the currently-selected document. It will also work if multiple documents are selected when it is invoked. 14 | 15 | -------------------------------------------------------------------------------- /obsolete-no-longer-used/open-annotation-file/annotations-drop-down.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mhucka/devonthink-hacks/9a4211aa29f9163ce625b9fb9ede2dccdb6546f1/obsolete-no-longer-used/open-annotation-file/annotations-drop-down.png -------------------------------------------------------------------------------- /obsolete-no-longer-used/open-annotation-file/open-annotation-file.applescript: -------------------------------------------------------------------------------- 1 | -- Summary: open annotation file associated w/ selected document. 2 | -- 3 | -- Copyright 2024 Michael Hucka. 4 | -- License: MIT License – see file "LICENSE" in the project website. 5 | -- Website: https://github.com/mhucka/devonthink-hacks 6 | 7 | tell application id "DNtp" 8 | try 9 | repeat with theRecord in (selection as list) 10 | if (exists annotation of theRecord) then 11 | set annot to get annotation of theRecord 12 | set annotRecord to get record with uuid (get uuid of annot) 13 | open window for record annotRecord with force 14 | else 15 | set rec_name to get name of theRecord 16 | display alert "Document has no annotation" message rec_name 17 | end if 18 | end repeat 19 | on error error_message number error_number 20 | if the error_number is not -128 then 21 | display alert "DEVONthink" message error_message as warning 22 | end if 23 | end try 24 | end tell 25 | --------------------------------------------------------------------------------