Merge branch 'feature/puread-level-iii-2' into develop

This commit is contained in:
Kenshin 2019-04-27 11:40:43 +08:00
commit d543e16565
18 changed files with 4962 additions and 2544 deletions

View File

@ -4,7 +4,7 @@
* sr-rd-title, sr-rd-desc, sr-rd-content, p, div, a, img, pre, code, sr-blockquote, ul ol
*/
.simpread-theme-root {
.simpread-theme-root {
font-size: 62.5%!important;
}
@ -13,8 +13,13 @@ sr-rd-title, sr-rd-desc, sr-rd-content {
}
sr-rd-title {
text-rendering: optimizelegibility;
display: -webkit-box;
margin: 1em 0px 0.5em;
overflow : hidden;
text-overflow: ellipsis;
text-rendering: optimizelegibility;
-webkit-line-clamp: 3;
-webkit-box-orient: vertical;
}
sr-rd-content {
@ -186,6 +191,14 @@ sr-rd-content pre code * {
font-size: 1.1rem;
}
sr-rd-content pre p {
margin: 0;
padding: 0;
color: inherit;
font-size: inherit;
line-height: inherit;
}
sr-rd-content li code,
sr-rd-content p code {
margin: 0 4px;
@ -193,6 +206,14 @@ sr-rd-content p code {
font-size: 1.1rem;
}
sr-rd-content mark {
margin: 0 5px;
padding: 2px;
background: #fffdd1;
border-bottom: 1px solid #ffedce;
}
.sr-rd-content-img {
width: 90%;
height: auto;
@ -217,6 +238,17 @@ sr-rd-content p code {
-webkit-box-orient: vertical;
}
.sr-rd-content-center-small {
display: inline-flex;
}
.sr-rd-content-center-small img {
margin: 0;
padding: 0;
border: 0;
box-shadow: none;
}
.sr-rd-content-nobeautify {
margin: 0;
padding: 0;
@ -257,6 +289,8 @@ sr-rd-mult sr-rd-mult-avatar {
display: flex;
flex-direction: column;
align-items: center;
margin: 0 15px;
}
sr-rd-mult sr-rd-mult-avatar span {

View File

@ -267,6 +267,8 @@ function browserAction( is_update ) {
*/
function pRead() {
pr = new PureRead( storage.sites );
pr.cleanup = storage.read.cleanup == undefined ? true : storage.read.cleanup;
pr.pure = storage.read.pure == undefined ? false : storage.read.pure;
pr.AddPlugin( puplugin.Plugin() );
pr.Getsites();
storage.puread = pr;

View File

@ -22,7 +22,7 @@
},
"content_scripts" : [
{
"matches" : [ "http://*/*", "https://*/*", "file:///*/*.txt" ],
"matches" : [ "http://*/*", "https://*/*", "file:///*/*.txt", "file:///*/*.md" ],
"exclude_matches": [ "http://localhost/*", "https://simpread.herokuapp.com/view/*" ],
"js" : [
"/bundle/common.js",

View File

@ -32,6 +32,7 @@ export default class LabsOpt extends React.Component {
this.props.onChange && this.props.onChange( true );
model == "read" && state == "auto" && this.exclusionState( value );
model == "read" && state == "toc" && this.tocState( value );
model == "read" && state == "cleanup" && this.cleanupState( value );
}
changeExclusion( event ) {
@ -48,6 +49,10 @@ export default class LabsOpt extends React.Component {
$( this.refs.toc ).velocity( value ? "slideDown" : "slideUp" );
}
cleanupState( value ) {
$( this.refs.cleanup ).velocity( value ? "slideDown" : "slideUp" );
}
exclusionState( value ) {
$( this.refs.exclusion ).velocity( value ? "slideDown" : "slideUp" );
$( this.refs.whitelist ).velocity( !value ? "slideDown" : "slideUp" );
@ -61,6 +66,7 @@ export default class LabsOpt extends React.Component {
componentDidMount() {
this.exclusionState( this.props.read.auto );
this.tocState( this.props.read.toc );
this.cleanupState( this.props.read.cleanup == undefined ? true : this.props.read.cleanup );
}
onClick( state ) {
@ -199,6 +205,22 @@ export default class LabsOpt extends React.Component {
</div>
</div>
<div className="label">词法分析引擎 <a target="_blank" href="http://ksria.com/simpread/docs/#/词法分析引擎" style={{ color:' #FF5252', borderBottom: '2px dotted', fontSize: '10px', fontWeight: 'bold', cursor: 'pointer' }}>测试版</a></div>
<div style={{ 'padding-top': '10px', 'position': 'relative' }} className="lab">
<Switch width="100%" checked={ this.props.read.cleanup == undefined ? true : this.props.read.cleanup }
thumbedColor="#3F51B5" trackedColor="#7986CB"
label="是否启用增强解析模式?"
desc="增强解析模式会对版面重新设计,包括:去除多余空格、优化版面结构等,此功能为测试版,遇到解析失败时,请关闭此功能。"
onChange={ (s)=>this.onChange(s, "read", "cleanup") } />
<div ref="cleanup" style={{ 'padding-top': '10px', 'margin-bottom': '8px;' }}>
<Switch width="100%" checked={ this.props.read.pure }
thumbedColor="#3F51B5" trackedColor="#7986CB"
label="纯粹模式"
desc="比【增强解析模式】还要彻底优化版本包括字形、颜色、字号、代码段等专治页面及不规范微信订阅号CSDN 等。"
onChange={ (s)=>this.onChange(s, "read", "pure") } />
</div>
</div>
<div className="label">授权管理</div>
<div style={{ 'padding-top': '10px' }} className="lab">
<Auth/>

View File

@ -126,19 +126,23 @@ export default class ReadCtlbar extends React.Component {
}
componentWillMount() {
if ( storage.current.fap ) {
delete conf.readItems.exit;
delete conf.readItems.option.items.setting;
}
if ( this.props.type.startsWith( "txtread::" ) && this.props.type.endsWith( "::local" )) {
delete conf.readItems.download;
delete conf.readItems.readlater;
delete conf.readItems.send;
delete conf.readItems.share;
delete conf.readItems.option;
}
if ( this.props.type.startsWith( "metaread::" ) || this.props.type.startsWith( "txtread::" ) ) {
delete conf.readItems.option;
try {
if ( storage.current.fap ) {
delete conf.readItems.exit;
delete conf.readItems.option.items.setting;
}
if ( this.props.type.startsWith( "txtread::" ) && this.props.type.endsWith( "::local" )) {
delete conf.readItems.download;
delete conf.readItems.readlater;
delete conf.readItems.send;
delete conf.readItems.share;
delete conf.readItems.option;
}
if ( this.props.type.startsWith( "metaread::" ) || this.props.type.startsWith( "txtread::" ) ) {
delete conf.readItems.option;
}
} catch ( err ) {
// TO-DO
}
// hack code
!/chrome/ig.test( navigator.userAgent ) && ( delete conf.readItems.dyslexia );

View File

@ -54,6 +54,7 @@ class Read extends React.Component {
async componentDidMount() {
if ( $root.find( "sr-rd-content-error" ).length > 0 ) {
// Puread level to III,can't work this flow.
this.componentWillUnmount();
if ( ! localStorage["sr-update-site"] ) {
new Notify().Render({ content: "当前页面结构改变导致不匹配阅读模式,接下来请选择?", action: "更新", cancel: "高亮", callback: type => {
@ -79,29 +80,29 @@ class Read extends React.Component {
localStorage.removeItem( "sr-update-site" );
} else {
$root
.addClass( "simpread-font" )
.addClass( theme )
.find( rdclsjq )
.addClass( "simpread-font" )
.addClass( theme )
.sreffect( { opacity: 1 }, { delay: 100 })
.addClass( "simpread-read-root-show" );
.find( rdclsjq )
.addClass( theme )
.sreffect( { opacity: 1 }, { delay: 100 })
.addClass( "simpread-read-root-show" );
this.props.read.fontfamily && ss.FontFamily( this.props.read.fontfamily );
this.props.read.fontsize && ss.FontSize( this.props.read.fontsize );
this.props.read.layout && ss.Layout( this.props.read.layout );
this.props.read.site.css && this.props.read.site.css.length > 0
&& ss.SiteCSS( this.props.read.site.css );
!this.props.wrapper.avatar && this.props.read.toc
&& toc.Render( "sr-read", $( "sr-rd-content" ), this.props.read.theme, this.props.read.toc_hide );
ss.Preview( this.props.read.custom );
storage.pr.state == "txt" && $( "sr-rd-content" ).css({ "word-wrap": "break-word", "white-space": "pre-wrap" });
storage.pr.state == "txt" && !location.href.endsWith( ".md" ) && $( "sr-rd-content" ).css({ "word-wrap": "break-word", "white-space": "pre-wrap" });
storage.pr.current.site.desc == "" && $( "sr-rd-desc" ).addClass( "simpread-hidden" );
excludes( $("sr-rd-content"), this.props.wrapper.exclude );
storage.pr.Beautify( $( "sr-rd-content" ) );
storage.pr.Format( rdcls );
!this.props.wrapper.avatar && this.props.read.toc && toc.Render( "sr-read", $( "sr-rd-content" ), this.props.read.theme, this.props.read.toc_hide );
this.props.read.site.css && this.props.read.site.css.length > 0 &&
ss.SiteCSS( this.props.read.site.css );
kbd.Render( $( "sr-rd-content" ));
tooltip.Render( rdclsjq );
waves.Render({ root: rdclsjq });
@ -109,7 +110,8 @@ class Read extends React.Component {
loadPlugins( "read_complete" );
localStorage.removeItem( "sr-update-site" );
// Puread level to III,can't work this flow.
//localStorage.removeItem( "sr-update-site" );
}
}
@ -209,14 +211,17 @@ class Read extends React.Component {
/**
* Render entry
*
* @param {boolean} true: call mathJaxMode(); false: @see mathJaxMode
*/
function Render() {
function Render( callMathjax = true ) {
callMathjax && mathJaxMode();
storage.pr.ReadMode();
if ( typeof storage.pr.html.include == "string" && storage.pr.html.include.startsWith( "<sr-rd-content-error>" ) ) {
console.warn( '=== Adapter failed call Readability View ===' )
storage.pr.Readability();
storage.pr.ReadMode();
}
console.log( "current puread object is ", storage.pr )
} else console.warn( '=== Normal Read mode ===' )
console.warn( "=== Current PuRead object is ===", storage.pr )
ReactDOM.render( <Read read={ storage.current } wrapper={ storage.pr.html } />, getReadRoot() );
}
@ -258,6 +263,29 @@ function Exit() {
}).addClass( "simpread-read-root-hide" );
}
/**
* MathJax Mode
*/
function mathJaxMode() {
if ( storage.pr.isMathJax() && storage.pr.state == "temp" ) {
console.warn( '=== MathJax Mode ===' )
const dom = storage.pr.MathJaxMode();
console.log( 'current get dom is ', dom )
if ( typeof dom == "undefined" ) {
new Notify().Render( "智能感知失败,请移动鼠标框选。" );
Highlight().done( dom => {
storage.pr.TempMode( "read", dom );
Render( false );
});
} else if ( typeof dom == "string" ) {
const html = storage.pr.GetDom( dom, "html" );
storage.pr.Newsite( "read", html );
} else {
storage.pr.TempMode( "read", dom[0] );
}
}
}
/**
* Get read root
*

View File

@ -1,547 +0,0 @@
console.log( "=== PureRead: AdapteSite load ===" )
import * as util from './util';
const site = {
url : "",
target : "",
matching : [],
name : "", // only read mode
title : "", // only read mode
desc : "", // only read mode
exclude : [],
include : "",
avatar : [],
paging : [],
};
let minimatch, rdability;
export default class AdapteSite {
constructor( sites = { global:[], custom:[], local:[] } ) {
this.url = util.getURI();
this.sites = sites; // include: global, custom, local, person
this.current = {};
this.state = "none"; // include: meta, txt, adapter, none, temp
this.origins = [];
}
/**
* Set global minimatch
*/
SetMinimatch( value ) {
minimatch = value;
}
/**
* Set global rdability
*/
SetRdability( value ) {
rdability = value;
}
/**
* Not adapter usage mozilla readability and readtmpl
*/
Readability() {
try {
const location = document.location,
uri = {
spec: location.href,
host: location.host,
prePath: location.protocol + "//" + location.host,
scheme: location.protocol.substr(0, location.protocol.indexOf(":")),
pathBase: location.protocol + "//" + location.host + location.pathname.substr(0, location.pathname.lastIndexOf("/") + 1)
},
article = new rdability.Readability( uri, document.cloneNode(true) ).parse();
if ( article && article.content != "" ) {
console.warn( "current parse is Readability", article )
this.Newsite( "read", article.content );
this.dom = $(article.content)[0];
this.state = "temp";
} else throw "Readability error";
} catch ( error ) {
const $dom = readtmpl();
if ( $dom != -1 ) {
this.Newsite( "read", $dom[0].outerHTML );
this.dom = $dom[0];
this.state = "temp";
} else this.current.site = util.clone( site );
}
}
/**
* Get site from url
*
* @param {string} include: global, custom, local
* @param {string} url
*/
Getsite( type, url ) {
return this.sites[type].find( item => item[0] == url );
}
/**
* Get sites from url
*/
Getsites() {
const matching = [],
meta = readmeta();
this.current.url = this.url;
if ( meta ) {
this.current.auto = meta.auto;
this.current.url = meta.url;
delete meta.auto;
delete meta.url;
this.current.site = { ...meta };
this.current.site.name.startsWith( "metaread::" ) && ( this.state = "meta" );
this.current.site.name.startsWith( "txtread::" ) && ( this.state = "txt" );
} else {
getsite( "local", new Map( this.sites.local ), this.url, matching );
getsite( "global", new Map( this.sites.global ), this.url, matching );
getsite( "person", new Map( this.sites.person ), this.url, matching );
getsite( "custom", new Map( this.sites.custom ), this.url, matching );
if ( matching.length > 0 ) {
let found;
matching.forEach( site => {
if ( site[1].active ) {
found = site;
this.current.url = found[0];
this.current.site = this.Safesite({ ...found[1] }, found[2], found[0] );
this.state = "adapter";
}
});
if ( !found ) {
const found = matching[0];
found[1].active = true;
this.current.url = found[0];
this.current.site = this.Safesite({ ...found[1] }, found[2], found[0] );
this.state = "adapter";
}
} else {
const obj = readmulti();
if ( obj != -1 ) {
this.Newmultisite( "read", obj );
this.state = "temp";
} else {
this.Readability();
}
}
}
this.current.site.matching = matching;
}
/**
* Add new sites to this.sites.global( global sites )
*
* @param {object} sites.[array]
* @return {int} update sites count
*/
Addsites( result ) {
let count = 0;
if ( this.sites.global.length == 0 ) {
this.sites.global = this.Formatsites( result );
count = this.sites.global.length;
}
else {
const obj = addsites( this.Formatsites( result ), this.sites.global );
count = obj.count;
this.sites.global = obj.newsites;
}
return count;
}
/**
* Add new sites to this.sites.local( local sites )
*
* @param {object} new sites
* @return {array} this.sites.local
*/
Addlocalsites( new_sites ) {
this.sites.local = [ ...new_sites ];
return this.sites.local;
}
/**
* Add all sites to this.sites
*
* @param {object} new sites
* @return {object} this.sites
*/
Addallsites( sites ) {
this.sites = {
global: [ ...sites.global ],
person: [ ...sites.person ],
custom: [ ...sites.custom ],
local : [ ...sites.local ],
};
return this.sites;
}
/**
* Add new site( read only )
*
* @param {string} include: focus, read
* @param {string} when read html is dom.outerHTML
*/
Newsite( mode, html ) {
const new_site = { mode, url: window.location.href, site: { name: `tempread::${window.location.host}`, title: "<title>", desc: "", include: "", exclude: [] } };
html && ( new_site.site.html = html );
this.current.mode = new_site.mode,
this.current.url = new_site.url;
this.current.site = this.Safesite({ ...new_site.site }, "local", new_site.url );
console.log( "【read only】current site object is ", this.current )
}
/**
* Add new multi-site( read only )
*
* @param {string} include: focus, read
* @param {object} multi-page, avator, include
*/
Newmultisite( mode, multi ) {
const new_site = { mode, url: window.location.href, site: { name: `tempread::${window.location.host}`, title: "<title>", desc: "", include: multi.include, exclude: [], avatar: multi.avatar } };
this.current.mode = new_site.mode,
this.current.url = new_site.url;
this.current.site = this.Safesite({ ...new_site.site }, "local", new_site.url );
console.log( "【read only】current multi-site object is ", this.current )
}
/**
* Update url and site from param
*
* @param {string} value is: global, custom, local
* @param {string} older url
* @param {array} [ url, new site]
*/
Updatesite( key, older, newer ) {
let idx = this.sites[key].findIndex( item => item[0] == older );
idx == -1 && ( idx = this.sites[key].length );
this.sites[key].splice( idx, 1, newer );
}
/**
* Delete site from this.sites.local
*
* @param {string} value is: global, custom, local
* @param {string} older url
* @param {func} callback
*/
Deletesite( key, older, callback ) {
let idx = this.sites[key].findIndex( item => item[0] == older );
idx != -1 && this.sites[key].splice( idx, 1 );
callback( idx );
}
/**
* Safe site, add all site props
*
* @param {object} modify site
* @param {string} target include: global custom local
* @param {string} url
* @returns {object} site
*/
Safesite( site, target, url ) {
site.url = url;
site.target = target;
site.name == "" && ( site.name = "tempread::" );
( !site.avatar || site.avatar.length == 0 ) && ( site.avatar = [{ name: "" }, { url: "" }]);
( !site.paging || site.paging.length == 0 ) && ( site.paging = [{ prev: "" }, { next: "" }]);
return site;
}
/**
* Clean useless site props
*
* @param {object} site
* @returns {object} site
*/
Cleansite( site ) {
delete site.url;
delete site.html;
delete site.target;
delete site.matching;
site.avatar && site.avatar.length > 0 && site.avatar[0].name == "" && delete site.avatar;
site.paging && site.paging.length > 0 && site.paging[0].prev == "" && delete site.paging;
return site;
}
/**
* Format sites object from local or remote json file
*
* @param {object} sites.[array]
* @return {array} foramat e.g. [[ <url>, object ],[ <url>, object ]]
*/
Formatsites( result ) {
const format = new Map();
for ( let site of result.sites ) {
if ( verifysite( site ) != 0 ) continue;
const url = site.url;
delete site.url;
format.set( url, site );
}
return [ ...format ];
}
/**
* Clear sites
*
* @param {string} site type, only include: global, custom. local
*/
Clearsites( type ) {
type ? ( this.sites[type] = [] ) : ( this.sites = { global:[], custom:[], local:[] });
}
/**
* Add urls to origins
*
* @param {json} result json
*/
Origins( result ) {
let urls = result.origins.map( item => item.url );
urls = new Set( this.origins.concat( urls ) );
urls.forEach( item => {
if ( item.trim() == "" || !item.trim().startsWith( "http" ) || !item.trim().endsWith( ".json" ) ) urls.delete( item );
});
this.origins = [ ...urls ];
return this.origins;
}
/**
* Add new sites to this.sites.custom( custom sites )
*
* @param {object} new sites
* @return {array} this.sites.custom
*/
Addorigins( new_sites ) {
this.sites.custom = [ ...new_sites ];
return this.sites.custom;
}
/**
* Clear origins
*
* @returns custom.length
*/
Clearorigins() {
const len = this.sites.custom.length;
this.sites.custom = [];
return len;
}
}
/**
* Get readmeta, inlcude: txtread and metaread
*
* @return {object} meata data or undefined
*/
function readmeta() {
if ( minimatch( location.href, "file://**/*.txt" ) || minimatch( location.href, "http*://**/*.txt" ) ) {
return readtxt();
}
const reg = /<\S+ (class|id)=("|')?[\w-_=;:' ]+("|')?>?$|<[^/][-_a-zA-Z0-9]+>?$/ig, // from util.verifyHtml()
meta = {
name : $( "meta[name='simpread:name']" ).attr( "content" ),
url : $( "meta[name='simpread:url']" ).attr( "content" ),
title : $( "meta[name='simpread:title']" ).attr( "content" ),
desc : $( "meta[name='simpread:desc']" ).attr( "content" ),
include: $( "meta[name='simpread:include']" ).attr( "content" ),
exp : $( "meta[name='simpread:exclude']" ).attr( "content" ),
auto : $( "meta[name='simpread:auto']" ).attr( "content" ),
exclude: [],
};
if ( meta.name && meta.include ) {
if ( meta.url && !minimatch( location.href, meta.url )) {
return undefined;
}
!meta.title && ( meta.title = "<title>" );
!meta.desc && ( meta.desc = "" );
!meta.exp && ( meta.exp = "" );
meta.name = `metaread::${meta.name}`;
meta.auto = meta.auto == "true" ? true : false;
const idx = [ "title", "desc", "include", "exp" ].findIndex( item => meta[item] != "" && !meta[item].match( reg ));
meta.exclude.push( meta.exp );
delete meta.exp;
console.assert( idx == -1, "meta read mode error. ", meta )
return idx == -1 ? meta : undefined;
} else {
console.warn( "current not found meta data", meta )
return undefined;
}
}
/**
* Read txt, include: file and http
*/
function readtxt() {
const title = location.pathname.split( "/" ).pop(),
type = location.protocol == "file:" ? "local" : "remote",
meta = {
name : `txtread::${type}`,
title : "<title>",
desc : "",
include: "<pre>",
auto : false,
exclude: [],
};
if ( type == "remote" ) {
meta.include = "";
meta.html = $( "body pre" ).html().replace( /\n/ig, "<br>" );
}
!$( "title" ).html() && $( "head" ).append( `<title>${ decodeURI(title.replace( ".txt", "" )) }</title>` );
return meta;
}
/**
* Read mode template, include:
*
* - Hexo
* - WordPress
* - Common( include <article> )
*
* @return {jquery} jquery object
*/
function readtmpl() {
const $root = $( "body" ),
selectors = [
".post-content", ".entry-content", ".post-article", ".content-post", ".article-entry", ".article-content",
".article-body", ".markdown-body",
"[itemprop='articleBody']",
"article",
".post", ".content",
];
for ( const selector of selectors ) {
const $target = $root.find( selector );
if ( $target.length > 0 ) {
console.warn( "current selector is", selector );
return $target;
}
}
return -1;
}
/**
* Read mode multi template, include:
*
* - Discuz
* - Discourse
*
* @return {object} true: object; false: -1
*/
function readmulti() {
if ( location.pathname.includes( "thread" ) || location.pathname.includes( "forum.php" ) ) {
if ( $('.t_f').length > 0 && $('.favatar').find('.authi').length > 0 && $('.avatar').find('img').length > 0 ) {
return {
avatar: [
{"name" : "[[{$('.favatar').find('.authi')}]]"},
{"url" : "[[{$('.avatar').find('img')}]]"}
],
include: "[[{$('.t_f')}]]"
};
}
} else if ( /\/t\/[\w-]+\/\d+/.test( location.pathname ) && $('meta[name=generator]').attr("content").includes("discourse") ) {
return {
avatar: [
{"name" : "[[{$('.topic-avatar').find('.a[data-user-card]')}]]"},
{"url" : "[[{$('.topic-avatar').find('img')}]]"}
],
include: "[[{$('.cooked')}]]"
};
}
return -1;
}
/**
* Add new sites to old sites
*
* @param {array} new sites from local or remote
* @param {array} current sites from this.sites.global
* @return {object} count: new sites; forced: update sites( discard, all site must be forced update)
*/
function addsites( newsites, old ) {
const oldsites = new Map( old ),
urls = [ ...oldsites.keys() ];
let [ count, forced ] = [ 0, 0 ];
newsites.map( site => {
if ( !urls.includes( site[0] ) ) {
count++;
} else if ( urls.includes( site[0] )) {
forced++;
}
});
return { count, newsites };
}
/**
* Find site by url from sites
*
* @param {string} type, include: global, local, custom
* @param {map} sites
* @param {string} url
* @param {array} matching sites
*
* @return {array} 0: current site; 1: current url 2: type
*/
function getsite( type, sites, url, matching = [] ) {
const domain = (names)=>{
const arr = names.replace( "www.", "" ).match( /\.\S+\.\S+/g );
if ( arr ) {
return arr[0].substr(1);
} else {
return names.replace( "www.", "" );
}
},
urls = [ ...sites.keys() ],
arr = url.match( /[.a-zA-z0-9-_]+/g ),
uri = arr[1].replace( "www.", "" ),
hostname = domain( window.location.hostname ),
isroot = ()=>window.location.pathname == "/" || /\/(default|index|portal).[0-9a-zA-Z]+$/.test(window.location.pathname);
for ( const cur of urls ) {
const name = sites.get(cur).name,
sufname= domain( name );
if ( !isroot() && !cur.endsWith( "*" ) && cur.replace( /^http[s]?:/, "" ) == url.replace( /^http[s]?:/, "" ) ) {
matching.push( [ cur, util.clone( sites.get( cur )), type ] );
} else if ( cur.match( /\*/g ) && cur.match( /\*/g ).length == 1 && !isroot() && cur.endsWith( "*" ) && uri.includes( sufname ) && hostname == sufname && url.includes( name ) ) {
// e.g. https://www.douban.com/* http://mp.weixin.qq.com/*
matching.push( [ cur, util.clone( sites.get( cur )), type ] );
} else if ( minimatch( window.location.origin + window.location.pathname, cur ) ) {
matching.push( [ cur, util.clone( sites.get( cur )), type ] );
}
}
}
/**
* Verify site validity, include:
* - name, url, include, error is -1
* - title include desc, error is -2
* - paging, error is -3 ~ -6
* - avatar, error is -7 ~ -10
*
* @param {object} site
*/
function verifysite( site ) {
if ( !site.name || !site.url || !site.include ) return -1;
if ( util.verifyHtml( site.title )[0] == -1 ||
util.verifyHtml( site.include )[0] == -1 ||
util.verifyHtml( site.desc )[0] == -1
) {
return -2;
}
if ( site.paging ) {
if ( site.paging.length != 2 ) return -3;
if ( !site.paging[0].prev ) return -4;
if ( !site.paging[1].next ) return -5;
if ( util.verifyHtml( site.paging[0].prev )[0] == -1 || util.verifyHtml( site.paging[1].next )[0] == -1 ) {
return -6;
}
}
if ( site.avatar ) {
if ( site.avatar.length != 2 ) return -7;
if ( !site.avatar[0].name ) return -8;
if ( !site.avatar[1].url ) return -9;
if ( util.verifyHtml( site.avatar[0].name )[0] == -1 || util.verifyHtml( site.avatar[1].url )[0] == -1 ) {
return -10;
}
}
return 0;
}

View File

@ -1,32 +0,0 @@
console.log( "=== PureRead: Plugin load ===" )
import pangu from './plugin/pangu.min';
import minimatch from './plugin/minimatch';
import * as be from './plugin/beautify';
import * as ss from './plugin/stylesheet';
import * as rda from './plugin/readability';
const plugins = {
pangu : pangu,
minimatch: minimatch,
beautify : be,
style : ss,
rdability: rda,
};
/**
* Get PureRead Plugin
*
* @param {string} plugin name, when undefined, return all plugin
* @return {object} all plugins
* {string} plgin
*/
export function Plugin( type ) {
let result;
if ( type == undefined ) {
result = plugins;
} else {
result = plugins[type];
}
return result;
}

View File

@ -1,385 +0,0 @@
console.log( "=== PureRead: Beautify load ===" )
/**
* Beautify html
*
* @param {string} storage.current.site.name
*/
function specbeautify( name, $target ) {
switch ( name ) {
case "sspai.com":
//TO-DO
$target.find( ".relation-apps" ).remove();
$target.find( ".ss-app-card" ).remove();
break;
case "post.smzdm.com":
$target.find( "img.face" ).addClass( "sr-rd-content-nobeautify" );
$target.find( ".insert-outer img" ).addClass( "sr-rd-content-nobeautify" );
break;
case "infoq.com":
$target.find( "img" ).map( (index, item) => {
if ( $(item).css("float") == "left" ) {
$(item).addClass( "sr-rd-content-nobeautify" );
}
});
$target.find( "script" ).remove();
break;
case "appinn.com":
$target.find( ".emoji" ).addClass( "sr-rd-content-nobeautify" );
break;
case "hacpai.com":
$target.find( ".emoji" ).addClass( "sr-rd-content-nobeautify" );
break;
case "douban.com":
$target.find( ".review-content" ).children().unwrap();
$target.find( "table" ).addClass( "sr-rd-content-center" );
$target.find( "p" ).css({"white-space": "pre-wrap"});
$target.find( ".cc" ).removeClass();
break;
case "qdaily.com":
$target.find( "img" ).map( (index, item) => {
const $target = $(item),
height = Number.parseInt($target.css("height"));
if ( height == 0 ) $target.remove();
});
$target.find( ".com-insert-images" ).map( (index, item) => {
const $target = $(item),
imgs = $target.find( "img" ).map( (index, item)=>`<div>${item.outerHTML}</div>` ),
str = imgs.get().join( "" );
$target.empty().removeAttr( "class" ).append( str );
});
$target.find( ".com-insert-embed" ).remove();
break;
case "news.mtime.com":
$target.find( ".newspictool" ).map( ( index, item ) => {
const $target = $(item),
$img = $target.find( "img" ),
$label = $target.find( "p:last" );
$target.removeAttr( "class" ).addClass( "sr-rd-content-center" ).empty().append( $img ).append( $label );
});
break;
case "blog.csdn.net":
$target.find( ".save_code" ).remove();
$target.find( ".pre-numbering" ).remove();
$target.find( "pre" ).removeAttr( "style" ).removeAttr( "class" );
$target.find( "code" ).removeAttr( "style" );
$target.find( ".dp-highlighter" ).map( ( index, item )=> {
$(item).find(".bar .tools").remove();
if ( $(item).next().is( "pre" )) $(item).next().remove();
});
break;
case "news.sohu.com":
$target.find( ".conserve-photo" ).remove();
$target.find( "table" ).addClass( "sr-rd-content-center" );
break;
case "qq.com":
$target.find( ".rv-root-v2, #backqqcom" ).remove();
break;
case "azofreeware.com":
$target.find( "iframe" ).remove();
break;
case "apprcn.com":
$target.find( "img" ).map( ( index, item ) => {
const $target = $(item),
src = $target.attr( "src" );
if ( src && src.includes( "Apprcn_Wechat_Small.jpeg" ) ) $target.parent().remove();
});
$target.find( "a" ).map( ( index, item ) => {
const $target = $(item),
text = $target.text();
if ( text == "来自反斗软件" ) $target.parent().remove();
});
break;
case "tieba.baidu.com":
$target.find( ".BDE_Smiley" ).addClass( "sr-rd-content-nobeautify" );
$target.find( ".replace_div" ).removeAttr( "class" ).removeAttr( "style" );
$target.find( ".replace_tip" ).remove();
$target.find( ".d_post_content, .j_d_post_content, .post_bubble_top, .post_bubble_middle, .post_bubble_bottom" ).map( ( idx, target ) => {
$( target ).removeAttr( "class" ).removeAttr( "style" );
});
$( "body" ).find( ".p_author_face" ).map( ( idx, target ) => {
const $target = $( target ).find( "img" ),
src = $target.attr( "data-tb-lazyload" ),
name = $target.attr( "username" );
src && $( "sr-rd-mult-avatar" ).find( "span" ).map( ( idx, span ) => {
const $span = $( span ),
text = $span.text();
if ( text == name ) {
$span.parent().find( "img" ).attr( "src", src );
}
});
});
break;
case "jingyan.baidu.com":
$target.find( ".exp-image-wraper" ).removeAttr( "class" ).removeAttr( "href" );
break;
case "question.zhihu.com":
$target.find( ".zu-edit-button" ).remove();
$target.find( "a.external" ).map( ( idx, target ) => {
$( target ).removeAttr( "class" )
.attr( "style", "border: none;" );
});
$target.find( ".VagueImage" ).map( ( idx, target ) => {
const $target = $( target ),
src = $target.attr( "data-src" );
$target.replaceWith( `<img class="sr-rd-content-img" src="${ src }" style="zoom: 0.6;">` )
});
break;
case "chiphell.com":
$target.find( "img" ).map( ( index, item ) => {
const $target = $(item),
$parent = $target.parent(),
src = $target.attr( "src" ),
smilieid= $target.attr( "smilieid" );
if ( $parent.is( "ignore_js_op" )) $target.unwrap();
smilieid && src && src.includes( "static/image/smiley" ) &&
$target.addClass( "sr-rd-content-nobeautify" ).attr( "style", "width: 50px;" );
});
$target.find( ".quote" ).remove();
break;
case "jiemian.com":
$target.find( "script" ).remove();
break;
case "36kr.com":
$target.find( ".load-html-img" ).removeAttr( "class" );
break;
case "cnblogs.com":
$target.find( ".cnblogs_code" ).removeClass();
$target.find( ".cnblogs_code_hide" ).removeClass().removeAttr( "style" );
$target.find( ".cnblogs_code_toolbar" ).remove();
$target.find( ".code_img_opened" ).remove();
$target.find( ".code_img_closed" ).remove();
break;
case "news.cnblogs.com":
$target.find( ".topic_img" ).remove();
break;
case "g-cores.com":
$target.find( ".swiper-slide-active" ).find( "img" ).map( ( index, item ) => {
const $target = $(item);
$target.parent().parent().parent().parent().parent().parent().removeAttr( "class" ).removeAttr( "style" ).html($target);
});
break;
case "feng.com":
$target.find( "span" ).removeAttr( "style" );
break;
case "young.ifeng.com":
$target.find( "span" ).removeAttr( "style" );
break;
case "ftchinese.com":
$target.find( "script" ).remove();
break;
case "segmentfault.com":
$target.find( ".widget-codetool" ).remove();
break;
case "mp.weixin.qq.com":
$target.find( 'section[powered-by="xiumi.us"]' ).find( "img" ).map( ( index, item ) => {
const $target = $(item),
src = $target.attr( "data-src" );
$target.addClass( "sr-rd-content-nobeautify" ).attr( "src", src );
});
break;
case "ruby-china.org":
$target.find( ".twemoji" ).remove();
break;
case "w3cplus.com":
$target.find( "iframe" ).addClass( "sr-rd-content-nobeautify" );
break;
case "zuojj.com":
$target.find( ".syntaxhighlighter .Brush" ).attr( "style", "font-size: .7em !important;" )
break;
case "aotu.io":
$target.find( ".highlight table" ).map( ( index, item ) => {
const $target = $(item),
$pre = $target.find( "pre" ),
$table = $target.find( "table" );
$target.html( $pre[1] );
$table.unwrap();
});
$target.find( "table" ).addClass( "sr-rd-content-center" );
break;
case "colobu.com":
$target.find( ".highlight table" ).map( ( index, item ) => {
const $target = $(item),
$pre = $target.find( "pre" );
$target.html( $pre[1] );
$target.unwrap();
});
break;
case "hao.caibaojian.com":
$target.find( ".tlink" ).map( ( index, item ) => {
const $target = $(item);
$target.html( "<link>" );
});
break;
case "wkee.net":
$target.find( "script" ).remove();
break;
case "linux.cn":
$target.find( "pre" ).attr( "style", "background-color: #161b20; background-image: none;" );
$target.find( "code" ).attr( "style", "background-color: transparent; background-image: none;" );
break;
case "zhuanlan.zhihu.com":
$target.find( "div[data-src]" ).map( ( index, item ) => {
const $target = $(item),
src = $target.attr( "data-src" );
$target.replaceWith( `<div class="sr-rd-content-center"><img src="${ src }"></div>` );
});
break;
case "jianshu.com":
$target.find( ".image-package" ).map( ( index, item ) => {
const $target = $( item ),
$div = $target.find( "img" );
$target.html( $div );
});
break;
case "medium.com":
$target.find( "figure" ).map( ( index, item ) => {
const $target = $(item),
$img = $target.find( "img" );
$target.replaceWith( `<div class="sr-rd-content-center"><img class="sr-rd-content-nobeautify" src="${ $img.attr('data-src') }" style="max-width:100%"></div>` );
});
break;
case "worldcup.fifa.com":
$target.find( "iframe" ).css({ width: "790px", height: "450px" });
$target.find( "div" ).removeClass();
break;
}
}
/**
* Remove spare tag
*
* @param {string} storage.current.site.name
* @param {jquery} jquery object
*/
function removeSpareTag( name, $target ) {
let [ remove, tag ] = [ false, "" ];
if ([ "lib.csdn.net", "huxiu.com", "my.oschina.net", "caixin.com", "163.com", "steachs.com", "hacpai.com", "apprcn.com", "mp.weixin.qq.com" ].includes( name )) {
[ remove, tag ] = [ true, "p" ];
} else if ([ "nationalgeographic.com.cn", "dgtle.com", "news.mtime.com" ].includes( name )) {
[ remove, tag ] = [ true, "div" ];
} else if ( ["chiphell.com"].includes( name ) ) {
[ remove, tag ] = [ true, "font" ];
}
if ( remove ) {
$target.find( tag ).map( ( index, item ) => {
const str = $(item).text().toLowerCase().trim();
if ( $(item).find( "img" ).length == 0 && str == "" ) $(item).remove();
});
}
}
/**
* Beautify html, incldue:
*
* - change all <blockquote> to <sr-blockquote>
* - remove useless <br>
*
* @param {jquery} jquery object
*/
function htmlbeautify( $target ) {
try {
$target.html( ( index, html ) => {
return html.trim()
.replace( /<\/?blockquote/g, (value) => value[1] == "/" ? "</sr-blockquote" : "<sr-blockquote" )
.replace( /<br>\n?<br>(\n?<br>)*/g, "<br>" )
.replace( /\/(div|p)>\n*(<br>\n)+/g, (value) =>value.replace( "<br>", "" ));
});
} catch ( error ) {
console.error( error );
return $target.html();
}
}
/**
* Common Beautify html, include:
* - task: all webiste image, remove old image and create new image
* - task: all webiste sr-blockquote, remove style
* - task: all webiste iframe, embed add center style
* - task: all hr tag add simpread-hidden class
* - task: all pre/code tag remove class
* - task: all a tag remove style
*
* @param {jquery}
*/
function commbeautify( name, $target ) {
$target.find( "img:not(.sr-rd-content-nobeautify)" ).map( ( index, item ) => {
const $target = $(item),
$orgpar = $target.parent(),
$img = $( "<img class='sr-rd-content-img-load'>" ),
src = $target.attr( "src" ),
lazysrc = $target.attr( "data-src" ),
zuimei = $target.attr( "data-original" ),
cnbeta = $target.attr( "original" ),
jianshu = $target.attr( "data-original-src" ),
sina = $target.attr( "real_src" ),
fixOverflowImgsize = () => {
$img.removeClass( "sr-rd-content-img-load" );
if ( $img[0].clientWidth > 1000 ) {
$img.css( "zoom", "0.6" );
}
else if ( $img[0].clientHeight > 620 ) {
if ( /win|mac/i.test(navigator.platform) ) {
$img.attr( "height", 620 );
if ( $img[0].clientWidth < $("sr-rd-content").width()) $img.css({ "width":"auto" });
}
}
if ( $img[0].clientWidth > $("sr-rd-content").width()) $img.addClass( "sr-rd-content-img" );
},
loaderrorHandle = () => {
$img.addClass( "simpread-hidden" );
if ( $img.parent().hasClass( "sr-rd-content-center" )) {
$img.parent().removeAttr( "class" ).addClass( "simpread-hidden" );
}
};
let newsrc,
$parent = $target.parent(),
tagname = $parent[0].tagName.toLowerCase();
// remove current image and create new image object
newsrc = cnbeta ? cnbeta : src;
newsrc = lazysrc ? lazysrc : newsrc;
newsrc = zuimei ? zuimei : newsrc;
newsrc = jianshu ? jianshu : newsrc;
newsrc = sina ? sina : newsrc;
// hack code
location.host.includes( "infoq.com" ) && ( newsrc = src );
!newsrc.startsWith( "http" ) && ( newsrc = newsrc.startsWith( "//" ) ? location.protocol + newsrc : location.origin + newsrc );
$img.attr( "src", newsrc )
.replaceAll( $target )
.wrap( "<div class='sr-rd-content-center'></div>" );
if ( !/win|mac/i.test(navigator.platform) ) {
$img.on( "load", ()=>fixOverflowImgsize() )
.on( "error", ()=>loaderrorHandle() );
} else {
$img.one( "load", ()=>fixOverflowImgsize() )
.one( "error", ()=>loaderrorHandle() );
}
});
$target.find( "sr-blockquote" ).map( ( index, item ) => {
const $target = $(item),
$parent = $target.parent();
$target.removeAttr( "style" ).removeAttr( "class" );
if ( name == "dgtle.com" ) {
$parent.removeClass( "quote" );
}
});
$target.find( "iframe:not(.sr-rd-content-nobeautify), embed:not(.sr-rd-content-nobeautify)" ).map( ( index, item )=> {
$(item).wrap( "<div class='sr-rd-content-center'></div>" );
});
$target.find( "hr" ).map( ( index, item )=> {
$(item).addClass( "simpread-hidden" );
});
$target.find( "pre" ).map( ( index, item )=> {
$(item).find( "code" ).removeAttr( "class" );
});
$target.find( "pre" ).removeAttr( "class" );
$target.find( "a" ).removeAttr( "style" );
}
export {
specbeautify,
removeSpareTag,
htmlbeautify,
commbeautify,
}

View File

@ -1,923 +0,0 @@
module.exports = minimatch
minimatch.Minimatch = Minimatch
var path = { sep: '/' }
try {
path = require('path')
} catch (er) {}
var GLOBSTAR = minimatch.GLOBSTAR = Minimatch.GLOBSTAR = {}
var expand = require('brace-expansion')
var plTypes = {
'!': { open: '(?:(?!(?:', close: '))[^/]*?)'},
'?': { open: '(?:', close: ')?' },
'+': { open: '(?:', close: ')+' },
'*': { open: '(?:', close: ')*' },
'@': { open: '(?:', close: ')' }
}
// any single thing other than /
// don't need to escape / when using new RegExp()
var qmark = '[^/]'
// * => any number of characters
var star = qmark + '*?'
// ** when dots are allowed. Anything goes, except .. and .
// not (^ or / followed by one or two dots followed by $ or /),
// followed by anything, any number of times.
var twoStarDot = '(?:(?!(?:\\\/|^)(?:\\.{1,2})($|\\\/)).)*?'
// not a ^ or / followed by a dot,
// followed by anything, any number of times.
var twoStarNoDot = '(?:(?!(?:\\\/|^)\\.).)*?'
// characters that need to be escaped in RegExp.
var reSpecials = charSet('().*{}+?[]^$\\!')
// "abc" -> { a:true, b:true, c:true }
function charSet (s) {
return s.split('').reduce(function (set, c) {
set[c] = true
return set
}, {})
}
// normalizes slashes.
var slashSplit = /\/+/
minimatch.filter = filter
function filter (pattern, options) {
options = options || {}
return function (p, i, list) {
return minimatch(p, pattern, options)
}
}
function ext (a, b) {
a = a || {}
b = b || {}
var t = {}
Object.keys(b).forEach(function (k) {
t[k] = b[k]
})
Object.keys(a).forEach(function (k) {
t[k] = a[k]
})
return t
}
minimatch.defaults = function (def) {
if (!def || !Object.keys(def).length) return minimatch
var orig = minimatch
var m = function minimatch (p, pattern, options) {
return orig.minimatch(p, pattern, ext(def, options))
}
m.Minimatch = function Minimatch (pattern, options) {
return new orig.Minimatch(pattern, ext(def, options))
}
return m
}
Minimatch.defaults = function (def) {
if (!def || !Object.keys(def).length) return Minimatch
return minimatch.defaults(def).Minimatch
}
function minimatch (p, pattern, options) {
if (typeof pattern !== 'string') {
throw new TypeError('glob pattern string required')
}
if (!options) options = {}
// shortcut: comments match nothing.
if (!options.nocomment && pattern.charAt(0) === '#') {
return false
}
// "" only matches ""
if (pattern.trim() === '') return p === ''
return new Minimatch(pattern, options).match(p)
}
function Minimatch (pattern, options) {
if (!(this instanceof Minimatch)) {
return new Minimatch(pattern, options)
}
if (typeof pattern !== 'string') {
throw new TypeError('glob pattern string required')
}
if (!options) options = {}
pattern = pattern.trim()
// windows support: need to use /, not \
if (path.sep !== '/') {
pattern = pattern.split(path.sep).join('/')
}
this.options = options
this.set = []
this.pattern = pattern
this.regexp = null
this.negate = false
this.comment = false
this.empty = false
// make the set of regexps etc.
this.make()
}
Minimatch.prototype.debug = function () {}
Minimatch.prototype.make = make
function make () {
// don't do it more than once.
if (this._made) return
var pattern = this.pattern
var options = this.options
// empty patterns and comments match nothing.
if (!options.nocomment && pattern.charAt(0) === '#') {
this.comment = true
return
}
if (!pattern) {
this.empty = true
return
}
// step 1: figure out negation, etc.
this.parseNegate()
// step 2: expand braces
var set = this.globSet = this.braceExpand()
if (options.debug) this.debug = console.error
this.debug(this.pattern, set)
// step 3: now we have a set, so turn each one into a series of path-portion
// matching patterns.
// These will be regexps, except in the case of "**", which is
// set to the GLOBSTAR object for globstar behavior,
// and will not contain any / characters
set = this.globParts = set.map(function (s) {
return s.split(slashSplit)
})
this.debug(this.pattern, set)
// glob --> regexps
set = set.map(function (s, si, set) {
return s.map(this.parse, this)
}, this)
this.debug(this.pattern, set)
// filter out everything that didn't compile properly.
set = set.filter(function (s) {
return s.indexOf(false) === -1
})
this.debug(this.pattern, set)
this.set = set
}
Minimatch.prototype.parseNegate = parseNegate
function parseNegate () {
var pattern = this.pattern
var negate = false
var options = this.options
var negateOffset = 0
if (options.nonegate) return
for (var i = 0, l = pattern.length
; i < l && pattern.charAt(i) === '!'
; i++) {
negate = !negate
negateOffset++
}
if (negateOffset) this.pattern = pattern.substr(negateOffset)
this.negate = negate
}
// Brace expansion:
// a{b,c}d -> abd acd
// a{b,}c -> abc ac
// a{0..3}d -> a0d a1d a2d a3d
// a{b,c{d,e}f}g -> abg acdfg acefg
// a{b,c}d{e,f}g -> abdeg acdeg abdeg abdfg
//
// Invalid sets are not expanded.
// a{2..}b -> a{2..}b
// a{b}c -> a{b}c
minimatch.braceExpand = function (pattern, options) {
return braceExpand(pattern, options)
}
Minimatch.prototype.braceExpand = braceExpand
function braceExpand (pattern, options) {
if (!options) {
if (this instanceof Minimatch) {
options = this.options
} else {
options = {}
}
}
pattern = typeof pattern === 'undefined'
? this.pattern : pattern
if (typeof pattern === 'undefined') {
throw new TypeError('undefined pattern')
}
if (options.nobrace ||
!pattern.match(/\{.*\}/)) {
// shortcut. no need to expand.
return [pattern]
}
return expand(pattern)
}
// parse a component of the expanded set.
// At this point, no pattern may contain "/" in it
// so we're going to return a 2d array, where each entry is the full
// pattern, split on '/', and then turned into a regular expression.
// A regexp is made at the end which joins each array with an
// escaped /, and another full one which joins each regexp with |.
//
// Following the lead of Bash 4.1, note that "**" only has special meaning
// when it is the *only* thing in a path portion. Otherwise, any series
// of * is equivalent to a single *. Globstar behavior is enabled by
// default, and can be disabled by setting options.noglobstar.
Minimatch.prototype.parse = parse
var SUBPARSE = {}
function parse (pattern, isSub) {
if (pattern.length > 1024 * 64) {
throw new TypeError('pattern is too long')
}
var options = this.options
// shortcuts
if (!options.noglobstar && pattern === '**') return GLOBSTAR
if (pattern === '') return ''
var re = ''
var hasMagic = !!options.nocase
var escaping = false
// ? => one single character
var patternListStack = []
var negativeLists = []
var stateChar
var inClass = false
var reClassStart = -1
var classStart = -1
// . and .. never match anything that doesn't start with .,
// even when options.dot is set.
var patternStart = pattern.charAt(0) === '.' ? '' // anything
// not (start or / followed by . or .. followed by / or end)
: options.dot ? '(?!(?:^|\\\/)\\.{1,2}(?:$|\\\/))'
: '(?!\\.)'
var self = this
function clearStateChar () {
if (stateChar) {
// we had some state-tracking character
// that wasn't consumed by this pass.
switch (stateChar) {
case '*':
re += star
hasMagic = true
break
case '?':
re += qmark
hasMagic = true
break
default:
re += '\\' + stateChar
break
}
self.debug('clearStateChar %j %j', stateChar, re)
stateChar = false
}
}
for (var i = 0, len = pattern.length, c
; (i < len) && (c = pattern.charAt(i))
; i++) {
this.debug('%s\t%s %s %j', pattern, i, re, c)
// skip over any that are escaped.
if (escaping && reSpecials[c]) {
re += '\\' + c
escaping = false
continue
}
switch (c) {
case '/':
// completely not allowed, even escaped.
// Should already be path-split by now.
return false
case '\\':
clearStateChar()
escaping = true
continue
// the various stateChar values
// for the "extglob" stuff.
case '?':
case '*':
case '+':
case '@':
case '!':
this.debug('%s\t%s %s %j <-- stateChar', pattern, i, re, c)
// all of those are literals inside a class, except that
// the glob [!a] means [^a] in regexp
if (inClass) {
this.debug(' in class')
if (c === '!' && i === classStart + 1) c = '^'
re += c
continue
}
// if we already have a stateChar, then it means
// that there was something like ** or +? in there.
// Handle the stateChar, then proceed with this one.
self.debug('call clearStateChar %j', stateChar)
clearStateChar()
stateChar = c
// if extglob is disabled, then +(asdf|foo) isn't a thing.
// just clear the statechar *now*, rather than even diving into
// the patternList stuff.
if (options.noext) clearStateChar()
continue
case '(':
if (inClass) {
re += '('
continue
}
if (!stateChar) {
re += '\\('
continue
}
patternListStack.push({
type: stateChar,
start: i - 1,
reStart: re.length,
open: plTypes[stateChar].open,
close: plTypes[stateChar].close
})
// negation is (?:(?!js)[^/]*)
re += stateChar === '!' ? '(?:(?!(?:' : '(?:'
this.debug('plType %j %j', stateChar, re)
stateChar = false
continue
case ')':
if (inClass || !patternListStack.length) {
re += '\\)'
continue
}
clearStateChar()
hasMagic = true
var pl = patternListStack.pop()
// negation is (?:(?!js)[^/]*)
// The others are (?:<pattern>)<type>
re += pl.close
if (pl.type === '!') {
negativeLists.push(pl)
}
pl.reEnd = re.length
continue
case '|':
if (inClass || !patternListStack.length || escaping) {
re += '\\|'
escaping = false
continue
}
clearStateChar()
re += '|'
continue
// these are mostly the same in regexp and glob
case '[':
// swallow any state-tracking char before the [
clearStateChar()
if (inClass) {
re += '\\' + c
continue
}
inClass = true
classStart = i
reClassStart = re.length
re += c
continue
case ']':
// a right bracket shall lose its special
// meaning and represent itself in
// a bracket expression if it occurs
// first in the list. -- POSIX.2 2.8.3.2
if (i === classStart + 1 || !inClass) {
re += '\\' + c
escaping = false
continue
}
// handle the case where we left a class open.
// "[z-a]" is valid, equivalent to "\[z-a\]"
if (inClass) {
// split where the last [ was, make sure we don't have
// an invalid re. if so, re-walk the contents of the
// would-be class to re-translate any characters that
// were passed through as-is
// TODO: It would probably be faster to determine this
// without a try/catch and a new RegExp, but it's tricky
// to do safely. For now, this is safe and works.
var cs = pattern.substring(classStart + 1, i)
try {
RegExp('[' + cs + ']')
} catch (er) {
// not a valid class!
var sp = this.parse(cs, SUBPARSE)
re = re.substr(0, reClassStart) + '\\[' + sp[0] + '\\]'
hasMagic = hasMagic || sp[1]
inClass = false
continue
}
}
// finish up the class.
hasMagic = true
inClass = false
re += c
continue
default:
// swallow any state char that wasn't consumed
clearStateChar()
if (escaping) {
// no need
escaping = false
} else if (reSpecials[c]
&& !(c === '^' && inClass)) {
re += '\\'
}
re += c
} // switch
} // for
// handle the case where we left a class open.
// "[abc" is valid, equivalent to "\[abc"
if (inClass) {
// split where the last [ was, and escape it
// this is a huge pita. We now have to re-walk
// the contents of the would-be class to re-translate
// any characters that were passed through as-is
cs = pattern.substr(classStart + 1)
sp = this.parse(cs, SUBPARSE)
re = re.substr(0, reClassStart) + '\\[' + sp[0]
hasMagic = hasMagic || sp[1]
}
// handle the case where we had a +( thing at the *end*
// of the pattern.
// each pattern list stack adds 3 chars, and we need to go through
// and escape any | chars that were passed through as-is for the regexp.
// Go through and escape them, taking care not to double-escape any
// | chars that were already escaped.
for (pl = patternListStack.pop(); pl; pl = patternListStack.pop()) {
var tail = re.slice(pl.reStart + pl.open.length)
this.debug('setting tail', re, pl)
// maybe some even number of \, then maybe 1 \, followed by a |
tail = tail.replace(/((?:\\{2}){0,64})(\\?)\|/g, function (_, $1, $2) {
if (!$2) {
// the | isn't already escaped, so escape it.
$2 = '\\'
}
// need to escape all those slashes *again*, without escaping the
// one that we need for escaping the | character. As it works out,
// escaping an even number of slashes can be done by simply repeating
// it exactly after itself. That's why this trick works.
//
// I am sorry that you have to see this.
return $1 + $1 + $2 + '|'
})
this.debug('tail=%j\n %s', tail, tail, pl, re)
var t = pl.type === '*' ? star
: pl.type === '?' ? qmark
: '\\' + pl.type
hasMagic = true
re = re.slice(0, pl.reStart) + t + '\\(' + tail
}
// handle trailing things that only matter at the very end.
clearStateChar()
if (escaping) {
// trailing \\
re += '\\\\'
}
// only need to apply the nodot start if the re starts with
// something that could conceivably capture a dot
var addPatternStart = false
switch (re.charAt(0)) {
case '.':
case '[':
case '(': addPatternStart = true
}
// Hack to work around lack of negative lookbehind in JS
// A pattern like: *.!(x).!(y|z) needs to ensure that a name
// like 'a.xyz.yz' doesn't match. So, the first negative
// lookahead, has to look ALL the way ahead, to the end of
// the pattern.
for (var n = negativeLists.length - 1; n > -1; n--) {
var nl = negativeLists[n]
var nlBefore = re.slice(0, nl.reStart)
var nlFirst = re.slice(nl.reStart, nl.reEnd - 8)
var nlLast = re.slice(nl.reEnd - 8, nl.reEnd)
var nlAfter = re.slice(nl.reEnd)
nlLast += nlAfter
// Handle nested stuff like *(*.js|!(*.json)), where open parens
// mean that we should *not* include the ) in the bit that is considered
// "after" the negated section.
var openParensBefore = nlBefore.split('(').length - 1
var cleanAfter = nlAfter
for (i = 0; i < openParensBefore; i++) {
cleanAfter = cleanAfter.replace(/\)[+*?]?/, '')
}
nlAfter = cleanAfter
var dollar = ''
if (nlAfter === '' && isSub !== SUBPARSE) {
dollar = '$'
}
var newRe = nlBefore + nlFirst + nlAfter + dollar + nlLast
re = newRe
}
// if the re is not "" at this point, then we need to make sure
// it doesn't match against an empty path part.
// Otherwise a/* will match a/, which it should not.
if (re !== '' && hasMagic) {
re = '(?=.)' + re
}
if (addPatternStart) {
re = patternStart + re
}
// parsing just a piece of a larger pattern.
if (isSub === SUBPARSE) {
return [re, hasMagic]
}
// skip the regexp for non-magical patterns
// unescape anything in it, though, so that it'll be
// an exact match against a file etc.
if (!hasMagic) {
return globUnescape(pattern)
}
var flags = options.nocase ? 'i' : ''
try {
var regExp = new RegExp('^' + re + '$', flags)
} catch (er) {
// If it was an invalid regular expression, then it can't match
// anything. This trick looks for a character after the end of
// the string, which is of course impossible, except in multi-line
// mode, but it's not a /m regex.
return new RegExp('$.')
}
regExp._glob = pattern
regExp._src = re
return regExp
}
minimatch.makeRe = function (pattern, options) {
return new Minimatch(pattern, options || {}).makeRe()
}
Minimatch.prototype.makeRe = makeRe
function makeRe () {
if (this.regexp || this.regexp === false) return this.regexp
// at this point, this.set is a 2d array of partial
// pattern strings, or "**".
//
// It's better to use .match(). This function shouldn't
// be used, really, but it's pretty convenient sometimes,
// when you just want to work with a regex.
var set = this.set
if (!set.length) {
this.regexp = false
return this.regexp
}
var options = this.options
var twoStar = options.noglobstar ? star
: options.dot ? twoStarDot
: twoStarNoDot
var flags = options.nocase ? 'i' : ''
var re = set.map(function (pattern) {
return pattern.map(function (p) {
return (p === GLOBSTAR) ? twoStar
: (typeof p === 'string') ? regExpEscape(p)
: p._src
}).join('\\\/')
}).join('|')
// must match entire pattern
// ending in a * or ** will make it less strict.
re = '^(?:' + re + ')$'
// can match anything, as long as it's not this.
if (this.negate) re = '^(?!' + re + ').*$'
try {
this.regexp = new RegExp(re, flags)
} catch (ex) {
this.regexp = false
}
return this.regexp
}
minimatch.match = function (list, pattern, options) {
options = options || {}
var mm = new Minimatch(pattern, options)
list = list.filter(function (f) {
return mm.match(f)
})
if (mm.options.nonull && !list.length) {
list.push(pattern)
}
return list
}
Minimatch.prototype.match = match
function match (f, partial) {
this.debug('match', f, this.pattern)
// short-circuit in the case of busted things.
// comments, etc.
if (this.comment) return false
if (this.empty) return f === ''
if (f === '/' && partial) return true
var options = this.options
// windows: need to use /, not \
if (path.sep !== '/') {
f = f.split(path.sep).join('/')
}
// treat the test path as a set of pathparts.
f = f.split(slashSplit)
this.debug(this.pattern, 'split', f)
// just ONE of the pattern sets in this.set needs to match
// in order for it to be valid. If negating, then just one
// match means that we have failed.
// Either way, return on the first hit.
var set = this.set
this.debug(this.pattern, 'set', set)
// Find the basename of the path by looking for the last non-empty segment
var filename
var i
for (i = f.length - 1; i >= 0; i--) {
filename = f[i]
if (filename) break
}
for (i = 0; i < set.length; i++) {
var pattern = set[i]
var file = f
if (options.matchBase && pattern.length === 1) {
file = [filename]
}
var hit = this.matchOne(file, pattern, partial)
if (hit) {
if (options.flipNegate) return true
return !this.negate
}
}
// didn't get any hits. this is success if it's a negative
// pattern, failure otherwise.
if (options.flipNegate) return false
return this.negate
}
// set partial to true to test if, for example,
// "/a/b" matches the start of "/*/b/*/d"
// Partial means, if you run out of file before you run
// out of pattern, then that's fine, as long as all
// the parts match.
Minimatch.prototype.matchOne = function (file, pattern, partial) {
var options = this.options
this.debug('matchOne',
{ 'this': this, file: file, pattern: pattern })
this.debug('matchOne', file.length, pattern.length)
for (var fi = 0,
pi = 0,
fl = file.length,
pl = pattern.length
; (fi < fl) && (pi < pl)
; fi++, pi++) {
this.debug('matchOne loop')
var p = pattern[pi]
var f = file[fi]
this.debug(pattern, p, f)
// should be impossible.
// some invalid regexp stuff in the set.
if (p === false) return false
if (p === GLOBSTAR) {
this.debug('GLOBSTAR', [pattern, p, f])
// "**"
// a/**/b/**/c would match the following:
// a/b/x/y/z/c
// a/x/y/z/b/c
// a/b/x/b/x/c
// a/b/c
// To do this, take the rest of the pattern after
// the **, and see if it would match the file remainder.
// If so, return success.
// If not, the ** "swallows" a segment, and try again.
// This is recursively awful.
//
// a/**/b/**/c matching a/b/x/y/z/c
// - a matches a
// - doublestar
// - matchOne(b/x/y/z/c, b/**/c)
// - b matches b
// - doublestar
// - matchOne(x/y/z/c, c) -> no
// - matchOne(y/z/c, c) -> no
// - matchOne(z/c, c) -> no
// - matchOne(c, c) yes, hit
var fr = fi
var pr = pi + 1
if (pr === pl) {
this.debug('** at the end')
// a ** at the end will just swallow the rest.
// We have found a match.
// however, it will not swallow /.x, unless
// options.dot is set.
// . and .. are *never* matched by **, for explosively
// exponential reasons.
for (; fi < fl; fi++) {
if (file[fi] === '.' || file[fi] === '..' ||
(!options.dot && file[fi].charAt(0) === '.')) return false
}
return true
}
// ok, let's see if we can swallow whatever we can.
while (fr < fl) {
var swallowee = file[fr]
this.debug('\nglobstar while', file, fr, pattern, pr, swallowee)
// XXX remove this slice. Just pass the start index.
if (this.matchOne(file.slice(fr), pattern.slice(pr), partial)) {
this.debug('globstar found match!', fr, fl, swallowee)
// found a match.
return true
} else {
// can't swallow "." or ".." ever.
// can only swallow ".foo" when explicitly asked.
if (swallowee === '.' || swallowee === '..' ||
(!options.dot && swallowee.charAt(0) === '.')) {
this.debug('dot detected!', file, fr, pattern, pr)
break
}
// ** swallows a segment, and continue.
this.debug('globstar swallow a segment, and continue')
fr++
}
}
// no match was found.
// However, in partial mode, we can't say this is necessarily over.
// If there's more *pattern* left, then
if (partial) {
// ran out of file
this.debug('\n>>> no match, partial?', file, fr, pattern, pr)
if (fr === fl) return true
}
return false
}
// something other than **
// non-magic patterns just have to match exactly
// patterns with magic have been turned into regexps.
var hit
if (typeof p === 'string') {
if (options.nocase) {
hit = f.toLowerCase() === p.toLowerCase()
} else {
hit = f === p
}
this.debug('string match', p, f, hit)
} else {
hit = f.match(p)
this.debug('pattern match', p, f, hit)
}
if (!hit) return false
}
// Note: ending in / means that we'll get a final ""
// at the end of the pattern. This can only match a
// corresponding "" at the end of the file.
// If the file ends in /, then it can only match a
// a pattern that ends in /, unless the pattern just
// doesn't have any more for it. But, a/b/ should *not*
// match "a/b/*", even though "" matches against the
// [^/]*? pattern, except in partial mode, where it might
// simply not be reached yet.
// However, a/b/ should still satisfy a/*
// now either we fell off the end of the pattern, or we're done.
if (fi === fl && pi === pl) {
// ran out of pattern and filename at the same time.
// an exact hit!
return true
} else if (fi === fl) {
// ran out of file, but still had pattern left.
// this is ok if we're doing the match as part of
// a glob fs traversal.
return partial
} else if (pi === pl) {
// ran out of pattern, still have file left.
// this is only acceptable if we're on the very last
// empty segment of a file with a trailing slash.
// a/* should match a/b/
var emptyFileEnd = (fi === fl - 1) && (file[fi] === '')
return emptyFileEnd
}
// should be unreachable.
throw new Error('wtf?')
}
// replace stuff like \* with *
function globUnescape (s) {
return s.replace(/\\(.)/g, '$1')
}
function regExpEscape (s) {
return s.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, '\\$&')
}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -1,198 +0,0 @@
console.log( "=== PureRead: StyleSheet load ===" )
const [ bgcolorstyl, bgcls ] = [ "background-color", ".simpread-focus-root" ];
let origin_read_style = "", html_style_bal = "-1";
/**
* Get background color value for focus mode
*
* @param {string} background-color, e.g. rgba(235, 235, 235, 0.901961)
* @return {string} e.g. 235, 235, 235
*/
function getColor( value ) {
const arr = value ? value.match( /[0-9]+, /ig ) : [];
if ( arr.length > 0 ) {
return arr.join( "" ).replace( /, $/, "" );
} else {
return null;
}
};
/**
* Set focus mode background color for focus mode
*
* @param {string} background color
* @param {number} background opacity
* @return {string} new background color
*/
function backgroundColor( bgcolor, opacity ) {
const color = getColor( bgcolor ),
newval = `rgba(${color}, ${opacity / 100})`;
$( bgcls ).css( bgcolorstyl, newval );
return newval;
}
/**
* Set background opacity for focus mode
*
* @param {string} opacity
* @return {string} new background color or null
*/
function opacity( opacity ) {
const bgcolor = $( bgcls ).css( bgcolorstyl ),
color = getColor( bgcolor ),
newval = `rgba(${color}, ${opacity / 100})`;
if ( color ) {
$( bgcls ).css( bgcolorstyl, newval );
return newval;
} else {
return null;
}
}
/**
* Set read mode font family for read mode
*
* @param {string} font family name e.g. PingFang SC; Microsoft Yahei
*/
function fontFamily( family ) {
$( "sr-read" ).css( "font-family", family == "default" ? "" : family );
}
/**
* Set read mode font size for read mode
*
* @param {string} font size, e.g. 70% 62.5%
*/
function fontSize( value ) {
if ( html_style_bal == "-1" ) {
html_style_bal = $( "html" ).attr( "style" );
html_style_bal == undefined && ( html_style_bal = "" );
}
value ? $( "html" ).attr( "style", `font-size: ${value}!important;${html_style_bal}` ) : $( "html" ).attr( "style", html_style_bal );
}
/**
* Set read mode layout width for read mode
*
* @param {string} layout width
*/
function layout( width ) {
$( "sr-read" ).css( "margin", width ? `20px ${width}` : "" );
}
/**
* Add custom css to <head> for read mode
*
* @param {string} read.custom[type]
* @param {object} read.custom
*/
function custom( type, props ) {
const format = ( name ) => {
return name.replace( /[A-Z]/, name => { return `-${name.toLowerCase()}` } );
},
arr = Object.keys( props ).map( v => {
return props[v] && `${format( v )}: ${ props[v] };`
});
let styles = arr.join( "" );
switch ( type ) {
case "title":
styles = `sr-rd-title {${styles}}`;
break;
case "desc":
styles = `sr-rd-desc {${styles}}`;
break;
case "art":
styles = `sr-rd-content *, sr-rd-content p, sr-rd-content div {${styles}}`;
break;
case "pre":
styles = `sr-rd-content pre {${styles}}`;
break;
case "code":
styles = `sr-rd-content pre code, sr-rd-content pre code * {${styles}}`;
break;
}
const $target = $( "head" ).find( `style#simpread-custom-${type}` );
if ( $target.length == 0 ) {
$( "head" ).append(`<style type="text/css" id="simpread-custom-${type}">${styles}</style>`);
} else {
$target.html( styles );
}
}
/**
* Add css to <head> for read mode
*
* @param {string} read.custom.css
* @param {object} read.custom.css value
*/
function css( type, styles ) {
const $target = $( "head" ).find( `style#simpread-custom-${type}` );
if ( $target.length == 0 ) {
$( "head" ).append(`<style type="text/css" id="simpread-custom-${type}">${styles}</style>`);
} else {
$target.html( styles );
}
}
/**
* Add/Remove current site styles( string ) to head for read mdoe
*
* @param {string} styles
*/
function siteCSS( styles ) {
styles ? $( "head" ).append(`<style type="text/css" id="simpread-site-css">${styles}</style>`) :
$( "#simpread-site-css" ).remove();
}
/**
* Add custom to .preview tag
*
* @param {object} read.custom
* @param {string} theme backgroud color
*/
function preview( styles ) {
Object.keys( styles ).forEach( v => {
v != "css" && custom( v, styles[v] );
});
css( "css", styles["css"] );
}
/**
* Verify custom is exist
*
* @param {string} verify type
* @param {object} read.custom value
*/
function vfyCustom( type, styles ) {
switch( type ) {
case "layout":
case "margin":
case "fontfamily":
case "custom":
return styles.css != "";
case "fontsize":
return styles.title.fontSize != "" ||
styles.desc.fontSize != "" ||
styles.art.fontSize != "" ||
styles.css != "";
case "theme":
return styles.css.search( "simpread-theme-root" ) != -1;
}
}
export {
getColor as GetColor,
backgroundColor as BgColor,
opacity as Opacity,
fontFamily as FontFamily,
fontSize as FontSize,
layout as Layout,
siteCSS as SiteCSS,
preview as Preview,
custom as Custom,
css as CSS,
vfyCustom as VerifyCustom,
}

3424
src/vender/puread/puplugin.min.js vendored Normal file

File diff suppressed because one or more lines are too long

View File

@ -1,231 +0,0 @@
console.log( "=== PureRead: PureRead load ===" )
import * as util from './util';
import AdapteSite from './adaptesite';
export default class PureRead extends AdapteSite {
constructor( sites ) {
super( sites );
this.version = "0.0.3";
this.org_url = location.href;
this.html = {}; // clone site, include: title, desc, include, avatar, paging
this.plugin = {};
}
/**
* Verify current puread is same
*
* @return {boolean} true: same; false: not same;
*/
Exist() {
return this.org_url == location.href;
}
/**
* Add Plugin
*
* @param {object} plugin object
*/
AddPlugin( plugin ) {
this.plugin = {
minimatch : plugin.minimatch,
pangu : plugin.pangu,
beautify : plugin.beautify,
stylesheet: plugin.style,
rdability : plugin.rdability,
};
super.SetMinimatch( this.plugin.minimatch );
super.SetRdability( this.plugin.rdability );
}
/**
* Create temp read mode
*
* @param {string} include: read, focus
* @param {dom} html dom element
*/
TempMode( mode, dom ) {
this.state = "temp";
this.dom = dom;
this.Newsite( mode, dom.outerHTML );
}
/**
* Get read mode html
*/
ReadMode() {
this.html = wrap( this.current.site );
}
/**
* Get highlight( focus ) jquery, only usage focus mode
*
* @return {jquery} jquery object
*/
Include() {
let include = this.current.site.include,
$focus = [];
const target = util.selector( include );
try {
if ( util.specTest( target ) ) {
const [ value, state ] = util.specAction( include );
if ( state == 0 ) {
include = include.replace( /\[\[{\$\(|}\]\]|\).html\(\)/g, "" );
$focus = $( util.specAction( `[[[${include}]]]` )[0] );
} else if ( state == 3 ) {
$focus = value;
}
} else if ( target ) {
$focus = $( "body" ).find( target );
}
} catch ( error ) {
console.error( "Get $focus failed", error )
}
return $focus;
}
/**
* Get exlcude jquery selector array list
*
* @param {jquery} jquery object
* @return {array} jquery selector
*/
Exclude( $target ) {
return excludeSelector( $target, this.current.site.exclude );
}
/**
* Beautify html
*
* @param {jquery} jquery
*/
Beautify( $target ) {
if ( this.plugin.beautify ) {
this.plugin.beautify.specbeautify( this.current.site.name, $target );
this.plugin.beautify.removeSpareTag( this.current.site.name, $target );
this.plugin.beautify.htmlbeautify( $target );
this.plugin.beautify.commbeautify( this.current.site.name, $target );
}
}
/**
* Format usage pangu plugin
*
* @param {string} class name
*/
Format( cls ) {
this.plugin.pangu &&
this.plugin.pangu.spacingElementByClassName( cls );
}
}
/**
* Wrap storage.current.site object
*
* @param {object} storage.current.site object
* @return {object} wrapper object
*/
function wrap( site ) {
const wrapper = util.clone( site ),
title = util.selector( site.title == "" ? "<title>" : site.title ),
desc = util.selector( site.desc ),
include = util.selector( site.include );
wrapper.title = site.title == "" || site.title == "<title>" ? $( "head title" ).text() : query( title );
wrapper.desc = query( desc );
wrapper.include = site.include == "" && site.html != "" ? site.html : query( include, "html" );
wrapper.avatar && wrapper.avatar.length > 0 && wrapper.avatar[0].name == "" && delete wrapper.avatar;
wrapper.paging && wrapper.paging.length > 0 && wrapper.paging[0].prev == "" && delete wrapper.paging;
wrapper.avatar && wrapper.avatar.forEach( item => {
const key = Object.keys( item ).join(),
value = item[key];
item[key] = query( util.selector( value ), "html" );
});
wrapper.paging && wrapper.paging.forEach( item => {
const key = Object.keys( item ).join(),
value = item[key];
item[key] = query( util.selector( value ) );
});
return wrapper;
}
/**
* Query content usage jquery
*
* @param {string} query content
* @param {string} type, incldue: text, html and multi
* @return {string} query result
*/
function query( content, type = "text" ) {
const $root = $( "html" );
if ( util.specTest( content ) ) {
const [ value, state ] = util.specAction( content );
if ( state == 0 ) {
content = value;
} else if ( state == 3 ) {
content = getcontent( $root.find( value ) );
}
} else if ( type == "html" ) {
content = getcontent( $root.find( content ) );
} else if ( type == "multi" ) {
// TO-DO
} else {
content = $root.find( content ).text().trim();
}
return content;
}
/**
* Get content from current.site.include
*
* @param {jquery} jquery object e.g. $root.find( content )
* @return {string} $target html
*/
function getcontent( $target ) {
let html = "";
switch ( $target.length ) {
case 0:
html = "<sr-rd-content-error></sr-rd-content-error>";
break;
case 1:
html = $target.html().trim();
break;
default:
html = $target.map( (index, item) => $(item).html() ).get().join( "<br>" );
break;
}
return html;
}
/**
* Get exclude tags list
*
* @param {jquery} jquery object
* @param {array} hidden html
* @return {string} tags list string
*/
function excludeSelector( $target, exclude ) {
let tags = [], tag = "";
for ( let content of exclude ) {
if ( util.specTest( content )) {
const [ value, type ] = util.specAction( content );
if ( type == 1 ) {
tag = value;
} else if ( type == 2 ) {
const arr = $target.html().match( new RegExp( value, "g" ) );
if ( arr && arr.length > 0 ) {
const str = arr.join( "" );
tag = `*[${str}]`;
} else {
tag = undefined;
}
} else if ( type == 3 ) {
value.remove();
}
} else {
tag = util.selector( content );
}
if ( tag ) tags.push( tag );
}
return tags.join( "," );
}

1416
src/vender/puread/puread.min.js vendored Normal file

File diff suppressed because it is too large Load Diff

View File

@ -1,141 +0,0 @@
console.log( "=== PureRead: Util load ===" )
/**
* Deep clone object
*
* @param {object} target object
* @return {object} new target object
*/
function clone( target ) {
return $.extend( true, {}, target );
}
/**
* Get URI
*
* @return {string} e.g. current site url is http://www.cnbeta.com/articles/1234.html return http://www.cnbeta.com/articles/
*/
function getURI() {
const name = (pathname) => {
pathname = pathname != "/" && pathname.endsWith("/") ? pathname = pathname.replace( /\/$/, "" ) : pathname;
return pathname.replace( /\/[%@#.~a-zA-Z0-9_-]+$|^\/$/g, "" );
},
path = name( window.location.pathname );
return `${ window.location.protocol }//${ window.location.hostname }${ path }/`;
}
/**
* Verify html
*
* @param {string} input include html tag, e.g.:
<div class="article fmt article__content">
*
* @return {array} 0: int include ( -1: fail 0: empty html; 1: success; 2: special tag )
* 1: result
*/
function verifyHtml( html ) {
if ( html == "" ) return [ 0, html ];
else if ( specTest( html )) return [ 2, html ];
const item = html.match( /<\S+ (class|id)=("|')?[\w-_=;:' ]+("|')?>?$|<[^/][-_a-zA-Z0-9]+>?$/ig );
if ( item && item.length > 0 ) {
return [ 1, item ];
} else {
return [ -1, undefined ];
}
}
/**
* Conver html to jquery object
*
* @param {string} input include html tag, e.g.:
<div class="article fmt article__content">
*
* @return {string} formatting e.g.:
h2#news_title
div.introduction
div.content
div.clearfix
div.rating_box
span
special tag, @see specTest
e.g. [['<strong>▽</strong>']] [[[$('.article-btn')]]]
[[/src=\\S+(342459.png)\\S+'/]] [[{$('.content').html()}]]
*
*/
function selector( html ) {
const [ code, item ] = verifyHtml( html );
if ( code == 2 ) return html;
else if ( code == 1 ) {
let [tag, prop, value] = item[0].trim().replace( /['"<>]/g, "" ).replace( / /ig, "=" ).split( "=" ); // ["h2", "class", "title"]
if ( !prop ) prop = tag;
else if ( prop.toLowerCase() === "class") prop = `${tag}.${value}`;
else if ( prop.toLowerCase() === "id" ) prop = `${tag}#${value}`;
return prop;
} else {
return null;
}
}
/**
* Verify special action, action include:
- [[{juqery code}]] // new Function, e.g. $("xxx").xxx() return string
- [['text']] // remove '<text>'
- [[/regexp/]] // regexp e.g. $("sr-rd-content").find( "*[src='http://ifanr-cdn.b0.upaiyun.com/wp-content/uploads/2016/09/AppSo-qrcode-signature.jpg']" )
- [[[juqery code]]] // new Function, e.g. $("xxx").find() return jquery object
*
* @param {string} verify content
* @return {boolen} verify result
*/
function specTest( content ) {
return /^(\[\[)[\[{'/]{1}[ \S]+[}'/\]]\]\]{1}($)/g.test( content );
}
/**
* Exec special action, action include: @see specTest
* type: 0, 3 - be chiefly used in include logic
* type: 1, 2 - be chiefly used in exclude logic
*
* @param {string} content
* @return {array} 0: result; 1: type( include: -1:error 0:{} 1:'' 2:// 3:[])
*/
function specAction( content ) {
let [ value, type ] = [ content.replace( /(^)\[\[|\]\]$/g, "" ) ];
switch (value[0]) {
case "{":
value = value.replace( /^{|}$/g, "" );
content = ( v=>new Function( `return ${v}` )() )(value);
type = 0;
break;
case "'":
content = value.replace( /^'|'$/g, "" );
const name = content.match(/^<[a-zA-Z0-9_-]+>/g).join("").replace( /<|>/g, "" );
const str = content.replace( /<[/a-zA-Z0-9_-]+>/g, "" );
content = `${name}:contains(${str})`;
type = 1;
break;
case "/":
content = value.replace( /^\/|\/$/g, "" ).replace( /\\{2}/g, "\\" ).replace( /'/g, '"' );
type = 2;
break;
case "[":
value = value.replace( /^{|}$/g, "" );
content = ( v=>new Function( `return ${v}` )() )(value)[0];
type = 3;
break;
default:
console.error( "Not support current action.", content )
type = -1;
break;
}
return [ content, type ];
}
export {
clone,
getURI,
verifyHtml,
selector,
specTest,
specAction
}

View File

@ -308,8 +308,8 @@ const webpack = require( 'webpack' ),
tooltip : __dirname + '/src/vender/mduikit/tooltip.jsx',
waves : __dirname + '/src/vender/mduikit/waves.js',
puread : __dirname + '/src/vender/puread/puread.js',
puplugin : __dirname + '/src/vender/puread/plugin.js',
puread : __dirname + '/src/vender/puread/puread.min.js',
puplugin : __dirname + '/src/vender/puread/puplugin.min.js',
}
}