├── .gitignore ├── icon.png ├── media └── demo.gif ├── src ├── index.html └── index.ts ├── package.json ├── README.md └── .github └── workflows └── publish.yml /.gitignore: -------------------------------------------------------------------------------- 1 | /.vscode 2 | /node_modules 3 | /.parcel-cache 4 | .DS_Store 5 | /dist 6 | -------------------------------------------------------------------------------- /icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhengyu-yang/logseq-plugin-omnifocus/HEAD/icon.png -------------------------------------------------------------------------------- /media/demo.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhengyu-yang/logseq-plugin-omnifocus/HEAD/media/demo.gif -------------------------------------------------------------------------------- /src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 7 | 8 | Document 9 | 10 | 11 |
12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "logseq-plugin-omnifocus", 3 | "version": "0.1.3", 4 | "description": "Send tasks to OmniFocus", 5 | "main": "dist/index.html", 6 | "targets": { 7 | "main": false 8 | }, 9 | "default": "dist/index.html", 10 | 11 | "scripts": { 12 | "dev": "parcel ./src/index.html --public-url ./", 13 | "build": "parcel build --public-url . --no-source-maps src/index.html" 14 | }, 15 | 16 | 17 | "author": "Zhengyu Yang", 18 | 19 | "license": "MIT", 20 | 21 | "devDependencies": { 22 | "parcel": "^2.0.0" 23 | }, 24 | 25 | "dependencies": { 26 | "@logseq/libs": "^0.0.14", 27 | "remove-markdown": "^0.5.0" 28 | }, 29 | 30 | "logseq": { 31 | "id": "logseq-plugin-omnifocus", 32 | "title": "Logseq OmniFocus", 33 | "icon": "./icon.png" 34 | } 35 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Logseq OmniFocus Plugin 2 | 3 |

4 | 5 |

A simple plugin for sending tasks from Logseq to OmniFocus.

6 |

