Bug 959297 - Get description and approx. reading time for reading list items. r=liuche, r=lucasr, r=margaret, r=rnewman

This commit is contained in:
Sola Ogunsakin 2014-03-12 14:53:25 -07:00
Родитель a8936cf3db
Коммит 93a0a6fc56
7 изменённых файлов: 122 добавлений и 23 удалений

Просмотреть файл

@ -20,6 +20,7 @@ import org.mozilla.gecko.GeckoProfileDirectories.NoMozillaDirectoryException;
import org.mozilla.gecko.animation.PropertyAnimator;
import org.mozilla.gecko.animation.ViewHelper;
import org.mozilla.gecko.db.BrowserContract.Combined;
import org.mozilla.gecko.db.BrowserContract.ReadingListItems;
import org.mozilla.gecko.db.BrowserDB;
import org.mozilla.gecko.favicons.Favicons;
import org.mozilla.gecko.favicons.LoadFaviconTask;
@ -59,6 +60,7 @@ import org.mozilla.gecko.widget.GeckoActionProvider;
import android.app.Activity;
import android.app.AlertDialog;
import android.content.ContentValues;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
@ -385,7 +387,7 @@ abstract public class BrowserApp extends GeckoApp
});
}
void handleReaderAdded(int result, final String title, final String url) {
private void handleReaderAdded(int result, final ContentValues values) {
if (result != READER_ADD_SUCCESS) {
if (result == READER_ADD_FAILED) {
showToast(R.string.reading_list_failed, Toast.LENGTH_SHORT);
@ -399,7 +401,7 @@ abstract public class BrowserApp extends GeckoApp
ThreadUtils.postToBackgroundThread(new Runnable() {
@Override
public void run() {
BrowserDB.addReadingListItem(getContentResolver(), title, url);
BrowserDB.addReadingListItem(getContentResolver(), values);
showToast(R.string.reading_list_added, Toast.LENGTH_SHORT);
final int count = BrowserDB.getReadingListCount(getContentResolver());
@ -408,6 +410,15 @@ abstract public class BrowserApp extends GeckoApp
});
}
private ContentValues messageToReadingListContentValues(JSONObject message) {
final ContentValues values = new ContentValues();
values.put(ReadingListItems.URL, message.optString("url"));
values.put(ReadingListItems.TITLE, message.optString("title"));
values.put(ReadingListItems.LENGTH, message.optInt("length"));
values.put(ReadingListItems.EXCERPT, message.optString("excerpt"));
return values;
}
void handleReaderRemoved(final String url) {
ThreadUtils.postToBackgroundThread(new Runnable() {
@Override
@ -1127,9 +1138,7 @@ abstract public class BrowserApp extends GeckoApp
handleReaderListStatusRequest(message.getString("url"));
} else if (event.equals("Reader:Added")) {
final int result = message.getInt("result");
final String title = message.getString("title");
final String url = message.getString("url");
handleReaderAdded(result, title, url);
handleReaderAdded(result, messageToReadingListContentValues(message));
} else if (event.equals("Reader:Removed")) {
final String url = message.getString("url");
handleReaderRemoved(url);

Просмотреть файл

@ -396,6 +396,9 @@ public class BrowserContract {
public static final String DEFAULT_SORT_ORDER = _ID + " DESC";
public static final String[] DEFAULT_PROJECTION = new String[] { _ID, URL, TITLE, EXCERPT, LENGTH };
// Minimum fields required to create a reading list item.
public static final String[] REQUIRED_FIELDS = { Bookmarks.URL, Bookmarks.TITLE };
public static final String TABLE_NAME = "reading_list";
}

Просмотреть файл

@ -13,6 +13,7 @@ import org.mozilla.gecko.favicons.decoders.LoadFaviconResult;
import org.mozilla.gecko.mozglue.RobocopTarget;
import android.content.ContentResolver;
import android.content.ContentValues;
import android.database.ContentObserver;
import android.database.Cursor;
import android.database.CursorWrapper;
@ -98,7 +99,7 @@ public class BrowserDB {
@RobocopTarget
public void updateBookmark(ContentResolver cr, int id, String uri, String title, String keyword);
public void addReadingListItem(ContentResolver cr, String title, String uri);
public void addReadingListItem(ContentResolver cr, ContentValues values);
public void removeReadingListItemWithURL(ContentResolver cr, String uri);
@ -271,8 +272,8 @@ public class BrowserDB {
sDb.updateBookmark(cr, id, uri, title, keyword);
}
public static void addReadingListItem(ContentResolver cr, String title, String uri) {
sDb.addReadingListItem(cr, title, uri);
public static void addReadingListItem(ContentResolver cr, ContentValues values) {
sDb.addReadingListItem(cr, values);
}
public static void removeReadingListItemWithURL(ContentResolver cr, String uri) {

Просмотреть файл

@ -699,11 +699,16 @@ public class LocalBrowserDB implements BrowserDB.BrowserDBIface {
}
@Override
public void addReadingListItem(ContentResolver cr, String title, String uri) {
final ContentValues values = new ContentValues();
public void addReadingListItem(ContentResolver cr, ContentValues values) {
// Check that required fields are present.
for (String field: ReadingListItems.REQUIRED_FIELDS) {
if (!values.containsKey(field)) {
throw new IllegalArgumentException("Missing required field for reading list item: " + field);
}
}
// Clear delete flag if necessary
values.put(ReadingListItems.IS_DELETED, 0);
values.put(ReadingListItems.URL, uri);
values.put(ReadingListItems.TITLE, title);
// Restore deleted record if possible
final Uri insertUri = mReadingListUriWithProfile
@ -714,7 +719,7 @@ public class LocalBrowserDB implements BrowserDB.BrowserDBIface {
final int updated = cr.update(insertUri,
values,
ReadingListItems.URL + " = ? ",
new String[] { uri });
new String[] { values.getAsString(ReadingListItems.URL) });
debug("Updated " + updated + " rows to new modified time.");
}

Просмотреть файл

@ -718,6 +718,77 @@ Readability.prototype = {
}
},
/**
* Attempts to get the excerpt from these
* sources in the following order:
* - meta description tag
* - open-graph description
* - twitter cards description
* - article's first paragraph
* If no excerpt is found, an empty string will be
* returned.
*
* @param Element - root element of the processed version page
* @return String - excerpt of the article
**/
_getExcerpt: function(articleContent) {
let values = {};
let metaElements = this._doc.getElementsByTagName("meta");
// Match "description", or Twitter's "twitter:description" (Cards)
// in name attribute.
let namePattern = /^\s*((twitter)\s*:\s*)?description\s*$/gi;
// Match Facebook's og:description (Open Graph) in property attribute.
let propertyPattern = /^\s*og\s*:\s*description\s*$/gi;
// Find description tags.
for (let i = 0; i < metaElements.length; i++) {
let element = metaElements[i];
let elementName = element.getAttribute("name");
let elementProperty = element.getAttribute("property");
let name;
if (namePattern.test(elementName)) {
name = elementName;
} else if (propertyPattern.test(elementProperty)) {
name = elementProperty;
}
if (name) {
let content = element.getAttribute("content");
if (content) {
// Convert to lowercase and remove any whitespace
// so we can match below.
name = name.toLowerCase().replace(/\s/g, '');
values[name] = content.trim();
}
}
}
if ("description" in values) {
return values["description"];
}
if ("og:description" in values) {
// Use facebook open graph description.
return values["og:description"];
}
if ("twitter:description" in values) {
// Use twitter cards description.
return values["twitter:description"];
}
// No description meta tags, use the article's first paragraph.
let paragraphs = articleContent.getElementsByTagName("p");
if (paragraphs.length > 0) {
return paragraphs[0].textContent;
}
return "";
},
/**
* Removes script tags from the document.
*
@ -1434,9 +1505,13 @@ Readability.prototype = {
// }).bind(this), 500);
// }
let excerpt = this._getExcerpt(articleContent);
return { title: articleTitle,
byline: this._articleByline,
dir: this._articleDir,
content: articleContent.innerHTML };
content: articleContent.innerHTML,
length: articleContent.textContent.length,
excerpt: excerpt };
}
};

Просмотреть файл

@ -349,6 +349,8 @@ AboutReader.prototype = {
result: result,
title: this._article.title,
url: this._article.url,
length: this._article.length,
excerpt: this._article.excerpt
});
}.bind(this));
} else {

Просмотреть файл

@ -7433,33 +7433,36 @@ let Reader = {
throw new Error("Reader:Add requires a tabID or an URL as argument");
}
let sendResult = function(result, title) {
this.log("Reader:Add success=" + result + ", url=" + url + ", title=" + title);
let sendResult = function(result, article) {
article = article || {};
this.log("Reader:Add success=" + result + ", url=" + url + ", title=" + article.title + ", excerpt=" + article.excerpt);
sendMessageToJava({
type: "Reader:Added",
result: result,
title: title,
title: article.title,
url: url,
length: article.length,
excerpt: article.excerpt
});
}.bind(this);
let handleArticle = function(article) {
if (!article) {
sendResult(this.READER_ADD_FAILED, "");
sendResult(this.READER_ADD_FAILED, null);
return;
}
this.storeArticleInCache(article, function(success) {
let result = (success ? this.READER_ADD_SUCCESS : this.READER_ADD_FAILED);
sendResult(result, article.title);
sendResult(result, article);
}.bind(this));
}.bind(this);
this.getArticleFromCache(urlWithoutRef, function (article) {
// If the article is already in reading list, bail
if (article) {
sendResult(this.READER_ADD_DUPLICATE, "");
sendResult(this.READER_ADD_DUPLICATE, null);
return;
}
@ -7473,13 +7476,14 @@ let Reader = {
}
case "Reader:Remove": {
this.removeArticleFromCache(aData, function(success) {
this.log("Reader:Remove success=" + success + ", url=" + aData);
let url = aData;
this.removeArticleFromCache(url, function(success) {
this.log("Reader:Remove success=" + success + ", url=" + url);
if (success) {
sendMessageToJava({
type: "Reader:Removed",
url: aData
url: url
});
}
}.bind(this));