AppleScript
Safari
Evernote

citation - Evernoteに実装するアンビリバボーな機能 #2

More than 3 years have passed since last update.

アンビリーバボ?アンビリバボー?

http://rashita.net/blog/?p=16722

こちらの2つ目、「拡張型ノートリンク」と書かれた機能をAppleScriptで実装しました。


使い方(基礎編)

Evernoteのノート上で引用したい箇所を選択状態に。

citation screenshot 1

スクリプトメニューからcitationを実行すると

citation screenshot 2

「Copied」と通知。

citation screenshot 3

好きなところでペーストすれば、それっぽい見た目のノートリンク。

citation screenshot 4


使い方(応用編)

スクリプトオブジェクトを使って他のアプリケーションにも拡張できます。

例えばSafariの場合。ページ上で引用部分を選択してcitationを実行。

citation screenshot 5

Evernoteにペーストで引用文を含んだリンクに。画像は反映されません。

citation screenshot 6

今のところEvernoteとSafariの2種類のみです。ソースコードをいじって他のアプリケーションを追加してみてください。

リンクの色を変えるなり、アイコンを付けるなりしないと見分けがつきませんが、それはまた別の機会に。


(2015-12-18 追記) 使い方(発展編)

HTMLを理解できる方にオススメ。

ソースコード冒頭にあるproperty citationTemplate : ...の部分で、貼り付けるリンクの見た目を決めています。

このテンプレート、見ればわかるようにHTMLで記述されているのです。


  • 元のままでは無駄な情報が多い

  • 文字やリンクの大きさや色、順番が気にくわない

  • 引用はblockquoteじゃないと気持ち悪い

なんて方はHTMLをいじって自分好みの見た目に変更しましょう。

その際に注意すべきこととして 特殊文字の取り扱い があります。

