const labelMap = {
'Assets': 'Ассеты'
}
const textMap = {
'Copy link': 'Копировать ссылку',
'Line tool': 'Линия',
'Line': 'Линия',
'Lock/Unlock': 'Заблокировать/Разблокировать',
'Use as mask': 'Использовать как маску',
}
/**
interface Config {
[selector: string]: {
text:
string
| undefined
| ((oldVal: string, parentElement: Element, textNode?: Text) => string | undefined);
'<attribute-name>':
string
| undefined
| null
| { [key: string]: any }
| ((oldVal: string, parentElement: HTMLElement) => string | undefined | null);
}
}
selector - css селектор
text - текст в элементе
значения:
function - вызывает функцию и использует возвращаемое значение
string - устанавливает текст в элементе
undefined - ничего не делает
attribute - любой атрибут
значения:
function - вызывает функцию и использует возвращаемое значение
string - устанавливает значение атрибута
null - удаляет атрибут
object - присваевает значения полей значениям соответствующих полей свойства
undefined - ничего не делает
**/
const selectorMap = {
'[data-label="Layers"]': {
'data-label': 'Слои'
},
'[data-label]': {
'data-label': (oldVal, element) => labelMap[oldVal]
},
'*:not(script, style, link, meta)': {
'text': (oldVal, parentElement, textNode) => textMap[oldVal]
},
'[for="frame-mask-disabled-checkbox"]': {
'style': 'width: 150px;line-height: 15px;',
},
'[class^="toolbar_view--shareButton--"]': {
'style': 'width: 90px;',
},
'[class^="raw_components--panelTitle--"]': {
'style': 'text-transform: none;',
},
'[class^="select--dropdownContainer--"]': {
'style': {
'width': "250px"
}
}
}
function matchAndProcess (el, textNode) {
Object.entries(selectorMap).forEach(
([selector, config]) => el.matches(selector) && processElement(config, el, textNode)
);
}
function queryAndProcess (el) {
Object.entries(selectorMap).forEach(([selector, config]) => {
if (el.nodeType === 3) {
const { parentNode } = el;
if (parentNode?.matches(selector))
processElement(config, parentNode, el);
} else {
el.querySelectorAll(selector).forEach(el => processTextNodes(config, el));
if (el.matches(selector))
processTextNodes(config, el);
}
});
}
function processTextNodes (config, el) {
if ('text' in config) {
const textNodes = Array.prototype.filter.call(
el.childNodes,
node => node.nodeType === 3 && node.data.trim()
);
if (textNodes.length)
return textNodes.forEach(textNode => processElement(config, el, textNode));
}
processElement(config, el);
}
function processElement (config, el, textNode = null) {
let { text, ...attributes } = config;
if (typeof text === 'function') {
text = text(textNode ? textNode.data : '', el, textNode);
}
if (typeof text !== 'undefined') {
if (textNode) textNode.nodeValue = text;
else console.warn('[can not set text for', el, ': no text childNodes.]');
}
Object.entries(attributes).forEach(([attribute, value]) => {
let oldValue = el.getAttribute(attribute);
if (typeof value === 'function') {
value = value(oldValue, el);
}
if (typeof value !== 'undefined') {
if (value === null)
el.removeAttribute(attribute);
else if(typeof value === 'object')
Object.assign(el[attribute] || (el[attribute] = {}), value);
else
el.setAttribute(attribute, value);
}
})
}
let MutationObserverConfig = {
childList: true,
attributes: true,
subtree: true,
attributeFilter: ['data-label'],
characterData: true
};
let observer = new MutationObserver(mutations => {
observer.disconnect();
mutations.forEach(record => {
switch (record.type) {
case 'childList':
record.addedNodes.forEach(queryAndProcess);
break;
case 'characterData':
matchAndProcess(record.target.parentNode, record.target);
break;
case 'attributes':
matchAndProcess(record.target);
break;
default:
console.error(`[unknown type "${record.type}"]`)
break;
}
});
observer.observe(document.body, MutationObserverConfig);
});
queryAndProcess(document.body);
observer.observe(document.body, MutationObserverConfig);
'[class^="select--dropdownContainer--"]': {
'style': (oldVal, element) => {
element.style.width = "250px"
// т.к. функция ничего не возвращает(undefined), то дальше ничего не происходит
}
}
function monkeyPatchModuleCall(callback) {
const oldCall = Function.prototype.call;
Function.prototype.call = function(...args) {
const [ t, i, exports, o ] = args;
const isModule = t === window && i && i.exports && i.exports === exports && typeof o === 'function';
const result = oldCall.apply(this, args);
if(isModule && callback(i)) {
Function.prototype.call = oldCall;
}
return result
};
}
monkeyPatchModuleCall(({exports}) => {
if(exports && typeof exports === 'object' && 'diffThumbnailWidth' in exports) {
Object.assign(exports, {
diffThumbnailWidth:"22444px",
diffThumbnailHeight:"160px",
diffThumbnailPadding:"20px"
});
/*
возврат true останавливает мониторинг вызовов модулей,
если надо дождаться нескольких - тут должно быть условие сложнее
*/
return true;
}
});
exports
попадает собственно всё, что в коде e.exports=
, отделять нужное именно тебе следует по условию.call
вызовы функций, так что возвращать true
желательно сразу как заменил всё что надо. const labelMap = {
'Assets': 'Ассеты'
}
const textMap = {
'Copy link': 'Копировать ссылку',
'Line tool': 'Линия',
'Line': 'Линия',
'Lock/Unlock': 'Заблокировать/Разблокировать',
'Use as mask': 'Использовать как маску',
}
/**
interface Config {
[selector: string]: {
text:
string
| undefined
| ((oldVal: string, parentElement: Element, textNode?: Text) => string | undefined);
'<attribute-name>':
string
| undefined
| null
| ((oldVal: string, parentElement: HTMLElement) => string | undefined | null);
}
}
selector - css селектор
text - текст в элементе
значения:
function - вызывает функцию и использует возвращаемое значение
string - устанавливает текст в элементе
undefined - ничего не делает
attribute - любой атрибут
значения:
function - вызывает функцию и использует возвращаемое значение
string - устанавливает значение атрибута
null - удаляет атрибут
undefined - ничего не делает
**/
const selectorMap = {
'[data-label="Layers"]': {
'data-label': 'Слои'
},
'[data-label]': {
'data-label': (oldVal, element) => labelMap[oldVal]
},
'*:not(script, style, link, meta)': {
'text': (oldVal, parentElement, textNode) => textMap[oldVal]
},
'[for="frame-mask-disabled-checkbox"]': {
'style': 'width: 150px;line-height: 15px;',
},
'[class^="toolbar_view--shareButton--"]': {
'style': 'width: 90px;',
},
'[class^="raw_components--panelTitle--"]': {
'style': 'text-transform: none;',
}
}
function matchAndProcess (el, textNode) {
Object.entries(selectorMap).forEach(
([selector, config]) => el.matches(selector) && processElement(config, el, textNode)
);
}
function queryAndProcess (el) {
Object.entries(selectorMap).forEach(([selector, config]) => {
if (el.nodeType === 3) {
const { parentNode } = el;
if (parentNode?.matches(selector))
processElement(config, parentNode, el);
} else {
el.querySelectorAll(selector).forEach(el => processTextNodes(config, el));
if (el.matches(selector))
processTextNodes(config, el);
}
});
}
function processTextNodes (config, el) {
if ('text' in config) {
const textNodes = Array.prototype.filter.call(
el.childNodes,
node => node.nodeType === 3 && node.data.trim()
);
if (textNodes.length)
return textNodes.forEach(textNode => processElement(config, el, textNode));
}
processElement(config, el);
}
function processElement (config, el, textNode = null) {
let { text, ...attributes } = config;
if (typeof text === 'function') {
text = text(textNode ? textNode.data : '', el, textNode);
}
if (typeof text !== 'undefined') {
if (textNode) textNode.nodeValue = text;
else console.warn('[can not set text for', el, ': no text childNodes.]');
}
Object.entries(attributes).forEach(([attribute, value]) => {
let oldValue = el.getAttribute(attribute);
if (typeof value === 'function') {
value = value(oldValue, el);
}
if (typeof value !== 'undefined') {
if (value !== null) el.setAttribute(attribute, value);
else el.removeAttribute(attribute);
}
})
}
let MutationObserverConfig = {
childList: true,
attributes: true,
subtree: true,
attributeFilter: ['data-label'],
characterData: true
};
let observer = new MutationObserver(mutations => {
observer.disconnect();
mutations.forEach(record => {
switch (record.type) {
case 'childList':
record.addedNodes.forEach(queryAndProcess);
break;
case 'characterData':
matchAndProcess(record.target.parentNode, record.target);
break;
case 'attributes':
matchAndProcess(record.target);
break;
default:
console.error(`[unknown type "${record.type}"]`)
break;
}
});
observer.observe(document.body, MutationObserverConfig);
});
queryAndProcess(document.body);
observer.observe(document.body, MutationObserverConfig);
textMap
применяется не везде, а к конкретным селекторам, вот тут:'[class^="multilevel_dropdown--name--"]': {
"text": (oldVal, parentElement, textNode) => textMap[oldVal] || oldVal
},
т.е. только для меню. Если надо где-то ещё, надо, соответственно указать нужные селекторы. Анализировать вообще весь текст - можно, но это жирно и опасно. const labelMap = {
'Assets': 'Ассеты'
}
const textMap = {
'Copy link': 'Копировать ссылку'
}
/**
interface Config {
[selector: string]: {
text: string | undefined | ((oldVal: string, parentElement: HTMLElement, textNode?: Text) => string | undefined);
'<attribute-name>': string | undefined | null | ((oldVal: string, parentElement: HTMLElement) => string | undefined | null);
}
}
selector - css селектор
text - текст в элементе
значения:
function - вызывает функцию и использует возвращаемое значение
string - устанавливает текст в элементе
undefined - ничего не делает
attribute - любой атрибут
значения:
function - вызывает функцию и использует возвращаемое значение
string - устанавливает значение атрибута
null - удаляет атрибут
undefined - ничего не делает
**/
const selectorMap = {
'[data-label="Layers"]': {
'data-label': 'Слои'
},
'[data-label]': {
'data-label': (oldVal, element) => labelMap[oldVal] || oldVal
},
'[class^="multilevel_dropdown--name--"]': {
"text": (oldVal, parentElement, textNode) => textMap[oldVal] || oldVal
},
'[for="frame-mask-disabled-checkbox"]': {
"style": "width: 150px;line-height: 15px;",
},
'[class^="toolbar_view--shareButton--"]': {
"style": "width: 90px;",
},
'[class^="raw_components--panelTitle--"]': {
"style": "text-transform: none;",
}
}
function matchAndProcess(el, textNode) {
Object.entries(selectorMap).forEach(
([selector, config]) => el.matches(selector) && processElement(config, el, textNode)
);
}
function queryAndProcess(el) {
Object.entries(selectorMap).forEach(([selector, config]) => {
let textNode = null;
if(el.nodeType === 3) {
textNode = el;
el = el.parentNode;
if(!el) return;
} else {
el.querySelectorAll(selector).forEach(el => processElement(config, el));
}
if(el.matches(selector))
processElement(config, el, textNode);
});
}
function processElement(config, el, textNode = null) {
let { text, ...attributes } = config;
if (typeof text === 'function') {
text = text(
textNode ? textNode.nodeValue : el.textContent,
el,
textNode
);
}
if (typeof text !== 'undefined') {
if (textNode) textNode.nodeValue = text;
else el.textContent = text;
}
Object.entries(attributes).forEach(([attribute, value]) => {
let oldValue = el.getAttribute(attribute);
if (typeof value === 'function') {
value = value(oldValue, el);
}
if (typeof value !== 'undefined') {
if(value !== null) el.setAttribute(attribute, value)
else el.removeAttribute(attribute)
}
})
}
let MutationObserverConfig = {
childList: true,
attributes: true,
subtree: true,
attributeFilter: ['data-label'],
characterData: true
};
let observer = new MutationObserver(mutations => {
observer.disconnect();
mutations.forEach(record => {
switch (record.type) {
case 'childList':
record.addedNodes.forEach(queryAndProcess);
break;
case 'characterData':
matchAndProcess(record.target.parentNode, record.target);
break;
case 'attributes':
matchAndProcess(record.target);
break;
default:
console.error(`[unknown type "${record.type}"]`)
break;
}
});
observer.observe(document.body, MutationObserverConfig);
});
queryAndProcess(document.body);
observer.observe(document.body, MutationObserverConfig);
const labelMap = {
'Assets': 'Ассеты'
}
const textMap = {
'Copy link': 'Копировать ссылку'
}
/**
interface Config {
[selector: string]: {
text: string | undefined | ((oldVal: string, parentElement: HTMLElement, textNode?: Text) => string | undefined);
'<attribute-name>': string | undefined | null | ((oldVal: string, parentElement: HTMLElement) => string | undefined | null);
}
}
selector - css селектор
text - текст в элементе
значения:
function - вызывает функцию и использует возвращаемое значение
string - устанавливает текст в элементе
undefined - ничего не делает
attribute - любой атрибут
значения:
function - вызывает функцию и использует возвращаемое значение
string - устанавливает значение атрибута
null - удаляет атрибут
undefined - ничего не делает
**/
const selectorMap = {
'[data-label="Layers"]': {
'data-label': 'Слои'
},
'[data-label]': {
'data-label': (oldVal, element) => labelMap[oldVal] || oldVal
},
'label[class^="raw_components--labelInactive--"]': {
"style": "width: 150px;line-height: 15px;",
"text": (oldVal, parentElement, textNode) => textMap[oldVal] || oldVal
}
}
function matchAndProcess(el, textNode) {
Object.entries(selectorMap).forEach(
([selector, config]) => el.matches(selector) && processElement(config, el, textNode)
);
}
function queryAndProcess(el) {
Object.entries(selectorMap).forEach(([selector, config]) => {
if(el.matches(selector)) processElement(config, el);
el.querySelectorAll(selector).forEach(el => processElement(config, el));
});
}
function processElement(config, el, textNode = null) {
let { text, ...attributes } = config;
if (typeof text === 'function') {
text = text(
textNode ? textNode.nodeValue : el.textContent,
el,
textNode
);
}
if (typeof text !== 'undefined') {
if (textNode) textNode.nodeValue = text;
else el.textContent = text;
}
Object.entries(attributes).forEach(([attribute, value]) => {
let oldValue = el.getAttribute(attribute);
if (typeof value === 'function') {
value = value(oldValue, el);
}
if (typeof value !== 'undefined') {
if(value !== null) el.setAttribute(attribute, value)
else el.removeAttribute(attribute)
}
})
}
let MutationObserverConfig = {
childList: true,
attributes: true,
subtree: true,
attributeFilter: ['data-label'],
characterData: true
};
let observer = new MutationObserver(mutations => {
observer.disconnect();
mutations.forEach(record => {
switch (record.type) {
case 'childList':
record.addedNodes.forEach(queryAndProcess);
break;
case 'characterData':
matchAndProcess(record.target.parentNode, record.target);
break;
case 'attributes':
matchAndProcess(record.target);
break
default:
console.error(`[unknown type "${record.type}"]`)
}
});
observer.observe(document.body, MutationObserverConfig);
});
queryAndProcess(document.body);
observer.observe(document.body, MutationObserverConfig);
Другое дело, что раз доступен DOMContent - то пора его показывать, а не левый лоадер.
А вот если запускать лоадер до DOMContentLoaded, то тогда действительно не ясно сколько там будет картинок, такая вот дилемма.
Если всё это генерируется на сервере - сервер может подсчитать и заранее в скрипт подставить.
В целом же - выше правильно говорят: люям нужен не лоадер, а хоть какой-то контент сразу.