From e28a9c3bdb0801ea83216276588f556207343e55 Mon Sep 17 00:00:00 2001 From: Juraj Oravec Date: Tue, 11 Jun 2019 00:08:05 +0200 Subject: [PATCH] Port of QupZilla version Signed-off-by: Juraj Oravec --- readability/__init__.py | 76 +- readability/data/Call.js | 118 ++ readability/data/RM-Delete-24x24.png | Bin 0 -> 14935 bytes readability/data/RM-Minus-24x24.png | Bin 0 -> 1685 bytes readability/data/RM-Plus-24x24.png | Bin 0 -> 2946 bytes readability/data/RM-Type-Controls-24x24.png | Bin 0 -> 15987 bytes readability/data/RM-Type-Controls-Arrow.png | 1 + readability/data/RM-close-hover.png | Bin 0 -> 8466 bytes readability/data/RM-close.png | Bin 0 -> 8010 bytes readability/data/Readability.js | 1835 +++++++++++++++++++ readability/data/Toolbar.js | 145 ++ readability/data/icon.png | Bin 0 -> 22991 bytes readability/data/style.css | 543 ++++++ readability/metadata.desktop | 7 +- 14 files changed, 2718 insertions(+), 7 deletions(-) create mode 100644 readability/data/Call.js create mode 100644 readability/data/RM-Delete-24x24.png create mode 100644 readability/data/RM-Minus-24x24.png create mode 100644 readability/data/RM-Plus-24x24.png create mode 100644 readability/data/RM-Type-Controls-24x24.png create mode 100644 readability/data/RM-Type-Controls-Arrow.png create mode 100644 readability/data/RM-close-hover.png create mode 100644 readability/data/RM-close.png create mode 100644 readability/data/Readability.js create mode 100644 readability/data/Toolbar.js create mode 100644 readability/data/icon.png create mode 100644 readability/data/style.css diff --git a/readability/__init__.py b/readability/__init__.py index 184e169..3bb4a68 100644 --- a/readability/__init__.py +++ b/readability/__init__.py @@ -1,13 +1,16 @@ import Falkon -from PySide2 import QtCore +import os +from PySide2 import QtCore, QtGui class Readability(Falkon.PluginInterface, QtCore.QObject): + view = None + def init(self, state, settingsPath): plugins = Falkon.MainApplication.instance().plugins() plugins.mainWindowCreated.connect(self.onMainWindowCreated) - plugins.mainWindowDeleted.connect(self.mainWindowDeleted) + plugins.mainWindowDeleted.connect(self.onMainWindowDeleted) if state == Falkon.PluginInterface.LateInitState: for window in Falkon.MainApplication.instance().windows(): @@ -15,7 +18,7 @@ class Readability(Falkon.PluginInterface, QtCore.QObject): def unload(self): for window in Falkon.MainApplication.instance().windows(): - self.mainWindowDeleted(window) + self.onMainWindowDeleted(window) def testPlugin(self): return True @@ -23,8 +26,73 @@ class Readability(Falkon.PluginInterface, QtCore.QObject): def onMainWindowCreated(self, window): pass - def mainWindowDeleted(self, window): + def onMainWindowDeleted(self, window): pass + def populateWebViewMenu(self, menu, view, hitTestResult): + self.view = view + + if (hitTestResult.imageUrl().isEmpty() + and hitTestResult.linkUrl().isEmpty() + and hitTestResult.mediaUrl().isEmpty() + and not hitTestResult.isContentEditable() + and not hitTestResult.isContentSelected()): + menu.addAction( + QtGui.QIcon(os.path.join(os.path.dirname(__file__), "data", "icon.png")), + "Readability", + self.makeReadability + ) + + def makeReadability(self): + dataDir = os.path.join(os.path.dirname(__file__), "data") + + iconClose = Falkon.QzTools.pixmapToDataUrl( + QtGui.QPixmap(os.path.join(dataDir, "RM-close.png")) + ).toString() + iconCloseHover = Falkon.QzTools.pixmapToDataUrl( + QtGui.QPixmap(os.path.join(dataDir, "RM-close-hover.png")) + ).toString() + iconDelete = Falkon.QzTools.pixmapToDataUrl( + QtGui.QPixmap(os.path.join(dataDir, "RM-Delete-24x24.png")) + ).toString() + iconPlus = Falkon.QzTools.pixmapToDataUrl( + QtGui.QPixmap(os.path.join(dataDir, "RM-Plus-24x24.png")) + ).toString() + iconMinus = Falkon.QzTools.pixmapToDataUrl( + QtGui.QPixmap(os.path.join(dataDir, "RM-Minus-24x24.png")) + ).toString() + iconArrow = Falkon.QzTools.readAllFileContents( + os.path.join(dataDir, "RM-Type-Controls-Arrow.png") + ) + iconControls = Falkon.QzTools.pixmapToDataUrl( + QtGui.QPixmap(os.path.join(dataDir, "RM-Type-Controls-24x24.png")) + ).toString() + + css = Falkon.QzTools.readAllFileContents( + os.path.join(dataDir, "style.css") + ).replace("\n", "") + css = css.replace("{1}", iconClose) + css = css.replace("{2}", iconCloseHover) + css = css.replace("{3}", iconDelete) + css = css.replace("{4}", iconPlus) + css = css.replace("{5}", iconMinus) + css = css.replace("{6}", iconArrow) + css = css.replace("{7}", iconControls) + + javascript = Falkon.QzTools.readAllFileContents( + os.path.join(dataDir, "Readability.js") + ) + toolbar = Falkon.QzTools.readAllFileContents( + os.path.join(dataDir, "Toolbar.js") + ) + call = Falkon.QzTools.readAllFileContents( + os.path.join(dataDir, "Call.js") + ) + call = call.replace("{1}", javascript) + call = call.replace("{2}", toolbar) + call = call.replace("{3}", css) + + self.view.page().runJavaScript(call) + Falkon.registerPlugin(Readability()) diff --git a/readability/data/Call.js b/readability/data/Call.js new file mode 100644 index 0000000..55399f1 --- /dev/null +++ b/readability/data/Call.js @@ -0,0 +1,118 @@ +/* ============================================================ +* QupZilla - WebKit based browser +* Copyright (C) 2016 Jaroslav Bambas +* +* This program is free software: you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation, either version 3 of the License, or +* (at your option) any later version. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with this program. If not, see . +* ============================================================ */ + +{1} + +{2} + +function callReadability(){ + var loc = document.location; + var uri = { + spec: loc.href, + host: loc.host, + prePath: loc.protocol + "//" + loc.host, + scheme: loc.protocol.substr(0, loc.protocol.indexOf(":")), + pathBase: loc.protocol + "//" + loc.host + loc.pathname.substr(0, loc.pathname.lastIndexOf("/") + 1) + }; + var article = new Readability(uri, document).parse(); + renderPage(article); +} + +function readabilityHead(article){ + var head = document.createElement('head'); + document.documentElement.appendChild(head); + + var title = document.createElement('title'); + title.innerHTML = article.title; + head.appendChild(title); + + var css = document.createElement('style'); + css.innerHTML = '{3}'; + head.appendChild(css); + + var meta = document.createElement('meta'); + meta.content = "text/html; charset=UTF-8"; + meta.httpEquiv = 'content-type'; + head.appendChild(meta); + meta = document.createElement('meta'); + meta.name = 'viewport'; + meta.content = 'width=device-width, user-scalable=0'; + head.appendChild(meta); +} + +function readablilityBody(article){ + var body = document.createElement('body'); + body.className = 'loaded sans-serif sepia'; + document.documentElement.appendChild(body); + + var container = document.createElement('div'); + container.className = 'container font-size5'; + container.id = 'container'; + body.appendChild(container); + + var header = document.createElement('div'); + header.className = 'header'; + header.id = 'reader-header'; + header.style.display = 'block'; + container.appendChild(header); + + var domain = document.createElement('a'); + domain.id = 'reader-domain'; + domain.className = 'domain'; + domain.href = article.uri.spec; + domain.innerHTML = article.uri.host; + header.appendChild(domain); + + var h1 = document.createElement('h1'); + h1.id = 'reader-title'; + h1.innerHTML = article.title; + header.appendChild(h1); + + var credit = document.createElement('div'); + credit.id = 'reader-credits'; + credit.className = 'credits'; + credit.innerHTML = article.byline; + header.appendChild(credit); + + var content = document.createElement('div'); + content.className = 'content'; + container.appendChild(content); + + var reader_content = document.createElement('div'); + reader_content.id = 'moz-reader-content'; + reader_content.style.display = 'block'; + reader_content.innerHTML = article.content; + content.appendChild(reader_content); +} + +function renderPage(article){ + var element = document.documentElement; + if(element && !!article && !!article.content){ + element.removeChild(document.body); + element.removeChild(document.head); + + readabilityHead(article); + readablilityBody(article); + + readablilityToolbar(); + } else { + alert("No content to transform."); + } +} + +callReadability(); \ No newline at end of file diff --git a/readability/data/RM-Delete-24x24.png b/readability/data/RM-Delete-24x24.png new file mode 100644 index 0000000000000000000000000000000000000000..2472f3179bcc72adc500857c40ca314043b12868 GIT binary patch literal 14935 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 literal 0 HcmV?d00001 diff --git a/readability/data/RM-Minus-24x24.png b/readability/data/RM-Minus-24x24.png new file mode 100644 index 0000000000000000000000000000000000000000..fc884e334b75816925305c2674038ec6d5c83e11 GIT binary patch literal 1685 zcmeAS@N?(olHy`uVBq!ia0y~yV2S`?4mP03zO)&4fD~Jjx4R3&e-K=-cll(X2xoyu zWHAE+w=f7ZGR&GI0Tg5}@$_|Nf50Lxz+mjCv1<`fNV3E=qQp5rH#aq}gu%HeHL)Z$ zMWH;iBtya7(>EZzkx!g~f%UJai(^Q|t+&?<1sM!@j%-+RVt!@w!kv9WEdJ}5fQF2M z(GZ|n2sEtqD@tWx`0?C>fgxc70|Sp3L&HHPh64$V3=%pF49%=yt^qJluyKRAq$mRE zA!rh^BB^VEKzpk#?_L`iUdT1k0gQ7S`udAVL@UUqSEVnM22eo^}DcQ#TC3|#*` zT^vIy=DfXmFqg?u!1drUCH)gUipD3-h#xulT>b32P|<_o^XFZe&iEsF?bfts8>^3d z+Sq?*`tj?gEd#@Y{enQJ88)&q2ndfdMgw6qC5+~RfzAcH*KED^PVm6PJL%81RUh%( zlm2XLHA{ic^_i7#FVdQ&MBb@0Oy|1I{*Lx literal 0 HcmV?d00001 diff --git a/readability/data/RM-Type-Controls-24x24.png b/readability/data/RM-Type-Controls-24x24.png new file mode 100644 index 0000000000000000000000000000000000000000..cb0ab19ca556ecd52abded588ae793a0384c9451 GIT binary patch literal 15987 zcmeHu`9IWO^#2f960&58kQw_v*|!K8vX3Q8hBC60WX)c7BE~u)sj;s`N%kb~7^9}F ziIAqTjV){V-l@;$`!{@l_|`)`yynim=brsM&v_+VnqNA}%+CygKu#iz4Xhy$S_kS6 z<8kn$1D!t!{yXk&ddUECME(1uy*vv%VY+R6B>)0B!Abq0fjlbY1rHel5oU%A-|2YR zX$0fu6J)?6zCc5Vz>9u2Z+hGcgj~Gi;TGs|T{y%m@P_augqfxNJysqFL>PiF&_jk! zEPuOO=xkebbhO>l4i$m28yJtpp)y!sh~D?T|3p|*UzkZvZ7w6RQ31{;iV$}>ATJQm z==@!eT#A9G&Ap7{9;Z|p)mHwidLn2^a|-5W`lRHu)3A1@ZqI%v*Wb}V=wu#9!M~ajdu_SI>fOHleelW@x_s4enzDx8ib@cKTB1NYk6+Qgq5yRP-@%UHIsev$G>FhD^2ZVks|)J4q|kGvIZY?a3M~#!oZ!nJ)l(h?5%P% z^+HA|SVK%`*^Sk@60j3sO_91O@i5!%K>d$b(sr)8M4(fdJ#J_v_4ulV_e_y4N3Rp{TnkQHEfmqR04}D0*9hp=5=-><-iE&bN!5F?z?*WNfc*lnP zPhGX3MPc@}C?jAU5>7dgsZyAw5(s{)_EiaQ!fo3mpSlEOCX6a^?DfZ6k4R9jgOope zr`{cX8nIW4J68F=BGiU>9T0xu_YA9@a=8zQAncr0#YAXHl{^NRVre+GUjrCeUY;Oo4TY^}$D4soZd-!AOa4a4n=~%Ne7U~0fg~rcN!OO7zx)F8NEG4RD=G;sC zdaPSX1cKCYoJZ2YtMfLW;zF#_F4Xxx{Z(;UYy+ADiDA|rvbkTi>i^c(VOH&D`Z>U- zj5~i6*oKCw(xg3Y-H#VD(I_^dOZ<0mii@0=fCnSTKf)z@*HK+FdH+7@&mWJ%I3zHS zuDH0k@Yoy&!V+;oBWCp$8mR1}lfkhJ#Lheeasy|a2G)EMlU~BnT}_oFc*3z8ap+x@ z!TRr9VmD&8&Nxh(`eb)*NQgZ)m@{A4=jn%X|uRQTfrx1@c zLvA(WH2LZbw*IlxQf%CJ>pJ2yt5M{- z_}A#&va#bj7#)sPktrXvJlkeLVSv`sWA=a2{}hsgn*h`XVWyGx_M>u@#WR=xUGuHP ziMw|jE)l;WqOM)LHYbf)@lfNWsvFK&7G}%?53k3u(M2SM6kM52AP`y#+P6vY7ssSu z5n75(ATagO6*(_I#J=$(hd+I->ZJFflp>sV>XC0vz^zHv!=&c6ZKsuweS{X(S9>xc zm#j9?0)2kQ3j%O7zHDl0N;Tz-Vr)#jQi~(z#LkW#+fUVS3I(%ShGSw`j*(Ms>{M!O zmY`R_eMHKWQKU(csr`#kv)s)(_l&TRwb6EP&XPIdO7h|Og--GGqx{ma@bG4V=>p8u z*g~}paR3t~?-jW=dh+GVm+~N1WdCZsb$3QQVLrg zmg3l!77AZja2bAX+qOv+Szt%lq;{^zOPX*b^C>Km#of2I)Wzoi+w$;PLPEkzK|#SY$k#3pT{O#UYi}MIrk*~yXZ^%+GITC& zPD4{u_j{q{E8ep7+`u@TBF%J~RI@g$$l(uW`|57QRrFQ@?h;IUwlMs^@FQ(_c+Q

