зеркало из 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.PropertyAnimator;
|
||||||
import org.mozilla.gecko.animation.ViewHelper;
|
import org.mozilla.gecko.animation.ViewHelper;
|
||||||
import org.mozilla.gecko.db.BrowserContract.Combined;
|
import org.mozilla.gecko.db.BrowserContract.Combined;
|
||||||
|
import org.mozilla.gecko.db.BrowserContract.ReadingListItems;
|
||||||
import org.mozilla.gecko.db.BrowserDB;
|
import org.mozilla.gecko.db.BrowserDB;
|
||||||
import org.mozilla.gecko.favicons.Favicons;
|
import org.mozilla.gecko.favicons.Favicons;
|
||||||
import org.mozilla.gecko.favicons.LoadFaviconTask;
|
import org.mozilla.gecko.favicons.LoadFaviconTask;
|
||||||
|
@ -59,6 +60,7 @@ import org.mozilla.gecko.widget.GeckoActionProvider;
|
||||||
|
|
||||||
import android.app.Activity;
|
import android.app.Activity;
|
||||||
import android.app.AlertDialog;
|
import android.app.AlertDialog;
|
||||||
|
import android.content.ContentValues;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.DialogInterface;
|
import android.content.DialogInterface;
|
||||||
import android.content.Intent;
|
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_SUCCESS) {
|
||||||
if (result == READER_ADD_FAILED) {
|
if (result == READER_ADD_FAILED) {
|
||||||
showToast(R.string.reading_list_failed, Toast.LENGTH_SHORT);
|
showToast(R.string.reading_list_failed, Toast.LENGTH_SHORT);
|
||||||
|
@ -399,7 +401,7 @@ abstract public class BrowserApp extends GeckoApp
|
||||||
ThreadUtils.postToBackgroundThread(new Runnable() {
|
ThreadUtils.postToBackgroundThread(new Runnable() {
|
||||||
@Override
|
@Override
|
||||||
public void run() {
|
public void run() {
|
||||||
BrowserDB.addReadingListItem(getContentResolver(), title, url);
|
BrowserDB.addReadingListItem(getContentResolver(), values);
|
||||||
showToast(R.string.reading_list_added, Toast.LENGTH_SHORT);
|
showToast(R.string.reading_list_added, Toast.LENGTH_SHORT);
|
||||||
|
|
||||||
final int count = BrowserDB.getReadingListCount(getContentResolver());
|
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) {
|
void handleReaderRemoved(final String url) {
|
||||||
ThreadUtils.postToBackgroundThread(new Runnable() {
|
ThreadUtils.postToBackgroundThread(new Runnable() {
|
||||||
@Override
|
@Override
|
||||||
|
@ -1127,9 +1138,7 @@ abstract public class BrowserApp extends GeckoApp
|
||||||
handleReaderListStatusRequest(message.getString("url"));
|
handleReaderListStatusRequest(message.getString("url"));
|
||||||
} else if (event.equals("Reader:Added")) {
|
} else if (event.equals("Reader:Added")) {
|
||||||
final int result = message.getInt("result");
|
final int result = message.getInt("result");
|
||||||
final String title = message.getString("title");
|
handleReaderAdded(result, messageToReadingListContentValues(message));
|
||||||
final String url = message.getString("url");
|
|
||||||
handleReaderAdded(result, title, url);
|
|
||||||
} else if (event.equals("Reader:Removed")) {
|
} else if (event.equals("Reader:Removed")) {
|
||||||
final String url = message.getString("url");
|
final String url = message.getString("url");
|
||||||
handleReaderRemoved(url);
|
handleReaderRemoved(url);
|
||||||
|
|
|
@ -396,6 +396,9 @@ public class BrowserContract {
|
||||||
public static final String DEFAULT_SORT_ORDER = _ID + " DESC";
|
public static final String DEFAULT_SORT_ORDER = _ID + " DESC";
|
||||||
public static final String[] DEFAULT_PROJECTION = new String[] { _ID, URL, TITLE, EXCERPT, LENGTH };
|
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";
|
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 org.mozilla.gecko.mozglue.RobocopTarget;
|
||||||
|
|
||||||
import android.content.ContentResolver;
|
import android.content.ContentResolver;
|
||||||
|
import android.content.ContentValues;
|
||||||
import android.database.ContentObserver;
|
import android.database.ContentObserver;
|
||||||
import android.database.Cursor;
|
import android.database.Cursor;
|
||||||
import android.database.CursorWrapper;
|
import android.database.CursorWrapper;
|
||||||
|
@ -98,7 +99,7 @@ public class BrowserDB {
|
||||||
@RobocopTarget
|
@RobocopTarget
|
||||||
public void updateBookmark(ContentResolver cr, int id, String uri, String title, String keyword);
|
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);
|
public void removeReadingListItemWithURL(ContentResolver cr, String uri);
|
||||||
|
|
||||||
|
@ -271,8 +272,8 @@ public class BrowserDB {
|
||||||
sDb.updateBookmark(cr, id, uri, title, keyword);
|
sDb.updateBookmark(cr, id, uri, title, keyword);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void addReadingListItem(ContentResolver cr, String title, String uri) {
|
public static void addReadingListItem(ContentResolver cr, ContentValues values) {
|
||||||
sDb.addReadingListItem(cr, title, uri);
|
sDb.addReadingListItem(cr, values);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void removeReadingListItemWithURL(ContentResolver cr, String uri) {
|
public static void removeReadingListItemWithURL(ContentResolver cr, String uri) {
|
||||||
|
|
|
@ -699,11 +699,16 @@ public class LocalBrowserDB implements BrowserDB.BrowserDBIface {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void addReadingListItem(ContentResolver cr, String title, String uri) {
|
public void addReadingListItem(ContentResolver cr, ContentValues values) {
|
||||||
final ContentValues values = new ContentValues();
|
// 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.IS_DELETED, 0);
|
||||||
values.put(ReadingListItems.URL, uri);
|
|
||||||
values.put(ReadingListItems.TITLE, title);
|
|
||||||
|
|
||||||
// Restore deleted record if possible
|
// Restore deleted record if possible
|
||||||
final Uri insertUri = mReadingListUriWithProfile
|
final Uri insertUri = mReadingListUriWithProfile
|
||||||
|
@ -714,7 +719,7 @@ public class LocalBrowserDB implements BrowserDB.BrowserDBIface {
|
||||||
final int updated = cr.update(insertUri,
|
final int updated = cr.update(insertUri,
|
||||||
values,
|
values,
|
||||||
ReadingListItems.URL + " = ? ",
|
ReadingListItems.URL + " = ? ",
|
||||||
new String[] { uri });
|
new String[] { values.getAsString(ReadingListItems.URL) });
|
||||||
|
|
||||||
debug("Updated " + updated + " rows to new modified time.");
|
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.
|
* Removes script tags from the document.
|
||||||
*
|
*
|
||||||
|
@ -1434,9 +1505,13 @@ Readability.prototype = {
|
||||||
// }).bind(this), 500);
|
// }).bind(this), 500);
|
||||||
// }
|
// }
|
||||||
|
|
||||||
|
let excerpt = this._getExcerpt(articleContent);
|
||||||
|
|
||||||
return { title: articleTitle,
|
return { title: articleTitle,
|
||||||
byline: this._articleByline,
|
byline: this._articleByline,
|
||||||
dir: this._articleDir,
|
dir: this._articleDir,
|
||||||
content: articleContent.innerHTML };
|
content: articleContent.innerHTML,
|
||||||
|
length: articleContent.textContent.length,
|
||||||
|
excerpt: excerpt };
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
|
@ -349,6 +349,8 @@ AboutReader.prototype = {
|
||||||
result: result,
|
result: result,
|
||||||
title: this._article.title,
|
title: this._article.title,
|
||||||
url: this._article.url,
|
url: this._article.url,
|
||||||
|
length: this._article.length,
|
||||||
|
excerpt: this._article.excerpt
|
||||||
});
|
});
|
||||||
}.bind(this));
|
}.bind(this));
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -7433,33 +7433,36 @@ let Reader = {
|
||||||
throw new Error("Reader:Add requires a tabID or an URL as argument");
|
throw new Error("Reader:Add requires a tabID or an URL as argument");
|
||||||
}
|
}
|
||||||
|
|
||||||
let sendResult = function(result, title) {
|
let sendResult = function(result, article) {
|
||||||
this.log("Reader:Add success=" + result + ", url=" + url + ", title=" + title);
|
article = article || {};
|
||||||
|
this.log("Reader:Add success=" + result + ", url=" + url + ", title=" + article.title + ", excerpt=" + article.excerpt);
|
||||||
|
|
||||||
sendMessageToJava({
|
sendMessageToJava({
|
||||||
type: "Reader:Added",
|
type: "Reader:Added",
|
||||||
result: result,
|
result: result,
|
||||||
title: title,
|
title: article.title,
|
||||||
url: url,
|
url: url,
|
||||||
|
length: article.length,
|
||||||
|
excerpt: article.excerpt
|
||||||
});
|
});
|
||||||
}.bind(this);
|
}.bind(this);
|
||||||
|
|
||||||
let handleArticle = function(article) {
|
let handleArticle = function(article) {
|
||||||
if (!article) {
|
if (!article) {
|
||||||
sendResult(this.READER_ADD_FAILED, "");
|
sendResult(this.READER_ADD_FAILED, null);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.storeArticleInCache(article, function(success) {
|
this.storeArticleInCache(article, function(success) {
|
||||||
let result = (success ? this.READER_ADD_SUCCESS : this.READER_ADD_FAILED);
|
let result = (success ? this.READER_ADD_SUCCESS : this.READER_ADD_FAILED);
|
||||||
sendResult(result, article.title);
|
sendResult(result, article);
|
||||||
}.bind(this));
|
}.bind(this));
|
||||||
}.bind(this);
|
}.bind(this);
|
||||||
|
|
||||||
this.getArticleFromCache(urlWithoutRef, function (article) {
|
this.getArticleFromCache(urlWithoutRef, function (article) {
|
||||||
// If the article is already in reading list, bail
|
// If the article is already in reading list, bail
|
||||||
if (article) {
|
if (article) {
|
||||||
sendResult(this.READER_ADD_DUPLICATE, "");
|
sendResult(this.READER_ADD_DUPLICATE, null);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -7473,13 +7476,14 @@ let Reader = {
|
||||||
}
|
}
|
||||||
|
|
||||||
case "Reader:Remove": {
|
case "Reader:Remove": {
|
||||||
this.removeArticleFromCache(aData, function(success) {
|
let url = aData;
|
||||||
this.log("Reader:Remove success=" + success + ", url=" + aData);
|
this.removeArticleFromCache(url, function(success) {
|
||||||
|
this.log("Reader:Remove success=" + success + ", url=" + url);
|
||||||
|
|
||||||
if (success) {
|
if (success) {
|
||||||
sendMessageToJava({
|
sendMessageToJava({
|
||||||
type: "Reader:Removed",
|
type: "Reader:Removed",
|
||||||
url: aData
|
url: url
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}.bind(this));
|
}.bind(this));
|
||||||
|
|
Загрузка…
Ссылка в новой задаче