the huge 117+ fixes update
This commit is contained in:
parent
baf06e964f
commit
ccae90fb3e
110 changed files with 7809 additions and 2864 deletions
BIN
chrome/utils/styloaix/16.png
Normal file
BIN
chrome/utils/styloaix/16.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 661 B |
BIN
chrome/utils/styloaix/16w.png
Normal file
BIN
chrome/utils/styloaix/16w.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 589 B |
320
chrome/utils/styloaix/autocomplete.js
Normal file
320
chrome/utils/styloaix/autocomplete.js
Normal file
|
@ -0,0 +1,320 @@
|
|||
/* 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 AutocompletePopup = require('devtools/client/shared/autocomplete-popup');
|
||||
|
||||
let loader;
|
||||
try {
|
||||
({ loader } = ChromeUtils.import('resource://devtools/shared/loader/Loader.jsm'));
|
||||
} catch (e) {
|
||||
// tb91
|
||||
({ loader } = ChromeUtils.import('resource://devtools/shared/Loader.jsm'));
|
||||
}
|
||||
|
||||
loader.lazyRequireGetter(
|
||||
this,
|
||||
"KeyCodes",
|
||||
"devtools/client/shared/keycodes",
|
||||
true
|
||||
);
|
||||
loader.lazyRequireGetter(
|
||||
this,
|
||||
"CSSCompleter",
|
||||
"devtools/client/shared/sourceeditor/css-autocompleter"
|
||||
);
|
||||
|
||||
const autocompleteMap = new WeakMap();
|
||||
|
||||
/**
|
||||
* Prepares an editor instance for autocompletion.
|
||||
*/
|
||||
function initializeAutoCompletion(ctx, options = {}) {
|
||||
const { cm, ed, Editor } = ctx;
|
||||
if (autocompleteMap.has(ed)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const win = ed.container.contentWindow.wrappedJSObject;
|
||||
const { CodeMirror } = win;
|
||||
|
||||
let completer = null;
|
||||
const autocompleteKey =
|
||||
"Ctrl-" + Editor.keyFor("autocompletion", { noaccel: true });
|
||||
if (ed.config.mode == Editor.modes.css) {
|
||||
completer = new CSSCompleter({
|
||||
walker: options.walker,
|
||||
cssProperties: options.cssProperties,
|
||||
maxEntries: 1000,
|
||||
});
|
||||
}
|
||||
|
||||
function insertSelectedPopupItem() {
|
||||
const autocompleteState = autocompleteMap.get(ed);
|
||||
if (!popup || !popup.isOpen || !autocompleteState) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!autocompleteState.suggestionInsertedOnce && popup.selectedItem) {
|
||||
autocompleteMap.get(ed).insertingSuggestion = true;
|
||||
insertPopupItem(ed, popup.selectedItem);
|
||||
}
|
||||
|
||||
popup.once("popup-closed", () => {
|
||||
// This event is used in tests.
|
||||
ed.emit("popup-hidden");
|
||||
});
|
||||
popup.hidePopup();
|
||||
return true;
|
||||
}
|
||||
|
||||
// Give each popup a new name to avoid sharing the elements.
|
||||
|
||||
let popup = new AutocompletePopup(win.parent.document, {
|
||||
position: "bottom",
|
||||
autoSelect: true,
|
||||
onClick: insertSelectedPopupItem,
|
||||
});
|
||||
|
||||
const cycle = reverse => {
|
||||
if (popup?.isOpen) {
|
||||
// eslint-disable-next-line mozilla/no-compare-against-boolean-literals
|
||||
cycleSuggestions(ed, reverse == true);
|
||||
return null;
|
||||
}
|
||||
|
||||
return CodeMirror.Pass;
|
||||
};
|
||||
|
||||
let keyMap = {
|
||||
Tab: cycle,
|
||||
Down: cycle,
|
||||
"Shift-Tab": cycle.bind(null, true),
|
||||
Up: cycle.bind(null, true),
|
||||
Enter: () => {
|
||||
const wasHandled = insertSelectedPopupItem();
|
||||
return wasHandled ? true : CodeMirror.Pass;
|
||||
},
|
||||
};
|
||||
|
||||
const autoCompleteCallback = autoComplete.bind(null, ctx);
|
||||
const keypressCallback = onEditorKeypress.bind(null, ctx);
|
||||
keyMap[autocompleteKey] = autoCompleteCallback;
|
||||
cm.addKeyMap(keyMap);
|
||||
|
||||
cm.on("keydown", keypressCallback);
|
||||
ed.on("change", autoCompleteCallback);
|
||||
ed.on("destroy", destroy);
|
||||
|
||||
function destroy() {
|
||||
ed.off("destroy", destroy);
|
||||
cm.off("keydown", keypressCallback);
|
||||
ed.off("change", autoCompleteCallback);
|
||||
cm.removeKeyMap(keyMap);
|
||||
popup.destroy();
|
||||
keyMap = popup = completer = null;
|
||||
autocompleteMap.delete(ed);
|
||||
}
|
||||
|
||||
autocompleteMap.set(ed, {
|
||||
popup: popup,
|
||||
completer: completer,
|
||||
keyMap: keyMap,
|
||||
destroy: destroy,
|
||||
insertingSuggestion: false,
|
||||
suggestionInsertedOnce: false,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides suggestions to autocomplete the current token/word being typed.
|
||||
*/
|
||||
function autoComplete({ ed, cm }) {
|
||||
const autocompleteOpts = autocompleteMap.get(ed);
|
||||
const { completer, popup } = autocompleteOpts;
|
||||
if (
|
||||
!completer ||
|
||||
autocompleteOpts.insertingSuggestion ||
|
||||
autocompleteOpts.doNotAutocomplete
|
||||
) {
|
||||
autocompleteOpts.insertingSuggestion = false;
|
||||
return;
|
||||
}
|
||||
const cur = ed.getCursor();
|
||||
completer
|
||||
.complete(cm.getRange({ line: 0, ch: 0 }, cur), cur)
|
||||
.then(suggestions => {
|
||||
if (
|
||||
!suggestions ||
|
||||
!suggestions.length ||
|
||||
suggestions[0].preLabel == null
|
||||
) {
|
||||
autocompleteOpts.suggestionInsertedOnce = false;
|
||||
popup.once("popup-closed", () => {
|
||||
// This event is used in tests.
|
||||
ed.emit("after-suggest");
|
||||
});
|
||||
popup.hidePopup();
|
||||
return;
|
||||
}
|
||||
// The cursor is at the end of the currently entered part of the token,
|
||||
// like "backgr|" but we need to open the popup at the beginning of the
|
||||
// character "b". Thus we need to calculate the width of the entered part
|
||||
// of the token ("backgr" here).
|
||||
|
||||
const cursorElement = cm.display.cursorDiv.querySelector(
|
||||
".CodeMirror-cursor"
|
||||
);
|
||||
const left = suggestions[0].preLabel.length * cm.defaultCharWidth();
|
||||
popup.hidePopup();
|
||||
popup.setItems(suggestions);
|
||||
|
||||
popup.once("popup-opened", () => {
|
||||
// This event is used in tests.
|
||||
ed.emit("after-suggest");
|
||||
});
|
||||
popup.openPopup(cursorElement, -1 * left, 0);
|
||||
autocompleteOpts.suggestionInsertedOnce = false;
|
||||
})
|
||||
.catch(console.error);
|
||||
}
|
||||
|
||||
/**
|
||||
* Inserts a popup item into the current cursor location
|
||||
* in the editor.
|
||||
*/
|
||||
function insertPopupItem(ed, popupItem) {
|
||||
const { preLabel, text } = popupItem;
|
||||
const cur = ed.getCursor();
|
||||
const textBeforeCursor = ed.getText(cur.line).substring(0, cur.ch);
|
||||
const backwardsTextBeforeCursor = textBeforeCursor
|
||||
.split("")
|
||||
.reverse()
|
||||
.join("");
|
||||
const backwardsPreLabel = preLabel
|
||||
.split("")
|
||||
.reverse()
|
||||
.join("");
|
||||
|
||||
// If there is additional text in the preLabel vs the line, then
|
||||
// just insert the entire autocomplete text. An example:
|
||||
// if you type 'a' and select '#about' from the autocomplete menu,
|
||||
// then the final text needs to the end up as '#about'.
|
||||
if (backwardsPreLabel.indexOf(backwardsTextBeforeCursor) === 0) {
|
||||
ed.replaceText(text, { line: cur.line, ch: 0 }, cur);
|
||||
} else {
|
||||
ed.replaceText(text.slice(preLabel.length), cur, cur);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Cycles through provided suggestions by the popup in a top to bottom manner
|
||||
* when `reverse` is not true. Opposite otherwise.
|
||||
*/
|
||||
function cycleSuggestions(ed, reverse) {
|
||||
const autocompleteOpts = autocompleteMap.get(ed);
|
||||
const { popup } = autocompleteOpts;
|
||||
const cur = ed.getCursor();
|
||||
autocompleteOpts.insertingSuggestion = true;
|
||||
if (!autocompleteOpts.suggestionInsertedOnce) {
|
||||
autocompleteOpts.suggestionInsertedOnce = true;
|
||||
let firstItem;
|
||||
if (reverse) {
|
||||
firstItem = popup.getItemAtIndex(popup.itemCount - 1);
|
||||
popup.selectPreviousItem();
|
||||
} else {
|
||||
firstItem = popup.getItemAtIndex(0);
|
||||
if (firstItem.label == firstItem.preLabel && popup.itemCount > 1) {
|
||||
firstItem = popup.getItemAtIndex(1);
|
||||
popup.selectNextItem();
|
||||
}
|
||||
}
|
||||
if (popup.itemCount == 1) {
|
||||
popup.hidePopup();
|
||||
}
|
||||
insertPopupItem(ed, firstItem);
|
||||
} else {
|
||||
const fromCur = {
|
||||
line: cur.line,
|
||||
ch: cur.ch - popup.selectedItem.text.length,
|
||||
};
|
||||
if (reverse) {
|
||||
popup.selectPreviousItem();
|
||||
} else {
|
||||
popup.selectNextItem();
|
||||
}
|
||||
ed.replaceText(popup.selectedItem.text, fromCur, cur);
|
||||
}
|
||||
// This event is used in tests.
|
||||
ed.emit("suggestion-entered");
|
||||
}
|
||||
|
||||
/**
|
||||
* onkeydown handler for the editor instance to prevent autocompleting on some
|
||||
* keypresses.
|
||||
*/
|
||||
function onEditorKeypress({ ed, Editor }, cm, event) {
|
||||
const autocompleteOpts = autocompleteMap.get(ed);
|
||||
|
||||
// Do not try to autocomplete with multiple selections.
|
||||
if (ed.hasMultipleSelections()) {
|
||||
autocompleteOpts.doNotAutocomplete = true;
|
||||
autocompleteOpts.popup.hidePopup();
|
||||
return;
|
||||
}
|
||||
|
||||
if (
|
||||
(event.ctrlKey || event.metaKey) &&
|
||||
event.keyCode == KeyCodes.DOM_VK_SPACE
|
||||
) {
|
||||
// When Ctrl/Cmd + Space is pressed, two simultaneous keypresses are emitted
|
||||
// first one for just the Ctrl/Cmd and second one for combo. The first one
|
||||
// leave the autocompleteOpts.doNotAutocomplete as true, so we have to make
|
||||
// it false
|
||||
autocompleteOpts.doNotAutocomplete = false;
|
||||
return;
|
||||
}
|
||||
|
||||
if (event.ctrlKey || event.metaKey || event.altKey) {
|
||||
autocompleteOpts.doNotAutocomplete = true;
|
||||
autocompleteOpts.popup.hidePopup();
|
||||
return;
|
||||
}
|
||||
|
||||
switch (event.keyCode) {
|
||||
case KeyCodes.DOM_VK_RETURN:
|
||||
autocompleteOpts.doNotAutocomplete = true;
|
||||
break;
|
||||
case KeyCodes.DOM_VK_ESCAPE:
|
||||
if (autocompleteOpts.popup.isOpen) {
|
||||
// Prevent the Console input to open, but still remove the autocomplete popup.
|
||||
autocompleteOpts.doNotAutocomplete = true;
|
||||
autocompleteOpts.popup.hidePopup();
|
||||
event.preventDefault();
|
||||
}
|
||||
break;
|
||||
case KeyCodes.DOM_VK_LEFT:
|
||||
case KeyCodes.DOM_VK_RIGHT:
|
||||
case KeyCodes.DOM_VK_HOME:
|
||||
case KeyCodes.DOM_VK_END:
|
||||
autocompleteOpts.doNotAutocomplete = true;
|
||||
autocompleteOpts.popup.hidePopup();
|
||||
break;
|
||||
case KeyCodes.DOM_VK_BACK_SPACE:
|
||||
case KeyCodes.DOM_VK_DELETE:
|
||||
if (ed.config.mode == Editor.modes.css) {
|
||||
autocompleteOpts.completer.invalidateCache(ed.getCursor().line);
|
||||
}
|
||||
autocompleteOpts.doNotAutocomplete = true;
|
||||
autocompleteOpts.popup.hidePopup();
|
||||
break;
|
||||
default:
|
||||
autocompleteOpts.doNotAutocomplete = false;
|
||||
}
|
||||
}
|
||||
|
||||
// Export functions
|
||||
|
||||
exports.initializeAutoCompletion = initializeAutoCompletion;
|
48
chrome/utils/styloaix/edit.css
Normal file
48
chrome/utils/styloaix/edit.css
Normal file
|
@ -0,0 +1,48 @@
|
|||
#styloaix-edit {
|
||||
min-width: 850px;
|
||||
}
|
||||
#errors {
|
||||
margin: 2px 0px 4px 5px;
|
||||
color: red;
|
||||
}
|
||||
#errors label {
|
||||
cursor: pointer;
|
||||
}
|
||||
#internal-code {
|
||||
font-family: monospace;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
.findbar-closebutton, .findbar-highlight, *[anonid="find-case-sensitive"] {
|
||||
display: none;
|
||||
}
|
||||
#findbar {
|
||||
border-top: 0;
|
||||
}
|
||||
.devtools-horizontal-splitter {
|
||||
/* Overriding global.css */
|
||||
height: 3px !important;
|
||||
}
|
||||
input.devtools-textinput {
|
||||
padding: 4px;
|
||||
margin: 0 6px 0 4px;
|
||||
border: 1px solid ThreeDShadow;
|
||||
-moz-box-flex: 1;
|
||||
}
|
||||
.devtools-toolbarbutton {
|
||||
border-radius: 2px !important;
|
||||
border: 1px solid rgba(0,0,0,0.3) !important;
|
||||
padding: 1px 5px !important;
|
||||
}
|
||||
.devtools-separator {
|
||||
margin: 0 5px 0 4px !important;
|
||||
}
|
||||
.devtools-toolbarbutton[disabled="true"] {
|
||||
opacity: 0.2;
|
||||
}
|
||||
.update-url-row {
|
||||
display: none;
|
||||
}
|
||||
#editor-tools {
|
||||
padding: 5px;
|
||||
-moz-box-align: center;
|
||||
}
|
459
chrome/utils/styloaix/edit.js
Normal file
459
chrome/utils/styloaix/edit.js
Normal file
|
@ -0,0 +1,459 @@
|
|||
const { Services } = ChromeUtils.import('resource://gre/modules/Services.jsm');
|
||||
const { NetUtil } = ChromeUtils.import('resource://gre/modules/NetUtil.jsm');
|
||||
|
||||
let require;
|
||||
try {
|
||||
({ require } = ChromeUtils.import('resource://devtools/shared/loader/Loader.jsm'));
|
||||
} catch (e) {
|
||||
// tb91
|
||||
({ require } = ChromeUtils.import('resource://devtools/shared/Loader.jsm'));
|
||||
}
|
||||
|
||||
docShell.cssErrorReportingEnabled = true;
|
||||
|
||||
function require_mini (m) {
|
||||
let scope = {
|
||||
exports: {}
|
||||
};
|
||||
Services.scriptloader.loadSubScript('chrome://' + m + '.js', scope);
|
||||
return scope.exports;
|
||||
};
|
||||
|
||||
let url;
|
||||
let type;
|
||||
let id;
|
||||
let style;
|
||||
|
||||
if (isChromeWindow) {
|
||||
let params = window.arguments[0];
|
||||
url = params.url;
|
||||
type = params.type;
|
||||
id = params.id;
|
||||
} else {
|
||||
let params = new URLSearchParams(location.search);
|
||||
url = params.get('url');
|
||||
type = params.get('type');
|
||||
id = params.get('id');
|
||||
}
|
||||
|
||||
origin = 2;
|
||||
let lastOrigin;
|
||||
let unsaved = false;
|
||||
let previewCode;
|
||||
let previewOrigin;
|
||||
let previewActive = false;
|
||||
let isInstantPreview;
|
||||
let isInstantCheck;
|
||||
let timeoutRunning = false;
|
||||
let interval;
|
||||
let nameE;
|
||||
let initialCode = '';
|
||||
let sourceEditor;
|
||||
|
||||
function init () {
|
||||
if (id)
|
||||
style = UC.styloaix.styles.get(id);
|
||||
if (style) {
|
||||
origin = style.type;
|
||||
NetUtil.asyncFetch(
|
||||
{
|
||||
uri: style.url,
|
||||
loadingNode: document,
|
||||
securityFlags: Ci.nsILoadInfo.SEC_ALLOW_CROSS_ORIGIN_INHERITS_SEC_CONTEXT || Ci.nsILoadInfo.SEC_ALLOW_CROSS_ORIGIN_DATA_INHERITS,
|
||||
contentPolicyType: Ci.nsIContentPolicy.TYPE_OTHER,
|
||||
},
|
||||
async function (stream) {
|
||||
const bstream = Cc['@mozilla.org/binaryinputstream;1'].createInstance(Ci.nsIBinaryInputStream);
|
||||
bstream.setInputStream(stream);
|
||||
|
||||
try {
|
||||
initialCode = bstream.readBytes(bstream.available());
|
||||
} catch {}
|
||||
|
||||
stream.close();
|
||||
|
||||
try {
|
||||
const converter = Cc['@mozilla.org/intl/scriptableunicodeconverter'].createInstance(Ci.nsIScriptableUnicodeConverter);
|
||||
converter.charset = 'utf-8';
|
||||
initialCode = converter.ConvertToUnicode(initialCode);
|
||||
} catch {}
|
||||
|
||||
initEditor();
|
||||
}
|
||||
);
|
||||
} else {
|
||||
if (url)
|
||||
initialCode = '@-moz-document ' + type + '("' + url + '") {\n\n\n}';
|
||||
initEditor();
|
||||
}
|
||||
|
||||
nameE = document.getElementById('name');
|
||||
nameE.value = style?.name || '';
|
||||
updateTitle();
|
||||
nameE.addEventListener('input', function () {
|
||||
unsaved = true;
|
||||
toggleUI('save-button', true);
|
||||
});
|
||||
|
||||
document.getElementById('origin').value = origin;
|
||||
|
||||
document.getElementById('errors').addEventListener('click', function (e) {
|
||||
if (e.target == this)
|
||||
this.style.display = 'none';
|
||||
});
|
||||
|
||||
isInstantPreview = xPref.get(UC.styloaix.PREF_INSTANTPREVIEW);
|
||||
document.getElementById('instant-preview').checked = isInstantPreview;
|
||||
isInstantCheck = xPref.get(UC.styloaix.PREF_INSTANTCHECK);
|
||||
document.getElementById('instant-check').checked = isInstantCheck;
|
||||
if (style?.enabled === false)
|
||||
toggleUI('preview-button', true);
|
||||
interval = xPref.get(UC.styloaix.PREF_INSTANTINTERVAL);
|
||||
xPref.addListener(UC.styloaix.PREF_INSTANTINTERVAL, function (ms) {
|
||||
interval = ms;
|
||||
});
|
||||
}
|
||||
|
||||
function initEditor () {
|
||||
const Editor = require('devtools/client/shared/sourceeditor/editor');
|
||||
|
||||
const extraKeys = {
|
||||
[Editor.accel('S')]: save,
|
||||
'F3': 'findNext',
|
||||
'Shift-F3': 'findPrev'
|
||||
};
|
||||
|
||||
const lineWrapping = xPref.get(UC.styloaix.PREF_LINEWRAPPING);
|
||||
document.getElementById('wrap-lines').checked = lineWrapping;
|
||||
|
||||
sourceEditor = new Editor({
|
||||
mode: Editor.modes.css,
|
||||
contextMenu: 'sourceEditorContextMenu',
|
||||
extraKeys: extraKeys,
|
||||
lineNumbers: true,
|
||||
lineWrapping: lineWrapping,
|
||||
value: initialCode,
|
||||
maxHighlightLength: 10000
|
||||
});
|
||||
|
||||
sourceEditor.setupAutoCompletion = function () {
|
||||
this.extend(require_mini('userchromejs/content/styloaix/autocomplete'));
|
||||
this.initializeAutoCompletion();
|
||||
};
|
||||
|
||||
document.getElementById('editor').selectedIndex = 1;
|
||||
|
||||
sourceEditor.appendTo(document.getElementById('sourceeditor')).then(function () {
|
||||
sourceEditor.insertCommandsController();
|
||||
sourceEditor.focus();
|
||||
if (isInstantCheck)
|
||||
checkForErrors();
|
||||
});
|
||||
|
||||
sourceEditor.on('change', function () {
|
||||
changed();
|
||||
if (!isInstantCheck)
|
||||
toggleUI('check-for-errors-button', true);
|
||||
});
|
||||
}
|
||||
|
||||
function changed () {
|
||||
if ((isInstantPreview || isInstantCheck) && !timeoutRunning)
|
||||
instantTimeout();
|
||||
|
||||
unsaved = true;
|
||||
toggleUI('save-button', true);
|
||||
if (!isInstantPreview)
|
||||
toggleUI('preview-button', true);
|
||||
}
|
||||
|
||||
function instantTimeout () {
|
||||
timeoutRunning = true;
|
||||
setTimeout(() => {
|
||||
if (isInstantPreview) {
|
||||
if (previewActive)
|
||||
_uc.sss.unregisterSheet(previewCode, previewOrigin);
|
||||
else if (style?.enabled)
|
||||
style.unregister();
|
||||
previewCode = Services.io.newURI('data:text/css;charset=UTF-8,' + encodeURIComponent(codeElementWrapper.value));
|
||||
previewOrigin = origin;
|
||||
previewActive = true;
|
||||
_uc.sss.loadAndRegisterSheet(previewCode, previewOrigin);
|
||||
toggleUI('preview-button', false);
|
||||
if (origin === _uc.sss.AGENT_SHEET || lastOrigin === _uc.sss.AGENT_SHEET) {
|
||||
lastOrigin = origin;
|
||||
UC.styloaix.forceRefresh();
|
||||
}
|
||||
}
|
||||
if (isInstantCheck)
|
||||
checkForErrors();
|
||||
timeoutRunning = false;
|
||||
}, interval)
|
||||
}
|
||||
|
||||
function save () {
|
||||
if (!nameE.value)
|
||||
return alert('Style name must not be empty.');
|
||||
|
||||
if (style)
|
||||
style.unregister();
|
||||
|
||||
const finalTitle = nameE.value + (origin == 0 ? '.as' : origin == 1 ? '.us' : '') + '.css';
|
||||
const file = UC.styloaix.CSSDIR.clone();
|
||||
file.append(finalTitle);
|
||||
if (!file.exists())
|
||||
file.createUnique(Ci.nsIFile.NORMAL_FILE_TYPE, 0o666);
|
||||
|
||||
const ostream = Cc['@mozilla.org/network/file-output-stream;1'].createInstance(Ci.nsIFileOutputStream);
|
||||
ostream.init(file, -1, -1, 0);
|
||||
|
||||
const converter = Cc['@mozilla.org/intl/scriptableunicodeconverter'].createInstance(Ci.nsIScriptableUnicodeConverter);
|
||||
converter.charset = 'UTF-8';
|
||||
|
||||
const istream = converter.convertToInputStream(codeElementWrapper.value);
|
||||
|
||||
NetUtil.asyncCopy(istream, ostream, function (aResult) {
|
||||
if (Components.isSuccessCode(aResult)) {
|
||||
const enabled = style ? style.enabled : true;
|
||||
|
||||
if (style && finalTitle != style.fullName) {
|
||||
const oldFile = UC.styloaix.CSSDIR.clone()
|
||||
oldFile.append(style.fullName);
|
||||
oldFile.remove(false);
|
||||
UC.styloaix.styles.delete(style.fullName);
|
||||
if (!enabled) {
|
||||
UC.styloaix.disabledStyles.add(finalTitle);
|
||||
UC.styloaix.disabledStyles.delete(oldFile.leafName);
|
||||
}
|
||||
}
|
||||
|
||||
style = new UC.styloaix.UserStyle(file);
|
||||
style.enabled = enabled;
|
||||
updateTitle();
|
||||
UC.styloaix.styles.set(style.fullName, style);
|
||||
if (UC.styloaix.enabled && enabled) {
|
||||
style.register();
|
||||
toggleUI('preview-button', false);
|
||||
if (previewActive) {
|
||||
_uc.sss.unregisterSheet(previewCode, previewOrigin);
|
||||
previewActive = false;
|
||||
if (origin === _uc.sss.AGENT_SHEET || lastOrigin === _uc.sss.AGENT_SHEET)
|
||||
UC.styloaix.forceRefresh();
|
||||
}
|
||||
}
|
||||
|
||||
unsaved = false;
|
||||
|
||||
toggleUI('save-button', false);
|
||||
} else {
|
||||
alert('Error!');
|
||||
}
|
||||
})
|
||||
|
||||
sourceEditor.focus();
|
||||
}
|
||||
|
||||
function updateTitle () {
|
||||
document.title = (style?.fullName || 'New Style') + ' - StyloaiX Editor';
|
||||
}
|
||||
|
||||
function toggleUI (id, state) {
|
||||
document.getElementById(id).disabled = !state;
|
||||
}
|
||||
|
||||
function preview () {
|
||||
if (previewActive)
|
||||
_uc.sss.unregisterSheet(previewCode, previewOrigin);
|
||||
else if (style?.enabled)
|
||||
style.unregister();
|
||||
previewCode = Services.io.newURI('data:text/css;charset=UTF-8,' + encodeURIComponent(codeElementWrapper.value));
|
||||
previewOrigin = origin;
|
||||
_uc.sss.loadAndRegisterSheet(previewCode, previewOrigin);
|
||||
previewActive = true;
|
||||
if (origin === _uc.sss.AGENT_SHEET || lastOrigin === _uc.sss.AGENT_SHEET) {
|
||||
lastOrigin = origin;
|
||||
UC.styloaix.forceRefresh();
|
||||
}
|
||||
|
||||
checkForErrors();
|
||||
toggleUI('preview-button', false);
|
||||
sourceEditor.focus();
|
||||
}
|
||||
|
||||
function checkForErrors () {
|
||||
const errors = document.getElementById('errors');
|
||||
errors.style.display = 'none';
|
||||
|
||||
while (errors.hasChildNodes())
|
||||
errors.lastChild.remove();
|
||||
|
||||
let count = 0;
|
||||
|
||||
const errorListener = {
|
||||
observe: (message) => {
|
||||
if (!count)
|
||||
errors.style.display = 'block';
|
||||
|
||||
const error = message.QueryInterface(Ci.nsIScriptError);
|
||||
const errMsg = error.lineNumber + ':' + error.columnNumber + ' - ' + error.errorMessage;
|
||||
|
||||
const label = document.createElement('label');
|
||||
label.appendChild(document.createTextNode(errMsg));
|
||||
label.addEventListener('click', function () {
|
||||
goToLine(error.lineNumber, error.columnNumber);
|
||||
});
|
||||
errors.appendChild(label);
|
||||
errors.appendChild(document.createElement('br'));
|
||||
count++;
|
||||
|
||||
if (count == 10) {
|
||||
errors.appendChild(document.createTextNode('...'));
|
||||
Services.console.unregisterListener(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Services.console.registerListener(errorListener);
|
||||
|
||||
const styleEl = document.createElement('style');
|
||||
styleEl.appendChild(document.createTextNode(codeElementWrapper.value));
|
||||
document.documentElement.appendChild(styleEl);
|
||||
styleEl.remove();
|
||||
|
||||
setTimeout(() => {
|
||||
if (count < 10)
|
||||
Services.console.unregisterListener(errorListener);
|
||||
});
|
||||
|
||||
toggleUI('check-for-errors-button', false);
|
||||
sourceEditor.focus();
|
||||
}
|
||||
|
||||
function goToLine (line, col) {
|
||||
sourceEditor.focus();
|
||||
sourceEditor.setCursor({line: line - 1, ch: col});
|
||||
}
|
||||
|
||||
function insertCodeAtStart (snippet) {
|
||||
let position = codeElementWrapper.value.indexOf(snippet);
|
||||
if (position == -1) {
|
||||
codeElementWrapper.value = snippet + '\n' + codeElementWrapper.value;
|
||||
position = 0;
|
||||
}
|
||||
const positionEnd = position + snippet.length;
|
||||
|
||||
codeElementWrapper.setSelectionRange(positionEnd, positionEnd);
|
||||
sourceEditor.focus();
|
||||
}
|
||||
|
||||
function insertCodeAtCaret (snippet) {
|
||||
const selectionStart = codeElementWrapper.selectionStart;
|
||||
const selectionEnd = selectionStart + snippet.length;
|
||||
codeElementWrapper.value = codeElementWrapper.value.substring(0, codeElementWrapper.selectionStart) + snippet + codeElementWrapper.value.substring(codeElementWrapper.selectionEnd, codeElementWrapper.value.length);
|
||||
codeElementWrapper.setSelectionRange(selectionEnd, selectionEnd);
|
||||
sourceEditor.focus();
|
||||
}
|
||||
|
||||
function changeWordWrap (bool, persist) {
|
||||
if (persist)
|
||||
xPref.set(UC.styloaix.PREF_LINEWRAPPING, bool);
|
||||
sourceEditor.setOption('lineWrapping', bool);
|
||||
sourceEditor.focus();
|
||||
}
|
||||
|
||||
function instantPreview (bool, persist) {
|
||||
if (persist)
|
||||
xPref.set(UC.styloaix.PREF_INSTANTPREVIEW, bool);
|
||||
isInstantPreview = bool
|
||||
if (isInstantPreview && !timeoutRunning)
|
||||
instantTimeout();
|
||||
|
||||
sourceEditor.focus();
|
||||
}
|
||||
|
||||
function instantCheck (bool, persist) {
|
||||
if (persist)
|
||||
xPref.set(UC.styloaix.PREF_INSTANTCHECK, bool);
|
||||
isInstantCheck = bool
|
||||
if (isInstantCheck && !timeoutRunning)
|
||||
instantTimeout();
|
||||
|
||||
sourceEditor.focus();
|
||||
}
|
||||
|
||||
function insertDataURI() {
|
||||
const fp = Cc['@mozilla.org/filepicker;1'].createInstance(Ci.nsIFilePicker);
|
||||
fp.init(window, 'Choose File…', Ci.nsIFilePicker.modeOpen);
|
||||
fp.open(res => {
|
||||
if (res != Ci.nsIFilePicker.returnOK)
|
||||
return;
|
||||
|
||||
const contentType = Cc['@mozilla.org/mime;1'].getService(Ci.nsIMIMEService).getTypeFromFile(fp.file);
|
||||
const inputStream = Cc['@mozilla.org/network/file-input-stream;1'].createInstance(Ci.nsIFileInputStream);
|
||||
inputStream.init(fp.file, parseInt('01', 16), parseInt('0600', 8), 0);
|
||||
const stream = Cc['@mozilla.org/binaryinputstream;1'].createInstance(Ci.nsIBinaryInputStream);
|
||||
stream.setInputStream(inputStream);
|
||||
const encoded = btoa(stream.readBytes(stream.available()));
|
||||
stream.close();
|
||||
inputStream.close();
|
||||
insertCodeAtCaret('data:' + contentType + ';base64,' + encoded);
|
||||
});
|
||||
}
|
||||
|
||||
const codeElementWrapper = {
|
||||
get value() {
|
||||
return sourceEditor.getText();
|
||||
},
|
||||
|
||||
set value(v) {
|
||||
sourceEditor.setText(v);
|
||||
},
|
||||
|
||||
setSelectionRange: function (start, end) {
|
||||
sourceEditor.setSelection(sourceEditor.getPosition(start), sourceEditor.getPosition(end));
|
||||
},
|
||||
|
||||
get selectionStart() {
|
||||
return sourceEditor.getOffset(sourceEditor.getCursor('start'));
|
||||
},
|
||||
|
||||
get selectionEnd() {
|
||||
return sourceEditor.getOffset(sourceEditor.getCursor('end'));
|
||||
},
|
||||
|
||||
}
|
||||
|
||||
const closeFn = window.close;
|
||||
let shouldHandle = true;
|
||||
|
||||
if (isChromeWindow) {
|
||||
window.close = function () {
|
||||
if (!unsaved || confirm('Do you want to close and lose unsaved changes?')) {
|
||||
shouldHandle = false;
|
||||
setTimeout(closeFn);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
window.addEventListener('close', function (e) {
|
||||
e.preventDefault();
|
||||
window.close();
|
||||
})
|
||||
|
||||
window.addEventListener('beforeunload', function (e) {
|
||||
if (shouldHandle && unsaved)
|
||||
e.preventDefault();
|
||||
});
|
||||
|
||||
window.addEventListener('unload', function (event) {
|
||||
if (previewActive) {
|
||||
_uc.sss.unregisterSheet(previewCode, previewOrigin);
|
||||
if (origin === _uc.sss.AGENT_SHEET || lastOrigin === _uc.sss.AGENT_SHEET)
|
||||
UC.styloaix.forceRefresh();
|
||||
}
|
||||
|
||||
if (style?.enabled && previewActive)
|
||||
style.register();
|
||||
});
|
||||
|
||||
window.addEventListener('DOMContentLoaded', init, {once: true});
|
140
chrome/utils/styloaix/edit.xhtml
Normal file
140
chrome/utils/styloaix/edit.xhtml
Normal file
|
@ -0,0 +1,140 @@
|
|||
<?xml version="1.0"?>
|
||||
|
||||
<!DOCTYPE window>
|
||||
|
||||
<?xml-stylesheet href="chrome://global/skin/" type="text/css"?>
|
||||
<?xml-stylesheet href="chrome://userchromejs/content/styloaix/edit.css" type="text/css"?>
|
||||
<?xml-stylesheet href="chrome://devtools/content/shared/toolbarbutton.css" type="text/css"?>
|
||||
|
||||
<window
|
||||
class="theme-body"
|
||||
id="styloaix-edit"
|
||||
title="StyloaiX Editor"
|
||||
persist="width height screenX screenY sizemode"
|
||||
height="350"
|
||||
xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
|
||||
xmlns:html="http://www.w3.org/1999/xhtml">
|
||||
|
||||
<html:link rel="icon" href="chrome://userchromejs/content/styloaix/16.png"/>
|
||||
<html:link rel="localization" href="toolkit/global/textActions.ftl"/>
|
||||
<html:link rel="localization" href="devtools/client/styleeditor.ftl"/>
|
||||
|
||||
<script src="chrome://userchromejs/content/styloaix/edit.js"/>
|
||||
<script src="chrome://devtools/content/shared/theme-switching.js"/>
|
||||
<script src="chrome://global/content/globalOverlay.js"/>
|
||||
<script src="chrome://global/content/editMenuOverlay.js"/>
|
||||
<script>
|
||||
"use strict";
|
||||
/* import-globals-from ../../../toolkit/content/globalOverlay.js */
|
||||
/* import-globals-from ../../../toolkit/content/editMenuOverlay.js */
|
||||
/* exported goUpdateSourceEditorMenuItems */
|
||||
function goUpdateSourceEditorMenuItems() {
|
||||
goUpdateGlobalEditMenuItems();
|
||||
|
||||
['cmd_undo', 'cmd_redo', 'cmd_cut', 'cmd_paste',
|
||||
'cmd_delete', 'cmd_find', 'cmd_findAgain'].forEach(goUpdateCommand);
|
||||
}
|
||||
</script>
|
||||
|
||||
<popupset id="style-editor-popups">
|
||||
<menupopup id="sourceEditorContextMenu"
|
||||
incontentshell="false"
|
||||
onpopupshowing="goUpdateSourceEditorMenuItems()">
|
||||
<menuitem id="cMenu_undo"
|
||||
data-l10n-id="text-action-undo" command="cmd_undo"/>
|
||||
<menuseparator/>
|
||||
<menuitem id="cMenu_cut"
|
||||
data-l10n-id="text-action-cut" command="cmd_cut"/>
|
||||
<menuitem id="cMenu_copy"
|
||||
data-l10n-id="text-action-copy" command="cmd_copy"/>
|
||||
<menuitem id="cMenu_paste"
|
||||
data-l10n-id="text-action-paste" command="cmd_paste"/>
|
||||
<menuitem id="cMenu_delete"
|
||||
data-l10n-id="text-action-delete" command="cmd_delete"/>
|
||||
<menuseparator/>
|
||||
<menuitem id="cMenu_selectAll"
|
||||
data-l10n-id="text-action-select-all" command="cmd_selectAll"/>
|
||||
<menuseparator/>
|
||||
<menuitem id="se-menu-find" data-l10n-id="styleeditor-find"
|
||||
command="cmd_find"/>
|
||||
<menuitem id="cMenu_findAgain" data-l10n-id="styleeditor-find-again"
|
||||
command="cmd_findAgain"/>
|
||||
<menuseparator/>
|
||||
<menuitem id="se-menu-gotoLine"
|
||||
data-l10n-id="styleeditor-go-to-line"
|
||||
command="cmd_gotoLine"/>
|
||||
</menupopup>
|
||||
<menupopup id="sidebar-context" incontentshell="false">
|
||||
<menuitem id="context-openlinknewtab"
|
||||
data-l10n-id="styleeditor-open-link-new-tab"/>
|
||||
<menuitem id="context-copyurl"
|
||||
data-l10n-id="styleeditor-copy-url"/>
|
||||
</menupopup>
|
||||
</popupset>
|
||||
|
||||
<commandset id="sourceEditorCommands">
|
||||
<command id="cmd_gotoLine" oncommand="goDoCommand('cmd_gotoLine')"/>
|
||||
<command id="cmd_find" oncommand="goDoCommand('cmd_find')"/>
|
||||
<command id="cmd_findAgain" oncommand="goDoCommand('cmd_findAgain')"/>
|
||||
</commandset>
|
||||
|
||||
<keyset id="sourceEditorKeys"/>
|
||||
|
||||
<keyset>
|
||||
<key id="find-key" modifiers="control" key="F" oncommand="sourceEditor.codeMirror.execCommand('find')"/>
|
||||
<key id="save-key" modifiers="control" key="S" oncommand="save()"/>
|
||||
<key id="close-key" modifiers="control" key="W" oncommand="window.close()"/>
|
||||
</keyset>
|
||||
|
||||
<vbox id="main-area" flex="1" class="theme-toolbar">
|
||||
<hbox id="editor-tools">
|
||||
<button class="devtools-toolbarbutton" id="save-button" label="Save" accesskey="S" oncommand="save()" disabled="true"/>
|
||||
<button class="devtools-toolbarbutton" id="preview-button" label="Preview" accesskey="P" oncommand="preview()" disabled="true"/>
|
||||
<button class="devtools-toolbarbutton" id="check-for-errors-button" label="Check for Errors" accesskey="C" oncommand="checkForErrors()"/>
|
||||
|
||||
<vbox class="devtools-separator"></vbox>
|
||||
|
||||
<button class="devtools-toolbarbutton" label="Insert" accesskey="I" type="menu">
|
||||
<menupopup>
|
||||
<menuitem label="Firefox chrome URL" accesskey="F" oncommand="insertCodeAtStart('@-moz-document url(\u0022' + _uc.BROWSERCHROME + '\u0022) {\n\n\n}')"/>
|
||||
<menuitem label="Current Tab URL" accesskey="T" oncommand="insertCodeAtStart('@-moz-document url(\u0022' + Services.wm.getMostRecentBrowserWindow().gBrowser.currentURI.specIgnoringRef + '\u0022) {\n\n\n}')"/>
|
||||
<menuitem label="Current Tab domain" accesskey="D" oncommand="let uri = Services.wm.getMostRecentBrowserWindow().gBrowser.currentURI; let host = uri.asciiHost; insertCodeAtStart('@-moz-document ' + (host ? 'domain' : 'url') + '(\u0022' + (host || uri.currentURI.specIgnoringRef) + '\u0022) {\n\n\n}')"/>
|
||||
<menuitem label="HTML namespace as default" accesskey="H" oncommand="insertCodeAtStart('@namespace url(\u0022http://www.w3.org/1999/xhtml\u0022);')"/>
|
||||
<menuitem label="XUL namespace as default" accesskey="X" oncommand="insertCodeAtStart('@namespace url(\u0022http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul\u0022);');"/>
|
||||
<menuitem label="Chrome folder path" accesskey="C" oncommand="insertCodeAtCaret('resource://userchromejs/')"/>
|
||||
<menuitem label="Data URI…" accesskey="D" oncommand="insertDataURI()"/>
|
||||
</menupopup>
|
||||
</button>
|
||||
<checkbox id="wrap-lines" label="Wrap lines" accesskey="W" oncommand="changeWordWrap(this.checked, event.shiftKey)" tooltiptext="Press holding Shift to save setting" />
|
||||
<checkbox id="instant-preview" label="Instant Preview" accesskey="R" oncommand="instantPreview(this.checked, event.shiftKey)" tooltiptext="Press holding Shift to save setting" />
|
||||
<checkbox id="instant-check" label="Instant Check for Errors" accesskey="T" oncommand="instantCheck(this.checked, event.shiftKey)" tooltiptext="Press holding Shift to save setting" />
|
||||
<spacer flex="1"/>
|
||||
<label>Origin:</label>
|
||||
<menulist id="origin" oncommand="val = Number(this.value); if (origin != val) { lastOrigin = origin; origin = val; changed();}">
|
||||
<menupopup>
|
||||
<menuitem label="AGENT_SHEET" value="0"/>
|
||||
<menuitem label="USER_SHEET" value="1"/>
|
||||
<menuitem label="AUTHOR_SHEET" value="2" selected="true"/>
|
||||
</menupopup>
|
||||
</menulist>
|
||||
</hbox>
|
||||
<separator orient="horizontal" class="devtools-horizontal-splitter"/>
|
||||
<hbox align="center" style="margin-bottom: 4px;">
|
||||
<hbox style="margin-bottom: 2px;">
|
||||
<label control="name" accesskey="N">Name:</label>
|
||||
</hbox>
|
||||
<hbox flex="1">
|
||||
<html:input class="devtools-textinput" id="name"/>
|
||||
</hbox>
|
||||
</hbox>
|
||||
<separator orient="horizontal" class="devtools-horizontal-splitter"/>
|
||||
<deck id="editor" flex="1">
|
||||
<hbox flex="1" style="display: flex; margin: 1px 4px;">
|
||||
<html:textarea id="internal-code" style="width: 100%;box-sizing: border-box; resize: none;"/>
|
||||
</hbox>
|
||||
<hbox id="sourceeditor" flex="1"/>
|
||||
</deck>
|
||||
<vbox class="theme-toolbar" id="errors" style="display:none;"/>
|
||||
</vbox>
|
||||
|
||||
</window>
|
Loading…
Add table
Add a link
Reference in a new issue