Статьи

Создание расширения Chrome для Diigo, часть 3

В первой части мы представили несколько новых концепций и создали скелетную версию нашего расширения, готовую к установке и тестированию. Во второй части мы познакомились с некоторыми вспомогательными методами и обработкой ошибок, а также проанализировали результаты, полученные нами от Diigo, и отфильтровали уникальные теги.

В третьей части этой серии мы напишем тело нашего расширения, используя все, что мы уже сделали.

подготовка

Я очистил файл background.js который мы сделали в предыдущих частях, так что давайте возьмем его содержимое из Github . Он по сути идентичен, просто переформатирован и слегка перестроен.

Слушатели событий закладки

Первое, что мы сделаем, это добавим слушателей для событий закладки. В частности, когда происходит создание, изменение или удаление закладки, мы хотим, чтобы Diigo узнала об этом.

 chrome.bookmarks.onCreated.addListener(function (id, node) { chrome.bookmarks.get(node.parentId, function (parent) { if (parent !== false) { chrome.bookmarks.get(parent[0].parentId, function (grandparent) { /** @namespace grandparent.title */ if (grandparent[0] !== false && grandparent[0].title == "Tags") { // Bookmark was created in proper location, send to Diigo doRequest(node, parent[0].title); } }); } }); }); chrome.bookmarks.onRemoved.addListener(function (id, removeInfo) { // To be added when API supports it }); chrome.bookmarks.onChanged.addListener(function (id, changeInfo) { // To be added when API supports it }); 

Два нижних слушателя являются просто заполнителями, потому что Diigo пока не поддерживает эту функцию. Мне сказали, что их API скоро будет обновлен, поэтому, тем не менее, мы размещаем их там.

Слушатель onCreated сначала проверяет, есть ли у созданного узла закладки родительский элемент. Если это так, тогда он проверяет имя родителя этого родителя — и если это имя «Теги», мы знаем, что у нас есть правильная папка, и нам нужно отправить ее в Diigo. Теперь, эта функция предполагает, что у вас нет другой двойной родительской закладки с «Тегами» как прародителя, но, теоретически, это может произойти. Чтобы проверить это, нам нужно добавить еще одну проверку родительского уровня для главной папки Diigo, но я оставлю это на ваше усмотрение для домашней работы.

Затем мы вызываем doRequest с двумя параметрами: фактическим узлом закладки, который был создан, и именем папки тегов, в которой он был создан. Очевидно, нам нужны эти данные, чтобы сообщить Diigo, какую закладку создать и какой тег дать. Но зачем doRequest ? Разве это не наша функция «GET»? Да, но, как вы увидите через мгновение, мы изменим его, чтобы он мог обрабатывать как действия POST, так и GET нашего расширения.

Далее нам нужно добавить эти параметры в нашу функцию doRequest и заставить их реагировать на их присутствие или отсутствие следующим образом:

 var doRequest = function (bookmarknode, tag) { var xml = new XMLHttpRequest(); if (bookmarknode !== undefined) { if (tag === undefined) { console.error("Tag not passed in. Unaware of where to store bookmark in Diigo. Nothing done."); } else { // Bookmark node was passed in. We're doing a POST for update, create or delete // Currently only create is supported var uriPart = encodeURI("url=" + bookmarknode.url + "&title=" + bookmarknode.title + "&tags=" + tag); xml.open('POST', rootUrl + uriPart); xml.setRequestHeader('Authorization', auth); xml.send(); xml.onreadystatechange = function () { if (xml.readyState === 4) { if (xml.status === 200) { clog("Successfully created new bookmark in Diigo"); } else { if (possibleErrors 
 

! == undefined) {
console.error (xml.status + '' + возможных ошибок)

);
} еще {
console.error (possibleErrors.other);
}
}
}
};
}

} еще {

xml.open ('GET', rootUrl + "& count = 100 & filter = all & user =" + user);
xml.setRequestHeader («Авторизация», аутентификация);
XML.send ();

xml.onreadystatechange = function () {
if (xml.readyState === 4) {
if (xml.status === 200) {
Процесс (JSON.parse (xml.responseText));
} еще {
если (возможные ошибки)

! == undefined) {
console.error (xml.status + '' + возможных ошибок)

);
} еще {
console.error (possibleErrors.other);
console.error (xml.status);
}
}
}
};
}
};

Если параметры bookmarknode и tag предоставлены и действительны, мы выполняем XHR-запрос почти так же, как мы делали исходный запрос для получения закладок, с одним существенным отличием - на этот раз мы делаем это запрос POST и добавляем заголовок, тег и имя закладки в URL. Это все, что нужно - теперь Diigo может принять наш запрос POST и реагировать соответствующим образом. Это красота дизайна RESTful API.

Корневые закладки

Теперь давайте сохраним все закладки BBS-root. Мы уже имеем их в массиве из начального цикла в функции process , но мы ничего с ними не делаем. Давайте изменим это.

Во второй части мы убедились, что папка «Diigo #BBS» существует. Как только мы убедились, что это так, мы можем инициировать создание корневых закладок - у них есть дом, в который мы можем поместить их в этот момент.

Перепишите часть функции process из этого:

 var folderName = 'Diigo #BBS'; chrome.bookmarks.getFirstChildByTitle("1", folderName, function(value) { if (value === false) { chrome.bookmarks.create({ parentId: "1", title: folderName }, function (folder) { console.log(folderName + " not found and has been created at ID " + folder.id); }); } }); 

в

 var folderName = 'Diigo #BBS'; chrome.bookmarks.getFirstChildByTitle("1", folderName, function(value) { if (value === false) { chrome.bookmarks.create({ parentId: "1", title: folderName }, function (folder) { clog(folderName + " not found and has been created at ID " + folder.id); processTagsFolder(folder, allTags); }); } else { processTagsFolder(value, allTags); } }); 

Как видите, мы добавили новый вызов функции processTagsFolder . Эта функция получает папку «Diigo #BBS», переданную в качестве первого параметра, и массив всех тегов в качестве второго. Поскольку этот метод выполняется в любом случае - независимо от того, существовала ли папка «Diigo #BBS» или нет, мы можем поместить в нее нашу логику создания корневых закладок.

  /** * Creates the Tags master folder if it doesn't exist * Initiates the check for tag subfolders * Creates ROOT bookmarks * @param rootNode * @param tagsArray */ function processTagsFolder(rootNode, tagsArray) { // Get all current root bookmarks, if any chrome.bookmarks.getChildren(rootNode.id, function (currentRoots) { var crl = currentRoots.length; var ignoredUrls = []; var rootNumOrig = rootBookmarks.length; if (crl) { var bAmongThem = false; var rootNum = rootNumOrig; // Iterate through all the current items in the root folder while (crl--) { // Check if current item is a URL bookmark, not a folder if (currentRoots[crl].hasOwnProperty('url')) { // Iterate through downloaded bookmarks to see if it's among them bAmongThem = false; while (rootNum--) { if (rootBookmarks[rootNum].url == currentRoots[crl].url) { // Found among existing! bAmongThem = true; if (rootBookmarks[rootNum].title != currentRoots[crl].title) { // Does title need updating? chrome.bookmarks.update(currentRoots[crl].id, { title: rootBookmarks[rootNum].title }); } // Ignore this URL when later adding the downloaded root bookmarks ignoredUrls.push(rootBookmarks[rootNum].url); break; } } if (!bAmongThem) { // Does not exist in downloaded - needs to be deleted from browser chrome.bookmarks.remove(currentRoots[crl].id); } } } } // At this point, we know we removed all the bookmarks that are no longer in our Diigo account // Now let's add those that are left while (rootNumOrig--) { if (ignoredUrls.indexOf(rootBookmarks[rootNumOrig].url) === -1) { chrome.bookmarks.create({ url: rootBookmarks[rootNumOrig].url, title: rootBookmarks[rootNumOrig].title, parentId: rootNode.id }); } } }); } 

Короче говоря, мы делаем выборку всех текущих корневых закладок, проверяем, есть ли они среди недавно загруженных, и удаляем их, если их нет (это означает, что они были помечены как bbs-root в Diigo), и наконец, мы добавляем все остальные. Если вы попробуете это, это должно работать замечательно.

Нам также нужно создать папку Tags, если она не существует. Добавьте следующий код прямо под последний бит:

 chrome.bookmarks.getFirstChildByTitle(rootNode.id, 'Tags', function (tagsFolder) { if (tagsFolder === false) { chrome.bookmarks.create({ parentId: rootNode.id, title: "Tags" }, function (folder) { processTags(folder, tagsArray); }); } else { processTags(tagsFolder, tagsArray); } }); 

Очевидно, мы создали еще одну функцию, которая вызывается независимо от того, существовала ли ранее папка Tags. Давайте определим processTags .

Обработка тегов

  /** * Creates all non-existent tag subfolders. * Removes all tag subfolders that do not have any bookmarks. * @param tagsFolder * @param tagsArray */ function processTags(tagsFolder, tagsArray) { // Remove all unused tag subfolders chrome.bookmarks.getChildren(tagsFolder.id, function (currentTagSubfolders) { var numCurrentTags = currentTagSubfolders.length; if (numCurrentTags > 0) { var currentTags = []; var currentTagsIds = {}; var cTag; while (numCurrentTags--) { cTag = currentTagSubfolders[numCurrentTags]; currentTags.push(cTag.title); currentTagsIds[cTag.title] = cTag.id; } var diff = currentTags.diff(allTags, false); var numUnused = diff.length; if (numUnused) { while (numUnused--) { chrome.bookmarks.removeTree(currentTagsIds 
 

]);
}
}
}
});

