diff --git a/src/assets/css/options_page.css b/src/assets/css/options_page.css index b991c0a2..f0975019 100644 --- a/src/assets/css/options_page.css +++ b/src/assets/css/options_page.css @@ -3,7 +3,7 @@ --text-color: #333; --secondary-color: color(#333 alpha(-30%)); --background-color: #fff; - --width: 740px; + --width: 835px; } * { @@ -237,4 +237,120 @@ a { background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAMAAADXqc3KAAAAV1BMVEUAAAD////ExMS2tra/v7/Dw8O8vLzCwsK5ubnCwsK6urq+vr68vLy9vb29vb28vLy9vb29vb29vb29vb29vb29vb29vb29vb2+vr6+vr69vb29vb29vb2oiyseAAAAHHRSTlMAAQ0OEBETFRYZGkpMg8rLzM7R0tPW19je4uTlotOuxwAAAAFiS0dEAf8CLd4AAAB1SURBVCjPrdLHDoAgEATQUbH33vb/v1PEEnVXT85pM48QCAC/JhloC3AOJiO9wDms8UoZdN9KEHZUuAKEPeUuOGw9h0j36nqPHVi/g19RE9y3NoNenylwcGqqfQiANFMQwbIhAz/lB0z0yHg81Hzvp/jfj7AA4P8P+rUn4dEAAAAASUVORK5CYII=); background-position: center; background-repeat: no-repeat; +} + +cards { + display: flex; + flex-flow: row wrap; +} + +card { + display: flex; + flex-direction: column; + + margin: 6px; + + width: 259px; + + color: rgba(51, 51, 51, .87); + background-color: #fff; + + border-radius: 2px; + box-shadow: 0 3px 1px -2px rgba(0,0,0,.2), 0 2px 2px 0 rgba(0,0,0,.14), 0 1px 5px 0 rgba(0,0,0,.12); + + transition: all .25s ease-out; +} + +card:hover { + box-shadow: 0 10px 20px 0 rgba(168,182,191,0.6); + transform: translateY(-1px); +} + +card-header { + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + + width: 100%; + height: 196px; + + background-color: #40C4FF; +} + +card-header icon { + display: block; + + width: 100px; + height: 100px; + + font-size: 80px; +} + +card-content { + display: flex; + flex-direction: column; + align-items: flex-start; + + width: 100%; + height: 134px; + + padding: 16px; +} + +card-content title { + display: block; + + font-size: 20px; + font-weight: 500; + + height: 32px; + line-height: 32px; +} + +card-content desc { + display: block; + + font-size: 14px; + font-weight: 500; + + height: 22px; + line-height: 22px; +} + +card-content note { + display: block; + + margin-top: 16px; + + text-align: left; + font-size: 14px; + font-weight: 400; + + line-height: 20px; +} + +card-footer { + display: flex; + align-items: flex-end; + justify-content: flex-end; + + width: 100%; + height: 52px; +} + +card-footer i { + font-size: 16px; +} + +card-empty { + width: 100%; + padding: 50px; +} + +card-empty a { + color: #9b9b9b; + + font-size: 30px; + font-weight: 500; } \ No newline at end of file diff --git a/src/assets/images/plugins_icon.png b/src/assets/images/plugins_icon.png new file mode 100644 index 00000000..e4204fe6 Binary files /dev/null and b/src/assets/images/plugins_icon.png differ diff --git a/src/assets/images/update_icon.png b/src/assets/images/update_icon.png new file mode 100644 index 00000000..ee947b10 Binary files /dev/null and b/src/assets/images/update_icon.png differ diff --git a/src/assets/images/website_icon.png b/src/assets/images/website_icon.png deleted file mode 100644 index 9125f85f..00000000 Binary files a/src/assets/images/website_icon.png and /dev/null differ diff --git a/src/background.js b/src/background.js index 58d8e99e..8685dbd3 100644 --- a/src/background.js +++ b/src/background.js @@ -156,6 +156,10 @@ browser.tabs.onUpdated.addListener( function( tabId, changeInfo, tab ) { browser.tabs.remove( tabId ); } }); + } else if ( tab.url.startsWith( "http://simpread.ksria.cn/plugins/install/" )) { + const url = tab.url.replace( "http://simpread.ksria.cn/plugins/install/", "" ); + browser.tabs.create({ url: browser.extension.getURL( "options/options.html#plugins?install=" + encodeURIComponent(url) ) }); + browser.tabs.remove( tabId ); } if ( !tab.url.startsWith( "chrome://" ) ) { diff --git a/src/module/common.jsx b/src/module/common.jsx index 0aa064e0..7f114495 100644 --- a/src/module/common.jsx +++ b/src/module/common.jsx @@ -133,6 +133,8 @@ export default class CommonOpt extends React.Component { menu.Refresh( json.option.menu ); json.option.origins && json.option.origins.length > 0 && new Notify().Render( "导入的配置文件包含了第三方源,请通过手动导入。" ); + json.option.plugins && json.option.plugins.length > 0 && + new Notify().Render( "导入的配置文件包含了插件,请通过手动导入。" ); this.importsecret( json.option.secret, { ...json.secret }, () => { delete json.secret; storage.Write( ()=> { @@ -240,7 +242,7 @@ export default class CommonOpt extends React.Component {
+
+
+ + +
管理
+
+ this.onChange(t) } /> +
+ + + ) + } +} \ No newline at end of file diff --git a/src/module/read.jsx b/src/module/read.jsx index 9e5a58db..afd1b352 100644 --- a/src/module/read.jsx +++ b/src/module/read.jsx @@ -76,7 +76,7 @@ export default class ReadOpt extends React.Component { } render() { - const slider_width = location.protocol.includes( "extension" ) ? "565.8px" : undefined; + const slider_width = location.protocol.includes( "extension" ) ? "660.09px" : undefined; return ( diff --git a/src/options/options.js b/src/options/options.js index 45ae464c..4fad4522 100644 --- a/src/options/options.js +++ b/src/options/options.js @@ -26,10 +26,11 @@ import FocusOpt from 'focusopt'; import ReadOpt from 'readopt'; import CommonOpt from 'commonopt'; import LabsOpt from 'labsopt'; +import PluginsOpt from 'pluginsopt'; import About from 'about'; import Unrdist from 'unrdist'; import * as welc from 'welcome'; -import * as plug from 'plugin'; +import * as run from 'runtime'; import PureRead from 'puread'; @@ -181,7 +182,7 @@ function welcomeRender( first, version ) { function mainRender( idx ) { $( ".top" ).css( "background-color", conf.topColors[idx] ); $( ".header" ).css( "background-color", conf.topColors[idx] ).find( ".title" ).text( conf.tabsItem[idx].name ); - ( idx == 3 || idx == 5 ) ? $( '.main' ).addClass( "main_labs" ) : $( '.main' ).removeClass( "main_labs" ); + ( idx == 3 || idx == 4 || idx == 6 ) ? $( '.main' ).addClass( "main_labs" ) : $( '.main' ).removeClass( "main_labs" ); tabsRender( conf.headerColors[ idx ] ); } @@ -218,6 +219,9 @@ function tabsRender( color ) {
save(s) } />
+
+ +
{ return { ...item }} ) } />
welcomeRender(true)}/>
, @@ -268,7 +272,7 @@ function sidebarRender() { * set user uid */ function setUserUID() { - storage.user.uid = plug.ID( "user" ); + storage.user.uid = run.ID( "user" ); storage.Write( () => { console.log( "current user info create!" ) watch.SendMessage( "option", true ); diff --git a/src/read/controlbar.jsx b/src/read/controlbar.jsx index 15bb5d9f..2478629c 100644 --- a/src/read/controlbar.jsx +++ b/src/read/controlbar.jsx @@ -12,6 +12,7 @@ import { storage } from 'storage'; import ReadOpt from 'readopt'; import Actionbar from 'actionbar'; +import Pluginbar from 'pluginbar'; import Fab from 'fab'; import Fap from 'fap' @@ -137,12 +138,13 @@ export default class ReadCtlbar extends React.Component { render() { const Controlbar = storage.current.fap ? - this.onPop( "open" ) } onClose={ ()=> this.onPop( "close" ) } onAction={ (event, type)=>this.onAction(event, type ) }> this.onChange(t,c)}/> this.onAction(undefined, type ) }/> + : this.onAction(event, type ) } /> diff --git a/src/read/read.jsx b/src/read/read.jsx index 9d078e16..5ad7924b 100644 --- a/src/read/read.jsx +++ b/src/read/read.jsx @@ -14,6 +14,7 @@ import * as ss from 'stylesheet'; import {browser} from 'browser'; import * as msg from 'message'; import * as highlight from 'highlight'; +import * as run from 'runtime'; import * as tooltip from 'tooltip'; import * as waves from 'waves'; @@ -40,8 +41,15 @@ const Footer = () => { class Read extends React.Component { componentWillMount() { + loadPlugins( "read_start" ); $( "body" ).addClass( "simpread-hidden" ); th.Change( this.props.read.theme ); + // hack code + //storage.current.fap && $( "head" ).append( '' ); + if ( storage.current.fap ) { + $( "head" ).append( '' ); + $( "head" ).append( '' ); + } } async componentDidMount() { @@ -83,10 +91,13 @@ class Read extends React.Component { tooltip.Render( rdclsjq ); waves.Render({ root: rdclsjq }); storage.Statistics( "read" ); + + loadPlugins( "read_complete" ); } } componentWillUnmount() { + loadPlugins( "read_end" ); ss.FontSize( "" ); $root.removeClass( theme ) .removeClass( "simpread-font" ); @@ -248,4 +259,17 @@ function excludes( $target, exclude ) { $target.find( tags ).remove(); } +/** + * Load plugins from storage and exec + * + * @param {string} state include: plugin.run_at + */ +function loadPlugins( state ) { + storage.Plugins( () => { + storage.option.plugins.forEach( id => { + storage.plugins[id] && run.Exec( state, storage.current.site.name, storage.plugins[id] ); + }); + }); +} + export { Render, Exist, Exit, Highlight }; diff --git a/src/read/toc.jsx b/src/read/toc.jsx index dbfe604d..8937324c 100644 --- a/src/read/toc.jsx +++ b/src/read/toc.jsx @@ -81,8 +81,8 @@ class TOC extends React.Component { function Render( root, $target, theme, hidden ) { // hack code - if ( location.host.includes( "blog.csdn.net" )) return; - if ( location.host.includes( "post.smzdm.com" )) return; + //if ( location.host.includes( "blog.csdn.net" )) return; + //if ( location.host.includes( "post.smzdm.com" )) return; const table = [], cls = hidden ? "toc-bg-hidden" : ""; diff --git a/src/service/config.js b/src/service/config.js index 378cec87..8f36b77b 100644 --- a/src/service/config.js +++ b/src/service/config.js @@ -566,6 +566,10 @@ const tabsItem = [{ name: "高级设定", value: "labs", route: "#labs", + },{ + name: "插件管理", + value: "plugins", + route: "#plugins", },{ name: "稍后读", value: "later", @@ -579,8 +583,8 @@ const tabsItem = [{ value: "help", route: "https://github.com/kenshin/simpread/wiki", }], - headerColors = [ "#64B5F6", "#81C784", "#9575CD", "#7986CB", "#BA68C8", "#4DB6AC" ], - topColors = [ "#2196F3", "#4CAF50", "#673AB7", "#3F51B5", "#9C27B0", "#009688" ], + headerColors = [ "#64B5F6", "#81C784", "#9575CD", "#7986CB", "#4DD0E1", "#BA68C8", "#4DB6AC" ], + topColors = [ "#2196F3", "#4CAF50", "#673AB7", "#3F51B5", "#00BCD4", "#9C27B0", "#009688" ], menuItem = tabsItem.map( ( item, idx ) => { const menu = { ...item }; switch ( idx ) { @@ -598,12 +602,15 @@ const tabsItem = [{ menu.icon = ss.IconPath( "labs_icon" ); break; case 4: - menu.icon = ss.IconPath( "read_later_icon" ); + menu.icon = ss.IconPath( "plugins_icon" ); break; case 5: - menu.icon = ss.IconPath( "about_icon" ); + menu.icon = ss.IconPath( "read_later_icon" ); break; case 6: + menu.icon = ss.IconPath( "about_icon" ); + break; + case 7: menu.icon = ss.IconPath( "help_icon" ); break; } diff --git a/src/service/plugin.js b/src/service/plugin.js deleted file mode 100644 index dc3d4cd9..00000000 --- a/src/service/plugin.js +++ /dev/null @@ -1,35 +0,0 @@ - -import nanoid from 'nanoid'; - -/** - * Generate ID - * - * @param {string} generate id, include: user id( uuid v4 ), plugin id( like t.co/cKPFh3Qsh4 ) - */ -function generateID( type ) { - if ( type == "user" ) { - const random = "0123456789abcdefghijklmnopqrstuvwxyz", - first = nanoid( random, 8 ), - second = nanoid( random, 4 ), - third = nanoid( random, 4 ), - fourth = nanoid( random, 4 ), - fifth = nanoid( random, 12 ); - return `${first}-${second}-${third}-${fourth}-${fifth}`; - } else if ( type == "plugin" ) { - return nanoid( "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ", 10 ); - } -} - -function install( id ) { - -} - -function exec( str ) { - -} - -export { - install as Install, - exec as Exec, - generateID as ID, -} \ No newline at end of file diff --git a/src/service/runtime.js b/src/service/runtime.js new file mode 100644 index 00000000..33ecdf92 --- /dev/null +++ b/src/service/runtime.js @@ -0,0 +1,98 @@ +console.log( "=== simpread runtime load ===" ) + +import nanoid from 'nanoid'; +import {browser} from 'browser'; +import {storage, Clone} from 'storage'; + +/** + * Generate ID + * + * @param {string} generate id, include: user id( uuid v4 ), plugin id( like t.co/cKPFh3Qsh4 ) + */ +function generateID( type ) { + if ( type == "user" ) { + const random = "0123456789abcdefghijklmnopqrstuvwxyz", + first = nanoid( random, 8 ), + second = nanoid( random, 4 ), + third = nanoid( random, 4 ), + fourth = nanoid( random, 4 ), + fifth = nanoid( random, 12 ); + return `${first}-${second}-${third}-${fourth}-${fifth}`; + } else if ( type == "plugin" ) { + return nanoid( "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ", 10 ); + } +} + +/** + * Install plugin + * + * @param {string} id e.g. kw36BtjGu0 + * @param {string} url + */ +function install( id, url, callback ) { + url = id ? `http://simpread.ksria.cn/plugins/srplug/${id}.srplug` : url; + url = url + `?${+new Date()}`; + console.log( "install url is", url ) + $.ajax({ url, dataType: "json" }) + .done( result => callback( result, undefined )) + .fail( () => callback( undefined, "error" )); +} + +/** + * Execute + * + * @param {string} state, include: read_start, read_loading, read_complete, read_end + * @param {string} site name, inlcude: "", "xxx", "xxx, yyy" + * @param {object} plugin object + */ +function exec( state, site, plugin ) { + if ( plugin.enable == false ) return; + if ( plugin.run_at != state ) return; + if ( plugin.site != "" && !plugin.site.split(",").includes( site ) ) return; + console.log( "current plugin is running", plugin ) + new Function( func( plugin.script ) )(); + plugin.style != "" && addStyle( plugin.style ); +} + +/** + * Contact (function(){})() string + * + * @param {string} source + */ +function func( source ) { + window.Notify = Notify; + window.browser = browser; + window.current = Clone( storage.pr.current ); + window.read = Clone( storage.read ); + return `( function ( $$version, $title, $desc, $content, $footer, $process, $toc, Notify, browser, $$current, $$read ) { + ${ source } + })( "0.0.1", $( "sr-rd-title" ), $( "sr-rd-desc" ), $( "sr-rd-content" ), $( "sr-rd-footer" ), $( "read-process" ), $( "toc" ), Notify, browser, current, read );` +} + +/** + * Add style + * + * @param {string} add css to head + */ +function addStyle( str ) { + $( "head" ).append(``); +} + +/** + * Test Plugin + * + * @param {func} style func + * @param {func} plugin func + */ +function testPlugin( style, plugin ) { + style && addStyle( style() ); + plugin && plugin( "0.0.1", $( "sr-rd-title" ), $( "sr-rd-desc" ), $( "sr-rd-content" ), $( "sr-rd-footer" ), $( "read-process" ), $( "toc" ), Notify, browser, storage.pr.current, storage.read ); +} + +window.simpread = { testPlugin }; + +export { + install as Install, + exec as Exec, + generateID as ID, +} \ No newline at end of file diff --git a/src/service/storage.js b/src/service/storage.js index a0b4ff35..d93f9034 100644 --- a/src/service/storage.js +++ b/src/service/storage.js @@ -119,7 +119,8 @@ const name = "simpread", origins : [], blacklist : [ "google.com", - ] + ], + plugins : [], // plugin id, e.g. kw36BtjGu0 }, statistics = { "focus" : 0, @@ -176,7 +177,8 @@ let current = {}, statistics, user, }, - secret = { + plugins = {}, + secret = { version : "2017-11-22", "dropbox" : { "access_token": "" @@ -312,6 +314,15 @@ class Storage { return secret; } + /** + * Get plugins data structure + * + * @return {object} plugins object + */ + get plugins() { + return plugins; + } + /** * Get all sites structure * @@ -653,6 +664,28 @@ class Storage { } } + /** + * Plugins set/get, plugins not import/export + * + * @param {object} plugins + * @param {function} callback + */ + Plugins( callback, data ) { + if ( data ) { + plugins = { ...data }; + browser.storage.local.set( { ["plugins"] : plugins }, () => { + console.log( "chrome storage plugins set success!", plugins ); + callback && callback(); + }); + } else { + browser.storage.local.get( ["plugins"], result => { + console.log( "chrome storage plugins get success!", result ); + result && !$.isEmptyObject( result ) && ( plugins = result["plugins"] ); + callback && callback(); + }); + } + } + /** * Export, only include: version, option, focus, read, unrdist * diff --git a/src/service/version.js b/src/service/version.js index e64eaa80..bb3b732b 100644 --- a/src/service/version.js +++ b/src/service/version.js @@ -148,6 +148,7 @@ function Verify( curver, data ) { if ( curver == "1.1.1" ) { data.user = { "uid": "","name": "","email": "","avatar": "","permission": "" }; + data.option.plugins = []; curver = "1.1.2"; } diff --git a/src/vender/mduikit/button.jsx b/src/vender/mduikit/button.jsx index d9167049..5d356823 100644 --- a/src/vender/mduikit/button.jsx +++ b/src/vender/mduikit/button.jsx @@ -1,8 +1,8 @@ /*! * React Material Design: Button * - * @version : 0.0.3 - * @update : 2018/04/26 + * @version : 0.0.4 + * @update : 2018/06/21 * @homepage: https://github.com/kenshin/mduikit-ui * @license : MIT https://github.com/kenshin/mduikit/blob/master/LICENSE * @author : Kenshin Wang @@ -122,6 +122,23 @@ const cssinjs = () => { backgroundRepeat: 'no-repeat', }, + font_icon: { + display: 'flex', + justifyContent: 'center', + alignItems: 'center', + + fontSize: '18px', + color: '#fff', + + order: -1, + display: 'block', + + width: '24px', + height: '24px', + + border: 'none', + }, + circle: { borderRadius: '50%', }, @@ -151,6 +168,7 @@ export default class Button extends React.Component { text : "", disable : false, icon : "", + fontIcon: "", order : "before", type : "flat", mode : "primary", @@ -170,6 +188,7 @@ export default class Button extends React.Component { text : React.PropTypes.string, disable : React.PropTypes.bool, icon : React.PropTypes.string, + fontIcon: React.PropTypes.string, order : React.PropTypes.oneOf([ "before", "after" ]), type : React.PropTypes.oneOf([ "flat", "raised" ]), mode : React.PropTypes.oneOf([ "primary", "secondary" ]), @@ -218,7 +237,7 @@ export default class Button extends React.Component { current.color = this.state.color; current.backgroundColor = this.state.backgroundColor; - if ( this.props.text == "" && this.props.icon != "" ) { + if ( this.props.text == "" && ( this.props.icon != "" || this.props.fontIcon != "" )) { delete style.normal_root.minWidth; delete style.normal_root.borderRadius; style.normal_root.width = style.normal_root.height; @@ -242,7 +261,13 @@ export default class Button extends React.Component { ( style.root = { ...style.root, ...this.props.style } ); this.props.text == "" && ( style.text.display = "none" ); - this.props.icon != "" ? ( style.icon.backgroundImage = `url(${this.props.icon})` ) : ( style.icon.display = "none" ); + + if ( this.props.fontIcon != "" ) { + style.icon = { ...style.font_icon }; + style.icon.display = "flex"; + style.icon.color = style.color; + } else this.props.icon != "" ? ( style.icon.backgroundImage = `url(${this.props.icon})` ) : ( style.icon.display = "none" ); + this.props.order == "after" && ( style.icon.order = 1 ); this.props.width && ( style.root.width = this.props.width ); @@ -261,7 +286,7 @@ export default class Button extends React.Component { { ...events }> - + { this.props.text } diff --git a/webpack.config.js b/webpack.config.js index 64b5f8d0..29d68662 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -249,7 +249,7 @@ const webpack = require( 'webpack' ), export : __dirname + '/src/service/export.js', highlight : __dirname + '/src/service/highlight.js', output : __dirname + '/src/service/output.js', - plugin : __dirname + '/src/service/plugin.js', + runtime : __dirname + '/src/service/runtime.js', focus : __dirname + '/src/focus/focus.js', controlbar : __dirname + '/src/focus/controlbar.jsx', @@ -265,6 +265,7 @@ const webpack = require( 'webpack' ), focusopt : __dirname + '/src/module/focus.jsx', readopt : __dirname + '/src/module/read.jsx', commonopt : __dirname + '/src/module/common.jsx', + pluginsopt : __dirname + '/src/module/plugins.jsx', labsopt : __dirname + '/src/module/labs.jsx', about : __dirname + '/src/module/about.jsx', unrdist : __dirname + '/src/module/unrdist.jsx', @@ -272,6 +273,7 @@ const webpack = require( 'webpack' ), authorize : __dirname + '/src/module/authorize.jsx', siteeditor : __dirname + '/src/module/siteeditor.jsx', actionbar : __dirname + '/src/module/actionbar.jsx', + pluginbar : __dirname + '/src/module/pluginbar.jsx', editor : __dirname + '/src/module/common/editor.jsx', themesel : __dirname + '/src/module/common/theme.jsx', shortcuts : __dirname + '/src/module/common/shortcuts.jsx',