Задать вопрос
  • Как запустить JS перед загрузкой страницы?

    Aetae
    @Aetae Куратор тега JavaScript
    uuuu, при DOMContentLoaded доступен уже весь DOMContent, очевидно. Т.е. все узлы картинок и прочего имеются в документе. Осталось только посчитать.
    Другое дело, что раз доступен DOMContent - то пора его показывать, а не левый лоадер.
    А вот если запускать лоадер до DOMContentLoaded, то тогда действительно не ясно сколько там будет картинок, такая вот дилемма.
    Если всё это генерируется на сервере - сервер может подсчитать и заранее в скрипт подставить.
    В целом же - выше правильно говорят: люям нужен не лоадер, а хоть какой-то контент сразу.
  • Как выбрать элемент с определённым стилем?

    Aetae
    @Aetae
    Ты же понимаешь, что такая хрень, будь она возможна, могла бы вызывать бесконечные циклы и рекурсии?
    Js'ом можно что-то накостылять, но это будет очень жирное и очень мерзкое решение.
    В любом случае нет ни одного адекватного варианта, когда такое могло бы действительно понадобиться.
  • Стоит ли тратить время на изучение float?

    Aetae
    @Aetae
    Сергей delphinpro, править легаси хероту проще, не с нуля же. Ну и флексы туда спокойно впихнуть можно, никто не заставляет придерживаться "исторического стиля фасадов".)
    Я сам начинал во времена ie6 и флоатов, и какие-то мутные воспоминания о хитростях у меня есть, но конкретику я давно позабыл т.к. уже многие годы не прикасался к этому.
  • Почему calc не правильно определяет ширину экрана?

    Aetae
    @Aetae
    2ristBY, сейчас все браузеры хром кроме файерфокса. Баги, соответственно, у них одинаковые. Тебе в багтреккер хрома.
  • Не подключается jquery в firefox, как исправить?

    Aetae
    @Aetae Куратор тега JavaScript
    Проверь не стоит ли у тебя noscript\adblock\аналоги, и нет ли там кривых фильтраций.
  • Python - как работать с арабскими, китайскими и испанскими символами в url?

    Aetae
    @Aetae
    Python — как работать с арабскими, китайскими и испанскими символами в url?
    С мучительной болью и нечеловеческими страданиями.
    Простите.
  • Git: как игнорировать файл при пуше?

    Aetae
    @Aetae
    Коммит - это конкретная единица изменений. push или pull ничего явного не делают, просто перекладывает коммиты меж репами.
    Ты можешь перестать отслеживать изменения файла через git update-index --assume-unchanged file, но тогда он не будет попадать в коммиты.
  • Вырезать из ссылок определенное слово?

    Aetae
    @Aetae Куратор тега JavaScript
    Нормально для костыля.
    Можно было бы менять не в item.href, а в item.search, можно было воспользоваться регуляркой или URLSearchParams, но в целом в этом нет смысла - править надо первопричину.
  • Tampermonkey Замена кода script.js при загрузке страницы?

    Aetae
    @Aetae Куратор тега JavaScript
    CCO, очередная версия с возможностью присваивать объект свойству:
    spoiler
    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), то дальше ничего не происходит
        }
      }
  • Tampermonkey Замена кода script.js при загрузке страницы?

    Aetae
    @Aetae Куратор тега JavaScript
    CCO, по поводу подмены, вот ещё такой кусок кода:
    spoiler
    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 желательно сразу как заменил всё что надо.
  • Tampermonkey Замена кода script.js при загрузке страницы?

    Aetae
    @Aetae Куратор тега JavaScript
    CCO, новая версия с поддержкой универсального селектора:
    spoiler
    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);
  • Tampermonkey Замена кода script.js при загрузке страницы?

    Aetae
    @Aetae Куратор тега JavaScript
    CCO, textMap применяется не везде, а к конкретным селекторам, вот тут:
    '[class^="multilevel_dropdown--name--"]': {
        "text": (oldVal, parentElement, textNode) => textMap[oldVal] || oldVal
      },
    т.е. только для меню. Если надо где-то ещё, надо, соответственно указать нужные селекторы. Анализировать вообще весь текст - можно, но это жирно и опасно.
  • Tampermonkey Замена кода script.js при загрузке страницы?

    Aetae
    @Aetae Куратор тега JavaScript
    CCO, пофиксил:
    spoiler
    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);

    А тот скрипт что ты нашёл - бяка. Он не использует MutationObserver по назначению: он тупо перебирает всё дерево документа при каждом изменении, вместо того чтобы обрабатывать только изменённые узлы.
  • Tampermonkey Замена кода script.js при загрузке страницы?

    Aetae
    @Aetae Куратор тега JavaScript
    CCO, как-то так:
    spoiler
    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);

    Особо не проверял, так что могут быть баги.)
  • Typescript не работает с try-catch почему?

    Aetae
    @Aetae Куратор тега TypeScript
    Владимир Проскурин, или не превращается, если целевые брауеры - только современные.
  • Проблема с подключением axios?

    Aetae
    @Aetae Куратор тега JavaScript
    У меня есть подозрение, что ты нифига не собираешь, а пуляешь файл как есть с import.
  • Как в Angular 8 собрать цельную сущность с разных API?

    Aetae
    @Aetae
    Правильно это решается (опциональным) join'ом в запросе к базе на бэке.
    Остальное - те или иные костыли.
  • Стоит ли изучать одновременно React и Angular?

    Aetae
    @Aetae Куратор тега JavaScript
    Антон Швец, проблема Angular для меня в том, что в нём буквально всё сделано так, как я никогда бы не стал делать. Энетерпрайзный буллщит-оверинжиниринг, полностью противоположный тому, за что я люблю в javascript.)
    Каждая необходимость работы с ним у меня выливается в плачь кровавыми слезами.