зеркало из https://github.com/mozilla/gecko-dev.git
Bug 1213767 - Rule-view class toggle panel. r=jdescottes
MozReview-Commit-ID: 2roKEm6Jr26
This commit is contained in:
Родитель
7300a549b0
Коммит
f7a1e4b454
|
@ -98,13 +98,15 @@
|
|||
<div id="ruleview-command-toolbar">
|
||||
<button id="ruleview-add-rule-button" data-localization="title=inspector.addRule.tooltip" class="devtools-button"></button>
|
||||
<button id="pseudo-class-panel-toggle" data-localization="title=inspector.togglePseudo.tooltip" class="devtools-button"></button>
|
||||
<button id="class-panel-toggle" data-localization="title=inspector.classPanel.toggleClass.tooltip" class="devtools-button"></button>
|
||||
</div>
|
||||
</div>
|
||||
<div id="pseudo-class-panel" hidden="true">
|
||||
<div id="pseudo-class-panel" class="ruleview-reveal-panel" hidden="true">
|
||||
<label><input id="pseudo-hover-toggle" type="checkbox" value=":hover" tabindex="-1" />:hover</label>
|
||||
<label><input id="pseudo-active-toggle" type="checkbox" value=":active" tabindex="-1" />:active</label>
|
||||
<label><input id="pseudo-focus-toggle" type="checkbox" value=":focus" tabindex="-1" />:focus</label>
|
||||
</div>
|
||||
</div>
|
||||
<div id="ruleview-class-panel" class="ruleview-reveal-panel" hidden="true"></div>
|
||||
</div>
|
||||
|
||||
<div id="ruleview-container" class="ruleview">
|
||||
|
|
|
@ -17,6 +17,7 @@ const {PrefObserver} = require("devtools/client/shared/prefs");
|
|||
const ElementStyle = require("devtools/client/inspector/rules/models/element-style");
|
||||
const Rule = require("devtools/client/inspector/rules/models/rule");
|
||||
const RuleEditor = require("devtools/client/inspector/rules/views/rule-editor");
|
||||
const ClassListPreviewer = require("devtools/client/inspector/rules/views/class-list-previewer");
|
||||
const {gDevTools} = require("devtools/client/framework/devtools");
|
||||
const {getCssProperties} = require("devtools/shared/fronts/css-properties");
|
||||
const {
|
||||
|
@ -120,6 +121,7 @@ function CssRuleView(inspector, document, store, pageStyle) {
|
|||
this._onClearSearch = this._onClearSearch.bind(this);
|
||||
this._onTogglePseudoClassPanel = this._onTogglePseudoClassPanel.bind(this);
|
||||
this._onTogglePseudoClass = this._onTogglePseudoClass.bind(this);
|
||||
this._onToggleClassPanel = this._onToggleClassPanel.bind(this);
|
||||
|
||||
let doc = this.styleDocument;
|
||||
this.element = doc.getElementById("ruleview-container-focusable");
|
||||
|
@ -128,6 +130,8 @@ function CssRuleView(inspector, document, store, pageStyle) {
|
|||
this.searchClearButton = doc.getElementById("ruleview-searchinput-clear");
|
||||
this.pseudoClassPanel = doc.getElementById("pseudo-class-panel");
|
||||
this.pseudoClassToggle = doc.getElementById("pseudo-class-panel-toggle");
|
||||
this.classPanel = doc.getElementById("ruleview-class-panel");
|
||||
this.classToggle = doc.getElementById("class-panel-toggle");
|
||||
this.hoverCheckbox = doc.getElementById("pseudo-hover-toggle");
|
||||
this.activeCheckbox = doc.getElementById("pseudo-active-toggle");
|
||||
this.focusCheckbox = doc.getElementById("pseudo-focus-toggle");
|
||||
|
@ -146,8 +150,8 @@ function CssRuleView(inspector, document, store, pageStyle) {
|
|||
this.searchField.addEventListener("input", this._onFilterStyles);
|
||||
this.searchField.addEventListener("contextmenu", this.inspector.onTextBoxContextMenu);
|
||||
this.searchClearButton.addEventListener("click", this._onClearSearch);
|
||||
this.pseudoClassToggle.addEventListener("click",
|
||||
this._onTogglePseudoClassPanel);
|
||||
this.pseudoClassToggle.addEventListener("click", this._onTogglePseudoClassPanel);
|
||||
this.classToggle.addEventListener("click", this._onToggleClassPanel);
|
||||
this.hoverCheckbox.addEventListener("click", this._onTogglePseudoClass);
|
||||
this.activeCheckbox.addEventListener("click", this._onTogglePseudoClass);
|
||||
this.focusCheckbox.addEventListener("click", this._onTogglePseudoClass);
|
||||
|
@ -181,6 +185,8 @@ function CssRuleView(inspector, document, store, pageStyle) {
|
|||
|
||||
this.highlighters.addToView(this);
|
||||
|
||||
this.classListPreviewer = new ClassListPreviewer(this.inspector, this.classPanel);
|
||||
|
||||
EventEmitter.decorate(this);
|
||||
}
|
||||
|
||||
|
@ -673,6 +679,7 @@ CssRuleView.prototype = {
|
|||
|
||||
this.tooltips.destroy();
|
||||
this.highlighters.removeFromView(this);
|
||||
this.classListPreviewer.destroy();
|
||||
|
||||
// Remove bound listeners
|
||||
this.shortcuts.destroy();
|
||||
|
@ -683,8 +690,8 @@ CssRuleView.prototype = {
|
|||
this.searchField.removeEventListener("contextmenu",
|
||||
this.inspector.onTextBoxContextMenu);
|
||||
this.searchClearButton.removeEventListener("click", this._onClearSearch);
|
||||
this.pseudoClassToggle.removeEventListener("click",
|
||||
this._onTogglePseudoClassPanel);
|
||||
this.pseudoClassToggle.removeEventListener("click", this._onTogglePseudoClassPanel);
|
||||
this.classToggle.removeEventListener("click", this._onToggleClassPanel);
|
||||
this.hoverCheckbox.removeEventListener("click", this._onTogglePseudoClass);
|
||||
this.activeCheckbox.removeEventListener("click", this._onTogglePseudoClass);
|
||||
this.focusCheckbox.removeEventListener("click", this._onTogglePseudoClass);
|
||||
|
@ -693,6 +700,8 @@ CssRuleView.prototype = {
|
|||
this.searchClearButton = null;
|
||||
this.pseudoClassPanel = null;
|
||||
this.pseudoClassToggle = null;
|
||||
this.classPanel = null;
|
||||
this.classToggle = null;
|
||||
this.hoverCheckbox = null;
|
||||
this.activeCheckbox = null;
|
||||
this.focusCheckbox = null;
|
||||
|
@ -1372,18 +1381,30 @@ CssRuleView.prototype = {
|
|||
*/
|
||||
_onTogglePseudoClassPanel: function () {
|
||||
if (this.pseudoClassPanel.hidden) {
|
||||
this.pseudoClassToggle.classList.add("checked");
|
||||
this.hoverCheckbox.setAttribute("tabindex", "0");
|
||||
this.activeCheckbox.setAttribute("tabindex", "0");
|
||||
this.focusCheckbox.setAttribute("tabindex", "0");
|
||||
this.showPseudoClassPanel();
|
||||
} else {
|
||||
this.pseudoClassToggle.classList.remove("checked");
|
||||
this.hoverCheckbox.setAttribute("tabindex", "-1");
|
||||
this.activeCheckbox.setAttribute("tabindex", "-1");
|
||||
this.focusCheckbox.setAttribute("tabindex", "-1");
|
||||
this.hidePseudoClassPanel();
|
||||
}
|
||||
},
|
||||
|
||||
this.pseudoClassPanel.hidden = !this.pseudoClassPanel.hidden;
|
||||
showPseudoClassPanel: function () {
|
||||
this.hideClassPanel();
|
||||
|
||||
this.pseudoClassToggle.classList.add("checked");
|
||||
this.hoverCheckbox.setAttribute("tabindex", "0");
|
||||
this.activeCheckbox.setAttribute("tabindex", "0");
|
||||
this.focusCheckbox.setAttribute("tabindex", "0");
|
||||
|
||||
this.pseudoClassPanel.hidden = false;
|
||||
},
|
||||
|
||||
hidePseudoClassPanel: function () {
|
||||
this.pseudoClassToggle.classList.remove("checked");
|
||||
this.hoverCheckbox.setAttribute("tabindex", "-1");
|
||||
this.activeCheckbox.setAttribute("tabindex", "-1");
|
||||
this.focusCheckbox.setAttribute("tabindex", "-1");
|
||||
|
||||
this.pseudoClassPanel.hidden = true;
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -1395,6 +1416,32 @@ CssRuleView.prototype = {
|
|||
this.inspector.togglePseudoClass(target.value);
|
||||
},
|
||||
|
||||
/**
|
||||
* Called when the class panel button is clicked and toggles the display of the class
|
||||
* panel.
|
||||
*/
|
||||
_onToggleClassPanel: function () {
|
||||
if (this.classPanel.hidden) {
|
||||
this.showClassPanel();
|
||||
} else {
|
||||
this.hideClassPanel();
|
||||
}
|
||||
},
|
||||
|
||||
showClassPanel: function () {
|
||||
this.hidePseudoClassPanel();
|
||||
|
||||
this.classToggle.classList.add("checked");
|
||||
this.classPanel.hidden = false;
|
||||
|
||||
this.classListPreviewer.focusAddClassField();
|
||||
},
|
||||
|
||||
hideClassPanel: function () {
|
||||
this.classToggle.classList.remove("checked");
|
||||
this.classPanel.hidden = true;
|
||||
},
|
||||
|
||||
/**
|
||||
* Handle the keypress event in the rule view.
|
||||
*/
|
||||
|
|
|
@ -61,6 +61,13 @@ support-files =
|
|||
[browser_rules_authored_color.js]
|
||||
[browser_rules_authored_override.js]
|
||||
[browser_rules_blob_stylesheet.js]
|
||||
[browser_rules_class_panel_add.js]
|
||||
[browser_rules_class_panel_content.js]
|
||||
[browser_rules_class_panel_edit.js]
|
||||
[browser_rules_class_panel_invalid_nodes.js]
|
||||
[browser_rules_class_panel_mutation.js]
|
||||
[browser_rules_class_panel_state_preserved.js]
|
||||
[browser_rules_class_panel_toggle.js]
|
||||
[browser_rules_colorpicker-and-image-tooltip_01.js]
|
||||
[browser_rules_colorpicker-and-image-tooltip_02.js]
|
||||
[browser_rules_colorpicker-appears-on-swatch-click.js]
|
||||
|
|
|
@ -0,0 +1,91 @@
|
|||
/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
"use strict";
|
||||
|
||||
// Test that classes can be added in the class panel
|
||||
|
||||
// This array contains the list of test cases. Each test case contains these properties:
|
||||
// - {String} textEntered The text to be entered in the field
|
||||
// - {Boolean} expectNoMutation Set to true if we shouldn't wait for a DOM mutation
|
||||
// - {Array} expectedClasses The expected list of classes to be applied to the DOM and to
|
||||
// be found in the class panel
|
||||
const TEST_ARRAY = [{
|
||||
textEntered: "",
|
||||
expectNoMutation: true,
|
||||
expectedClasses: []
|
||||
}, {
|
||||
textEntered: "class",
|
||||
expectedClasses: ["class"]
|
||||
}, {
|
||||
textEntered: "class",
|
||||
expectNoMutation: true,
|
||||
expectedClasses: ["class"]
|
||||
}, {
|
||||
textEntered: "a a a a a a a a a a",
|
||||
expectedClasses: ["class", "a"]
|
||||
}, {
|
||||
textEntered: "class2 class3",
|
||||
expectedClasses: ["class", "a", "class2", "class3"]
|
||||
}, {
|
||||
textEntered: " ",
|
||||
expectNoMutation: true,
|
||||
expectedClasses: ["class", "a", "class2", "class3"]
|
||||
}, {
|
||||
textEntered: " class4",
|
||||
expectedClasses: ["class", "a", "class2", "class3", "class4"]
|
||||
}, {
|
||||
textEntered: " \t class5 \t \t\t ",
|
||||
expectedClasses: ["class", "a", "class2", "class3", "class4", "class5"]
|
||||
}];
|
||||
|
||||
add_task(function* () {
|
||||
yield addTab("data:text/html;charset=utf-8,");
|
||||
let {testActor, inspector, view} = yield openRuleView();
|
||||
|
||||
info("Open the class panel");
|
||||
view.showClassPanel();
|
||||
|
||||
const textField = inspector.panelDoc.querySelector("#ruleview-class-panel .add-class");
|
||||
ok(textField, "The input field exists in the class panel");
|
||||
|
||||
textField.focus();
|
||||
|
||||
let onMutation;
|
||||
for (let {textEntered, expectNoMutation, expectedClasses} of TEST_ARRAY) {
|
||||
if (!expectNoMutation) {
|
||||
onMutation = inspector.once("markupmutation");
|
||||
}
|
||||
|
||||
info(`Enter the test string in the field: ${textEntered}`);
|
||||
for (let key of textEntered.split("")) {
|
||||
EventUtils.synthesizeKey(key, {}, view.styleWindow);
|
||||
}
|
||||
|
||||
info("Submit the change and wait for the textfield to become empty");
|
||||
let onEmpty = waitForFieldToBeEmpty(textField);
|
||||
EventUtils.synthesizeKey("VK_RETURN", {}, view.styleWindow);
|
||||
|
||||
if (!expectNoMutation) {
|
||||
info("Wait for the DOM to change");
|
||||
yield onMutation;
|
||||
}
|
||||
|
||||
yield onEmpty;
|
||||
|
||||
info("Check the state of the DOM node");
|
||||
let className = yield testActor.getAttribute("body", "class");
|
||||
let expectedClassName = expectedClasses.length ? expectedClasses.join(" ") : null;
|
||||
is(className, expectedClassName, "The DOM node has the right className");
|
||||
|
||||
info("Check the content of the class panel");
|
||||
checkClassPanelContent(view, expectedClasses.map(name => {
|
||||
return {name, state: true};
|
||||
}));
|
||||
}
|
||||
});
|
||||
|
||||
function waitForFieldToBeEmpty(textField) {
|
||||
return waitForSuccess(() => !textField.value);
|
||||
}
|
|
@ -0,0 +1,65 @@
|
|||
/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
"use strict";
|
||||
|
||||
// Test that class panel shows the right content when selecting various nodes.
|
||||
|
||||
// This array contains the list of test cases. Each test case contains these properties:
|
||||
// - {String} inputClassName The className on a node
|
||||
// - {Array} expectedClasses The expected list of classes in the class panel
|
||||
const TEST_ARRAY = [{
|
||||
inputClassName: "",
|
||||
expectedClasses: []
|
||||
}, {
|
||||
inputClassName: " a a a a a a a a a",
|
||||
expectedClasses: ["a"]
|
||||
}, {
|
||||
inputClassName: "c1 c2 c3 c4 c5",
|
||||
expectedClasses: ["c1", "c2", "c3", "c4", "c5"]
|
||||
}, {
|
||||
inputClassName: "a a b b c c a a b b c c",
|
||||
expectedClasses: ["a", "b", "c"]
|
||||
}, {
|
||||
inputClassName: "ajdhfkasjhdkjashdkjghaskdgkauhkbdhvliashdlghaslidghasldgliashdglhasli",
|
||||
expectedClasses: [
|
||||
"ajdhfkasjhdkjashdkjghaskdgkauhkbdhvliashdlghaslidghasldgliashdglhasli"
|
||||
]
|
||||
}, {
|
||||
inputClassName: "c0 c1 c2 c3 c4 c5 c6 c7 c8 c9 " +
|
||||
"c10 c11 c12 c13 c14 c15 c16 c17 c18 c19 " +
|
||||
"c20 c21 c22 c23 c24 c25 c26 c27 c28 c29 " +
|
||||
"c30 c31 c32 c33 c34 c35 c36 c37 c38 c39 " +
|
||||
"c40 c41 c42 c43 c44 c45 c46 c47 c48 c49",
|
||||
expectedClasses: ["c0", "c1", "c2", "c3", "c4", "c5", "c6", "c7", "c8", "c9",
|
||||
"c10", "c11", "c12", "c13", "c14", "c15", "c16", "c17", "c18", "c19",
|
||||
"c20", "c21", "c22", "c23", "c24", "c25", "c26", "c27", "c28", "c29",
|
||||
"c30", "c31", "c32", "c33", "c34", "c35", "c36", "c37", "c38", "c39",
|
||||
"c40", "c41", "c42", "c43", "c44", "c45", "c46", "c47", "c48", "c49"]
|
||||
}, {
|
||||
inputClassName: " \n \n class1 \t class2 \t\tclass3\t",
|
||||
expectedClasses: ["class1", "class2", "class3"]
|
||||
}];
|
||||
|
||||
add_task(function* () {
|
||||
yield addTab("data:text/html;charset=utf-8,<div>");
|
||||
let {testActor, inspector, view} = yield openRuleView();
|
||||
|
||||
yield selectNode("div", inspector);
|
||||
|
||||
info("Open the class panel");
|
||||
view.showClassPanel();
|
||||
|
||||
for (let {inputClassName, expectedClasses} of TEST_ARRAY) {
|
||||
info(`Apply the '${inputClassName}' className to the node`);
|
||||
const onMutation = inspector.once("markupmutation");
|
||||
yield testActor.setAttribute("div", "class", inputClassName);
|
||||
yield onMutation;
|
||||
|
||||
info("Check the content of the class panel");
|
||||
checkClassPanelContent(view, expectedClasses.map(name => {
|
||||
return {name, state: true};
|
||||
}));
|
||||
}
|
||||
});
|
|
@ -0,0 +1,51 @@
|
|||
/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
"use strict";
|
||||
|
||||
// Test that classes can be toggled in the class panel
|
||||
|
||||
add_task(function* () {
|
||||
yield addTab("data:text/html;charset=utf-8,<body class='class1 class2'>");
|
||||
let {view, testActor} = yield openRuleView();
|
||||
|
||||
info("Open the class panel");
|
||||
view.showClassPanel();
|
||||
|
||||
info("Click on class1 and check that the checkbox is unchecked and the DOM is updated");
|
||||
yield toggleClassPanelCheckBox(view, "class1");
|
||||
checkClassPanelContent(view, [
|
||||
{name: "class1", state: false},
|
||||
{name: "class2", state: true}
|
||||
]);
|
||||
let newClassName = yield testActor.getAttribute("body", "class");
|
||||
is(newClassName, "class2", "The class attribute has been updated in the DOM");
|
||||
|
||||
info("Click on class2 and check the same thing");
|
||||
yield toggleClassPanelCheckBox(view, "class2");
|
||||
checkClassPanelContent(view, [
|
||||
{name: "class1", state: false},
|
||||
{name: "class2", state: false}
|
||||
]);
|
||||
newClassName = yield testActor.getAttribute("body", "class");
|
||||
is(newClassName, "", "The class attribute has been updated in the DOM");
|
||||
|
||||
info("Click on class2 and checks that the class is added again");
|
||||
yield toggleClassPanelCheckBox(view, "class2");
|
||||
checkClassPanelContent(view, [
|
||||
{name: "class1", state: false},
|
||||
{name: "class2", state: true}
|
||||
]);
|
||||
newClassName = yield testActor.getAttribute("body", "class");
|
||||
is(newClassName, "class2", "The class attribute has been updated in the DOM");
|
||||
|
||||
info("And finally, click on class1 again and checks it is added again");
|
||||
yield toggleClassPanelCheckBox(view, "class1");
|
||||
checkClassPanelContent(view, [
|
||||
{name: "class1", state: true},
|
||||
{name: "class2", state: true}
|
||||
]);
|
||||
newClassName = yield testActor.getAttribute("body", "class");
|
||||
is(newClassName, "class1 class2", "The class attribute has been updated in the DOM");
|
||||
});
|
|
@ -0,0 +1,50 @@
|
|||
/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
"use strict";
|
||||
|
||||
// Test the class panel shows a message when invalid nodes are selected.
|
||||
// text nodes, pseudo-elements, DOCTYPE, comment nodes.
|
||||
|
||||
add_task(function* () {
|
||||
yield addTab(`data:text/html;charset=utf-8,
|
||||
<body>
|
||||
<style>div::after {content: "test";}</style>
|
||||
<!-- comment -->
|
||||
Some text
|
||||
<div></div>
|
||||
</body>`);
|
||||
|
||||
info("Open the class panel");
|
||||
let {inspector, view} = yield openRuleView();
|
||||
view.showClassPanel();
|
||||
|
||||
info("Selecting the DOCTYPE node");
|
||||
let {nodes} = yield inspector.walker.children(inspector.walker.rootNode);
|
||||
yield selectNode(nodes[0], inspector);
|
||||
checkMessageIsDisplayed(view);
|
||||
|
||||
info("Selecting the comment node");
|
||||
let styleNode = yield getNodeFront("style", inspector);
|
||||
let commentNode = yield inspector.walker.nextSibling(styleNode);
|
||||
yield selectNode(commentNode, inspector);
|
||||
checkMessageIsDisplayed(view);
|
||||
|
||||
info("Selecting the text node");
|
||||
let textNode = yield inspector.walker.nextSibling(commentNode);
|
||||
yield selectNode(textNode, inspector);
|
||||
checkMessageIsDisplayed(view);
|
||||
|
||||
info("Selecting the ::after pseudo-element");
|
||||
let divNode = yield getNodeFront("div", inspector);
|
||||
let pseudoElement = (yield inspector.walker.children(divNode)).nodes[0];
|
||||
yield selectNode(pseudoElement, inspector);
|
||||
checkMessageIsDisplayed(view);
|
||||
});
|
||||
|
||||
function checkMessageIsDisplayed(view) {
|
||||
ok(view.classListPreviewer.classesEl.querySelector(".no-classes"),
|
||||
"The message is displayed");
|
||||
checkClassPanelContent(view, []);
|
||||
}
|
|
@ -0,0 +1,74 @@
|
|||
/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
"use strict";
|
||||
|
||||
// Test that class panel updates on markup mutations
|
||||
|
||||
add_task(function* () {
|
||||
yield addTab("data:text/html;charset=utf-8,<div class='c1 c2'>");
|
||||
let {inspector, view, testActor} = yield openRuleView();
|
||||
|
||||
yield selectNode("div", inspector);
|
||||
|
||||
info("Open the class panel");
|
||||
view.showClassPanel();
|
||||
|
||||
info("Trigger an unrelated mutation on the div (id attribute change)");
|
||||
let onMutation = view.inspector.once("markupmutation");
|
||||
yield testActor.setAttribute("div", "id", "test-id");
|
||||
yield onMutation;
|
||||
|
||||
info("Check that the panel still contains the right classes");
|
||||
checkClassPanelContent(view, [
|
||||
{name: "c1", state: true},
|
||||
{name: "c2", state: true}
|
||||
]);
|
||||
|
||||
info("Trigger a class mutation on a different, unknown, node");
|
||||
onMutation = view.inspector.once("markupmutation");
|
||||
yield testActor.setAttribute("body", "class", "test-class");
|
||||
yield onMutation;
|
||||
|
||||
info("Check that the panel still contains the right classes");
|
||||
checkClassPanelContent(view, [
|
||||
{name: "c1", state: true},
|
||||
{name: "c2", state: true}
|
||||
]);
|
||||
|
||||
info("Trigger a class mutation on the current node");
|
||||
onMutation = view.inspector.once("markupmutation");
|
||||
yield testActor.setAttribute("div", "class", "c3 c4");
|
||||
yield onMutation;
|
||||
|
||||
info("Check that the panel now contains the new classes");
|
||||
checkClassPanelContent(view, [
|
||||
{name: "c3", state: true},
|
||||
{name: "c4", state: true}
|
||||
]);
|
||||
|
||||
info("Change the state of one of the new classes");
|
||||
yield toggleClassPanelCheckBox(view, "c4");
|
||||
checkClassPanelContent(view, [
|
||||
{name: "c3", state: true},
|
||||
{name: "c4", state: false}
|
||||
]);
|
||||
|
||||
info("Select another node");
|
||||
yield selectNode("body", inspector);
|
||||
|
||||
info("Trigger a class mutation on the div");
|
||||
onMutation = view.inspector.once("markupmutation");
|
||||
yield testActor.setAttribute("div", "class", "c5 c6 c7");
|
||||
yield onMutation;
|
||||
|
||||
info("Go back to the previous node and check the content of the class panel." +
|
||||
"Even if hidden, it should have refreshed when we changed the DOM");
|
||||
yield selectNode("div", inspector);
|
||||
checkClassPanelContent(view, [
|
||||
{name: "c5", state: true},
|
||||
{name: "c6", state: true},
|
||||
{name: "c7", state: true}
|
||||
]);
|
||||
});
|
|
@ -0,0 +1,37 @@
|
|||
/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
"use strict";
|
||||
|
||||
// Test that class states are preserved when switching to other nodes
|
||||
|
||||
add_task(function* () {
|
||||
yield addTab("data:text/html;charset=utf-8,<body class='class1 class2 class3'><div>");
|
||||
let {inspector, view} = yield openRuleView();
|
||||
|
||||
info("Open the class panel");
|
||||
view.showClassPanel();
|
||||
|
||||
info("With the <body> selected, uncheck class2 and class3 in the panel");
|
||||
yield toggleClassPanelCheckBox(view, "class2");
|
||||
yield toggleClassPanelCheckBox(view, "class3");
|
||||
|
||||
info("Now select the <div> so the panel gets refreshed");
|
||||
yield selectNode("div", inspector);
|
||||
is(view.classPanel.querySelectorAll("[type=checkbox]").length, 0,
|
||||
"The panel content doesn't contain any checkboxes anymore");
|
||||
|
||||
info("Select the <body> again");
|
||||
yield selectNode("body", inspector);
|
||||
const checkBoxes = view.classPanel.querySelectorAll("[type=checkbox]");
|
||||
|
||||
is(checkBoxes[0].dataset.name, "class1", "The first checkbox is class1");
|
||||
is(checkBoxes[0].checked, true, "The first checkbox is still checked");
|
||||
|
||||
is(checkBoxes[1].dataset.name, "class2", "The second checkbox is class2");
|
||||
is(checkBoxes[1].checked, false, "The second checkbox is still unchecked");
|
||||
|
||||
is(checkBoxes[2].dataset.name, "class3", "The third checkbox is class3");
|
||||
is(checkBoxes[2].checked, false, "The third checkbox is still unchecked");
|
||||
});
|
|
@ -0,0 +1,45 @@
|
|||
/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
"use strict";
|
||||
|
||||
// Test that the class panel can be toggled.
|
||||
|
||||
add_task(function* () {
|
||||
yield addTab("data:text/html;charset=utf-8,<body class='class1 class2'>");
|
||||
let {inspector, view} = yield openRuleView();
|
||||
|
||||
info("Check that the toggle button exists");
|
||||
const button = inspector.panelDoc.querySelector("#class-panel-toggle");
|
||||
ok(button, "The class panel toggle button exists");
|
||||
is(view.classToggle, button, "The rule-view refers to the right element");
|
||||
|
||||
info("Check that the panel exists and is hidden by default");
|
||||
const panel = inspector.panelDoc.querySelector("#ruleview-class-panel");
|
||||
ok(panel, "The class panel exists");
|
||||
is(view.classPanel, panel, "The rule-view refers to the right element");
|
||||
ok(panel.hasAttribute("hidden"), "The panel is hidden");
|
||||
|
||||
info("Click on the button to show the panel");
|
||||
button.click();
|
||||
ok(!panel.hasAttribute("hidden"), "The panel is shown");
|
||||
ok(button.classList.contains("checked"), "The button is checked");
|
||||
|
||||
info("Click again to hide the panel");
|
||||
button.click();
|
||||
ok(panel.hasAttribute("hidden"), "The panel is hidden");
|
||||
ok(!button.classList.contains("checked"), "The button is unchecked");
|
||||
|
||||
info("Open the pseudo-class panel first, then the class panel");
|
||||
view.pseudoClassToggle.click();
|
||||
ok(!view.pseudoClassPanel.hasAttribute("hidden"), "The pseudo-class panel is shown");
|
||||
button.click();
|
||||
ok(!panel.hasAttribute("hidden"), "The panel is shown");
|
||||
ok(view.pseudoClassPanel.hasAttribute("hidden"), "The pseudo-class panel is hidden");
|
||||
|
||||
info("Click again on the pseudo-class button");
|
||||
view.pseudoClassToggle.click();
|
||||
ok(panel.hasAttribute("hidden"), "The panel is hidden");
|
||||
ok(!view.pseudoClassPanel.hasAttribute("hidden"), "The pseudo-class panel is shown");
|
||||
});
|
|
@ -506,3 +506,40 @@ function focusAndSendKey(win, key) {
|
|||
win.document.documentElement.focus();
|
||||
EventUtils.sendKey(key, win);
|
||||
}
|
||||
|
||||
/**
|
||||
* Toggle one of the checkboxes inside the class-panel. Resolved after the DOM mutation
|
||||
* has been recorded.
|
||||
* @param {CssRuleView} view The rule-view instance.
|
||||
* @param {String} name The class name to find the checkbox.
|
||||
*/
|
||||
function* toggleClassPanelCheckBox(view, name) {
|
||||
info(`Clicking on checkbox for class ${name}`);
|
||||
const checkBox = [...view.classPanel.querySelectorAll("[type=checkbox]")].find(box => {
|
||||
return box.dataset.name === name;
|
||||
});
|
||||
|
||||
const onMutation = view.inspector.once("markupmutation");
|
||||
checkBox.click();
|
||||
info("Waiting for a markupmutation as a result of toggling this class");
|
||||
yield onMutation;
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify the content of the class-panel.
|
||||
* @param {CssRuleView} view The rule-view isntance
|
||||
* @param {Array} classes The list of expected classes. Each item in this array is an
|
||||
* object with the following properties: {name: {String}, state: {Boolean}}
|
||||
*/
|
||||
function checkClassPanelContent(view, classes) {
|
||||
const checkBoxNodeList = view.classPanel.querySelectorAll("[type=checkbox]");
|
||||
is(checkBoxNodeList.length, classes.length,
|
||||
"The panel contains the expected number of checkboxes");
|
||||
|
||||
for (let i = 0; i < classes.length; i++) {
|
||||
is(checkBoxNodeList[i].dataset.name, classes[i].name,
|
||||
`Checkbox ${i} has the right class name`);
|
||||
is(checkBoxNodeList[i].checked, classes[i].state,
|
||||
`Checkbox ${i} has the right state`);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,356 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
"use strict";
|
||||
|
||||
const EventEmitter = require("devtools/shared/event-emitter");
|
||||
const {LocalizationHelper} = require("devtools/shared/l10n");
|
||||
|
||||
const L10N = new LocalizationHelper("devtools/client/locales/inspector.properties");
|
||||
|
||||
// This serves as a local cache for the classes applied to each of the node we care about
|
||||
// here.
|
||||
// The map is indexed by NodeFront. Any time a new node is selected in the inspector, an
|
||||
// entry is added here, indexed by the corresponding NodeFront.
|
||||
// The value for each entry is an array of each of the class this node has. Items of this
|
||||
// array are objects like: { name, isApplied } where the name is the class itself, and
|
||||
// isApplied is a Boolean indicating if the class is applied on the node or not.
|
||||
const CLASSES = new WeakMap();
|
||||
|
||||
/**
|
||||
* Manages the list classes per DOM elements we care about.
|
||||
* The actual list is stored in the CLASSES const, indexed by NodeFront objects.
|
||||
* The responsibility of this class is to be the source of truth for anyone who wants to
|
||||
* know which classes a given NodeFront has, and which of these are enabled and which are
|
||||
* disabled.
|
||||
* It also reacts to DOM mutations so the list of classes is up to date with what is in
|
||||
* the DOM.
|
||||
* It can also be used to enable/disable a given class, or add classes.
|
||||
*
|
||||
* @param {Inspector} inspector
|
||||
* The current inspector instance.
|
||||
*/
|
||||
function ClassListPreviewerModel(inspector) {
|
||||
EventEmitter.decorate(this);
|
||||
|
||||
this.inspector = inspector;
|
||||
|
||||
this.onMutations = this.onMutations.bind(this);
|
||||
this.inspector.on("markupmutation", this.onMutations);
|
||||
|
||||
this.classListProxyNode = this.inspector.panelDoc.createElement("div");
|
||||
}
|
||||
|
||||
ClassListPreviewerModel.prototype = {
|
||||
destroy() {
|
||||
this.inspector.off("markupmutation", this.onMutations);
|
||||
this.inspector = null;
|
||||
this.classListProxyNode = null;
|
||||
},
|
||||
|
||||
/**
|
||||
* The current node selection (which only returns if the node is an ELEMENT_NODE type
|
||||
* since that's the only type this model can work with.)
|
||||
*/
|
||||
get currentNode() {
|
||||
if (this.inspector.selection.isElementNode() &&
|
||||
!this.inspector.selection.isPseudoElementNode()) {
|
||||
return this.inspector.selection.nodeFront;
|
||||
}
|
||||
return null;
|
||||
},
|
||||
|
||||
/**
|
||||
* The class states for the current node selection. See the documentation of the CLASSES
|
||||
* constant.
|
||||
*/
|
||||
get currentClasses() {
|
||||
if (!this.currentNode) {
|
||||
return [];
|
||||
}
|
||||
|
||||
if (!CLASSES.has(this.currentNode)) {
|
||||
// Use the proxy node to get a clean list of classes.
|
||||
this.classListProxyNode.className = this.currentNode.className;
|
||||
let nodeClasses = [...new Set([...this.classListProxyNode.classList])].map(name => {
|
||||
return { name, isApplied: true };
|
||||
});
|
||||
|
||||
CLASSES.set(this.currentNode, nodeClasses);
|
||||
}
|
||||
|
||||
return CLASSES.get(this.currentNode);
|
||||
},
|
||||
|
||||
/**
|
||||
* Same as currentClasses, but returns it in the form of a className string, where only
|
||||
* enabled classes are added.
|
||||
*/
|
||||
get currentClassesPreview() {
|
||||
return this.currentClasses.filter(({ isApplied }) => isApplied)
|
||||
.map(({ name }) => name)
|
||||
.join(" ");
|
||||
},
|
||||
|
||||
/**
|
||||
* Set the state for a given class on the current node.
|
||||
*
|
||||
* @param {String} name
|
||||
* The class which state should be changed.
|
||||
* @param {Boolean} isApplied
|
||||
* True if the class should be enabled, false otherwise.
|
||||
* @return {Promise} Resolves when the change has been made in the DOM.
|
||||
*/
|
||||
setClassState(name, isApplied) {
|
||||
// Do the change in our local model.
|
||||
let nodeClasses = this.currentClasses;
|
||||
nodeClasses.find(({ name: cName }) => cName === name).isApplied = isApplied;
|
||||
|
||||
return this.applyClassState();
|
||||
},
|
||||
|
||||
/**
|
||||
* Add several classes to the current node at once.
|
||||
*
|
||||
* @param {String} classNameString
|
||||
* The string that contains all classes.
|
||||
* @return {Promise} Resolves when the change has been made in the DOM.
|
||||
*/
|
||||
addClassName(classNameString) {
|
||||
this.classListProxyNode.className = classNameString;
|
||||
return Promise.all([...new Set([...this.classListProxyNode.classList])].map(name => {
|
||||
return this.addClass(name);
|
||||
}));
|
||||
},
|
||||
|
||||
/**
|
||||
* Add a class to the current node at once.
|
||||
*
|
||||
* @param {String} name
|
||||
* The class to be added.
|
||||
* @return {Promise} Resolves when the change has been made in the DOM.
|
||||
*/
|
||||
addClass(name) {
|
||||
// Avoid adding the same class again.
|
||||
if (this.currentClasses.some(({ name: cName }) => cName === name)) {
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
// Change the local model, so we retain the state of the existing classes.
|
||||
this.currentClasses.push({ name, isApplied: true });
|
||||
|
||||
return this.applyClassState();
|
||||
},
|
||||
|
||||
/**
|
||||
* Used internally by other functions like addClass or setClassState. Actually applies
|
||||
* the class change to the DOM.
|
||||
*
|
||||
* @return {Promise} Resolves when the change has been made in the DOM.
|
||||
*/
|
||||
applyClassState() {
|
||||
// If there is no valid inspector selection, bail out silently. No need to report an
|
||||
// error here.
|
||||
if (!this.currentNode) {
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
// Remember which node we changed and the className we applied, so we can filter out
|
||||
// dom mutations that are caused by us in onMutations.
|
||||
this.lastStateChange = {
|
||||
node: this.currentNode,
|
||||
className: this.currentClassesPreview
|
||||
};
|
||||
|
||||
// Apply the change to the node.
|
||||
let mod = this.currentNode.startModifyingAttributes();
|
||||
mod.setAttribute("class", this.currentClassesPreview);
|
||||
return mod.apply();
|
||||
},
|
||||
|
||||
onMutations(e, mutations) {
|
||||
for (let {type, target, attributeName} of mutations) {
|
||||
// Only care if this mutation is for the class attribute.
|
||||
if (type !== "attributes" || attributeName !== "class") {
|
||||
continue;
|
||||
}
|
||||
|
||||
let isMutationForOurChange = this.lastStateChange &&
|
||||
target === this.lastStateChange.node &&
|
||||
target.className === this.lastStateChange.className;
|
||||
|
||||
if (!isMutationForOurChange) {
|
||||
CLASSES.delete(target);
|
||||
if (target === this.currentNode) {
|
||||
this.emit("current-node-class-changed");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* This UI widget shows a textfield and a series of checkboxes in the rule-view. It is
|
||||
* used to toggle classes on the current node selection, and add new classes.
|
||||
*
|
||||
* @param {Inspector} inspector
|
||||
* The current inspector instance.
|
||||
* @param {DomNode} containerEl
|
||||
* The element in the rule-view where the widget should go.
|
||||
*/
|
||||
function ClassListPreviewer(inspector, containerEl) {
|
||||
this.inspector = inspector;
|
||||
this.containerEl = containerEl;
|
||||
this.model = new ClassListPreviewerModel(inspector);
|
||||
|
||||
this.onNewSelection = this.onNewSelection.bind(this);
|
||||
this.onCheckBoxChanged = this.onCheckBoxChanged.bind(this);
|
||||
this.onKeyPress = this.onKeyPress.bind(this);
|
||||
this.onCurrentNodeClassChanged = this.onCurrentNodeClassChanged.bind(this);
|
||||
|
||||
// Create the add class text field.
|
||||
this.addEl = this.doc.createElement("input");
|
||||
this.addEl.classList.add("devtools-textinput");
|
||||
this.addEl.classList.add("add-class");
|
||||
this.addEl.setAttribute("placeholder",
|
||||
L10N.getStr("inspector.classPanel.newClass.placeholder"));
|
||||
this.addEl.addEventListener("keypress", this.onKeyPress);
|
||||
this.containerEl.appendChild(this.addEl);
|
||||
|
||||
// Create the class checkboxes container.
|
||||
this.classesEl = this.doc.createElement("div");
|
||||
this.classesEl.classList.add("classes");
|
||||
this.containerEl.appendChild(this.classesEl);
|
||||
|
||||
// Start listening for interesting events.
|
||||
this.inspector.selection.on("new-node-front", this.onNewSelection);
|
||||
this.containerEl.addEventListener("input", this.onCheckBoxChanged);
|
||||
this.model.on("current-node-class-changed", this.onCurrentNodeClassChanged);
|
||||
}
|
||||
|
||||
ClassListPreviewer.prototype = {
|
||||
destroy() {
|
||||
this.inspector.selection.off("new-node-front", this.onNewSelection);
|
||||
this.addEl.removeEventListener("keypress", this.onKeyPress);
|
||||
this.containerEl.removeEventListener("input", this.onCheckBoxChanged);
|
||||
|
||||
this.containerEl.innerHTML = "";
|
||||
|
||||
this.model.destroy();
|
||||
this.containerEl = null;
|
||||
this.inspector = null;
|
||||
this.addEl = null;
|
||||
this.classesEl = null;
|
||||
},
|
||||
|
||||
get doc() {
|
||||
return this.containerEl.ownerDocument;
|
||||
},
|
||||
|
||||
/**
|
||||
* Render the content of the panel. You typically don't need to call this as the panel
|
||||
* renders itself on inspector selection changes.
|
||||
*/
|
||||
render() {
|
||||
this.classesEl.innerHTML = "";
|
||||
|
||||
for (let { name, isApplied } of this.model.currentClasses) {
|
||||
let checkBox = this.renderCheckBox(name, isApplied);
|
||||
this.classesEl.appendChild(checkBox);
|
||||
}
|
||||
|
||||
if (!this.model.currentClasses.length) {
|
||||
this.classesEl.appendChild(this.renderNoClassesMessage());
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Render a single checkbox for a given classname.
|
||||
*
|
||||
* @param {String} name
|
||||
* The name of this class.
|
||||
* @param {Boolean} isApplied
|
||||
* Is this class currently applied on the DOM node.
|
||||
* @return {DOMNode} The DOM element for this checkbox.
|
||||
*/
|
||||
renderCheckBox(name, isApplied) {
|
||||
let box = this.doc.createElement("input");
|
||||
box.setAttribute("type", "checkbox");
|
||||
if (isApplied) {
|
||||
box.setAttribute("checked", "checked");
|
||||
}
|
||||
box.dataset.name = name;
|
||||
|
||||
let labelWrapper = this.doc.createElement("label");
|
||||
labelWrapper.setAttribute("title", name);
|
||||
labelWrapper.appendChild(box);
|
||||
|
||||
// A child element is required to do the ellipsis.
|
||||
let label = this.doc.createElement("span");
|
||||
label.textContent = name;
|
||||
labelWrapper.appendChild(label);
|
||||
|
||||
return labelWrapper;
|
||||
},
|
||||
|
||||
/**
|
||||
* Render the message displayed in the panel when the current element has no classes.
|
||||
*
|
||||
* @return {DOMNode} The DOM element for the message.
|
||||
*/
|
||||
renderNoClassesMessage() {
|
||||
let msg = this.doc.createElement("p");
|
||||
msg.classList.add("no-classes");
|
||||
msg.textContent = L10N.getStr("inspector.classPanel.noClasses");
|
||||
return msg;
|
||||
},
|
||||
|
||||
/**
|
||||
* Focus the add-class text field.
|
||||
*/
|
||||
focusAddClassField() {
|
||||
if (this.addEl) {
|
||||
this.addEl.focus();
|
||||
}
|
||||
},
|
||||
|
||||
onCheckBoxChanged({ target }) {
|
||||
if (!target.dataset.name) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.model.setClassState(target.dataset.name, target.checked).catch(e => {
|
||||
// Only log the error if the panel wasn't destroyed in the meantime.
|
||||
if (this.containerEl) {
|
||||
console.error(e);
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
onKeyPress(event) {
|
||||
if (event.key !== "Enter" || this.addEl.value === "") {
|
||||
return;
|
||||
}
|
||||
|
||||
this.model.addClassName(this.addEl.value).then(() => {
|
||||
this.render();
|
||||
this.addEl.value = "";
|
||||
}).catch(e => {
|
||||
// Only log the error if the panel wasn't destroyed in the meantime.
|
||||
if (this.containerEl) {
|
||||
console.error(e);
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
onNewSelection() {
|
||||
this.render();
|
||||
},
|
||||
|
||||
onCurrentNodeClassChanged() {
|
||||
this.render();
|
||||
}
|
||||
};
|
||||
|
||||
module.exports = ClassListPreviewer;
|
|
@ -3,6 +3,7 @@
|
|||
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
DevToolsModules(
|
||||
'class-list-previewer.js',
|
||||
'rule-editor.js',
|
||||
'text-property-editor.js',
|
||||
)
|
||||
|
|
|
@ -377,6 +377,19 @@ inspector.addRule.tooltip=Add new rule
|
|||
# rule view toolbar.
|
||||
inspector.togglePseudo.tooltip=Toggle pseudo-classes
|
||||
|
||||
# LOCALIZATION NOTE (inspector.classPanel.toggleClass.tooltip): This is the tooltip
|
||||
# shown when hovering over the `Toggle Class Panel` button in the
|
||||
# rule view toolbar.
|
||||
inspector.classPanel.toggleClass.tooltip=Toggle classes
|
||||
|
||||
# LOCALIZATION NOTE (inspector.classPanel.newClass.placeholder): This is the placeholder
|
||||
# shown inside the text field used to add a new class in the rule-view.
|
||||
inspector.classPanel.newClass.placeholder=Add new class
|
||||
|
||||
# LOCALIZATION NOTE (inspector.classPanel.noClasses): This is the text displayed in the
|
||||
# class panel when the current element has no classes applied.
|
||||
inspector.classPanel.noClasses=No classes on this element
|
||||
|
||||
# LOCALIZATION NOTE (inspector.noProperties): In the case where there are no CSS
|
||||
# properties to display e.g. due to search criteria this message is
|
||||
# displayed.
|
||||
|
|
|
@ -50,24 +50,71 @@
|
|||
display: flex;
|
||||
}
|
||||
|
||||
#pseudo-class-panel {
|
||||
.ruleview-reveal-panel {
|
||||
display: flex;
|
||||
height: 24px;
|
||||
overflow: hidden;
|
||||
transition: height 150ms ease;
|
||||
}
|
||||
|
||||
#pseudo-class-panel[hidden] {
|
||||
.ruleview-reveal-panel[hidden] {
|
||||
height: 0px;
|
||||
}
|
||||
|
||||
#pseudo-class-panel > label {
|
||||
#pseudo-class-panel:not([hidden]) {
|
||||
height: 24px;
|
||||
}
|
||||
|
||||
.ruleview-reveal-panel label {
|
||||
-moz-user-select: none;
|
||||
flex-grow: 1;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
/* Class toggle panel */
|
||||
#ruleview-class-panel:not([hidden]) {
|
||||
/* The class panel can contain 0 to N classes, so we can't hardcode a height here like
|
||||
we do for the pseudo-class panel. Unfortunately, that means we don't get the height
|
||||
transition when toggling the panel */
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
#ruleview-class-panel .add-class {
|
||||
margin: 0;
|
||||
border-width: 0 0 1px 0;
|
||||
padding: 2px 6px;
|
||||
border-radius: 0;
|
||||
}
|
||||
|
||||
#ruleview-class-panel .classes {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
#ruleview-class-panel .classes {
|
||||
max-height: 100px;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
#ruleview-class-panel .classes label {
|
||||
flex: 0 0;
|
||||
max-width: 50%;
|
||||
}
|
||||
|
||||
#ruleview-class-panel .classes label span {
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
#ruleview-class-panel .no-classes {
|
||||
flex: 1;
|
||||
color: var(--theme-body-color-inactive);
|
||||
margin: 0;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
/* Rule View Container */
|
||||
|
||||
#ruleview-container {
|
||||
|
@ -559,6 +606,12 @@
|
|||
background-size: cover;
|
||||
}
|
||||
|
||||
#class-panel-toggle::before {
|
||||
content: ".cls";
|
||||
direction: ltr;
|
||||
font-size: 11px;
|
||||
}
|
||||
|
||||
.ruleview-overridden-rule-filter {
|
||||
opacity: 0.8;
|
||||
}
|
||||
|
|
Загрузка…
Ссылка в новой задаче