Ответы пользователя по тегу JavaScript
  • Как обернуть в теги некоторые слова в HTML?

    sergiks
    @sergiks Куратор тега JavaScript
    ♬♬
    Попробовал сделать через DocumentFragment, рекурсивный обход текстовых Node
    и обёртку найденных слов с помощью Range.surroundContents()

    Получилось как-то переусложнённо. Но наверное лучше, чем регуляркой HTML разбирать: не станет менять слова в атрибутах тегов. Раз уж это разметка, можно работать с DOM.
    Слабое место – не обошлось без регулярного выражения для поиска слов. Т.к. там кириллица, привычные \b для границы слова не работают, пришлось заглядывать впрёд-назад, и регулярка вышла некороткая.

    Криво-длинно, но работает:

    Из исходного HTML делается DocumentFragment — как DOM полноценного документа. Перебираются его узлы. Если узел не-текстовый, рекурсивно перебираем его дочерние узлы.
    В текстовых узлах ищем искомые слова.

    Найденное слово (по одному за раз) заменяется на обёртку с этим словом.
    Вместо 1 исходного текстового узла, у нас становится уже 3: текст-элемент-текст.
    Далее поиск повторяется с хвостовым остатком текста – третьим (текстовым) узлом, пока в тексте не останется искомых слов для замены.

    Решение не лаконичное и не простое. Если возникнут вопросы, пишите, постараюсь объяснить.
    Ответ написан
    7 комментариев
  • Как сравнить переменную с ключами объекта?

    sergiks
    @sergiks Куратор тега JavaScript
    ♬♬
    Можно отсортировать по ключам по убыванию, и найти find() первый, где данное число окажется больше ключа.
    Если не нашлось такое, отдать последнее значение (с минимальным ключом).
    что за entries ?
    Object.entries(obj) делает из объекта массив пар [[ключ, значение], ... ] — 
    [
      [50: "3500"],
      [100, "6000"], 
      ...
    ]

    const findRange = n => {
      const obj = { 
        50: "3500", 
        100: "6000", 
        200: "8000", 
        300: "10500", 
        500: "13000", 
        501: "индивидуально" 
      };
      const lookup = Object.entries(obj).sort(([a], [b]) => b - a);
    
      return (lookup.find(([k, v]) => Number(k) <= n) ?? lookup.pop())[1];
    };
    
    findRange(49) // "3500"
    findRange(99) // "3500"
    findRange(100) // "6000"
    findRange(500) // "13000"
    findRange(501) // "индивидуально"
    Ответ написан
    Комментировать
  • Как правильно сделать зависящие друг от друга сетевые запросы с помощью Promise.allSettled?

    sergiks
    @sergiks Куратор тега JavaScript
    ♬♬
    Просто склейте их в одну цепочку. Что-то типа:
    макароны
    const state = {};
    const getJSON = response => response.json();
    getAuth()
      .then(getJSON)
      .then(({ id }) => {
        state.id = id;
        return getUser(id);
      })
      .then(getJSON)
      .then(user => {
        state.user = user;
      })
      .then(() => getOrder('users', state.id))
      .then(getJSON)
      .then(({ order }) => state.order = order)
      .catch(console.error)
      .finally(() => console.log("all done", state));


    возможна ли реализация данной логики с помощью Promise.allSettled?

    Нет. allSettled() параллелит выполнение всех промисов и дожидается, чтобы «все расселись» – будь то resolve или reject.

    Хотя, пожалуй, тут можно последние два запроса запараллелить. Они оба нуждаются сначала в авторизации, а затем могут идти независимо-одновременно.
    const state = {};
    
    // подготовка
    const getJSON = response => response.json();
    const getUser = id =>
      fetchUser(id)
        .then(getJSON)
        .then(({ user }) => (state.user = user));
    const getOrder = id =>
      fetchOrder('users', id)
        .then(getJSON)
        .then(({ order }) => (state.order = order));
    
    // поехали
    getAuth()
      .then(getJSON)
      .then(({ id }) => {
        state.id = id;
        return Promise.allSettled([getUser(id), getOrder(id)]);
      })
      .catch(console.error)
      .finally(() => console.log('all done', state));
    Ответ написан
    Комментировать
  • Как решить задачу по JS?

    sergiks
    @sergiks Куратор тега JavaScript
    ♬♬
    function count(min, max) {
      let counter = 0;
      for (let n = min; n <= max; n++) {
        if (!n.toString(10).includes('5')) counter++;
      }
      return counter;
    }
    
    count(4, 17)  // 12
    Определять, есть ли в числе цифра 5 можно и иначе, без преобразования в строку, но так короче )
    Ответ написан
    Комментировать
  • Как подставить нули к дублируемому инпуту?

    sergiks
    @sergiks Куратор тега JavaScript
    ♬♬
    Если в поле только цифры – сделать число Number(value) и методом toFixed(2) гарантировать 2 знака после запятой.
    Если в поле только пробелы или пусто, нарисовать подчёркивание.
    Применить ко всем трём полям одинаково:
    Ответ написан
    Комментировать
  • Как вывести фото, текст и кнопки в телеграмме в одном сообщении?

    sergiks
    @sergiks Куратор тега JavaScript
    ♬♬
    попробуйте так:
    сtx.telegram
      .sendPhoto(
        ctx.chat.id,
        ctx.message.photo[0].file_id,
        { caption: s, parse_mode: 'HTML', reply_markup: markup }
      )

    Наскоро посмотрел: вроде бы, третий параметр telegram.sendPhoto() (исходник) передаётся через функцию fmtCaption() (код) в api тележки, и доп. параметры помимо caption должны пройти без изменений.
    Ответ написан
  • Как сделать паузу в js вычислениях, чтобы отрисовать изменения?

    sergiks
    @sergiks Куратор тега JavaScript
    ♬♬
    Как вариант, всю тяжёлую работу передать в web worker.

    1. показать лоадер
    2. вынуть из localStorage данные, и отправить их в worker
    3. спиннер крутится, воркер в другом треде считает
    4. воркер наконец пришлёт ответ: большой кусок готового HTML
    5. вставить весь полученный HTML разом
    6. скрыть спиннер
    Ответ написан
    Комментировать
  • Как добавить в js стили классам как в css?

    sergiks
    @sergiks Куратор тега JavaScript
    ♬♬
    Переписать то же, короче, можно примерно так:
    (names =>
      names.forEach(name => {
        document.getElementsByClassName(name).forEach(({ style }) => {
          style.display = name;
        });
      }))(['block', 'inline-block', 'inline', 'flex', 'inline-flex', 'table', 'inline-table', 'table-caption']);


    Или можно «просто взять, и ..» добавить в head документа сгенерированный CSS:
    Ответ написан
    2 комментария
  • Зачем две функции?

    sergiks
    @sergiks Куратор тега JavaScript
    ♬♬
    Что происходит
    Каждому элементу коллекции peresortDel вешается слушатель события клика.

    Каким образом
    1. Внутрь forEach() передается функция, которую применят к каждому элементу.
    2. Слушатель события click — тоже функция. Другая, чем в п.1, поэтому их тут.. две.

    Как быть
    Чтобы короче — можно повесить всего один слушатель не на каждый элемент, а на общего родителя где-то выше. Это позволит сэкономить на функции в forEach(), и не создавать новую функцию для каждого элемента.
    commonParent.addEventListener('click', ({ target }) => {
      if (!target.classList.contains("tot-samyj-class")) return;
      target.style.display = "none";
    });
    Подробнее не подскажу, т.к. в вопросе нет действующего примера или хотя бы разметки.
    Ответ написан
    Комментировать
  • Как решить данную задачу?

    sergiks
    @sergiks Куратор тега JavaScript
    ♬♬
    Т.е. надо все значения умножить на некий общий коэффициент так,
    чтобы сумма их стала 360.
    const normalize = obj => {
      const result = { ...obj }; // shallow copy
      const k = 360 / Object.values(result).reduce((acc, c) => acc + c);
      for (const prop in result) result[prop] *= k;
      return result;
    };
    Ответ написан
    Комментировать
  • Почему возвращается true а не 5?

    sergiks
    @sergiks Куратор тега JavaScript
    ♬♬
    Ошибка тут:
    оператор ИЛИ возвращает последнюю правду, а по факту дает True, а не 5. Ведь 5 в этом контексте тоже true

    На самом деле ИЛИ || возвращает первую правду. И дальше не проверяет. Т.е. 3 > 1 === true

    Ещё тут важно, что у || приоритет ниже ↓, чем у && ↑. Очерёдность выполнения:
    5 === 5 && 3 > 1 || 5
    (5 === 5 && 3 > 1) || 5
    // && проверит, чтобы все были true
    // и вернёт последний, т.е. вернёт 3 > 1 === true
    // || увидит, что уже есть true и вернёт его, не проверяя 5

    Позавчера, как раз, отвечал на похожий вопрос. Вам же.
    Ответ написан
    5 комментариев
  • Литерал объекта и блок инструкций. Есть ли связь?

    sergiks
    @sergiks Куратор тега JavaScript
    ♬♬
    Усы похожие, даже идентичные. Но по смыслу ничего общего.

    Литерал объекта это выражение, значение которого как-то используется, куда-то передаётся.

    А блок инструкций сам по себе или со всякими do {} while () и if (condition) {}

    В общем, вроде бы, различия однозначны и коллизий не возникает:
    function foo() {}; // пустой блок
    foo({}); // передали пустой объект аргументом
    
    {} // пустой блок инструкций
    ({}) // отдельно висящий никому не нужный пустой объект
    
    [{}] // создали массив с пустым объектом
    {[]} // внутри блока создали пустой массив и забыли про него
    «Умные» редакторы кода подсвечивают усы разными цветами и ругаются на бессмысленность  бытия  пустых блоков и неиспользуемых выражений.
    как это выглядит
    6378cb1e939e9216154366.png
    Ответ написан
    Комментировать
  • Не могу сделать аналог вывода простых чисел. В чем проблема?

    sergiks
    @sergiks Куратор тега JavaScript
    ♬♬
    Рабочий пример из учебника (с форматированием) хорош предсказуемостью циклов.
    Цикл for() сходу устанваливает ограничения и следит за тем, чтобы на каждой итерации все эти i, j увеличивались.

    Цикл while() — опасный анархист. Легко впадает в бесконечный цикл, если в каком-то из условий забыть i = i + 1 Что у вас в последней попытке и присутствует:
    butthurt: while (i <= 10) {
      while (j < i) {
        alert('Внутренний цикл ' + j);
        if (i % j === 0) continue butthurt;
    «Нет выхода», если i % j === 0: никто из них не поменяется и будет крутиться бесконечно.

    Как вариант:
    const LIMIT = 10;
    let i = 2;
    while (i <= LIMIT) {
      let j = 2;
      let isPrime = true;
      while (j < i && isPrime) {
        alert(`Внутренний: ${i} / ${j}`);
        if (i % j === 0) isPrime = false; // делится, значит не простое
        j++;
      }
      alert(`Внешний: ${i} - ${isPrime ? 'ПРОСТОЕ' : 'не простое'}`);
      i++;
    }
    Отказался от меток циклов и от continue – нежелательны именно из-за вносимой путаницы.
    Ответ написан
    1 комментарий
  • Как правильно модифицировать дату для value input?

    sergiks
    @sergiks Куратор тега JavaScript
    ♬♬
    ВременнАя зона "Z" — это UTC.
    А в поле ввода, очевидно, хочется видеть время в часовом поясе браузера пользователя.

    Есть несколько способов получить строку формата "YYYY-mm-ddTHH:MM" в часовом поясе браузера. Например, вытаскивать отдельно часы, минуты и т.п. и собирать в строку.
    const timeFromBackend = "2022-11-15T20:00:00Z";
    const localTime = new Date(timeFromBackend); // 23:00 Msk
    const oo = n => n.toString(10).padStart(2, '0');
    const YYYY = localTime.getFullYear();
    const MM = oo(localTime.getMonth() + 1);
    const DD = oo(localTime.getDate());
    const HH = oo(localTime.getHours());
    const II = oo(localTime.getMinutes());
    const localTimeForInput = [YYYY, MM, DD].join('-') + 'T' + [HH, II].join(':');
    console.log(localTimeForInput); // 2022-11-15T23:00

    Или добавить минуты смещения нашего часового пояса, чтобы получить строку времени сразу целиком:
    const timeFromBackend = "2022-11-15T20:00:00Z";
    const localTime = new Date(timeFromBackend); // 23:00 Msk
    const shiftedTime = new Date(localTime);
    shiftedTime.setMinutes(shiftedTime.getMinutes() - shiftedTime.getTimezoneOffset());
    const timeForInput = shiftedTime.toISOString().substring(0, 16);
    console.log(timeForInput); // "2022-11-15T23:00"


    Обратное преобразование из локального времени в поле ввода — в UTC время для бэкенда:
    const timeFromInput = "2022-11-15T22:00";
    const date = new Date(timeFromInput);
    const timeForBackend = date.toISOString().substring(0, 19) + 'Z';
    console.log(timeForBackend)  // "2022-11-15T19:00:00Z"
    Ответ написан
    2 комментария
  • Почему в console.log получаю разные результаты от операторов?

    sergiks
    @sergiks Куратор тега JavaScript
    ♬♬
    Логические операторы && и || возвращают свои операнды:

    1 && "2" // строка "2"
    0 && "2" // число 0
    
    0 || "2" // строка "2"
    1 || false // число 1

    Логическое И &&: для позитивного результата нужно, чтобы оба операнда были как-true. Проверка идёт слева направо. Как только наткнулись на как-false, возвращается он. Прошли всю цепочку и все по пути были как-true — вернули последний из них.

    Логическое ИЛИ || даст позитивный результат, как только наткнётся хоть на один как-true. Его и вернёт. Ну или проверит все значения и если все как-false, вернёт последний из них.

    1 && "2" && "habr" // вернёт "habr"
    "1" && 0 && "qna" // 0
    
    0 || false || "habr" === "qna" // false, результат ("habr" === "qna")
    0 || 1 || 2 || true // 1, дальше проверять не пришлось
    Ответ написан
    Комментировать
  • Как получить ссылку через this?

    sergiks
    @sergiks Куратор тега JavaScript
    ♬♬
    Геттер render находится в одном объекте, который под ключом "settings".
    А data – в другом объекте, который под ключом "template".

    Так что только через store можно "зайти":
    const store = [
      {
        template: {
          data: [1, 2, 3],
        },
        settings: {
          get render() {
            console.log(store[0].template.data);
          },
        },
      },
    ];
    
    store[0].settings.render  // [ 1, 2, 3 ]
    Ответ написан
  • При выборе div блока он должен скрыться?

    sergiks
    @sergiks Куратор тега JavaScript
    ♬♬
    Может, просто удалять кликнутые?
    Ответ написан
    3 комментария
  • Как отнять 2 месяца от текущей даты?

    sergiks
    @sergiks Куратор тега JavaScript
    ♬♬
    const dateTwoMonthsBack = new Date();
    dateTwoMonthsBack.setMonth(dateTwoMonthsBack.getMonth() - 2);
    
    const id = 'testId'; // TODO: remove
    
    const params = {
      page_size: 200,
      manager: id,
      last_kpi_graded__lte: dateTwoMonthsBack.toISOString().substring(0, 10),
    };
    
    const url =
      'http://10.1.5.65/api/personal/users/?' +
      Object.entries(params)
        .map(([k, v]) => `${k}=${encodeURIComponent(v)}`)
        .join('&');
    
    console.log(url);
    // http://10.1.5.65/api/personal/users/?page_size=200&manager=testId&last_kpi_graded__lte=2022-09-16
    
    // fetch(url);
    Ответ написан
    Комментировать
  • Как проверять строки на равность, начиная с определённого момента?

    sergiks
    @sergiks Куратор тега JavaScript
    ♬♬
    Пока только про сравнение двух строк.

    Предлагаю прокатиться одной строкой вдоль второй.
    _________zzzoooABCuuu
    xxxABCiii      |||
     xxxABCiii     |||
      xxxABCiii    |||
    ...            |||
                xxxABCiii
    ...
                        xxxABCiii
    Начать ещё до их пересечения – это максимально возможное их несовпадение.
    И сдвигать по одной позиции вправо.

    Сравнивать, считая ошибки: количество не совпавших позиций.

    Взять тот сдвиг, где ошибок минимум.

    С «минимальными отличиями» пока не придумал ничего толкового. В этом коде просто предполагаю, что единично-отличные символы находятся не с краю совпадающих строк. Иначе всегда можно присобачить по 1 лишнему символу в начале и в конце, заявив, что именно они в этот раз случайно оказались разными )
    шит-код
    function mostCommon(a, b) {
      const A = a.split('');
      const B = b.split('');
      const min = {
        diff: A.length + B.length,
        index: -A.length,
        start: undefined,
        finish: undefined,
      };
    
      for (let offset = -A.length; offset < B.length; offset++) {
    		let diff = Math.max(0, -offset) + Math.max(offset + A.length - B.length, 0);
    		const initialDiff = Math.max(0, -offset) + Math.max(offset + A.length - B.length, 0);
        const start = Math.min(Math.max(0, offset), B.length);
        const finish = Math.min(Math.max(0, offset + A.length), B.length);
        let matchStart;
        let matchFinish;
        for (let i = start, isMatchStarted = false; i < finish; i++) {
          if (B[i] !== A[i - offset]) diff++;
          else {
            if (!isMatchStarted) {
              matchStart = i;
              isMatchStarted = true;
            }
            matchFinish = i;
          }
        }
    
        if (diff < min.diff) {
          min.diff = diff;
          min.index = offset;
          min.start = matchStart;
          min.finish = matchFinish;
        }
      }
    
      console.log(min, b.substring(min.start, min.finish + 1));
    
      return min;
    }
    
    mostCommon('xxx>abcABCxxx', 'bbbzzz>abcAXCiiiqqq');
    // { diff: 7, index: 3, start: 6, finish: 12 } >abcAXC

    На примере из вопроса:
    mostCommon("1246380924534", "88899212465809");
    // { diff: 6, index: 6, start: 6, finish: 13 } 12465809
    Ответ написан
    3 комментария
  • Как реализовать алгоритм наложения нот в чарте ритм-игры?

    sergiks
    @sergiks Куратор тега JavaScript
    ♬♬
    setInterval(), setTimeout() — не точны. Для музыки/ритма/игры лучше полагаться на абсолютные метки времени и часы компьютера.

    // в относительных единицах от старта песни
    const hits = [ { time: 0 }, { time: 24 }, { time: 36 }, ];
    
    // on play start
    const timeK = 1200; // в зависимости от темпа
    const playStartTime = Date.now();
    const events = hits.map(hit => {
      hit.ts = playStartTime + timeK * hit.time;
      return hit;
    });
    // теперь у каждой ноты есть время ts, когда она должна сыграть
    
    // внутри цикла requestAnimationFrame, который часто-часто,
    // сраниваем текущее время с нотами,
    // если разница менее 100 мс, ноту можно "играть"
    const now = Date.now();
    events.filter(note => Math.abs(note.ts - now) < 100).forEach(note =>play(note));
    Наверное, надо смотреть и «в прошлом» пропущенные ноты – мало ли, комп притормозил.. И убирать сыгранные ноты из массива, чтобы не возвращаться к ним.
    Ответ написан
    1 комментарий