// Создать необходимые подпапки тегов
var numTags = tagsArray.length;
while (numTags--) {
let title = tagsArray [numTags];
chrome.bookmarks.getFirstChildByTitle (tagsFolder.id, title, function (tagFolder) {
if (tagFolder === false) {
// Необходимо создать
chrome.bookmarks.create ({
parentId: tagsFolder.id,
название: название
}, функция (папка) {
addAllBookmarksWithTag (папка);
});
} еще {
addAllBookmarksWithTag (tagFolder);
}
});
}
}

Вышеприведенная функция отфильтровывает разницу между массивом AllTags (список тегов, которые мы извлекли только что из Diigo и сделали уникальными) и подпапками тегов, которые в настоящее время находятся в папке «Теги». Это различие представляет те папки на панели закладок Chrome, которые больше не имеют членов в пользовательской библиотеке Diigo. Таким образом, эти папки удаляются из Chrome.

После завершения очистки функция выполняет итерацию по списку самых последних тегов, загруженных из Diigo, и создает эти подпапки, после чего addAllBookmarksWithTag функция addAllBookmarksWithTag .

Добавление закладок в подпапку тега

  /** * Adds all bookmarks with given tag to provided folder, if they don't exist. * Looks at URL for comparison, not title. * @param folder */ function addAllBookmarksWithTag(folder) { chrome.bookmarks.getChildren(folder.id, function (children) { var urls = {}; if (children.length > 0) { var numChildren = children.length; var subItem; while (numChildren--) { subItem = children[numChildren]; urls[subItem.url] = subItem; } } var i = iLength; var key = false; while (i--) { var item = response[i]; var tags = item.tags.split(','); if (tags.indexOf(folder.title) > -1) { // Bookmark belongs in folder if (urls.hasOwnProperty(item.url)) { key = item.url; } if (urls.hasOwnProperty(item.url + "/")) { key = item.url + "/"; } if (key) { // Bookmark already exists in folder if (urls[key].title != item.title) { // Title needs an update clog('Title updated: "' + urls[key].title + '" to "' + item.title + '"'); chrome.bookmarks.update(urls[key].id, {title: item.title}); } } else { // Bookmark needs to be created chrome.bookmarks.create({ parentId: folder.id, title: item.title, url: item.url }, function (bookmarkItem) { clog("Created Item: " + bookmarkItem.title + " on " + bookmarkItem.url); }); } } } }); } 

Наконец, мы добавляем закладки в соответствующие папки тегов. Сначала мы создаем объект, содержащий текущие URL-адреса закладок из каждой папки в качестве ключей, а сами узлы закладок - в качестве значений.

Функция перебирает исходный набор результатов, разбивает теги и проверяет, принадлежит ли закладка, с которой она имеет дело в данный момент, к текущей папке. Трюк "/" происходит из-за того, что Diigo иногда вставляет случайные косые черты в URL. Мы рассмотрим это в следующей статье «Оптимизации». Если закладка принадлежит папке и уже находится внутри, функция проверяет, нужно ли обновить заголовок закладки. Если так, это обновляет это. Если закладка не существует в папке, и должна, то она создается.

Вывод

В конце концов, мы построили большую часть нашего расширения. Есть еще некоторые причуды, чтобы сгладить, но большая часть работы сделана. Вы можете скачать финальную версию background.js с Github .

В части 4 мы сконцентрируемся на том, чтобы позволить людям войти в расширение и использовать их собственную учетную запись, мы разрешим добавление пользовательских ключей API в случае проблем с квотами и немного оптимизируем наш код. Будьте на связи!