-
-
Notifications
You must be signed in to change notification settings - Fork 1.4k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add chainFilters plugin #733
Closed
Closed
Changes from all commits
Commits
Show all changes
17 commits
Select commit
Hold shift + click to select a range
3d19de1
Add + update files.
strarsis d357346
Fix Map iteration for node v4.
strarsis 997309c
Simplify class name loop.
strarsis 1e6859f
Add chainFilters to .svgo.yml (before minifyStyles).
strarsis a280fa0
Add further test for chainFilters.
strarsis 05a6b45
Fix <style> creation + insertion.
strarsis 73b6e44
Add fix for the case that filter is not used by element.
strarsis 46199e6
Add test for element using non-existant filter.
strarsis ec33f1d
Improve element-filter mapping.
strarsis b0687f2
Rename class list module file.
strarsis 6964351
Fix test.
strarsis 4d4144a
Merge remote-tracking branch 'remotes/origin/master' into chain-filters
strarsis 8d80965
Fix jshint errors.
strarsis a8a63f5
Add explicit camelcase dependency.
strarsis 50e94ec
Merge branch 'master' into chain-filters
strarsis 226c328
Merge branch 'master' into chain-filters
strarsis d5d0e1a
Merge branch 'master' into chain-filters
strarsis File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,39 @@ | ||
'use strict'; | ||
|
||
var CssRx = require('css-url-regex'), | ||
rxId = /^#(.*)$/; // regular expression for matching an ID + extracing its name | ||
|
||
// Checks if attribute is empty | ||
var attrNotEmpty = function(attr) { | ||
return (attr && attr.value && attr.value.length > 0); | ||
}; | ||
|
||
// Escapes a string for being used as ID | ||
var escapeIdentifierName = function(str) { | ||
return str.replace(/[\. ]/g, '_'); | ||
}; | ||
|
||
// Matches an #ID value, captures the ID name | ||
var matchId = function(urlVal) { | ||
var idUrlMatches = urlVal.match(rxId); | ||
if (idUrlMatches === null) { | ||
return false; | ||
} | ||
return idUrlMatches[1]; | ||
}; | ||
|
||
// Matches an url(...) value, captures the URL | ||
var matchUrl = function(val) { | ||
var cssRx = new CssRx(); // workaround for /~https://github.com/cssstats/css-url-regex/issues/4 | ||
var urlMatches = cssRx.exec(val); | ||
if (urlMatches === null) { | ||
return false; | ||
} | ||
return urlMatches[1]; | ||
}; | ||
|
||
module.exports.rxId = rxId; | ||
module.exports.attrNotEmpty = attrNotEmpty; | ||
module.exports.escapeIdentifierName = escapeIdentifierName; | ||
module.exports.matchId = matchId; | ||
module.exports.matchUrl = matchUrl; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
'use strict'; | ||
|
||
var walk = require('tree-walk'); | ||
|
||
var domWalker = walk(function(node) { | ||
return node.content; | ||
}); | ||
|
||
module.exports = domWalker; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,197 @@ | ||
'use strict'; | ||
|
||
exports.type = 'full'; | ||
|
||
exports.active = false; | ||
|
||
exports.params = {}; | ||
|
||
exports.description = 'chain filter elements using CSS filter(...)'; | ||
|
||
|
||
var csstree = require('css-tree'), | ||
camelCase = require('camelcase'), | ||
domWalker = require('../lib/dom-walker'), | ||
domTools = require('../lib/dom-tools'); | ||
|
||
|
||
/** | ||
* Chain filter elements using CSS filter(...) (Workaround for some browsers like Firefox). | ||
* | ||
* @param {Object} document document element | ||
* @param {Object} opts plugin params | ||
* | ||
* @author strarsis <strarsis@gmail.com> | ||
*/ | ||
exports.fn = function(document) { | ||
|
||
// Collect <filter> elements and elements that use a <filter> element by ID | ||
var filterIds = new Set(), | ||
elementsUsingFilterById = []; | ||
domWalker.preorder(document, function(node) { | ||
|
||
// <filter> elements | ||
if (node.elem === 'filter') { | ||
if(!node.hasAttr('id')) { | ||
return; // skip if no ID attribute | ||
} | ||
var filterElemId = node.attr('id').value; | ||
|
||
if (filterIds.has(filterElemId)) { | ||
console.warn('Warning: \'#' + filterElemId + '\' is used multiple times for <filter> elements.'); | ||
return; // skip if ID already added | ||
} | ||
filterIds.add(filterElemId); | ||
|
||
return; // done with <filter> element | ||
} | ||
|
||
|
||
// elements that use a filter (filter attribute) or | ||
// a filter style | ||
if (!node.hasAttr('filter') && | ||
!(node.style && node.style.getPropertyValue('filter') !== null)) { | ||
return; // skip if no filter attribute | ||
} | ||
|
||
var useFilterVal; | ||
if(node.style.getPropertyValue('filter') !== null) { | ||
// style filter | ||
useFilterVal = node.style.getPropertyValue('filter'); | ||
} else { | ||
// attribute filter | ||
useFilterVal = node.attr('filter').value; | ||
} | ||
|
||
|
||
if (useFilterVal.length === 0) { | ||
return; // skip if empty filter attribute | ||
} | ||
|
||
var useFilterUrl = domTools.matchUrl(useFilterVal); | ||
if (!useFilterUrl) { | ||
return; // skip if no url(...) used | ||
} | ||
|
||
var useFilterId = domTools.matchId(useFilterUrl); | ||
if (!useFilterId) { | ||
return; // skip if no #id in url(...) used | ||
} | ||
|
||
elementsUsingFilterById.push({ | ||
filterId: useFilterId, | ||
node: node | ||
}); | ||
}); | ||
if(filterIds.length === 0) { | ||
return document; // No <filter> elements, skip this SVG. | ||
} | ||
|
||
|
||
// elements that use a <filter> element that actually exists | ||
var elementsUsingExistingFilterById = elementsUsingFilterById.filter(function(element) { | ||
var filterExists = filterIds.has(element.filterId); | ||
if (!filterExists) { | ||
console.warn('Warning: Element uses non-existing <filter> \'#' + element.filterId + '\', element skipped.'); | ||
} | ||
return filterExists; | ||
}); | ||
|
||
if(elementsUsingExistingFilterById.length === 0) { | ||
return document; // No existing <filter> elements are used, skip this SVG. | ||
} | ||
|
||
|
||
// Generate CSS class list + styles for the <filter> elements | ||
var usedFilterIds = new Set( | ||
elementsUsingExistingFilterById.map(function(element) { | ||
return element.filterId; | ||
}) | ||
); | ||
|
||
var filterClasses = new Map(), | ||
filterClassesStyles = csstree.fromPlainObject({type:'StyleSheet', children: []}); | ||
for (var filterId of usedFilterIds) { | ||
var filterClassName = camelCase('filter ' + filterId); | ||
filterClasses.set(filterId, filterClassName); | ||
|
||
var filterClassRuleObj = { | ||
type: 'Rule', | ||
prelude: { | ||
type: 'SelectorList', | ||
children: [ | ||
{ | ||
type: 'Selector', | ||
children: [ | ||
{ | ||
type: 'ClassSelector', | ||
name: filterClassName | ||
} | ||
] | ||
} | ||
] | ||
}, | ||
block: { | ||
type: 'Block', | ||
children: [ | ||
{ | ||
type: 'Declaration', | ||
important: false, | ||
property: 'filter', | ||
value: { | ||
type: 'Value', | ||
children: [ | ||
{ | ||
type: 'String', | ||
value: '"url(' + '#' + filterId + ')"' | ||
} | ||
] | ||
} | ||
} | ||
] | ||
} | ||
}; | ||
filterClassesStyles.children.appendData(csstree.fromPlainObject(filterClassRuleObj)); | ||
} | ||
|
||
|
||
if(!filterClassesStyles.children.isEmpty()) { | ||
// Add new style element with these filter classes styles | ||
var svgElem = document.querySelector('svg'); | ||
|
||
// New <style> | ||
var styleFilterClasses = new document.constructor({ | ||
elem: 'style', | ||
prefix: '', | ||
local: 'style', | ||
content: [] // has to be added in extra step | ||
}, svgElem); | ||
|
||
// New text content for <style> | ||
var styleFilterClassesText = new document.constructor({ | ||
text: csstree.translate(filterClassesStyles) | ||
}, styleFilterClasses); | ||
// Add text content to <style> | ||
styleFilterClasses.spliceContent(0, 0, styleFilterClassesText); | ||
|
||
// Add new <style> to <svg> | ||
svgElem.spliceContent(0, 0, styleFilterClasses); | ||
} | ||
|
||
|
||
for (var element of elementsUsingExistingFilterById) { | ||
// Assign filter-using classes to corresponding filter-using elements | ||
element.node.class.add(filterClasses.get(element.filterId)); | ||
|
||
// Remove the then redundant filter attribute + styles | ||
element.node.removeAttr('filter'); | ||
element.node.style.removeProperty('filter'); | ||
if(element.node.style.item(0) === '') { | ||
// clean up now empty style attributes | ||
element.node.removeAttr('style'); | ||
} | ||
} | ||
|
||
|
||
return document; | ||
}; |
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I added traverse utility in lib/xast.js.