您需要先安装一个扩展,例如 篡改猴、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;
- }
- })();