Скрипт для відправки дописів у Steemit з редактором HTML-тегівsteemCreated with Sketch.

in Ukraine on Steemlast month (edited)

Ви напевно чули, що рухає прогрес. Лінь. І тільки лінь. Мені набридло вручну вводити HTML-теги в тексти дописів. Я довго думав, як вирішити це питання. І не тому що я повільно думаю, а часто просто не вистачає часу на розробку чогось, у житті пріоритети розставляються не завжди так як нам хочеться. Останніми днями мені вдалося приділити увагу багатьом справам, які я відклав у далекий ящик. І я кажу не тільки про Steemit.

Кілька місяців тому я писав у спільноті Steem Dev невеликий допис з пропозицією додати кнопки до редактора тексту в режимі розмітки (markdown), за допомогою яких можна було б легко додавати HTML-теги. Я нікого не зацікавив і це нормально, я знаю що на платформі Steemit є багато інших важливих задач, які потребують фундаментального підходу.

2025-08-16_213758.png

Створити сторінку для публікації допису виявилося не дуже складною справою. Два дні я намагався розібратися з механізмом завантаження зображень на сервер steemitimages, але так нічого й не вийшло. Справа в тому, що запит на завантаження картинки відбувається з токеном із cookie, який видається під час авторизації на сайті steemit.com, таким чином завантаження картинки через інший домен стає неможливим. Гарним рішенням мені могло би стати браузерне розширення SteemKeychain, але я свідомо використовую приватний постинг-ключ, щоб використовувати цей скрипт на мобільному телефоні, попри те що це найнебезпечніший спосіб авторизації. Теорія з токеном із cookie не стовідсоткова, але має право на існування. Таким чином я залишаю за собою право бути неправим :)

На мій погляд, html-розмітка краща й зручніша, ніж візуальний редактор. Спробую пояснити чому. Оформлюючи текст у HTML, ти точно знаєш де що знаходиться, і не залежиш від обмежень візуального редактора. Наприклад, ти можеш задати обтікання картинки текстом так як тобі потрібно, а не так як передбачено в редакторі. Є можливість гнучкого налаштування параметрів. Ви не використовуєте нічого зайвого. У той час як візуальний редактор дуже часто дублює теги, залишає в коді багато "сміття". Якщо під час оформлення щось "з'їхало", то в коді одразу видно чому. Коли хочеться створити щось своє, а не те що дозволяє візуальний редактор, то HTML завжди у виграші.

Якщо ви маєте намір експериментувати з роботою скрипта для відправки дописів у Steemit, хочу звернути вашу увагу на один момент. Новий допис визначається парою параметрів: author і permlink. Якщо ви надсилаєте той самий permlink (заголовок допису), то блокчейн не створює новий допис, а відредагує вже існуючий із ідентичним permlink, змінить лише last_update.

Скрипт нижче, який я хочу продемонструвати, дозволяє вставляти в текстове поле основні теги, які підтримуються в Steemit, а також код посилання й готові блочні елементи:

<div class="pull-left"></div> - HTML-блок з класом pull-left, який вирівнює вміст по лівому краю.

<div class="pull-right"></div> - HTML-блок з класом pull-left, який вирівнює вміст по правому краю.

<div class="text-rtl"></div> - HTML-блок з класом text-rtl, який задає напрямок тексту справа наліво.

<div class="text-justify"></div> - HTML-блок з класом text-justify, який вирівнює текст по ширині.

Під час використання тегів DIV при оформленні дописів у Steemit я дуже рекомендую обгортати їх у тег P, інакше втрачається відступ між абзацом із DIV і наступним абзацом. Наприклад, замість <div>текст</div> використовуйте <p><div>текст</div></p>.

2025-08-16_201043.png

2025-08-16_200850.png


Приклад використання HTML-блока з класом pull-left і тега P:

2025-08-16_201717.png

2025-08-16_201802.png


Теги ul і li немає сенсу вписувати в код вручну, оскільки символ "зірочка" (*) повністю їх замінює.