7 | 8 | 9 | ## Effect 10 | - Send selected blocks in Logseq to OmniFocus as tasks in Inbox 11 | - Syntax symbols like markdown, Logseq markers, etc. will be cleaned before being sent to OmniFocus 12 | - The first line of each block will be the name of the task 13 | - The full content of the block will be the note of the task 14 | - Deadline -> Due, Scheduled -> Defer 15 | - A `logseq` tag is attached to the task 16 | - A link pointing to the original block will be attached to the note 17 | 18 | ## Usage 19 | ![demo](media/demo.gif) 20 | - Slash command 21 | - Type `\Send to OmniFocus` to send the current block to OmniFocus 22 | - Context Menu 23 | - Click the bullet at the right of the block to send it 24 | - Command palette 25 | - Send the selected blocks to OmniFocus 26 | - `cmd+shift+p` to activate the command palette 27 | - Select `Send to OmniFocus` 28 | - Keyboard Shortcut 29 | - `mod+ctrl+o` (default) to send the selected blocks to OmniFocus 30 | - You can change the default shortcut in Setting > Plugins > Logseq OmniFocus 31 | 32 | ## Install 33 | Install the plugin in Logseq marketplace or download it from the release section. 34 | To use the plugin, you must enable Automation in OmniFocus. You will be prompted when you launch this plugin for the first time. To avoid being prompted every time you send a task, please trust this script in OmniFocus. 35 | -------------------------------------------------------------------------------- /.github/workflows/publish.yml: -------------------------------------------------------------------------------- 1 | name: Build plugin 2 | 3 | on: 4 | push: 5 | # Sequence of patterns matched against refs/tags 6 | tags: 7 | - '*' # Push events to matching any tag format, i.e. 1.0, 20.15.10 8 | 9 | env: 10 | PLUGIN_NAME: logseq-plugin-omnifocus 11 | 12 | jobs: 13 | build: 14 | runs-on: ubuntu-latest 15 | 16 | steps: 17 | - uses: actions/checkout@v3 18 | - name: Use Node.js 19 | uses: actions/setup-node@v3 20 | with: 21 | node-version: 16 # You might need to adjust this value to your own version 22 | - name: Build 23 | id: build 24 | run: | 25 | npm i && npm run build 26 | mkdir ${{ env.PLUGIN_NAME }} 27 | cp README.md package.json icon.png ${{ env.PLUGIN_NAME }} 28 | mv dist ${{ env.PLUGIN_NAME }} 29 | zip -r ${{ env.PLUGIN_NAME }}.zip ${{ env.PLUGIN_NAME }} 30 | ls 31 | echo "tag_name=$(git tag --sort version:refname | tail -n 1)" >> $GITHUB_OUTPUT 32 | - name: Create Release 33 | uses: ncipollo/release-action@v1 34 | id: create_release 35 | env: 36 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 37 | VERSION: ${{ github.ref }} 38 | with: 39 | allowUpdates: true 40 | draft: false 41 | prerelease: false 42 | 43 | - name: Upload zip file 44 | id: upload_zip 45 | uses: actions/upload-release-asset@v1 46 | env: 47 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 48 | with: 49 | upload_url: ${{ steps.create_release.outputs.upload_url }} 50 | asset_path: ./${{ env.PLUGIN_NAME }}.zip 51 | asset_name: ${{ env.PLUGIN_NAME }}-${{ steps.build.outputs.tag_name }}.zip 52 | asset_content_type: application/zip 53 | 54 | - name: Upload package.json 55 | id: upload_metadata 56 | uses: actions/upload-release-asset@v1 57 | env: 58 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 59 | with: 60 | upload_url: ${{ steps.create_release.outputs.upload_url }} 61 | asset_path: ./package.json 62 | asset_name: package.json 63 | asset_content_type: application/json -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | import "@logseq/libs"; 2 | import { BlockEntity } from '@logseq/libs/dist/LSPlugin' 3 | import removeMarkdown from 'remove-markdown'; 4 | 5 | const settings = [ 6 | { 7 | key: "KeyboardShortcut_SendToOmniFocus", 8 | title: "Keyboard shortcut to send a block to OmniFocus", 9 | description: "This is the the keyboard shortcut to send a block to OmniFocus (default: mod+ctrl+o)", 10 | type: "string", 11 | default: "mod+ctrl+o" 12 | } as const 13 | ]; 14 | logseq.useSettingsSchema(settings); 15 | 16 | // ref: https://github.com/ahonn/logseq-plugin-todo/blob/6cf084a2419b9c6df78e5eb32ae5d06d73afe4e4/src/models/TaskEntity.ts 17 | function get_clean_block_content(block: BlockEntity) { 18 | let content = block.content; 19 | content = content.replace(block.marker, ''); 20 | content = content.replace(`[#${block.priority}]`, ''); 21 | content = content.replace(/^\s*SCHEDULED: <[^>]+>\s*$/m, ''); 22 | content = content.replace(/^\s*DEADLINE: <[^>]+>\s*$/m, ''); 23 | content = content.replace(/^((:LOGBOOK:)|(:logbook:)|(\*\s.*)|(:END:)|(\s*CLOCK:.*))$/gm, ''); 24 | content = content.replace(/^\s*id::[^:]+\s*$/m, ''); 25 | content = removeMarkdown(content); 26 | return content.trim(); 27 | } 28 | 29 | function convert_logseq_date(date: number | undefined) { 30 | if (date == undefined) { 31 | return "NULL"; 32 | } 33 | const d = date.toString(); 34 | const yyyy = +d.slice(0, 4); 35 | const mm = +d.slice(4, 6) - 1; 36 | const dd = +d.slice(6, 8); 37 | return (new Date(yyyy, mm, dd)).toISOString(); 38 | } 39 | 40 | const of_js_new_task = `(args => { 41 | const task_name = args[0]; 42 | const note = args[1]; 43 | const dueDate = new Date(args[2]); 44 | const deferDate = new Date(args[3]); 45 | 46 | let task = new Task(task_name); 47 | task.note = note; 48 | if (!isNaN(dueDate)){ 49 | task.dueDate = dueDate; 50 | } 51 | if (!isNaN(deferDate)){ 52 | task.deferDate = deferDate; 53 | } 54 | 55 | task.addTag(tagNamed('logseq')); 56 | 57 | })(argument)` 58 | 59 | async function block2OF(block: BlockEntity) { 60 | const content = get_clean_block_content(block); 61 | const name = content.split('\n')[0]; 62 | 63 | const graph_name = (await logseq.App.getCurrentGraph())!.name; 64 | const logseq_block_url = `logseq://graph/${graph_name}?block-id=${block.uuid}` 65 | const note = (content + `\n\n------------\nLogSeq Link: ${logseq_block_url}`).replace(/\n/g, "\\n");; 66 | 67 | const defer_date = convert_logseq_date(block?.scheduled); 68 | const due_date = convert_logseq_date(block?.deadline); 69 | 70 | // let of_url = new URL('omnifocus://localhost/omnijs-run'); 71 | // of_url.searchParams.append('script', of_js_new_task); 72 | // of_url.searchParams.append('arg', `["${name}", "${note}", "${due_date}", "${defer_date}"]`); 73 | 74 | const of_url = `omnifocus://localhost/omnijs-run?script=${encodeURIComponent(of_js_new_task)}&arg=${encodeURIComponent(`["${name}", "${note}", "${due_date}", "${defer_date}"]`)}`; 75 | 76 | // Insert the UUID to file to ensure the link can be opened on other devices 77 | logseq.Editor.upsertBlockProperty(block.uuid, 'id', block.uuid); 78 | 79 | window.open(of_url); 80 | } 81 | 82 | async function selectedBlocks2OF() { 83 | let blocks = await logseq.Editor.getSelectedBlocks(); 84 | // selected blocks from query result have duplication 85 | blocks = blocks!.filter((value, index, self) => 86 | index === self.findIndex((t) => ( 87 | t.uuid === value.uuid 88 | )) 89 | ); 90 | 91 | blocks!.forEach(block2OF); 92 | logseq.UI.showMsg("Send to OmniFocus!"); 93 | } 94 | 95 | async function main() { 96 | console.log("logseq-of-sorted-plugin loaded"); 97 | 98 | logseq.App.registerCommandPalette({ 99 | key: `tidy-blocks-KeyboardShortcut_SendToOmniFocus`, 100 | label: "Send to OmniFocus", 101 | keybinding: { 102 | binding: logseq.settings?.KeyboardShortcut_SendToOmniFocus, 103 | mode: "global", 104 | } 105 | }, async (e) => { 106 | selectedBlocks2OF(); 107 | }); 108 | 109 | logseq.Editor.registerSlashCommand("Send to OmniFocus", async (e) => { 110 | const block = await logseq.Editor.getBlock(e.uuid); 111 | block2OF(block!); 112 | }); 113 | 114 | logseq.Editor.registerBlockContextMenuItem("Send to OmniFocus", async (e) => { 115 | const block = await logseq.Editor.getBlock(e.uuid); 116 | block2OF(block!); 117 | }); 118 | 119 | // lose focus not working 120 | // logseq.App.registerUIItem("toolbar", { 121 | // key: "logseq-send-to-omnifocus", 122 | // template: 123 | // ` 124 | // 125 | // 126 | // 127 | // 128 | // 129 | // ` 130 | // }); 131 | // logseq.provideModel({ 132 | // send2of(e) { 133 | // console.log(e); 134 | // e.preventDefault(); 135 | // selectedBlocks2OF(); 136 | // } 137 | // }); 138 | } 139 | 140 | logseq.ready(main).catch(console.error); 141 | --------------------------------------------------------------------------------