E}MsQw^4ir{%9|!SV)* z7TR@R@>Zn(o@z|9^it!_wR^ZebySNx3*RpZSb01#F|h^sSyygwQC^1VZ@A6PJtfRC z`~ki7U7@ot2*c{x#Bkd1lNI*zPlVLH^NuZd=JV6es6G}U(;`UKtLz$Yv+@FqarKuA z)z;Em0!YC&m>+IlgkMrLx!6_3l}8t)^yZ4_s+|d!Ve^lT-P8V_ z%E{GVzkWUAiOnsKxZoJoovnUv6D-1Yty?C*ws4$k% z!OK3=kFCST#^y7;*W-N_-#X3mD2Rb&xR=?qB3>Xc{zuw?+y268q@^HnKrhRK0t2-? z(WYgWvFoMk0Y5+5=f>p7ndZve4RLjyR;LkiPsAR$;|?2UA3M<1T(1*BiO``{#??d8 zwxfb|igh2QAk!{FBmEEdexJbC*b>c_XdrnO44^=nla#sx@5l5or`iy#_H6x4>~0O%5W!Nf8sgR|0Jz!z%jC^ zC@X8L5~FYUV^<^47w*TIE(&DJITXo}>I+o;uG-r=A1JMKGlx;CKRKSAd|FcCR*O4%Io2RuvGJM2 zu>{VVln~y>nN21fGtAH2?10mb1$?r7uFFp&jn8s{l2c;Xo{xq{_uV+8?@c^6s&Iy* z^$`auiB!-C(_U#m3PCVfo_g?No3ydZ&^W#%3BqR2_&ry>sL%MTLcc%o8MX=LO)hP1 zZSnWV;9iU`ftn;*>ikBLxuK$7yZl$v)J$53>A4gV%Yuhx8Fi_2!SY?%w1(ym~g+;_SyOVsTKf@fRN9 z`QwjfNuC_sdf(rJE4>5-#xbzJKcPiB6`_M%4)}>D%kP$&MIP`Y4)7V&Oa(ixsI9I2 zu;SyyapTkvEzT<|+|qUr#8GYr@f}(cUP0=I#P{#t+e5z%upqXu1k#4RRYDc3{U~BD z8MkeH68BLNWPwM9U2l`Te0;(m;@ALj^2U4qEr)kV0wAe~)fkAgtpMfMd;Fnce9>5+ z^(ov~j;yR~bncelTg7y@8(v;lezp67NTchAR?S|kicsZtBwa?mg5GJuYU}Y;^}O&H z*!o2#d-1TnFeO%FqHMHW+hoWdq{(WGifNb&5lx8<;WG4FT4GC@8$(Pq@*=kb3Ckj0 zev|_m%vM%l2F?rf-g3oi!_LfX(BI6=tkZl^^+F@*_KF6c(s zYFK2t60(EJx$v;C4j4H}YJX(ZbT1w!UKG%|I;E=~yfo%IRIGcHX8mV=d3jmcLib>^ z+h4$9drC;29NQ~@Rf3nV6{u{1eXV1ViTdLML8i%W<_jtX+A8UDeZX^<<7-ML89ot- z>xz_vW~^dzn_A@FW^9VKpjC;ZlT$gU0QaHouZys8e0p)?OsvI&E}3BnPUCLJnReIQoA~i8-hnu7_uch*&O0rWsz?{MCVLzRGMlQZsz(LP z1>I5iQlP|!Ti@PTa5Q8j1+3_EZ#gmzZ=aTR+)Vt)S_SeTG~zK|_J89|gvzAU=M}B3 z`(lVo)@dpHqvr5}xk63*_?zMlC#J%kTJCswZ=DrI<%GOw&{CrVnl)s+CkdAr_bePy z=XR^arAl1%@CJ8H$IU?_v%HsxdIc&s-=xRHyA;cUk{U{$1P-vXu5QJLMo5UQRh(^I zhO;am8;K-pNm;GUwRPj=Boe%q{zRyZ;;obTF5P@pAlFbjqMqwU}=Hm1nrlTrQh*K@A7uJe|yvRD7eB8cSMQTD|d` zhuG|~;cVeZkZm4aB;G?leUm@lY4S+6@}c+AM^7e)vGGKbFrpk}%^+KLwa}9UwZ^UB ziL3rUwcI>SAL+rWg8%sC1jaMh-fO6x1J0Qb9PnRFmrryawms**#*`#kw#??-ek~}| zWco8$dle!?b1L>Sf|>(XG^C!ltEKCjb-g9f+BN9Ao}FCy(RhU@c)9#~=$N*)hK9zW zv+oB80%#hD&cy(6k%KoT)5)~S77U&h+({1jj6-Ctn3-x91;}u+uEZHE7Ou(*c*DM3 zfHmX{A@)Q(#@^ooE|19})JdSP@tE$rul1dWds9tc8vjKod=2n?S*&^0t7Tjbut1Wx zx3`XDWvcg|%)(HwAZozE32>DlP-iqNi-4C9fU~;gn z8+)?Osdw}v?dhD$F@vFxxH zgS{W(UUn!eE2{?B=8c_dA~0F_`1r))ypXg^gpfhZI#M5lBo%5V#@K!XVrYX6(v73kBph#9xkyrm^>*jCov=DfWIsDP{O*FwG)2-{_O+(4W9$?%{bG~~x$MVO&fYTRf3UHShhnL>AMPtrfu|Ep;{i+&DxmLJn z?S8v>ER{F-9fTv6xsi8rnX;Y*nG0Wy+MSPu4O=s@7)3@N$YR1O;(oNVt&n7Bd^TNV zC4YFjL|sRB);B?92^Zs_P;_HMjnk1-{7@L#G~?uq!-!yB}A zLC&^?Weu*aD}0*4OD;K%M-nLxVwclL)aOSLObRTz<#h1@d^FV>afi3}M@d7)M@FTm zb+bNb$&kIu+}yR6u1-l)R4Zr``qj7TY=IbHC|fluew5s$n^7$tDNr>NE}+*$kgOjC z=brl55^KA9mDX;4|1~azJ|d!sQ|$6ia4DNUm!Ya;@5vfd-_G>(^oN!SFLq@C%4}Aw zbHMjW>@sG{`Ar}hN^9qFBJ%EPbpVO5T(_xr!`nN|Nb8R}uMB7Ikj<>R42#uT?_-{d zxG&SwLU<(+8lO1qxB-Hc81caNRUlBt4=yH)g)fR!-4PEhnOJ%PvM;PpaW+Wh1gvmk z-cyr~a7w3>?3|UWYp2nqhD8csf_BIsAzKKI<_(1wc0ItzQGNK6n{>*||?+V>>C( zU=4Gf=qq9}uFHPFPcpH53NsN#OytGb-oyLqB7|6Xd7nvCy;oiQAD(JPYe`oYnV^zF zIbWibXsT(|NoW)1U0Z-AVDYiGYUe{t!czGj?G#>EEL?BVBNk-ST-@1hOqMd9WwrA# z>0bXXTlMzl3A}HB;&l>pXn*T3h8KhKkpxgxJ`{FD(`-!5g;b)n>pF1D=O-AXg4%fMWB!6|)x>4M?+anU*1&(4kUQQDs|)BCmm z9xPcNhjg^KHQJj+H8FbQw7hMGBJY) zCdS?%)m3F%y#RGNwc}QY#xYmbn0>tcNkhEmM4W5>_+7FKHI`fN}hox3?+B4=} z6sMscU5#Ucd#H-<*zG!8I{>vDh|3SYd=+XdSl^9kWF8uH8-3M8*kvwdl)BL_K`w?J zG-8KMH73qFgU_F>Cn#Mua`xiC=hi1{UhX9x`?1oIt|-K%PZLpxgJv5127c zH4+G1z~e*cYMP4X78xBRM?ZfKxbi*gjl~kGqv*{Wjk8>5->E+xqh-D@=5X%MGhsUt zqeD-|{rPUcN8?6D-cJ7$l}(_JNzwY;5L&ww!j;Kx6FB_qB_j5okQit)^eMUB0ifyI zW^q{PnUDq|UR~A6trPlDIqJ`jpMte!P`MOkxylM4X)!T|UFT3a2F$q(lzdR9@rb^> zhJJNLt5}7Xy1^?3m@umJ>i@u#QTZ}#tUy>lKrs+^lLaPmn?UccXxznrKSD=n@6Ly6 zo3hI@A}jZ5bDHicLEkHhu*+?|lwmHF7p3vJQOEpe=THDHv*YCc zNdTwP)nw)jqAhaI%dYlZzpJ1Q{NtJFZ{H#%Js|hr!7+3-fvJDB#x-N}qLCEV{cB-) zr2z=C0E+c_7tK3r@i5h@cw6pPRR4vwI3(wquMV9q|}`0x+Y6-85VcbE&;y5fs3$~6;f4dGOF#?Y)f zdg_B3CeLI-`^U9S;8PMRKt?tKI#|)!$Viwjm^Ay03IZ?8ZHQ{Fv=7#Udy0 z5svMhf7_WHa9$cn+b?eH{$Zfiy`0N%i7-f4^DY>Grn^d|kmGGJ1rlTf?z5`uFF}5M z46{(lLR0MxDi5kp+4e%0i5zf9s-|xW-1yfxNf*&uptod| zc8}YM1R6wSQv3%(3jm@#t3T##p|*ZtS5S}3CG2>AcxzNSW-6%-dH#KPGLRsD@iv9S%@Ti5=OypR%ZV{~F7?1n%ePbg(*acN1*g{W3VOKS)D-&Sov z1)ai;CW77vYEh;2`-@HAJO79h+==yFE&0Qk81ARw-h7w^unG!rj0$QKU}vyEwRpb0 z^9g9Ao_qd5oY7%sW2#Y$k>%SJ0QfP;IX3;GH@TOI^fe54g*(yu=8cZj)Xp&)X>Cy9 zvJH|p#I2Z6|2=hH+0gRZ+@re8^EoLXCl|E0vA1MGy#wEIBGfaek#uI- zkFE>!^;w-_Z>R!gqUhBAK_#8Sof}0q!zJDp2%gH@7GbTvqS zf(yC5+)YB^4JexkmfY$j!uHfq-lm9@lDCHKYt5G<;YZKfw67VA4Z@?NkvSf|bF zJ%NJNbtvUe>f4f(182&C9`gFT+Puw+q{)Yc0lIilogmh;<6~?U0aQMfH8B5L>X=C^ z$oLtmW1*4e#Ftb1Yv3Dg667d^4@?_{?ktDW)uiNtGRgGLWtu0@y0Rei2A%?jpLbe@ zOx~Ci9i)_eU8J;hUyi6Z=V~J9YP`>9%`ljtg%F;mdn%=j7uV8ozFmt#Ut2mM;d zoDC#=19(M~(6bDtK7g+}y05HxOwA7N<=6F(Jd3Uyg>{{me#J6Qa;)Ow3`?wghoT za!9=%bZpDPYC~dc#eYx!*b!wh9p31Nsh*V=x+c^Cb~3epj5qAbCQvX(fR72SO&w)z z9~xI>j)@_*jrU$)zgbk!(n#+zMcFa4Hhu+MMJU>ww+*PaCGJn@z0+>l`ju{`6jD2 z8D3n<(aHroOXFcV_ugEOit(pqO2X-8I_g}sJ8vJuro z;5P914b|5a?}NrgAhj&UXQRvpC>2(1ij@p zvKgN2mg;yKah0Et*LGOo?E@9O#CQC4JbT}$aaf@V%R{W-2tdSP2yv^lqJ`{D3(T$a zjpqILtScXBIsgdLUV?rIYGCzl9rfm0zpekd#?+w`Zb^=gj&j+XHgAW`Z{{|=0HI$& zTG+V+S4F1FOPKGT1~{r{nMV~x;tv78Q=+QiNMf-H4-YrHbm>yob_Zg3JO2{Lfs|J2 zh&x)|nw7o5XghIA4`XiDT{7j4_8rWK@?mrUtzFMm(u+s{ZQRNU6dN8Z@iTrig@0Vi zCDH8NvWLw{peUz2b;`$(?d|QGjh5VU-<1yJ%6^Sig0k9+SFCc-&v=K+tC8ds8&5FW z11GP8ieDj7J4a>L_hg-4-)_eVStrw^Et1JC*-9)mrKR(i5!$&rd&!F_vV)xtYr3n0pa+?Gi7g2JwWRKjC8@z4X_0-8n(`02tv^P4$hB>Z+ ze+5-KGqVM-wV!rn*XAqg>pRV?(@j`@^-z7hb%LOem)GtC+;Cj=vX--r%?8a|Zbpas z*7a2A;N|X;sOV2m4doRCr7!($rW`&rVToYvCv7xJ^so-OrcLcf^Y}4$e2zB9D$QpD z_>Lq&uWv^-jVm`b=^0iWh|TdzV|F@);<-`O*bZ7WotsngWgK6VVBb*aqYIK5Db;<< zM~B1)tub-3&*k2J(2vcpKi$XEYP48*vuF4+<-i1BG;IMZpW~$28)#AJ-xxVlAeC$A zGuV_I&+hy51<=}34r=GDG7KFIdM>8!S8n^LBkYv{Qpi>cp^;|cF`kg7@B6Th@^HGy z#8J67qTU->cPkIs7Z_TQx9N!v)}$_b!Wqn*{ybpzUGlU$zg?aG-1~1Ll?5g&H%u1` ztC|AIM1<55>HOo$)k}nyGx((eBrRRd?K(Pf6AgL##IUcEEdjeTPTjX&&m^eAA`?xZ z*0lq1bM<)I~Fhp7rmkR4B@m*)F~n7G#w`W9zb5I2&N^YxTT#N+Lz)!?#HGhPRq-%i`oF zE`fzXz`6h6^wqP8ztD*3CmhMhK_xe!;%PYfB$tWinL?iud1-KZl$xh$6;sD5)qsNB zunO2JucAhPqjqMzJ9PcJHwcXS7LG+2MgL!TF|eE+5Q4gIs2eX{14fx43WWV!l6m-F?jsva81|^EEhRVX+%$Ah0C!sJ35n`Ea zAoU{19F#{Qn7Zqs&5aZI8xL4%?Vi}TdA**Wa&>i$KJ&RQ?D^&VF&#onfYfpU(CH26 zuu|LCnHD5_gZ_1@rda^}uYUe~O}WhFxA%=3p%;jzNO#oKeu|@*%+{GdT%xT-<%3Hi z{h-v#51}VyZPsxKNDai=*1c|78m%xmmxr`oECk~P($F*yhvps<4P8x4ecbNFr%4`fuS?YivHG#D@XGdpJ7;3evIF(_>xe0`HkR|VYKQl5y41V4V;f#;9#pcB zg4eJ0r5`YP0FAvsXt6Nn&$~Z&{Vuzc9J4#3*#@k98e|UUl`5H+3KeM|=j%r62(p5G ztxR$mMtOv89cLEO%4Oorlgb;geM_9!WtnGvuEj`emqhJ{8xj+3ZdWv)2FsubAb34Q zXDLTA?oAM(KSvtO2xPiVJ`PBKTqBH%m&h9!#2`5%GM^>Q>$PR&y?cD)!}_wXa59=W!=rLOc12=0|GbQ=vgU;M`)hzTZ1D3SGw}Y zXE68u7#9n#CAu$JDuy0CGJz^d_tplOU)u?;uqUQwgH@}}MJcqGKGj}^FX?N-jG=?I z0qJf#!M|E&&%6W&37Zq~KwT_AKze=go(uhLa8Z+Mi-j=L+%{99&Vwmd|7~Wvnvd5T z$Jr2%_#>FFQ>0mhL7^#0&O@Hd{PL1OUR>0?@AkD)&ICvlHLGUc)3e+%G2y6^WxxS! z6;8G17ukXvfeAOufBDkT+8I*|L~(KoFu?W`JaQ4qk_mL7=JF+6a-1p(-gGh`iE`^n zI7u>@(4)pdKgkRWg@RuZww!S>@zFtP5rZ9p=UkV2BxY zX^+|fR@8sNRRtU%cTb}B^YQ}qK)7dxho_R`aRA^Lsc2}`Jf2%NqRyb?tZ8e?1mtW0 z`m^HDG@!UIw376^019GG>;acUHW3sXpJS$aZ!dqjl?@<{+c-C`@7xFf1vxOk*Mi#Y(n~G?ZcRq6Arr( zI^3S6knHz@`dX_=DMR zZ$H1RR>97>-aBewbfc@~P_K%5X#O|?bHB6SpRjgY)5rio&C8fQV6j3fjj}=b9+{(&(>cnZ8F(^4oDsySVXl81r?po;aKqbJfYL=A!N#{(^F!4j5 z$v5YH{-p~*ZgCSDiFOLdG;uo|oYboCp~-|QeGsr!(f;u0MBG`aOyi57pQzI{=bo%^ zPc2=(oEanqSNU@pTB#r%j}4RP2fW7LSaK*LINP%Vprl~MH6|)mOtlWr-P>@f)|Nf3 zKLI3s94t=rc&mqP@gA=F`1r27Mcegs)y8dW;2uqI)zypErO)iS2V!|^rT4*{npLvF z)|Jkv1ND1?oM+yPZSznyf=G=yMu=^tGzEUVZzImnI{BTxlh~4{F~|P-);F;+wzSmg zj%Hk=jb*1Y`hAokuQF)enB1rhP*a7fmH^vu?1#{f+;H9cz3p1cr6tU7cnTb8JGBoc zt#BO4aU&Xu{%0ngT^k~H*O{pC^V7kTQn$OSyP#ost&H6$3;Wx6Pb0@TVujHQdz({K zNn$@ZB;-gNI6I&0rdO}dN|E1jeOVD(%Vjr-0k>r;3rY`2k5LXS1|1R245gSDW#(D6 z4#)T{9ycDj6gzmNC3BXz#o;Svd8Dvaoj?4d8aw5{ho$dH+Q9rBX-N=hp9NSy!rp(Q z{rJQ6s1KWZl6Wk<+?7~_Jh!F}&GvXZ4d=gjCmDMH@Gcr3I_!8dW-9N#>GiX>E>(ii z;S7e_78Vw+q9hWQik(17Tmk}TTH$%E$+O{}D7^I&3`>K?9G%Rf_u za<1?GDQdF?NjT#TKfn6S^P=?1`*m3VMIo36S{>o~j3YT7L|N0Y+!Edw&)&4Q`oZC0 z8uw$A1r|qYHJyk*KAw2LF5I7_S^goOsFCQYoJhKcIW&_DU8{V+aHZ_$`c%ZCh5VeuIWB5k$L8p1(3ID58Q=9<6d6G&Al z_$4odZbl&ejFhw9WcM9{%ToKaHvMn+yX^C?%0h=z?tyXzi#w7VrTOyZi&pl4|7VXl zyfaY3D0FZR_GU${-y4wKmjNV#1u?q0wRKg}OTb1%@IDxq!pwJ@Q7f|Md?g8S_=0*!c<6AaPjP#b3Wd^@sgv87X z$23A#CRdo>#Atxgb4=ZXxgAu(qZnnGgM$Nj_I>3apjmU)Kw^Zwf#WMt!_$k8?R)D~ zIPJ)PRas3>8XE%=um|zjZw2rD{`RuL;IYn9yY8iURWY_}H<%nmL)2FeYPi05T@>ik zMZFQusGhupU*ba~TBjM1@_qXG8rrtE6`7y?v_woRHVPS9H7R6$h>-sgdlTr*-G$2X z^tGwt<#ydx0N({7ezRPQzxpj_m!TJl=(O4iFKj9p&$-M)$Mxd0sW-5{B6qZ|+*~M_ zf4axgQ?Z{LaLX_tVB@H=q42656;1s3?c0GKLI@LhXKgf@<#R>BHMFb5wqEu=>xfM0 zwPP*f*m_0;m*KL%0A~Zn2c#){M~C6i$OJduAktmR5WLY77KtF{neP5^0eM#m7!kgX z5JzZGvBd+7H*f9&awSGt6BaZ(c#;m5_2ExjsQnLN0A=5GMJsv)s+gZ(K8JWS`(u~$VK(||NiTfQPfSf+efIXMke6aveLY1ezwv*EmG7Idq-1Rt&wDp>7=Gg6qkRH3 zubzk*27`EL38nz{x|^U@Wq#W=JE;;^`>z6DTe0(nVQNo-Ce1F>0Tcg)i_T8{;*j6fwdASIloDJxmGFqo`K2=fnp=?w2VVTrNNxN zJoyS@-zEx${B82+>g0=OFOH3X!Ru`R4<>>(_&wZ^ZP)kO0D`O__|mrx(ol44%Y#pU z3o2lb#amOJ&}DN&qdx|J%n;jOMngL?kwU5c*7_&uv_IJXMlRN-EX2bvQG z(E9>0vI6D6O={EbQCWZFoTQzb>K^IUUYJVCQ_`$j6alZI)R(!s(fAM-TNFT9o9eto z?8Z!qk~3yW73oqr@BCoe3x(m6;YXn^F5l*WVma$tNALX4e9~Pz;?-<^-k@ePtdd8t zx?SOM!3JuM*Bo`QcMN70L=c>5FRpuAcFQJJmr-D@CG9LizN$fHwI*O?*>z4WY63s? z)L__+Of1a;v()8blyZ}ap>SP~>LJ4UPx_+Q3!p*9VMyXLD=uyRzBLVaY9eph8JKKY z%>{&(0vJjfFlnFo5$sPpWPB$4<1v!y-cSna+#_ez*HsXf}9XoKFOo0qJ$t+pF(-z1uf-=(nS z2Hsv9-RA0K#UL$ms;<`KT#du<>f5gaR=Pp--Z7VRKLYrYrP31|K;#ZWs0r1xgFB|R zIEoboHEbH@x8}3>EnX-mkQQug?;06IQA_W%fHj=B1{IVihf>p}-J zH>3I!+(HeB7El)hNe=Z7;nCK9ew3=&GYJ+ZVF;oaY0`*t0R9!D9F$3f{P9qvCjvsf6#dV%?<;T~ip927z7Sd(|Dz8cPZ7=S#jz+)9wk7ezKzM6F!_jjv2w_59v2+2%0dGWc zIfrepsiQ3UHr(kt`r@0^EB5x=F}VCY91ReNCJKE58kuZ@jAwWzp)rU@W5@jxHyD&v zR64}Sp4sPFe9tpOX4WuuSxv>b0EtV6VUEN_plSW znOb{ZUY&I4-JdOaDP8|HhS~4zed=AXv(>voJgtHD_eY^GjWudoT6U*@;<2A~O`?a` zY(pK+0@;AEn(?22SFRG|uSn6N?tb-A2{KChm%#GwIdWfi5!i?K!kYO=JmxUf8~45d zlMA#DT+kXGUX6@a{Hh#xFL9Dam>w@9P97Tc1xA5Luc8BQd`0JMQqWsr>a1Pr%_HH{ z?=7==LK+~#+HF`_kj{YFqC0f+Oo#xenQ|j+p4lD~xHFTed(MmVqCVgui#|BwrWIGw zxiFqw?NyV#TpYl4Zov2NV}EDwKcNoH@%R-}IT@P=a!olh-E$Ey^)@*#4=~So3cckI zKT*fv&QVV$^w%J)f&*_%gCB{bgS%~cwYPHH=Ilit~2qJ~y;*uX`eDCv(>W9kU# zM%M>(p{3xJrT&awOw>8@0OpqD{OJ>-OHqjEx3{Sz_GEv!-r2rSKp?Gmax1 zXb?B$U|}|x*Q+@f^rmSc4>D#SUpaaOe9Y1E!ouDeL~+jf0P0hkd3t3aT2yB_h8 zery%1WuoC_Ig%)C6FzHCL6}54XhcQn$n)aL`*-trLSkHBfO;UBys~l_fQS|+LlO)- zb3tDAb#Uo9GwX!>qj47u>-GOkA0kgcY~SJ+U@mfGIL5s8gAgYFTiNDeVu_9bRRw=nNbCTxhjh&h;9Hd0hloIE%y^x5gpbdl8N z?{FJpDxoE>eOn$8rU)V(h&2SfMy1C8>o2SRfBOEH1FwD`9g|-xmo)ymu0wr>Ff=!) I(?{L^KM*q;(f|Me literal 0 HcmV?d00001 diff --git a/readability/data/RM-Type-Controls-Arrow.png b/readability/data/RM-Type-Controls-Arrow.png new file mode 100644 index 0000000..dbb31a6 --- /dev/null +++ b/readability/data/RM-Type-Controls-Arrow.png @@ -0,0 +1 @@ +data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiPz4KPCEtLSBUaGlzIFNvdXJjZSBDb2RlIEZvcm0gaXMgc3ViamVjdCB0byB0aGUgdGVybXMgb2YgdGhlIE1vemlsbGEgUHVibGljCiAgIC0gTGljZW5zZSwgdi4gMi4wLiBJZiBhIGNvcHkgb2YgdGhlIE1QTCB3YXMgbm90IGRpc3RyaWJ1dGVkIHdpdGggdGhpcwogICAtIGZpbGUsIFlvdSBjYW4gb2J0YWluIG9uZSBhdCBodHRwOi8vbW96aWxsYS5vcmcvTVBMLzIuMC8uIC0tPgo8c3ZnIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgdmlld0JveD0iMCAwIDI0IDI0Ij4KICA8cG9seWdvbiBvcGFjaXR5PSIwLjE1IiBwb2ludHM9IjE2LjU4MywwLjAxNSAxNi41NjksMCA0LjU4MywxMiAxNi41NjksMjQgMTYuNTgzLDIzLjk4NSIvPgogIDxwb2x5Z29uIGZpbGw9IiNmYmZiZmIiIHBvaW50cz0iMTYuNTc1LDEuMDIxIDE2LjU2MSwxLjAwOCA1LjU4MywxMiAxNi41NzcsMjMuMDA4IDE2LjU5MSwyMi45OTQgIi8+Cjwvc3ZnPg== \ No newline at end of file diff --git a/readability/data/RM-close-hover.png b/readability/data/RM-close-hover.png new file mode 100644 index 0000000000000000000000000000000000000000..4303e8576d2609dce250cd83d9616ee68400e942 GIT binary patch literal 8466 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 TKfqrhVA$DHnkO@ln_T@r%9Ovp literal 0 HcmV?d00001 diff --git a/readability/data/RM-close.png b/readability/data/RM-close.png new file mode 100644 index 0000000000000000000000000000000000000000..cc1fba3a912697e7270e6b7bebf1873f05fecbbb GIT binary patch literal 8010 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 F{{Z-qq~ZVo literal 0 HcmV?d00001 diff --git a/readability/data/Readability.js b/readability/data/Readability.js new file mode 100644 index 0000000..f8c12a5 --- /dev/null +++ b/readability/data/Readability.js @@ -0,0 +1,1835 @@ +/*eslint-env es6:false*/ +/* + * Copyright (c) 2010 Arc90 Inc + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* + * This code is heavily based on Arc90's readability.js (1.7.1) script + * available at: http://code.google.com/p/arc90labs-readability + */ +var root = this; + +/** + * Public constructor. + * @param {Object} uri The URI descriptor object. + * @param {HTMLDocument} doc The document to parse. + * @param {Object} options The options object. + */ +var Readability = function(uri, doc, options) { + options = options || {}; + + this._uri = uri; + this._doc = doc; + this._biggestFrame = false; + this._articleByline = null; + this._articleDir = null; + + // Configureable options + this._debug = !!options.debug; + this._maxElemsToParse = options.maxElemsToParse || this.DEFAULT_MAX_ELEMS_TO_PARSE; + this._nbTopCandidates = options.nbTopCandidates || this.DEFAULT_N_TOP_CANDIDATES; + this._maxPages = options.maxPages || this.DEFAULT_MAX_PAGES; + + // Start with all flags set + this._flags = this.FLAG_STRIP_UNLIKELYS | + this.FLAG_WEIGHT_CLASSES | + this.FLAG_CLEAN_CONDITIONALLY; + + // The list of pages we've parsed in this call of readability, + // for autopaging. As a key store for easier searching. + this._parsedPages = {}; + + // A list of the ETag headers of pages we've parsed, in case they happen to match, + // we'll know it's a duplicate. + this._pageETags = {}; + + // Make an AJAX request for each page and append it to the document. + this._curPageNum = 1; + + var logEl; + + // Control whether log messages are sent to the console + if (this._debug) { + logEl = function(e) { + var rv = e.nodeName + " "; + if (e.nodeType == e.TEXT_NODE) { + return rv + '("' + e.textContent + '")'; + } + var classDesc = e.className && ("." + e.className.replace(/ /g, ".")); + var elDesc = ""; + if (e.id) + elDesc = "(#" + e.id + classDesc + ")"; + else if (classDesc) + elDesc = "(" + classDesc + ")"; + return rv + elDesc; + }; + this.log = function () { + if ("dump" in root) { + var msg = Array.prototype.map.call(arguments, function(x) { + return (x && x.nodeName) ? logEl(x) : x; + }).join(" "); + dump("Reader: (Readability) " + msg + "\n"); + } else if ("console" in root) { + var args = ["Reader: (Readability) "].concat(arguments); + console.log.apply(console, args); + } + }; + } else { + this.log = function () {}; + } +} + +Readability.prototype = { + FLAG_STRIP_UNLIKELYS: 0x1, + FLAG_WEIGHT_CLASSES: 0x2, + FLAG_CLEAN_CONDITIONALLY: 0x4, + + // Max number of nodes supported by this parser. Default: 0 (no limit) + DEFAULT_MAX_ELEMS_TO_PARSE: 0, + + // The number of top candidates to consider when analysing how + // tight the competition is among candidates. + DEFAULT_N_TOP_CANDIDATES: 5, + + // The maximum number of pages to loop through before we call + // it quits and just show a link. + DEFAULT_MAX_PAGES: 5, + + // Element tags to score by default. + DEFAULT_TAGS_TO_SCORE: "section,h2,h3,h4,h5,h6,p,td,pre".toUpperCase().split(","), + + // All of the regular expressions in use within readability. + // Defined up here so we don't instantiate them repeatedly in loops. + REGEXPS: { + unlikelyCandidates: /banner|combx|comment|community|disqus|extra|foot|header|menu|modal|related|remark|rss|share|shoutbox|sidebar|skyscraper|sponsor|ad-break|agegate|pagination|pager|popup/i, + okMaybeItsACandidate: /and|article|body|column|main|shadow/i, + positive: /article|body|content|entry|hentry|main|page|pagination|post|text|blog|story/i, + negative: /hidden|^hid$| hid$| hid |^hid |banner|combx|comment|com-|contact|foot|footer|footnote|masthead|media|meta|modal|outbrain|promo|related|scroll|share|shoutbox|sidebar|skyscraper|sponsor|shopping|tags|tool|widget/i, + extraneous: /print|archive|comment|discuss|e[\-]?mail|share|reply|all|login|sign|single|utility/i, + byline: /byline|author|dateline|writtenby/i, + replaceFonts: /<(\/?)font[^>]*>/gi, + normalize: /\s{2,}/g, + videos: /\/\/(www\.)?(dailymotion|youtube|youtube-nocookie|player\.vimeo)\.com/i, + nextLink: /(next|weiter|continue|>([^\|]|$)|ยป([^\|]|$))/i, + prevLink: /(prev|earl|old|new|<|ยซ)/i, + whitespace: /^\s*$/, + hasContent: /\S$/, + }, + + DIV_TO_P_ELEMS: [ "A", "BLOCKQUOTE", "DL", "DIV", "IMG", "OL", "P", "PRE", "TABLE", "UL", "SELECT" ], + + ALTER_TO_DIV_EXCEPTIONS: ["DIV", "ARTICLE", "SECTION", "P"], + + /** + * Run any post-process modifications to article content as necessary. + * + * @param Element + * @return void + **/ + _postProcessContent: function(articleContent) { + // Readability cannot open relative uris so we convert them to absolute uris. + this._fixRelativeUris(articleContent); + }, + + /** + * Iterate over a NodeList, which doesn't natively fully implement the Array + * interface. + * + * For convenience, the current object context is applied to the provided + * iterate function. + * + * @param NodeList nodeList The NodeList. + * @param Function fn The iterate function. + * @return void + */ + _forEachNode: function(nodeList, fn) { + return Array.prototype.forEach.call(nodeList, fn, this); + }, + + /** + * Iterate over a NodeList, return true if any of the provided iterate + * function calls returns true, false otherwise. + * + * For convenience, the current object context is applied to the + * provided iterate function. + * + * @param NodeList nodeList The NodeList. + * @param Function fn The iterate function. + * @return Boolean + */ + _someNode: function(nodeList, fn) { + return Array.prototype.some.call(nodeList, fn, this); + }, + + /** + * Concat all nodelists passed as arguments. + * + * @return ...NodeList + * @return Array + */ + _concatNodeLists: function() { + var slice = Array.prototype.slice; + var args = slice.call(arguments); + var nodeLists = args.map(function(list) { + return slice.call(list); + }); + return Array.prototype.concat.apply([], nodeLists); + }, + + _getAllNodesWithTag: function(node, tagNames) { + if (node.querySelectorAll) { + return node.querySelectorAll(tagNames.join(',')); + } + return [].concat.apply([], tagNames.map(function(tag) { + return node.getElementsByTagName(tag); + })); + }, + + /** + * Converts each and uri in the given element to an absolute URI, + * ignoring #ref URIs. + * + * @param Element + * @return void + */ + _fixRelativeUris: function(articleContent) { + var scheme = this._uri.scheme; + var prePath = this._uri.prePath; + var pathBase = this._uri.pathBase; + + function toAbsoluteURI(uri) { + // If this is already an absolute URI, return it. + if (/^[a-zA-Z][a-zA-Z0-9\+\-\.]*:/.test(uri)) + return uri; + + // Scheme-rooted relative URI. + if (uri.substr(0, 2) == "//") + return scheme + "://" + uri.substr(2); + + // Prepath-rooted relative URI. + if (uri[0] == "/") + return prePath + uri; + + // Dotslash relative URI. + if (uri.indexOf("./") === 0) + return pathBase + uri.slice(2); + + // Ignore hash URIs: + if (uri[0] == "#") + return uri; + + // Standard relative URI; add entire path. pathBase already includes a + // trailing "/". + return pathBase + uri; + } + + var links = articleContent.getElementsByTagName("a"); + this._forEachNode(links, function(link) { + var href = link.getAttribute("href"); + if (href) { + // Replace links with javascript: URIs with text content, since + // they won't work after scripts have been removed from the page. + if (href.indexOf("javascript:") === 0) { + var text = this._doc.createTextNode(link.textContent); + link.parentNode.replaceChild(text, link); + } else { + link.setAttribute("href", toAbsoluteURI(href)); + } + } + }); + + var imgs = articleContent.getElementsByTagName("img"); + this._forEachNode(imgs, function(img) { + var src = img.getAttribute("src"); + if (src) { + img.setAttribute("src", toAbsoluteURI(src)); + } + }); + }, + + /** + * Get the article title as an H1. + * + * @return void + **/ + _getArticleTitle: function() { + var doc = this._doc; + var curTitle = ""; + var origTitle = ""; + + try { + curTitle = origTitle = doc.title; + + // If they had an element with id "title" in their HTML + if (typeof curTitle !== "string") + curTitle = origTitle = this._getInnerText(doc.getElementsByTagName('title')[0]); + } catch(e) {} + + if (curTitle.match(/ [\|\-] /)) { + curTitle = origTitle.replace(/(.*)[\|\-] .*/gi,'$1'); + + if (curTitle.split(' ').length < 3) + curTitle = origTitle.replace(/[^\|\-]*[\|\-](.*)/gi,'$1'); + } else if (curTitle.indexOf(': ') !== -1) { + // Check if we have an heading containing this exact string, so we + // could assume it's the full title. + var headings = this._concatNodeLists( + doc.getElementsByTagName('h1'), + doc.getElementsByTagName('h2') + ); + var match = this._someNode(headings, function(heading) { + return heading.textContent === curTitle; + }); + + // If we don't, let's extract the title out of the original title string. + if (!match) { + curTitle = origTitle.substring(origTitle.lastIndexOf(':') + 1); + + // If the title is now too short, try the first colon instead: + if (curTitle.split(' ').length < 3) + curTitle = origTitle.substring(origTitle.indexOf(':') + 1); + } + } else if (curTitle.length > 150 || curTitle.length < 15) { + var hOnes = doc.getElementsByTagName('h1'); + + if (hOnes.length === 1) + curTitle = this._getInnerText(hOnes[0]); + } + + curTitle = curTitle.trim(); + + if (curTitle.split(' ').length <= 4) + curTitle = origTitle; + + return curTitle; + }, + + /** + * Prepare the HTML document for readability to scrape it. + * This includes things like stripping javascript, CSS, and handling terrible markup. + * + * @return void + **/ + _prepDocument: function() { + var doc = this._doc; + + // Remove all style tags in head + this._forEachNode(doc.getElementsByTagName("style"), function(styleNode) { + styleNode.parentNode.removeChild(styleNode); + }); + + if (doc.body) { + this._replaceBrs(doc.body); + } + + this._forEachNode(doc.getElementsByTagName("font"), function(fontNode) { + this._setNodeTag(fontNode, "SPAN"); + }); + }, + + /** + * Finds the next element, starting from the given node, and ignoring + * whitespace in between. If the given node is an element, the same node is + * returned. + */ + _nextElement: function (node) { + var next = node; + while (next + && (next.nodeType != Node.ELEMENT_NODE) + && this.REGEXPS.whitespace.test(next.textContent)) { + next = next.nextSibling; + } + return next; + }, + + /** + * Replaces 2 or more successive
elements with a single

. + * Whitespace between
elements are ignored. For example: + *

+ * will become: + *
foo
bar

abc

+ */ + _replaceBrs: function (elem) { + this._forEachNode(elem.getElementsByTagName("br"), function(br) { + var next = br.nextSibling; + + // Whether 2 or more
elements have been found and replaced with a + //

block. + var replaced = false; + + // If we find a
chain, remove the
s until we hit another element + // or non-whitespace. This leaves behind the first
in the chain + // (which will be replaced with a

later). + while ((next = this._nextElement(next)) && (next.tagName == "BR")) { + replaced = true; + var brSibling = next.nextSibling; + next.parentNode.removeChild(next); + next = brSibling; + } + + // If we removed a
chain, replace the remaining
with a

. Add + // all sibling nodes as children of the

until we hit another
+ // chain. + if (replaced) { + var p = this._doc.createElement("p"); + br.parentNode.replaceChild(p, br); + + next = p.nextSibling; + while (next) { + // If we've hit another

, we're done adding children to this

. + if (next.tagName == "BR") { + var nextElem = this._nextElement(next); + if (nextElem && nextElem.tagName == "BR") + break; + } + + // Otherwise, make this node a child of the new

. + var sibling = next.nextSibling; + p.appendChild(next); + next = sibling; + } + } + }); + }, + + _setNodeTag: function (node, tag) { + this.log("_setNodeTag", node, tag); + if (node.__JSDOMParser__) { + node.localName = tag.toLowerCase(); + node.tagName = tag.toUpperCase(); + return node; + } + + var replacement = node.ownerDocument.createElement(tag); + while (node.firstChild) { + replacement.appendChild(node.firstChild); + } + node.parentNode.replaceChild(replacement, node); + if (node.readability) + replacement.readability = node.readability; + + for (var i = 0; i < node.attributes.length; i++) { + replacement.setAttribute(node.attributes[i].name, node.attributes[i].value); + } + return replacement; + }, + + /** + * Prepare the article node for display. Clean out any inline styles, + * iframes, forms, strip extraneous

tags, etc. + * + * @param Element + * @return void + **/ + _prepArticle: function(articleContent) { + this._cleanStyles(articleContent); + + // Clean out junk from the article content + this._cleanConditionally(articleContent, "form"); + this._clean(articleContent, "object"); + this._clean(articleContent, "embed"); + this._clean(articleContent, "h1"); + this._clean(articleContent, "footer"); + + // If there is only one h2, they are probably using it as a header + // and not a subheader, so remove it since we already have a header. + if (articleContent.getElementsByTagName('h2').length === 1) + this._clean(articleContent, "h2"); + + this._clean(articleContent, "iframe"); + this._cleanHeaders(articleContent); + + // Do these last as the previous stuff may have removed junk + // that will affect these + this._cleanConditionally(articleContent, "table"); + this._cleanConditionally(articleContent, "ul"); + this._cleanConditionally(articleContent, "div"); + + // Remove extra paragraphs + this._forEachNode(articleContent.getElementsByTagName('p'), function(paragraph) { + var imgCount = paragraph.getElementsByTagName('img').length; + var embedCount = paragraph.getElementsByTagName('embed').length; + var objectCount = paragraph.getElementsByTagName('object').length; + // At this point, nasty iframes have been removed, only remain embedded video ones. + var iframeCount = paragraph.getElementsByTagName('iframe').length; + var totalCount = imgCount + embedCount + objectCount + iframeCount; + + if (totalCount === 0 && !this._getInnerText(paragraph, false)) + paragraph.parentNode.removeChild(paragraph); + }); + + this._forEachNode(articleContent.getElementsByTagName("br"), function(br) { + var next = this._nextElement(br.nextSibling); + if (next && next.tagName == "P") + br.parentNode.removeChild(br); + }); + }, + + /** + * Initialize a node with the readability object. Also checks the + * className/id for special names to add to its score. + * + * @param Element + * @return void + **/ + _initializeNode: function(node) { + node.readability = {"contentScore": 0}; + + switch(node.tagName) { + case 'DIV': + node.readability.contentScore += 5; + break; + + case 'PRE': + case 'TD': + case 'BLOCKQUOTE': + node.readability.contentScore += 3; + break; + + case 'ADDRESS': + case 'OL': + case 'UL': + case 'DL': + case 'DD': + case 'DT': + case 'LI': + case 'FORM': + node.readability.contentScore -= 3; + break; + + case 'H1': + case 'H2': + case 'H3': + case 'H4': + case 'H5': + case 'H6': + case 'TH': + node.readability.contentScore -= 5; + break; + } + + node.readability.contentScore += this._getClassWeight(node); + }, + + _removeAndGetNext: function(node) { + var nextNode = this._getNextNode(node, true); + node.parentNode.removeChild(node); + return nextNode; + }, + + /** + * Traverse the DOM from node to node, starting at the node passed in. + * Pass true for the second parameter to indicate this node itself + * (and its kids) are going away, and we want the next node over. + * + * Calling this in a loop will traverse the DOM depth-first. + */ + _getNextNode: function(node, ignoreSelfAndKids) { + // First check for kids if those aren't being ignored + if (!ignoreSelfAndKids && node.firstElementChild) { + return node.firstElementChild; + } + // Then for siblings... + if (node.nextElementSibling) { + return node.nextElementSibling; + } + // And finally, move up the parent chain *and* find a sibling + // (because this is depth-first traversal, we will have already + // seen the parent nodes themselves). + do { + node = node.parentNode; + } while (node && !node.nextElementSibling); + return node && node.nextElementSibling; + }, + + /** + * Like _getNextNode, but for DOM implementations with no + * firstElementChild/nextElementSibling functionality... + */ + _getNextNodeNoElementProperties: function(node, ignoreSelfAndKids) { + function nextSiblingEl(n) { + do { + n = n.nextSibling; + } while (n && n.nodeType !== n.ELEMENT_NODE); + return n; + } + // First check for kids if those aren't being ignored + if (!ignoreSelfAndKids && node.children[0]) { + return node.children[0]; + } + // Then for siblings... + var next = nextSiblingEl(node); + if (next) { + return next; + } + // And finally, move up the parent chain *and* find a sibling + // (because this is depth-first traversal, we will have already + // seen the parent nodes themselves). + do { + node = node.parentNode; + if (node) + next = nextSiblingEl(node); + } while (node && !next); + return node && next; + }, + + _checkByline: function(node, matchString) { + if (this._articleByline) { + return false; + } + + if (node.getAttribute !== undefined) { + var rel = node.getAttribute("rel"); + } + + if ((rel === "author" || this.REGEXPS.byline.test(matchString)) && this._isValidByline(node.textContent)) { + this._articleByline = node.textContent.trim(); + return true; + } + + return false; + }, + + _getNodeAncestors: function(node, maxDepth) { + maxDepth = maxDepth || 0; + var i = 0, ancestors = []; + while (node.parentNode) { + ancestors.push(node.parentNode) + if (maxDepth && ++i === maxDepth) + break; + node = node.parentNode; + } + return ancestors; + }, + + /*** + * grabArticle - Using a variety of metrics (content score, classname, element types), find the content that is + * most likely to be the stuff a user wants to read. Then return it wrapped up in a div. + * + * @param page a document to run upon. Needs to be a full document, complete with body. + * @return Element + **/ + _grabArticle: function (page) { + this.log("**** grabArticle ****"); + var doc = this._doc; + var isPaging = (page !== null ? true: false); + page = page ? page : this._doc.body; + + // We can't grab an article if we don't have a page! + if (!page) { + this.log("No body found in document. Abort."); + return null; + } + + var pageCacheHtml = page.innerHTML; + + // Check if any "dir" is set on the toplevel document element + this._articleDir = doc.documentElement.getAttribute("dir"); + + while (true) { + var stripUnlikelyCandidates = this._flagIsActive(this.FLAG_STRIP_UNLIKELYS); + + // First, node prepping. Trash nodes that look cruddy (like ones with the + // class name "comment", etc), and turn divs into P tags where they have been + // used inappropriately (as in, where they contain no other block level elements.) + var elementsToScore = []; + var node = this._doc.documentElement; + + while (node) { + var matchString = node.className + " " + node.id; + + // Check to see if this node is a byline, and remove it if it is. + if (this._checkByline(node, matchString)) { + node = this._removeAndGetNext(node); + continue; + } + + // Remove unlikely candidates + if (stripUnlikelyCandidates) { + if (this.REGEXPS.unlikelyCandidates.test(matchString) && + !this.REGEXPS.okMaybeItsACandidate.test(matchString) && + node.tagName !== "BODY" && + node.tagName !== "A") { + this.log("Removing unlikely candidate - " + matchString); + node = this._removeAndGetNext(node); + continue; + } + } + + if (this.DEFAULT_TAGS_TO_SCORE.indexOf(node.tagName) !== -1) { + elementsToScore.push(node); + } + + // Turn all divs that don't have children block level elements into p's + if (node.tagName === "DIV") { + // Sites like http://mobile.slate.com encloses each paragraph with a DIV + // element. DIVs with only a P element inside and no text content can be + // safely converted into plain P elements to avoid confusing the scoring + // algorithm with DIVs with are, in practice, paragraphs. + if (this._hasSinglePInsideElement(node)) { + var newNode = node.children[0]; + node.parentNode.replaceChild(newNode, node); + node = newNode; + } else if (!this._hasChildBlockElement(node)) { + node = this._setNodeTag(node, "P"); + elementsToScore.push(node); + } else { + // EXPERIMENTAL + this._forEachNode(node.childNodes, function(childNode) { + if (childNode.nodeType === Node.TEXT_NODE) { + var p = doc.createElement('p'); + p.textContent = childNode.textContent; + p.style.display = 'inline'; + p.className = 'readability-styled'; + node.replaceChild(p, childNode); + } + }); + } + } + node = this._getNextNode(node); + } + + /** + * Loop through all paragraphs, and assign a score to them based on how content-y they look. + * Then add their score to their parent node. + * + * A score is determined by things like number of commas, class names, etc. Maybe eventually link density. + **/ + var candidates = []; + this._forEachNode(elementsToScore, function(elementToScore) { + if (!elementToScore.parentNode || typeof(elementToScore.parentNode.tagName) === 'undefined') + return; + + // If this paragraph is less than 25 characters, don't even count it. + var innerText = this._getInnerText(elementToScore); + if (innerText.length < 25) + return; + + // Exclude nodes with no ancestor. + var ancestors = this._getNodeAncestors(elementToScore, 3); + if (ancestors.length === 0) + return; + + var contentScore = 0; + + // Add a point for the paragraph itself as a base. + contentScore += 1; + + // Add points for any commas within this paragraph. + contentScore += innerText.split(',').length; + + // For every 100 characters in this paragraph, add another point. Up to 3 points. + contentScore += Math.min(Math.floor(innerText.length / 100), 3); + + // Initialize and score ancestors. + this._forEachNode(ancestors, function(ancestor, level) { + if (!ancestor.tagName) + return; + + if (typeof(ancestor.readability) === 'undefined') { + this._initializeNode(ancestor); + candidates.push(ancestor); + } + + // Node score divider: + // - parent: 1 (no division) + // - grandparent: 2 + // - great grandparent+: ancestor level * 3 + if (level === 0) + var scoreDivider = 1; + else if (level === 1) + scoreDivider = 2; + else + scoreDivider = level * 3; + ancestor.readability.contentScore += contentScore / scoreDivider; + }); + }); + + // After we've calculated scores, loop through all of the possible + // candidate nodes we found and find the one with the highest score. + var topCandidates = []; + for (var c = 0, cl = candidates.length; c < cl; c += 1) { + var candidate = candidates[c]; + + // Scale the final candidates score based on link density. Good content + // should have a relatively small link density (5% or less) and be mostly + // unaffected by this operation. + var candidateScore = candidate.readability.contentScore * (1 - this._getLinkDensity(candidate)); + candidate.readability.contentScore = candidateScore; + + this.log('Candidate:', candidate, "with score " + candidateScore); + + for (var t = 0; t < this._nbTopCandidates; t++) { + var aTopCandidate = topCandidates[t]; + + if (!aTopCandidate || candidateScore > aTopCandidate.readability.contentScore) { + topCandidates.splice(t, 0, candidate); + if (topCandidates.length > this._nbTopCandidates) + topCandidates.pop(); + break; + } + } + } + + var topCandidate = topCandidates[0] || null; + var neededToCreateTopCandidate = false; + + // If we still have no top candidate, just use the body as a last resort. + // We also have to copy the body node so it is something we can modify. + if (topCandidate === null || topCandidate.tagName === "BODY") { + // Move all of the page's children into topCandidate + topCandidate = doc.createElement("DIV"); + neededToCreateTopCandidate = true; + // Move everything (not just elements, also text nodes etc.) into the container + // so we even include text directly in the body: + var kids = page.childNodes; + while (kids.length) { + this.log("Moving child out:", kids[0]); + topCandidate.appendChild(kids[0]); + } + + page.appendChild(topCandidate); + + this._initializeNode(topCandidate); + } else if (topCandidate) { + // Because of our bonus system, parents of candidates might have scores + // themselves. They get half of the node. There won't be nodes with higher + // scores than our topCandidate, but if we see the score going *up* in the first + // few steps up the tree, that's a decent sign that there might be more content + // lurking in other places that we want to unify in. The sibling stuff + // below does some of that - but only if we've looked high enough up the DOM + // tree. + var parentOfTopCandidate = topCandidate.parentNode; + var lastScore = topCandidate.readability.contentScore; + // The scores shouldn't get too low. + var scoreThreshold = lastScore / 3; + while (parentOfTopCandidate && parentOfTopCandidate.readability) { + var parentScore = parentOfTopCandidate.readability.contentScore; + if (parentScore < scoreThreshold) + break; + if (parentScore > lastScore) { + // Alright! We found a better parent to use. + topCandidate = parentOfTopCandidate; + break; + } + lastScore = parentOfTopCandidate.readability.contentScore; + parentOfTopCandidate = parentOfTopCandidate.parentNode; + } + } + + // Now that we have the top candidate, look through its siblings for content + // that might also be related. Things like preambles, content split by ads + // that we removed, etc. + var articleContent = doc.createElement("DIV"); + if (isPaging) + articleContent.id = "readability-content"; + + var siblingScoreThreshold = Math.max(10, topCandidate.readability.contentScore * 0.2); + var siblings = topCandidate.parentNode.children; + + for (var s = 0, sl = siblings.length; s < sl; s++) { + var sibling = siblings[s]; + var append = false; + + this.log("Looking at sibling node:", sibling, sibling.readability ? ("with score " + sibling.readability.contentScore) : ''); + this.log("Sibling has score", sibling.readability ? sibling.readability.contentScore : 'Unknown'); + + if (sibling === topCandidate) { + append = true; + } else { + var contentBonus = 0; + + // Give a bonus if sibling nodes and top candidates have the example same classname + if (sibling.className === topCandidate.className && topCandidate.className !== "") + contentBonus += topCandidate.readability.contentScore * 0.2; + + if (sibling.readability && + ((sibling.readability.contentScore + contentBonus) >= siblingScoreThreshold)) { + append = true; + } else if (sibling.nodeName === "P") { + var linkDensity = this._getLinkDensity(sibling); + var nodeContent = this._getInnerText(sibling); + var nodeLength = nodeContent.length; + + if (nodeLength > 80 && linkDensity < 0.25) { + append = true; + } else if (nodeLength < 80 && nodeLength > 0 && linkDensity === 0 && + nodeContent.search(/\.( |$)/) !== -1) { + append = true; + } + } + } + + if (append) { + this.log("Appending node:", sibling); + + if (this.ALTER_TO_DIV_EXCEPTIONS.indexOf(sibling.nodeName) === -1) { + // We have a node that isn't a common block level element, like a form or td tag. + // Turn it into a div so it doesn't get filtered out later by accident. + this.log("Altering sibling:", sibling, 'to div.'); + + sibling = this._setNodeTag(sibling, "DIV"); + } + + articleContent.appendChild(sibling); + // siblings is a reference to the children array, and + // sibling is removed from the array when we call appendChild(). + // As a result, we must revisit this index since the nodes + // have been shifted. + s -= 1; + sl -= 1; + } + } + + if (this._debug) + this.log("Article content pre-prep: " + articleContent.innerHTML); + // So we have all of the content that we need. Now we clean it up for presentation. + this._prepArticle(articleContent); + if (this._debug) + this.log("Article content post-prep: " + articleContent.innerHTML); + + if (this._curPageNum === 1) { + if (neededToCreateTopCandidate) { + // We already created a fake div thing, and there wouldn't have been any siblings left + // for the previous loop, so there's no point trying to create a new div, and then + // move all the children over. Just assign IDs and class names here. No need to append + // because that already happened anyway. + topCandidate.id = "readability-page-1"; + topCandidate.className = "page"; + } else { + var div = doc.createElement("DIV"); + div.id = "readability-page-1"; + div.className = "page"; + var children = articleContent.childNodes; + while (children.length) { + div.appendChild(children[0]); + } + articleContent.appendChild(div); + } + } + + if (this._debug) + this.log("Article content after paging: " + articleContent.innerHTML); + + // Now that we've gone through the full algorithm, check to see if + // we got any meaningful content. If we didn't, we may need to re-run + // grabArticle with different flags set. This gives us a higher likelihood of + // finding the content, and the sieve approach gives us a higher likelihood of + // finding the -right- content. + if (this._getInnerText(articleContent, true).length < 500) { + page.innerHTML = pageCacheHtml; + + if (this._flagIsActive(this.FLAG_STRIP_UNLIKELYS)) { + this._removeFlag(this.FLAG_STRIP_UNLIKELYS); + } else if (this._flagIsActive(this.FLAG_WEIGHT_CLASSES)) { + this._removeFlag(this.FLAG_WEIGHT_CLASSES); + } else if (this._flagIsActive(this.FLAG_CLEAN_CONDITIONALLY)) { + this._removeFlag(this.FLAG_CLEAN_CONDITIONALLY); + } else { + return null; + } + } else { + return articleContent; + } + } + }, + + /** + * Check whether the input string could be a byline. + * This verifies that the input is a string, and that the length + * is less than 100 chars. + * + * @param possibleByline {string} - a string to check whether its a byline. + * @return Boolean - whether the input string is a byline. + */ + _isValidByline: function(byline) { + if (typeof byline == 'string' || byline instanceof String) { + byline = byline.trim(); + return (byline.length > 0) && (byline.length < 100); + } + return false; + }, + + /** + * Attempts to get excerpt and byline metadata for the article. + * + * @return Object with optional "excerpt" and "byline" properties + */ + _getArticleMetadata: function() { + var metadata = {}; + var values = {}; + var metaElements = this._doc.getElementsByTagName("meta"); + + // Match "description", or Twitter's "twitter:description" (Cards) + // in name attribute. + var namePattern = /^\s*((twitter)\s*:\s*)?(description|title)\s*$/gi; + + // Match Facebook's Open Graph title & description properties. + var propertyPattern = /^\s*og\s*:\s*(description|title)\s*$/gi; + + // Find description tags. + this._forEachNode(metaElements, function(element) { + var elementName = element.getAttribute("name"); + var elementProperty = element.getAttribute("property"); + + if ([elementName, elementProperty].indexOf("author") !== -1) { + metadata.byline = element.getAttribute("content"); + return; + } + + var name = null; + if (namePattern.test(elementName)) { + name = elementName; + } else if (propertyPattern.test(elementProperty)) { + name = elementProperty; + } + + if (name) { + var content = element.getAttribute("content"); + if (content) { + // Convert to lowercase and remove any whitespace + // so we can match below. + name = name.toLowerCase().replace(/\s/g, ''); + values[name] = content.trim(); + } + } + }); + + if ("description" in values) { + metadata.excerpt = values["description"]; + } else if ("og:description" in values) { + // Use facebook open graph description. + metadata.excerpt = values["og:description"]; + } else if ("twitter:description" in values) { + // Use twitter cards description. + metadata.excerpt = values["twitter:description"]; + } + + if ("og:title" in values) { + // Use facebook open graph title. + metadata.title = values["og:title"]; + } else if ("twitter:title" in values) { + // Use twitter cards title. + metadata.title = values["twitter:title"]; + } + + return metadata; + }, + + /** + * Removes script tags from the document. + * + * @param Element + **/ + _removeScripts: function(doc) { + this._forEachNode(doc.getElementsByTagName('script'), function(scriptNode) { + scriptNode.nodeValue = ""; + scriptNode.removeAttribute('src'); + + if (scriptNode.parentNode) + scriptNode.parentNode.removeChild(scriptNode); + }); + this._forEachNode(doc.getElementsByTagName('noscript'), function(noscriptNode) { + if (noscriptNode.parentNode) + noscriptNode.parentNode.removeChild(noscriptNode); + }); + }, + + /** + * Check if this node has only whitespace and a single P element + * Returns false if the DIV node contains non-empty text nodes + * or if it contains no P or more than 1 element. + * + * @param Element + **/ + _hasSinglePInsideElement: function(element) { + // There should be exactly 1 element child which is a P: + if (element.children.length != 1 || element.children[0].tagName !== "P") { + return false; + } + + // And there should be no text nodes with real content + return !this._someNode(element.childNodes, function(node) { + return node.nodeType === Node.TEXT_NODE && + this.REGEXPS.hasContent.test(node.textContent); + }); + }, + + /** + * Determine whether element has any children block level elements. + * + * @param Element + */ + _hasChildBlockElement: function (element) { + return this._someNode(element.childNodes, function(node) { + return this.DIV_TO_P_ELEMS.indexOf(node.tagName) !== -1 || + this._hasChildBlockElement(node); + }); + }, + + /** + * Get the inner text of a node - cross browser compatibly. + * This also strips out any excess whitespace to be found. + * + * @param Element + * @param Boolean normalizeSpaces (default: true) + * @return string + **/ + _getInnerText: function(e, normalizeSpaces) { + normalizeSpaces = (typeof normalizeSpaces === 'undefined') ? true : normalizeSpaces; + var textContent = e.textContent.trim(); + + if (normalizeSpaces) { + return textContent.replace(this.REGEXPS.normalize, " "); + } else { + return textContent; + } + }, + + /** + * Get the number of times a string s appears in the node e. + * + * @param Element + * @param string - what to split on. Default is "," + * @return number (integer) + **/ + _getCharCount: function(e,s) { + s = s || ","; + return this._getInnerText(e).split(s).length - 1; + }, + + /** + * Remove the style attribute on every e and under. + * TODO: Test if getElementsByTagName(*) is faster. + * + * @param Element + * @return void + **/ + _cleanStyles: function(e) { + e = e || this._doc; + if (!e) + return; + var cur = e.firstChild; + + // Remove any root styles, if we're able. + if (typeof e.removeAttribute === 'function' && e.className !== 'readability-styled') + e.removeAttribute('style'); + + // Go until there are no more child nodes + while (cur !== null) { + if (cur.nodeType === cur.ELEMENT_NODE) { + // Remove style attribute(s) : + if (cur.className !== "readability-styled") + cur.removeAttribute("style"); + + this._cleanStyles(cur); + } + + cur = cur.nextSibling; + } + }, + + /** + * Get the density of links as a percentage of the content + * This is the amount of text that is inside a link divided by the total text in the node. + * + * @param Element + * @return number (float) + **/ + _getLinkDensity: function(element) { + var textLength = this._getInnerText(element).length; + if (textLength === 0) + return 0; + + var linkLength = 0; + + // XXX implement _reduceNodeList? + this._forEachNode(element.getElementsByTagName("a"), function(linkNode) { + linkLength += this._getInnerText(linkNode).length; + }); + + return linkLength / textLength; + }, + + /** + * Find a cleaned up version of the current URL, to use for comparing links for possible next-pageyness. + * + * @author Dan Lacy + * @return string the base url + **/ + _findBaseUrl: function() { + var uri = this._uri; + var noUrlParams = uri.path.split("?")[0]; + var urlSlashes = noUrlParams.split("/").reverse(); + var cleanedSegments = []; + var possibleType = ""; + + for (var i = 0, slashLen = urlSlashes.length; i < slashLen; i += 1) { + var segment = urlSlashes[i]; + + // Split off and save anything that looks like a file type. + if (segment.indexOf(".") !== -1) { + possibleType = segment.split(".")[1]; + + // If the type isn't alpha-only, it's probably not actually a file extension. + if (!possibleType.match(/[^a-zA-Z]/)) + segment = segment.split(".")[0]; + } + + // EW-CMS specific segment replacement. Ugly. + // Example: http://www.ew.com/ew/article/0,,20313460_20369436,00.html + if (segment.indexOf(',00') !== -1) + segment = segment.replace(',00', ''); + + // If our first or second segment has anything looking like a page number, remove it. + if (segment.match(/((_|-)?p[a-z]*|(_|-))[0-9]{1,2}$/i) && ((i === 1) || (i === 0))) + segment = segment.replace(/((_|-)?p[a-z]*|(_|-))[0-9]{1,2}$/i, ""); + + var del = false; + + // If this is purely a number, and it's the first or second segment, + // it's probably a page number. Remove it. + if (i < 2 && segment.match(/^\d{1,2}$/)) + del = true; + + // If this is the first segment and it's just "index", remove it. + if (i === 0 && segment.toLowerCase() === "index") + del = true; + + // If our first or second segment is smaller than 3 characters, + // and the first segment was purely alphas, remove it. + if (i < 2 && segment.length < 3 && !urlSlashes[0].match(/[a-z]/i)) + del = true; + + // If it's not marked for deletion, push it to cleanedSegments. + if (!del) + cleanedSegments.push(segment); + } + + // This is our final, cleaned, base article URL. + return uri.scheme + "://" + uri.host + cleanedSegments.reverse().join("/"); + }, + + /** + * Look for any paging links that may occur within the document. + * + * @param body + * @return object (array) + **/ + _findNextPageLink: function(elem) { + var uri = this._uri; + var possiblePages = {}; + var allLinks = elem.getElementsByTagName('a'); + var articleBaseUrl = this._findBaseUrl(); + + // Loop through all links, looking for hints that they may be next-page links. + // Things like having "page" in their textContent, className or id, or being a child + // of a node with a page-y className or id. + // + // Also possible: levenshtein distance? longest common subsequence? + // + // After we do that, assign each page a score, and + for (var i = 0, il = allLinks.length; i < il; i += 1) { + var link = allLinks[i]; + var linkHref = allLinks[i].href.replace(/#.*$/, '').replace(/\/$/, ''); + + // If we've already seen this page, ignore it. + if (linkHref === "" || + linkHref === articleBaseUrl || + linkHref === uri.spec || + linkHref in this._parsedPages) { + continue; + } + + // If it's on a different domain, skip it. + if (uri.host !== linkHref.split(/\/+/g)[1]) + continue; + + var linkText = this._getInnerText(link); + + // If the linkText looks like it's not the next page, skip it. + if (linkText.match(this.REGEXPS.extraneous) || linkText.length > 25) + continue; + + // If the leftovers of the URL after removing the base URL don't contain + // any digits, it's certainly not a next page link. + var linkHrefLeftover = linkHref.replace(articleBaseUrl, ''); + if (!linkHrefLeftover.match(/\d/)) + continue; + + if (!(linkHref in possiblePages)) { + possiblePages[linkHref] = {"score": 0, "linkText": linkText, "href": linkHref}; + } else { + possiblePages[linkHref].linkText += ' | ' + linkText; + } + + var linkObj = possiblePages[linkHref]; + + // If the articleBaseUrl isn't part of this URL, penalize this link. It could + // still be the link, but the odds are lower. + // Example: http://www.actionscript.org/resources/articles/745/1/JavaScript-and-VBScript-Injection-in-ActionScript-3/Page1.html + if (linkHref.indexOf(articleBaseUrl) !== 0) + linkObj.score -= 25; + + var linkData = linkText + ' ' + link.className + ' ' + link.id; + if (linkData.match(this.REGEXPS.nextLink)) + linkObj.score += 50; + + if (linkData.match(/pag(e|ing|inat)/i)) + linkObj.score += 25; + + if (linkData.match(/(first|last)/i)) { + // -65 is enough to negate any bonuses gotten from a > or ยป in the text, + // If we already matched on "next", last is probably fine. + // If we didn't, then it's bad. Penalize. + if (!linkObj.linkText.match(this.REGEXPS.nextLink)) + linkObj.score -= 65; + } + + if (linkData.match(this.REGEXPS.negative) || linkData.match(this.REGEXPS.extraneous)) + linkObj.score -= 50; + + if (linkData.match(this.REGEXPS.prevLink)) + linkObj.score -= 200; + + // If a parentNode contains page or paging or paginat + var parentNode = link.parentNode; + var positiveNodeMatch = false; + var negativeNodeMatch = false; + + while (parentNode) { + var parentNodeClassAndId = parentNode.className + ' ' + parentNode.id; + + if (!positiveNodeMatch && parentNodeClassAndId && parentNodeClassAndId.match(/pag(e|ing|inat)/i)) { + positiveNodeMatch = true; + linkObj.score += 25; + } + + if (!negativeNodeMatch && parentNodeClassAndId && parentNodeClassAndId.match(this.REGEXPS.negative)) { + // If this is just something like "footer", give it a negative. + // If it's something like "body-and-footer", leave it be. + if (!parentNodeClassAndId.match(this.REGEXPS.positive)) { + linkObj.score -= 25; + negativeNodeMatch = true; + } + } + + parentNode = parentNode.parentNode; + } + + // If the URL looks like it has paging in it, add to the score. + // Things like /page/2/, /pagenum/2, ?p=3, ?page=11, ?pagination=34 + if (linkHref.match(/p(a|g|ag)?(e|ing|ination)?(=|\/)[0-9]{1,2}/i) || linkHref.match(/(page|paging)/i)) + linkObj.score += 25; + + // If the URL contains negative values, give a slight decrease. + if (linkHref.match(this.REGEXPS.extraneous)) + linkObj.score -= 15; + + /** + * Minor punishment to anything that doesn't match our current URL. + * NOTE: I'm finding this to cause more harm than good where something is exactly 50 points. + * Dan, can you show me a counterexample where this is necessary? + * if (linkHref.indexOf(window.location.href) !== 0) { + * linkObj.score -= 1; + * } + **/ + + // If the link text can be parsed as a number, give it a minor bonus, with a slight + // bias towards lower numbered pages. This is so that pages that might not have 'next' + // in their text can still get scored, and sorted properly by score. + var linkTextAsNumber = parseInt(linkText, 10); + if (linkTextAsNumber) { + // Punish 1 since we're either already there, or it's probably + // before what we want anyways. + if (linkTextAsNumber === 1) { + linkObj.score -= 10; + } else { + linkObj.score += Math.max(0, 10 - linkTextAsNumber); + } + } + } + + // Loop thrugh all of our possible pages from above and find our top + // candidate for the next page URL. Require at least a score of 50, which + // is a relatively high confidence that this page is the next link. + var topPage = null; + for (var page in possiblePages) { + if (possiblePages.hasOwnProperty(page)) { + if (possiblePages[page].score >= 50 && + (!topPage || topPage.score < possiblePages[page].score)) + topPage = possiblePages[page]; + } + } + + if (topPage) { + var nextHref = topPage.href.replace(/\/$/,''); + + this.log('NEXT PAGE IS ' + nextHref); + this._parsedPages[nextHref] = true; + return nextHref; + } else { + return null; + } + }, + + _successfulRequest: function(request) { + return (request.status >= 200 && request.status < 300) || + request.status === 304 || + (request.status === 0 && request.responseText); + }, + + _ajax: function(url, options) { + var request = new XMLHttpRequest(); + + function respondToReadyState(readyState) { + if (request.readyState === 4) { + if (this._successfulRequest(request)) { + if (options.success) + options.success(request); + } else { + if (options.error) + options.error(request); + } + } + } + + if (typeof options === 'undefined') + options = {}; + + request.onreadystatechange = respondToReadyState; + + request.open('get', url, true); + request.setRequestHeader('Accept', 'text/html'); + + try { + request.send(options.postBody); + } catch (e) { + if (options.error) + options.error(); + } + + return request; + }, + + _appendNextPage: function(nextPageLink) { + var doc = this._doc; + this._curPageNum += 1; + + var articlePage = doc.createElement("DIV"); + articlePage.id = 'readability-page-' + this._curPageNum; + articlePage.className = 'page'; + articlePage.innerHTML = '

§

'; + + doc.getElementById("readability-content").appendChild(articlePage); + + if (this._curPageNum > this._maxPages) { + var nextPageMarkup = "
"; + articlePage.innerHTML = articlePage.innerHTML + nextPageMarkup; + return; + } + + // Now that we've built the article page DOM element, get the page content + // asynchronously and load the cleaned content into the div we created for it. + (function(pageUrl, thisPage) { + this._ajax(pageUrl, { + success: function(r) { + + // First, check to see if we have a matching ETag in headers - if we do, this is a duplicate page. + var eTag = r.getResponseHeader('ETag'); + if (eTag) { + if (eTag in this._pageETags) { + this.log("Exact duplicate page found via ETag. Aborting."); + articlePage.style.display = 'none'; + return; + } else { + this._pageETags[eTag] = 1; + } + } + + // TODO: this ends up doubling up page numbers on NYTimes articles. Need to generically parse those away. + var page = doc.createElement("DIV"); + + // Do some preprocessing to our HTML to make it ready for appending. + // - Remove any script tags. Swap and reswap newlines with a unicode + // character because multiline regex doesn't work in javascript. + // - Turn any noscript tags into divs so that we can parse them. This + // allows us to find any next page links hidden via javascript. + // - Turn all double br's into p's - was handled by prepDocument in the original view. + // Maybe in the future abstract out prepDocument to work for both the original document + // and AJAX-added pages. + var responseHtml = r.responseText.replace(/\n/g,'\uffff').replace(/.*?<\/script>/gi, ''); + responseHtml = responseHtml.replace(/\n/g,'\uffff').replace(/.*?<\/script>/gi, ''); + responseHtml = responseHtml.replace(/\uffff/g,'\n').replace(/<(\/?)noscript/gi, '<$1div'); + responseHtml = responseHtml.replace(this.REGEXPS.replaceFonts, '<$1span>'); + + page.innerHTML = responseHtml; + this._replaceBrs(page); + + // Reset all flags for the next page, as they will search through it and + // disable as necessary at the end of grabArticle. + this._flags = 0x1 | 0x2 | 0x4; + + var nextPageLink = this._findNextPageLink(page); + + // NOTE: if we end up supporting _appendNextPage(), we'll need to + // change this call to be async + var content = this._grabArticle(page); + + if (!content) { + this.log("No content found in page to append. Aborting."); + return; + } + + // Anti-duplicate mechanism. Essentially, get the first paragraph of our new page. + // Compare it against all of the the previous document's we've gotten. If the previous + // document contains exactly the innerHTML of this first paragraph, it's probably a duplicate. + var firstP = content.getElementsByTagName("P").length ? content.getElementsByTagName("P")[0] : null; + if (firstP && firstP.innerHTML.length > 100) { + for (var i = 1; i <= this._curPageNum; i += 1) { + var rPage = doc.getElementById('readability-page-' + i); + if (rPage && rPage.innerHTML.indexOf(firstP.innerHTML) !== -1) { + this.log('Duplicate of page ' + i + ' - skipping.'); + articlePage.style.display = 'none'; + this._parsedPages[pageUrl] = true; + return; + } + } + } + + this._removeScripts(content); + + thisPage.innerHTML = thisPage.innerHTML + content.innerHTML; + + // After the page has rendered, post process the content. This delay is necessary because, + // in webkit at least, offsetWidth is not set in time to determine image width. We have to + // wait a little bit for reflow to finish before we can fix floating images. + setTimeout((function() { + this._postProcessContent(thisPage); + }).bind(this), 500); + + + if (nextPageLink) + this._appendNextPage(nextPageLink); + } + }); + }).bind(this)(nextPageLink, articlePage); + }, + + /** + * Get an elements class/id weight. Uses regular expressions to tell if this + * element looks good or bad. + * + * @param Element + * @return number (Integer) + **/ + _getClassWeight: function(e) { + if (!this._flagIsActive(this.FLAG_WEIGHT_CLASSES)) + return 0; + + var weight = 0; + + // Look for a special classname + if (typeof(e.className) === 'string' && e.className !== '') { + if (this.REGEXPS.negative.test(e.className)) + weight -= 25; + + if (this.REGEXPS.positive.test(e.className)) + weight += 25; + } + + // Look for a special ID + if (typeof(e.id) === 'string' && e.id !== '') { + if (this.REGEXPS.negative.test(e.id)) + weight -= 25; + + if (this.REGEXPS.positive.test(e.id)) + weight += 25; + } + + return weight; + }, + + /** + * Clean a node of all elements of type "tag". + * (Unless it's a youtube/vimeo video. People love movies.) + * + * @param Element + * @param string tag to clean + * @return void + **/ + _clean: function(e, tag) { + var isEmbed = ["object", "embed", "iframe"].indexOf(tag) !== -1; + + this._forEachNode(e.getElementsByTagName(tag), function(element) { + // Allow youtube and vimeo videos through as people usually want to see those. + if (isEmbed) { + var attributeValues = [].map.call(element.attributes, function(attr) { + return attr.value; + }).join("|"); + + // First, check the elements attributes to see if any of them contain youtube or vimeo + if (this.REGEXPS.videos.test(attributeValues)) + return; + + // Then check the elements inside this element for the same. + if (this.REGEXPS.videos.test(element.innerHTML)) + return; + } + + element.parentNode.removeChild(element); + }); + }, + + /** + * Check if a given node has one of its ancestor tag name matching the + * provided one. + * @param HTMLElement node + * @param String tagName + * @param Number maxDepth + * @return Boolean + */ + _hasAncestorTag: function(node, tagName, maxDepth) { + maxDepth = maxDepth || 3; + tagName = tagName.toUpperCase(); + var depth = 0; + while (node.parentNode) { + if (depth > maxDepth) + return false; + if (node.parentNode.tagName === tagName) + return true; + node = node.parentNode; + depth++; + } + return false; + }, + + /** + * Clean an element of all tags of type "tag" if they look fishy. + * "Fishy" is an algorithm based on content length, classnames, link density, number of images & embeds, etc. + * + * @return void + **/ + _cleanConditionally: function(e, tag) { + if (!this._flagIsActive(this.FLAG_CLEAN_CONDITIONALLY)) + return; + + var tagsList = e.getElementsByTagName(tag); + var curTagsLength = tagsList.length; + var isList = tag === "ul" || tag === "ol"; + + // Gather counts for other typical elements embedded within. + // Traverse backwards so we can remove nodes at the same time + // without effecting the traversal. + // + // TODO: Consider taking into account original contentScore here. + for (var i = curTagsLength-1; i >= 0; i -= 1) { + var weight = this._getClassWeight(tagsList[i]); + var contentScore = 0; + + this.log("Cleaning Conditionally", tagsList[i]); + + if (weight + contentScore < 0) { + tagsList[i].parentNode.removeChild(tagsList[i]); + } else if (this._getCharCount(tagsList[i],',') < 10) { + // If there are not very many commas, and the number of + // non-paragraph elements is more than paragraphs or other + // ominous signs, remove the element. + var p = tagsList[i].getElementsByTagName("p").length; + var img = tagsList[i].getElementsByTagName("img").length; + var li = tagsList[i].getElementsByTagName("li").length-100; + var input = tagsList[i].getElementsByTagName("input").length; + + var embedCount = 0; + var embeds = tagsList[i].getElementsByTagName("embed"); + for (var ei = 0, il = embeds.length; ei < il; ei += 1) { + if (!this.REGEXPS.videos.test(embeds[ei].src)) + embedCount += 1; + } + + var linkDensity = this._getLinkDensity(tagsList[i]); + var contentLength = this._getInnerText(tagsList[i]).length; + var toRemove = false; + if (img > p && !this._hasAncestorTag(tagsList[i], "figure")) { + toRemove = true; + } else if (!isList && li > p) { + toRemove = true; + } else if (input > Math.floor(p/3)) { + toRemove = true; + } else if (!isList && contentLength < 25 && (img === 0 || img > 2)) { + toRemove = true; + } else if (!isList && weight < 25 && linkDensity > 0.2) { + toRemove = true; + } else if (weight >= 25 && linkDensity > 0.5) { + toRemove = true; + } else if ((embedCount === 1 && contentLength < 75) || embedCount > 1) { + toRemove = true; + } + + if (toRemove) { + tagsList[i].parentNode.removeChild(tagsList[i]); + } + } + } + }, + + /** + * Clean out spurious headers from an Element. Checks things like classnames and link density. + * + * @param Element + * @return void + **/ + _cleanHeaders: function(e) { + for (var headerIndex = 1; headerIndex < 3; headerIndex += 1) { + var headers = e.getElementsByTagName('h' + headerIndex); + for (var i = headers.length - 1; i >= 0; i -= 1) { + if (this._getClassWeight(headers[i]) < 0) + headers[i].parentNode.removeChild(headers[i]); + } + } + }, + + _flagIsActive: function(flag) { + return (this._flags & flag) > 0; + }, + + _addFlag: function(flag) { + this._flags = this._flags | flag; + }, + + _removeFlag: function(flag) { + this._flags = this._flags & ~flag; + }, + + /** + * Decides whether or not the document is reader-able without parsing the whole thing. + * + * @return boolean Whether or not we suspect parse() will suceeed at returning an article object. + */ + isProbablyReaderable: function(helperIsVisible) { + var nodes = this._getAllNodesWithTag(this._doc, ["p", "pre"]); + + // FIXME we should have a fallback for helperIsVisible, but this is + // problematic because of jsdom's elem.style handling - see + // https://github.com/mozilla/readability/pull/186 for context. + + var score = 0; + // This is a little cheeky, we use the accumulator 'score' to decide what to return from + // this callback: + return this._someNode(nodes, function(node) { + if (helperIsVisible && !helperIsVisible(node)) + return false; + var matchString = node.className + " " + node.id; + + if (this.REGEXPS.unlikelyCandidates.test(matchString) && + !this.REGEXPS.okMaybeItsACandidate.test(matchString)) { + return false; + } + + if (node.matches && node.matches("li p")) { + return false; + } + + var textContentLength = node.textContent.trim().length; + if (textContentLength < 140) { + return false; + } + + score += Math.sqrt(textContentLength - 140); + + if (score > 20) { + return true; + } + return false; + }); + }, + + /** + * Runs readability. + * + * Workflow: + * 1. Prep the document by removing script tags, css, etc. + * 2. Build readability's DOM tree. + * 3. Grab the article content from the current dom tree. + * 4. Replace the current DOM tree with the new one. + * 5. Read peacefully. + * + * @return void + **/ + parse: function () { + // Avoid parsing too large documents, as per configuration option + if (this._maxElemsToParse > 0) { + var numTags = this._doc.getElementsByTagName("*").length; + if (numTags > this._maxElemsToParse) { + throw new Error("Aborting parsing document; " + numTags + " elements found"); + } + } + + if (typeof this._doc.documentElement.firstElementChild === "undefined") { + this._getNextNode = this._getNextNodeNoElementProperties; + } + // Remove script tags from the document. + this._removeScripts(this._doc); + + // FIXME: Disabled multi-page article support for now as it + // needs more work on infrastructure. + + // Make sure this document is added to the list of parsed pages first, + // so we don't double up on the first page. + // this._parsedPages[uri.spec.replace(/\/$/, '')] = true; + + // Pull out any possible next page link first. + // var nextPageLink = this._findNextPageLink(doc.body); + + this._prepDocument(); + + var metadata = this._getArticleMetadata(); + var articleTitle = metadata.title || this._getArticleTitle(); + + var articleContent = this._grabArticle(); + if (!articleContent) + return null; + + this.log("Grabbed: " + articleContent.innerHTML); + + this._postProcessContent(articleContent); + + // if (nextPageLink) { + // // Append any additional pages after a small timeout so that people + // // can start reading without having to wait for this to finish processing. + // setTimeout((function() { + // this._appendNextPage(nextPageLink); + // }).bind(this), 500); + // } + + // If we haven't found an excerpt in the article's metadata, use the article's + // first paragraph as the excerpt. This is used for displaying a preview of + // the article's content. + if (!metadata.excerpt) { + var paragraphs = articleContent.getElementsByTagName("p"); + if (paragraphs.length > 0) { + metadata.excerpt = paragraphs[0].textContent.trim(); + } + } + + return { uri: this._uri, + title: articleTitle, + byline: metadata.byline || this._articleByline, + dir: this._articleDir, + content: articleContent.innerHTML, + length: articleContent.textContent.length, + excerpt: metadata.excerpt }; + } +}; diff --git a/readability/data/Toolbar.js b/readability/data/Toolbar.js new file mode 100644 index 0000000..83a6a08 --- /dev/null +++ b/readability/data/Toolbar.js @@ -0,0 +1,145 @@ +/* ============================================================ +* QupZilla - WebKit based browser +* Copyright (C) 2016 Jaroslav Bambas +* +* This program is free software: you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation, either version 3 of the License, or +* (at your option) any later version. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with this program. If not, see . +* ============================================================ */ + +function readablilityToolbar(){ + renderToolbar(); + addListeners(); +} + +function renderToolbar(){ + var toolbar = document.createElement('div'); + document.getElementById('container').appendChild(toolbar); + + toolbar.innerHTML = '
'; +} + +function addListeners(){ + document.getElementById('close-button').addEventListener("click", closeReadability); + document.getElementById('style-button').addEventListener("click", toolbarService); + + var lightButton = document.getElementById('light-button'); + lightButton.addEventListener("click", function(){switchToLight(lightButton);}); + var sepiaButton = document.getElementById('sepia-button'); + sepiaButton.addEventListener("click", function(){switchToSepia(sepiaButton);}); + var darkButton = document.getElementById('dark-button'); + darkButton.addEventListener("click", function(){switchToDark(darkButton);}); + + var serifButton = document.getElementById('serif-button'); + serifButton.addEventListener("click", function(){switchToSerif(serifButton);}); + var sansSerifButton = document.getElementById('sans-serif-button'); + sansSerifButton.addEventListener("click", function(){switchToSansSerif(sansSerifButton);}); + + var fontSizePlusButton = document.getElementById('font-size-plus'); + fontSizePlusButton.addEventListener("click", fontSizePlus); + var fontSizeMinusButton = document.getElementById('font-size-minus'); + fontSizeMinusButton.addEventListener("click", fontSizeMinus); +} + +function closeReadability(){ + location.reload(); +} + +function toolbarService(){ + var element = document.getElementById('style-dropdown'); + + if(element.classList.contains('open')){ + element.classList.remove('open'); + } else { + element.classList.add('open'); + + } +} + +function switchToLight(item){ + document.body.classList.remove('sepia', 'dark'); + document.body.classList.add('light'); + + var items = document.getElementById('color-scheme-buttons').children; + for (var j = items.length - 1; j >= 0; j--) { + items[j].classList.remove("selected"); + } + item.classList.add('selected'); +} + +function switchToSepia(item){ + document.body.classList.remove('light', 'dark'); + document.body.classList.add('sepia'); + + var items = document.getElementById('color-scheme-buttons').children; + for (var j = items.length - 1; j >= 0; j--) { + items[j].classList.remove("selected"); + } + item.classList.add('selected'); +} + +function switchToDark(item){ + document.body.classList.remove('sepia', 'light'); + document.body.classList.add('dark'); + + var items = document.getElementById('color-scheme-buttons').children; + for (var j = items.length - 1; j >= 0; j--) { + items[j].classList.remove("selected"); + } + item.classList.add('selected'); +} + +function switchToSerif(item){ + document.body.classList.remove('sans-serif'); + document.body.classList.add('serif'); + document.getElementById('sans-serif-button').classList.remove('selected'); + item.classList.add('selected'); +} + +function switchToSansSerif(item){ + document.body.classList.remove('serif'); + document.body.classList.add('sans-serif'); + document.getElementById('serif-button').classList.remove('selected'); + item.classList.add('selected'); +} + +function fontSizePlus(){ + var container = document.getElementById('container'); + var incrementedSize = 0; + for (var i = 0; i < container.classList.length; i++){ + if(container.classList[i].indexOf('font-size') > -1){ + var size = container.classList[i].substr(-1, 1); + if(parseInt(size) < 9){ + incrementedSize = parseInt(size) + 1; + } else return; + container.classList.remove(container.classList[i]); + container.classList.add('font-size' + incrementedSize); + return; + } + } +} + +function fontSizeMinus(){ + var container = document.getElementById('container'); + var decrementedSize = 0; + for (var i = 0; i < container.classList.length; i++){ + if(container.classList[i].indexOf('font-size') > -1){ + var size = container.classList[i].substr(-1, 1); + if(parseInt(size) > 1){ + incrementedSize = parseInt(size) - 1; + } else return; + container.classList.remove(container.classList[i]); + container.classList.add('font-size' + incrementedSize); + return; + } + } +} \ No newline at end of file diff --git a/readability/data/icon.png b/readability/data/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..45d4624bf4d9cc5e74deb43776deb9c22f54be61 GIT binary patch literal 22991 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 literal 0 HcmV?d00001 diff --git a/readability/data/style.css b/readability/data/style.css new file mode 100644 index 0000000..30b098b --- /dev/null +++ b/readability/data/style.css @@ -0,0 +1,543 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +body { + padding: 64px 0; + margin: 0 auto; +} + +@media (max-width: 785px) { + body { + padding-top: 64px; + -moz-padding-end: 0; + padding-bottom: 64px; + -moz-padding-start: 51px; + } +} + +body.loaded { + transition: color 0.4s, background-color 0.4s; +} + +#container { + max-width: 40em; + margin: 0 auto; +} + +.light, +.light-button { + color: #333333; + background-color: #ffffff; +} + +.dark, +.dark-button { + color: #eeeeee; + background-color: #333333; +} + +.sepia, +.sepia-button { + color: #333333; + background-color: #f0ece7; +} + +.sans-serif, +.sans-serif-button, +.sans-serif .remove-button { + font-family: Helvetica, Arial, sans-serif; +} + +.serif, +.serif-button, +.serif .remove-button { + font-family: Georgia, "Times New Roman", serif; +} + +.font-size1 { + font-size: 10px; +} + +.font-size2 { + font-size: 12px; +} + +.font-size3 { + font-size: 14px; +} + +.font-size4 { + font-size: 16px; +} + +.font-size5 { + font-size: 18px; +} + +.font-size6 { + font-size: 20px; +} + +.font-size7 { + font-size: 22px; +} + +.font-size8 { + font-size: 24px; +} + +.font-size9 { + font-size: 26px; +} + + +/* Loading/error message */ + +.message { + margin-top: 40px; + display: none; + text-align: center; + width: 100%; + font-size: 0.9em; +} + +/* Header */ + +.header { + text-align: start; + display: none; +} + +.domain { + font-size: 0.9em; + line-height: 1.48em; + padding-bottom: 4px; + font-family: Helvetica, Arial, sans-serif; + text-decoration: none; + border-bottom: 1px solid; + color: #0095dd; +} + +.light > .container > .header > .domain, +.sepia > .container > .header > .domain { + border-bottom-color: #333333; +} + +.dark > .container > .header > .domain { + border-bottom-color: #eeeeee; +} + +.header > h1 { + font-size: 1.33em; + line-height: 1.25em; + width: 100%; + margin: 30px 0; + padding: 0; +} + +.header > .credits { + font-size: 0.9em; + line-height: 1.48em; + margin: 0 0 30px 0; + padding: 0; + font-style: italic; +} + +/* Content */ + +#moz-reader-content { + display: none; + font-size: 1em; + line-height: 1.6em; +} + +.content h1, +.content h2, +.content h3 { + font-weight: bold; +} + +#moz-reader-content h1 { + font-size: 1.33em; + line-height: 1.25em; +} + +#moz-reader-content h2 { + font-size: 1.1em; + line-height: 1.51em; +} + +#moz-reader-content h3 { + font-size: 1em; + line-height: 1.66em; +} + +.content a { + text-decoration: underline; + font-weight: normal; +} + +.content a, +.content a:visited, +.content a:hover, +.content a:active { + color: #0095dd; +} + +.content * { + max-width: 100%; + height: auto; +} + +.content p, +.content code, +.content pre, +.content blockquote, +.content ul, +.content ol, +.content li, +.content figure, +.content .wp-caption { + margin: 0 0 30px 0; +} + +.content p > img:only-child, +.content p > a:only-child > img:only-child, +.content .wp-caption img, +.content figure img { + display: block; +} + +.content img[moz-reader-center] { + margin-left: auto; + margin-right: auto; +} + +#moz-reader-content .caption, +#moz-reader-content .wp-caption-text, +#moz-reader-content figcaption { + font-size: 0.9em; + line-height: 1.48em; + font-style: italic; +} + +.content code, +.content pre { + white-space: pre-wrap; +} + +.content blockquote { + padding: 0; + -moz-padding-start: 16px; +} + +.light > .container > .content blockquote, +.sepia > .container > .content blockquote { + -moz-border-start: 2px solid #333333; +} + +.dark > .container > .content blockquote { + -moz-border-start: 2px solid #eeeeee; +} +.dark *::-moz-selection { + background-color: #FFFFFF; + color: #0095DD; +} +.dark a::-moz-selection { + color: #DD4800; +} + +.content ul, +.content ol { + padding: 0; +} + +.content ul { + -moz-padding-start: 30px; + list-style: disc; +} + +.content ol { + -moz-padding-start: 30px; + list-style: decimal; +} + +/*======= Controls toolbar =======*/ + +.toolbar { + font-family: Helvetica, Arial, sans-serif; + position: fixed; + height: 100%; + top: 0; + left: 0; + margin: 0; + padding: 0; + list-style: none; + background-color: #fbfbfb; + -moz-user-select: none; + border-right: 1px solid #b5b5b5; +} + +.button { + display: block; + background-size: 24px 24px; + background-repeat: no-repeat; + color: #333; + background-color: #fbfbfb; + height: 40px; + padding: 0; +} + +.toolbar .button { + width: 40px; + background-position: center; + margin-right: -1px; + border-top: 0; + border-left: 0; + border-right: 1px solid #b5b5b5; + border-bottom: 1px solid #c1c1c1; +} + +.button[hidden] { + display: none; +} + +.dropdown { + text-align: center; + list-style: none; + margin: 0; + padding: 0; +} + +.dropdown li { + margin: 0; + padding: 0; +} + +/*======= Font style popup =======*/ + +.dropdown-popup { + min-width: 300px; + text-align: start; + position: absolute; + left: 48px; /* offset to account for toolbar width */ + z-index: 1000; + background-color: #fbfbfb; + visibility: hidden; + border-radius: 4px; + border: 1px 1px 0 1px solid #b5b5b5; + box-shadow: 0 1px 12px #666; +} + +.dropdown-popup > hr { + display: none; +} + +.open > .dropdown-popup { + visibility: visible; +} + +.dropdown-arrow { + position: absolute; + top: 30px; /* offset arrow from top of popup */ + left: -16px; + width: 24px; + height: 24px; + background-image: url({6}); + display: block; +} + +#font-type-buttons, +#font-size-buttons, +#color-scheme-buttons { + display: flex; + flex-direction: row; +} + +#font-type-buttons > button:first-child { + border-top-left-radius: 3px; +} +#font-type-buttons > button:last-child { + border-top-right-radius: 3px; +} +#color-scheme-buttons > button:first-child { + border-bottom-left-radius: 3px; +} +#color-scheme-buttons > button:last-child { + border-bottom-right-radius: 3px; +} + +#font-type-buttons > button, +#font-size-buttons > button, +#color-scheme-buttons > button { + text-align: center; + border: 0; +} + +#font-type-buttons > button, +#font-size-buttons > button { + width: 50%; + background-color: transparent; + border-left: 1px solid #B5B5B5; + border-bottom: 1px solid #B5B5B5; +} + +#color-scheme-buttons > button { + width: 33.33%; + font-size: 14px; +} + +#color-scheme-buttons > .dark-button { + margin-top: -1px; + height: 61px; +} + +#font-type-buttons > button:first-child, +#font-size-buttons > button:first-child { + border-left: 0; +} + +#font-type-buttons > button { + display: inline-block; + font-size: 62px; + height: 100px; +} + +#font-size-buttons > button, +#color-scheme-buttons > button { + height: 60px; +} + +#font-type-buttons > button:active:hover, +#font-type-buttons > button.selected, +#color-scheme-buttons > button:active:hover, +#color-scheme-buttons > button.selected { + box-shadow: inset 0 -3px 0 0 #fc6420; +} + +#font-type-buttons > button:active:hover, +#font-type-buttons > button.selected { + border-bottom: 1px solid #FC6420; +} + +/* Make the serif button content the same size as the sans-serif button content. */ +#font-type-buttons > button > .description { + color: #666; + font-size: 12px; + margin-top: -5px; +} + +/* Font sizes are different per-platform, so we need custom CSS to line them up. */ +#font-type-buttons > .sans-serif-button > .name { + margin-top: 5px; +} + +#font-type-buttons > .sans-serif-button > .description { + margin-top: -8px; +} + +#font-type-buttons > .serif-button > .name { + font-size: 70px; +} + +.button:hover, +#font-size-buttons > button:hover, +#font-type-buttons > button:hover { + background-color: #ebebeb; +} + +.dropdown.open, +.button:active, +#font-size-buttons > button:active, +#font-size-buttons > button.selected { + background-color: #dadada; +} + +.minus-button, +.plus-button { + background-color: transparent; + border: 0; + background-size: 18px 18px; + background-repeat: no-repeat; + background-position: center; +} + +.footer { + height: 64px; + background-color: #ebebeb; + position: absolute; + left: 0; + width: 100%; + text-align: center; + padding: 12px 0; + box-sizing: border-box; + box-shadow: 0 3px 3px -3px rgba(0, 0, 0, 0.35) inset; +} + +.sepia .footer { + background-color: #dedad4; +} + +.remove-button { + background-image: url({3}); + margin: 0 auto; + border: 1px solid #c1c1c1; + background-position: 10px 7px; + padding-left: 42px; + padding-right: 10px; + border-radius: 2px; + box-shadow: 0 1px 2px rgba(0, 0, 0, 0.1); + font-size: 18px; +} + +/*======= Toolbar icons =======*/ + +.close-button { + background-image: url({1}); + height: 68px; + background-position: center 8px; +} + +.close-button:hover { + background-image: url({2}); + background-color: #d94141; + border-bottom: 1px solid #d94141; + border-right: 1px solid #d94141; +} + +.close-button:hover:active { + background-image: url({2}); + background-color: #AE2325; + border-bottom: 1px solid #AE2325; + border-right: 1px solid #AE2325; +} + +.style-button { + background-image: url({7}); +} + +.minus-button { + background-image: url({5}); +} + +.plus-button { + background-image: url({4}); +} + +@media print { + .toolbar { + display: none; + } + .footer { + display: none; + } +} + +#close-hover { + fill: #fff; +} + +#close { + fill: #808080; +} \ No newline at end of file diff --git a/readability/metadata.desktop b/readability/metadata.desktop index 4a90c0f..30584a1 100644 --- a/readability/metadata.desktop +++ b/readability/metadata.desktop @@ -1,11 +1,12 @@ [Desktop Entry] Name=Readability -Comment=A basic python plugin for Falkon +Comment=Transform webpage to easy reading. +Icon=data/icon.png Type=Service X-Falkon-Type=Extension/Python -X-Falkon-Author=Example name -X-Falkon-Email=python@example.com +X-Falkon-Author=Juraj Oravec +X-Falkon-Email=sgd.orava@gmail.com X-Falkon-Version=1.0.0 X-Falkon-Settings=false