Отже, розберемо устрій скрипта за основними важливими сегментами:

<label for="author">Username:</label>
<input type="text" id="author" placeholder="Username" value="" required>

<label for="key">Private posting key:</label>
<input type="password" id="key" placeholder="Private posting key" value="" required>

<label for="title">Title:</label>
<input type="text" id="title" placeholder="Title" value="" required>

<label for="tags">Tags (space separated):</label>
<input type="text" id="tags" placeholder="e.g. travel ukraine photo" value="" required>

<label for="body">Post body:</label>
<div class="formTxt">
    <input type="button" class="format strong" title="Bold" onclick="tag('strong')" value="B" />
    <input type="button" class="format em" title="Italics" onclick="tag('em')" value="I" />
    <input type="button" class="format del" title="Strike" onclick="tag('strike')" value="S" />
    <input type="button" class="format" title="Insert URL" onclick="tag('link')" value="URL" />
    <input type="button" class="format" title="P" onclick="tag('p')" value="P" />
    <input type="button" class="format" title="DIV" onclick="tag('div')" value="DIV" />
    <input type="button" class="format" title="Text direction right to left" onclick="tag('text-rtl')" value="Text RTL" />
    <input type="button" class="format" title="Align Right" onclick="tag('align-right')" value="Align DIV Right" />
    <input type="button" class="format" title="Align Left" onclick="tag('align-left')" value="Align DIV Left" />
    <input type="button" class="format" title="Text Justify" onclick="tag('text-justify')" value="&#9776;" />
</div>
<textarea id="body" placeholder="Post body"></textarea>

 
Введення особистих даних, заголовка майбутнього допису, тегів і тексту. Над текстовим полем (textarea) розташовані кнопки для додавання тегів і блочних елементів. Наприклад, щоб додати тег strong, потрібно виділити необхідне слово або шматок тексту й натиснути на кнопку. Це дуже зручно.

const comment = {
    parent_author: '',
    parent_permlink: parentPermlink,
    author,
    permlink,
    title,
    body,
    json_metadata: JSON.stringify({
        tags: tags,
        app: 'script/0.1',
        format: 'markdown'
    })
};

 
Тут визначається структура створюваного допису. Систематизуємо дані, визначаємо автора, заголовок і т.д. Зверніть увагу на рядок app: 'script/0.1' — все, що опубліковано через сайт Steemit, підписується як steemit/0.2, тобто ви можете замінити цей текст на що завгодно, навіть на ім’я свого кота.

Відправлення допису відбувається при натисканні на кнопку <button onclick="sendPost()">Publish</button>, після чого викликається функція відправки sendPost(). Результат цієї дії буде відображено в блоці <div class="log" id="log"></div>, це своєрідний звіт про відправку.

Функція function tag(tag) дозволяє вставляти HTML-теги в текст допису при натисканні на відповідні кнопки (описано раніше). Скрипт "бачить", куди вставляти теги за допомогою id="body", у моєму прикладі він прописаний у textarea.

Не забудьте, що підключення до Steemit через API можливе лише після підключення бібліотеки dsteem. Посилання на бібліотеку dsteem вказане в шапці сайту:

<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/dsteem.min.js"></script>

Якщо вказане посилання недоступне, просто завантажте цю бібліотеку (можливо, доведеться змінити розширення файлу *.JPG на *.JS).

Увесь код:

