Написав Repost для DreamWidth
Четвер, 29 Грудень 2016 22:55![[personal profile]](https://www.dreamwidth.org/img/silk/identity/user.png)
Ура, %SUBJ%, котани! Налітай!
Скрипт працює так:
- Додає от такий лінк на сторінку допису:
- По кліку на посилання відкриває
http://www.dreamwidth.org/entry/new
у новому вікні (target="_blank"
) та заповнює два поля: Subject та Body.
Оновлено: з версії 0.8 вміє заповнювати також To-Journal, Icon, та Tags.
Причому можна налаштовувати «шаблони» для перепосту: наприклад, «перепост до спільноти такої-то з юзерпикою такою-то і наперед прописаними тегами».
Known Bugs:
Не вставляється YouTube відео. Спробую побороти, але там багато возні.— Починив у v.0.3- Не розгортаються Cut Tags. Перед перепостом треба все руками розгортати
- З тієї ж причини, Cut Tags у перепощеному дописі не завжди вставляються. Їх треба прописувати руками, якщо перепост робився зі сторінки конкретного допису. А якщо перепост зі сторінки журналу, то всі cut вставляються правильно.
Глючить, якщо пост містить <lj user="..."> — ім'я цього юзера помилково вставляється у шапку перепосту (замість імені того юзера, чий пост ви перепощуєте).— Починив у v.0.2
Зауваження:
- Перше і найголовніше: Ніколи не встановлюйте скрипти від невідомих джерел!
Особливо якщо ви не розумієте код. Tags та To [Journal/Community] не прописує.Прописує з версії 0.8.Можливо, потім придумаю шаблони для перепостів, але поки що цього нема.Зроблено у версії 0.8.- Перевіряв на сторінках індивідуальних журналів (
http://bytebuster.dreamwidth.org/
), комун (http://bitter-onion.dreamwidth.org/
) та індивідуальних дописів (http://bitter-onion.dreamwidth.org/1139072.html
). - Перевіряв лише на Firefox. У кого Chrome, перевірте хто-небудь на Tampermonkey, га? Я не упевнений, що там працюють функції, специфічні для GM.
- Багрепорти приймаються, але я не глибоко дружу з JavaScript, тому не гарантія, що я зможу вилікувати усі можливі проблеми.
Встановлення
- Ставимо собі Greasemonkey;
- Перезапускаємо Firefox;
- Повертаємося до цього допису, виділяємо все тіло скрипта і копіюємо до Clipboard;
- Тиць в кнопку Greasemonkey на Toolbar, тиць New User Script…;
- У новому віконці внизу кнопка Use Script from Clipboard;
- Налаштувати свої шаблони у секції:
// **** SETTINGS ****************************************************
- Зберегти;
- Відкрити будь-яку сторінку на DreamWidth.
// ==UserScript== // @name DreamWidth Reposter // @namespace bytebuster.dreamwidth.org // @description Adds LJ-like Repost functionality for DreamWidth // @include http://*.dreamwidth.org/* // @exclude http://*.dreamwidth.org/profile* // @exclude http://*.dreamwidth.org/calendar* // @exclude http://*.dreamwidth.org/tag/ // @version 0.3 // @grant GM_log // @grant GM_getValue // @grant GM_setValue // @grant GM_deleteValue // ==/UserScript== /* ********************************************************************************** This script has two main functions: * addRepostLinks - triggers on all DreamWidth pages; adds "Repost" links to entries; * populatePostForm - triggers on /entry/new page; fills up the "Post Entry" fields Цей скрипт містить дві основні функції: * addRepostLinks - запускається на усіх сторінках DreamWidth pages; додає кнопку «Перепостити»; * populatePostForm - запускається на сторінці /entry/new page; заповнює поля вводу ********************************************************************************** */ /* * 0.3 fixed applied domain and URL; fixed Youtube video embed minor fix if mistakenly decided that post in community, while it actually is in user's own journal * 0.2 added group/blog references fix bug on other users references * 0.1 Initial reease */ (function(){ // **** SETTINGS **************************************************** // Main settings // Головні налаштування const REPOST_TEXT = "Перепостити"; // Так виглядатиме текст кнопки «Репост» const REPOST_TEMPLATE = 'Перепост допису <lj user="{poster}">: <a href="{link}">{subj}</a>\n{body}'; const REPOST_TEMPLATE_COMMUNITY = 'Перепост допису <lj user="{poster}"> @ <lj user="{community}">: <a href="{link}">{subj}</a>\n{body}'; const DEBUG = false; // **** CONSTANTS *************************************************** // You don't need it unless DreamWidth changes its classes (impossible, yeah) // Вам тут не треба нічого міняти, допоки Дрім не поміняє імена класів (майже нереально) const CLS_ENTRY_TITLE = "entry-title"; const CLS_ENTRY_WRAPPER = "entry-wrapper"; const CLS_ENTRY_POSTER = "entry-poster"; const CLS_LJUSER = "ljuser"; const ATTR_LJUSER = "lj:user"; const CLS_ENTRY_CONTENT = "entry-content"; const CLS_ENTRY_LINKS = "entry-interaction-links"; const ID_INPUT_SUBJECT = "id-subject-0"; const ID_INPUT_BODY = "id-event-1"; const CLS_EMBED = "lj_embedcontent-wrapper"; const POST_URL = "http://www.dreamwidth.org/update"; const POST_URL2 = "/entry/new"; const REPOST_PARAM="repost"; const CONTENT_DEFAULT = ""; const CONTENT_TAG = "DW_REPOST_CONTENT"; // how we save the reposted entry in GM_setValue const REGEX_YOUTUBE_LINK = /^.*(?:youtu\.be\/|v\/|u\/\w\/|embed\/|watch\?v=|\&v=)([^#\&\?]*).*/gmi; // /www.youtube.com\/watch?v=(.+?)/gmi; const EMBED_YOUTUBE_TEMPLATE = '<iframe width="640" height="360" src="https://www.youtube.com/embed/{videoID}" frameborder="0" allowfullscreen></iframe>'; // applies a parser on strings and log error if failed function parse(parser, where, description) { var ret; match = parser.exec(where); if( match === null ) { GM_log("Failed to parse " + description + " from text: " + where); ret = "(null)"; } else { ret = match[1]; } return ret; } function DebugMessage( what ) { if(DEBUG) { GM_log(what); } } // checks for URL parameter function getQueryVariable(variable) { var query = window.location.search.substring(1); var vars = query.split("&"); for (var i=0;i<vars.length;i++) { var pair = vars[i].split("="); if (pair[0] == variable) { return pair[1]; } } DebugMessage('Query Variable ' + variable + ' not found'); } // Adds Repost links to all entries on the page // Додає посилання «Перепост» внизу усіх дописів на сторінці function addRepostLinks() { DebugMessage("Adding Repost links begin..."); var entries = document.getElementsByClassName(CLS_ENTRY_WRAPPER); for (var entry_index=0; entry_index<entries.length; entry_index++) { addRepostLink(entries[entry_index]); } DebugMessage("Adding Repost links end"); } // Gets the saved content for repost function getRepostData() { DebugMessage("getRepostData begin"); return GM_getValue(CONTENT_TAG, CONTENT_DEFAULT); } // Sets the content for repost function setRepostData(repostData) { DebugMessage("setRepostData begin " + repostData); // delete the state if it is the default if( repostData == CONTENT_DEFAULT ) { GM_deleteValue(CONTENT_TAG); } else { // set the state otherwise GM_setValue(CONTENT_TAG, repostData); } DebugMessage("setRepostData end"); } // Saves JSON data from the reposted entry // Зберігає допис і його шапку function rememberPost(elEntry) { var elPoster, elLjuser, elTitle, elContent; // elements var entryPoster, entryCommunity, entryLink, entrySubject, entryBody, subdomain; // strings var repostData; // JSON object DebugMessage("rememberPost begin"); subdomain = window.location.host.split(".")[0]; // get Poster userId elPoster = elEntry.getElementsByClassName(CLS_ENTRY_POSTER)[0]; if(elPoster && elPoster.hasChildNodes()) { // we seem to be in a community, parse out poster name from attribute elLjuser = elPoster.getElementsByClassName(CLS_LJUSER)[0]; entryPoster = elLjuser.getAttribute(ATTR_LJUSER); entryCommunity = subdomain; } else { // we seem to be in a journal, parse out poster name from URL (FIXME) entryPoster = subdomain; entryCommunity = ""; } // Quick fix: fix if we mistakenly set community if(entryPoster == entryCommunity) { entryCommunity = ""; } // get Subj and Link elTitle = elEntry.getElementsByClassName(CLS_ENTRY_TITLE)[0]; entryLink = elTitle.getElementsByTagName("a")[0].href; entrySubject = elTitle.textContent; // get Body elContent = elEntry.getElementsByClassName(CLS_ENTRY_CONTENT)[0] .cloneNode(true); // Apply fixes elContent = FixYoutube(elContent); elContent = FixUsers(elContent); entryBody = elContent.innerHTML; repostData = JSON.stringify( { "link" : entryLink, "poster" : entryPoster, "community" : entryCommunity, "subj" : entrySubject, "body" : entryBody } ); DebugMessage("Saving JSON: " + repostData); setRepostData(repostData); DebugMessage("rememberPost end"); } // Fixes User references // Takes a cloned node to fix // Returns it as well function FixUsers(element) { var embeds; // list of embed objects DebugMessage("FixUsers begin"); embeds = element.getElementsByClassName(CLS_LJUSER); for (var embed_index=0; embed_index<embeds.length; embed_index++) { var elLjuser, elLjUser2; // elements var userName; // strings elLjuser = embeds[embed_index]; if(elLjuser.nodeName != "SPAN") { DebugMessage("Element frame does not look like a user reference, exiting"); continue; } userName = elLjuser.getAttribute(ATTR_LJUSER); // creating an HTML fragment to replace the broken link elLjUser2 = document.createElement("lj"); elLjUser2.setAttribute("user", userName); // replacing the element element.replaceChild(elLjUser2, elLjuser) embed_index--; } DebugMessage("FixUsers end"); return element; } // Fixes YouTube links // Takes a cloned node to fix // Returns it as well function FixYoutube(element) { var embeds; // list of embed objects DebugMessage("FixYoutube begin"); embeds = element.getElementsByClassName(CLS_EMBED); for (var embed_index=0; embed_index<embeds.length; embed_index++) { var elFrame, elLinkDiv, elLink, elRange, elFragment, elFirstNode; // elements var linkAddress, linkBodyNew; // strings elFrame = embeds[embed_index]; if(elFrame.nodeName != "DIV") { DebugMessage("Element frame does not look like a YouTube, exiting"); continue; } elLinkDiv = elFrame.nextSibling; if(elLinkDiv === null) { DebugMessage("Failed to find YouTube link DIV, exiting"); continue; } elLink = elLinkDiv.getElementsByTagName("a")[0]; if(elLink === null) { DebugMessage("YouTube link seems to be broken, exiting"); continue; } linkAddress = parse(REGEX_YOUTUBE_LINK, elLink.href, "YouTube Link"); linkBodyNew = EMBED_YOUTUBE_TEMPLATE .replace("{videoID}", linkAddress); // creating an HTML fragment to replace the broken link elRange = document.createRange(); elRange.selectNode(document.body); // required in Safari elFragment = elRange.createContextualFragment(linkBodyNew); elFirstNode = elFragment.firstChild; // replacing the element element.removeChild(elFrame); element.replaceChild(elFirstNode, elLinkDiv) embed_index--; } DebugMessage("FixYoutube end"); return element; } // Adds Repost links to one entry // Додає посилання «Перепост» внизу допису function addRepostLink(entry) { var elFooter, elRepostLink, elRepostLinkLI; // elements var entryTitle, entryLink, userName, entryId; // check if entry is a DOM element if( !entry.tagName ) { return; } elFooter = entry.getElementsByClassName(CLS_ENTRY_LINKS)[0]; // must be < ul > elRepostLink = document.createElement("a"); elRepostLink.href = POST_URL + "?" + REPOST_PARAM +"=1"; elRepostLink.target = "_blank"; elRepostLink.addEventListener('click', function(event) { rememberPost(entry); /*event.stopPropagation(); event.preventDefault();*/ }, true); elRepostLink.text = REPOST_TEXT; elRepostLinkLI = document.createElement("li"); elRepostLinkLI.appendChild(elRepostLink); elFooter.appendChild(elRepostLinkLI); } // Fills up the "Post Entry" form // Заповнює форму «Створити допис» function populatePostForm() { DebugMessage("Populate Post begin"); var repostedUrl = getQueryVariable(REPOST_PARAM); if(! repostedUrl) { DebugMessage("Does not look like a repost, exiting"); return; } // continue var repostDataString, repostData; // JSON data var postBody; // strings var elSubject, elBody; // elements repostDataString = getRepostData(); DebugMessage("Repost data=" + repostDataString); repostData = JSON.parse(repostDataString); DebugMessage("Repost data parsed=" + JSON.stringify(repostData)); DebugMessage("Preparing postBody..."); postBody = ((repostData.community == "") ? REPOST_TEMPLATE : REPOST_TEMPLATE_COMMUNITY) .replace('{poster}', repostData.poster) .replace('{community}', repostData.community) .replace('{link}', repostData.link) .replace('{subj}', repostData.subj) .replace('{body}', repostData.body) ; DebugMessage("postBody=" + postBody); elBody = document.getElementById(ID_INPUT_BODY); DebugMessage("elBody was: " + elBody.value); elBody.value = postBody; DebugMessage("elBody became: " + elBody.value); elSubject = document.getElementById(ID_INPUT_SUBJECT); DebugMessage("elSubject was: " + elSubject.value); elSubject.value = repostData.subj; DebugMessage("elSubject became: " + elSubject.value); setRepostData(CONTENT_DEFAULT); DebugMessage("Populate Post end"); } var thisUrl = window.location.href; if(thisUrl.indexOf(POST_URL2) != -1) { populatePostForm(); } else { addRepostLinks(); } })();
...
Дата: Понеділок, 10 Квітень 2017 07:17 (UTC)Насправді, я там діагностики ніякої не писав (це ж скрипт на колєнці).
Тисніть Shift+Ctrl+J, відкриється віконце Browser Console, а потім тиць мишкою у «Перепостити». У віконці буде помилка і рядок.
...
Дата: Понеділок, 10 Квітень 2017 11:57 (UTC)...
Дата: Понеділок, 10 Квітень 2017 12:37 (UTC)...
Дата: Вівторок, 18 Квітень 2017 12:28 (UTC)Щоправда, цей варіант примусово видасть нову сторінку незалежно від налаштувань.
...
Дата: Вівторок, 18 Квітень 2017 14:07 (UTC)Поясніть, будь ласка, оцю фразу:
> нова (бета) сторінка створення повідомлення не увіменена в налаштуваннях
Бо не найшов у налаштуваннях.
І ще не дуже ясно оце:
> примусово видасть нову сторінку
— це маєте на увазі, нове вікно у target=_blank чи що?
...
Дата: Вівторок, 18 Квітень 2017 14:22 (UTC)https://www.dreamwidth.org/beta
можна ввімкнути або вимкнути бета-фічі, серед яких - нова сторінка створення/редагування повідомлення. Наскільки я розумію, вмикання цієї фічі дає редирект з /update на /entry/new , якщо ж вона вимкнена, кожна з цих сторінок доступна окремо (і виглядають по-різному). Нова сторінка (/entry/new) доступна всім користувачам, але якщо ця опція не ввімкнена, то всі лінки ведуть на стару, тому, щоб потрапити на нову сторінку, потрібно вводити її адресу вручну.
...
Дата: Вівторок, 18 Квітень 2017 14:35 (UTC)Гляну, чи можна програмно визначити, яка опція увімкнена (ну, не хочеться лазити на /beta щоразу).
Ну або фіг з ним, з видом сторінки, погляну, чи там на старій сторінці імена контролів інші можуть бути.
...
Дата: Вівторок, 18 Квітень 2017 14:39 (UTC)...
Дата: Вівторок, 18 Квітень 2017 16:00 (UTC)Воно поки що кривеньке:
1. Неправильно відображається одразу; але якщо перемкнути на HTML, а потім на Rich Text, то все видно нормально
2. Не знаю, як заборонити FCKEditor'у перепитувати "restore draft?"
Як будуть ідеї — допилю, а поки що так.
...
Дата: Четвер, 31 Серпень 2017 22:06 (UTC)1. Щось не працює фікс. Якщо я вірно зрозумів, то на /update JSON не підтягується взагалі, лише на /entry/new. Якщо ввімкнено в налаштуваннях відповідну бета-фічу, спрацьовує редирект з першого на друге, тоді все нормально. Але якщо її вимкнути, допомагає лише правка "
const POST_URL = "https://www.dreamwidth.org/entry/new";
" Ну та то таке, дрібниця.2. А от у lj щось, схоже, знов змінили. От що відбувається на lg-hater.livejournal.com:
bytebuster.dreamwidth.org/DreamWidth Reposter:
rememberPost begin
bytebuster.dreamwidth.org/DreamWidth Reposter:
FixYoutube begin
bytebuster.dreamwidth.org/DreamWidth Reposter:
FixYoutube end
bytebuster.dreamwidth.org/DreamWidth Reposter:
FixUsers begin
NotFoundError: Node was not found DreamWidth_Reposter.user.js:218.
bytebuster.dreamwidth.org/DreamWidth Reposter:
Populate Post begin
bytebuster.dreamwidth.org/DreamWidth Reposter:
getRepostData begin
bytebuster.dreamwidth.org/DreamWidth Reposter:
Repost data=
JSON.parse: unexpected end of data at line 1 column 1 of the JSON data DreamWidth_Reposter.user.js:387:16
Тобто помилка виникає в
element.replaceChild(elLjUser2, elLjuser)
І масив не формується взагалі, звідти й наступна помилка. Усе, до чого я дотумкав, це додати перед проблемною строкою:
DebugMessage("elLjUser"+elLjUser+" elLjuser2"+elLjuser2);
На що отримав ось таке повідомлення:
ReferenceError: elLjUser is not defined
Щось мені підказує, що проблема десь у CLS_LJUSER, і тому змінна embeds пуста. Але у джаваскрипті я повний невіглас, то знайти, де собаку зарито насправді, не спромігся.
А на ibigdan.livejournal.com взагалі посилання репосту не додається:
bytebuster.dreamwidth.org/DreamWidth Reposter:
Adding Repost links begin...
elFooter is undefined DreamWidth_Reposter.user.js:172:3
Здогадуюся, що значення elFooter має витягуватися звідкись з CLS_LJ_ENTRY_LINKS, але підробиць вже ніяк не второпаю.
Подивіться, будь ласка, як матимете час.
...
Дата: П'ятниця, 1 Вересень 2017 00:05 (UTC)1. А погляньте на попередню. гілку коментарів — це те ж саме?
2. ЖЖ зіпсувалося, воно і так не завжди працювало. Різні скіни по-різному рендерили, воно працювало тільки на деяких. На моїй схемі працювало, але я бачив такі, де — ні.
І ще, воно по-різному працювало у стрічці новин і в окремих ЖЖурналах. Теж не виправлено.
Але вибачте, я закрив свій еккаунт ЖЖ і не допилюватиму цей код під ЖЖ, просто нема де тестуватися.
...
Дата: П'ятниця, 1 Вересень 2017 00:31 (UTC)Насправді, у ЖЖ там главна муйня у тому, що до різних схем треба по-різному робити пошук, я не придумав універсального методу, як це робити, а возитися з ланцюжками «якщо не так, то ось так, а потім ще спробувати от так» — тупо полінувався. :(