Появилась тестовая с вертикальными вкладками. Скачать можно здесь Directory Listing: /pub/firefox/nightly/latest-larch/
Отсутствует
loadSubScript() ведь грузит data: адреса
Да но вроде это не рекомендуемый способ, впрочем сделал так для параметра func,
а для создания объектов ucf_custom_script_win, ucf_custom_script_all_win использовал методы Cu.createObjectIn и Cu.exportFunction,
что там по ним багов не завезли?
Добавь пожалуйста! MJS скрипты нужны
Добавил проверь, только для фоновых скриптов, для оконных то скриптов вроде не требуется.
В параметре module кроме Boolean можно указать массив из строк ["importSymbol"]
это будет доступно в песочнице или так UcfPrefs.customSandbox.importSymbol
PS: Для модулей следует указать полный адрес chrome://user_chrome_files/content/custom_scripts/... в ospath
так как можно же не только из UCF импортировать но и браузера
UPD: впрочем добавил для модулей если вначеле есть %UCFDIR% то будет замена на chrome://user_chrome_files/content/custom_scripts/
Отредактировано Vitaliy V. (23-05-2024 17:33:54)
Отсутствует
рамку окна скрыть
Да вот скриптик для scriptschrome.domload чтобы скрыть совсем, у меня на 11 даже углы закругленные убрались
(async () => { if (AppConstants.platform !== "win") return; var margin = "0,0,0,0"; if (TabsInTitlebar.enabled) document.documentElement.setAttribute("chromemargin", margin); TabsInTitlebar._update = eval(`(${`${TabsInTitlebar._update}`.replace(/^(async\s)?.*?\(/, `$1function ${TabsInTitlebar._update.name}(`) .replace(/\.setAttribute\("chromemargin",\s*"0,2,2,2"\)\;/g, `.setAttribute("chromemargin", "${margin}");`)})`); })();
Отсутствует
Почему при внесении любых изменений в user_chrome.manifest браузер перестает "видеть" UserChromeFiles, как будто его нет вообще? Windows 7, Firefox 88.0.1, UserChromeFiles отсюда.
Отсутствует
Алексей У.
Вариантов несколько, возможно ваш блокнот кодировку ломает или добавляет расширение сохраняемому файлу, синтаксическая ошибка в тексте или вы добавляет свое до первой строки оригинала.
Добавлено 23-05-2024 21:20:29
Vitaliy V.
А хорошо, мне нравится. Спасибо.
Теперь можно свои рамки добавить только там где нужно и только для активного окна.
Отредактировано _zt (23-05-2024 21:20:38)
Отсутствует
В параметре module кроме Boolean можно указать массив из строк ["importSymbol"]
Спасибо! MJS работают, но как подключить mjs скрипт, который должен запускать функцию ?
export {registerUCFTitleChanged, UCFTitleChangedChild}; // Замены в именах вкладок var reg = /^Скачать |-\sПоиск\sв\sGoogle$| \| Форум Mozilla Россия$/; function registerUCFTitleChanged() { var esModuleURI = Components.stack.filename; ChromeUtils.registerWindowActor("UCFTitleChanged", { child: { esModuleURI, events: { DOMTitleChanged: { capture: true }}, }, matches: ["https://*"], messageManagerGroups: ["browsers"], }); } class UCFTitleChangedChild extends JSWindowActorChild { handleEvent(e) { if (reg.test(this.document.title)) this.document.title = this.document.title.replace(reg, ""); } }
На форуме
использовал методы Cu.createObjectIn и Cu.exportFunction,
что там по ним багов не завезли?
Ну, я не прям уж такой мёртвой хваткой вцепившись в багзиллу.
Просто иногда пытаюсь просматривать на предмет отвала.
Так что, более чем запросто, могу что-то пропустить, проворонить.
Поэтому, здесь на меня полагаться не следует.
А скажу так: само существование этих двух методов Cu виднелось,
но каких-то багов о них, лично мне, не попадалось. Вообще.
Кстати, было бы неплохо, если бы был какой-то репортинг
на ошибку исполнения func() добавленных в unloadMap.
А то добавишь кривой деструктор, закроешь окно,
и никакая консоль не подскажет, что деструктор кривой.
А если нет, то можно убрать «e».
То есть, просто catch {} а не catch (e) {}
Вроде, это называется «Optional catch binding».
Такой упрощённый синтаксис завезли с Firefox 58.
Отсутствует
как подключить mjs скрипт, который должен запускать функцию ?
Ну смотря откуда хочешь запускать, можешь так записать
{ ospath: "%UCFDIR%UCFTitleChangedChild.mjs", module: ["registerUCFTitleChanged"], func: "registerUCFTitleChanged();"},
или из другого background скрипта registerUCFTitleChanged();
или из оконного скрипта UcfPrefs.customSandbox.registerUCFTitleChanged();
или сделай как у Dumby типа ChromeUtils.domProcessChild.childID || ...
Кстати, было бы неплохо, если бы был какой-то репортинг
на ошибку исполнения func() добавленных в unloadMap.
Done!
Отсутствует
Вариантов несколько, возможно ваш блокнот кодировку ломает или добавляет расширение сохраняемому файлу, синтаксическая ошибка в тексте или вы добавляет свое до первой строки оригинала.
Если не трудно, можно подробнее по каждому из пунктов.
Отсутствует
Алексей У.
1. Используйте Notepad++ или другой нормальный блокнот
2. Включите отображение зарегистрированных расширений файлов в Windows
3. Не пишите в манифест ничего, кроме данных вам строк
4. Не пишите ничего до первой оригинальной строки
Все остальное можно найти в гугле.
Отсутствует
1. Используйте Notepad++ или другой нормальный блокнот
2. Включите отображение зарегистрированных расширений файлов в Windows
3. Не пишите в манифест ничего, кроме данных вам строк
4. Не пишите ничего до первой оригинальной строки
По пунктам 2-4 все в порядке. Что касается пункта 1, то обнаружил, что стандартный Блокнот предлагает при сохранении следующие варианты кодировки: ANSI, UTF-8, Юникод, Юникод Big Endian. Какую из них выбрать?
P.S. Раньше вообще не обращал внимания на такие тонкости.
Отсутствует
Dumby посиотрите пожалуйста кнопку Check for Addons Updates на работает но при прверке обновлений в консоли пишет
TypeError: Property 'handleEvent' is not callable. browser-custom-element.js:1033:13
construct chrome://global/content/elements/browser-custom-element.js:1033
connectedCallback chrome://global/content/elements/browser-custom-element.js:328
_insertBrowser chrome://browser/content/tabbrowser.js:2296
addTab chrome://browser/content/tabbrowser.js:2695
checkForAddonsUpdates chrome://user_chrome_files/content/custom_scripts/custom_script.js line 197 > Function:625
checkForAddonsUpdates chrome://user_chrome_files/content/custom_scripts/custom_script.js line 197 > Function:970
onclick chrome://user_chrome_files/content/custom_scripts/custom_script.js line 197 > Function:343
(async () => CustomizableUI.createWidget({ label: "Дополнения", id: "ucf-cbbtn-ToggleRestartlessAddons", localized: false, get initCode() { this.event = Object.create(null); delete this.initCode; return this.initCode = Cu.readUTF8URI(Services.io.newURI( "chrome://user_chrome_files/content/custom_scripts/custom_script/toggleRestartlessAddons.js" )); }, get icon() { var icon = "data:image/svg+xml;charset=utf-8,<svg xmlns='http://www.w3.org/2000/svg' width='16' height='16' viewBox='0 0 16 16'><path style='fill:none;stroke:context-fill rgb(39, 174, 129);stroke-opacity:context-fill-opacity;stroke-width:1.2;stroke-linecap:round;stroke-linejoin:round;' d='M12.9 15.3H3.2c-.88 0-1.6-.6-1.6-1.4v-2.7c0-.4.33-.6.74-.6h1.72c.7 0 1.25-.64 1.25-1.2 0-.64-.55-1.15-1.25-1.15H2.34c-.41 0-.74-.32-.74-.68V5.84c0-.81.72-1.48 1.6-1.48h2.36V3.13c0-1.21.93-2.297 2.21-2.419C9.23.57 10.5 1.62 10.5 2.98v1.38h2.4c.9 0 1.5.67 1.5 1.48v8.06c0 .8-.6 1.4-1.5 1.4z'/></svg>"; var subst = this.id.toLowerCase() + "-icon"; Services.io.getProtocolHandler("resource") .QueryInterface(Ci.nsIResProtocolHandler) .setSubstitution(subst, Services.io.newURI(icon)); delete this.icon; return this.icon = "resource://" + subst; }, onCreated(btn) { btn.setAttribute("image", this.icon); new btn.ownerGlobal.Function("self,event,_phase", this.initCode) .call(btn, btn, this.event, "init"); } }))();
// http://infocatcher.ucoz.net/js/cb/toggleRestartlessAddons.js // https://forum.mozilla-russia.org/viewtopic.php?id=57948 // https://github.com/Infocatcher/Custom_Buttons/tree/master/Toggle_Restartless_Add-ons // Toggle Restartless Add-ons button for Custom Buttons // (code for "initialization" section) // Also the code can be used from main window context (as Mouse Gestures code, for example) // Also you can check for add-ons updates using right-click: // copy all code from // https://github.com/Infocatcher/Custom_Buttons/blob/master/Check_for_Addons_Updates/checkForAddonsUpdates.js // after "//== Check for Addons Updates begin" // See "var style = " to modify styles for specific add-ons // (c) Infocatcher 2013-2019 // version 0.1.3pre4 - 2020-01-01 var options = { addonTypes: ["extension", "plugin"], // Possible values: "extension", "plugin" // From extensions: "userstyle" (Stylish), "greasemonkey-user-script" (Greasemonkey), "userscript" (Scriptish) // (swap to reorder in the menu) showVersions: 1, // 0 - don't show versions // 1 - show after name: "Addon Name 1.2" // 2 - show as "acceltext" (in place for hotkey text) showHidden: 0, // 0 - don't show hidden add-ons // -1 - show only enabled hidden add-ons (e.g. to track new items) // 1 - show all hidden add-ons sort: { enabled: 0, clickToPlay: 0, disabled: 1 // Sort order: // 0, 0, 0 - sort add-ons of each type alphabetically // 0, 0, 1 - show enabled add-ons (of each type) first // 0, 1, 2 - enabled add-ons, then click-to-play and then disabled }, closeMenu: false, // Close menu after left-click closeMenuClickToPlay: false // Close menu after left-click, for click to play plugins // Use Shift+click to invert closeMenu* behavior }; var xulns = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"; var mp = document.createElementNS(xulns, "menupopup"); mp.setAttribute("onpopupshowing", "this.updateMenu();"); mp.setAttribute("oncommand", "if(!event.button) this.handleEvent(event);"); // Ignore middle-click in Firefox 89+ mp.setAttribute("onmousedown", "if(event.button == 0) this.handleEvent(event);"); mp.setAttribute("onclick", "if(event.button > 0) this.handleEvent(event);"); mp.setAttribute("oncontextmenu", "return false;"); mp.setAttribute("onpopuphidden", "this.destroyMenu();"); var tb = this.parentNode; if(tb && tb.getAttribute("orient") == "vertical") { // https://addons.mozilla.org/firefox/addon/vertical-toolbar/ var isRight = tb.parentNode.getAttribute("placement") == "right"; mp.setAttribute("position", isRight ? "start_before" : "end_before"); } var cleanupTimer = 0; mp.updateMenu = function() { clearTimeout(cleanupTimer); addStyle(); getRestartlessAddons(options.addonTypes, function(addons) { var df = document.createDocumentFragment(); var prevType; function sortPosition(addon) { if("STATE_ASK_TO_ACTIVATE" in AddonManager && addon.userDisabled == AddonManager.STATE_ASK_TO_ACTIVATE) return options.sort.clickToPlay; if(addon.isActive) return options.sort.enabled; return options.sort.disabled; } function key(addon) { return options.addonTypes.indexOf(addon.type) + "\n" + sortPosition(addon) + "\n" + addon.name.toLowerCase(); } addons.sort(function(a, b) { var ka = key(a); var kb = key(b); return ka == kb ? 0 : ka < kb ? -1 : 1; }).forEach(function(addon) { var type = addon.type; if(prevType && type != prevType) df.appendChild(document.createElementNS(xulns, "menuseparator")); prevType = type; var icon = addon.iconURL || addon.icon64URL; var mi = document.createElementNS(xulns, "menuitem"); mi.className = "menuitem-iconic"; var label = addon.name; if(options.showVersions == 1) label += " " + addon.version; else if(options.showVersions == 2) mi.setAttribute("acceltext", addon.version); mi.setAttribute("label", label); mi.setAttribute("image", icon || mp.icons[type] || ""); if(!icon && mp.icons.useSVG) mi.style.fill = "#15c"; var tip = addon.description || ""; var delay = "delayedStartupAddons" in Services && Services.delayedStartupAddons[addon.id] || null; var isDelayed = delay !== null; mi.classList.toggle("toggleRestartlessAddons-isDelayed", isDelayed); if(isDelayed) tip = "[Delayed Startup: " + delay.toLocaleString() + "]" + (tip ? "\n" + tip : ""); tip && mi.setAttribute("tooltiptext", tip); mi.classList.toggle("toggleRestartlessAddons-isHidden", addon.hidden || false); setDisabled(mi, addon.userDisabled); mi._cbAddon = addon; df.appendChild(mi); }); mp.textContent = ""; mp.appendChild(df); }); }; mp.handleEvent = function(e) { var mi = e.target; if(!("_cbAddon" in mi)) return; var addon = mi._cbAddon; if(e.type == "mousedown") { var closeMenu = isAskToActivateAddon(addon) ? options.closeMenuClickToPlay : options.closeMenu; if(e.shiftKey) closeMenu = !closeMenu; mi.setAttribute("closemenu", closeMenu ? "auto" : "none"); return; } var hasMdf = hasModifier(e); if(e.type == "command" && (!hasMdf || e.shiftKey)) { let newDis = setNewDisabled(addon); setDisabled(mi, newDis); } else if(e.type == "command" && hasMdf || e.type == "click" && e.button == 1) { openAddonPage(addon); closeMenus(mi); } else if(e.type == "click" && e.button == 2) { if(openAddonOptions(addon)) closeMenus(mi); } }; mp.destroyMenu = function() { removeStyle(); clearTimeout(cleanupTimer); cleanupTimer = setTimeout(function() { mp.textContent = ""; }, 5000); }; mp.icons = { get platformVersion() { delete this.platformVersion; return this.platformVersion = parseFloat(Services.appinfo.platformVersion); }, get useSVG() { delete this.useSVG; return this.useSVG = Services.appinfo.name == "Firefox" && this.platformVersion >= 57; }, get plugin() { delete this.plugin; return this.plugin = this.useSVG ? this.platformVersion >= 65 ? "chrome://global/skin/plugins/pluginGeneric.svg" : "chrome://mozapps/skin/plugins/pluginGeneric.svg" : "chrome://mozapps/skin/plugins/pluginGeneric-16.png"; }, get extension() { delete this.extension; return this.extension = this.useSVG ? this.platformVersion >= 76 ? "chrome://mozapps/skin/extensions/extensionGeneric.svg" // Or chrome://mozapps/skin/extensions/extension.svg : "chrome://mozapps/skin/extensions/extensionGeneric-16.svg" : "chrome://mozapps/skin/extensions/extensionGeneric-16.png"; } }; function isAskToActivateAddon(addon) { return addon.type == "plugin" && "STATE_ASK_TO_ACTIVATE" in AddonManager && Services.prefs.getBoolPref("plugins.click_to_play", true); } function setNewDisabled(addon) { var newDis = getNewDisabled(addon); var oldDis = addon.userDisabled; if(Components.interfaces.nsIWebTransportHash) { // random, Fx 123+ var func = function() { func = false; } var thread = Services.tm.currentThread; var meth = newDis ? "disable" : "enable"; addon[meth]({allowSystemAddons: true}).finally(func); while(func) thread.processNextEvent(true); } else try { addon.userDisabled = newDis; } catch(e) { // Error: Cannot disable hidden add-on firefox@getpocket.com _log("Can't set addon.userDisabled to " + newDis + ", error:\n" + e); if(addon.hidden) setNewDisabledRaw(addon, newDis); } var realDis = addon.userDisabled; if(realDis != newDis && addon.type == "extension") { // Firefox 62+? Weird things happens setNewDisabledRaw(addon, newDis); realDis = addon.userDisabled; } if(realDis != newDis) { // We can't enable vulnerable plugins let err = "Can't set addon.userDisabled to " + newDis + ", real value: " + realDis; if(newDis) { _log(err + "\nSTATE_ASK_TO_ACTIVATE not supported?"); newDis = false; } else { _log(err + "\nVulnerable plugin?"); if(oldDis == AddonManager.STATE_ASK_TO_ACTIVATE) newDis = true; else newDis = AddonManager.STATE_ASK_TO_ACTIVATE; } addon.userDisabled = newDis; } ensureSpecialDisabled(addon, newDis); return addon.userDisabled; } function getNewDisabled(addon) { // disabled -> STATE_ASK_TO_ACTIVATE -> enabled -> ... var curDis = addon.userDisabled; var newDis; if("STATE_ASK_TO_ACTIVATE" in AddonManager && curDis == AddonManager.STATE_ASK_TO_ACTIVATE) newDis = false; else if(!curDis) newDis = true; else { if(isAskToActivateAddon(addon)) newDis = AddonManager.STATE_ASK_TO_ACTIVATE; else newDis = false; } return newDis; } function setNewDisabledRaw(addon, newDis) { _log("Let's try set addon.userDisabled using raw hack"); if("lazy" in g) g = g.lazy; if("XPIDatabase" in g && "updateAddonDisabledState" in g.XPIDatabase) { // Firefox 61+ let rawAddon = g.XPIDatabase.getAddons().find(function(rawAddon) { return rawAddon.id == addon.id; }); g.XPIDatabase.updateAddonDisabledState( rawAddon, g.XPIDatabase.updateAddonDisabledState.length == 1 // Firefox 74+ ? { userDisabled: newDis } : newDis ); } else if("eval" in g) { // See "set userDisabled(val)" let addonFor = g.eval("addonFor"); let rawAddon = addonFor(addon); //rawAddon.userDisabled = newDis; g.XPIProvider.updateAddonDisabledState(rawAddon, newDis); } else { // Firefox 57+? See https://forum.mozilla-russia.org/viewtopic.php?pid=745272#p745272 updateAddonDisabledState(addon, newDis); } } function updateAddonDisabledState(addon, newDis) { var key = "_cbToggleRestartlessAddonsData"; var url = URL.createObjectURL(new Blob([ "XPIProvider.updateAddonDisabledState(addonFor(this." + key + "[0]), this." + key + "[1]); delete this." + key + ";" ])); addDestructor(function() { URL.revokeObjectURL(url); }); (updateAddonDisabledState = function(addon, newDis) { nsvo[key] = [addon, newDis]; Services.scriptloader.loadSubScript(url, nsvo); })(addon, newDis); } function setDisabled(mi, disabled) { var askToActivate = "STATE_ASK_TO_ACTIVATE" in AddonManager && disabled == AddonManager.STATE_ASK_TO_ACTIVATE; var cl = mi.classList; cl.toggle("toggleRestartlessAddons-askToActivate", askToActivate); cl.toggle("toggleRestartlessAddons-disabled", disabled && !askToActivate); } function ensureSpecialDisabled(addon, newDis) { if(addon.id == "screenshots@mozilla.org") Services.prefs.setBoolPref("extensions.screenshots.disabled", newDis); } if( this instanceof XULElement // Custom Buttons && typeof event == "object" && !("type" in event) && typeof _phase == "string" && _phase == "init" // Initialization ) { this.type = "menu"; this.orient = "horizontal"; this.appendChild(mp); this.onmouseover = function(e) { if(e.target != this) return; Array.prototype.some.call( this.parentNode.getElementsByTagName("*"), function(node) { if( node != this && node.namespaceURI == xulns // See https://github.com/Infocatcher/Custom_Buttons/issues/28 //&& node.boxObject //&& node.boxObject instanceof Components.interfaces.nsIMenuBoxObject && "open" in node && node.open && node.getElementsByTagName("menupopup").length ) { node.open = false; this.open = true; return true; } return false; }, this ); }; this.onmousedown = function(e) { if(e.target == this && e.button == 0 && hasModifier(e)) e.preventDefault(); }; this.oncontextmenu = function(e) { if(e.target == this && !hasModifier(e) && hasUpdater()) e.preventDefault(); }; this.onclick = function(e) { if(e.target != this) return; if(e.button == 0 && hasModifier(e) || e.button == 1) openAddonsManager(); else if(e.button == 2 && !hasModifier(e) && hasUpdater()) checkForAddonsUpdates.call(this); }; } else { // Mouse gestures or something other... let e; if(typeof event == "object" && event instanceof Event && "screenX" in event) // FireGestures e = event; else if( this instanceof Components.interfaces.nsIDOMChromeWindow && "mgGestureState" in window && "endEvent" in mgGestureState // Mouse Gestures Redox ) e = mgGestureState.endEvent; else { let anchor = this instanceof XULElement && this || window.gBrowser && gBrowser.selectedBrowser || document.documentElement; if("boxObject" in anchor) { let bo = anchor.boxObject; e = { screenX: bo.screenX, screenY: bo.screenY }; if(this instanceof XULElement) e.screenY += bo.height; } } if(!e || !("screenX" in e)) throw new Error("[Toggle Restartless Add-ons]: Can't get event object"); document.documentElement.appendChild(mp); mp.addEventListener("popuphidden", function destroy(e) { mp.removeEventListener(e.type, destroy, false); setTimeout(function() { mp.destroyMenu(); mp.parentNode.removeChild(mp); }, 0); }, false); mp.openPopupAtScreen(e.screenX, e.screenY); } function getRestartlessAddons(addonTypes, callback, context) { if(!("AddonManager" in window)); var then, promise = AddonManager.getAddonsByTypes(addonTypes, then = function(addons) { callback.call(context, addons.filter(function(addon) { var ops = addon.operationsRequiringRestart; return !addon.appDisabled && !(ops & AddonManager.OP_NEEDS_RESTART_ENABLE || ops & AddonManager.OP_NEEDS_RESTART_DISABLE) && ( !addon.hidden || options.showHidden > 0 || options.showHidden == -1 && !addon.userDisabled ) && (addon.iconURL || "").substr(0, 29) != "resource://search-extensions/"; })); }); promise && typeof promise.then == "function" && promise.then(then, Components.utils.reportError); // Firefox 61+ } function openAddonOptions(addon) { // Based on code from chrome://mozapps/content/extensions/extensions.js // Firefox 21.0a1 (2013-01-27) var optionsURL = addon.optionsURL; if(!addon.isActive || !optionsURL) return false; if(addon.type == "plugin") // No options for now! return false; if( addon.optionsType == (AddonManager.OPTIONS_TYPE_INLINE || NaN) || addon.optionsType == (AddonManager.OPTIONS_TYPE_INLINE_INFO || NaN) || addon.optionsType == (AddonManager.OPTIONS_TYPE_INLINE_BROWSER || NaN) ) openAddonPage(addon, true); else if(addon.optionsType == AddonManager.OPTIONS_TYPE_TAB && "switchToTabHavingURI" in window) switchToTabHavingURI(optionsURL, true); else { let windows = Services.wm.getEnumerator(null); while(windows.hasMoreElements()) { let win = windows.getNext(); if(win.document.documentURI == optionsURL) { win.focus(); return true; } } // Note: original code checks browser.preferences.instantApply and may open modal windows window.openDialog(optionsURL, "", "chrome,titlebar,toolbar,centerscreen,dialog=no"); } return true; } function openAddonsManager(view) { var openAddonsMgr = window.BrowserOpenAddonsMgr // Firefox || window.openAddonsMgr // Thunderbird || window.toEM; // SeaMonkey openAddonsMgr(view); } function openAddonPage(addon, scrollToPreferences) { var platformVersion = parseFloat( Services.appinfo.name == "Pale Moon" ? Services.appinfo.version : Services.appinfo.platformVersion ); scrollToPreferences = scrollToPreferences && platformVersion >= 12 ? "/preferences" : ""; openAddonsManager("addons://detail/" + encodeURIComponent(addon.id) + scrollToPreferences); } function hasModifier(e) { return e.ctrlKey || e.shiftKey || e.altKey || e.metaKey; } function addStyle() { if(addStyle.hasOwnProperty("_style")) return; var style = '\ .toggleRestartlessAddons-isDelayed > .menu-iconic-text {\n\ opacity: 0.75;\n\ color: #070;\n\ }\n\ .toggleRestartlessAddons-isHidden > .menu-iconic-text {\n\ color: #609;\n\ }\n\ .toggleRestartlessAddons-disabled > .menu-iconic-left {\n\ opacity: 0.4;\n\ }\n\ .toggleRestartlessAddons-disabled > .menu-iconic-text,\n\ .toggleRestartlessAddons-disabled > .menu-accel-container {\n\ opacity: 0.5;\n\ }\n\ .toggleRestartlessAddons-askToActivate {\n\ color: -moz-nativehyperlinktext;\n\ }'; addStyle._style = document.insertBefore( document.createProcessingInstruction( "xml-stylesheet", 'href="' + "data:text/css," + encodeURIComponent(style) + '" type="text/css"' ), document.documentElement ); } function removeStyle() { if(!addStyle.hasOwnProperty("_style")) return; var s = addStyle._style; s.parentNode.removeChild(s); delete addStyle._style; } function closeMenus(node) { // Based on function closeMenus from chrome://browser/content/utilityOverlay.js for(; node && "tagName" in node; node = node.parentNode) { if( node.namespaceURI == "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" && (node.localName == "menupopup" || node.localName == "popup") ) node.hidePopup(); } } function _log(s) { if(typeof LOG == "function") // Custom Buttons LOG(s); else // Or something else Services.console.logStringMessage("Toggle Restartless Add-ons: " + s); } function hasUpdater() { var has = checkForAddonsUpdates.toString().indexOf("about:addons") != -1; hasUpdater = function() { return has; }; return has; } function checkForAddonsUpdates() { //== Check for Addons Updates begin // http://infocatcher.ucoz.net/js/cb/checkForAddonsUpdates.js // https://forum.mozilla-russia.org/viewtopic.php?id=57958 // https://github.com/Infocatcher/Custom_Buttons/tree/master/Check_for_Addons_Updates // Check for Addons Updates button for Custom Buttons // (code for "code" section) // (c) Infocatcher 2012-2021 // version 0.1.6pre4 - 2021-03-28 // Button just open hidden tab with about:addons and trigger built-in "Check for Updates" function. // And show tab, if found updates. (function() { var btn = this instanceof XULElement ? this : { // Launched not from custom button image: "", // Base64-encoded icon (if empty, will be used "imgLoading") label: "Check for Addons Updates", tooltipText: "" }; if("_cb_disabled" in btn) return; btn._cb_disabled = true; var app = Services.appinfo.name; var pv = parseFloat(Services.appinfo.platformVersion); var ADDONS_URL = "about:addons"; var progressIcon = new ProgressIcon(btn); var image = btn.image || progressIcon.imgLoading; var tip = btn.tooltipText; btn.tooltipText = "Open " + ADDONS_URL + "…"; var tab, browser, gBrowser; var tbTabInfo, tbTab; var trgWindow = Services.wm.getMostRecentWindow("navigator:browser") || app == "Thunderbird" && Services.wm.getMostRecentWindow("mail:3pane") || window; var trgDocument = trgWindow.document; var tabmail = trgDocument.getElementById("tabmail"); if(tabmail && app == "Thunderbird") { // Note: SeaMonkey doesn't support content tabs in mail window let addonsWin; let receivePong = function(subject, topic, data) { addonsWin = subject; }; Services.obs.addObserver(receivePong, "EM-pong", false); Services.obs.notifyObservers(null, "EM-ping", ""); Services.obs.removeObserver(receivePong, "EM-pong"); if(addonsWin) { let rootWindow = addonsWin .QueryInterface(Components.interfaces.nsIInterfaceRequestor) .getInterface(Components.interfaces.nsIWebNavigation) .QueryInterface(Components.interfaces.nsIDocShellTreeItem) .rootTreeItem .QueryInterface(Components.interfaces.nsIInterfaceRequestor) .getInterface(Components.interfaces.nsIDOMWindow); tabmail = rootWindow.document.getElementById("tabmail"); tbTabInfo = tabmail.getBrowserForDocument(addonsWin); tbTab = tab = tbTabInfo.tabNode; processAddonsTab(addonsWin); } else { Services.obs.addObserver(function observer(subject, topic, data) { Services.obs.removeObserver(observer, topic); if(subject.document.readyState == "complete") processAddonsTab(subject); else { subject.addEventListener("load", function onLoad(e) { subject.removeEventListener(e.type, onLoad, false); processAddonsTab(subject); }, false); } }, "EM-loaded", false); // See openAddonsMgr() -> openContentTab() tbTabInfo = tabmail.openTab("contentTab", { contentPage: ADDONS_URL, clickHandler: "specialTabs.siteClickHandler(event, /addons\.mozilla\.org/);", background: true }); tbTab = tab = tbTabInfo.tabNode; tbTab.collapsed = true; // Note: dontSelectHiddenTab() not implemented } } else if("gBrowser" in trgWindow && trgWindow.gBrowser.tabs) { let isPending = false; let ws = Services.wm.getEnumerator("navigator:browser"); windowsLoop: while(ws.hasMoreElements()) { let w = ws.getNext(); let tabs = w.gBrowser.tabs; for(let i = 0, l = tabs.length; i < l; ++i) { let t = tabs[i]; if( !t.closing && t.linkedBrowser && t.linkedBrowser.currentURI.spec == ADDONS_URL ) { tab = t; break windowsLoop; } } } gBrowser = trgWindow.gBrowser; if(!tab) { tab = gBrowser.addTab(ADDONS_URL, { triggeringPrincipal: "Services" in window // Firefox 63+ && Services.scriptSecurityManager && Services.scriptSecurityManager.getSystemPrincipal() }); tab.collapsed = true; tab.closing = true; // See "visibleTabs" getter in chrome://browser/content/tabbrowser.xml trgWindow.addEventListener("TabSelect", dontSelectHiddenTab, false); } else if( tab.getAttribute("pending") == "true" // Gecko >= 9.0 || tab.linkedBrowser.contentDocument.readyState == "uninitialized" // || tab.linkedBrowser.__SS_restoreState == 1 ) isPending = true; browser = tab.linkedBrowser; if( isPending || browser.webProgress.isLoadingDocument || browser.currentURI.spec == "about:blank" // Firefox 79+ ) { browser.addEventListener("load", processAddonsTab, true); if(isPending) { if(pv >= 41) { // Workaround to correctly restore pending tab // See https://github.com/Infocatcher/Custom_Buttons/issues/39 let selTab = gBrowser.selectedTab; gBrowser.selectedTab = tab; gBrowser.selectedTab = selTab; } else { browser.reload(); } } } else { processAddonsTab(); } } else { progressIcon.restore(); btn.tooltipText = tip; delete btn._cb_disabled; Services.prompt.alert(window, btn.label, "Error: Can't find supported window!"); return; } function processAddonsTab(e, again) { var doc; if(e && e instanceof Components.interfaces.nsIDOMWindow) { doc = e.document; } else if(e) { doc = e.target; if(doc.location != ADDONS_URL) return; browser.removeEventListener(e.type, processAddonsTab, true); } else { doc = browser.contentDocument; } btn.tooltipText = "Process " + ADDONS_URL + "…"; progressIcon.loading(); var origAttr = "_cb_checkForAddonsUpdates_origImage"; if(!tab.hasAttribute(origAttr)) { var link = doc.querySelector('link[rel="shortcut icon"]'); // Not loaded yet? tab.setAttribute(origAttr, link && link.href || tab.image); } tab.image = image; var fu = $("cmd_findAllUpdates"); if(!fu) { // Firefox 72+ var win = doc.defaultView; var vb = doc.getElementById("html-view-browser"); if(!vb) { if(!HTMLHtmlElement.isInstance(doc.documentElement)) { // Firefox 87+ win.setTimeout(processAddonsTab, 20, win); return; } vb = browser; } if(!again) { // Strange errors happens // chrome://mozapps/content/extensions/aboutaddons.js // getTelemetryViewName() -> el.closest(...) is null win.setTimeout(processAddonsTab, 20, win, true); return; } var vbDoc = vb.contentDocument; fu = vbDoc.querySelector('[action="check-for-updates"]'); var um = vbDoc.getElementById("updates-message"); } var notFound = $("updates-noneFound") || { get hidden() { return um.getAttribute("state") != "none-found"; } }; var updated = $("updates-installed") || { get hidden() { return um.getAttribute("state") != "installed"; } }; // Avoid getting false results from the past update check (may not be required for "noneFound") if(um) { // Firefox 72+ um.hidden = true; um.removeAttribute("state"); } else { notFound.hidden = updated.hidden = true; } //fu.doCommand(); fu.click(); function localize(node, key, callback) { if(um) { // Firefox 72+ doc.l10n.formatValue(key).then(function(s) { callback(s || key); }, Components.utils.reportError); return; } callback(node.getAttribute("value") || key); } var inProgress = $("updates-progress") || { get hidden() { return um.getAttribute("state") != "updating"; } }; localize(inProgress, "addon-updates-updating", function(s) { btn.tooltipText = s; }); var waitTimer = setInterval(function() { if(!doc.defaultView || doc.defaultView.closed) { stopWait(); notify("Tab with add-ons manager was closed!"); return; } if(!inProgress.hidden) return; var autoUpdate = $("utils-autoUpdateDefault") || vbDoc.querySelector('[action="set-update-automatically"]'); var autoUpdateChecked = autoUpdate.getAttribute("checked") == "true" || autoUpdate.checked; var found = $("updates-manualUpdatesFound-btn") || { get hidden() { return um.getAttribute("state") != "manual-updates-found"; } }; if( autoUpdateChecked ? notFound.hidden && updated.hidden : notFound.hidden && found.hidden ) // Too early? return; stopWait(); if(!tbTab) tab.closing = false; function removeTab() { if(!tab.collapsed) return; if(tbTab) { tabmail.closeTab(tbTabInfo, true /*aNoUndo*/); return; } gBrowser.removeTab(tab); (function forgetClosedTab(isSecondTry) { var ss = "nsISessionStore" in Components.interfaces ? ( Components.classes["@mozilla.org/browser/sessionstore;1"] || Components.classes["@mozilla.org/suite/sessionstore;1"] ).getService(Components.interfaces.nsISessionStore) : trgWindow.SessionStore; // Firefox 61+ https://bugzilla.mozilla.org/show_bug.cgi?id=1450559 if(!("forgetClosedTab" in ss)) return; var closedTabs = (ss.getClosedTabDataForWindow(window)); for(let i = 0, l = closedTabs.length; i < l; ++i) { let closedTab = closedTabs[i]; let state = closedTab.state; if(state.entries[state.index - 1].url == ADDONS_URL) { ss.forgetClosedTab(window, i); return; } } if(!isSecondTry) // May be needed in SeaMonkey setTimeout(forgetClosedTab, 0, true); })(); } if(!notFound.hidden) { removeTab(); localize(notFound, "addon-updates-none-found", function(s) { notify(s); }); return; } if(autoUpdateChecked) { removeTab(); localize(updated, "addon-updates-installed", function(s) { notify(s); }); return; } tab.collapsed = false; var cats = $("categories"); var upds = $("category-availableUpdates"); if(cats && upds) { if(vb && cats.selectedItem == upds) // Only for Firefox 72+ cats.selectedItem = $("category-extension"); // Trick to force update cats.selectedItem = upds; } else { // Firefox 76+ ? vbDoc.querySelector('.category[name="available-updates"]').click(); } var tabWin = tab.ownerDocument.defaultView; if(tbTab) tabmail.switchToTab(tbTabInfo); else tabWin.gBrowser.selectedTab = tab; setTimeout(function() { tabWin.focus(); doc.defaultView.focus(); var al = $("addon-list") || vb; al.focus(); }, 0); }, 50); function $(id) { return doc.getElementById(id); } function stopWait() { clearInterval(waitTimer); progressIcon.restore(); btn.tooltipText = tip; if(tab.image == image) tab.image = tab.getAttribute(origAttr); tab.removeAttribute(origAttr); trgWindow.removeEventListener("TabSelect", dontSelectHiddenTab, false); setTimeout(function() { delete btn._cb_disabled; }, 500); } function notify(msg) { Components.classes["@mozilla.org/alerts-service;1"] .getService(Components.interfaces.nsIAlertsService) .showAlertNotification( app == "Firefox" && pv >= 57 ? "chrome://mozapps/skin/extensions/extensionGeneric.svg" : "chrome://mozapps/skin/extensions/extensionGeneric.png", btn.label, msg, false, "", null ); } } function dontSelectHiddenTab(e) { // <tab /><tab collapsed="true" /> // Close first tab: collapsed tab becomes selected var trgTab = e.originalTarget || e.target; if(trgTab != tab) return; if(/\n(?:BrowserOpenAddonsMgr|toEM)@chrome:\/\//.test(new Error().stack)) { // User open Add-ons Manager, show tab trgWindow.removeEventListener("TabSelect", dontSelectHiddenTab, false); setTimeout(function() { // Hidden tab can't be selected, so select it manually... tab.collapsed = tab.closing = false; gBrowser.selectedTab = tab; }, 0); } function done(t) { if(!t.hidden && !t.closing) { e.preventDefault(); e.stopPropagation(); return gBrowser.selectedTab = t; } return false; } for(var t = tab.nextSibling; t; t = t.nextSibling) if(done(t)) return; for(var t = tab.previousSibling; t; t = t.previousSibling) if(done(t)) return; } function ProgressIcon(btn) { var app = Services.appinfo.name; var pv = parseFloat(Services.appinfo.platformVersion); if(app == "SeaMonkey") this.imgConnecting = this.imgLoading = "chrome://communicator/skin/icons/loading.gif"; else if(app == "Thunderbird") { this.imgConnecting = "chrome://messenger/skin/icons/connecting.png"; this.imgLoading = "chrome://messenger/skin/icons/loading.png"; } else { this.imgConnecting = app == "Firefox" && pv >= 58 ? "chrome://browser/skin/tabbrowser/tab-connecting.png" : "chrome://browser/skin/tabbrowser/connecting.png"; this.imgLoading = app == "Firefox" && pv >= 48 ? "chrome://global/skin/icons/loading.png" : "chrome://browser/skin/tabbrowser/loading.png"; } if(!(btn instanceof XULElement)) { this.loading = this.restore = function() {}; return; } var useAnimation = app == "Firefox" && pv >= 32 && pv < 48; var btnIcon = btn.icon || btn.ownerDocument.getAnonymousElementByAttribute(btn, "class", "toolbarbutton-icon"); var origIcon = btnIcon.src; btnIcon.src = this.imgConnecting; if(useAnimation) { let cs = btnIcon.ownerDocument.defaultView.getComputedStyle(btnIcon, null); let s = btnIcon.style; s.margin = [cs.marginTop, cs.marginRight, cs.marginBottom, cs.marginLeft].join(" "); s.padding = [cs.paddingTop, cs.paddingRight, cs.paddingBottom, cs.paddingLeft].join(" "); s.width = cs.width; s.height = cs.height; s.boxShadow = "none"; s.borderColor = s.background = "transparent"; btnIcon.setAttribute("fadein", "true"); btnIcon.setAttribute("busy", "true"); btnIcon.classList.add("tab-throbber"); btnIcon._restore = function() { delete btnIcon._restore; btnIcon.removeAttribute("busy"); btnIcon.removeAttribute("progress"); setTimeout(function() { btnIcon.classList.remove("tab-throbber"); btnIcon.removeAttribute("style"); btnIcon.removeAttribute("fadein"); }, 0); }; } this.loading = function() { btnIcon.src = this.imgLoading; if(useAnimation) btnIcon.setAttribute("progress", "true"); }; this.restore = function() { btnIcon.src = origIcon; if(useAnimation) btnIcon._restore(); }; } }).call(this); //== Check for Addons Updates end } this.tooltipText = "Переключатель джетпаков" + "\nПКМ – проверить обновления" + "\nСКМ – открыть страницу дополнений" + "\nShift+ПКМ – меню кнопки" + "\n\nВ меню: \nЛКМ – включить/выключить дополнение без закрытия меню" + "\nShift+ЛКМ – включить/выключить дополнение" + "\nСКМ – открыть страницу дополнения в управлении дополнениями" + "\nПКМ – открыть настройки дополнения (если есть)"; // Autoopen/close feature var openDelay = 200; var closeDelay = 350; var _openTimer = 0; var _closeTimer = 0; this.onmouseover = function(e) { clearTimeout(_closeTimer); if(e.target == this && closeOtherMenus()) { this.open = true; return; } _openTimer = setTimeout(function() { self.open = true; }, openDelay); }; this.onmouseout = function(e) { clearTimeout(_openTimer); _closeTimer = setTimeout(function() { if(!isContextOpened()) self.open = false; }, closeDelay); }; function closeOtherMenus() { return Array.prototype.some.call( self.parentNode.getElementsByTagName("*"), function(node) { if( node != self && node.namespaceURI == xulns // See https://github.com/Infocatcher/Custom_Buttons/issues/28 //&& node.boxObject //&& node.boxObject instanceof Components.interfaces.nsIMenuBoxObject && "open" in node && node.open && node.getElementsByTagName("menupopup").length ) { node.open = false; return true; } return false; } ); } function isContextOpened() { return inBtn(document.popupNode); } function inBtn(node) { for(; node; node = node.parentNode) if(node == self) return true; return false; }
Отсутствует
Доработал ucf_hookClicks.js и исправил неоткрытие менюшек в многооконом режиме.
Сделал две настраиваемых команды в Меню пользователя, которое открывается правым кликом на unified-extensions и add-ons-button.
Ещё при наведении на них мыши показывается содержимое буфера обмена.
Можно задать имена строк меню и js-код, сохраняемые при рестарте .
По правому клику на 2-х последних строках открывается опция about:config с вашими командами или строкой по-умолчанию.
Дополнил подсказки – на кнопках, строках меню и в статусе достаточно подробные подсказки.
На форуме
Done!
Похоже, здесь не всё так просто.
Добавил в custom_script_win.js строку
ucf_custom_script_win.setUnloadMap("bbbla", () => bbbla());
Рестарт, Ctrl+N, Alt+F4 — и получаю в консоли две записи
«can't access property "destructor", this.ucfo[key] is undefined»
и «bbbla is not defined».
Немного странно что две, но вполне приемлимо.
Больше репортинга — скорее понуждает к исправлению кривого деструктора.
Иначе говоря — две записи лучше, чем ноль.
Но, рассмотрим такой код:
((key, ucf) => { ucf[key] = { destructor() { bbbla(); } }; ucf.unloadlisteners.push(key); })("bbbla", ucf_custom_script_win);
То есть, тут получается, что кривой деструктор вызывается дважды.
А это уже нехорошо.
Dumby посиотрите пожалуйста кнопку Check for Addons Updates на работает но при прверке обновлений в консоли пишет
TypeError: Property 'handleEvent' is not callable. browser-custom-element.js:1033:13
Попробовал посмотреть на 128. Жму ПКМ — в консоли куча всяких
«addons.update-checker WARN HTTP Request failed for an unknown reason»
но, в моём случае, это совершенно нормально и ожидаемо.
А вот такой странной ошибки как у тебя — увидеть не смог.
Скажу так: если для воспроизводства нужен выход в сеть,
то тогда, увы, здесь я ничем помочь не смогу.
Отсутствует
egorsemenov06 пишетDumby посиотрите пожалуйста кнопку Check for Addons Updates на работает но при прверке обновлений в консоли пишет
TypeError: Property 'handleEvent' is not callable. browser-custom-element.js:1033:13Попробовал посмотреть на 128. Жму ПКМ — в консоли куча всяких
«addons.update-checker WARN HTTP Request failed for an unknown reason»
но, в моём случае, это совершенно нормально и ожидаемо.
А вот такой странной ошибки как у тебя — увидеть не смог.
Скажу так: если для воспроизводства нужен выход в сеть,
то тогда, увы, здесь я ничем помочь не смогу.
очень жаль.но в любом случае спасибо!!!
Отсутствует
получается, что кривой деструктор вызывается дважды.
А это уже нехорошо.
А чего такого нехорошего, ну вызвало два раза на что это повлияет, на скорость закрытия окна, вряд ли.
Кстати а зачем нужны кривые деструкторы, и потом вот эту часть в user_chrome.js
try { this.ucfo[key].destructor(); } catch (e) {Cu.reportError(e);}
можно было вообще не добавлять, но например https://forum.mozilla-russia.org/viewto … 44#p806144
destructor не существует когда добавляется unloadlisteners.push
TypeError: Property 'handleEvent' is not callable
А это похоже моя ошибка, но кроме мусора ни на что не влияет, можно добавить пока не обновил UCF
в user_chrome/StylesScriptsChild.mjs
export class UcfCustomStylesScriptsChild extends JSWindowActorChild {
actorCreated() {
this.handleEvent = () => {}
...
Отредактировано Vitaliy V. (25-05-2024 13:39:13)
Отсутствует
TypeError: Property 'handleEvent' is not callable
А это похоже моя ошибка, но кроме мусора ни на что не влияет, можно добавить пока не обновил UCF
Ещё SaveHTML.mjs при сохранении страницы about:newtab эту же ошибку выводит в консоль.
На форуме
А чего такого нехорошего, ну вызвало два раза на что это повлияет
Ну, например, код, который идёт до строки с ошибкой,
делает нечто такое, чего не должен делать второй раз.
Хотя да, это я себя уже слишком накручиваю.
Кстати а зачем нужны кривые деструкторы
Как зачем? В тестовых целях, посмотреть что случится.
destructor не существует когда добавляется unloadlisteners.push
Может проверять как-то так, даже не знаю
/* try { val.func.apply(val.context); } catch (e) { try { this.ucfo[key].destructor(); } catch (e) {Cu.reportError(e);} Cu.reportError(e); } */ if (val.func) try {val.func.apply(val.context);} catch(ex) {Cu.reportError(ex);} else if (this.ucfo[key]?.destructor) try {this.ucfo[key].destructor();} catch(ex) {Cu.reportError(ex);} else Cu.reportError("Missing destructor for key " + key);
Отсутствует
egorsemenov06 пишетTypeError: Property 'handleEvent' is not callable
А это похоже моя ошибка, но кроме мусора ни на что не влияет, можно добавить пока не обновил UCF
в user_chrome/StylesScriptsChild.mjs
export class UcfCustomStylesScriptsChild extends JSWindowActorChild {
actorCreated() {
this.handleEvent = () => {}
...
Спасибо!тогда лучше подождать обновление UCF
Отсутствует
Ещё SaveHTML.mjs при сохранении страницы about:newtab эту же ошибку выводит в консоль
Да, если включены стили, скрипты для контента, как убрать ошибку в посте выше.
Может проверять как-то так
Или так, сообщение об ошибке, побуждает её исправить, ну и заменить на setUnloadMap
try { val.func.apply(val.context); } catch (e) { if (!val.func) try { this.ucfo[key].destructor(); } catch (e) {Cu.reportError(e);} Cu.reportError(e); }
Отредактировано Vitaliy V. (26-05-2024 00:56:37)
Отсутствует
Подскажите, пожалуйста, нет ли скрипта (или любого другого способа) вернуть системный вид скроллбаров на ВСЕХ сайтах?
Отсутствует
https://github.com/Aris-t2/CustomJSforFx/blob/master/scripts/old/custom_scrollbars_fx112.uc.js
Это нужно в custom_script_win.js добавить?
Отсутствует
Добавляю текст буфера обмена к подсказке url в строке адреса, но получается изменить только tooltip рамки. Там «aHTMLTooltip» и на http без шифрования подсказки адреса нет.
Помогите сделать правильно! (включая подсказку для http://…… сайтов)
(async id => { // добавить текст буфера обмена к подсказке url в строке адреса var listener = { // изменить aHTMLTooltip handleEvent(e){ let trg = e.target, clip = readFromClip(); // let box = document.getElementById("urlbar-input-container").childNodes[6]; //"urlbar-input-box" trg.tooltipText = gBrowser.currentURI.spec + "\n\nбуфер обмена (текст)\n" + crop(clip, 88 ,'…\n', 1); console.log(trg.tooltipText); }, } var events = ["mouseenter"], els = document.getElementById("urlbar-input"); els.addEventListener(events,listener,true); ucf_custom_script_win.unloadlisteners.push(id); ucf_custom_script_win[id] = { destructor(){ el.removeEventListener(events,listener,true); } } var addDestructor = nextDestructor => { var {destructor} = ucf_custom_script_win[id]; ucf_custom_script_win[id].destructor =()=> { try {destructor();} catch(ex){Cu.reportError(ex)} nextDestructor(); } } var readFromClip = ({clipboard} = Services, data = {}) => { try {let trans = Cc["@mozilla.org/widget/transferable;1"].createInstance(Ci.nsITransferable), flavor = `text/${parseInt(Services.appinfo.platformVersion) >= 111 ? "plain" : "unicode"}`; trans.init(docShell.QueryInterface(Ci.nsILoadContext)); trans.addDataFlavor(flavor); clipboard.getData(trans, clipboard.kGlobalClipboard); trans.getTransferData(flavor, data); if (data.value) return data.value.QueryInterface(Ci.nsISupportsString).data; } catch {return ""} }, crop = (z = "",cut = 30,ch = '…\n',g = 0) => { //обрезать/разбить текст z = z.match(new RegExp('.{1,'+ cut +'}','g')); cut = z.slice(-1); return g ? z.join(ch) : z[0] == cut ? z[0] : z[0] + ch +'…'+ cut; } })("ucf_clipboard_in_url");
Отредактировано Dobrov (26-05-2024 06:18:30)
На форуме