您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
ルールにマッチするアイテムを既読にして取り除きます。ルールは正規表現で記述でき、複数のルールをツリー状に組み合わせることができます。
当前为
// ==UserScript== // @name Feedly NG Filter // @id feedlyngfilter // @description ルールにマッチするアイテムを既読にして取り除きます。ルールは正規表現で記述でき、複数のルールをツリー状に組み合わせることができます。 // @include http://feedly.com/* // @include https://feedly.com/* // @grant GM_getValue // @grant GM_setValue // @grant GM_addStyle // @grant GM_xmlhttpRequest // @grant GM_registerMenuCommand // @grant GM_unregisterMenuCommand // @grant GM_notification // @grant GM_log // @charset utf-8 // @compatibility Firefox // @run-at document-start // @jsversion 1.8 // @priority 1 // @homepage https://greasyfork.org/scripts/9030-feedly-ng-filter // @supportURL https://twitter.com/intent/tweet?text=%40xulapp+ // @icon https://greasyfork.org/system/screenshots/screenshots/000/000/615/original/icon.png // @screenshot https://greasyfork.org/system/screenshots/screenshots/000/000/614/original/large.png // @namespace http://twitter.com/xulapp // @author xulapp // @license MIT License // @version 0.7.1 // ==/UserScript== (function feedlyNGFilter() { const CSS_STYLE_TEXT = String.raw` .unselectable { -moz-user-select: none; } .goog-inline-block { display: inline-block; position: relative; } .jfk-button { min-width: 54px; height: 27px; margin-right: 16px; padding: 0 8px; border-radius: 2px 2px 2px 2px; line-height: 27px; font-size: 11px; font-weight: bold; white-space: nowrap; text-align: center; outline: 0 none; cursor: default; } .jfk-button-standard { background-image: linear-gradient(to bottom, #f5f5f5, #f1f1f1); color: #444; border: 1px solid rgba(0, 0, 0, 0.1); } .jfk-button-standard:hover { border: 1px solid #c6c6c6; background-image: linear-gradient(to bottom, #f8f8f8, #f1f1f1); color: #333; } .jfk-button-standard:focus { border: 1px solid #4d90fe; } .jfk-button-standard:active { box-shadow: inset 0 1px 2px rgba(0, 0, 0, .1); } .jfk-button-standard.jfk-button-disabled { border: 1px solid rgba(0, 0, 0, 0.05); background: none; color: #b8b8b8; } .goog-flat-menu-button { min-width: 46px; margin: 0 2px; padding: 0 18px 0 6px; border: 1px solid #dcdcdc; border-radius: 2px 2px 2px 2px; background-image: linear-gradient(to bottom, #f5f5f5, #f1f1f1); color: #444; font-size: 11px; font-weight: bold; line-height: 27px; list-style: none outside none; text-align: center; text-decoration: none; vertical-align: middle; outline: medium none; cursor: default; } .goog-flat-menu-button-open, .goog-flat-menu-button:active { border: 1px solid #ccc; background-image: linear-gradient(to bottom, #eee, #e0e0e0); box-shadow:inset 0 1px 2px rgba(0, 0, 0, .1); color: #333; z-index: 2 } .goog-flat-menu-button-collapse-left { min-width: 0; margin-left: -1px; padding-left: 0; border-bottom-left-radius: 0; border-top-left-radius: 0; vertical-align: top; } .jfk-button-collapse-left, .jfk-button-collapse-right { z-index: 1; } .jfk-button-collapse-right { margin-right: 0; border-top-right-radius: 0; border-bottom-right-radius: 0; } .goog-flat-menu-button-caption { vertical-align: top; white-space: nowrap; } .goog-flat-menu-button-dropdown { position: absolute; right: 5px; top: 12px; width: 0; height: 0; border-color: #777 transparent; border-style: solid; border-width: 4px 4px 0; } .goog-menu { position: absolute margin: 0; padding: 6px 0; border: 1px solid rgba(0, 0, 0, .2); background: #fff; font-size: 13px; outline: none; box-shadow: 0 2px 4px rgba(0, 0, 0, .2); transition: opacity .218s; cursor: default; } .goog-menuitem { position: relative; margin: 0; padding: 6px 7em 6px 30px; white-space: nowrap; color: #333; list-style: none; cursor: pointer; } .goog-menuitem:hover { padding-top: 5px; padding-bottom: 5px border-color: #eee; border-style: dotted; border-width: 1px 0; background-color: #eee; color: #333; } .feedlyng-menu-button-container > .goog-menu-button { margin-left: -2px; } .feedlyng.goog-menu { position: absolute; z-index: 2147483646; } .feedlyng .goog-menuitem:hover { background-color: #eeeeee; } #feedlyng-open-panel { float: left; } .feedlyng-panel { position: fixed; background-color: #ffffff; color: #333333; box-shadow: 1px 2px 5px rgba(0, 0, 0, 0.5); z-index: 2147483646; } .feedlyng-panel :-moz-any(label, legend) { cursor: default; } .feedlyng-panel input[type="text"] { padding: 2px; border: 1px solid #b2b2b2; } .feedlyng-panel-body { margin: 8px; } .feedlyng-panel-body > fieldset { margin: 8px 0; padding: 0; } .feedlyng-panel-body > fieldset > legend { margin: 0 4px; padding: 0 4px; } .feedlyng-panel.root > .feedlyng-panel-body > :-moz-any(.feedlyng-panel-name, fieldset) { display: none; } .feedlyng-panel-terms { border-spacing: 4px; } .feedlyng-panel-terms > tbody > tr > td { padding: 0; white-space: nowrap; } .feedlyng-panel-terms :-moz-any(input, label) { margin: 0; vertical-align: middle; } @-moz-keyframes error { 0% { background-color: #ffff00; border-color: #ff0000; } } .feedlyng-panel-terms-textbox.error { animation: error 1s; } .feedlyng-panel-terms-textbox-label { display: block; font-size: 90%; text-align: right; } .feedlyng-panel-terms-checkbox-label { padding: 0 8px; } .feedlyng-panel-rules { display: table; } .feedlyng-panel-rule { display: table-row; } .feedlyng-panel-rule:hover { background-color: #eeeeee; } .feedlyng-panel-rule > div { display: table-cell; white-space: nowrap; } .feedlyng-panel-rule-name { width: 100%; padding-left: 16px; cursor: default; } .feedlyng-panel-rule-count { padding: 0 8px; font-weight: bold; cursor: default; } .feedlyng-panel-buttons { margin: 8px; text-align: right; white-space: nowrap; } .feedlyng-panel-addfilter { float: left; margin-right: 8px; } .feedlyng-panel-pastefilter { float: left; margin-right: 16px; } .feedlyng-panel-ok { margin-right: 8px; } .feedlyng-panel-cancel { margin-right: 0; } `; function __(strings, ...values) { var key = values.map((v, i) => `${strings[i]}{${i}}`).join('') + strings[strings.length - 1]; if (!(key in __.data)) throw new Error('localized string not found: ' + key); return __.data[key].replace(/\{(\d+)\}/g, (_, cap) => { return values[cap]; }); } Object.defineProperties(__, { 'config': { configurable: true, value: { defaultLocale: 'en-US', }, }, 'locales': { configurable: true, value: {}, }, 'data': { configurable: true, get() { return this.locales[this.config.locale]; }, }, 'languages': { configurable: true, get() { return Object.keys(this.locales); }, }, 'add': { configurable: true, value: function add({locale, data}) { if (locale in this.locales) throw new Error('failed to add existing locale: ' + locale); this.locales[locale] = data; }, }, 'use': { configurable: true, value: function use(locale) { if (locale in this.locales) this.config.locale = locale; else if (this.config.defaultLocale) this.config.locale = this.config.defaultLocale; else throw new Error('unknown locale: ' + locale); }, }, }); __.add({ locale: 'en-US', data: { 'Feedly NG Filter': 'Feedly NG Filter', 'OK': 'OK', 'Cancel': 'Cancel', 'Add': 'Add', 'Copy': 'Copy', 'Paste': 'Paste', 'New Filter': 'New Filter', '{0} Rules': '{0} Rules', 'Title': 'Title', 'URL': 'URL', 'Feed Title': 'Feed Title', 'Feed URL': 'Feed URL', 'Author': 'Author', 'Contents': 'Contents', 'Ignore Case': 'Ignore Case', 'Edit': 'Edit', 'Delete': 'Delete', 'Hit Count': 'Hit Count:\t{0}', 'Last Hit': 'Last Hit:\t{0}', 'NG Setting': 'NG Setting', 'Setting': 'Setting', 'Import Configuration': 'Import Configuration', 'Preferences were successfully imported.': 'Preferences were successfully imported.', 'Export Configuration': 'Export Configuration', 'Language': 'Language', 'NG Settings were modified.\nNew filters take effect after next refresh.': 'NG Settings were modified.\nNew filters take effect after next refresh.', }, }); __.add({ locale: 'ja', data: { 'Feedly NG Filter': 'Feedly NG Filter', 'OK': 'OK', 'Cancel': 'キャンセル', 'Add': '追加', 'Copy': 'コピー', 'Paste': '貼り付け', 'New Filter': '新しいフィルタ', '{0} Rules': '{0}のルール', 'Title': 'タイトル', 'URL': 'URL', 'Feed Title': 'フィードのタイトル', 'Feed URL': 'フィードの URL', 'Author': '著者', 'Contents': '本文', 'Ignore Case': '大/小文字を区別しない', 'Edit': '編集', 'Delete': '削除', 'Hit Count:\t{0}': 'ヒット数:\t{0}', 'Last Hit:\t{0}': '最終ヒット:\t{0}', 'NG Setting': 'NG 設定', 'Setting': '設定', 'Import Configuration': '設定をインポート', 'Preferences were successfully imported.': '設定をインポートしました', 'Export Configuration': '設定をエクスポート', 'Language': '言語', 'NG Settings were modified.\nNew filters take effect after next refresh.': 'NG 設定を更新しました。\n新しいフィルタは次回読み込み時から有効になります。', }, }); __.use(navigator.language); function Class(sup, pro) { if (sup && typeof sup === 'object') pro = sup, sup = Object; var con = Object.getOwnPropertyDescriptor(pro, 'constructor'); if (!con) con = {value: Function(), writable: true, configurable: true}; if (con.configurable) { con.enumerable = false; Object.defineProperty(pro, 'constructor', con); } con = pro.constructor; con.prototype = pro; con.superclass = sup; Object.setPrototypeOf(con, Class.prototype); Object.setPrototypeOf(pro, sup && sup.prototype); return Proxy.createFunction(con, function() con.createInstance(arguments)); } Class = Class(Function, { constructor: Class, $super: function $super() { var sup = this.superclass; var method = sup.prototype[$super.caller === this ? 'constructor' : $super.caller.name]; return Function.prototype.call.apply(method, arguments); }, isSubClass: function isSubClass(cls) { return this.prototype instanceof cls; }, createInstance: function createInstance(args) { var instance = Object.create(this.prototype); var result = this.apply(instance, args || []); return result instanceof Object ? result : instance; }, toString: function toString() { var arr = []; var cls = this; do { arr.push(cls.name); } while (cls = cls.superclass); return '[object Class [class ' + arr.join(', ') + ']]'; }, getOwnPropertyDescriptor: function(name) Object.getOwnPropertyDescriptor(this, name), getPropertyDescriptor: function(name) Object.getPropertyDescriptor(this, name), getOwnPropertyNames: function(name) Object.getOwnPropertyNames(this, name), getPropertyNames: function(name) Object.getPropertyNames(this, name), defineProperty: function(name) Object.defineProperty(this, name), delete: function(name) delete this[name], fix: function() { if (!Object.isFrozen(this)) return void 0; var res = {}; Object.getOwnPropertyNames(this).forEach((name) => res[name] = Object.getOwnPropertyDescriptor(this, name)); return res; }, has: function(name) name in this, hasOwn: function(name) Object.prototype.hasOwnProperty.call(this, name), get: function(receiver, name) { if (name in this) return this[name]; var method = this.prototype[name]; if (typeof method === 'function') return Function.prototype.call.bind(method); return void 0; }, set: function(receiver, name, val) this[name] = val, enumerate: function() [name for (name in this)], keys: function() Object.keys(this), }); var Subject = Class({ constructor: function Subject() { this.listeners = {}; }, on: function on(type, listener) { type += ''; if (type.trim().indexOf(' ') !== -1) { type.match(/\S+/g).forEach(function(t) this.on(t, listener), this); return; } if (!(type in this.listeners)) this.listeners[type] = []; var arr = this.listeners[type]; var index = arr.indexOf(listener); if (index === -1) arr.push(listener); }, once: function once(type, listener) { function onetimeListener() { this.removeListener(onetimeListener); return listener.apply(this, arguments); } this.on(type, onetimeListener); return onetimeListener; }, removeListener: function removeListener(type, listener) { if (!(type in this.listeners)) return; var arr = this.listeners[type]; var index = arr.indexOf(listener); if (index !== -1) arr.splice(index, 1); }, removeAllListeners: function removeAllListeners(type) { delete this.listeners[type]; }, dispatchEvent: function dispatchEvent(event) { event.timeStamp = Date.now(); if (event.type in this.listeners) { this.listeners[event.type].concat().forEach(function(listener) { try { if (typeof listener === 'function') listener.call(this, event); else listener.handleEvent(event); } catch (e) { setTimeout(function() { throw e; }, 0); } }, this); } return !event.canceled; }, emit: function emit(type, data) { var event = this.createEvent(type); if (data instanceof Object) extend(event, data); return this.dispatchEvent(event); }, createEvent: function createEvent(type) { return new Event(type, this); }, }); var Event = Class({ constructor: function Event(type, target) { this.type = type; this.target = target; }, canceled: false, timeStamp: null, preventDefault: function preventDefault() { this.canceled = true; }, }); var DataTransfer = Class(Subject, { constructor: function DataTransfer() { DataTransfer.$super(this); }, set: function set(type, data) { this.purge(); this.type = type; this.data = data; this.emit(type, {data: data}); }, purge: function purge() { this.emit('purge', {data: this.data}); delete this.data; }, setForCut: function setForCut(data) { this.set('cut', data); }, setForCopy: function setForCopy(data) { this.set('copy', data); }, receive: function receive() { var data = this.data; if (this.type === 'cut') this.purge(); return data; }, }); var MenuCommand = Class({ constructor: function MenuCommand(label, oncommand, disabled) { this.label = label; this.oncommand = oncommand; this.disabled = !!disabled; this.register(); }, register: function register() { this.uuid = GM_registerMenuCommand(this.label, this.oncommand); if (MenuCommand.contextmenu) { this.menuitem = document.createElement('menuitem'); this.menuitem.label = this.label; this.menuitem.addEventListener('click', this.oncommand, false); MenuCommand.contextmenu.appendChild(this.menuitem); } if (this.disabled) this.disable(); }, unregister: function unregister() { if (typeof GM_unregisterMenuCommand === 'function') GM_unregisterMenuCommand(this.uuid); document.adoptNode(this.menuitem); }, disable: function disable() { if (typeof GM_disableMenuCommand === 'function') GM_disableMenuCommand(this.uuid); this.menuitem.disabled = true; }, enable: function enable() { if (typeof GM_enableMenuCommand === 'function') GM_enableMenuCommand(this.uuid); this.menuitem.disabled = false; }, }); MenuCommand.contextmenu = null; var Preference = Class(Subject, { constructor: function Preference() { if (Preference._instance) return Preference._instance; Preference.$super(this); Preference._instance = this; this.dict = {}; }, has: function has(key) key in this.dict, get: function get(key, def) this.has(key) ? this.dict[key] : def, set: function set(key, value) { var prev = this.dict[key]; if (value !== prev) { this.dict[key] = value; this.emit('change', { propertyName: key, prevValue: prev, newValue: value, }); } return value; }, del: function del(key) { if (!this.has(key)) return; var prev = this.dict[key]; delete this.dict[key]; this.emit('delete', { propertyName: key, prevValue: prev, }); }, load: function load(str) { if (!str) str = GM_getValue(Preference.prefName, Preference.defaultPref || '({})'); var obj = eval('(' + str + ')'); if (!obj || typeof obj !== 'object') return; this.dict = {}; for (let [key, value] in Iterator(obj)) this.set(key, value); this.emit('load'); }, write: function write() { GM_setValue(Preference.prefName, this.toSource()); }, autoSave: function autoSave() { if (autoSave.reserved) return; window.addEventListener('unload', () => this.write(), false); autoSave.reserved = true; }, exportToFile: function exportToFile() { var blob = new Blob([this.toSource()], { type: 'application/octet-stream', }); var url = URL.createObjectURL(blob); location.href = url; URL.revokeObjectURL(url); }, importFromString: function importFromString(str) { try { this.load(str); } catch (e if e instanceof SyntaxError) { showMessage(e, 'warning'); return false; } showMessage(__`Preferences were successfully imported.`); return true; }, importFromFile: function importFromFile() { openFilePicker(files => { if (!files) return; var r = new FileReader(); r.addEventListener('load', () => this.importFromString(r.result), false); r.readAsText(files[0]); }); }, toString: function toString() '[object Preference]', toSource: function toSource() this.dict.toSource(), }); Preference.prefName = 'settings'; var draggable = Class({ constructor: function draggable(element) { this.element = element; element.addEventListener('mousedown', this, false, false); }, isDraggableTarget: function isDraggableTarget(target) { if (!target) return false; if (target === this.element) return true; return !target.mozMatchesSelector(':-moz-any(select, button, input, textarea, [tabindex]), :-moz-any(select, button, input, textarea, [tabindex]) *'); }, detatch: function detatch() { this.element.removeEventListener('mousedown', this, false); }, handleEvent: function handleEvent(event) { var name = 'on' + event.type; if (name in this) this[name](event); }, onmousedown: function onMouseDown(event) { if (event.button !== 0) return; if (!this.isDraggableTarget(event.target)) return; event.preventDefault(); var focused = this.element.querySelector(':focus'); if (focused) focused.blur(); this.offsetX = event.pageX - this.element.offsetLeft; this.offsetY = event.pageY - this.element.offsetTop; document.addEventListener('mousemove', this, true, false); document.addEventListener('mouseup', this, true, false); }, onmousemove: function onMouseMove(event) { event.preventDefault(); this.element.style.left = event.pageX - this.offsetX + 'px'; this.element.style.top = event.pageY - this.offsetY + 'px'; }, onmouseup: function onMouseUp(event) { if (event.button !== 0) return; event.preventDefault(); document.removeEventListener('mousemove', this, true); document.removeEventListener('mouseup', this, true); }, }); var Filter = Class({ constructor: function Filter(filter) { if (!(this instanceof Filter)) return Filter.createInstance(arguments); if (!(filter instanceof Object)) filter = {}; this.name = filter.name || ''; this.regexp = extend({}, filter.regexp || {}); this.children = filter.children ? filter.children.map(Filter) : []; this.hitcount = filter.hitcount || 0; this.lasthit = filter.lasthit || 0; }, test: function test(entry) { for (var [name, reg] in Iterator(this.regexp)) if (!reg.test(entry[name] || '')) return false; var hit = this.children.length ? this.children.some(filter => filter.test(entry)) : !!reg; if (hit) { this.hitcount++; this.lasthit = Date.now(); } return hit; }, appendChild: function appendChild(filter) { if (!(filter instanceof this.constructor)) return null; this.removeChild(filter); this.children.push(filter); this.sortChildren(); return filter; }, removeChild: function removeChild(filter) { if (!(filter instanceof this.constructor)) return null; var index = this.children.indexOf(filter); if (index !== -1) this.children.splice(index, 1); return filter; }, sortChildren: function sortChildren() { return this.children.sort((a, b) => b.name < a.name); }, }); var Entry = Class({ constructor: function Entry(data) { this.data = data; }, get title() { var div = document.createElement('div'); div.innerHTML = this.data.title || ''; Object.defineProperty(this, 'title', {configurable: true, value: div.textContent}); return this.title; }, get id() this.data.id, get url() ((this.data.alternate || 0)[0] || 0).href, get sourceTitle() this.data.origin.title, get sourceURL() this.data.origin.streamId.replace(/^[^/]+\//, ''), get body() (this.data.content || this.data.summary || 0).content, get author() this.data.author, get recrawled() this.data.recrawled, get published() this.data.published, get updated() this.data.updated, get keywords() this.data.keywords, get unread() this.data.unread, get tags() this.data.tags.map(tag => tag.label), }); var Panel = Class(Subject, { constructor: function Panel() { Panel.$super(this); var panel = document.createElement('form'); panel.classList.add('feedlyng-panel'); draggable(panel); panel.addEventListener('submit', event => { event.preventDefault(); event.stopPropagation(); this.apply(); }, false); var submit = document.createElement('input'); submit.type = 'submit'; submit.style.display = 'none'; var body = document.createElement('div'); body.classList.add('feedlyng-panel-body'); var buttons = document.createElement('div'); buttons.classList.add('feedlyng-panel-buttons'); var ok = createGoogButton(__`OK`, () => this.apply()); ok.classList.add('feedlyng-panel-ok'); var cancel = createGoogButton(__`Cancel`, () => this.close()); cancel.classList.add('feedlyng-panel-cancel'); panel.appendChild(submit); panel.appendChild(body); panel.appendChild(buttons); buttons.appendChild(ok); buttons.appendChild(cancel); this.dom = { element: panel, body: body, buttons: buttons, }; }, get opened() !!this.dom.element.parentNode, open: function open(anchorElement) { if (this.opened) return; if (!this.emit('showing')) return; if (!anchorElement || anchorElement.nodeType !== 1) anchorElement = null; document.body.appendChild(this.dom.element); this.snapTo(anchorElement); if (anchorElement) { let onWindowResize = this.snapTo.bind(this, anchorElement); window.addEventListener('resize', onWindowResize, false); this.on('hidden', window.removeEventListener.bind(window, 'resize', onWindowResize, false)); } var focused = document.querySelector(':focus'); if (focused) focused.blur(); var tab = Array.slice(this.dom.element.querySelectorAll(':not(.feedlyng-panel) > :-moz-any(button, input, select, textarea, [tabindex])')) .sort((a, b) => (b.tabIndex || 0) < (a.tabIndex || 0))[0]; if (tab) { tab.focus(); if (tab.select) tab.select(); } this.emit('shown'); }, apply: function apply() { if (this.emit('apply')) this.close(); }, close: function close() { if (!this.opened) return; if (!this.emit('hiding')) return; document.adoptNode(this.dom.element); this.emit('hidden'); }, toggle: function toggle(anchorElement) { if (this.opened) this.close(); else this.open(anchorElement); }, moveTo: function moveTo(x, y) { this.dom.element.style.left = x + 'px'; this.dom.element.style.top = y + 'px'; }, snapTo: function snapTo(anchorElement) { var pad = 5; var x = pad; var y = pad; if (anchorElement) { var {left, bottom: top} = anchorElement.getBoundingClientRect(); left += pad; top += pad; var {width, height} = this.dom.element.getBoundingClientRect(); var right = left + width + pad; var bottom = top + height + pad; var {innerWidth, innerHeight} = window; if (innerWidth < right) left -= right - innerWidth; if (innerHeight < bottom) top -= bottom - innerHeight; x = Math.max(x, left); y = Math.max(y, top); } this.moveTo(x, y); }, getFormData: function getFormData(asElement) { var data = {}; Array.slice(this.dom.body.querySelectorAll('[name]')).forEach((elem) => { var value; if (asElement) { value = elem; } else { if (elem.localName.toLowerCase() === 'input' && (elem.type === 'checkbox' || elem.type === 'radio')) value = elem.checked; else value = 'value' in elem ? elem.value : elem.getAttribute('value'); } var path = elem.name.split('.'); var leaf = path.pop(); var cd = path.reduce((parent, dirName) => { if (!(dirName in parent)) parent[dirName] = {}; return parent[dirName]; }, data); var reg = /\[\]$/; if (reg.test(leaf)) { leaf = leaf.replace(reg, ''); if (!(leaf in cd)) cd[leaf] = []; cd[leaf].push(value); } else { cd[leaf] = value; } }); return data; }, appendContent: function appendContent(element) { if (element instanceof Array) return element.map(appendContent, this); return this.dom.body.appendChild(element); }, removeContents: function removeContents() { var range = node.ownerDocument.createRange(); range.selectNodeContents(this.dom.body); range.deleteContents(); range.detach(); }, }); var FilterListPanel = Class(Panel, { constructor: function FilterListPanel(filter, isRoot) { FilterListPanel.$super(this); this.filter = filter; var self = this; if (isRoot) this.dom.element.classList.add('root'); var add = createGoogButton(__`Add`, () => { var f = new Filter(); f.name = __`New Filter`; this.on('apply', () => this.filter.appendChild(f)); this.appendFilter(f); }); add.classList.add('feedlyng-panel-addfilter'); this.dom.buttons.insertBefore(add, this.dom.buttons.firstChild); var paste = createGoogButton(__`Paste`, () => { if (!clipboard.data) return; var f = new Filter(clipboard.receive()); this.on('apply', () => this.filter.appendChild(f)); this.appendFilter(f); }); paste.classList.add('feedlyng-panel-pastefilter'); if (!clipboard.data) paste.classList.add('jfk-button-disabled'); clipboard.on('copy', onCopy); clipboard.on('purge', onPurge); function onCopy() { paste.classList.remove('jfk-button-disabled'); } function onPurge() { paste.classList.add('jfk-button-disabled'); } this.dom.buttons.insertBefore(paste, add.nextSibling); this.on('showing', this.initContents); this.on('apply', this); this.on('hidden', () => { clipboard.removeListener('copy', onCopy); clipboard.removeListener('purge', onPurge); }); }, initContents: function initContents() { var filter = this.filter; var nameTextbox = document.createElement('input'); nameTextbox.classList.add('feedlyng-panel-name'); nameTextbox.type = 'text'; nameTextbox.name = 'name'; nameTextbox.size = '32'; nameTextbox.autocomplete = 'off'; nameTextbox.value = filter.name; var terms = document.createElement('fieldset'); var legend = document.createElement('legend'); legend.textContent = __`${filter.name} Rules`; var table = document.createElement('table'); table.classList.add('feedlyng-panel-terms'); var tbody = document.createElement('tbody'); for (let [type, labelText] in Iterator({ title: __`Title`, url: __`URL`, sourceTitle: __`Feed Title`, sourceURL: __`Feed URL`, author: __`Author`, body: __`Contents`, })) { let row = document.createElement('tr'); let left = document.createElement('td'); let center = document.createElement('td'); let right = document.createElement('td'); let textbox = document.createElement('input'); textbox.classList.add('feedlyng-panel-terms-textbox'); textbox.type = 'text'; textbox.name = 'regexp.' + type + '.source'; textbox.size = '32'; textbox.autocomplete = 'off'; if (type in filter.regexp) textbox.value = filter.regexp[type].source.replace(/((?:^|[^\\])(?:\\\\)*)\\(?=\/)/g, '$1'); let label = createLabel(textbox, labelText); label.classList.add('feedlyng-panel-terms-textbox-label'); let ic = document.createElement('input'); ic.classList.add('feedlyng-panel-terms-checkbox'); ic.type = 'checkbox'; ic.name = 'regexp.' + type + '.ignoreCase'; if (type in filter.regexp) ic.checked = filter.regexp[type].ignoreCase; let icl = createLabel(ic, 'i'); icl.classList.add('feedlyng-panel-terms-checkbox-label'); icl.title = __`Ignore Case`; tbody.appendChild(row); row.appendChild(left); left.appendChild(label); row.appendChild(center); center.appendChild(textbox); row.appendChild(right); right.appendChild(ic); right.appendChild(icl); } var rules = document.createElement('div'); rules.classList.add('feedlyng-panel-rules'); terms.appendChild(legend); terms.appendChild(table); table.appendChild(tbody); this.appendContent([nameTextbox, terms, rules]); this.dom.rules = rules; filter.children.forEach(this.appendFilter, this); }, appendFilter: function appendFilter(filter) { var panel; var updateRow = () => { var title = __`Hit Count:\t${filter.hitcount}`; if (filter.lasthit) title += '\n' + __`Last Hit:\t${new Date(filter.lasthit).toLocaleString()}`; rule.title = title; name.textContent = filter.name; count.textContent = filter.children.length || ''; }; var onEdit = () => { if (panel) { panel.close(); return; } panel = new FilterListPanel(filter); panel.on('shown', () => edit.querySelector('.jfk-button').classList.add('jfk-button-checked')); panel.on('hidden', () => { edit.querySelector('.jfk-button').classList.remove('jfk-button-checked'); panel = null; }); panel.on('apply', setTimeout.bind(null, updateRow, 0)); panel.open(this); }; var onCopy = () => clipboard.setForCopy(filter); var onDelete = () => { document.adoptNode(rule); this.on('apply', () => this.filter.removeChild(filter)); } var rule = document.createElement('div'); rule.classList.add('feedlyng-panel-rule'); if (filter.children.length) rule.classList.add('parent'); var name = document.createElement('div'); name.classList.add('feedlyng-panel-rule-name'); name.addEventListener('dblclick', onEdit, true); var count = document.createElement('div'); count.classList.add('feedlyng-panel-rule-count'); var buttons = document.createElement('div'); buttons.classList.add('feedlyng-panel-rule-buttons'); var edit = createGoogMenuButton(__`Edit`, onEdit, [[__`Copy`, onCopy], [__`Delete`, onDelete]]); edit.classList.add('feedlyng-panel-rule-edit'); updateRow(); rule.appendChild(name); rule.appendChild(count); rule.appendChild(buttons); buttons.appendChild(edit); this.dom.rules.appendChild(rule); }, handleEvent: function handleEvent(event) { if (event.type !== 'apply') return; var data = this.getFormData(true); var filter = this.filter; var regexp = {}; var error = false; for (let [type, {source, ignoreCase}] in Iterator(data.regexp)) { if (!source.value) continue; try { regexp[type] = RegExp(source.value, ignoreCase.checked ? 'i' : ''); } catch (e if e instanceof SyntaxError) { error = true; event.preventDefault(); source.classList.remove('error'); source.offsetWidth; source.classList.add('error'); } } if (error) return; var prevSource = filter.toSource(); filter.name = data.name.value; filter.regexp = regexp; if (filter.toSource() !== prevSource) { filter.hitcount = 0; filter.lasthit = 0; } filter.sortChildren(); }, }); var GoogMenu = Class({ constructor: function GoogMenu(anchorElement, items) { this.items = items; this.anchorElement = anchorElement; anchorElement.addEventListener('mousedown', this, false); }, get opened() !!((this.dom || 0).element || 0).parentNode, init: function init() { var menu = document.createElement('div'); menu.className = 'feedlyng goog-menu goog-menu-vertical'; menu.addEventListener('click', this, false); this.items.forEach((item) => { var menuitem = document.createElement('div'); if (typeof item === 'string') { if (/^-+$/.test(item)) menuitem.className = 'goog-menuseparator'; } else { var [label, fn] = item; menuitem.className = 'goog-menuitem'; var content = document.createElement('div'); content.className = 'goog-menuitem-content'; content.textContent = label; menuitem.appendChild(content); if (fn) menuitem.addEventListener('click', fn, false); } menu.appendChild(menuitem); }); this.dom = { element: menu, }; }, open: function open() { if (this.opened) return; var {right, bottom} = this.anchorElement.getBoundingClientRect(); var menu = this.dom.element; document.body.appendChild(menu); menu.style.left = right - menu.offsetWidth + 'px'; menu.style.top = bottom + 'px'; this.anchorElement.classList.add('goog-flat-menu-button-open'); document.addEventListener('mousedown', this, true); document.addEventListener('blur', this, true); }, close: function close() { document.removeEventListener('mousedown', this, true); document.removeEventListener('blur', this, true); document.adoptNode(this.dom.element); this.anchorElement.classList.remove('goog-flat-menu-button-open'); }, handleEvent: function handleEvent({type, target, currentTarget}) { switch (type) { case 'blur': if (target === document) this.close(); return; case 'click': if (target.mozMatchesSelector('.goog-menuitem, .goog-menuitem *')) this.close(); return; case 'mousedown': var pos = this.anchorElement.compareDocumentPosition(target); if (currentTarget === document && (!pos || pos & target.DOCUMENT_POSITION_CONTAINED_BY)) return; if (this.opened) { if (!target.mozMatchesSelector('.goog-menu *')) this.close(); } else { if (!this.dom) this.init(); this.open(); } return; } }, }); Preference.defaultPref = { filter: { name: '', regexp: {}, children: [ { name: 'AD', regexp: { title: /^\W?(?:ADV?|PR)\b/, }, children: [], }, ], }, }.toSource(); evalInContent(String.raw` (() => { var XHR = XMLHttpRequest; var uniqueId = 0; XMLHttpRequest = function XMLHttpRequest() { var req = new XHR(); req.open = open; req.setRequestHeader = setRequestHeader; req.addEventListener('readystatechange', onReadyStateChange, false); return req; }; function open(method, url, async) { this.__url__ = url; return XHR.prototype.open.apply(this, arguments); } function setRequestHeader(header, value) { if (header === 'Authorization') this.__auth__ = value; return XHR.prototype.setRequestHeader.apply(this, arguments); } function onReadyStateChange() { if (this.readyState < 4 || this.status !== 200) return; if (!/^\/\/(?:cloud\.)?feedly\.com\/v3\/streams\/contents\b/.test(this.__url__)) return; var pongEventType = 'streamcontentloaded_callback' + uniqueId++; var data = JSON.stringify({ type: pongEventType, auth: this.__auth__, text: this.responseText, }); var event = new MessageEvent('streamcontentloaded', { bubbles: true, cancelable: false, data: data, origin: location.href, source: null, }); var onPong = ({data}) => Object.defineProperty(this, 'responseText', {configurable: true, value: data}); document.addEventListener(pongEventType, onPong, false); document.dispatchEvent(event); document.removeEventListener(pongEventType, onPong, false); } })(); `); document.addEventListener('streamcontentloaded', function(event) { var {type: pongEventType, auth, text} = JSON.parse(event.data); var data = JSON.parse(text); var logging = pref.get('logging', true); var filter = pref.get('filter'); var filteredEntryIds = []; var hasUnread = false; data.items = data.items.filter((item) => { var entry = new Entry(item); if (!filter.test(entry)) return true; if (logging) GM_log('filtered: "' + (entry.title || '') + '" ' + entry.url); filteredEntryIds.push(entry.id); if (entry.unread) hasUnread = true; return false; }); if (!filteredEntryIds.length) return; var data = JSON.stringify(data); try { var ev = new MessageEvent(pongEventType, { bubbles: true, cancelable: false, data: data, origin: location.href, source: window, }); } catch (e if e instanceof TypeError) { var ev = document.createEvent('MessageEvent'); ev.initMessageEvent(pongEventType, true, false, data, location.href, '', null); } document.dispatchEvent(ev); if (!hasUnread) return; sendJSON({ url: '/v3/markers', headers: { Authorization: auth, }, data: { action: 'markAsRead', entryIds: filteredEntryIds, type: 'entries', }, }); }, false); var contextmenu = document.createElement('menu'); contextmenu.type = 'context'; contextmenu.id = 'feedlyng-contextmenu'; MenuCommand.contextmenu = contextmenu; var rootFilterPanel; var settingsMenuItem; var clipboard = new DataTransfer(); var pref = new Preference(); pref.on('change', function({propertyName, newValue}) { switch (propertyName) { case 'filter': if (!Filter.prototype.isPrototypeOf(newValue)) this.set('filter', new Filter(newValue)); break; case 'language': __.use(newValue); break; } }); document.addEventListener('DOMContentLoaded', () => { GM_addStyle(CSS_STYLE_TEXT); pref.load(); pref.autoSave(); registerMenuCommands(); addSettingsMenuItem(); }, false); function registerMenuCommands() { menuCommand(__`Setting` + '...', togglePrefPanel); menuCommand(__`Language` + '...', function() { var langField = document.createElement('fieldset'); var title = document.createElement('legend'); title.textContent = __`Language`; var select = document.createElement('select'); __.languages.forEach((lang) => { var option = document.createElement('option'); option.value = lang; option.textContent = lang; if (lang === __.config.locale) option.selected = true; select.appendChild(option); }); langField.appendChild(title); langField.appendChild(select); var p = new Panel(); p.appendContent(langField); p.on('apply', () => pref.set('language', select.value)); p.open(); }); menuCommand(__`Import Configuration` + '...', () => pref.importFromFile()); menuCommand(__`Export Configuration`, () => pref.exportToFile()); } function togglePrefPanel(anchorElement) { if (rootFilterPanel) { rootFilterPanel.close(); return; } rootFilterPanel = new FilterListPanel(pref.get('filter'), true); rootFilterPanel.on('apply', () => showMessage(__`NG Settings were modified.\nNew filters take effect after next refresh.`)); rootFilterPanel.on('hidden', () => { clipboard.purge(); rootFilterPanel = null; }); rootFilterPanel.open(anchorElement); } function onNGSettingCommand({target}) { togglePrefPanel(target); } function createGoogButton(text, fn) { var button = document.createElement('div'); button.className = 'goog-inline-block jfk-button jfk-button-standard unselectable'; button.tabIndex = 0; button.textContent = text; if (fn) { button.addEventListener('click', fn, false); button.addEventListener('keydown', function({which}) { if (which === 13) fn.apply(this, arguments); }, false); } return button; } function createGoogMenuButton(text, fn, arr) { var container = document.createElement('div'); container.className = 'goog-inline-block'; var button = createGoogButton(text, fn); button.classList.add('jfk-button-collapse-right'); var options = document.createElement('div'); options.className = 'goog-inline-block goog-flat-menu-button goog-flat-menu-button-collapse-left unselectable'; options.tabIndex = 0; container.appendChild(button); container.appendChild(options); options.insertAdjacentHTML('beforeend', '<div class="goog-inline-block goog-flat-menu-button-caption"> </div>'); options.insertAdjacentHTML('beforeend', '<div class="goog-inline-block goog-flat-menu-button-dropdown"></div>'); new GoogMenu(options, arr); return container; } function showMessage(str, type) { if (typeof GM_notification === 'function') GM_notification(str); } function addSettingsMenuItem() { var feedlyTabs = document.getElementById('feedlyTabs'); if (!feedlyTabs) { setTimeout(addSettingsMenuItem, 100); return; } var prefListener; var observer = new MutationObserver(function mutationCallback() { if (!document.getElementById('feedly-ng-filter-setting')) pref.removeListener('change', prefListener); var prefItem = document.querySelector('#feedlyTabs .tab > .label[data-uri="preferences"]'); if (!prefItem) return; var prefItemTab = prefItem.parentNode; var tab = document.createElement('div'); tab.className = 'tab'; tab.setAttribute('contextmenu', MenuCommand.contextmenu.id); tab.addEventListener('click', onNGSettingCommand, false); var label = document.createElement('div'); label.id = 'feedly-ng-filter-setting'; label.className = 'label primary iconless'; label.textContent = __`NG Setting`; tab.appendChild(label); prefItemTab.parentNode.insertBefore(tab, prefItemTab.nextSibling); document.body.appendChild(contextmenu); prefListener = ({propertyName}) => { if (propertyName === 'language') label.textContent = __`NG Setting`; }; pref.on('change', prefListener); }); observer.observe(feedlyTabs, { childList: true, }); } function menuCommand(label, fn) { return new MenuCommand(__`Feedly NG Filter` + ' - ' + label, fn); } function xhr(details) { var opt = extend({}, details); var {data} = opt; if (!opt.method) opt.method = data ? 'POST' : 'GET'; if (data instanceof Object) { opt.data = [pair.map(encodeURIComponent).join('=') for (pair in Iterator(data))].join('&'); if (!opt.headers) opt.headers = {}; opt.headers['Content-Type'] = 'application/x-www-form-urlencoded; charset=utf-8'; } setTimeout(GM_xmlhttpRequest, 0, opt); } function sendJSON(details) { var opt = extend({}, details); var {data} = opt; if (!opt.headers) opt.headers = {}; opt.method = 'POST'; opt.headers['Content-Type'] = 'application/json; charset=utf-8'; opt.data = JSON.stringify(data); return xhr(opt); } function evalInContent(code) { var script = document.createElement('script'); script.textContent = code; document.adoptNode(document.head.appendChild(script)); } function openFilePicker(callback, multiple) { var canceled = true; var input = document.createElement('input'); input.type = 'file'; input.multiple = multiple; input.addEventListener('change', () => { canceled = false; callback(Array.slice(input.files)); }, false); input.click(); if (canceled) setTimeout(callback, 0, null); } function createLabel(element, text) { var label = document.createElement('label'); if (1 < arguments.length) label.textContent = text; var id = element.id; if (!id) { if (!('id' in createLabel)) createLabel.id = 0; id = 'id_for_label_' + createLabel.id++; element.id = id; } label.htmlFor = id; return label; } function extend(dst, src) { for (let [key, value] in Iterator(src)) dst[key] = value; return dst; } })();