- // ==UserScript==
- // @name Bionic Reading / ⌘ + B
- // @name:zh 英文前部加粗 / ⌘ + B
- // @namespace https://github.com/itorr/bionic-reading.user.js
- // @version 0.8.4
- // @description Bionic Reading User Script Ctrl + B / ⌘ + B
- // @description:zh 网页英文前部加粗脚本 Ctrl + B / ⌘ + B 开启关闭
- // @icon data:image/vnd.microsoft.icon;base64,AAABAAEAICACAAEAAQAwAQAAFgAAACgAAAAgAAAAQAAAAAEAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABvb28Ab29vAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA/////////////////////8AAfH/AABx/wAAMf8AADH/D/gR/w/8Ef8P/BH/D/wR/w/8Ef8P+DH/AAAx/wAAcf8AAPD/AABwfw/wMA8P+DEPD/gxjw/4P/8P8D//AAA//wAAf/8AAP//AAP////////////////////////////8=
- // @author itorr
- // @match *://*/*
- // @exclude /\.(js|java|c|cpp|h|py|css|less|scss|json|yaml|yml|xml)(?:\?.+)$/
- // @license MIT
- // @run-at document-end
- // @supportURL https://github.com/itorr/bionic-reading.user.js/issues
- // @grant GM_getValue
- // @grant GM_setValue
- // ==/UserScript==
-
-
- const defaultConfig = {
- autoBionic: true,
- skipLinks: false,
- skipWords: false,
- scale: 0.5,
- maxBionicLength: null,
- opacity: 1,
- saccade: 0, // 0 - ~
- symbolMode: false,
- excludeWords:['is','and','as','if','the','of','to','be','for','this'],
- };
-
- let config = defaultConfig;
- try{
- config = (_=>{
- const _config = GM_getValue('config');
- if(!_config) return defaultConfig;
-
- for(let key in defaultConfig){
- if(_config[key] === undefined) _config[key] = defaultConfig[key];
- }
- return _config;
- })();
-
- GM_setValue('config',config);
- }catch(e){
- console.log('读取默认配置失败')
- }
-
- console.log(JSON.stringify(config,0,2))
-
- let isBionic = false;
-
- const enCodeHTML = s=> s.replace(/[\u00A0-\u9999<>\&]/g,w=>'&#'+w.charCodeAt(0)+';');
-
- let body = document.body;
-
- if(/weibo/.test(location.hostname)){
- const wbMainEl = document.querySelector('.WB_main');
- if(wbMainEl) body = wbMainEl;
-
- // 修复旧版微博自定义样式失效 bug
- const customStyleEl = document.querySelector('#custom_style');
- if(customStyleEl) customStyleEl.removeAttribute('id');
- }
-
- const styleEl = document.createElement('style');
- styleEl.innerHTML = `
- bbb{
- font-weight:bold;
- opacity: ${config.opacity};
- }
- html[data-site="greasyfork"] a bionic{
- pointer-events: none;
- }
- `;
-
- document.documentElement.setAttribute('data-site',location.hostname.replace(/\.\w+$|www\./ig,''))
-
- const excludeNodeNames = [
- 'script','style','xmp',
- 'input','textarea','select',
- 'pre','code',
- 'h1','h2', // 'h3','h4',
- 'b','strong',
- 'svg','embed',
- 'img','audio','video',
- 'canvas',
- ];
-
- const excludeClasses = [
- 'highlight',
- 'katex',
- 'editor',
- ]
- const excludeClassesRegexi = new RegExp(excludeClasses.join('|'),'i');
- const linkRegex = /^https?:\/\//;
- const gather = el=>{
- let textEls = [];
- el.childNodes.forEach(el=>{
- if(el.isEnB) return;
- if(el.originEl) return;
-
- if(el.nodeType === 3){
- textEls.push(el);
- }else if(el.childNodes){
- const nodeName = el.nodeName.toLowerCase();
- if(excludeNodeNames.includes(nodeName)) return;
- if(config.skipLinks){
- if(nodeName === 'a'){
- if(linkRegex.test(el.textContent)) return;
- }
- }
- if(el.getAttribute){
- if(el.getAttribute('class') && excludeClassesRegexi.test(el.getAttribute('class'))) return;
-
- // 跳过所有可编辑元素
- if(el.getAttribute('contentEditable') === 'true') return;
- }
-
- textEls = textEls.concat(gather(el))
- }
- })
- return textEls;
- };
-
- const engRegex = /[a-zA-Z][a-z]+/;
- const engRegexg = new RegExp(engRegex,'g');
- const getHalfLength = word=>{
-
- let halfLength;
- if(/ing$/.test(word)){
- halfLength = word.length - 3;
- }else if(word.length<5){
- halfLength = Math.floor(word.length * config.scale);
- }else{
- halfLength = Math.ceil(word.length * config.scale);
- }
-
- if(config.maxBionicLength){
- halfLength = Math.min(halfLength, config.maxBionicLength)
- }
- return halfLength;
- }
-
-
- let count = 0;
- const saccadeRound = config.saccade + 1;
- const saccadeCounter = _=>{
- return ++count % saccadeRound === 0;
- };
- const replaceTextByEl = el=>{
- const text = el.data;
- if(!engRegex.test(text))return;
-
- if(!el.replaceEl){
- const spanEl = document.createElement('bionic');
- spanEl.isEnB = true;
- spanEl.innerHTML = enCodeHTML(text).replace(engRegexg,word=>{
- if(config.skipWords && config.excludeWords.includes(word)) return word;
- if(config.saccade && !saccadeCounter()) return word;
-
- const halfLength = getHalfLength(word);
- return '<bbb>'+word.substr(0,halfLength)+'</bbb>'+word.substr(halfLength)
- })
- spanEl.originEl = el;
- el.replaceEl = spanEl;
- }
-
- el.after(el.replaceEl);
- el.remove();
- };
-
- const replaceTextSymbolModeByEl = el=>{
- el.data = el.data.replace(engRegexg,word=>{
- if(config.skipWords && config.excludeWords.includes(word)) return word;
- if(config.saccade && !saccadeCounter()) return word;
-
- const halfLength = getHalfLength(word);
- const a = word.substr(0,halfLength).
- replace(/[a-z]/g,w=>String.fromCharCode(55349,w.charCodeAt(0)+56717)).
- replace(/[A-Z]/g,w=>String.fromCharCode(55349,w.charCodeAt(0)+56723));
- const b = word.substr(halfLength).
- replace(/[a-z]/g,w=> String.fromCharCode(55349,w.charCodeAt(0)+56665)).
- replace(/[A-Z]/g,w=> String.fromCharCode(55349,w.charCodeAt(0)+56671));
- return a + b;
- })
- }
-
- const bionic = _=>{
- const textEls = gather(body);
-
- isBionic = true;
- count = 0;
-
- let replaceFunc = config.symbolMode ? replaceTextSymbolModeByEl : replaceTextByEl;
-
- textEls.forEach(replaceFunc);
-
- document.head.appendChild(styleEl);
- }
-
- const lazy = (func,ms = 15)=> {
- return _=>{
- clearTimeout(func.T)
- func.T = setTimeout(func,ms)
- }
- };
-
- const listenerFunc = lazy(_=>{
- if(!isBionic) return;
-
- bionic();
- });
-
- if(window.MutationObserver){
- (new MutationObserver(listenerFunc)).observe(body,{
- childList: true,
- subtree: true,
- attributes: true,
- });
- }else{
- const {open,send} = XMLHttpRequest.prototype;
- XMLHttpRequest.prototype.open = function(){
- this.addEventListener('load',listenerFunc);
- return open.apply(this,arguments);
- };
- document.addEventListener('DOMContentLoaded',listenerFunc);
- document.addEventListener('DOMNodeInserted',listenerFunc);
- }
-
- if(config.autoBionic){ // auto Bionic
- window.addEventListener('load',bionic);
- }
- // document.addEventListener('click',listenerFunc);
-
-
- const revoke = _=>{
- const els = [...document.querySelectorAll('bionic')];
-
- els.forEach(el=>{
- const {originEl} = el;
- if(!originEl) return;
-
- el.after(originEl);
- el.remove();
- })
-
- isBionic = false;
- };
- // document.addEventListener('mousedown',revoke);
-
- const redo = _=>{
- const textEls = gather(body);
-
- textEls.forEach(el=>{
- const { replaceEl } = el;
-
- if(!replaceEl) return;
-
-
- el.after(replaceEl);
- el.remove();
- })
-
- isBionic = false;
- };
-
- document.addEventListener('keydown',e=>{
- const { ctrlKey , metaKey, key } = e;
-
- if( ctrlKey || metaKey ){
- if(key === 'b'){
- if(isBionic){
- revoke();
- }else{
- bionic();
- }
- }
- }
- })
-
-
- // let id = base.registerMenuCommand ('Setting', function(){
- // // 配置相关
- // }, 's');
-
-
- // document.addEventListener('mouseup',redo);