<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <title>Steemit Post</title>
    <script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/dsteem.min.js"></script>
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <style>
        body {
            font: 16px/1.4 arial, sans-serif;
            margin: 24px;
            max-width: 800px;
            margin-inline: auto;
        }

        #main {
            margin: 0 auto;
            width: 800px;
        }

        label {
            display: block;
            margin: .5rem 0 .25rem;
        }

        input[type="text"],
        input[type="password"],
        textarea {
            width: 100%;
            padding: .6rem;
            font-size: 16px;
            border: 1px solid #b3c1c2;
            border-radius: 4px;
        }

        textarea {
            height: 150px;
        }

        button {
            margin-top: 1rem;
            padding: .7rem 1rem;
            cursor: pointer;
            font-size: 16px;
            width: auto;
            border: 1px solid #b3c1c2;
            border-radius: 4px;
        }

        .log {
            margin-top: 1rem;
            white-space: pre-wrap;
            padding: 1rem;
        }

        /* Formatting panel */
        .formTxt {
            display: flex;
            flex-wrap: wrap;
            gap: 6px;
            margin: 6px 0 10px;
        }

        /* Formatting buttons */
        input.format {
            border: 1px solid #b3c1c2;
            color: #000;
            font-size: .95em;
            line-height: 1.5em;
            cursor: pointer;
            background: #f9f6f2;
            padding: .15em .7em;
            border-radius: 4px;
            display: inline-flex;
            align-items: center;
            justify-content: center;
            min-height: 32px;
            min-width: 34px;
            width: auto;
            margin-bottom: 2px;
        }

        .strong {
            font-weight: bold;
        }

        .em {
            font-style: italic;
        }

        .underline {
            text-decoration: underline;
        }

        .del {
            text-decoration: line-through;
        }

        /* Mobile adaptation */
        @media (max-width: 600px) {
            body {
                font-size: 15px;
            }
            #main {
                margin: 0 auto;
                width: 95%;
            }
            input[type="text"],
            input[type="password"],
            textarea,
            button {
                font-size: 16px;
                width: 90%;
            }
            textarea {
                height: 140px;
            }
            .formTxt {
                gap: 3px;
                flex-wrap: wrap;
            }
            input.format {
                font-size: .93em;
                padding: .18em .35em;
                min-width: 30px;
                min-height: 28px;
                width: auto;
            }
            button {
                width: 100%;
            }
        }
    </style>