property citationTemplate : ...の部分ではHTML全体を文字列としてcitationTemplateという変数に入れるため、ダブルクオーテーション(")で囲っています。

HTML内でダブルクオーテーション(")を使うと、そこが文字列の終わりと判断されて「残りの文字列はなんだ?」ってエラーが出てしまいます。

そこで、エスケープのためにバックスラッシュ(\)を前に置いて、\"と記述してください。こうすればエラーは出ないはず。

他にもつまずくことがあるかもしれませんが、Googleに頼りながら試してみてください。


動作環境


  • Mac OS X: 10.11

  • Evernote: ver 6.3 (452832 Direct)


ソースコード


citation.scpt

(*

* [[Clipping]]: 引用内容 - getClipping()
* [[Link]]: リンクURL - getLink()
* [[Title]]: リンクタイトル - getTitle()
* [[Author]]: 作者 - getAuthor()
* [[Publisher]]: 出版社、発行元 - getPublisher()
* [[Date]]: 日付 - getDate()
*)
property citationTemplate : "<br>
<div class='citation' style='padding: 0px 30px;'>
<div class='clipping' style='line-height:18px; padding: 8px 15px; border-left:2px solid #dedede; color: #919191; font-family: Helvetica, sans-serif; font-style: italic; font-size: 14px;'>[[Clipping]]</div>
<div style='padding-left: 17px;'>
<div class='title' style='color: #000000; font-family: Helvetica, sans-serif; font-size: 14px; margin-top: 19px;'>
<a style='text-decoration: none; color: rgba(45,190,96,1.0);' href='[[Link]]'>
&quot;[[Title]]&quot;
</a>
</div>
<div style='color: #919191; font-size: 12px; margin-top: 4px; font-family: 'CaeciliaLTStd', serif; font-style: italic;'>
<span class='author'>[[Author]]</span>
<span class='publisher'>[[Publisher]].</span>
<span class='date'>[[Date]]</span>
</div>
</div>
</div>
<br>"

on run

--アプリケーション判別
if application "Evernote" is running and application "Evernote" is frontmost then
set curApp to make Evernote
else if application "Safari" is running and application "Safari" is frontmost then
set curApp to make Safari
else
--set curApp to make some_application
error
end if

--citationのHTML作成
copy citationTemplate to citation
repeat with replacement in {¬
{"[[Clipping]]", getClipping() of curApp}, ¬
{"[[Link]]", getLink() of curApp}, ¬
{"[[Title]]", getTitle() of curApp}, ¬
{"[[Author]]", getAuthor() of curApp}, ¬
{"[[Publisher]]", getPublisher() of curApp}, ¬
{"[[Date]]", getDate() of curApp} ¬
}
set {f, r} to replacement
set r to my escapeHTMLSpecialChars(r)
set r to my replace(r, {return, linefeed}, "<br>")
set citation to my replace(citation, f, r)
end repeat

--HTMLをコピー
my copyHTML(citation)
display notification getTitle() of curApp with title "Copied"
end run

script some_application

on getClipping()
repeat until application id (my appID) is frontmost
activate application id (my appID)
end repeat
set the clipboard to ""
set tmpClipboard to the clipboard as record
tell application "System Events"
keystroke "c" using {command down}
set timestamp to current date
repeat while (the clipboard as record) = tmpClipboard
if (current date) > timestamp + 10 then
error
end if
delay 1
end repeat
end tell
return the clipboard as text
end getClipping

on getLink()
return ""
end getLink

on getTitle()
return ""
end getTitle

on getAuthor()
return ""
end getAuthor

on getPublisher()
return ""
end getPublisher

on getDate()
return ""
end getDate

on make
set a_class to me
script Instance
property parent : a_class
property appID : run script "tell application \"System Events\" to return bundle identifier of process 1 whose frontmost = true and visible = true"
end script
end make
end script

script Evernote
property parent : some_application

--on getClipping()
-- continue getClipping()
--end getClipping

on getLink()
tell application "Evernote"
return note link of (note id (my noteID)) of notebook 1
end tell
end getLink

on getTitle()
tell application "Evernote"
return title of (note id (my noteID)) of notebook 1
end tell
end getTitle

on getAuthor()
tell application "Evernote"
set noteAuthor to author of (note id (my noteID)) of notebook 1
end tell
if noteAuthor = missing value then
set noteAuthor to ""
end if
return noteAuthor
end getAuthor

on getPublisher()
tell application "Evernote"
return name of notebook of (note id (my noteID)) of notebook 1
end tell
end getPublisher

on getDate()
tell application "Evernote"
tell creation date of (note id (my noteID)) of notebook 1 as date
return "" & year & "年" & (month of it as number) & "月" & day & "日"
end tell
end tell
end getDate

on make
set self to continue make
script EvernoteInstance
property parent : self
property noteID : run script "tell application \"Evernote\" to return local id of item 1 of (selection as list)"
end script
end make
end script

script Safari
property parent : some_application

on getClipping()
tell application "Safari"
set clipping to do JavaScript "document.getSelection().toString();" in current tab of window id (my windowID)
end tell
if clipping = "" then
try
set clipping to my extractText(my metas, linefeed & "og:description,", linefeed)
on error number -2700
end try
end if
return clipping
end getClipping

on getLink()
try
return my extractText(my metas, linefeed & "og:url,", linefeed)
on error number -2700
end try
tell application "Safari"
return URL of current tab of window id (my windowID)
end tell
end getLink

on getTitle()
try
return my extractText(my metas, linefeed & "og:title,", linefeed)
on error number -2700
end try
tell application "Safari"
return name of current tab of window id (my windowID)
end tell
end getTitle

on getAuthor()
try
return my extractText(my metas, linefeed & "twitter:creator,", linefeed)
on error number -2700
end try
return ""
end getAuthor

on getPublisher()
try
return my extractText(my metas, linefeed & "og:site_name,", linefeed)
on error number -2700
end try
return ""
end getPublisher

on getDate()
try
set ISODate to my extractText(my metas, linefeed & "article:published_time,", linefeed)
tell my ISODateToDate(ISODate)
return "" & year & "年" & (month of it as number) & "月" & day & "日"
end tell
on error number -2700
end try
return ""
end getDate

on make
set self to continue make
script SafariInstance
property parent : self
property windowID : run script "tell application \"Safari\" to return id of window 1 whose current tab ≠ missing value"
property metas : run script "tell application \"Safari\" to return do JavaScript \"var ary=[]; var elements=document.getElementsByTagName('meta'); for(i=0; i<elements.length; i++) {content=elements[i].getAttribute('content'); if(elements[i].getAttribute('property')) {ary.push(elements[i].getAttribute('property') + ',' + elements[i].getAttribute('content'));} else if(elements[i].getAttribute('name')) {ary.push(elements[i].getAttribute('name') + ',' + elements[i].getAttribute('content'));}} '\\\\n' + ary.join('\\\\n') + '\\\\n';\" in current tab of window id " & windowID
end script
end make
end script

on escapeHTMLSpecialChars(args)
if class of args = list then
repeat with i from 1 to count args
set item i of args to my escapeHTMLSpecialChars(item i of args)
end repeat
else
set args to my replace(args, "&", "&amp;")
set args to my replace(args, "<", "&lt;")
set args to my replace(args, ">", "&gt;")
set args to my replace(args, quote, "&quot;")
set args to my replace(args, "'", "&#039;")
end if
return args
end escapeHTMLSpecialChars

on replace(str as text, find, replace as text)
set oldDel to AppleScript's text item delimiters
set AppleScript's text item delimiters to find
set replaceList to text items of str
set AppleScript's text item delimiters to replace
set str to replaceList as string
set AppleScript's text item delimiters to oldDel
return str
end replace

on copyHTML(HTML as text)
set the clipboard to {«class utf8»:HTML, string:HTML, Unicode text:HTML, «class HTML»:my htmlToRawData(HTML)}
end copyHTML

on extractText(theText, beginText, endText)
--beginText, endText例: {text:"a", forwardSearch:false, containText:true}
set beginText to (beginText as record) & {forwardSearch:true, containText:false}
set endText to (endText as record) & {forwardSearch:true, containText:false}

if theText does not contain text of beginText or theText does not contain text of endText then error "引数の文字列が含まれません"

set scraps to my split(theText, text of beginText)
if forwardSearch of beginText then
set theText to my join(rest of scraps, text of beginText)
else
set {theText} to reverse of scraps
end if
set scraps to my split(theText, text of endText)
if forwardSearch of endText then
set {theText} to scraps
else
set theText to my join(reverse of rest of reverse of scraps, text of endText)
end if
if containText of beginText then
set theText to "" & text of beginText & theText
end if
if containText of endText then
set theText to "" & theText & text of endText
end if
return theText
end extractText

on ISODateToDate(ISODate)
if class of ISODate = «class isot» then
set aDate to ISODate as date
else
set aDate to (do shell script "echo " & quoted form of ISODate as «class isot») as date
if ISODate ends with "Z" then
set aDate to my shiftDateFromGMT(aDate)
end if
end if
return aDate
end ISODateToDate

on htmlToRawData(HTML as text)
return run script "«data HTML" & (do shell script "echo " & quoted form of HTML & " | hexdump -v -e '/1 \"%02X\"'") & "»"
end htmlToRawData

on split(str as text, delim)
set oldDel to AppleScript's text item delimiters
set AppleScript's text item delimiters to delim
set aList to text items of str
set AppleScript's text item delimiters to oldDel
return aList
end split

on join(textList, term as text)
set oldDel to AppleScript's text item delimiters
set AppleScript's text item delimiters to term
set aText to textList as string
set AppleScript's text item delimiters to oldDel
return aText
end join

on shiftDateFromGMT(aDate)
global timeToGMT
try
timeToGMT
on error number -2753
set timeToGMT to time to GMT
end try
return aDate + timeToGMT
end shiftDateFromGMT



普段AppleScriptを使わない方へ


メニューバーにスクリプトメニューを表示する方法

このスクリプトはスクリプトメニューから実行することを想定しています。スクリプトエディタから実行してもエラーになってしまうので、次に示す設定をお願いします。


  • スクリプトエディタ.app (/Applications/Utilities/Script Editor.app) 起動

  • メニューバー

  • 「スクリプトエディタ」

  • 「環境設定…」

  • 「一般」

  • 「スクリプトメニュー」

  • 「メニューバーにスクリプトメニューを表示」 にチェック

メニューバーにスクリプトメニューのアイコンが表示されればOK。


GUIスクリプティングを有効に

スクリプト中に「GUIスクリプティング」と呼ばれる機能を使っています。スクリプトメニューから実行する前に以下の設定が必要です。


  • システム環境設定.app (/Applications/System Preferences.app) 起動

  • 「セキュリティとプライバシー」

  • 「プライバシー」

  • 鍵マーク 「変更するにはカギをクリックします。」をクリック

  • パスワード入力

  • 「ロックを解除」

  • 「アクセシビリティ」

  • 「+」

  • /System/Library/CoreServices/SystemUIServer.app を選択

  • 「開く」


(2015-12-18 追記) スクリプトファイルの保存方法

スクリプトファイルの保存場所を書いていませんでした。スクリプトメニューからAppleScriptを実行するには特定の場所にファイルがなければいけません。

下の方法でスクリプトファイルを保存してください。


  • スクリプトエディタ.app (/Applications/Utilities/Script Editor.app) 起動

  • ファイル選択ダイアログが表示された場合は「新規書類」

  • このページ中部の「ソースコード」にある「citation.scpt」全体をスクリプトエディタ.appにコピペ

  • ⌘+Kでコンパイル

  • ⌘+Sで保存

  • 名前を「citation.scpt」に

  • 保存場所を「/Users/(ユーザ名)/Library/Scripts」に

  • ファイルフォーマットは「スクリプト」のままで

  • 「保存」

保存が完了しました。スクリプトエディタ.appを終了してください。

スクリプトの実行方法は冒頭の「使い方(基礎編)・(応用編)」に書いたとおり。きちんと動くか確認してみてください。


更新履歴


  • 2015-12-16: Evernoteのノート上で引用型ノートリンクをコピーする機能実装

  • 2015-12-17: Safariで引用型リンクをコピーする機能追加