зеркало из https://github.com/mozilla/gecko-dev.git
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:
Родитель
a8936cf3db
Коммит
93a0a6fc56
|
@ -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));
|
||||
|
|
Загрузка…
Ссылка в новой задаче