mirror of
https://gitlab.com/KevinRoebert/ClearUrls
synced 2025-12-17 14:45:37 +07:00
init 2
This commit is contained in:
44
__tests__/utils/CircularBuffer.ts
Normal file
44
__tests__/utils/CircularBuffer.ts
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
import CircularBuffer from '../../source/utils/CircularBuffer'
|
||||||
|
|
||||||
|
describe('CircularBuffer', () => {
|
||||||
|
let capacity: number
|
||||||
|
let buffer: CircularBuffer<string>
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
capacity = 10
|
||||||
|
buffer = new CircularBuffer<string>(capacity)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should creates a buffer with the specified capacity', () => {
|
||||||
|
expect(buffer.capacity).toBe(capacity)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should add item to the buffer', () => {
|
||||||
|
expect(buffer.size).toBe(0)
|
||||||
|
buffer.enqueue('foo')
|
||||||
|
expect(buffer.size).toBe(1)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should remove oldest item if the buffer is full on adding new items', () => {
|
||||||
|
buffer = new CircularBuffer<string>(2)
|
||||||
|
buffer.enqueue('foo1')
|
||||||
|
buffer.enqueue('foo2')
|
||||||
|
buffer.enqueue('foo3')
|
||||||
|
buffer.enqueue('foo4')
|
||||||
|
expect(buffer.toArray()).toEqual(['foo3', 'foo4'])
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
it('should return the current size via the size() getter', () => {
|
||||||
|
buffer.enqueue('foo')
|
||||||
|
expect(buffer.size).toBe(1)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should return true if buffer is empty', () => {
|
||||||
|
expect(buffer.isEmpty()).toBe(true)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should throw exception on illegal argument', () => {
|
||||||
|
expect(() => new CircularBuffer<any>(0)).toThrow('The capacity must be > 0')
|
||||||
|
})
|
||||||
|
})
|
||||||
4
jest.config.js
Normal file
4
jest.config.js
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
module.exports = {
|
||||||
|
preset: 'ts-jest',
|
||||||
|
testEnvironment: 'node',
|
||||||
|
};
|
||||||
23
source/log/Log.ts
Normal file
23
source/log/Log.ts
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
import CircularBuffer from '../utils/CircularBuffer'
|
||||||
|
import LogEntry from './LogEntry'
|
||||||
|
|
||||||
|
export default class Log extends CircularBuffer<LogEntry> {
|
||||||
|
public constructor(capacity: number) {
|
||||||
|
super(capacity)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new Log object from the given string.
|
||||||
|
*
|
||||||
|
* @param enc - the encoded log
|
||||||
|
* @returns a log object
|
||||||
|
*/
|
||||||
|
// public static from(enc: string) : Log {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns this log as string.
|
||||||
|
*
|
||||||
|
* @returns the log as string
|
||||||
|
*/
|
||||||
|
// public toString() : string {}
|
||||||
|
}
|
||||||
37
source/log/LogEntry.ts
Normal file
37
source/log/LogEntry.ts
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
import Rule from '../rules/rule'
|
||||||
|
|
||||||
|
export default class LogEntry {
|
||||||
|
private _before: string
|
||||||
|
private _after: string
|
||||||
|
private _rule: Rule
|
||||||
|
private _timestamp: number
|
||||||
|
private _runtime: number
|
||||||
|
|
||||||
|
constructor(before: string, after: string, rule: Rule, timestamp: number = Date.now(), runtime: number) {
|
||||||
|
this._before = before
|
||||||
|
this._after = after
|
||||||
|
this._rule = rule
|
||||||
|
this._timestamp = timestamp
|
||||||
|
this._runtime = runtime
|
||||||
|
}
|
||||||
|
|
||||||
|
get before() : string {
|
||||||
|
return this._before
|
||||||
|
}
|
||||||
|
|
||||||
|
get after() : string {
|
||||||
|
return this._after
|
||||||
|
}
|
||||||
|
|
||||||
|
get rule() : Rule {
|
||||||
|
return this._rule
|
||||||
|
}
|
||||||
|
|
||||||
|
get timestamp() : number {
|
||||||
|
return this._timestamp
|
||||||
|
}
|
||||||
|
|
||||||
|
get runtime() : number {
|
||||||
|
return this._runtime
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -51,18 +51,7 @@
|
|||||||
],
|
],
|
||||||
"background": {
|
"background": {
|
||||||
"scripts": [
|
"scripts": [
|
||||||
"browser-polyfill.js",
|
"background/main.js"
|
||||||
"core_js/message_handler.js",
|
|
||||||
"external_js/ip-range-check.js",
|
|
||||||
"core_js/tools.js",
|
|
||||||
"core_js/badgedHandler.js",
|
|
||||||
"core_js/pureCleaning.js",
|
|
||||||
"core_js/context_menu.js",
|
|
||||||
"core_js/historyListener.js",
|
|
||||||
"clearurls.js",
|
|
||||||
"core_js/storage.js",
|
|
||||||
"core_js/watchdog.js",
|
|
||||||
"core_js/eTagFilter.js"
|
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"content_scripts": [
|
"content_scripts": [
|
||||||
@@ -264,7 +253,7 @@
|
|||||||
"*://*.google.cat/*"
|
"*://*.google.cat/*"
|
||||||
],
|
],
|
||||||
"js": [
|
"js": [
|
||||||
"core_js/google_link_fix.js"
|
"scripts/googleLinkFix.js"
|
||||||
],
|
],
|
||||||
"run_at": "document_end"
|
"run_at": "document_end"
|
||||||
},
|
},
|
||||||
@@ -276,7 +265,7 @@
|
|||||||
"*://*.ya.ru/*"
|
"*://*.ya.ru/*"
|
||||||
],
|
],
|
||||||
"js": [
|
"js": [
|
||||||
"core_js/yandex_link_fix.js"
|
"scripts/yandexLinkFix.js"
|
||||||
],
|
],
|
||||||
"run_at": "document_end"
|
"run_at": "document_end"
|
||||||
}
|
}
|
||||||
11
source/rules/rule.ts
Normal file
11
source/rules/rule.ts
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
export default class Rule {
|
||||||
|
private _rule: RegExp
|
||||||
|
|
||||||
|
constructor(rule: RegExp) {
|
||||||
|
this._rule = rule
|
||||||
|
}
|
||||||
|
|
||||||
|
get value() : RegExp {
|
||||||
|
return this._rule
|
||||||
|
}
|
||||||
|
}
|
||||||
67
source/scripts/googleLinkFix.js
Normal file
67
source/scripts/googleLinkFix.js
Normal file
@@ -0,0 +1,67 @@
|
|||||||
|
/*
|
||||||
|
* ClearURLs
|
||||||
|
* Copyright (c) 2017-2020 Kevin Röbert
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU Lesser General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU Lesser General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Lesser General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*
|
||||||
|
* Based on:
|
||||||
|
* Remove Google Redirection
|
||||||
|
* https://github.com/kodango/Remove-Google-Redirection/blob/master/extension/chrome/remove-google-redirection.user.js
|
||||||
|
* Copyright (c) 2017 kodango
|
||||||
|
* MIT License: https://github.com/kodango/Remove-Google-Redirection/blob/master/LICENSE
|
||||||
|
*/
|
||||||
|
(function (window) {
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
function injectFunction() {
|
||||||
|
let ele = document.createElement('script');
|
||||||
|
let s = document.getElementsByTagName('script')[0];
|
||||||
|
|
||||||
|
ele.type = 'text/javascript';
|
||||||
|
ele.textContent = "Object.defineProperty(window, 'rwt', {" +
|
||||||
|
" value: function() { return true; }," +
|
||||||
|
" writable: false," +
|
||||||
|
" configurable: false" +
|
||||||
|
"});";
|
||||||
|
|
||||||
|
s.parentNode.insertBefore(ele, s);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* The main entry
|
||||||
|
*/
|
||||||
|
function main()
|
||||||
|
{
|
||||||
|
injectFunction();
|
||||||
|
|
||||||
|
document.addEventListener('mouseover', function (event) {
|
||||||
|
let a = event.target, depth = 1;
|
||||||
|
|
||||||
|
while (a && a.tagName !== 'A' && depth-- > 0) {
|
||||||
|
a = a.parentNode;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (a && a.tagName === 'A') {
|
||||||
|
try {
|
||||||
|
a.removeAttribute('data-cthref');
|
||||||
|
delete a.dataset.cthref;
|
||||||
|
} catch(e) {
|
||||||
|
console.log(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
main();
|
||||||
|
})(window);
|
||||||
66
source/scripts/yandexLinkFix.js
Normal file
66
source/scripts/yandexLinkFix.js
Normal file
@@ -0,0 +1,66 @@
|
|||||||
|
/*
|
||||||
|
* ClearURLs
|
||||||
|
* Copyright (c) 2017-2020 Kevin Röbert
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU Lesser General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU Lesser General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Lesser General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*
|
||||||
|
* Based on:
|
||||||
|
* Remove Google Redirection
|
||||||
|
* https://github.com/kodango/Remove-Google-Redirection/blob/master/extension/chrome/remove-google-redirection.user.js
|
||||||
|
* Copyright (c) 2017 kodango
|
||||||
|
* MIT License: https://github.com/kodango/Remove-Google-Redirection/blob/master/LICENSE
|
||||||
|
*/
|
||||||
|
(function (window) {
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
function injectFunction() {
|
||||||
|
let ele = document.createElement('script');
|
||||||
|
let s = document.getElementsByTagName('script')[0];
|
||||||
|
|
||||||
|
ele.type = 'text/javascript';
|
||||||
|
ele.textContent = "Object.defineProperty(window, '_borschik', {" +
|
||||||
|
" value: function() { return false; }," +
|
||||||
|
" writable: false," +
|
||||||
|
" configurable: false" +
|
||||||
|
"});";
|
||||||
|
|
||||||
|
s.parentNode.insertBefore(ele, s);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* The main entry
|
||||||
|
*/
|
||||||
|
function main() {
|
||||||
|
injectFunction();
|
||||||
|
|
||||||
|
document.addEventListener('mouseover', function (event) {
|
||||||
|
let a = event.target, depth = 1;
|
||||||
|
|
||||||
|
while (a && a.tagName !== 'A' && depth-- > 0) {
|
||||||
|
a = a.parentNode;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (a && a.tagName === 'A') {
|
||||||
|
try {
|
||||||
|
a.removeAttribute('data-counter');
|
||||||
|
delete a.dataset.cthref;
|
||||||
|
} catch (e) {
|
||||||
|
console.log(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
main();
|
||||||
|
})(window);
|
||||||
@@ -26,9 +26,11 @@
|
|||||||
import OptionsSync from 'webext-options-sync'
|
import OptionsSync from 'webext-options-sync'
|
||||||
import { Utils } from '../utils/utils'
|
import { Utils } from '../utils/utils'
|
||||||
|
|
||||||
export default new OptionsSync({
|
/**
|
||||||
|
* Saves all options in sync storage.
|
||||||
|
*/
|
||||||
|
export const syncStorage = new OptionsSync({
|
||||||
defaults: {
|
defaults: {
|
||||||
rules: '{}',
|
|
||||||
rulesHash: '',
|
rulesHash: '',
|
||||||
badgedStatus: true,
|
badgedStatus: true,
|
||||||
enabled: true,
|
enabled: true,
|
||||||
@@ -36,7 +38,6 @@ export default new OptionsSync({
|
|||||||
cleanedCount: 0,
|
cleanedCount: 0,
|
||||||
rulesStatus: 'error',
|
rulesStatus: 'error',
|
||||||
loggingEnabled: false,
|
loggingEnabled: false,
|
||||||
log: '',
|
|
||||||
logLimit: 100,
|
logLimit: 100,
|
||||||
statisticsEnabled: true,
|
statisticsEnabled: true,
|
||||||
badgedColor: '#ffa500',
|
badgedColor: '#ffa500',
|
||||||
@@ -54,23 +55,24 @@ export default new OptionsSync({
|
|||||||
pingRequestTypes: '',
|
pingRequestTypes: '',
|
||||||
},
|
},
|
||||||
migrations: [
|
migrations: [
|
||||||
(savedOptions, defaults) => {
|
// Secound param is normaly "defaults", but currently not used
|
||||||
|
(savedOptions, _) => {
|
||||||
if (Utils.getBrowser() === 'Firefox') {
|
if (Utils.getBrowser() === 'Firefox') {
|
||||||
if (savedOptions.requestTypes === '') {
|
if (savedOptions.requestTypes === '') {
|
||||||
savedOptions.requestTypes = 'font,image,imageset,main_frame,media,object,object_subrequest,other,script,stylesheet,sub_frame,websocket,xml_dtd,xmlhttprequest,xslt'
|
savedOptions.requestTypes = Object.values(FirefoxRequestTypes).join(',')
|
||||||
}
|
}
|
||||||
|
|
||||||
if (savedOptions.pingRequestTypes === '') {
|
if (savedOptions.pingRequestTypes === '') {
|
||||||
savedOptions.pingRequestTypes = 'ping,beacon'
|
savedOptions.pingRequestTypes = Object.values(FirefoxPingRequestTypes).join(',')
|
||||||
}
|
}
|
||||||
} else {
|
}
|
||||||
|
else {
|
||||||
if (savedOptions.requestTypes === '') {
|
if (savedOptions.requestTypes === '') {
|
||||||
savedOptions.requestTypes = 'main_frame,sub_frame,stylesheet,script,image,font,object,xmlhttprequest,ping,csp_report,media,websocket,other'
|
savedOptions.requestTypes = Object.values(ChromeRequestTypes).join(',')
|
||||||
}
|
}
|
||||||
|
|
||||||
if (savedOptions.pingRequestTypes === '') {
|
if (savedOptions.pingRequestTypes === '') {
|
||||||
savedOptions.pingRequestTypes = 'ping'
|
savedOptions.pingRequestTypes = Object.values(ChromePingRequestTypes).join(',')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -80,3 +82,70 @@ export default new OptionsSync({
|
|||||||
logging: true,
|
logging: true,
|
||||||
storageName: 'clearurlsData',
|
storageName: 'clearurlsData',
|
||||||
})
|
})
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Saves rules and logs on local storage.
|
||||||
|
*/
|
||||||
|
/* export class LocalStorage {
|
||||||
|
private _rules: {}
|
||||||
|
private _log: Log
|
||||||
|
|
||||||
|
public constructor() {
|
||||||
|
this._rules = {}
|
||||||
|
this._log = new Log()
|
||||||
|
}
|
||||||
|
} */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Models the supported request types of Firefox.
|
||||||
|
*/
|
||||||
|
export enum FirefoxRequestTypes {
|
||||||
|
FONT = 'font',
|
||||||
|
IMAGE = 'image',
|
||||||
|
IMAGESET = 'imageset',
|
||||||
|
MAIN_FRAME = 'main_frame',
|
||||||
|
MEDIA = 'media',
|
||||||
|
OBJECT = 'object',
|
||||||
|
OBJECT_SUBREQUEST = 'object_subrequest',
|
||||||
|
OTHER = 'other',
|
||||||
|
SCRIPT = 'script',
|
||||||
|
STYLESHEET = 'stylesheet',
|
||||||
|
SUB_FRAME = 'sub_frame',
|
||||||
|
WEBSOCKET = 'websocket',
|
||||||
|
XML_DTD = 'xml_dtd',
|
||||||
|
XMLHTTPREQUEST = 'xmlhttprequest',
|
||||||
|
XSLT = 'xslt',
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Models the supported ping request types of Firefox.
|
||||||
|
*/
|
||||||
|
export enum FirefoxPingRequestTypes {
|
||||||
|
PING = 'ping',
|
||||||
|
BEACON = 'beacon',
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Models the supported request types of Chrome.
|
||||||
|
*/
|
||||||
|
export enum ChromeRequestTypes {
|
||||||
|
MAIN_FRAME = 'main_frame',
|
||||||
|
SUB_FRAME = 'sub_frame',
|
||||||
|
STYLESHEET = 'stylesheet',
|
||||||
|
SCRIPT = 'script',
|
||||||
|
IMAGE = 'image',
|
||||||
|
FONT = 'font',
|
||||||
|
OBJECT = 'object',
|
||||||
|
XMLHTTPREQUEST = 'xmlhttprequest',
|
||||||
|
CSP_REPORT = 'csp_report',
|
||||||
|
MEDIA = 'media',
|
||||||
|
WEBSOCKET = 'websocket',
|
||||||
|
OTHER = 'other',
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Models the supported ping request types of Chrome.
|
||||||
|
*/
|
||||||
|
export enum ChromePingRequestTypes {
|
||||||
|
PING = 'ping',
|
||||||
|
}
|
||||||
63
source/utils/CircularBuffer.ts
Normal file
63
source/utils/CircularBuffer.ts
Normal file
@@ -0,0 +1,63 @@
|
|||||||
|
export default class CircularBuffer<T> {
|
||||||
|
private buffer: T[]
|
||||||
|
public readonly capacity: number
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new circular buffer, that removes the oldes elements if the capacity is reached.
|
||||||
|
*
|
||||||
|
* @param capacity - the capacity of this queue
|
||||||
|
* @throws error if the capacity is <= 0
|
||||||
|
*/
|
||||||
|
public constructor(capacity: number) {
|
||||||
|
if (capacity <= 0) {
|
||||||
|
throw new Error('The capacity must be > 0')
|
||||||
|
}
|
||||||
|
|
||||||
|
this.capacity = capacity
|
||||||
|
this.buffer = []
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns true if the buffer is empty.
|
||||||
|
*
|
||||||
|
* @returns true if the buffer is empty otherwise false
|
||||||
|
*/
|
||||||
|
public isEmpty() : boolean {
|
||||||
|
return this.buffer.length === 0
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns true if the buffer is full.
|
||||||
|
*
|
||||||
|
* @returns true if the buffer is full otherwise false
|
||||||
|
*/
|
||||||
|
public isFull() : boolean {
|
||||||
|
return this.buffer.length === this.capacity
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Enqueues an item.
|
||||||
|
*
|
||||||
|
* @param item - item that should be enqueued
|
||||||
|
*/
|
||||||
|
public enqueue(item: T) : void {
|
||||||
|
if (this.isFull()) {
|
||||||
|
this.buffer.shift()
|
||||||
|
}
|
||||||
|
|
||||||
|
this.buffer.push(item)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the buffer in a fresh array.
|
||||||
|
*
|
||||||
|
* @returns the buffer
|
||||||
|
*/
|
||||||
|
public toArray() : T[] {
|
||||||
|
return [...this.buffer]
|
||||||
|
}
|
||||||
|
|
||||||
|
get size() : number {
|
||||||
|
return this.buffer.length
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -22,6 +22,8 @@
|
|||||||
*/
|
*/
|
||||||
import { browser } from 'webextension-polyfill-ts'
|
import { browser } from 'webextension-polyfill-ts'
|
||||||
|
|
||||||
|
declare const InstallTrigger: any
|
||||||
|
|
||||||
export class Utils {
|
export class Utils {
|
||||||
// Needed by the sha256 method
|
// Needed by the sha256 method
|
||||||
static enc = new TextEncoder()
|
static enc = new TextEncoder()
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ module.exports = {
|
|||||||
stats: 'errors-only',
|
stats: 'errors-only',
|
||||||
entry: {
|
entry: {
|
||||||
// target_path/target_file_name: full_source_path
|
// target_path/target_file_name: full_source_path
|
||||||
'background/main': './source/background/main.ts',
|
'background/main': './source/background/main.ts'
|
||||||
},
|
},
|
||||||
output: {
|
output: {
|
||||||
path: path.join(__dirname, 'distribution'),
|
path: path.join(__dirname, 'distribution'),
|
||||||
@@ -62,6 +62,25 @@ module.exports = {
|
|||||||
ignore: ['**/*.js', '**/*.ts', '**/*.tsx', '**/*.scss'],
|
ignore: ['**/*.js', '**/*.ts', '**/*.tsx', '**/*.scss'],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
from: '_locales/**/*'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
from: 'img/*'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
from: 'fonts/*'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
from: 'css/*'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
from: 'html/*'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
from: 'scripts/*',
|
||||||
|
context: 'source'
|
||||||
|
}
|
||||||
],
|
],
|
||||||
}),
|
}),
|
||||||
],
|
],
|
||||||
|
|||||||
Reference in New Issue
Block a user