Философские вопросы интернационализации веб-приложений | Веб-студия Nat.od.ua
Философские вопросы интернационализации веб-приложений
Имея дело с переводами, вы сталкиваетесь с множеством каверзных вопросов, например, какие языки поддерживать, как вы переводите сообщения и как вы определяете язык. То, что казалось быстрой легкой задачей, становится бременем. Вот некоторые вещи, о которых следует помнить, прежде чем приступать к переводу приложения.
Языки
Если вы поддерживаете два или три языка, это не имеет большого значения. Но если вы хотите поддерживать дюжину вариантов, вы должны как-то решить, какие из них. Для начала неплохо погуглить самые популярные языки, но этот вариант совсем не точен. Лучше посмотрите статистику или аналитические отчеты, которые дадут вам представление о том, на каких языках говорят ваши пользователи. В Росплате я такие данные не собираю, поэтому нужна была другая точка отсчета. Мне пришлось проявить инициативу и просто спросить потенциальных пользователей: «Привет, ребята, какие языки вы ожидаете увидеть на странице настроек?». Честно говоря, ответ был неожиданным. Арабский, иврит, хинди и бенгалбский!
Обнаружение
Когда новый пользователь попадает в приложение, он может не говорить по-английски, который является языком по умолчанию. Значит, должен быть какой-то способ угадать. И есть один: используйте заголовок Accept-Language на стороне сервера. Этот заголовок не только говорит вам, какую локаль использовать, но и дает вам подсказку, например, этот пользователь говорит на языке А и языках Б (я уверен на 90%) и С (я уверен на 50%). С этой информацией вы можете быть намного увереннее: если вы не поддерживаете основной язык пользователя, вы, вероятно, можете использовать дополнительные.
Читать далее
Работа с размерами изображений с помощью класса Uri в Joomla 4 / Хабр | Веб-студия Nat.od.ua
Работа с размерами изображений с помощью класса Uri в Joomla 4 / Хабр
Парадокс, конечно, но это возможно. Казалось бы, причем тут класс ядра для работы со строками url. Но именно он может помочь нам в Joomla 4.
Предисловие
Для улучшения скорости рендера страницы в браузере рекомендуют указывать атрибуты width и height для изображений. Естественно, удобно их заполнять автоматически. Подойти к решению задачи можно с разных сторон. Нужно помнить о том, что не стоит грузить при её решении сервер дополнительными обращениями, так как это будет влиять на скорость рендера страницы на стороне сервера.
Информация из данной заметки относится только к компонентам, использующим конструктор форм Joomla – Form (ex JForm), а так же использующим для вывода API Joomla.
Рендер изображений в HTML в Joomla
В коде для рендера может использоваться сразу класс HTMLHelper с его методом image, либо с помощью стандартных или переопределённых layouts. Layout – это макет вывода, который может использоваться в самых разных частях сайта. По своей логике использования он схож с trait в php – “оторванный от контекста” фрагмент логики. В случае с layout – вывод с преобработкой. Часто внутри layout конечный рендер изображения происходит не простым echo ‘‘, а тем же методом HTMLHelper::image();
Метод HTMLHelper::image() в Joomla
Метод производит рендер изображения со всеми переданными ему атрибутами. Очень удобно с ним работать следующим образом:
use JoomlaCMSHTMLHTMLHelper;
$img_attribs = ;
echo HTMLHelper::image($img_src, $img_alt, $img_attribs);
Метод HTMLHelper::image принимает на вход:
путь до изображения;
атрибут alt для него;
дополнительные атрибуты, какие Ваша душа пожелает.
3-й параметр для всех остальных атрибутов может быть как просто строкой, так и массивом. Таким образом можно добавить массово любые атрибуты для изображений, например, data-атрибуты или уникальные id, которые будут использоваться на js-фронте.
Изображения в Joomla 3
В Joomla 3 поле типа media для выбора картинок в панели администратора заполняется относительным путём до файла вида images/my_file.jpg.
Поле media для выбора изображений в Joomla 3
Для определения ширины и высоты файла картинки в Joomla 3 можно было использовать следующую конструкцию:
$size = getimagesize(JPATH_SITE.’/’.$img_src);
$img_attribs = ,
‘height’ => $size,
];
echo HTMLHelper::image($img_src, $img_alt, $img_attribs);
Функция getimagesize возвращает размеры изображения в разных форматах, которые уже можно передать в HTMLHelper. Но и сама Joomla 3 в целом не поддерживала формат webp, да и функция getimagesize начала поддерживать этот формат только с php 7.1, но не всегда верно обрабатывать файл, поэтому в сети немало решений с использованием imagecreatefromwebp. В то время, как этот формат является одной из рекомендаций по оптимизации скорости загрузки сайта Google Page Speed.
Изображения в Joomla 4
В Joomla 4 же стандартный медиа-менеджер вставляет в поля не просто путь изображения, а путь, дополненный параметрами вида images/headers/blue-flower.jpg#joomlaImage://local-images/headers/blue-flower.jpg?width=700&height=180
Поле типа media для выбора изображений в Joomla 4.
Когда вышла Joomla 4 многие (в том числе и я) не поняли, баг это или фича 🤣. GET-параметры к изображению не отображают другую картинку, но для поисковых систем это другой url, а следовательно другой файл. У СЕО-специалистов начинают шевелиться волосы… Время показало, что это скорее фича, просто нужно уметь её готовить.
Вариант получения размеров изображения при каждой генерации страницы утяжеляет рендер страницы в целом. Поэтому логично определить размеры изображения заранее и хранить их в базе, вместе с информацией о самом изображении (пути к нему).
Именно таким образом сейчас и работает Joomla 4. Когда Вы в админке выбираете изображение – медиа-менеджер определяет размер картинки и сохраняет в виде GET-параметров к URL картинки.
В своих макетах или layout Вы можете работать с картинками следующим образом.
Получить чистый путь к картинке без #joomlaImage://local-images…
Для этого в Joomla 4 в HTMLHelper появился метод cleanImageURL:
use JoomlaCMSHTMLHTMLHelper;
$img_src = HTMLHelper::_(‘cleanImageURL’, $displayData);Получить размеры файла картинки с помощью класса Uri
Поскольку images/headers/blue-flower.jpg#joomlaImage://local-images/headers/blue-flower.jpg?width=700&height=180 по сути является url – мы можем работать с ним с помощью класса ядра Joomla Uri. Таким образом нам будет удобно получить любой параметр из этой строки.
Для этого нам нужно:
получить часть строки после символа #;
создать новый класс JoomlaCMSUriUri;
получить нужный параметр url с помощью метода getVar(‘attr_name’).
Итак, пример кодаuse JoomlaCMSHTMLHTMLHelper;
use JoomlaCMSUriUri;
$img_src=”images/headers/blue-flower.jpg#joomlaImage://local-images/headers/blue-flower.jpg?width=700&height=180″;
$img_alt=”Атрибут alt для картинки”;
$img_attribs = ;
// Разделяем строку по символу # с помощью explode.
// Получаем массив с двумя элементами, где 2-й элемент – url с параметрами
$img_params_array = explode(‘#’, $img_src);
// Создаём объект Uri
$uri = new Uri($img_params_array);
// Получаем размеры картинки
$img_attribs = $uri->getVar(‘width’);
$img_attribs = $uri->getVar(‘height’);
// Если работаем в цикле, то по итератору определяем каким изображениям нужен
// атрибут loading=lazy. Как правило для картинок в первом экране или нескольких
// первых картинках он не нужен.
//
// if($i > 0){
// $img_attribs = ‘lazy’;
// }
// Получаем чистый путь до картинки, если СЕО-специалисты очень просят.
$img_clean_src = HTMLHelper::_(‘cleanImageURL’, $img_src);
// Рендер изображения
echo HTMLHelper::image($img_clean_src, $img_alt, $img_attribs);
Вот таким образом можно оптимизировать сайт на Joomla 4, указав размеры картинок в Ваших расширениях и при этом не грузить лишними обращениями сервер.
Минусы данного подхода
Минуса два:
Если Вы – не разработчик, или не умеете немного в PHP, полностью зависите от готовых расширений, то Вам данная инструкция вряд ли поможет. Не все разработчики Joomla учитывают подобные нюансы и используют API Joomla в работе. Но, если Вы умеете переопределять макеты в Joomla, то с помощью этой инструкции Вы можете сделать многое.
Замена файлов изображений по FTP. Если Вы любите менять картинки не через админку, а накатывать сверху по FTP новые файлы, оставив при этом старые названия, то новые размеры файла в базе данных не появятся сами. Хорошо, если Вы следите за размерами файлов и они не изменились или изменились не критично. Но если это совершенно другое изображение, других размеров и пропорций, то лучше делать замену через админку.
Послесловие
Предложения по улучшению статьи и замечания приветствуются.
Так же полезные ресурсыРесурсы сообщества:Telegram:
Mattermost:
Сервер – международный (англоязычный) чат Joomla Community, в котором собрались представители из Европы и Азии. Среди них многие имена мы видим среди разработчиков ядра Joomla, членов департаментов Joomla, в качестве авторов статей в Joomla Community Magazine.
Для того, чтобы присоединиться, нужно иметь аккаунт на https://identity.joomla.org/ – центр идентификации Joomla. Вход в мессенджер происходит с помощью этого портала. В сообществе.
на что влияет, как измерить и как повысить — 4 рабочих способа / Хабр | Веб-студия Nat.od.ua
на что влияет, как измерить и как повысить — 4 рабочих способа / Хабр
Про важность вовлечения аудитории в социальных сетях поговорили уже все — повторяться не будем. А что насчет сайта? В статье расскажем, какую роль вовлеченность играет в web продвижении, на какие метрики обращать внимание при продвижении сайта и какая есть корреляция между вовлеченностью и продажами.
Добрый день, коллеги! Меня зовут Наталья Вязигина, я руководитель агентства цифрового маркетинга «Аксиома Веб». С 2017 года мы занимаемся комплексным продвижением бизнеса в интернете, предоставляем услуги контекстной и таргетированной рекламы, разработки сайтов, SEO-оптимизацией и др. В соцсетях мы тоже работаем, но в этой статье касаться их не будем. Также здесь вы не встретите упоминания видеоблогов или видео рекламы, но найдете полезные рекомендации по повышению конверсионности сайта.
Основные метрики оценки вовлеченности
Чтобы определить, насколько ваш сайт приятен, удобен и интересен пользователю, нужно посмотреть на следующие показатели в метрике:
Глубина просмотра. Этот показатель указывает на количество страниц, которые открыл пользователь за одну сессию на сайте. Чем их больше, тем, разумеется, лучше: значит, посетителю интересно получить больше информации, а у вас есть больше возможностей его «догреть».
Отказы. Это доля посетителей, которые провели на сайте менее 15 секунд после его загрузки. Чем % выше, тем больше поводов для беспокойства: вероятно, стартовая страница чем-то сразу отталкивает пользователей.
Время на сайте. Тут все просто: чем дольше пользователь находится на сайте, тем лучше он может изучить представленную информацию. По опыту наших клиентов, для совершения целевого действия посетителю необходимо в среднем 8-10 минут активного просмотра сайта, но в зависимости от ниши этот показатель может варьироваться. Однако при анализе времени важно понимать, как этот показатель рассчитывается. Например, Яндекс.Метрика считает время от открытия первой страницы сайта до открытия последней. Так, если пользователь провел на странице выхода хоть час, этот показатель не будет зафиксирован.
Доля повторных посещений. Чем она выше, тем интереснее ваш ресурс для пользователей. Кроме того, мало кто принимает решение о покупке при первом же знакомстве с компанией. Цикл сделки и «прогрева» клиента может быть достаточно долгим, и, чем чаще за это время он будет посещать ваш сайт, тем более вероятно совершит покупку. Если по статистике преобладающее большинство посещений «одноразовые», стоит задуматься, почему пользователи не хотят к вам возвращаться.
Что дает вовлеченность
Считается, что чем дольше пользователь находится на сайте и чем чаще возвращается, тем проще ему принять решение о покупке. Мы решили проверить это утверждение. Для наглядности взяли статистику сайта, который разработали для интернет-магазина клиента. В Яндекс.Метрике мы видим, что среди всех посетителей на сайте показатель отказов достаточно высокий, а среднее время на сайте небольшое.
Теперь выберем фильтр — посетители, которые добавили товар в корзину. Страница обновляется, и перед нами совсем другая картина: доля отказов стала более чем в 10 раз ниже, глубина просмотра выросла в 3 раза, а среднее время на сайте увеличилось почти в 6 раз.
Получается, что только качественно вовлеченные пользователи из просто посетителей готовы перейти в статус клиентов и совершить покупку.
Кроме того, уровень вовлеченности влияет на SEO-оптимизацию сайта и помогает оказываться в ТОПе выдачи органически. Алгоритм поисковых систем прост: чем выше показатели вовлеченности, тем больше, значит, сайт интересен, релевантен и удобен пользователям. Значит, его можно рекомендовать для посещения по профильным запросам.
Как повысить вовлеченность на сайте
Повысьте юзабилити сайта. Зачастую, чтобы понять, что не так, нужно провести анализ и оценить, насколько быстро загружаются страницы, удобно ли выстроена система навигации, насколько легко найти нужную информацию на сайте и т.д. Кроме того, с помощью метрики можно увидеть, на каком этапе пользователь чаще всего покидает страницу и, возможно, выявить какие-то типичные проблемы, которые мешают конвертировать посетителя. Мы, например, проводим такой анализ для клиентов бесплатно: выявляем ошибки и рекомендуем, как их устранить в короткие сроки. Еще лучше — подумать о юзабилити заранее, на этапе создания сайта. Не экономить на UX/UI-дизайне, упростите навигацию, добавьте поиск по сайту и не забыть про адаптивную версию сайта для различных видов устройств.
Улучшите качество контента. Это касается как текстов, так и цепляющего визуала. Сделайте так, чтобы пользователю было интересно разглядывать и читать ваш сайт, тогда он проведет на нем больше времени, и просмотрит больше страниц.
Добавьте дополнительные элементы вовлечения. Это могут быть специальные чат-виджеты, квиз-формы или интегрированные интерактивы в удобном для пользователя формате. Например, в формате привычных сторис. Их можно добавить на сайт или в мобильное приложение, а дальше наполнять контентом, добавлять опросы/реакции/таймеры обратного отсчета/ссылки и другие стандартные элементы, которые «провоцируют» пользователя проявить активность. Для своих клиентов мы используем для такой интеграции сервис Storyly. По статистике, он позволяет повысить вовлеченность аудитории и конверсионность сайта в среднем на 31%.
Удерживайте внимание пользователя легкой анимацией. Движение элементов при наведении на них мышкой, «всплывание» при скроллинге страницы, gif-анимация — все это невольно привлекает внимание пользователя. Казалось бы, чем больше анимации, тем лучше, но важно и не переборщить. Если на вашем сайте все скачет-прыгает, посетитель может сбежать с него и никогда больше не вернуться.
Работать над вовлеченностью можно как самостоятельно, так и с привлечением профильных специалистов. Но помните, что чем раньше вы займетесь этим вопросом, тем меньше потенциальных клиентов упустите.
два интересных сниппета / Хабр | Веб-студия Nat.od.ua
два интересных сниппета / Хабр
Hello, world!
В этой небольшой заметке я хочу поделиться с вами двумя сниппетами, которые показались мне очень интересными. Первый сниппет представляет собой пример реализации простой реактивности (signal), второй — способ предотвращения несогласованности данных в результате состояния гонки (race condition). Первая конструкция используется в SolidJS (с некоторыми дополнительными оптимизациями), вторая — заимствована из одного рабочего проекта.
Интересно? Тогда прошу под кат.
Начнем с сигнала.
Взгляните на следующий код:
let currentListener
function createSignal(initialValue) {
let value = initialValue
const subscribers = new Set()
const read = () => {
if (currentListener) {
subscribers.add(currentListener)
}
return value
}
const write = (newValue) => {
value = newValue
subscribers.forEach((fn) => fn())
}
return }
function createEffect(callback) {
currentListener = callback
callback()
currentListener = null
}
Функция createSignal создает “реактивное” значение, а функция createEffect принимает коллбэк, который выполняется при изменении этого значения.
Пример использования данного сниппета:
const = createSignal(0)
const button = document.querySelector(‘button’)
createEffect(() => {
button.textContent = count()
})
button.addEventListener(‘click’, () => {
setCount(count() + 1)
})
При нажатии кнопки значение счетчика увеличивается на единицу. Это приводит к обновлению текста кнопки.
Таким образом, код работает, как ожидается. Но… почему? Как это работает? 😮
Давайте разбираться.
const = createSignal(0)
count и setCount — это, соответственно, функции чтения и записи (read и write) значения переменной value (“живущей” в замыкании (closure)), возвращаемые createSignal(). Значением value здесь становится 0.
createEffect(() => {
button.textContent = count()
})
Это, пожалуй, самая хитрая строчка в коде.
- createEffect() записывает переданный коллбэк в переменную currentListener;
- createEffect() запускает коллбэк;
- button.textContent = count() выполняется справа налево;
- count() (read()) добавляет currentListener в набор subscribers (делает коллбэк подписчиком);
- count() возвращает значение value;
- значение value становится текстом кнопки;
- наконец, createEffect() очищает currentListener.
button.addEventListener(‘click’, () => {
setCount(count() + 1)
})
Здесь нас интересует следующая строка:
setCount(count() + 1)
Она также выполняется справа налево:
- count() (read()) возвращает значение value (на данном этапе currentListener === null, поэтому никаких коллбэков в subscribers не добавляется);
- setCount(1 + 1) (или write(2)) обновляет value значением 2;
- setCount() запускает все коллбэки, содержащиеся в subscribers (() => { button.textContent = count() }).
Ловкость рук и никакого мошенничества 😉
Теперь поговорим о несогласованности данных в результате состояния гонки.
Начнем с общего описания проблемы.
- На одной странице имеется возможность модификации данных, хранящихся на сервере, несколькими способами;
- после каждой модификации от сервера запрашиваются свежие данные (выполняются одинаковые запросы);
- при получении ответа на каждый запрос обновляется локальное состояние (данные, хранящиеся в памяти на клиенте), которое используется для рендеринга компонентов;
- модификации (и, соответственно, запросы) могут выполняться очень быстро;
- предположим, что выполняется 2 модификации, вторая через секунду после первой;
- на сервер отправляется 2 запроса;
- первый обрабатывается сервером 3 секунды, второй — 1 секунду;
- ответ на второй запрос приходит через 2 (1 + 1) секунды (обновление локального состояния -> повторный рендеринг), а ответ на первый запрос — через 3 (0 + 3) секунды (обновление локального состояния -> повторный рендеринг);
- пользователь видит состояние, актуальное после выполнения первой модификации (sic!);
- данные на клиенте не согласованы (не совпадают) с данными на сервере.
Набросаем абстрактный пример.
Разметка:
0
Last button clicked:
Скрипт:
// функция, возвращающая случайное целое число в заданном диапазоне
const randInt = (min, max) => Math.floor(min + Math.random() * (max – min + 1))
// функция, имитирующая обработку запроса сервером
const sleep = (ms) => new Promise((res) => setTimeout(res, ms))
const = createSignal(0)
const counter = document.getElementById(‘counter’)
const lastBtn = document.getElementById(‘last-btn’)
// текст параграфа обновляется при каждом изменении значения счетчика
createEffect(() => {
counter.textContent = count()
})
// функция, имитирующая получение данных от сервера
// задержка может составлять от 1 до 6 секунд
const getData = async () => await sleep(randInt(1, 6) * 1000)
// функция, имитирующая отправку запроса и
// обновление локального состояния при получении ответа
const update = async (n) => {
// в реальном приложении `n` будет возвращаться `getData()`
await getData()
setCount(n)
}
document.querySelectorAll(‘button’).forEach((btn) => {
// каждая кнопка обновляет значение счетчика своим текстом (2, 4 или 6)
btn.addEventListener(‘click’, () => {
const n = btn.textContent
// отображаем значение последней нажатой кнопки
lastBtn.textContent = n
// обновляем значение счетчика
update(n)
})
})
Демо:
При быстром нажатии нескольких кнопок возникает “состояние гонки”, приводящее к тому, что итоговое значение счетчика может быть любым из трех: 2, 4 или 6. Мы не знаем, каким точно будет значение счетчика и не можем полагаться на него при производстве дальнейших вычислений. Кроме того, заметно, что текст параграфа все время обновляется новыми значениями. Это не есть хорошо. Значение счетчика (текст параграфа) должно быть таким же, как текст последней нажатой кнопки (последней модификации/запроса). Как этого достичь? Можно ли сделать это простыми средствами или без библиотеки не обойтись?
Сниппет:
class Query {
// переменная для хранения последнего промиса – запроса
#lastPromise
async last(promise) {
// записываем промис в переменную
this.#lastPromise = promise
// ждем ответа от сервера
const result = await promise
// индикатор того, что разрешенный промис является последним запросом
const isLast = this.#lastPromise === promise
// возвращаем результат и индикатор
return
}
}
Создаем экземпляр Query:
const query = new Query()
Оборачиваем вызов getData() в метод last и обновляем значение счетчика только в том случае, если индикатор isLast имеет значение true, т.е. данные для обновления являются ответом на последний запрос:
const update = async (n) => {
const = await query.last(getData())
if (isLast) {
// в реальном приложении в `setCount()` будет передаваться `result`
setCount(n)
}
}
Демо:
Теперь значение счетчика всегда будет идентично тексту последней нажатой кнопки (результату обработки последнего запроса), а обновление значения счетчика выполняется однократно.
Таким образом, мы не только обеспечиваем согласованность данных, что хорошо для пользователя*, но также предотвращаем лишний повторный рендеринг, что хорошо для производительности приложения.
* согласованность данных — это хорошо не только для пользователя, но также для сервера, поскольку для последующих модификаций серверных данных вполне могут использоваться данные, хранящиеся на клиенте, и т.п.
Следует отметить, что приведенное решение не является идеальным, поскольку “лишние” запросы все равно выполняются (нагрузка на сеть). Более оптимальным является техника под названием “дедупликация запросов”, когда мы отменяем запросы, находящиеся в процессе выполнения, например, с помощью AbortController.signal, и выполняем только последний запрос (понятно, что выполняющийся и новый запросы должны быть идентичными)*. Данный способ намного сложнее, чем рассмотренный. На мой взгляд, для дедупликации запросов лучше использовать готовые решения типа React Query, но там вас ждет одна из самых сложных задач в веб-разработке — правильная работа с кэшем 😊 Существуют и другие способы борьбы с состоянием гонки.
* или просто не выполняем запросы в течение определенного времени, когда уверены, что запросы будут множественными (привет, debouncing)
Пожалуй, это все, чем я хотел с вами поделиться.
Надеюсь, вы узнали что-то новое и не зря потратили время.
Happy coding!
Трудности маскирования текстового поля / Хабр | Веб-студия Nat.od.ua
Трудности маскирования текстового поля / Хабр
Один античный оратор говорил, что всем людям свойственно ошибаться. Прошло много веков, а человек продолжает совершать ошибки каждый день. Даже беглое заполнение формы на сайте не обходится без опечаток.
Хороший UI/UX помогает пользователю избежать большинства таких проблем. Инструментов контроля огромное количество, сегодня расскажу про один их них — создание маски для поля ввода силами Javascript.
Да что такое, эта ваша маска
Представим ситуацию, что сайт хочет запросить данные, которые должны содержать только цифры. Например, для поля ввода цены товара мы не хотим разрешать пользователю вводить буквы и прочие символы.
Читатель может вспомнить, что в HTML уже есть . Открываем наш любимый Chrome, страницу с документацией элемента, пробуем ввести что-то кроме цифр и точки или запятой… ура! Браузер запрещает это сделать, и при попытке ввода невалидного символа значение инпута не изменяется. Кажется, что проблема решилась быстро, пора и статью завершать на этой удачной находке!
Но погодите. Давайте откроем Firefox и повторим те же действия. К несчастью, этот браузер менее строг к вводу невалидных символов и лишь при отправке формы начинает выдавать предупреждение:
То, что сделал Firefox в нашем примере, — это издевательство один из способов предупредить пользователя о невалидном значении. Но, кажется, не очень своевременный. А вот Chrome показал один из примеров маскирования инпута.
Маска — это контролирование вводимых пользователем символов, чтобы значение текстового поля соответствовало определенному правилу или паттерну.
Пример с вводом чисел — один вид маски из множества возможных. Существуют более сложные примеры: ввод времени, даты или телефонного номера.
Маска может не только предотвратить ввод невалидных значений, но и помочь пользователю добавить нужные символы. Например, поставить пробелы между тысячными разрядами числа или разделители между днем/месяцем/годом в дате.
Маска может даже угадывать намерения пользователя: подставлять в поле ввода точку в качестве разделителя целой и дробной части, если в поле ввода пользователь нажимает на клавиатуре букву «ю».
Маскирование инпута — это скорее про повышение UX, чем про валидацию данных. Опытный злоумышленник способен обмануть любое фронтовое веб-приложение. Поэтому дополнительная финальная валидация данных на беке всегда нужна!
Ингредиенты маски
Нужно разобраться в большом списке событий, которые возникают у элементов и , чтобы контролировать вводимые значения в текстовом поле. Расскажу об основных.
Keydown — событие, которое возникает каждый раз при нажатии любой клавиши с клавиатуры. Оно содержит полезное свойство key, в котором и хранится информация о введенном значении. И самое главное — событие можно отменить через event.preventDefault!
Кажется, что такое событие идеально подходит для маскирования инпутов и полностью закрывает все необходимые задачи. Но есть два недостатка:
Существование системных клавиш создает ряд проблем в использовании события для нашей задачи. Например, пользователь будет копировать значение инпута через Ctrl + C и получится «ложноположительное» для нашей задачи срабатывание keydown. Требуется много усилий, чтобы отфильтровать нужные события для маски.
Событие не может контролировать ввод значений через браузерный автофил и выбор предлагаемого значения с нативной клавиатуры мобильного устройства.
А еще для нашей задачи при использовании keydown придется звать на помощь paste и drop события, которые обсудим чуть позже.
Keypress — событие, идентичное keydown-событию, но с одним приятным исключением: оно срабатывает только при нажатии клавиш, порождающих новое значение в поле ввода. Keypress не стреляет ненужным нам нажатием системных клавиш и полностью решает первый недостаток keydown-события.
Но и в этом событии есть минус: на странице документации висит предупреждение, что свойство устарело и больше не поддерживается браузерами. Если зайти в современный браузер, видно, что свойство все еще существует и работает как задумано, но в любой момент его поддержка прекратится. Поэтому такое событие отбрасываем и больше не рассматриваем для использования.
Paste и Drop — события, про которые часто забывают. Пользователь может изменить значение текстового поля не только нажатием клавиш с клавиатуры, но и через вставку из буфера обмена и сбросив текст в инпут. Поэтому paste и drop нужно использовать, когда маскирование инпута происходит с обработкой keydown.
Change — сообщает об изменении значения инпута. Но момент срабатывания события для текстовых инпутов происходят только после потери фокуса. То есть, если пользователь сфокусируется на инпуте и попытается напечатать слово «привет», у инпута сработает шесть событий keydown и только одно change — при условии, что пользователь все же уберет фокус с поля. Несмотря на многообещающее название и хорошую поддержку браузерами, это событие нам не подходит.
Input — полезное для нас событие, которое решает многие проблемы упомянутых ранее «коллег». Плюсы события:
Input срабатывает после каждого изменения значения текстового инпута, не дожидается потери фокуса, как Change, и не требует прочих условий.
Нажатие системных клавиш не триггерит событие, если значение не меняется.
Контролирует все манипуляции с текстовым полем: событие сработает и при вставке значения из буфера обмена, и при браузерном автозаполнении, и при сбрасывании текста в инпут мышкой, и при выборе подсказок с нативной мобильной клавиатуры.
Хорошая поддержка всеми современными браузерами.
Ложка дегтя с Input в том, что событие нельзя отменить свойством preventDefault, потому что событие сообщает об уже случившимся факте. А прошлое изменить нельзя!
Можно запоминать значение поля и позицию его каретки до изменений и в случае отмены программно обновлять поле старым значением. Но все это приводит к не очень хорошей поддерживаемости кода.
В интернете много библиотек, упрощающих маскирование текстовых полей. Большинство популярных «взрослых» решений используют комбинации описанных нативных событий со всеми преимуществами и недостатками. Но что, если бы появилась необходимость создать новую библиотеку в 2023 году? Повторила бы она опыт своих предшественников? Есть припрятанный туз в рукаве, который мы еще не успели обсуди, — beforeinput-событие.
Рецепт современной маски
Beforeinput — молодое событие, которое идеально подходит для маскирования инпутов. В марте 2017 года его подарил нам… Safari. Да, этот браузер умеет не только вызывать слезы фронтенд-разработчиков, но иногда и первым радовать их новыми фичами.
Следующим это свойство реализовал Chrome, а позже подхватили и другие браузеры. Отстающим крупным игроком стал Firefox, который обеспечил поддержку события лишь к 2021 году. На момент написания статьи beforeinput имеет хорошую поддержку современными браузерами и работает в 94,59% от всех используемых версий браузеров.
У beforeinput масса достоинств для нашей задачи:
Срабатывает только при нажатии клавиш, приводящих к изменению инпута.
Поддерживает все прочие возможности изменить инпут помимо взаимодействия с клавиатуры — у события есть поле inputType, которое может принимать различные значения: insertText, insertFromDrop, insertFromPaste, deleteContentBackward, deleteContentForward и др.
Событие можно отменить.
Кажется, что взяли все самое лучше от прошлых событий и объединили все в новом!
Мы получили современный рецепт для маскирования текстовых полей. Большую часть валидации значения можно производить в beforeinput, а в input завершать мелкие калибровки полученного события.
element.addEventListener(‘beforeinput’, event => {
switch (event.inputType) {
case ‘deleteContentBackward’:
case ‘deleteContentForward’:
case ‘deleteByCut’:
return handleDelete(event);
case ‘insertLineBreak’:
return handleEnter(event);
case ‘insertFromPaste’:
case ‘insertText’:
return handleInsert(event);
// …
// Many other cases
// …
}
});
Подчеркну важную особенность. Если нужно отменить beforeinput-событие — например, чтобы самостоятельно программно обновить инпут нужным значением, то отменится после него и последующее input-событие. Такое поведение ожидаемо с точки зрения логики.
Иногда мы хотим сообщить о случившемся внешним наблюдателям. Хорошим примером может стать фреймворк Angular: у него есть свои инструменты для работы с формами, которые полагаются на факт, что событие input произойдет на каждое изменение значения инпута. Проблема имеет множество решений, одно из них — при отмене beforeinput-события с последующим программным обновлением текстового поля можно самостоятельно сделать element.dispatchEvent(new InputEvent(…)).
Коллекция библиотек Maskito
Выявленную формулу для создания масок мы применили в новой разработке Maskito. Это коллекция библиотек, написанных на Typescript. Главная библиотека @maskito/core создана без использования внешних зависимостей, что позволяет применять ее в любом vanilla JavaScript проекте.
В Maskito есть библиотека @maskito/kit — набор уже готовых конфигурируемых масок. А еще мы создали отдельный опциональный пакет @maskito/angular на случай, если вы захотите использовать разработку в своем Angular-проекте. Подробнее обо всех возможностях Maskito читайте в документации. А в следующей статье расскажу подробнее, что у нас из этого получилось, и покажу немного реального кода.
GitHub – Tinkoff/maskito: Collection of libraries to create an input mask which ensures that user types value according to predefined format.
github.com
Maskito уже публикуется в npm под нулевой мажорной версией и готово к использованию. Мы проводим финальные тесты, ищем и исправляем баги, чтобы совсем скоро опубликовать первую мажорную версию. Сохраняйте библиотеку в заметки — надеемся, что она пригодится вам в следующем проекте!
Как мог бы выглядеть тёмный Хабр / Хабр | Веб-студия Nat.od.ua
Как мог бы выглядеть тёмный Хабр / Хабр
Пользователи часто спрашивали тёмную тему на Хабре. Насколько мне известно, команда разработчиков в курсе этой потребности, но пока не готова представить реализацию. Тем не менее, она есть.
Хотел было назвать “Тёмная сторона Хабра”, но всё же передумал. Извините.
Мы обсуждали этот момент с @Boomburum. Он буквально вытягивал меня на написание поста, на что я давал самый сильный отпор, на который был способен. Аргументировал тем, что информации настолько мало, что на пост не тянет. Новость из этого материала тоже так себе. Что ж, как вы видите, Алексей умеет убеждать.
Итак. Чего это будет стоить? Да совсем ничего. Всего лишь установить FOSS Browser. Никакие плагины и прочие штуки не нужны. Понимаю, пользователи iOS остаются немного за бортом, но тут я бессилен. Для тех, кому уже не терпится, показываю, что из этого получилось у меня на Xiaomi Mi A2 с тёмной системной темой оформления и FOSS Browser, поддерживающим эту настройку Android.
Лента. Обратите внимание, уровень сложности корректно отображается. Пиктограммы остальных элементов: время чтения, количество просмотров, закладок, комментариев с точкой наличия новых — все они слегка затемнены. Внизу выглядывают «Истории», которые не бьют по глазам белым.
Плашка с информацией об авторе комментария. Для появления нужно сделать долгий тап по нику, затем закрыть контекстное окно браузера, предлагающее скопировать ссылку и сделать прочие браузер-специфичные вещи с этой ссылкой. Взамен под этим конткестным меню откроется мини-плашка с информацией об авторе. Опять же, всё работает. Цветовая схема не ломается.
Сам пост. Элемент подзаголовка, как видим, тоже работает. Моё субъективное мнение, подзаголовок даже выделяется корректно чуть более насыщенным белым. Картинки, конечно, показывает, как есть, автоматической конвертации в прозрачные жпеги и PNG не будет, тут уж авторы браузера бессильны.
Профиль пользователя. Я специально скрыл часть, где показан ник, поскольку это не важно. Все плашки хабов и значков (badge) отображаются корректно.
Правое меню, которое открывается при нажатии на пиктограмму пользователя в шапке. Левое, отображаемое по нажатию на значок справа от “Хабр” выглядит точно так же.
Мне пришлось добавить Хабр в доверенные сайты в FOSS Browser, чтобы залогиниться. Но это позволило предоставить скриншоты того, как выглядят полноценные меню. Это меню, вызываемое “гамбургером” в шапке. Оно скроллится, если не помещается в экран.
А это меню пользователя, вызываемое по пиктограмме, установленной в профиле. Шапка, правый крайний элемент.
Это редактор статьи. Думаю, в редакторе комментария будет то же самое. Панелька оформления корректно прибивается к нижнему краю экрана и не скроллится за его пределы. Элементы панельки работают, я проверял. По вводу слэша в начале пустой строки так же, как и в полноразмерном браузере, показывается меню возможных действий.
А вот картинку подгрузить с телефона не получилось. Я пробовал несколько раз, даже грешил на свой домашний вайфай и пробовал через сотовую связь. Либо это шалит XPrivacy или что-то подобное, либо всё же где-то есть недоработки в скриптах, которые в WebView приводят к невозможности заливки в habrastorage. В связи с этим следующий за этим скриншотом абзац, показанный на скриншоте в изначальной редакции, был немного изменён.
Можете попробовать сами, команда Хабра настолько молодцы, что сайт почти полноценно (не заливаются изображения) работает даже в Android WebView, на котором основан FOSS Browser:
переходы по ссылкам в пост или комментарии без перезагрузки
возврат в предыдущий экран в ту же позицию, откуда был переход, без необходимости скроллить от самой шапки вниз
между прочим, корректно отображается плашка “Мы кое-что обновили, перезагрузите страницу”, и так же корректно работает нажатие ссылки в ней
Полагаю, неплохой выход для тех, кто жаждет смотреть Хабр в тёмном оформлении. Попробуйте, возможно, это именно то, что вы, не устающие напоминать команде Хабра о реализации тёмной темы, искали. Возможно, именно эту цветовую схему возьмёт за основу команда Хабра, когда предъявит на обозрение свою реализацию. Я посоветовал Алексею сформировать фокус-группу из наиболее доверенных пользователей (не имея, конечно же, в виду себя), которые могли бы потестировать их вариант решения, но это уже будет совсем другая история.
Спасибо за внимание.
Ловим и исправляем «Internal Server Error» — apache2 / Хабр | Веб-студия Nat.od.ua
Ловим и исправляем «Internal Server Error» — apache2 / Хабр
Одна из самых популярных и при этом не всегда логирующаяся ошибка apache2 – “Internal Server Error”. В этой статье разберу возможные причины и решения из интернета и личного опыта. Иногда эта ошибка весьма не очевидна.
Все ситуации описаны для операционной системы Ubuntu Linux 22.10.
Ну и, конечно, первое, что вы должны сделать это попробовать перезапустить службу.
sudo systemctl restart apache2
Если это не помогло, то двигаемся дальше.
1. Конфигурация1.1. Ошибка конфигурации веб-приложения
Прежде всего рекомендую сделать запрос к вашему сайту локально, прямо с сервера.
curl localhost:80
Если вы не видите “Internal Server Error”, а видите html страницу, то 99% проблема в файлах конфигурации и настройках доступа.
Проверьте файл: /etc/apache2/sites-available/your_app.conf
где your_app.conf – название вашего файла конфирурации, либо стандартный conf файл – «000-default.conf»
Как он выглядит:
/etc/apache2/sites-available/your_app.conf
Строка «Require ip 127.0.0.1» говорит, о том, что разрешено подключение только с локального хоста.
Решение: замените строку «Require ip 127.0.0.1» на «Require all granted»
Так же «Require all granted» должна быть в /etc/apache2/apache2.conf
/etc/apache2/apache2.conf
1.2. Старый конфигурационный файл
Возможно, создавая новый файл конфигурации веб-приложения он не был выбрал или включен.
Решение:
1. Выключаем стандартный файл конфигурации
2. Включаем свой
3. Перезагружаем apache2
a2dissite 000-default.conf
a2ensite your_app.conf
systemctl restart apache2 2. Права доступа
Проблема возникает тогда, когда права файлов вашего проекта не соответствуют ожидаемым.
2.1. Владелец файлов
Файлы вашего проекта не принадлежат пользователю www-data.
Папка проекта (your_project) не принадлежит пользователю www-data
Решение:
sudo chown -R www-data:www-data your_project/
P.S. Случается так, что apache2 при установке не создаёт пользователя www-data, в этом нет ничего страшного, можно обойтись и без его создания. Права отлично функционируют и сайт работает.
2.2. Права файлов
Ошибка так же может возникать когда владелец установлен верно, но всё равно не имеет доступа к файлам из-за настроек прав доступа.
Решение:
sudo chmod -R 755 your_project/2.3. Веб-приложение работает с файлами системы
“Internal Server Error” возникает, когда ваше веб-приложение взаимодействует с файлами системы у которых нет прав на исполнение. Звучит также странно как есть на самом деле, почему я не могу читать файлы у которых есть право на чтение? До сих пор не понимаю. Но проблема эта решается, если выдать файлам право на исполнение.
Решение:
sudo chmod -R 777 your_project/files/3. Ошибка в коде вашего веб-приложения
Также частая ситуация, когда apache2 настроен правильно, но всё равно возникает эта нелогирующаяся ошибка “Internal Server Error”, тогда следует проследить отрабатывает ли вообще ваше приложение.
Например, если оно не может подключиться к базе данных, то вы, всё равно, увидите “Internal Server Error”. Стоит проверить верный ли пароль и есть ли доступ к базе данных. А возможно, что просто веб-приложение запустилось раньше базы данных, в этом случае поможет простая перезагрузка apache2.
Если ваше приложение взаимодействует с внешним ресурсом, и он оказывается не доступен или привышен лимит ожидания ответа, то вы снова увидите “Internal Server Error”.
4. Переустановка apache2
Если вам не помогли все вышеуказаные действия, то возможно проблема с самим apache2, тогда нужно попробовать его переустановить:
sudo apt update
sudo apt install –reinstall apache2
Если вы не решили свою проблему, то вот еще возможные причины и направления для дальнейшего поиска.
Ошибка синтаксиса .htaccess. Если вы используете Apache в качестве веб-сервера, то, скорее всего, у вас есть .htaccess файл в корневом каталоге вашего сайта. Неверный синтаксис или несуществующая директива модуля может привести к ошибке 500.
Проблемы с плагинами и темами. Если вы используете WordPress или аналогичную CMS, ошибка 500 может появиться после обновления или изменения плагина или темы.
Проблемы с сервером. Поврежденная файловая система или исчерпанная память могут привести к ошибке 500.
Модули Node.js. Если у вас есть сайт на основе Node.js, обновление модулей может вызвать внутреннюю ошибку сервера 500.
Довольно часто вредоносный код, внедряемый на ваш сайт, приводит к ошибке 500.
Несовместимый модуль. Загрузка несовместимого модуля PHP или Apache вызывает ошибку 500.
P.S. Надеюсь, кому-то помог. Удачи.
Оцифровка процессов компании/проекта. Особенности рабочего стола в Notion в проектах с большим количеством процессов | Веб-студия Nat.od.ua
Оцифровка процессов компании/проекта. Особенности рабочего стола в Notion в проектах с большим количеством процессов
Рабочий стол сотрудника называется рабочим потому, что это одна из главных точек входа сотрудника в вовлеченные им процессы. Чем больше процессов оцифровываются в компании, тем большее число баз данных создается в системе управления.
В компаниях с высокой степенью оцифровки деятельности, чаще всего встречаются следующие процессы, в рамках которых фиксируются данные в базах данных Notion:
Команда (кто участвует в процессах компании)
Проекты (над чем работает команда)
Продукты (что создает команда)
Задачи (дефрагментация прогресса в результатах действий команды, который нужно достичь)
События (любые события, включая даты наступления ключевых показателей)
OKR / Цели (результаты деятельности команды спроецированные во времени)
Продажи (что и сколько продает команда)
Клиенты (кому продает команда)
Партнеры (кто участвует в деятельности проекта / компании)
Документы (нормативная, правовая, проектная и организационная база компании)
Wiki (база всесторонних знаний команды)
Отчеты (результаты достижений ключевых показателей процесса за период времени)
Совещания — на основе базы событий (встречи команды, на которых рассматриваются процессы, разбираются препятствия, задаются вопросы, ставятся дополнительные задачи и цели, принимаются решения)
Отдельно можно отметить группу состоящую из:
Inbox (входящая неотсортированная информация)
Вопросы (контекст, требующий разъяснения кем-то из членов команды)
Идеи / Предложения (инициативы, которыми делится команда)
Решения (согласие команды с действием определенной нормы)
Стоперы / Проблемы (все что препятствует прогрессу)
Письма (информация в виде документов, адресованная лично)
Давайте зададимся вопросом, для чего именно оцифровываются процессы компании. Явно не для бюрократии или, чтобы занять PM дополнительной рутиной. Прежде всего, это делается для:
определения реального статуса процессов в настоящем времени
выявления скрытых процессов и ответственных за них
привлечения внимания к процессу, с целью достижения желаемого результата
В отсутствие такого подхода команда сваливается в элементарный хаос, в котором реальный процесс затоплен факторами неизвестности, а руководители среднего звена получают выговоры за недостижение намеченных результатов или провала сроков.
Что дает оцифровка процессов?
ясность реального положения дел и текущего статуса процесса
возможность более точного прогнозирования событий
возможность оперативного реагирования с учетом статуса процесса
возможность создавать подробные отчеты по ключевым процессам деятельности
возможность проводить статистический анализ эффективности сотрудников
Большое число процессов, с которыми взаимодействует сотрудник должно иметь удобную архитектуру в рамках системы управления. Речь прежде всего о том, чтобы сотрудник имел возможность быстро получать информацию, к которой он имеет отношение.
В системах управления на базе Notion для этой цели создаются рабочие столы сотрудников, дешборды разделов и отделов. Ниже приведены примеры рабочих столов компаний разной деятельности.
Пример рабочего стола IT компанииРабочий стол интернет магазинаРабочий стол в маркетинговом агентствеСтроительная компания (мобильный интерфейс)
На примере рабочего стола IT компании разберем структуру и назначение его информационных блоков.
Сверху главное сквозное меню, позволяет осуществить переход в нужный раздел.
Навигационный блок удобен для моментального перехода к нужному процессу сотрудника минуя скроллинг по длинной странице рабочего стола. При этом фишкой данного блока является то, что включенные в название процессов ссылки ведут на базы данных, автоматически отфильтрованные именно на процессы конкретного сотрудника.
Делается это в рамках настройки шаблона рабочего стола один раз и далее ссылки генерируются автоматически для каждого сотрудника, применяющего шаблон к собственной карточке в базе команды.
Блок создания предстоящих событий
Данный блок нужен для создания митингов. Слева создаются предстоящие события, а блок справа удобен для назначения событий на ближайшие часы.
В середине страницы находятся блоки, включающие процессы с которыми пользователь взаимодействует не так часто.
Так слева мы видим предстоящие и прошедшие события (скрыты отдельным view базы), а ниже группу view базы спринтов (все, 🟢 – текущий и предстоящий). Справа как раз процессы с наименьшей частотой контактов, поэтому все они внутри toggle списков.
“Информационная доска” также скрыта в toggle список и включает группировку процессов, относящихся к оперативной информации: (вопросы, предложения / идеи), а также решения и стоперы (проблемы).
Все процессы имеют общие признаки состояний, таких как:
⌛ – ожидание
✔ – наличие ответа
⚡ – в процессе
👌🏻 – (вопрос решён, решение принято)
👍🏻👎🏻 – принятые или отклоненные идеи
⛔ – наличие нерешенной проблемы
В некоторых системах, где практикуется внутренний документооборот включающий: письма, приказы, распоряжения и требования создается дополнительный блок “сообщений”, в котором наличие новых сообщений для сотрудника отмечаются соответствующей индикацией. Когда сотрудник знакомится с документом любого из данных типов индикация пропадает. Цель индикации привлечь внимание сотрудника к ознакомлению с документом.
Подобные приемы индикаций также часто создаются непосредственно в карточках членов команды, цель которых сообщать о новых процессах, а также тех, которые еще не завершены.
Непонятными, используемые эмодзи выглядят только на первый взгляд. К подобным индикациям команда привыкает очень быстро.
✅ – число незакрытых задач
❓ – число вопросов, на которые нет еще ответа
❗ – число проблем, на которые нет реакции
💡 – Предложения, с которыми еще не ознакомились
📃 – Число писем, на которые нет реакции
⏰ – Число предстоящих событий, которые не подтверждены
еще пример индикации процессов в карточке члена команды
Здесь отражаются:
даты спринта
🔳 число задач
🟡 число возможных балов
❓ наличие/отсутствие незакрытых вопросов
✅ число закрытых задач
🟡 число полученных балов
Вернемся на рабочий стол, который рассматривается в данной статье как пример. Под блоком информации находится главный и самый структурированный блок — задачи.
Под названием блока находится локальное меню, позволяющее быстро увидеть задачи определенных типов, состояний и времени.
🟢 – задачи на сегодня
🔵 – эпики
⚡ – задачи текущего спринта
✅ – завершенные задачи
⚠ – просроченные задачи
⌛ – беклог (задачи оставленные на потом)
Далее
“все задачи” – содержит таблицу всех задач сотрудника в разных статусах
⚡ спринты — показывает все спринты сотрудника
доска — содержит все задачи сотрудника в формате board
♻ повторяющиеся — содержит шаблон, создающий список повторяющихся задач (еженедельные / ежемесячные), которые далее переносятся в базу задач
Отдельно стоит блок задач на сегодня, в котором задачи находятся на поверхности и не спрятаны в структуру раскрываемой папки. В нем также, все задачи разбиты на view задач относящихся к сегодня, но имеющих разный статус и тип.
Ниже находится группа папок, сортирующая задачи по большим группам:
🟢⚡ – текущий спринт
◀⚡ – прошлый спринт
⚠ – просроченные, 🔵 эпики и ⌛ беклог
Для кого-то может показаться это ужасным UX из-за количества папок и view баз данных. Но, на практике, к подобной структуре привыкаешь очень быстро, а экономия времени для доступа к нужному статусу или типу процесса оправдывает столь массивную архитектуру блока персональных задач.
Даже для пользователей Notion данная структура может показаться избыточной, но это скорее не от того, что она не практична, а в силу отсутствия опыта работы с подобными рабочими столами. Не в каждой организации, использующей Notion есть тот, кто в состоянии организовать рабочий процесс таким образом.
Сделать такой рабочий стол достаточно сложно, учитывая число предварительных настроек и множество включенных в него процессов. Для этого требуется опыт и немало знаний.
Если говорить об использовании Notion, как системы управления проектом при наличии специалиста можно создать шаблон процесса практически любой функциональности. Ниже приведен пример одного из лучших шаблонов, применяемых во время проведения командных встреч.
Здесь важно, чтобы весь обсуждаемый контекст не просто записывался в документ в виде стенограммы, но и приобретал статус процесса, в который вовлекаются нужные компетенции.
С таким подходом результативность проведения митинга повышается многократно, так как все ключевые мысли записаны и на их основе создаются:
новые задачи
ставятся вопросы, требующие получение дальнейших ответов
выносятся решения
выдвигаются инициативы
и т.п.
Умение раскладывать контекст на процессы, значительно повышает эффективность работы команды, так как ничего из обсуждаемого не остается упущенным и тем более забытым.
В крутых системах управления в Notion отдельное место имеет база знаний, фрагмент которой представлен и на рабочем столе, обсуждаемом в данной статье. Это wiki, создаваемое командой, записи которой относятся во всему, что требует пояснений.
Многое из того, что создается командой, требует отдельной справки, для этого и существует wiki проекта. На рабочем столе сотрудник видит справки, создаваемые самостоятельно, или с которыми он должен быть ознакомлен.
Пример базы знаний проекта
Совсем крутые базы знаний, как правило, представляют собой систему записей и связей типа Zettelkasten. Особенностью такой системы является не просто база знаний, а наличие связей между записями, которые погружают сотрудника в информационный процесс, создавая перед ним массив связанных друг с другом записей.
Как выглядит крутой zettel в Notion можно посмотреть в моем обзоре на Youtube.
А теперь, было бы интересно узнать у Вас, является приоритетной задачей в вашей организации / проекте оцифровка процессов помимо задач, продаж и прочих очевидных, без которых в принципе не возможен проджект менеджмент.
10 WordPress плагинов, которые вы должны знать в 2023 / Хабр | Веб-студия Nat.od.ua
10 WordPress плагинов, которые вы должны знать в 2023 / Хабр
(Примечание переводчика: здесь и далее в скобках примечания от переводчика).
Список плагинов которые изменят ваше привычное представление о WordPress. Отличная подборка из знаменитых и неизвестных плагинов, которые сделают вашу жизнь в 2023 проще.
Я опытный WordPress разработчик и хотел бы поделиться с вами плагинами, которые я использую при разработке. Эти плагины обладают обширным функционалом и при этом просты в использовании, так что подойдут как разработчикам, так и владельцам веб-сайтов без специальных навыков.
Список ниже содержит только бесплатные плагины, под названием плагина будет краткое описание и мой личный комментарий, а также ссылка на скачивание. Приятного ознакомления!
1. CPT UI
Предоставляет интерфейс для регистрации и управления пользовательскими типами постов (Custom Post Types, CPT далее) и таксономиями для вашего веб-сайта.
Я всегда создаю CPT и таксономии используя данный плагин вместо добавления их через код, так как это очень просто, имеет все необходимые опции и может быть изменено клиентом в любой момент.
Если вы не знаете что такое CPT, тогда вам обязательно необходимо исправить это, так как CPT одно из самый важных вещей в WordPress. Здесь хорошая статья про CPT и PostMeta.
CPT UI в WordPres репозитории
2. Advanced Custom Fields (ACF)
Предоставляет вам полный контроль над пользовательскими полями (custom fields or meta fields). Позволяет легко добавлять и управлять мета полями на любых WordPress экранах для редакторов.
Самый известный плагин в WordPress вселенной. Это лучшая оболочка для WordPress мета полей что позволяет управлять ими без всяких проблем.
По умолчанию WordPress Посты и Страницы (и любые другие типы постов) имеют статический список полей. ACF позволяет добавить любые пользовательские (произвольные) характеристики к вашим Страницам. ACF поддерживает более чем 30 разных типов полей: строки, поля выбора, чекбоксы, ссылки, изображения, карты и многое другое.
Мета поля – еще одна важная часть WordPress. Если вы разработчик, вы обязаны знать как Мета поля работают в самом WordPress, т.к. ACF лишь оболочка над ними. Вот хорошая статья про это.
ACF в WordPress репозитории
3. ACF Views
Позволяет отобразить выбранные ACF поля или Посты в любом месте используя шорткоды, HTML разметка создается плагином автоматически.
По умолчанию, ACF позволяет создавать мета поля через графический интерфейс, но требует кодинга для вывода этих полей. Это замечательное дополнение к ACF расширяет его, и позволяет отобразить ACF поля без кодинга.
Данный плагин экономит много времени и усилий, и самая важная часть для меня, что его автоматически сгенерированная разметка имеет отличное качество, такое же, как и созданная вручную. Разница лишь в том, что вы не тратите на это время. Как пользователь, вы даже не будете иметь дело с HTML разметкой напрямую, так как плагин генерирует шоркоды, которые вы устанавливаете в целевые места.
ACF Views в WordPress репозитории
4. Admin Menu Groups
Позволяет создавать вложенные меню в боковом WordPress меню для администраторов и распределить все пункты меню по группам.
WordPress имеет отличный интерфейс для администраторов из коробки, но через некоторое время левое меню (сайдбар) становится перегруженным, так как оно отображает все данные веб-сайта: каждый CPT, каждый плагин добавляет сюда свой пункт. Навигация становится кошмаром.
Данный плагин решает эту проблему и позволяет создавать группы и прикрепить каждый пункт меню к определенной группе. Это означает, что по умолчанию отображаются только группы, а пункты меню скрыты в подменю соответствующей группы. Например, я всегда создаю 3 группы: WordPress, , Плагины. Первая содержит все пункты WordPress меню по умолчанию, вторая все пункты, которые касаются данного проекта, как настройки темы, CPT темы и прочее. Последняя содержит пункты меню от плагинов.
Admin Menu Groups в WordPress репозитории
5. Members
Позволяет устанавливать ограничения на доступ к содержимому на вашем сайте через простой интерфейс для мощной WordPress системы ролей и разрешений.
Я всегда добавляю новые роли использую данный плагин, вместо добавления через код, так как это очень просто, имеет все необходимые опции и может быть изменено клиентом в любой момент.
Если ваш веб-сайт должен поддерживать какие-либо типы членства, тогда данный плагин сделает вашу жизнь гораздо проще. Вы можете посмотреть список всех ролей доступных на вашем веб сайте, в отличии от способа с кодингом, который требует поиска по файлам темы чтобы определить это. Также вы можете менять разрешения для ролей простыми чекбоксами.
Members в WordPress репозитории
6. WP Super Cache
Генерирует статические HTML файлы на основе вашего динамического WordPress сайта. После эти файлы передаются пользователям вместо выполнения более сложных, ресурсоемких и более длительных WordPress PHP скриптов.
Один из лучших плагинов для кеша что я использовал когда-либо. Простой, бесплатный и очень эффективный. Легко настраивается.
Использование какого-либо плагина для кеширования в WordPress обязательная вещь для уменьшения времени загрузки страницы для ваших пользователей, кроме того это один из главных факторов для поисковой оптимизации (Search Engine Optimization, SEO далее).
WP Super Cache в WordPress репозитории
7. Redirection
Позволяет легко управлять 301-ми перенаправлениями (redirection, редирект далее), следить за 404 ошибками и в целом наводить порядок в ваших ссылках. Это может уменьшить количество ошибок и улучшить SEO для вашего веб-сайта.
Очень полезный плагин. Каждый раз, когда вы меняете адрес вашей страницы или продукта, вам необходимо создать 301 редирект со старого адреса на новый, чтобы убедится что пользователи, которые запрашивают старый адрес, будет перенаправлены на новый. Также, это очень важно для SEO.
Данный плагин имеет понятный интерфейс, вы можете легко создать новое правило для редиректа или посмотреть уже существующие.
Redirection в WordPress репозитории
8. Better search and replace
При перемещении вашего WordPress веб-сайта на новый домен или сервер, вам вероятнее всего потребуется выполнить поиск/замену в базе данных, чтобы навигация снова работала правильно. Данная задача может быть легко решена с помощью этого плагина.
Каждый раз когда у меня есть задача перенести веб-сайт, я использую данный плагин чтобы обновить адреса в базе данных. Это работает замечательно вне зависимости от количества данных на вашем веб-сайте, и это очень важный момент, т.к. некоторые мои клиенты имели очень большие базы данные (обычно магазины) и другие плагины не смогли выполнить обновление.
Плагин имеет отличный интерфейс, понятный отчет и позволяет выполнить ‘сухой запуск’, что позволяет вам увидеть таблицы (и строки) которые будут изменены, не изменяя сами данные на деле.
Better Search Replace в WordPress репозитории
9. Updraft Plus
Создавайте резервные копии ваших файлов и базы данных, копируйте их в облако и восстанавливайте одним кликом.
Бизнес должен быть устойчивым. К сожалению у вашего хостинг провайдера могут возникнуть проблемы, вне зависимости от его репутации и количества клиентов. Нет места которое застраховано от пожаров и потопов. Вы обязаны не полагаться только на вашего провайдера и регулярно делать резервные копии вашего веб-сайта (файлов и базы данных) в другом месте, как любое облачное хранилище.
Данный плагин позволяет сделать настройку резервных копий один раз и забыть про эту задачу. Этот плагин создает резервные копии автоматически и загружает их в указанное облако по расписанию. Важный момент для меня, что данный плагин может работать с большими резервными копиями, например 20 ГБ (обычно магазины имеют большое количество изображений, которые важны для них так же, как и все остальные данные)
Updraft Plus в WordPress репозитории
10. All in One SEO
Улучшите SEO вашего вебсайта благодаря умным SEO инструментам.
До тех пор пока ваш веб-сайт не является закрытым ресурсом, вам необходимо использовать какой-либо SEO плагин, который сделает ваш веб-сайт дружественным к SEO. Есть столько много вещей, которые не представлены по умолчанию, как структурированные данные, и SEO плагины заботятся об этом.
Есть несколько хороших и известных SEO плагинов, но мой личный выбор именно “All in One SEO”, он показывает лучшие результаты для моих клиентов чем другие плагины.
All in One SEO плагин в WordPress репозитории
Выводы
Я надеюсь вы подобрали для себя что-то новое из данного списка для ваших текущих и будущих проектов. Делитесь плагинами которые вы пользуетесь в комментариях, и вместе мы сделаем эту статью еще полезнее.
Если вы находите данную статью полезной, не забудьте проголосовать за нее и поделиться с друзьями.
Удачи в 2023 году.
разработка игры на svelte с применением ChatGPT и stable diffusion / Хабр | Веб-студия Nat.od.ua
разработка игры на svelte с применением ChatGPT и stable diffusion / Хабр
Я решил попробовать использовать ChatGPT для создания игр и хотел выбрать что-то простое, что можно сделать за несколько вечеров. В итоге, в сотрудничестве с ChatGPT, мы пришли к идее пошагового рогалика, где основной механикой является бросок кубика. Финальный результат можно посмотреть здесь https://dicequest.vercel.app/
После нескольких итераций запросов, мы сформировали следующую механику игры: после победы над врагом пользователь может выбрать навык, улучшающий его характеристики. Главной особенностью игры является использование броска кубика в качестве базовой механики.
Разработка с использованием ChatGPT
Получить полный код игры сразу у ChatGPT не получится из-за ограничения на размер ответа. Поэтому я использовал следующие приемы:
Попросить написать файловую структуру проекта, а затем запрашивать каждый компонент по отдельности.
Попросить написать код, но закомментировать большие функции, которые уже были получены.
Я использовал версию ChatGPT 4, так как код от нее содержит меньше ошибок и лучше поддается корректировке.
После первой итерации я получил следующую разметку:
Первый вариант дизайна выглядел не очень впечатляюще, поэтому я запросил улучшения:
can you add appealing and amazing lord of the rings style to Player.svelte
Затем я попросил переделать компонент игрока так, чтобы он выглядел как игральная карта
Can you remake the player component to look like a playing card?
После нескольких итераций улучшения, я остановился на следующем варианте дизайна:
Логика битвы
Я запросил у ChatGPT логику битвы, и получил следующий результат:
https://svelte.dev/repl/3736b9c89ade4fac83f86c2456833ace?version=3.58.0
Концепция была понятна, но требовала доработки, чтобы всё работало без ошибок. Например, в коде менялись имена переменных (сначала было currentHealth, затем curHealth), что вызывало затруднения. Или добавлилсь вызовы функций, которых ранее не было. Тем не менее, это малая плата за значительный буст скорости разработки.
Анимации
Наша игра пока не выглядит впечатляюще, попробуем с помощью Chat GPT сгенерировать анимации.
Попросим СhatGPT сделать анимацию фаербола.
https://svelte.dev/repl/c9d511d269ac4f8293da5f77b28ecc2c?version=3.58.0
Хм, видимо, в нейросетевом мире именно такие фаерболы. Идем дальше. Попробуем создать анимацию лечения.
https://svelte.dev/repl/06094df1f63e4d0cbd9541dd8d04c2cc?version=3.58.0
Простенько, но со вкусом. А что будет, если попросить сделать анимацию на tree.js?
Can you create enemy defeat animation with three.js?
Получился базовый пример из обычного туториала. Не сдаемся, пробуем уточнить запрос.
Can you create something like an explosion?
https://svelte.dev/repl/d8c851cff68a4b48a968ad403f5d3e3d?version=3.58.0
ВАУ! Это впечатляет! Кажется, святой грааль найден! Продолжаем, просим сделать анимацию атаки.
Awesome! Could you create an attack animation with three?
https://svelte.dev/repl/e176512e074f4a29a86e8fbcd3b9b2de?version=3.58.0
Весьма неплохо! Немного в измененном виде эта анимация вошла в итоговую версию игры. Как дела с нашим многострадальным фаерболом?
Could you change the fireball animation? It could be like burning ball, moving from left to right
https://svelte.dev/repl/6a8dfcf6731148ad9335c802cafd6d18?version=3.58.0
Неплохо, но вау эффекта не вызывает. Спустя несколько итераций я так и не добился хорошего результата и от фаербола отказался. Что насчет анимации лечения?
Could you create a healing animation with a tree?
Уточняю запрос
Сould you remake this animation, and make it more dynamic, maybe some particles?
https://svelte.dev/repl/ce59812d1d004fa29361de6b3b4ea977?version=3.58.0
Невероятно! Забираем в проект. Что насчет анимации удара мечом?
Can you generate attack animation? the animation may look like a trail from a swing of a sword
https://svelte.dev/repl/ae7fd57d9f2549089ab349aecca07fea?version=3.58.0
Забавно, но не более. Получить красивый вариант анимации мне так и не удалось, поэтому останавливаемся на анимации частиц из предыдущих этапов.
Из анимаций у нас остался бросок кубика. Посмотрим, насколько ChatGPT хорош.
Can you make dice roll animation?
https://svelte.dev/repl/30497bcbbd9445989cbe2976059170b9?version=3.58.0
Хм, этот вариант на css анимациях, я попросил переделать на tree.js
Could it be in three.js?
Бум! То, что надо. Этот вариант я доработал вручную и использовал в игре.
Основные строительные блоки игры были готовы, и дальше я допилил их вручную до необходимого результата. Переходим к генерации игрового арта.
Генерация арта
Для генерации арта я использовал сервис Stable Horde (https://stablehorde.net/), который представляет собой невероятную идею использования распределенных вычислений для генеративных сетей. Рекомендую ознакомиться с этим сервисом и попробовать его. В качестве пользовательского интерфейса я использовал ресурс Stable UI (https://aqualxx.github.io/stable-ui/).
Исследовав несколько популярных моделей, я выбрал комбинацию Deliberate с постобработкой GFPGAN для создания игрового арта. Я обратился к ChatGPT для генерации промптов, которые были использованы для создания аватаров врагов, вдохновленных вселенной Властелина Колец.
Попросим chatGPT сгенерировать промты
Could you generate 5 prompts to generate enemy avatars with help of stable diffusion?
Stable diffusion allows for the generation of images based on textual prompts. Here are five prompts to generate enemy avatars that are inspired by the Lord of the Rings theme:
1. A menacing Nazgûl wearing a dark hooded cloak and wielding a long, sinister sword
2. A hulking troll with rough, stone-like skin and large tusks, wielding a massive club
3. A cunning goblin with green, scaly skin, pointy ears, and sharp teeth, armed with a jagged dagger
4. A twisted orc with a muscular build, red eyes, and battle scars, holding a crude axe
5. A fearsome Uruk-hai with dark, thick skin, a prominent brow ridge, and carrying a large, spiked mace
Отлично, скармливаем Deliberate, завариваем чай и ждем результат.
A menacing Nazgûl wearing a dark hooded cloak and wielding a long, sinister sword
A fearsome Uruk-hai with dark, thick skin, a prominent brow ridge, and carrying a large, spiked mace
Не обошлось и без забавных казусов. Например, эльфийка, проткнутая стрелой.
mysterious elven archer with long, flowing hair, dressed in elegant green and gold garments, holding a longbow
Или кроссовер со вселенной DC
A formidable dark knight clad in sinister from lord of the rings, black armor adorned with spikes and skulls, wielding a terrifying, cursed sword
Для заднего фона я также сгенерировал промты в chatGPT, а картинку получил в Deliberate.
A dark, eerie swamp with stagnant waters, twisted trees, and hidden dangers lurking beneath the surface, creating an atmosphere of dread and uncertainty
Дополнительные функции
ChatGPT справляется легко с генерацией компонента open gprah разметки, и даже написания функции генерации картинок на node.js, которая подтягивается, когда пользователь шарит результат в мессенджерах.
Изображение для Open Graph
ChatGPT с легкостью предлагает новые скилы и механики игры, поэтому с должной степенью усидчивости можно получить невероятную игру с крутыми механиками и анимациями.
Также я обратился к ChatGPT с просьбой предложить способы монетизации игры. Хотя полученный набор оказался стандартным, включая внутриигровые покупки, рекламу и платные функции, это может быть полезной отправной точкой для разработки стратегии монетизации. Я не внедрил эти функции в свою игру, но вот несколько забавных примеров рекламных блоков, основанных на промптах от ChatGPT:
Design your own Elven-inspired jewelry: Create breathtaking accessories inspired by the elegant craftsmanship of the Elves. From rings and necklaces to bracelets and tiaras, immerse yourself in the enchanting world of Middle-earth and discover the secrets of Elven artistry.
Middle-earth’s finest pipe-weed: Discover the aromatic world of Middle-earth’s most exquisite pipe-weed, grown in the fertile fields of the Shire. Experience the smooth and flavorful blends favored by Hobbits, Dwarves, and even Wizards.
Выводы
Используя ChatGPT и Stable Diffusion, за неделю работы по вечерам я смог создать забавный рогалик, который вы можете опробовать здесь: https://dicequest.vercel.app/
Исходный код тут: https://gitlab.com/az67128/dicequest
ChatGPT способен:
Предлагать идеи.
Генерировать строительные блоки вашего приложения.
Значительно ускорять разработку.
Предлагать тексты для игры, включая диалоги.
Генерировать промты для арта.
Помогать выйти из творческого затруднения, когда вы зашли в тупик.
Однако, ChatGPT пока не умеет:
Собирать проект с нуля до результата, вам все равно понадобится ручная доработка.
Полностью быть в контексте. Иногда часть предыдущей истории, как будто, забывается, и ChatGPT выдает не релевантный результат, как в коде, так и в тексте.
Придумывать прорывные идеи. Знания ChatGPT базируются на существующем опыте, поэтому создать что-то совершенно новое и уникальное может быть сложно.
Тем не менее, ChatGPT является мощным инструментом, который может существенно помочь в разработке игр и других проектов. Он может предложить разнообразные аспекты игрового процесса, от кода и анимаций до стратегий монетизации. Думаю, через пару лет подходы к разработке сильно изменятся, вплоть до появления новых профессий операторов ChatGPT