├── .gitignore ├── source ├── icon.png ├── icons │ ├── Warning.png │ ├── icon_label.png │ ├── icon_type.png │ ├── icon_folder.png │ └── paperpAlfred_ico.png ├── 33BF8A0E-B7AA-495E-B89F-1F8239DAECBE.png ├── 6DF20719-C29E-41E0-AEA3-E92AD68EA188.png ├── E2E84778-9472-48A7-851A-87F07879DA5C.png ├── prefs.plist ├── config.py ├── demo_db.py ├── labels.py ├── myTypes.py ├── folders.py ├── papers.py ├── paperpAlfred_functions.py ├── build_db.py └── info.plist ├── images ├── pp_json.png ├── demo_v1.0.gif ├── alfred_prefs.png └── paperpAlfred.png ├── releases ├── paperpAlfred_2.0.alfredworkflow ├── paperpAlfred_2.1.alfredworkflow ├── paperpAlfred_2.2.alfredworkflow └── paperpAlfred_2.3.alfredworkflow └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | *.pyc 3 | __pycache__ 4 | sandbox 5 | prefs.plist 6 | -------------------------------------------------------------------------------- /source/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/giovannicoppola/paperpAlfred/HEAD/source/icon.png -------------------------------------------------------------------------------- /images/pp_json.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/giovannicoppola/paperpAlfred/HEAD/images/pp_json.png -------------------------------------------------------------------------------- /images/demo_v1.0.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/giovannicoppola/paperpAlfred/HEAD/images/demo_v1.0.gif -------------------------------------------------------------------------------- /images/alfred_prefs.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/giovannicoppola/paperpAlfred/HEAD/images/alfred_prefs.png -------------------------------------------------------------------------------- /images/paperpAlfred.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/giovannicoppola/paperpAlfred/HEAD/images/paperpAlfred.png -------------------------------------------------------------------------------- /source/icons/Warning.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/giovannicoppola/paperpAlfred/HEAD/source/icons/Warning.png -------------------------------------------------------------------------------- /source/icons/icon_label.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/giovannicoppola/paperpAlfred/HEAD/source/icons/icon_label.png -------------------------------------------------------------------------------- /source/icons/icon_type.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/giovannicoppola/paperpAlfred/HEAD/source/icons/icon_type.png -------------------------------------------------------------------------------- /source/icons/icon_folder.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/giovannicoppola/paperpAlfred/HEAD/source/icons/icon_folder.png -------------------------------------------------------------------------------- /source/icons/paperpAlfred_ico.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/giovannicoppola/paperpAlfred/HEAD/source/icons/paperpAlfred_ico.png -------------------------------------------------------------------------------- /releases/paperpAlfred_2.0.alfredworkflow: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/giovannicoppola/paperpAlfred/HEAD/releases/paperpAlfred_2.0.alfredworkflow -------------------------------------------------------------------------------- /releases/paperpAlfred_2.1.alfredworkflow: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/giovannicoppola/paperpAlfred/HEAD/releases/paperpAlfred_2.1.alfredworkflow -------------------------------------------------------------------------------- /releases/paperpAlfred_2.2.alfredworkflow: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/giovannicoppola/paperpAlfred/HEAD/releases/paperpAlfred_2.2.alfredworkflow -------------------------------------------------------------------------------- /releases/paperpAlfred_2.3.alfredworkflow: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/giovannicoppola/paperpAlfred/HEAD/releases/paperpAlfred_2.3.alfredworkflow -------------------------------------------------------------------------------- /source/33BF8A0E-B7AA-495E-B89F-1F8239DAECBE.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/giovannicoppola/paperpAlfred/HEAD/source/33BF8A0E-B7AA-495E-B89F-1F8239DAECBE.png -------------------------------------------------------------------------------- /source/6DF20719-C29E-41E0-AEA3-E92AD68EA188.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/giovannicoppola/paperpAlfred/HEAD/source/6DF20719-C29E-41E0-AEA3-E92AD68EA188.png -------------------------------------------------------------------------------- /source/E2E84778-9472-48A7-851A-87F07879DA5C.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/giovannicoppola/paperpAlfred/HEAD/source/E2E84778-9472-48A7-851A-87F07879DA5C.png -------------------------------------------------------------------------------- /source/prefs.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | PAPLIBRARY 6 | ~/Downloads/Paperpile - Library - Jan 22.json 7 | PAPPATH 8 | ~/Library/CloudStorage/GoogleDrive-giovannicoppola@gmail.com/My Drive/Paperpile 9 | 10 | 11 | -------------------------------------------------------------------------------- /source/config.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | import os 4 | 5 | 6 | MAXRES = os.path.expanduser(os.getenv('MAXRESULTS', '')) 7 | LIBRARY_FILE = os.path.expanduser(os.getenv('PAPLIBRARY')) 8 | 9 | # BIBTEX_FILE = os.path.expanduser(os.getenv('PAPLIBRARY_BIB')) 10 | 11 | WF_BUNDLE = os.getenv('alfred_workflow_bundleid') 12 | WF_FOLDER = os.path.expanduser('~')+"/Library/Caches/com.runningwithcrayons.Alfred/Workflow Data/"+WF_BUNDLE+"/" 13 | INDEX_DB = WF_FOLDER+"index.db" 14 | TIMESTAMP = WF_FOLDER+'timestamp.txt' 15 | TIMESTAMP_BIBTEX = WF_FOLDER+'timestamp_bibTeX.txt' 16 | 17 | if not os.path.exists(WF_FOLDER): 18 | os.makedirs(WF_FOLDER) 19 | 20 | -------------------------------------------------------------------------------- /source/demo_db.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | # -*- coding: utf-8 -*- 3 | # 4 | # Rebuilding the database 5 | # rewritten script 6 | # 7 | # previously created on Sunday, February 28, 2021 8 | # March 2022 updated to Python3, eliminated dependencies 9 | 10 | import json 11 | 12 | 13 | 14 | 15 | def importingCompleteLibrary (myFile): # to import complete library before filtering, not used here 16 | ## reading JSON data in 17 | with open(myFile, "r") as read_file: 18 | json_data = json.load(read_file) 19 | 20 | mySubset = [x for x in json_data if "labelsNamed" in x and 'interesting' in x['labelsNamed']] 21 | 22 | with open("demo_library.json", "w") as f: 23 | json.dump(mySubset, f,indent=4) 24 | 25 | myFile = 'path/to/library' 26 | importingCompleteLibrary(myFile) 27 | 28 | 29 | -------------------------------------------------------------------------------- /source/labels.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # v1.0 giovanni, March 2021, from @deanishe tutorial 3 | # v2.0 Feb 2022 - Updated for Python3 4 | 5 | 6 | import sys 7 | import sqlite3 8 | from config import INDEX_DB 9 | import json 10 | 11 | 12 | 13 | myQuery=sys.argv[1] 14 | 15 | 16 | 17 | db = sqlite3.connect(INDEX_DB) 18 | cursor = db.cursor() 19 | result = {"items": []} 20 | 21 | if (not myQuery): 22 | cursor.execute("""SELECT * FROM Labels ORDER BY CAST (totalLabel as integer) DESC""") 23 | resultsQ = cursor.fetchall() 24 | 25 | else: 26 | try: 27 | cursor.execute("""SELECT * FROM Labels WHERE label MATCH ? ORDER BY CAST (totalLabel as integer) DESC""",(myQuery+ '*',)) 28 | resultsQ = cursor.fetchall() 29 | 30 | except sqlite3.OperationalError as err: 31 | # If the query is invalid, show an appropriate warning and exit 32 | result= {"items": [{ 33 | "title": "Error: " + str(err), 34 | "subtitle": "Invalid Query", 35 | "arg": ";;", 36 | "icon": { 37 | "path": "icons/Warning.png" 38 | } 39 | }]} 40 | print (json.dumps(result)) 41 | raise err 42 | 43 | 44 | if (resultsQ): 45 | for xx in resultsQ: 46 | 47 | result["items"].append({ 48 | 49 | "title": xx[0] + " (" + xx[1]+")" , 50 | "subtitle": xx[3], 51 | 52 | "valid": True, 53 | "icon": { 54 | "path": "icons/icon_label.png" 55 | }, 56 | "variables": { 57 | "mySource": 'label', 58 | "myLabelID": xx[2], 59 | } 60 | }) 61 | 62 | print (json.dumps(result)) 63 | 64 | 65 | 66 | if (myQuery and not resultsQ): 67 | result["items"].append({ 68 | "title": "No matches", 69 | "subtitle": "Try a different query", 70 | 71 | "arg": "", 72 | "icon": { 73 | 74 | "path": "icons/Warning.png" 75 | } 76 | }) 77 | 78 | print (json.dumps(result)) 79 | 80 | 81 | -------------------------------------------------------------------------------- /source/myTypes.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | # -*- coding: utf-8 -*- 3 | # giovanni, March 2021, from @deanishe's template 4 | # Modified from books, to show paper types for filtering 5 | # Tuesday, March 15, 2022, 2:52 PM update to Python3 6 | 7 | 8 | import sys 9 | import sqlite3 10 | from config import INDEX_DB 11 | import json 12 | 13 | 14 | myQuery=sys.argv[1] 15 | 16 | 17 | 18 | db = sqlite3.connect(INDEX_DB) 19 | cursor = db.cursor() 20 | result = {"items": []} 21 | 22 | if (not myQuery): 23 | cursor.execute("""SELECT * FROM Types ORDER BY CAST (totalType as integer) DESC""") 24 | resultsQ = cursor.fetchall() 25 | 26 | else: 27 | try: 28 | cursor.execute("""SELECT * FROM Types WHERE type MATCH ? ORDER BY CAST (totalType as integer) DESC""",(myQuery+ '*',)) 29 | resultsQ = cursor.fetchall() 30 | 31 | except sqlite3.OperationalError as err: 32 | # If the query is invalid, show an appropriate warning and exit 33 | result= {"items": [{ 34 | "title": "Error: " + str(err), 35 | "subtitle": "Invalid Query", 36 | "arg": ";;", 37 | "icon": { 38 | "path": "icons/Warning.png" 39 | } 40 | }]} 41 | print (json.dumps(result)) 42 | raise err 43 | 44 | 45 | if (resultsQ): 46 | for xx in resultsQ: 47 | 48 | result["items"].append({ 49 | 50 | "title": xx[0] + " (" + xx[1]+")" , 51 | 52 | 53 | "valid": True, 54 | "icon": { 55 | "path": "icons/icon_type.png" 56 | }, 57 | "variables": { 58 | "mySource": 'type', 59 | "myTypeID": xx[0], 60 | } 61 | }) 62 | 63 | print (json.dumps(result)) 64 | 65 | 66 | 67 | if (myQuery and not resultsQ): 68 | result["items"].append({ 69 | "title": "No matches", 70 | "subtitle": "Try a different query", 71 | 72 | "arg": "", 73 | "icon": { 74 | 75 | "path": "icons/Warning.png" 76 | } 77 | }) 78 | 79 | print (json.dumps(result)) 80 | 81 | 82 | 83 | 84 | 85 | -------------------------------------------------------------------------------- /source/folders.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # v1.0 giovanni, March 2021, from @deanishe tutorial 3 | # v2.0 Feb 2022 - Updated for Python3 4 | 5 | 6 | import sys 7 | import sqlite3 8 | from config import INDEX_DB 9 | import json 10 | 11 | 12 | # getting the user query 13 | myQuery=sys.argv[1] 14 | 15 | result = {"items": []} 16 | 17 | 18 | db = sqlite3.connect(INDEX_DB) 19 | cursor = db.cursor() 20 | 21 | if (not myQuery): 22 | cursor.execute("""SELECT * FROM Folders ORDER BY CAST (totalFolder as integer) DESC""") 23 | resultsQ = cursor.fetchall() 24 | 25 | else: 26 | try: 27 | 28 | cursor.execute("""SELECT * FROM Folders WHERE folder MATCH ? ORDER BY CAST (totalFolder as integer) DESC""",(myQuery+ '*',)) 29 | resultsQ = cursor.fetchall() 30 | 31 | 32 | except sqlite3.OperationalError as err: 33 | # If the query is invalid, show an appropriate warning and exit 34 | result= {"items": [{ 35 | "title": "Error: " + str(err), 36 | "subtitle": "Invalid Query", 37 | "arg": ";;", 38 | "icon": { 39 | 40 | "path": "icons/Warning.png" 41 | } 42 | }]} 43 | print (json.dumps(result)) 44 | raise err 45 | 46 | 47 | if (resultsQ): 48 | for xx in resultsQ: 49 | 50 | result["items"].append({ 51 | 52 | "title": xx[0] + " (" + xx[1]+")" , 53 | "subtitle": xx[3], 54 | 55 | "valid": True, 56 | "icon": { 57 | "path": "icons/icon_folder.png" 58 | }, 59 | "variables": { 60 | "mySource": 'folder', 61 | "myFolderID": xx[2], 62 | } 63 | }) 64 | 65 | print (json.dumps(result)) 66 | 67 | 68 | 69 | if (myQuery and not resultsQ): 70 | result["items"].append({ 71 | "title": "No matches", 72 | "subtitle": "Try a different query", 73 | 74 | 75 | "icon": { 76 | 77 | "path": "icons/Warning.png" 78 | } 79 | }) 80 | 81 | print (json.dumps(result)) 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | -------------------------------------------------------------------------------- /source/papers.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | # Search Paperpile library using Alfred 4 | # Search engine structure and code from deanishe@deanishe.net -- THANK YOU! 5 | # MIT Licence. See http://opensource.org/licenses/MIT 6 | # 7 | # November 2020 - March 2021 8 | #https://github.com/giovannicoppola/paperpAlfred/blob/main/README.md 9 | 10 | # February 2022, updated version for Python3 11 | 12 | 13 | 14 | import sys 15 | import os 16 | import json 17 | import sqlite3 18 | 19 | 20 | 21 | from config import INDEX_DB, MAXRES, LIBRARY_FILE, TIMESTAMP 22 | from build_db import * 23 | 24 | 25 | 26 | 27 | try: 28 | 29 | new_time = int(os.path.getmtime(LIBRARY_FILE)) 30 | 31 | except: #error if the library file is missing 32 | result= {"items": [{ 33 | "title": "Library file missing!", 34 | "subtitle": f"cannot locate the Paperpile library file: {LIBRARY_FILE}, {MAXRES}", 35 | "arg": "", 36 | "icon": { 37 | 38 | "path": "icons/Warning.png" 39 | } 40 | }]} 41 | print (json.dumps(result)) 42 | sys.exit("Script aborted – file missing") 43 | 44 | if not os.path.exists(TIMESTAMP): 45 | with open(TIMESTAMP, "w") as f: 46 | f.write(str(new_time)) 47 | f.close 48 | createLibrary (LIBRARY_FILE) 49 | 50 | 51 | 52 | ## checking the timestamp 53 | with open(TIMESTAMP) as f: 54 | old_time = int(f.readline()) #getting the old UNIX timestamp 55 | f.close 56 | 57 | 58 | if new_time != old_time: 59 | 60 | with open(TIMESTAMP, "w") as f: 61 | f.write(str(new_time)) 62 | f.close 63 | 64 | createLibrary (LIBRARY_FILE) 65 | 66 | 67 | # getting the user query 68 | myQuery=sys.argv[1] 69 | 70 | result = {"items": []} 71 | 72 | orderSel = "DESC" 73 | 74 | if "--a" in myQuery: 75 | orderSel = "ASC" 76 | myQuery = myQuery.replace (' --a','') 77 | 78 | #getting the source of the script 79 | mySource=os.path.expanduser(os.getenv('mySource', '')) 80 | myLabelID=os.path.expanduser(os.getenv('myLabelID', '')) 81 | myFolderID=os.path.expanduser(os.getenv('myFolderID', '')) 82 | myTypeID=os.path.expanduser(os.getenv('myTypeID', '')) 83 | 84 | if mySource == "label": 85 | myQuery = "labelID:"+myLabelID+' '+myQuery 86 | 87 | if mySource == "folder": 88 | myQuery = "folderID:"+myFolderID+' '+myQuery 89 | 90 | if mySource == "type": 91 | myQuery = "type:"+myTypeID+' '+myQuery 92 | 93 | 94 | 95 | # Search! 96 | db = sqlite3.connect(INDEX_DB) 97 | cursor = db.cursor() 98 | 99 | 100 | try: 101 | # cursor.execute(""" SELECT _id, abstract, citekey, fileName,first, folder,folderID, fullReference, journal, label,labelID, last, pdfFlag, pmid, subtitle, title, gdrive_id, type, year 102 | # FROM papers WHERE abstract || citekey LIKE ? 103 | # ORDER BY year """ +orderSel + """ LIMIT """ + MAXRES + """ """, (myQuery,)) 104 | 105 | 106 | cursor.execute("""SELECT _id,abstract, citekey, fileName, first, folder,folderID, fullReference, journal, label,labelID, last, pdfFlag, pmid, subtitle, title, gdrive_id, type, year FROM 107 | (SELECT papers 108 | AS r, _id, abstract, citekey, fileName,first, folder,folderID, fullReference, journal, label,labelID, last, pdfFlag, pmid, subtitle, title, gdrive_id, type, year 109 | FROM papers WHERE papers MATCH ?) 110 | ORDER BY year """ +orderSel + """ LIMIT """ + MAXRES + """ """, (myQuery + '*',)) 111 | 112 | 113 | 114 | 115 | results = cursor.fetchall() 116 | 117 | except sqlite3.OperationalError as err: 118 | # If the query is invalid, show an appropriate warning and exit 119 | result= {"items": [{ 120 | "title": "Error: " + str(err), 121 | "subtitle": "Invalid Query", 122 | "arg": ";;", 123 | "icon": { 124 | 125 | "path": "icons/Warning.png" 126 | } 127 | }]} 128 | print (json.dumps(result)) 129 | raise err 130 | 131 | 132 | 133 | 134 | if (not myQuery): 135 | introDial= {"items": [{ 136 | "title": "Welcome to paperpAlfred 👋", 137 | "subtitle": "Enter a query or ↩️ for help", 138 | "valid": True, 139 | "arg": "ShowHelpWindow", 140 | "icon": { 141 | "path": "icons/paperpAlfred_ico.png" 142 | } 143 | }]} 144 | print (json.dumps(introDial)) 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | # Output results to Alfred 153 | if (myQuery and results): 154 | myResLen = str(len (results)) 155 | countR=1 156 | for (_id, abstract, citekey, fileName, first, folder,folderID, fullReference, journal, label,labelID, last, pdfFlag, pmid, subtitle, title, gdrive_id, type, year) in results: 157 | aut_journ = str(countR) + '/' + myResLen + pdfFlag + subtitle + " 🏷" + label 158 | 159 | 160 | result["items"].append({ 161 | "title": title, 162 | "subtitle": aut_journ, 163 | "variables": { 164 | "myFileName": fileName, 165 | "FullReference": fullReference, 166 | "shortPMID": subtitle+" "+pmid, 167 | "myAbstract": abstract, 168 | "myCitekey": citekey, 169 | "gdrive_id": gdrive_id, 170 | "paperpileID": _id 171 | }, 172 | "valid": True, 173 | 174 | "icon": { 175 | 176 | "path": "icons/paperpAlfred_ico.png" 177 | } 178 | }) 179 | countR += 1 180 | 181 | 182 | print (json.dumps(result)) 183 | 184 | 185 | 186 | 187 | if (myQuery and not results): 188 | result= {"items": [{ 189 | "title": "No matches", 190 | "subtitle": "Try a different query", 191 | 192 | "arg": "", 193 | "icon": { 194 | 195 | "path": "icons/Warning.png" 196 | } 197 | }]} 198 | 199 | print (json.dumps(result)) 200 | 201 | 202 | 203 | 204 | -------------------------------------------------------------------------------- /source/paperpAlfred_functions.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | 4 | # Functions for paperpAlfred: 5 | # log (for logging and debugging) 6 | 7 | # JSONtoDB (to rebuild the database) 8 | # 9 | 10 | 11 | import sys 12 | import os 13 | import sqlite3 14 | import time 15 | import bibtexparser 16 | import re 17 | from config import INDEX_DB 18 | 19 | #Partly cloudy ⛅️ 🌡️+73°F (feels +77°F, 55%) 🌬️↗4mph 🌘 Wed Sep 21 16:59:51 2022 20 | #W38Q3 – 264 ➡️ 100 – 133 ❇️ 232 21 | startTime = time.time() 22 | 23 | 24 | 25 | 26 | 27 | def build_BibTeX_db (BIBTEX_FILE): 28 | with open(BIBTEX_FILE) as bibtex_file: 29 | #bib_database = bibtexparser.load(bibtex_file) 30 | bib_database = bibtexparser.bparser.BibTexParser(common_strings=True).parse_file(bibtex_file) # this is for the string in the month 31 | 32 | #print(bib_database.entries) 33 | 34 | myJSONlist = (bib_database.entries) 35 | 36 | #formatting the author block 37 | for myEntry in myJSONlist: 38 | if "author" in myEntry: 39 | myAuthors = myEntry['author'].split("and ") 40 | 41 | #print (f"number of authors: {len(myAuthors)}") 42 | authorBlock = '' 43 | 44 | authorCount = 0 45 | for eachAuthor in myAuthors: 46 | authorCount += 1 47 | #print (eachAuthor) 48 | if ',' in eachAuthor: 49 | lastName,firstName = eachAuthor.split(",",1) 50 | firstInitials = "".join(item[0].upper() for item in firstName.split()) 51 | #print (f"last name: {lastName}, initials: {firstInitials}") 52 | #print (f"{lastName} {firstInitials}") 53 | eachAuthor_name = f"{lastName} {firstInitials}" 54 | else: 55 | eachAuthor_name = eachAuthor.strip() 56 | 57 | #assigning first and last author 58 | if authorCount == 1: 59 | firstAuthor = eachAuthor_name.split()[0] 60 | myEntry['firstAuthor'] = firstAuthor 61 | linker = '' 62 | else: 63 | linker = ', ' 64 | if authorCount == len (myAuthors): 65 | lastAuthor = eachAuthor_name.split()[0] 66 | myEntry['lastAuthor'] = lastAuthor 67 | 68 | authorBlock = f"{authorBlock}{linker}{eachAuthor_name}" 69 | myEntry['authorBlock'] = authorBlock 70 | 71 | 72 | #stripping Journal of periods 73 | if "journal" in myEntry: 74 | myEntry['journal'] = re.sub(r'\.', '', myEntry['journal']) 75 | else: 76 | myEntry['journal'] = '' 77 | 78 | # strip {} 79 | if myEntry['title'][0] == "{": 80 | myEntry['title'] = myEntry['title'][1:] 81 | if myEntry['title'][-1] == "}": 82 | myEntry['title'] = myEntry['title'][:-1] 83 | 84 | if "pages" in myEntry: 85 | pagesBlock = f":{myEntry['pages']}." 86 | else: 87 | pagesBlock = "" 88 | 89 | if "pmid" in myEntry: 90 | pmidBlock = f" PMID: {myEntry['pmid']}." 91 | else: 92 | pmidBlock = "" 93 | myEntry['pmid'] = '' 94 | 95 | if "volume" not in myEntry: 96 | myEntry['volume'] = "-" 97 | 98 | if "annot" not in myEntry: 99 | myEntry['annot'] = "" 100 | 101 | shortRef = f"{firstAuthor}-{lastAuthor}, {myEntry['journal']} {myEntry['year']} {myEntry['pmid']}" 102 | myEntry['shortRef'] = shortRef 103 | #print (shortRef) 104 | 105 | fullRef = f"{authorBlock}. {myEntry['title']}. {myEntry['journal']} {myEntry['year']};{myEntry['volume']}{pagesBlock}{pmidBlock}" 106 | myEntry['fullRef'] = fullRef 107 | #print (fullRef) 108 | # renaming problematic names 109 | if "ID" in myEntry: 110 | myEntry['myID'] = myEntry['ID'] 111 | del myEntry['ID'] 112 | 113 | if "file" in myEntry: 114 | myEntry['myFile'] = myEntry['file'] 115 | del myEntry['file'] 116 | 117 | 118 | 119 | 120 | #print (authorBlock) 121 | #output = "".join(item[0].upper() for item in input.split()) 122 | 123 | 124 | #Huang AY, Taylor AMW, Ghogha A, Pribadi M, Wang Q, Kim TSJ, Cahill CM, Coppola G, Evans CJ. Genetic and functional analysis of a Pacific hagfish opioid system. J Neurosci Res 2022;1:19-34. PMID: 32830380 125 | 126 | #myJSON = json.dumps(bib_database.entries) 127 | 128 | 129 | JSONtoDB (myJSONlist, "BibTeX_db", INDEX_DB) 130 | 131 | 132 | 133 | def log(s, *args): 134 | if args: 135 | s = s % args 136 | print(s, file=sys.stderr) 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | def JSONtoDB (myJSON,myTable, mydatabase): 148 | column_list = [] 149 | column = [] 150 | for data in myJSON: 151 | 152 | column = list(data.keys()) 153 | for col in column: 154 | if col not in column_list: 155 | column_list.append(col) 156 | 157 | value = [] 158 | values = [] 159 | for data in myJSON: 160 | for i in column_list: 161 | value.append(str(dict(data).get(i))) 162 | values.append(list(value)) 163 | value.clear() 164 | 165 | 166 | 167 | create_statement = "create VIRTUAL table " + myTable + " USING FTS3 ({0})".format(" text,".join(column_list)) 168 | 169 | insert_statement = "insert into " + myTable + " ({0}) values (?{1})".format(",".join(column_list), ",?" * (len(column_list)-1)) 170 | drop_statement = "DROP TABLE IF EXISTS "+ myTable 171 | 172 | # execution 173 | db=sqlite3.connect(mydatabase) 174 | c = db.cursor() 175 | c.execute(drop_statement) 176 | c.execute(create_statement) 177 | c.executemany(insert_statement , values) 178 | values.clear() 179 | db.commit() 180 | 181 | 182 | 183 | 184 | 185 | 186 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # paperpAlfred 2 | 3 | 4 | Search your [Paperpile](https://paperpile.com/) library with [Alfred](https://www.alfredapp.com/) 5 | 6 | ![](images/demo_v1.0.gif "") 7 | 8 | 9 | Downloads
11 |
12 | 13 | 14 | 15 | - [Setting up](#setting-up) 16 | - [Basic Usage](#basic-usage) 17 | - [Output](#output) 18 | - [Known Issues](#known-issues) 19 | - [Acknowledgments](#acknowledgments) 20 | - [Changelog](#changelog) 21 | - [Feedback](#feedback) 22 | 23 | 24 | 25 | 26 |

Setting up paperpAlfred

27 | 28 | 1. Download the workflow from Github and double-click to install paperpAlfred 29 | 30 | 2. Download your Paperpile library 31 | - in Paperpile, cogwheel > Settings > Export > Export to JSON 32 | 33 | 34 | 3. Set the `PAPLIBRARY` path 35 | - Copy the library file path to the clipboard 36 | 37 | - In Finder, right-click the file, press option (⌥), the select `Copy [FileName] as Pathname` 38 | - Open the 'Configure Workflow' window in paperpAlfred preferences 39 | - set path to library as the `Paperpile Library` value 40 | 41 | 4. _Optional:_ Set `Paperpile Path` (Path to Paperpile in Google Drive) 42 | - this will allow to open PDFs in the system viewer 43 | 44 | 5. _Optional:_ change the max number of results returned (default: 99) 45 | - Set the `MAXRESULTS` value in the 'Configure Workflow' window in paperpAlfred 46 | - Set the `Key Prefix` (citekey prefix) in the 'Configure Workflow' window in paperpAlfred 47 | 48 | 6. _Optional:_ Setup hotkeys to launch 49 | - main search 50 | - filter by label 51 | - filter by folder 52 | - filter by type 53 | 54 | 55 |

Basic Usage

56 | 57 | ## Simple search 58 | - launch paperpAlfred by typing `ppp` or using an optional hotkey 59 | - words or names will be searched across all fields 60 | 61 | ### Filter by label first 62 | - entering `ppl` (or optional hotkey) will show a list of labels, the number of items in each label, and the count of item types 63 | - select the label by pressing `return`. PaperpAlfred will now search within that label 64 | - `option-return (⌥⏎)` will open the label in Paperpile 65 | ### Filter by folder first 66 | - entering `ppf` (or optional hotkey) will show a list of folders, the number of items in each folder, and the count of item types 67 | - select the folder by pressing `return`. PaperpAlfred will now search within that folder. 68 | - `option-return (⌥⏎)` will open the folder in Paperpile 69 | - Note: Added feature ✅ the Paperpile web interface does not allow to search by folder 70 | ### Filter by type first 71 | - entering `ppty` will show a list of publication types and the number of items in each 72 | - select the folder by pressing `return`. paperpAlfred will now search within that item type 73 | - `shift-return (⇧⏎)` will open the folder in Paperpile 74 | 75 | ## Advanced search 76 | - enter `field:`, where `field` is any of the fields below. Example: `year:2022` 77 | - `title` 78 | - `abstract` 79 | - `citekey` 80 | - `first` first author 81 | - `last` last author 82 | - `journal` 83 | - `folder` 84 | - `label` 85 | - `pmid` 86 | - `year` 87 | - `type` publication type 88 | 89 |

Output

90 | 91 | - Alfred returns the top 99 results, numbered. The max number of results returned can be set in Preferences (see [Setting up](#setting-up)) 92 | - results are sorted by year (most recent first). Adding `--a` to the query will invert this order. 93 | - The main text will return the title. 94 | - the subtext will return the following: 95 | - record count/total result count 96 | - 📜if a PDF is available 97 | - short ID string (first-last author, journal, year) 98 | - associated labels 99 | 100 | ## Acting on results 101 | Once the right item is found, the user has seven options to act on it: 102 | 1. `return (⏎)` will open the PDF in the system viewer (Preview for most users), provided that the `PAPPATH` has been set (see [Setting up](#setting-up)). Rarely this might not work (see [Known Issues](#known-issues)) 103 | 2. `shift-return (⇧⏎)` will show the **abstract** and copy it to clipboard 104 | 3. `control-return (⌃⏎)` will show the **complete reference** and copy it to the clipboard 105 | 4. `option-return (⌥⏎)` will copy a **short reference** (First-last author, journal, year, PMID) to the clipboard 106 | 5. `command-return (⌘⏎)` will copy the **citation key** to the clipboard 107 | 6. `command-shift-return (⌘⇧⏎)` will open the PDF in Google drive 108 | 7. `command-option-return (⌘⌥⏎)` will open the record in Paperpile 109 | 110 |

Known Issues

111 | 112 | - incomplete records will not be imported 113 | - special characters (e.g. ü) will need to be entered in order to match the record 114 | - File opening is currently using a name search. The Paperpile file naming logic is not entirely clear to me, the folder structure is deprecated and there might be a small number of cases where the PDF might not be retrievable via paperpAlfred. Google drive view should still work in these cases. 115 | - Currently tested mainly with research papers, reviews etc. There might be untested use cases for other types of publications. 116 | - label and folder search in the main window (i.e. using `label:` and `folder:`) will not be exact matches (e.g. AD will also return GWAS_AD). Match will be exact when starting from folder and label window. 117 | 118 |

Acknowledgments

119 | 120 | - [Dean Jackson](https://github.com/deanishe) for their incredible help on the Alfred mailing list and for creating [alfred-index-demo](https://github.com/deanishe/alfred-index-demo), and other scripts used as templates for this workflow. 121 | - Jirka from Paperpile for support on the path-to-file issue 122 | - Alain T, StackExchange user:5237560 (https://nebularena.wordpress.com) for help with a Python script 123 | 124 |

Changelog

125 | 126 | - 12-04-2022: version 2.1 (Alfred 5) 127 | - 03-15-2022: version 2.0 (Python3, removed dependencies) 128 | - 03-17-2021: version 1.0 129 | 130 | 131 |

Feedback

132 | Feedback welcome! If you notice a bug, or have ideas for new features, please feel free to get in touch either here, or on the [Paperpile](https://forum.paperpile.com)/[Alfred](https://www.alfredforum.com) forums. 133 | 134 | -------------------------------------------------------------------------------- /source/build_db.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | # -*- coding: utf-8 -*- 3 | # 4 | # Rebuilding the database 5 | # rewritten script 6 | # 7 | # previously created on Sunday, February 28, 2021 8 | # March 2022 updated to Python3, eliminated dependencies 9 | 10 | #Note it currently fails if none of the references are in folders. will need to fix 11 | 12 | import json 13 | import sqlite3 14 | import re 15 | import sys 16 | import collections 17 | 18 | def log(s, *args): 19 | if args: 20 | s = s % args 21 | print(s, file=sys.stderr) 22 | 23 | 24 | 25 | 26 | def importingCompleteLibrary (myFile,mydatabase): # to import complete library before filtering, not used here 27 | ## reading JSON data in 28 | with open(myFile, "r") as read_file: 29 | json_data = json.load(read_file) 30 | 31 | 32 | ## getting the list of columns (not all the records have the same columns, nor we can assume that for future dicts) 33 | # thanks to https://www.codeproject.com/Tips/4067936/Load-JSON-File-with-Array-of-Objects-to-SQLite3-On 34 | column_list = [] 35 | column = [] 36 | for data in json_data: 37 | 38 | column = list(data.keys()) 39 | for col in column: 40 | if col not in column_list: 41 | column_list.append(col) 42 | 43 | 44 | 45 | value = [] 46 | values = [] 47 | for data in json_data: 48 | for i in column_list: 49 | value.append(str(dict(data).get(i))) 50 | values.append(list(value)) 51 | value.clear() 52 | 53 | create_statement = "create table if not exists myLibrary ({0})".format(" text,".join(column_list)) 54 | insert_statement = "insert into myLibrary ({0}) values (?{1})".format(",".join(column_list), ",?" * (len(column_list)-1)) 55 | drop_statement = "DROP TABLE IF EXISTS myLibrary" 56 | # execution 57 | db=sqlite3.connect(mydatabase) 58 | c = db.cursor() 59 | c.execute(drop_statement) 60 | c.execute(create_statement) 61 | c.executemany(insert_statement , values) 62 | values.clear() 63 | db.commit() 64 | 65 | 66 | def JSONtoDB (myJSON,myTable, mydatabase): 67 | column_list = [] 68 | column = [] 69 | for data in myJSON: 70 | 71 | column = list(data.keys()) 72 | for col in column: 73 | if col not in column_list: 74 | column_list.append(col) 75 | 76 | value = [] 77 | values = [] 78 | for data in myJSON: 79 | for i in column_list: 80 | value.append(str(dict(data).get(i))) 81 | values.append(list(value)) 82 | value.clear() 83 | 84 | 85 | 86 | create_statement = "create VIRTUAL table " + myTable + " USING FTS3 ({0})".format(" text,".join(column_list)) 87 | 88 | insert_statement = "insert into " + myTable + " ({0}) values (?{1})".format(",".join(column_list), ",?" * (len(column_list)-1)) 89 | drop_statement = "DROP TABLE IF EXISTS "+ myTable 90 | 91 | # execution 92 | db=sqlite3.connect(mydatabase) 93 | c = db.cursor() 94 | c.execute(drop_statement) 95 | c.execute(create_statement) 96 | c.executemany(insert_statement , values) 97 | values.clear() 98 | db.commit() 99 | 100 | 101 | 102 | def createLibrary (myLibrary): 103 | 104 | from config import INDEX_DB 105 | ## reading JSON data in 106 | with open(myLibrary, "r") as read_file: 107 | mydata = json.load(read_file) 108 | 109 | 110 | ### creating a second JSON with a subset of the fields, plus some formatted fields 111 | 112 | #list of elements to get from original JSON 113 | mySubsetFields = {'title','published','abstract','issue','author','pages','citekey','journal','volume','pmid','labelsNamed','foldersNamed','labels','folders','attachments','subfolders','gdrive_id','_id','pubtype','kind'} 114 | 115 | # creating the subset (mySubset object) 116 | mySubset = [] 117 | for item in mydata: 118 | my_dict={} 119 | if item['incomplete']==1: ## skipping incomplete items 120 | continue 121 | 122 | for myfield in mySubsetFields: 123 | if myfield in item: 124 | my_dict[myfield]=item.get(myfield) 125 | mySubset.append(my_dict) 126 | 127 | #combining the authors in authorBlock 128 | for item in mySubset: 129 | #log (item['_id']) 130 | #declaration block, and defaults 131 | authorBlock ='' 132 | labelBlock ='' 133 | labelIDBlock ='' 134 | folderIDBlock ='' 135 | first ='' 136 | last ='' 137 | folderBlock ='' 138 | myFileName ='' 139 | item.setdefault('pmid', '-') 140 | item.setdefault('citekey', '-') 141 | item.setdefault('journal', '-') 142 | item.setdefault('published', '-') 143 | item.setdefault('abstract', '') 144 | item.setdefault('label', '') 145 | item.setdefault('author', '') 146 | item.setdefault('issue', '') 147 | item.setdefault('pages', '') 148 | item.setdefault('fileName', '') 149 | item.setdefault('gdrive_id', '') 150 | item.setdefault('_id', '') 151 | item.setdefault('pdfFlag', ' ') 152 | item.setdefault('type', '') 153 | 154 | 155 | 156 | # stripping dots from journal names 157 | item['journal'] = re.sub(r'\.', '', item['journal']) 158 | 159 | for myAuthor in item['author']: 160 | if myAuthor['formatted'] == item['author'][-1]['formatted']: 161 | authorBlock += myAuthor['formatted'] 162 | else: 163 | authorBlock += myAuthor['formatted']+', ' 164 | firstAuthorLN='' 165 | lastAuthorLN='' 166 | 167 | if (len(item['author'])) == 0: 168 | if ('last' in item['author']): 169 | firstAuthorLN= item['author']['last'] 170 | if (len(item['author'])) > 0: 171 | if ('last' in item['author'][0]): 172 | firstAuthorLN= item['author'][0]['last'] 173 | 174 | if ('last' in item['author'][-1]): 175 | lastAuthorLN = item['author'][-1]['last'] 176 | if ('year' in item['published'] and item['published']['year'] is not None): 177 | pubYear = item['published']['year'] 178 | else: 179 | pubYear = "-" 180 | 181 | item.update({'first':firstAuthorLN}) 182 | item.update({'last':lastAuthorLN}) 183 | 184 | item.update({'year':pubYear}) 185 | myJournal = item['journal'] 186 | 187 | # assigning publication type 188 | myType=item['pubtype'] 189 | myKinds = ['Commentary','Review','News'] 190 | if 'kind' in item: 191 | if item['kind'] in myKinds: 192 | myType=item['kind'] 193 | item.update({'type':myType}) 194 | 195 | 196 | 197 | # flattening labels 198 | if 'labelsNamed' in item: 199 | for myLabel in item['labelsNamed']: 200 | if myLabel == item['labelsNamed'][-1]: 201 | labelBlock += myLabel 202 | else: 203 | labelBlock += myLabel+',' 204 | item.update({'label':labelBlock}) 205 | 206 | # flattening labelIDs 207 | for myLabel in item['labels']: 208 | if myLabel == item['labels'][-1]: 209 | labelIDBlock += myLabel 210 | else: 211 | labelIDBlock += myLabel+',' 212 | item.update({'labelID':labelIDBlock}) 213 | 214 | 215 | # flattening folders 216 | if 'foldersNamed' in item: 217 | for myFolder in item['foldersNamed']: 218 | if myFolder == item['foldersNamed'][-1]: 219 | folderBlock += myFolder 220 | else: 221 | folderBlock += myFolder +',' 222 | item.update({'folder':folderBlock}) 223 | 224 | # flattening folderIDs 225 | for myFolder in item['folders']: 226 | if myFolder == item['folders'][-1]: 227 | folderIDBlock += myFolder 228 | else: 229 | folderIDBlock += myFolder +',' 230 | item.update({'folderID':folderIDBlock}) 231 | 232 | # PDF name or search string 233 | if (len(item['attachments'])) >0: 234 | 235 | # checking source_filename 236 | if item['attachments'][0]['source_filename'] == "[article_pdf].pdf": 237 | myFilename = item['attachments'][0]['filename'] 238 | else: 239 | myTitle=item['title'] 240 | myFilename = firstAuthorLN + '*' + myTitle[0:30]+ '*' 241 | # search string including the first author and the first 30 characters of the title. should work in most cases. 242 | 243 | # eliminating extra space after ellipsis. another option is to always take the first 30 chars of title 244 | myFilename = myFilename.replace("... ", "... ") 245 | 246 | # escaping exclamation point in title?? 247 | #myFilename = myFilename.replace("!", "\!") 248 | 249 | item.update({'fileName':myFilename}) 250 | item.update({'pdfFlag':'📜'}) 251 | 252 | # storing google drive ID 253 | if ('gdrive_id' in item['attachments'][0]): 254 | myGDrive_ID = item['attachments'][0]['gdrive_id'] 255 | item.update({'gdrive_id':myGDrive_ID}) 256 | 257 | 258 | 259 | 260 | 261 | # compiling the subtitle 262 | #log (f"{firstAuthorLN}-{lastAuthorLN}, {myJournal} {pubYear}") 263 | subtitle = f"{firstAuthorLN}-{lastAuthorLN}, {myJournal} {pubYear}" 264 | 265 | item.update({'subtitle':subtitle}) 266 | 267 | 268 | 269 | 270 | authorBlock = authorBlock+'.' 271 | ## generating full reference style used is with PMID, can be changed, or maybe I can have a couple of styles, not sure it is worth creating a universal solution 272 | fullRef = authorBlock + ' ' + item['title']+'. '+item['journal'] + ' ' + pubYear+';'+item['issue']+':'+item['pages']+'. PMID: '+item['pmid'] 273 | item.update({'fullReference':fullRef}) 274 | 275 | 276 | 277 | 278 | # generating label lists and file type counts (counting pub types for each label) 279 | # thanks to Alain T, StackExchange user:5237560 (https://nebularena.wordpress.com) 280 | 281 | counters = {dd['type']:0 for dd in mySubset if 'type' in dd} # if types not fixed 282 | counters['Total'] = 0 283 | counters['labelID'] = '' 284 | myOutput = dict() 285 | 286 | for item in mySubset: # go through dictionary list 287 | myLabels = item['label'].split(",") 288 | myLabelIDs = item['labelID'].split(",") 289 | 290 | itemType = item.get('type',None) # get the type 291 | 292 | lcc=-1 #label count 293 | for labell in myLabels: # go through labels list 294 | lcc += 1 295 | if labell: 296 | if labell in myOutput: 297 | labelCounts = myOutput[labell] #if a label exists, get the current type counts 298 | else: 299 | myOutput[labell] = labelCounts = dict (counters) 300 | labelCounts['labelID'] = myLabelIDs[lcc] #fetches the label ID 301 | if itemType : labelCounts[itemType] += 1 # count items for type if any 302 | labelCounts['Total'] += 1 303 | 304 | myFinalCount = [] 305 | for key, val in myOutput.items(): 306 | mySubText = '' 307 | val = {k.lower(): v for k, v in val.items()} #converting to lowercase 308 | val = {k.replace('pp_', ''): v for k, v in val.items()} # eliminating pp 309 | 310 | 311 | # this section below is to convert previous code and could be made better 312 | valSub = {k: val[k] for k in set(list(val.keys())) - set(['labelid','total'])} #subsetting the type totals, so that they can be sorted 313 | valSub = sorted(valSub.items(), key=lambda x: x[1], reverse=True) # sorting toitals for each type 314 | valSub = collections.OrderedDict(valSub) #converting back into a dictionary 315 | 316 | 317 | for key2, val2 in valSub.items(): 318 | if val2 != 0 and key2 != 'total' and key2 != 'labelid': #formatting all fields except total and labelID 319 | myText = '{} ({}) '.format(key2, val2) 320 | mySubText = mySubText + myText 321 | myFinalCount.append ({ 322 | 'label': key, 323 | 'totalLabel': val['total'], 324 | 'LabelID': val['labelid'], 325 | 'summaryLabel': mySubText 326 | }) 327 | 328 | # creating the table in the sqlite database 329 | 330 | JSONtoDB (myJSON=myFinalCount,myTable='Labels', mydatabase=INDEX_DB) 331 | 332 | 333 | # generating folder list and counts 334 | 335 | counters = {dd['type']:0 for dd in mySubset if 'type' in dd} # if types not fixed 336 | counters['Total'] = 0 337 | counters['folderID'] = '' 338 | myOutput = dict() 339 | 340 | for item in mySubset: # go through dictionary list 341 | myLabels = item['folder'].split(",") 342 | myLabelIDs = item['folderID'].split(",") 343 | itemType = item.get('type',None) # get the type 344 | 345 | lcc=-1 #label count 346 | for labell in myLabels: # go through labels list 347 | lcc += 1 348 | if labell: 349 | if labell in myOutput: 350 | labelCounts = myOutput[labell] #if a folder exists, get the current type counts 351 | else: 352 | myOutput[labell] = labelCounts = dict (counters) 353 | labelCounts['folderID'] = myLabelIDs[lcc] #fetches the folder ID 354 | if itemType : labelCounts[itemType] += 1 # count items for type if any 355 | labelCounts['Total'] += 1 356 | 357 | #print (myOutput) 358 | myFinalCount = [] 359 | for key, val in myOutput.items(): 360 | mySubText = '' 361 | val = {k.lower(): v for k, v in val.items()} 362 | val = {k.replace('pp_', ''): v for k, v in val.items()} 363 | 364 | # this section below is to convert previous code and could be made better 365 | valSub = {k: val[k] for k in set(list(val.keys())) - set(['folderid','total'])} #subsetting the type totals, so that they can be sorted 366 | valSub = sorted(valSub.items(), key=lambda x: x[1], reverse=True) # sorting toitals for each type 367 | valSub = collections.OrderedDict(valSub) #converting back into a dictionary 368 | 369 | for key2, val2 in valSub.items(): 370 | 371 | if val2 != 0 and key2 != 'total' and key2 != 'folderid': 372 | myText = '{} ({}) '.format(key2, val2) 373 | mySubText = mySubText + myText 374 | myFinalCount.append ({ 375 | 'folder': key, 376 | 'totalFolder': val['total'], 377 | 'FolderID': val['folderid'], 378 | 'summaryFolder': mySubText 379 | }) 380 | 381 | # creating the table in the sqlite database 382 | JSONtoDB (myJSON=myFinalCount,myTable='Folders', mydatabase=INDEX_DB) 383 | 384 | 385 | 386 | ### creating a list of unique types 387 | myTypeList=[] 388 | for item in mySubset: 389 | myTypes = item['type'].split(",") 390 | for myCurrType in myTypes: 391 | if myCurrType: 392 | myTypeList.append(myCurrType) 393 | 394 | 395 | myTypeList = [x.replace('PP_', '') for x in myTypeList] 396 | myTypeList = [x.replace('_', ' ') for x in myTypeList] 397 | myTypeList = [each_string.capitalize() for each_string in myTypeList] 398 | 399 | myTypeSummary = collections.Counter(myTypeList) 400 | 401 | myFinalCount = [] 402 | for key, val in myTypeSummary.items(): 403 | myFinalCount.append ({ 404 | 'type': key, 405 | 'totalType': val 406 | 407 | }) 408 | 409 | 410 | JSONtoDB (myJSON=myFinalCount,myTable='Types', mydatabase=INDEX_DB) 411 | 412 | 413 | 414 | 415 | 416 | # preparing the final output: excluding items no longer needed 417 | myFinalSubset = [] 418 | toExclude = ['author','published','issue','pages','volume','labelsNamed','foldersNamed','labels','folders','attachments','subfolders','pubtype','kind'] 419 | 420 | for item in mySubset: 421 | 422 | myItem={} 423 | myItem = {k: item[k] for k in item.keys() - toExclude} 424 | myFinalSubset.append(myItem) 425 | 426 | JSONtoDB (myJSON=myFinalSubset,myTable='papers', mydatabase=INDEX_DB) 427 | 428 | 429 | 430 | 431 | 432 | # if os.path.exists(INDEX_DB): 433 | # os.remove(INDEX_DB) 434 | # # deleting the existing index db, so the papers.py script is forced to rebuild 435 | 436 | 437 | 438 | 439 | #if __name__ == "__main__": 440 | #importingLibrary(LIBRARY_FILE, MY_DATABASE) 441 | #createLibrary (LIBRARY_FILE) 442 | -------------------------------------------------------------------------------- /source/info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | bundleid 6 | giovanni.paperpAlfred 7 | category 8 | Productivity 9 | connections 10 | 11 | 1AAAEDC3-C0EE-47D4-83C7-2A5AB352295E 12 | 13 | 14 | destinationuid 15 | D94E41FF-2E5C-420B-919A-5A2990C3F4A6 16 | modifiers 17 | 0 18 | modifiersubtext 19 | 20 | vitoclose 21 | 22 | 23 | 24 | destinationuid 25 | 1D9857A2-7E4B-4E14-9556-B38C14893DD9 26 | modifiers 27 | 0 28 | modifiersubtext 29 | 30 | vitoclose 31 | 32 | 33 | 34 | 21B88AAB-784B-4782-B4D6-607BDB0F7C25 35 | 36 | 37 | destinationuid 38 | 3B12BCC5-45EE-4E9D-95FB-0B156B0704B3 39 | modifiers 40 | 0 41 | modifiersubtext 42 | 43 | vitoclose 44 | 45 | 46 | 47 | destinationuid 48 | 8D033AF8-08E7-4097-B0BF-5BB509FAF1AB 49 | modifiers 50 | 0 51 | modifiersubtext 52 | 53 | vitoclose 54 | 55 | 56 | 57 | 28606112-A338-475C-9D0A-17F14E5D6E40 58 | 59 | 60 | destinationuid 61 | E2E84778-9472-48A7-851A-87F07879DA5C 62 | modifiers 63 | 0 64 | modifiersubtext 65 | 66 | vitoclose 67 | 68 | 69 | 70 | 33BF8A0E-B7AA-495E-B89F-1F8239DAECBE 71 | 72 | 73 | destinationuid 74 | 3FDF7937-4C98-4A75-B7C6-AF4AF5FBB5D7 75 | modifiers 76 | 262144 77 | modifiersubtext 78 | Open Folder in Paperpile 79 | vitoclose 80 | 81 | 82 | 83 | destinationuid 84 | E2E84778-9472-48A7-851A-87F07879DA5C 85 | modifiers 86 | 0 87 | modifiersubtext 88 | 89 | vitoclose 90 | 91 | 92 | 93 | 4797204D-1BEC-49C9-8577-07F086902572 94 | 95 | 96 | destinationuid 97 | 6DF20719-C29E-41E0-AEA3-E92AD68EA188 98 | modifiers 99 | 0 100 | modifiersubtext 101 | 102 | vitoclose 103 | 104 | 105 | 106 | 6DF20719-C29E-41E0-AEA3-E92AD68EA188 107 | 108 | 109 | destinationuid 110 | 439299E4-26DE-4954-9EE4-6AB9A055C4DC 111 | modifiers 112 | 262144 113 | modifiersubtext 114 | Open Label in Paperpile 115 | vitoclose 116 | 117 | 118 | 119 | destinationuid 120 | E2E84778-9472-48A7-851A-87F07879DA5C 121 | modifiers 122 | 0 123 | modifiersubtext 124 | 125 | vitoclose 126 | 127 | 128 | 129 | 837B6FCB-ACFA-46BE-AEC2-10135A81A784 130 | 131 | 132 | destinationuid 133 | E2E84778-9472-48A7-851A-87F07879DA5C 134 | modifiers 135 | 0 136 | modifiersubtext 137 | 138 | vitoclose 139 | 140 | 141 | 142 | DD1DC957-AD49-4CC1-A425-2D6FF0F6F2D5 143 | 144 | 145 | destinationuid 146 | C15FD31B-73B9-4D65-9FC9-B88846234B1C 147 | modifiers 148 | 0 149 | modifiersubtext 150 | 151 | sourceoutputuid 152 | B8F4A19F-1244-4D35-BFAF-7B30C48F9337 153 | vitoclose 154 | 155 | 156 | 157 | destinationuid 158 | C81A90A7-A0A8-4A02-8BC5-9315BC9BB108 159 | modifiers 160 | 0 161 | modifiersubtext 162 | 163 | vitoclose 164 | 165 | 166 | 167 | E2E84778-9472-48A7-851A-87F07879DA5C 168 | 169 | 170 | destinationuid 171 | 21B88AAB-784B-4782-B4D6-607BDB0F7C25 172 | modifiers 173 | 262144 174 | modifiersubtext 175 | Show and copy to clipboard complete reference 176 | vitoclose 177 | 178 | 179 | 180 | destinationuid 181 | 0F60EE65-A185-4BBF-B450-70AF97DF20D0 182 | modifiers 183 | 524288 184 | modifiersubtext 185 | Copy First-Last, Journal, Year, PMID to clipboard 186 | vitoclose 187 | 188 | 189 | 190 | destinationuid 191 | 09F993A7-F21C-4541-8C43-6641A0B6E1D8 192 | modifiers 193 | 1048576 194 | modifiersubtext 195 | Copy citation key to clipboard 196 | vitoclose 197 | 198 | 199 | 200 | destinationuid 201 | 1AAAEDC3-C0EE-47D4-83C7-2A5AB352295E 202 | modifiers 203 | 131072 204 | modifiersubtext 205 | Show (and copy to clipboard) abstract 206 | vitoclose 207 | 208 | 209 | 210 | destinationuid 211 | DD1DC957-AD49-4CC1-A425-2D6FF0F6F2D5 212 | modifiers 213 | 0 214 | modifiersubtext 215 | 216 | vitoclose 217 | 218 | 219 | 220 | destinationuid 221 | 30EEB713-8B4D-41AF-A310-C8793A498639 222 | modifiers 223 | 1179648 224 | modifiersubtext 225 | Open PDF in Google Drive 226 | vitoclose 227 | 228 | 229 | 230 | destinationuid 231 | D942F3A6-D97B-4FA5-80ED-18B88282CF61 232 | modifiers 233 | 1572864 234 | modifiersubtext 235 | Open record in Paperpile 236 | vitoclose 237 | 238 | 239 | 240 | E31F5435-FCA6-40FB-AC97-768A01A29B7B 241 | 242 | 243 | destinationuid 244 | 33BF8A0E-B7AA-495E-B89F-1F8239DAECBE 245 | modifiers 246 | 0 247 | modifiersubtext 248 | 249 | vitoclose 250 | 251 | 252 | 253 | 254 | createdby 255 | @giovannicoppoIa 256 | description 257 | Search your Paperpile library with Alfred 258 | disabled 259 | 260 | name 261 | paperpAlfred 262 | objects 263 | 264 | 265 | config 266 | 267 | autopaste 268 | 269 | clipboardtext 270 | {var:FullReference} 271 | ignoredynamicplaceholders 272 | 273 | transient 274 | 275 | 276 | type 277 | alfred.workflow.output.clipboard 278 | uid 279 | 3B12BCC5-45EE-4E9D-95FB-0B156B0704B3 280 | version 281 | 3 282 | 283 | 284 | config 285 | 286 | action 287 | 0 288 | argument 289 | 0 290 | focusedappvariable 291 | 292 | focusedappvariablename 293 | 294 | hotkey 295 | 37 296 | hotmod 297 | 1310720 298 | hotstring 299 | L 300 | leftcursor 301 | 302 | modsmode 303 | 0 304 | relatedAppsMode 305 | 0 306 | 307 | type 308 | alfred.workflow.trigger.hotkey 309 | uid 310 | 4797204D-1BEC-49C9-8577-07F086902572 311 | version 312 | 2 313 | 314 | 315 | config 316 | 317 | alfredfiltersresults 318 | 319 | alfredfiltersresultsmatchmode 320 | 2 321 | argumenttreatemptyqueryasnil 322 | 323 | argumenttrimmode 324 | 0 325 | argumenttype 326 | 1 327 | escaping 328 | 102 329 | keyword 330 | ppl 331 | queuedelaycustom 332 | 3 333 | queuedelayimmediatelyinitially 334 | 335 | queuedelaymode 336 | 0 337 | queuemode 338 | 1 339 | runningsubtext 340 | 341 | script 342 | export PATH=/opt/homebrew/bin:/usr/local/bin:$PATH 343 | /usr/bin/python3 labels.py "$1" 344 | 345 | 346 | 347 | scriptargtype 348 | 1 349 | scriptfile 350 | 351 | subtext 352 | 353 | title 354 | 355 | type 356 | 5 357 | withspace 358 | 359 | 360 | type 361 | alfred.workflow.input.scriptfilter 362 | uid 363 | 6DF20719-C29E-41E0-AEA3-E92AD68EA188 364 | version 365 | 3 366 | 367 | 368 | config 369 | 370 | concurrently 371 | 372 | escaping 373 | 102 374 | script 375 | open "https://paperpile.com/app/label/$myLabelID" 376 | scriptargtype 377 | 0 378 | scriptfile 379 | 380 | type 381 | 0 382 | 383 | type 384 | alfred.workflow.action.script 385 | uid 386 | 439299E4-26DE-4954-9EE4-6AB9A055C4DC 387 | version 388 | 2 389 | 390 | 391 | type 392 | alfred.workflow.utility.junction 393 | uid 394 | 21B88AAB-784B-4782-B4D6-607BDB0F7C25 395 | version 396 | 1 397 | 398 | 399 | config 400 | 401 | alignment 402 | 0 403 | backgroundcolor 404 | 405 | fadespeed 406 | 0 407 | fillmode 408 | 0 409 | font 410 | 411 | ignoredynamicplaceholders 412 | 413 | largetypetext 414 | {var:FullReference} 415 | textcolor 416 | 417 | wrapat 418 | 50 419 | 420 | type 421 | alfred.workflow.output.largetype 422 | uid 423 | 8D033AF8-08E7-4097-B0BF-5BB509FAF1AB 424 | version 425 | 3 426 | 427 | 428 | config 429 | 430 | action 431 | 0 432 | argument 433 | 0 434 | focusedappvariable 435 | 436 | focusedappvariablename 437 | 438 | hotkey 439 | 3 440 | hotmod 441 | 1310720 442 | hotstring 443 | F 444 | leftcursor 445 | 446 | modsmode 447 | 0 448 | relatedAppsMode 449 | 0 450 | 451 | type 452 | alfred.workflow.trigger.hotkey 453 | uid 454 | E31F5435-FCA6-40FB-AC97-768A01A29B7B 455 | version 456 | 2 457 | 458 | 459 | config 460 | 461 | alfredfiltersresults 462 | 463 | alfredfiltersresultsmatchmode 464 | 2 465 | argumenttreatemptyqueryasnil 466 | 467 | argumenttrimmode 468 | 0 469 | argumenttype 470 | 1 471 | escaping 472 | 102 473 | keyword 474 | ppf 475 | queuedelaycustom 476 | 3 477 | queuedelayimmediatelyinitially 478 | 479 | queuedelaymode 480 | 0 481 | queuemode 482 | 1 483 | runningsubtext 484 | 485 | script 486 | export PATH=/opt/homebrew/bin:/usr/local/bin:$PATH 487 | /usr/bin/python3 folders.py "$1" 488 | 489 | 490 | scriptargtype 491 | 1 492 | scriptfile 493 | 494 | subtext 495 | 496 | title 497 | 498 | type 499 | 5 500 | withspace 501 | 502 | 503 | type 504 | alfred.workflow.input.scriptfilter 505 | uid 506 | 33BF8A0E-B7AA-495E-B89F-1F8239DAECBE 507 | version 508 | 3 509 | 510 | 511 | config 512 | 513 | concurrently 514 | 515 | escaping 516 | 102 517 | script 518 | open "https://paperpile.com/app/folder/$myFolderID" 519 | scriptargtype 520 | 0 521 | scriptfile 522 | 523 | type 524 | 0 525 | 526 | type 527 | alfred.workflow.action.script 528 | uid 529 | 3FDF7937-4C98-4A75-B7C6-AF4AF5FBB5D7 530 | version 531 | 2 532 | 533 | 534 | config 535 | 536 | autopaste 537 | 538 | clipboardtext 539 | {var:myAbstract} 540 | ignoredynamicplaceholders 541 | 542 | transient 543 | 544 | 545 | type 546 | alfred.workflow.output.clipboard 547 | uid 548 | D94E41FF-2E5C-420B-919A-5A2990C3F4A6 549 | version 550 | 3 551 | 552 | 553 | config 554 | 555 | autopaste 556 | 557 | clipboardtext 558 | {var:shortPMID} 559 | ignoredynamicplaceholders 560 | 561 | transient 562 | 563 | 564 | type 565 | alfred.workflow.output.clipboard 566 | uid 567 | 0F60EE65-A185-4BBF-B450-70AF97DF20D0 568 | version 569 | 3 570 | 571 | 572 | config 573 | 574 | autopaste 575 | 576 | clipboardtext 577 | {var:KEY_PREFIX}{var:myCitekey} 578 | ignoredynamicplaceholders 579 | 580 | transient 581 | 582 | 583 | type 584 | alfred.workflow.output.clipboard 585 | uid 586 | 09F993A7-F21C-4541-8C43-6641A0B6E1D8 587 | version 588 | 3 589 | 590 | 591 | config 592 | 593 | alignment 594 | 0 595 | backgroundcolor 596 | 597 | fadespeed 598 | 0 599 | fillmode 600 | 0 601 | font 602 | 603 | ignoredynamicplaceholders 604 | 605 | largetypetext 606 | {var:myAbstract} 607 | textcolor 608 | 609 | wrapat 610 | 50 611 | 612 | type 613 | alfred.workflow.output.largetype 614 | uid 615 | 1D9857A2-7E4B-4E14-9556-B38C14893DD9 616 | version 617 | 3 618 | 619 | 620 | type 621 | alfred.workflow.utility.junction 622 | uid 623 | 1AAAEDC3-C0EE-47D4-83C7-2A5AB352295E 624 | version 625 | 1 626 | 627 | 628 | config 629 | 630 | alfredfiltersresults 631 | 632 | alfredfiltersresultsmatchmode 633 | 2 634 | argumenttreatemptyqueryasnil 635 | 636 | argumenttrimmode 637 | 0 638 | argumenttype 639 | 1 640 | escaping 641 | 102 642 | keyword 643 | ppty 644 | queuedelaycustom 645 | 3 646 | queuedelayimmediatelyinitially 647 | 648 | queuedelaymode 649 | 0 650 | queuemode 651 | 1 652 | runningsubtext 653 | 654 | script 655 | export PATH=/opt/homebrew/bin:/usr/local/bin:$PATH 656 | /usr/bin/python3 myTypes.py "$1" 657 | scriptargtype 658 | 1 659 | scriptfile 660 | 661 | subtext 662 | 663 | title 664 | 665 | type 666 | 5 667 | withspace 668 | 669 | 670 | type 671 | alfred.workflow.input.scriptfilter 672 | uid 673 | 837B6FCB-ACFA-46BE-AEC2-10135A81A784 674 | version 675 | 3 676 | 677 | 678 | config 679 | 680 | alignment 681 | 0 682 | backgroundcolor 683 | #000000FF 684 | fadespeed 685 | 0 686 | fillmode 687 | 0 688 | font 689 | Menlo Regular 690 | ignoredynamicplaceholders 691 | 692 | largetypetext 693 | - Simple search: `ppp` -- query across all fields 694 | 695 | - Filter by label first: `ppl` (or optional hotkey) 696 | - ⌃⏎ open label in Paperpile 697 | 698 | - Filter by folder first: `ppf` (or optional hotkey) 699 | - ⌃⏎ open folder in Paperpile 700 | 701 | - Filter by item type first: `ppty` 702 | 703 | - Advanced search 704 | - title: 705 | - abstract: 706 | - citekey: 707 | - journal: 708 | - folder: 709 | - label: 710 | - pmid: 711 | - year: 712 | - first: [first author] 713 | - last: [last author] 714 | - type: [publication type] 715 | 716 | - Output 717 | 1. ⏎ open PDF with system viewer 718 | 2. ⇧⏎ abstract (show and copy to clipboard) 719 | 3. ⌃⏎ complete reference (show and copy to clipboard) 720 | 4. ⌥⏎ [First-last author, journal, year, PMID] to clipboard 721 | 5. ⌘⏎ citation key to clipboard 722 | 6. ⌘⇧⏎ open PDF in Google drive 723 | 7. ⌘⌥⏎ open record in Paperpile 724 | textcolor 725 | #00F900FF 726 | wrapat 727 | 50 728 | 729 | type 730 | alfred.workflow.output.largetype 731 | uid 732 | C15FD31B-73B9-4D65-9FC9-B88846234B1C 733 | version 734 | 3 735 | 736 | 737 | config 738 | 739 | action 740 | 0 741 | argument 742 | 0 743 | focusedappvariable 744 | 745 | focusedappvariablename 746 | 747 | hotkey 748 | 35 749 | hotmod 750 | 1310720 751 | hotstring 752 | P 753 | leftcursor 754 | 755 | modsmode 756 | 0 757 | relatedAppsMode 758 | 0 759 | 760 | type 761 | alfred.workflow.trigger.hotkey 762 | uid 763 | 28606112-A338-475C-9D0A-17F14E5D6E40 764 | version 765 | 2 766 | 767 | 768 | config 769 | 770 | alfredfiltersresults 771 | 772 | alfredfiltersresultsmatchmode 773 | 0 774 | argumenttreatemptyqueryasnil 775 | 776 | argumenttrimmode 777 | 0 778 | argumenttype 779 | 1 780 | escaping 781 | 102 782 | keyword 783 | ppp 784 | queuedelaycustom 785 | 3 786 | queuedelayimmediatelyinitially 787 | 788 | queuedelaymode 789 | 0 790 | queuemode 791 | 1 792 | runningsubtext 793 | ❗Please wait while I rebuild the database❗ 794 | script 795 | export PATH=/opt/homebrew/bin:/usr/local/bin:$PATH 796 | /usr/bin/python3 papers.py "$1" 797 | 798 | 799 | 800 | 801 | 802 | scriptargtype 803 | 1 804 | scriptfile 805 | 806 | subtext 807 | 808 | title 809 | Welcome to paperpAlfred 👋 810 | type 811 | 0 812 | withspace 813 | 814 | 815 | type 816 | alfred.workflow.input.scriptfilter 817 | uid 818 | E2E84778-9472-48A7-851A-87F07879DA5C 819 | version 820 | 3 821 | 822 | 823 | config 824 | 825 | conditions 826 | 827 | 828 | inputstring 829 | 830 | matchcasesensitive 831 | 832 | matchmode 833 | 0 834 | matchstring 835 | ShowHelpWindow 836 | outputlabel 837 | ShowHelp 838 | uid 839 | B8F4A19F-1244-4D35-BFAF-7B30C48F9337 840 | 841 | 842 | elselabel 843 | else 844 | hideelse 845 | 846 | 847 | type 848 | alfred.workflow.utility.conditional 849 | uid 850 | DD1DC957-AD49-4CC1-A425-2D6FF0F6F2D5 851 | version 852 | 1 853 | 854 | 855 | config 856 | 857 | concurrently 858 | 859 | escaping 860 | 102 861 | script 862 | find "$PAPPATH" -name "$myFileName" | head -n 1 | tr \\n \\0 | xargs -0 open 863 | 864 | # older version opening multiple copies if >1 found 865 | # find /Users/giovanni/Google\ Drive/Paperpile/ -name "{query}" -exec open {} + 866 | scriptargtype 867 | 0 868 | scriptfile 869 | 870 | type 871 | 5 872 | 873 | type 874 | alfred.workflow.action.script 875 | uid 876 | C81A90A7-A0A8-4A02-8BC5-9315BC9BB108 877 | version 878 | 2 879 | 880 | 881 | config 882 | 883 | concurrently 884 | 885 | escaping 886 | 102 887 | script 888 | open "https://docs.google.com/file/d/$gdrive_id" 889 | scriptargtype 890 | 0 891 | scriptfile 892 | 893 | type 894 | 0 895 | 896 | type 897 | alfred.workflow.action.script 898 | uid 899 | 30EEB713-8B4D-41AF-A310-C8793A498639 900 | version 901 | 2 902 | 903 | 904 | config 905 | 906 | concurrently 907 | 908 | escaping 909 | 102 910 | script 911 | open "https://paperpile.com/app/p/$paperpileID" 912 | scriptargtype 913 | 0 914 | scriptfile 915 | 916 | type 917 | 0 918 | 919 | type 920 | alfred.workflow.action.script 921 | uid 922 | D942F3A6-D97B-4FA5-80ED-18B88282CF61 923 | version 924 | 2 925 | 926 | 927 | readme 928 | Search your Paperpile library with Alfred 929 | https://github.com/giovannicoppola/paperpAlfred 930 | 931 | Environment Variables 932 | - MAXRESULTS: max number of results returned (default: 99) 933 | - PAPLIBRARY: path to Paperpile library file (this is needed) 934 | - PAPPATH: path to Paperpile folder in Google Drive (optional, to open downloaded PDFs in system viewer) 935 | - KEY_PREFIX: prefix for citekeys (default: '@') 936 | 937 | 938 | Basic usage (`ppp` ↩️) 939 | 940 | - Simple search: `ppp` -- query across all fields 941 | 942 | - Filter by label first: `ppl` (or optional hotkey) 943 | - ⌃⏎ open label in Paperpile 944 | 945 | - Filter by folder first: `ppf` (or optional hotkey) 946 | - ⌃⏎ open folder in Paperpile 947 | 948 | - Filter by item type first: `ppty` 949 | 950 | - Advanced search 951 | - title: 952 | - abstract: 953 | - citekey: 954 | - journal: 955 | - folder: 956 | - label: 957 | - pmid: 958 | - year: 959 | - first: [first author] 960 | - last: [last author] 961 | - type: [publication type] 962 | 963 | - Output 964 | 1. ⏎ open PDF with system viewer 965 | 2. ⇧⏎ abstract (show and copy to clipboard) 966 | 3. ⌃⏎ complete reference (show and copy to clipboard) 967 | 4. ⌥⏎ [First-last author, journal, year, PMID] to clipboard 968 | 5. ⌘⏎ citation key to clipboard 969 | 6. ⌘⇧⏎ open PDF in Google drive 970 | 7. ⌘⌥⏎ open record in Paperpile 971 | uidata 972 | 973 | 09F993A7-F21C-4541-8C43-6641A0B6E1D8 974 | 975 | xpos 976 | 1000 977 | ypos 978 | 365 979 | 980 | 0F60EE65-A185-4BBF-B450-70AF97DF20D0 981 | 982 | note 983 | Copy Fist, Last, Journal, Year, and PMID to clipboard 984 | xpos 985 | 1125 986 | ypos 987 | 210 988 | 989 | 1AAAEDC3-C0EE-47D4-83C7-2A5AB352295E 990 | 991 | xpos 992 | 1290 993 | ypos 994 | 390 995 | 996 | 1D9857A2-7E4B-4E14-9556-B38C14893DD9 997 | 998 | xpos 999 | 1480 1000 | ypos 1001 | 380 1002 | 1003 | 21B88AAB-784B-4782-B4D6-607BDB0F7C25 1004 | 1005 | xpos 1006 | 790 1007 | ypos 1008 | 155 1009 | 1010 | 28606112-A338-475C-9D0A-17F14E5D6E40 1011 | 1012 | colorindex 1013 | 3 1014 | note 1015 | Set this Hotkey to launch paperAlfred 1016 | xpos 1017 | 30 1018 | ypos 1019 | 525 1020 | 1021 | 30EEB713-8B4D-41AF-A310-C8793A498639 1022 | 1023 | colorindex 1024 | 9 1025 | note 1026 | Open PDF in Google Drive 1027 | xpos 1028 | 950 1029 | ypos 1030 | 625 1031 | 1032 | 33BF8A0E-B7AA-495E-B89F-1F8239DAECBE 1033 | 1034 | colorindex 1035 | 7 1036 | note 1037 | Search by folder 1038 | xpos 1039 | 235 1040 | ypos 1041 | 200 1042 | 1043 | 3B12BCC5-45EE-4E9D-95FB-0B156B0704B3 1044 | 1045 | note 1046 | Show and copy to clipboard complete reference 1047 | xpos 1048 | 905 1049 | ypos 1050 | 15 1051 | 1052 | 3FDF7937-4C98-4A75-B7C6-AF4AF5FBB5D7 1053 | 1054 | colorindex 1055 | 8 1056 | note 1057 | Open Folder in Paperpile 1058 | xpos 1059 | 555 1060 | ypos 1061 | 200 1062 | 1063 | 439299E4-26DE-4954-9EE4-6AB9A055C4DC 1064 | 1065 | colorindex 1066 | 8 1067 | note 1068 | Open Label in Paperpile 1069 | xpos 1070 | 555 1071 | ypos 1072 | 40 1073 | 1074 | 4797204D-1BEC-49C9-8577-07F086902572 1075 | 1076 | colorindex 1077 | 3 1078 | note 1079 | Set this Hotkey to filter by label first 1080 | xpos 1081 | 30 1082 | ypos 1083 | 40 1084 | 1085 | 6DF20719-C29E-41E0-AEA3-E92AD68EA188 1086 | 1087 | colorindex 1088 | 1 1089 | note 1090 | Search by label 1091 | xpos 1092 | 235 1093 | ypos 1094 | 40 1095 | 1096 | 837B6FCB-ACFA-46BE-AEC2-10135A81A784 1097 | 1098 | colorindex 1099 | 2 1100 | note 1101 | Search by type 1102 | xpos 1103 | 235 1104 | ypos 1105 | 390 1106 | 1107 | 8D033AF8-08E7-4097-B0BF-5BB509FAF1AB 1108 | 1109 | xpos 1110 | 905 1111 | ypos 1112 | 165 1113 | 1114 | C15FD31B-73B9-4D65-9FC9-B88846234B1C 1115 | 1116 | xpos 1117 | 1225 1118 | ypos 1119 | 480 1120 | 1121 | C81A90A7-A0A8-4A02-8BC5-9315BC9BB108 1122 | 1123 | colorindex 1124 | 4 1125 | note 1126 | Open PDF with system viewer 1127 | xpos 1128 | 1225 1129 | ypos 1130 | 600 1131 | 1132 | D942F3A6-D97B-4FA5-80ED-18B88282CF61 1133 | 1134 | colorindex 1135 | 10 1136 | note 1137 | Open Paperpile Record 1138 | xpos 1139 | 1020 1140 | ypos 1141 | 805 1142 | 1143 | D94E41FF-2E5C-420B-919A-5A2990C3F4A6 1144 | 1145 | note 1146 | Show (and copy to clipboard) abstract 1147 | xpos 1148 | 1485 1149 | ypos 1150 | 200 1151 | 1152 | DD1DC957-AD49-4CC1-A425-2D6FF0F6F2D5 1153 | 1154 | xpos 1155 | 1095 1156 | ypos 1157 | 545 1158 | 1159 | E2E84778-9472-48A7-851A-87F07879DA5C 1160 | 1161 | colorindex 1162 | 10 1163 | note 1164 | Search your Paperpile library 1165 | xpos 1166 | 525 1167 | ypos 1168 | 530 1169 | 1170 | E31F5435-FCA6-40FB-AC97-768A01A29B7B 1171 | 1172 | colorindex 1173 | 3 1174 | note 1175 | Set this Hotkey to filter by folder first 1176 | xpos 1177 | 30 1178 | ypos 1179 | 195 1180 | 1181 | 1182 | userconfigurationconfig 1183 | 1184 | 1185 | config 1186 | 1187 | default 1188 | 99 1189 | placeholder 1190 | 1191 | required 1192 | 1193 | trim 1194 | 1195 | 1196 | description 1197 | 1198 | label 1199 | Maximum number of records returned 1200 | type 1201 | textfield 1202 | variable 1203 | MAXRESULTS 1204 | 1205 | 1206 | config 1207 | 1208 | default 1209 | @ 1210 | placeholder 1211 | 1212 | required 1213 | 1214 | trim 1215 | 1216 | 1217 | description 1218 | Prefix for citekey 1219 | label 1220 | Key Prefix 1221 | type 1222 | textfield 1223 | variable 1224 | KEY_PREFIX 1225 | 1226 | 1227 | config 1228 | 1229 | default 1230 | 1231 | filtermode 1232 | 2 1233 | placeholder 1234 | 1235 | required 1236 | 1237 | 1238 | description 1239 | Location of the JSON Paperpile Library file 1240 | label 1241 | Paperpile Library 1242 | type 1243 | filepicker 1244 | variable 1245 | PAPLIBRARY 1246 | 1247 | 1248 | config 1249 | 1250 | default 1251 | 1252 | filtermode 1253 | 1 1254 | placeholder 1255 | 1256 | required 1257 | 1258 | 1259 | description 1260 | Path to Google Drive Paperpile folder 1261 | label 1262 | Paperpile path 1263 | type 1264 | filepicker 1265 | variable 1266 | PAPPATH 1267 | 1268 | 1269 | variablesdontexport 1270 | 1271 | version 1272 | 2.3 1273 | webaddress 1274 | https://github.com/giovannicoppola/paperpAlfred 1275 | 1276 | 1277 | --------------------------------------------------------------------------------