</head>
<body>
    <div id="main">

        <h1>Publish to Steemit</h1>

        <label for="author">Username:</label>
        <input type="text" id="author" placeholder="Steemit Username" value="" required>

        <label for="key">Private posting key:</label>
        <input type="password" id="key" placeholder="Private posting key" value="" required>

        <label for="title">Title:</label>
        <input type="text" id="title" placeholder="Title" value="" required>

        <label for="tags">Tags (space separated):</label>
        <input type="text" id="tags" placeholder="e.g. travel ukraine photo" value="" required>

        <label for="body">Post body:</label>
        <div class="formTxt">
            <input type="button" class="format strong" title="Bold" onclick="tag('strong')" value="B" />
            <input type="button" class="format em" title="Italics" onclick="tag('em')" value="I" />
            <input type="button" class="format del" title="Strike" onclick="tag('strike')" value="S" />
            <input type="button" class="format" title="Insert URL" onclick="tag('link')" value="URL" />
            <input type="button" class="format" title="P" onclick="tag('p')" value="P" />
            <input type="button" class="format" title="DIV" onclick="tag('div')" value="DIV" />
            <input type="button" class="format" title="Text direction right to left" onclick="tag('text-rtl')" value="Text RTL" />
            <input type="button" class="format" title="Align Right" onclick="tag('align-right')" value="Align DIV Right" />
            <input type="button" class="format" title="Align Left" onclick="tag('align-left')" value="Align DIV Left" />
            <input type="button" class="format" title="Text Justify" onclick="tag('text-justify')" value="&#9776;" />
        </div>
        <textarea id="body" placeholder="Post body"></textarea>

        <button onclick="sendPost()">Publish</button>

        <div class="log" id="log"></div>

    </div>

    <script>
        const client = new dsteem.Client('https://api.steemit.com');

        async function sendPost() {
            const author = document.getElementById('author').value.trim();
            const postingKey = document.getElementById('key').value.trim();
            const title = document.getElementById('title').value.trim();
            const body = document.getElementById('body').value.trim();
            const tagsInput = document.getElementById('tags').value.trim();

            // Split tags to array
            const tags = tagsInput.split(' ').map(tag => tag.toLowerCase()).filter(Boolean);

            // Require at least one tag
            if (tags.length === 0) {
                alert("❗ Please provide at least one tag (space separated).");
                return;
            }

            const parentPermlink = tags[0];
            const permlink = title.toLowerCase()
                .replace(/[^a-z0-9]+/g, '-')
                .replace(/^-+|-+$/g, '') || 'post-' + Date.now();

            const key = dsteem.PrivateKey.fromString(postingKey);

            const comment = {
                parent_author: '',
                parent_permlink: parentPermlink,
                author,
                permlink,
                title,
                body,
                json_metadata: JSON.stringify({
                    tags: tags,
                    app: 'script/0.1',
                    format: 'markdown'
                })
            };

            try {
                await client.broadcast.comment(comment, key);
                const url = `https://steemit.com/${encodeURIComponent(parentPermlink)}/@${author}/${permlink}`;
                document.getElementById('log').textContent = '✅ Post published!\n' + url;
            } catch (e) {
                document.getElementById('log').textContent = '❌ Error: ' + e.message;
                console.error(e);
            }
        }

        // Basic HTML textarea editor
        function tag(tag) {
            var src = document.getElementById('body');
            var start, end, url;

            switch (tag) {
                case 'link':
                    url = prompt("Insert URL", '');
                    if (url != null) {
                        start = '<a href="' + url + '">';
                        end = '</a>';
                    } else {
                        start = '';
                        end = '';
                    }
                    break;
                case 'align-right':
                    start = '<div class="pull-right">';
                    end = '</div>';
                    break;
                case 'align-left':
                    start = '<div class="pull-left">';
                    end = '</div>';
                    break;
                case 'text-rtl':
                    start = '<div class="text-rtl">';
                    end = '</div>';
                    break;
                case 'text-justify':
                    start = '<div class="text-justify">';
                    end = '</div>';
                    break;
                default:
                    start = '<' + tag + '>';
                    end = '</' + tag + '>';
            }

            if (!src.setSelectionRange) {
                var selected = document.selection.createRange().text;
                src.focus();
                var codetext = selected.length <= 0 ? start + end : start + selected + end;
                document.selection.createRange().text = codetext;
            } else {
                var scrollTop = src.scrollTop;
                var pretext = src.value.substring(0, src.selectionStart);
                var selectedText = src.value.substring(src.selectionStart, src.selectionEnd);
                var codetext = start + selectedText + end;
                var posttext = src.value.substring(src.selectionEnd, src.value.length);
                src.value = pretext + codetext + posttext;
                src.scrollTop = scrollTop;
                src.selectionStart = pretext.length;
                src.selectionEnd = pretext.length + codetext.length;
                src.focus();
            }
        }
    </script>
</body>
</html>

 
Дякую за увагу!

Sort:  
 last month 

Взагалі чудово, що доступ до блокчейну можна отримувати різноманітними способами й під це діло прикручувати різні інстурменти. HTML теги нікого не зацікавили, бо P в div загортає автоматом із віузального редактора. Тобто для цього достатньо лише відступ в один ряд. Що підходить для більшості.

j.png

І без цих тегів легше, проте для вирівнювання тексту не скрізь є, лише на steempro додано, хоча не часто його й використовують (як на мою думку). А от додавання зображень із обтікання текстом або щоб декілька в ряю, це не зробили зручно. Тобто там є шаблони для вставки, але ж купа зайвого і не так зручно. Це такі висновки на основі того, що бачу.

 last month 

Дякую за коментар! Ви говорите про дуже логічні речі. Я подивився на свій допис трохи під іншим кутом і справді, з усього описаного лише три блочні елементи можуть бути відчутно корисними з практичної точки зору користувача.
До альтернативних платформ я ставлюся з часткою скепсису. Може навіть даремно... Справа у звичці сидіти на steemit.com :)

 last month 

До альтернативних платформ я ставлюся з часткою скепсису.

Це лише приклад, завертання в html теги, на steemit теж саме. Якщо скрип працює автономно для відправки допису, без веб посередника steemit.com чи іншого, то ймовірно, без html тегів не обійтись.