зеркало из https://github.com/mozilla/gecko-dev.git
Bug 1405539 - Add responsive bookmarking, info doorhanger and bug fixes to Activity Stream. r=k88hudson
MozReview-Commit-ID: C5LXhbuI0EA --HG-- extra : rebase_source : 580d19a032d6d033317c88470256d864ea8bc529
This commit is contained in:
Родитель
1272df551e
Коммит
b6f0a8f2d8
|
@ -87,6 +87,15 @@ let allowedImageReferences = [
|
|||
{file: "chrome://devtools/skin/images/dock-bottom-maximize@2x.png",
|
||||
from: "chrome://devtools/skin/toolbox.css",
|
||||
isFromDevTools: true},
|
||||
// Bug 1405539
|
||||
{file: "chrome://global/skin/arrow/panelarrow-vertical@2x.png",
|
||||
from: "resource://activity-stream/data/content/activity-stream.css",
|
||||
isFromDevTools: false,
|
||||
platforms: ["linux", "win"]},
|
||||
{file: "chrome://global/skin/arrow/panelarrow-vertical-themed.svg",
|
||||
from: "resource://activity-stream/data/content/activity-stream.css",
|
||||
isFromDevTools: false,
|
||||
platforms: ["macosx"]},
|
||||
];
|
||||
|
||||
// Add suffix to stylesheets' URI so that we always load them here and
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
"use strict";
|
||||
|
||||
const {actionTypes: at} = Components.utils.import("resource://activity-stream/common/Actions.jsm", {});
|
||||
const {Dedupe} = Components.utils.import("resource://activity-stream/common/Dedupe.jsm", {});
|
||||
|
||||
// Locales that should be displayed RTL
|
||||
const RTL_LIST = ["ar", "he", "fa", "ur"];
|
||||
|
@ -11,6 +12,8 @@ const RTL_LIST = ["ar", "he", "fa", "ur"];
|
|||
const TOP_SITES_DEFAULT_LENGTH = 6;
|
||||
const TOP_SITES_SHOWMORE_LENGTH = 12;
|
||||
|
||||
const dedupe = new Dedupe(site => site && site.url);
|
||||
|
||||
const INITIAL_STATE = {
|
||||
App: {
|
||||
// Have we received real data from the app yet?
|
||||
|
@ -230,7 +233,7 @@ function Sections(prevState = INITIAL_STATE.Sections, action) {
|
|||
}
|
||||
return newState;
|
||||
case at.SECTION_UPDATE:
|
||||
return prevState.map(section => {
|
||||
newState = prevState.map(section => {
|
||||
if (section && section.id === action.data.id) {
|
||||
// If the action is updating rows, we should consider initialized to be true.
|
||||
// This can be overridden if initialized is defined in the action.data
|
||||
|
@ -239,6 +242,28 @@ function Sections(prevState = INITIAL_STATE.Sections, action) {
|
|||
}
|
||||
return section;
|
||||
});
|
||||
|
||||
if (!action.data.dedupeConfigurations) {
|
||||
return newState;
|
||||
}
|
||||
|
||||
action.data.dedupeConfigurations.forEach(dedupeConf => {
|
||||
newState = newState.map(section => {
|
||||
if (section.id === dedupeConf.id) {
|
||||
const dedupedRows = dedupeConf.dedupeFrom.reduce((rows, dedupeSectionId) => {
|
||||
const dedupeSection = newState.find(s => s.id === dedupeSectionId);
|
||||
const [, newRows] = dedupe.group(dedupeSection.rows, rows);
|
||||
return newRows;
|
||||
}, section.rows);
|
||||
|
||||
return Object.assign({}, section, {rows: dedupedRows});
|
||||
}
|
||||
|
||||
return section;
|
||||
});
|
||||
});
|
||||
|
||||
return newState;
|
||||
case at.SECTION_UPDATE_CARD:
|
||||
return prevState.map(section => {
|
||||
if (section && section.id === action.data.id && section.rows) {
|
||||
|
@ -261,10 +286,12 @@ function Sections(prevState = INITIAL_STATE.Sections, action) {
|
|||
// find the item within the rows that is attempted to be bookmarked
|
||||
if (item.url === action.data.url) {
|
||||
const {bookmarkGuid, bookmarkTitle, dateAdded} = action.data;
|
||||
Object.assign(item, {bookmarkGuid, bookmarkTitle, bookmarkDateCreated: dateAdded});
|
||||
if (!item.type || item.type === "history") {
|
||||
item.type = "bookmark";
|
||||
}
|
||||
return Object.assign({}, item, {
|
||||
bookmarkGuid,
|
||||
bookmarkTitle,
|
||||
bookmarkDateCreated: dateAdded,
|
||||
type: "bookmark"
|
||||
});
|
||||
}
|
||||
return item;
|
||||
})
|
||||
|
|
Различия файлов скрыты, потому что одна или несколько строк слишком длинны
|
@ -351,6 +351,7 @@ module.exports = {
|
|||
|
||||
|
||||
const { actionTypes: at } = __webpack_require__(0);
|
||||
const { Dedupe } = __webpack_require__(20);
|
||||
|
||||
// Locales that should be displayed RTL
|
||||
const RTL_LIST = ["ar", "he", "fa", "ur"];
|
||||
|
@ -358,6 +359,8 @@ const RTL_LIST = ["ar", "he", "fa", "ur"];
|
|||
const TOP_SITES_DEFAULT_LENGTH = 6;
|
||||
const TOP_SITES_SHOWMORE_LENGTH = 12;
|
||||
|
||||
const dedupe = new Dedupe(site => site && site.url);
|
||||
|
||||
const INITIAL_STATE = {
|
||||
App: {
|
||||
// Have we received real data from the app yet?
|
||||
|
@ -580,7 +583,7 @@ function Sections(prevState = INITIAL_STATE.Sections, action) {
|
|||
}
|
||||
return newState;
|
||||
case at.SECTION_UPDATE:
|
||||
return prevState.map(section => {
|
||||
newState = prevState.map(section => {
|
||||
if (section && section.id === action.data.id) {
|
||||
// If the action is updating rows, we should consider initialized to be true.
|
||||
// This can be overridden if initialized is defined in the action.data
|
||||
|
@ -589,6 +592,28 @@ function Sections(prevState = INITIAL_STATE.Sections, action) {
|
|||
}
|
||||
return section;
|
||||
});
|
||||
|
||||
if (!action.data.dedupeConfigurations) {
|
||||
return newState;
|
||||
}
|
||||
|
||||
action.data.dedupeConfigurations.forEach(dedupeConf => {
|
||||
newState = newState.map(section => {
|
||||
if (section.id === dedupeConf.id) {
|
||||
const dedupedRows = dedupeConf.dedupeFrom.reduce((rows, dedupeSectionId) => {
|
||||
const dedupeSection = newState.find(s => s.id === dedupeSectionId);
|
||||
const [, newRows] = dedupe.group(dedupeSection.rows, rows);
|
||||
return newRows;
|
||||
}, section.rows);
|
||||
|
||||
return Object.assign({}, section, { rows: dedupedRows });
|
||||
}
|
||||
|
||||
return section;
|
||||
});
|
||||
});
|
||||
|
||||
return newState;
|
||||
case at.SECTION_UPDATE_CARD:
|
||||
return prevState.map(section => {
|
||||
if (section && section.id === action.data.id && section.rows) {
|
||||
|
@ -611,10 +636,12 @@ function Sections(prevState = INITIAL_STATE.Sections, action) {
|
|||
// find the item within the rows that is attempted to be bookmarked
|
||||
if (item.url === action.data.url) {
|
||||
const { bookmarkGuid, bookmarkTitle, dateAdded } = action.data;
|
||||
Object.assign(item, { bookmarkGuid, bookmarkTitle, bookmarkDateCreated: dateAdded });
|
||||
if (!item.type || item.type === "history") {
|
||||
item.type = "bookmark";
|
||||
}
|
||||
return Object.assign({}, item, {
|
||||
bookmarkGuid,
|
||||
bookmarkTitle,
|
||||
bookmarkDateCreated: dateAdded,
|
||||
type: "bookmark"
|
||||
});
|
||||
}
|
||||
return item;
|
||||
})
|
||||
|
@ -1099,9 +1126,9 @@ module.exports._unconnected = LinkMenu;
|
|||
const React = __webpack_require__(1);
|
||||
const { connect } = __webpack_require__(3);
|
||||
const { injectIntl, FormattedMessage } = __webpack_require__(2);
|
||||
const Card = __webpack_require__(20);
|
||||
const Card = __webpack_require__(21);
|
||||
const { PlaceholderCard } = Card;
|
||||
const Topics = __webpack_require__(22);
|
||||
const Topics = __webpack_require__(23);
|
||||
const { actionCreators: ac, actionTypes: at } = __webpack_require__(0);
|
||||
|
||||
const VISIBLE = "visible";
|
||||
|
@ -1387,10 +1414,10 @@ module.exports.InfoIntl = InfoIntl;
|
|||
const ReactDOM = __webpack_require__(12);
|
||||
const Base = __webpack_require__(13);
|
||||
const { Provider } = __webpack_require__(3);
|
||||
const initStore = __webpack_require__(29);
|
||||
const initStore = __webpack_require__(30);
|
||||
const { reducers } = __webpack_require__(6);
|
||||
const DetectUserSessionStart = __webpack_require__(31);
|
||||
const { addSnippetsSubscriber } = __webpack_require__(32);
|
||||
const DetectUserSessionStart = __webpack_require__(32);
|
||||
const { addSnippetsSubscriber } = __webpack_require__(33);
|
||||
const { actionTypes: at, actionCreators: ac } = __webpack_require__(0);
|
||||
|
||||
new DetectUserSessionStart().sendEventOrAddListener();
|
||||
|
@ -1427,13 +1454,13 @@ const React = __webpack_require__(1);
|
|||
const { connect } = __webpack_require__(3);
|
||||
const { addLocaleData, IntlProvider } = __webpack_require__(2);
|
||||
const TopSites = __webpack_require__(14);
|
||||
const Search = __webpack_require__(23);
|
||||
const ConfirmDialog = __webpack_require__(25);
|
||||
const ManualMigration = __webpack_require__(26);
|
||||
const PreferencesPane = __webpack_require__(27);
|
||||
const Search = __webpack_require__(24);
|
||||
const ConfirmDialog = __webpack_require__(26);
|
||||
const ManualMigration = __webpack_require__(27);
|
||||
const PreferencesPane = __webpack_require__(28);
|
||||
const Sections = __webpack_require__(10);
|
||||
const { actionTypes: at, actionCreators: ac } = __webpack_require__(0);
|
||||
const { PrerenderData } = __webpack_require__(28);
|
||||
const { PrerenderData } = __webpack_require__(29);
|
||||
|
||||
// Add the locale data for pluralization and relative-time formatting for now,
|
||||
// this just uses english locale data. We can make this more sophisticated if
|
||||
|
@ -2291,12 +2318,57 @@ module.exports.CheckPinTopSite = (site, index) => site.isPinned ? module.exports
|
|||
|
||||
/***/ }),
|
||||
/* 20 */
|
||||
/***/ (function(module, exports) {
|
||||
|
||||
var Dedupe = class Dedupe {
|
||||
constructor(createKey, compare) {
|
||||
this.createKey = createKey || this.defaultCreateKey;
|
||||
this.compare = compare || this.defaultCompare;
|
||||
}
|
||||
|
||||
defaultCreateKey(item) {
|
||||
return item;
|
||||
}
|
||||
|
||||
defaultCompare() {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Dedupe any number of grouped elements favoring those from earlier groups.
|
||||
*
|
||||
* @param {Array} groups Contains an arbitrary number of arrays of elements.
|
||||
* @returns {Array} A matching array of each provided group deduped.
|
||||
*/
|
||||
group(...groups) {
|
||||
const globalKeys = new Set();
|
||||
const result = [];
|
||||
for (const values of groups) {
|
||||
const valueMap = new Map();
|
||||
for (const value of values) {
|
||||
const key = this.createKey(value);
|
||||
if (!globalKeys.has(key) && (!valueMap.has(key) || this.compare(valueMap.get(key), value))) {
|
||||
valueMap.set(key, value);
|
||||
}
|
||||
}
|
||||
result.push(valueMap);
|
||||
valueMap.forEach((value, key) => globalKeys.add(key));
|
||||
}
|
||||
return result.map(m => Array.from(m.values()));
|
||||
}
|
||||
};
|
||||
module.exports = {
|
||||
Dedupe
|
||||
};
|
||||
|
||||
/***/ }),
|
||||
/* 21 */
|
||||
/***/ (function(module, exports, __webpack_require__) {
|
||||
|
||||
const React = __webpack_require__(1);
|
||||
const LinkMenu = __webpack_require__(9);
|
||||
const { FormattedMessage } = __webpack_require__(2);
|
||||
const cardContextTypes = __webpack_require__(21);
|
||||
const cardContextTypes = __webpack_require__(22);
|
||||
const { actionCreators: ac, actionTypes: at } = __webpack_require__(0);
|
||||
|
||||
// Keep track of pending image loads to only request once
|
||||
|
@ -2492,7 +2564,7 @@ module.exports = Card;
|
|||
module.exports.PlaceholderCard = PlaceholderCard;
|
||||
|
||||
/***/ }),
|
||||
/* 21 */
|
||||
/* 22 */
|
||||
/***/ (function(module, exports) {
|
||||
|
||||
module.exports = {
|
||||
|
@ -2515,7 +2587,7 @@ module.exports = {
|
|||
};
|
||||
|
||||
/***/ }),
|
||||
/* 22 */
|
||||
/* 23 */
|
||||
/***/ (function(module, exports, __webpack_require__) {
|
||||
|
||||
const React = __webpack_require__(1);
|
||||
|
@ -2566,7 +2638,7 @@ module.exports._unconnected = Topics;
|
|||
module.exports.Topic = Topic;
|
||||
|
||||
/***/ }),
|
||||
/* 23 */
|
||||
/* 24 */
|
||||
/***/ (function(module, exports, __webpack_require__) {
|
||||
|
||||
"use strict";
|
||||
|
@ -2577,7 +2649,7 @@ const React = __webpack_require__(1);
|
|||
const { connect } = __webpack_require__(3);
|
||||
const { FormattedMessage, injectIntl } = __webpack_require__(2);
|
||||
const { actionCreators: ac } = __webpack_require__(0);
|
||||
const { IS_NEWTAB } = __webpack_require__(24);
|
||||
const { IS_NEWTAB } = __webpack_require__(25);
|
||||
|
||||
class Search extends React.PureComponent {
|
||||
constructor(props) {
|
||||
|
@ -2677,7 +2749,7 @@ module.exports = connect(state => ({ locale: state.App.locale }))(injectIntl(Sea
|
|||
module.exports._unconnected = Search;
|
||||
|
||||
/***/ }),
|
||||
/* 24 */
|
||||
/* 25 */
|
||||
/***/ (function(module, exports, __webpack_require__) {
|
||||
|
||||
/* WEBPACK VAR INJECTION */(function(global) {module.exports = {
|
||||
|
@ -2687,7 +2759,7 @@ module.exports._unconnected = Search;
|
|||
/* WEBPACK VAR INJECTION */}.call(exports, __webpack_require__(4)))
|
||||
|
||||
/***/ }),
|
||||
/* 25 */
|
||||
/* 26 */
|
||||
/***/ (function(module, exports, __webpack_require__) {
|
||||
|
||||
const React = __webpack_require__(1);
|
||||
|
@ -2792,7 +2864,7 @@ module.exports._unconnected = ConfirmDialog;
|
|||
module.exports.Dialog = ConfirmDialog;
|
||||
|
||||
/***/ }),
|
||||
/* 26 */
|
||||
/* 27 */
|
||||
/***/ (function(module, exports, __webpack_require__) {
|
||||
|
||||
const React = __webpack_require__(1);
|
||||
|
@ -2856,7 +2928,7 @@ module.exports = connect()(ManualMigration);
|
|||
module.exports._unconnected = ManualMigration;
|
||||
|
||||
/***/ }),
|
||||
/* 27 */
|
||||
/* 28 */
|
||||
/***/ (function(module, exports, __webpack_require__) {
|
||||
|
||||
const React = __webpack_require__(1);
|
||||
|
@ -2884,7 +2956,12 @@ const PreferencesInput = props => React.createElement(
|
|||
"p",
|
||||
{ className: "prefs-input-description" },
|
||||
getFormattedMessage(props.descString)
|
||||
)
|
||||
),
|
||||
React.Children.map(props.children, child => React.createElement(
|
||||
"div",
|
||||
{ className: `options${child.props.disabled ? " disabled" : ""}` },
|
||||
child
|
||||
))
|
||||
);
|
||||
|
||||
class PreferencesPane extends React.PureComponent {
|
||||
|
@ -2977,21 +3054,51 @@ class PreferencesPane extends React.PureComponent {
|
|||
null,
|
||||
React.createElement(FormattedMessage, { id: "settings_pane_body2" })
|
||||
),
|
||||
React.createElement(PreferencesInput, { className: "showSearch", prefName: "showSearch", value: prefs.showSearch, onChange: this.handlePrefChange,
|
||||
titleString: { id: "settings_pane_search_header" }, descString: { id: "settings_pane_search_body" } }),
|
||||
React.createElement(PreferencesInput, {
|
||||
className: "showSearch",
|
||||
prefName: "showSearch",
|
||||
value: prefs.showSearch,
|
||||
onChange: this.handlePrefChange,
|
||||
titleString: { id: "settings_pane_search_header" },
|
||||
descString: { id: "settings_pane_search_body" } }),
|
||||
React.createElement("hr", null),
|
||||
React.createElement(PreferencesInput, { className: "showTopSites", prefName: "showTopSites", value: prefs.showTopSites, onChange: this.handlePrefChange,
|
||||
titleString: { id: "settings_pane_topsites_header" }, descString: { id: "settings_pane_topsites_body" } }),
|
||||
React.createElement(
|
||||
"div",
|
||||
{ className: `options${prefs.showTopSites ? "" : " disabled"}` },
|
||||
React.createElement(PreferencesInput, { className: "showMoreTopSites", prefName: "topSitesCount", disabled: !prefs.showTopSites,
|
||||
value: prefs.topSitesCount !== TOP_SITES_DEFAULT_LENGTH, onChange: this.handlePrefChange,
|
||||
titleString: { id: "settings_pane_topsites_options_showmore" }, labelClassName: "icon icon-topsites" })
|
||||
PreferencesInput,
|
||||
{
|
||||
className: "showTopSites",
|
||||
prefName: "showTopSites",
|
||||
value: prefs.showTopSites,
|
||||
onChange: this.handlePrefChange,
|
||||
titleString: { id: "settings_pane_topsites_header" },
|
||||
descString: { id: "settings_pane_topsites_body" } },
|
||||
React.createElement(PreferencesInput, {
|
||||
className: "showMoreTopSites",
|
||||
prefName: "topSitesCount",
|
||||
disabled: !prefs.showTopSites,
|
||||
value: prefs.topSitesCount !== TOP_SITES_DEFAULT_LENGTH,
|
||||
onChange: this.handlePrefChange,
|
||||
titleString: { id: "settings_pane_topsites_options_showmore" },
|
||||
labelClassName: "icon icon-topsites" })
|
||||
),
|
||||
sections.filter(section => !section.shouldHidePref).map(({ id, title, enabled, pref }) => React.createElement(PreferencesInput, { key: id, className: "showSection", prefName: pref && pref.feed || id,
|
||||
value: enabled, onChange: pref && pref.feed ? this.handlePrefChange : this.handleSectionChange,
|
||||
titleString: pref && pref.titleString || title, descString: pref && pref.descString })),
|
||||
sections.filter(section => !section.shouldHidePref).map(({ id, title, enabled, pref }) => React.createElement(
|
||||
PreferencesInput,
|
||||
{
|
||||
key: id,
|
||||
className: "showSection",
|
||||
prefName: pref && pref.feed || id,
|
||||
value: enabled,
|
||||
onChange: pref && pref.feed ? this.handlePrefChange : this.handleSectionChange,
|
||||
titleString: pref && pref.titleString || title,
|
||||
descString: pref && pref.descString },
|
||||
pref.nestedPrefs && pref.nestedPrefs.map(nestedPref => React.createElement(PreferencesInput, {
|
||||
key: nestedPref.name,
|
||||
prefName: nestedPref.name,
|
||||
disabled: !enabled,
|
||||
value: prefs[nestedPref.name],
|
||||
onChange: this.handlePrefChange,
|
||||
titleString: nestedPref.titleString,
|
||||
labelClassName: `icon ${nestedPref.icon}` }))
|
||||
)),
|
||||
React.createElement("hr", null),
|
||||
React.createElement(PreferencesInput, { className: "showSnippets", prefName: "feeds.snippets",
|
||||
value: prefs["feeds.snippets"], onChange: this.handlePrefChange,
|
||||
|
@ -3018,7 +3125,7 @@ module.exports.PreferencesPane = PreferencesPane;
|
|||
module.exports.PreferencesInput = PreferencesInput;
|
||||
|
||||
/***/ }),
|
||||
/* 28 */
|
||||
/* 29 */
|
||||
/***/ (function(module, exports) {
|
||||
|
||||
class _PrerenderData {
|
||||
|
@ -3108,12 +3215,12 @@ module.exports = {
|
|||
};
|
||||
|
||||
/***/ }),
|
||||
/* 29 */
|
||||
/* 30 */
|
||||
/***/ (function(module, exports, __webpack_require__) {
|
||||
|
||||
/* WEBPACK VAR INJECTION */(function(global) {/* eslint-env mozilla/frame-script */
|
||||
|
||||
const { createStore, combineReducers, applyMiddleware } = __webpack_require__(30);
|
||||
const { createStore, combineReducers, applyMiddleware } = __webpack_require__(31);
|
||||
const { actionTypes: at, actionCreators: ac, actionUtils: au } = __webpack_require__(0);
|
||||
|
||||
const MERGE_STORE_ACTION = "NEW_TAB_INITIAL_STATE";
|
||||
|
@ -3223,13 +3330,13 @@ module.exports.INCOMING_MESSAGE_NAME = INCOMING_MESSAGE_NAME;
|
|||
/* WEBPACK VAR INJECTION */}.call(exports, __webpack_require__(4)))
|
||||
|
||||
/***/ }),
|
||||
/* 30 */
|
||||
/* 31 */
|
||||
/***/ (function(module, exports) {
|
||||
|
||||
module.exports = Redux;
|
||||
|
||||
/***/ }),
|
||||
/* 31 */
|
||||
/* 32 */
|
||||
/***/ (function(module, exports, __webpack_require__) {
|
||||
|
||||
/* WEBPACK VAR INJECTION */(function(global) {const { actionTypes: at } = __webpack_require__(0);
|
||||
|
@ -3299,7 +3406,7 @@ module.exports = class DetectUserSessionStart {
|
|||
/* WEBPACK VAR INJECTION */}.call(exports, __webpack_require__(4)))
|
||||
|
||||
/***/ }),
|
||||
/* 32 */
|
||||
/* 33 */
|
||||
/***/ (function(module, exports, __webpack_require__) {
|
||||
|
||||
/* WEBPACK VAR INJECTION */(function(global) {const DATABASE_NAME = "snippets_db";
|
||||
|
|
|
@ -197,7 +197,7 @@ main {
|
|||
|
||||
.section-top-bar {
|
||||
height: 16px;
|
||||
margin-bottom: 12px; }
|
||||
margin-bottom: 16px; }
|
||||
|
||||
.section-title {
|
||||
font-size: 13px;
|
||||
|
@ -421,11 +421,17 @@ main {
|
|||
opacity: 1; }
|
||||
|
||||
.edit-topsites-wrapper .edit-topsites-button {
|
||||
position: absolute;
|
||||
offset-inline-end: 21px;
|
||||
top: -2px;
|
||||
border-right: 1px solid #D7D7DB;
|
||||
line-height: 13px;
|
||||
offset-inline-end: 24px;
|
||||
opacity: 0;
|
||||
padding: 0 10px;
|
||||
position: absolute;
|
||||
top: 2px;
|
||||
transition: opacity 0.2s cubic-bezier(0.07, 0.95, 0, 1); }
|
||||
.edit-topsites-wrapper .edit-topsites-button:dir(rtl) {
|
||||
border-left: 1px solid #D7D7DB;
|
||||
border-right: 0; }
|
||||
.edit-topsites-wrapper .edit-topsites-button:focus, .edit-topsites-wrapper .edit-topsites-button:active {
|
||||
opacity: 1; }
|
||||
.edit-topsites-wrapper .edit-topsites-button button {
|
||||
|
@ -550,23 +556,38 @@ section.top-sites:hover .edit-topsites-button {
|
|||
.section-top-bar .info-option-icon:focus, .section-top-bar .info-option-icon:active {
|
||||
opacity: 1; }
|
||||
.section-top-bar .info-option-icon[aria-expanded="true"] {
|
||||
background-color: rgba(12, 12, 13, 0.1);
|
||||
border-radius: 1px;
|
||||
box-shadow: 0 0 0 5px rgba(12, 12, 13, 0.1);
|
||||
fill: rgba(12, 12, 13, 0.8); }
|
||||
.section-top-bar .section-info-option .info-option {
|
||||
visibility: hidden;
|
||||
opacity: 0;
|
||||
transition: visibility 0.2s, opacity 0.2s cubic-bezier(0.07, 0.95, 0, 1); }
|
||||
.section-top-bar .section-info-option .info-option::before {
|
||||
.section-top-bar .section-info-option .info-option::after, .section-top-bar .section-info-option .info-option::before {
|
||||
content: "";
|
||||
display: block;
|
||||
offset-inline-end: 0;
|
||||
position: absolute; }
|
||||
.section-top-bar .section-info-option .info-option::before {
|
||||
background-image: url(chrome://global/skin/arrow/panelarrow-vertical-themed.svg), url(chrome://global/skin/arrow/panelarrow-vertical@2x.png);
|
||||
background-position: calc(100% - 7px) bottom;
|
||||
background-repeat: no-repeat;
|
||||
background-size: 18px 10px;
|
||||
height: 32px;
|
||||
left: 50%;
|
||||
position: absolute;
|
||||
right: 0;
|
||||
top: -32px; }
|
||||
top: -32px;
|
||||
width: 43px; }
|
||||
.section-top-bar .section-info-option .info-option:dir(rtl)::before {
|
||||
background-position-x: 7px; }
|
||||
.section-top-bar .section-info-option .info-option::after {
|
||||
height: 10px;
|
||||
offset-inline-start: 0;
|
||||
top: -10px; }
|
||||
.section-top-bar .info-option-icon[aria-expanded="true"] + .info-option {
|
||||
visibility: visible;
|
||||
opacity: 1;
|
||||
transition: visibility 0.2s, opacity 0.2s cubic-bezier(0.07, 0.95, 0, 1); }
|
||||
.section-top-bar .info-option-icon:not([aria-expanded="true"]) + .info-option {
|
||||
pointer-events: none; }
|
||||
.section-top-bar .info-option {
|
||||
z-index: 9999;
|
||||
position: absolute;
|
||||
|
@ -577,7 +598,7 @@ section.top-sites:hover .edit-topsites-button {
|
|||
line-height: 120%;
|
||||
margin-inline-end: -9px;
|
||||
offset-inline-end: 0;
|
||||
top: 20px;
|
||||
top: 26px;
|
||||
width: 320px;
|
||||
padding: 24px;
|
||||
box-shadow: 0 1px 4px 0 rgba(12, 12, 13, 0.1);
|
||||
|
@ -758,7 +779,7 @@ section.section:hover .info-option-icon {
|
|||
background-color: rgba(12, 12, 13, 0.1);
|
||||
cursor: pointer; }
|
||||
.search-wrapper .search-button:active {
|
||||
background-color: rgba(12, 12, 13, 0.15); }
|
||||
background-color: rgba(12, 12, 13, 0.2); }
|
||||
.search-wrapper .search-button:dir(rtl) {
|
||||
transform: scaleX(-1); }
|
||||
.search-wrapper .contentSearchSuggestionTable {
|
||||
|
@ -840,7 +861,7 @@ section.section:hover .info-option-icon {
|
|||
.prefs-pane .prefs-modal-inner-wrapper section {
|
||||
margin: 20px 0; }
|
||||
.prefs-pane .prefs-modal-inner-wrapper section p {
|
||||
margin: 5px 0 5px 30px; }
|
||||
margin: 5px 0 20px 30px; }
|
||||
.prefs-pane .prefs-modal-inner-wrapper section label {
|
||||
display: inline-block;
|
||||
position: relative;
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
"default_label_loading": "Tye ka cano…",
|
||||
"header_top_sites": "Kakube maloyo",
|
||||
"header_stories": "Lok madito",
|
||||
"header_highlights": "Wiye madito",
|
||||
"header_visit_again": "Lim doki",
|
||||
"header_bookmarks": "Alamabuk ma cok coki",
|
||||
"header_recommended_by": "Lami tam obedo {provider}",
|
||||
|
@ -35,6 +36,8 @@
|
|||
"search_web_placeholder": "Yeny kakube",
|
||||
"search_settings": "Lok ter me yeny",
|
||||
"section_info_option": "Ngec",
|
||||
"section_info_send_feedback": "Cwal adwogi",
|
||||
"section_info_privacy_notice": "Ngec me mung",
|
||||
"welcome_title": "Wajoli i dirica matidi manyen",
|
||||
"welcome_body": "Firefox bi tic ki kabedo man me nyuto alamabukke mamegi, coc akwana, vidio, ki potbukke ma ilimo cokcoki ma pi gi tego loyo, wek i dok ii gi ma yot.",
|
||||
"welcome_label": "Tye ka kube ki wiye madito mamegi",
|
||||
|
@ -44,6 +47,7 @@
|
|||
"time_label_day": "nino{number}",
|
||||
"settings_pane_button_label": "Yub potbuk me dirica matidi mamegi manyen",
|
||||
"settings_pane_header": "Ter me dirica matidi manyen",
|
||||
"settings_pane_body2": "Yer ngo ma i neno i potbuk man.",
|
||||
"settings_pane_search_header": "Yeny",
|
||||
"settings_pane_search_body": "Yeny Kakube ki i dirica ni matidi manyen.",
|
||||
"settings_pane_topsites_header": "Kakube ma gi loyo",
|
||||
|
@ -53,6 +57,12 @@
|
|||
"settings_pane_bookmarks_body": "Alamabukke ni ma kicweyo manyen i kabedo acel macek.",
|
||||
"settings_pane_visit_again_header": "Lim Kidoco",
|
||||
"settings_pane_visit_again_body": "Firefox bi nyuti but gin mukato me yeny mamegi ma itwero mito me poo ikome onyo dok cen iyie.",
|
||||
"settings_pane_highlights_header": "Wiye madito",
|
||||
"settings_pane_highlights_body2": "Nong yoo ni cen i jami mamit ma ilimo gi cokcokki onyo iketo alamabuk.",
|
||||
"settings_pane_highlights_options_bookmarks": "Alamabuk",
|
||||
"settings_pane_highlights_options_visited": "Kakube ma kilimo",
|
||||
"settings_pane_snippets_header": "Kwena macek",
|
||||
"settings_pane_snippets_body": "Kwan ngec manyen macego dok mamit ki bot Mozilla ikom Firefox, kwo me intanet, ki meme mabino atata.",
|
||||
"settings_pane_done_button": "Otum",
|
||||
"edit_topsites_button_text": "Yubi",
|
||||
"edit_topsites_button_label": "Yub bute pi kakubi ni ma giloyo",
|
||||
|
@ -75,8 +85,10 @@
|
|||
"pocket_read_more": "Lok macuk gi lamal:",
|
||||
"pocket_read_even_more": "Nen Lok mapol",
|
||||
"pocket_feedback_header": "Kakube maber loyo, dano makato milion 25 aye oyubo.",
|
||||
"pocket_description": "Nong jami me rwom ma lamal ma itwero keng woko, ki kony ma aa ki bot Pocket, dong tye but Mozilla.",
|
||||
"highlights_empty_state": "Cak yeny, ka wa binyuto coc akwana mabeco, video, ki potbuk mukene ma ilimo cokcokki onyo ma kiketo alamabuk kany.",
|
||||
"topstories_empty_state": "Ityeko weng. Rot doki lacen pi lok madito mapol ki bot {provider}. Pe itwero kuro? Yer lok macuke lamal me nongo lok mabeco mapol ki i but kakube.",
|
||||
"manual_migration_explanation2": "Tem Firefox ki alamabuk, gin mukato ki mung me donyo ki ii layeny mukene.",
|
||||
"manual_migration_cancel_button": "Pe Apwoyo",
|
||||
"manual_migration_import_button": "Kel kombedi"
|
||||
},
|
||||
|
@ -163,10 +175,54 @@
|
|||
"newtab_page_title": "Llingüeta nueva",
|
||||
"default_label_loading": "Cargando…",
|
||||
"header_top_sites": "Sitios destacaos",
|
||||
"header_stories": "Histories destacaes",
|
||||
"header_highlights": "Los destacaos",
|
||||
"header_visit_again": "Visitar de nueves",
|
||||
"header_bookmarks": "Marcadores recientes",
|
||||
"header_bookmarks_placeholder": "Entá nun tienes dengún marcador.",
|
||||
"header_stories_from": "de",
|
||||
"type_label_visited": "Visitóse",
|
||||
"type_label_bookmarked": "Amestóse a marcadores",
|
||||
"type_label_synced": "Sincronizóse dende otru preséu",
|
||||
"type_label_recommended": "Tendencia",
|
||||
"type_label_open": "Abrir",
|
||||
"type_label_topic": "Tema",
|
||||
"welcome_title": "Bienllegáu/ada a la llingüeta nueva",
|
||||
"welcome_body": "Firefox usará esti espaciu p'amosate los marcadores, artículos, vídeos y páxines más relevantes de los que visitares apocayá, asina pues volver a ello de mou cenciellu."
|
||||
"type_label_now": "Agora",
|
||||
"menu_action_bookmark": "Amestar a marcadores",
|
||||
"menu_action_remove_bookmark": "Desaniciar marcador",
|
||||
"menu_action_copy_address": "Copiar direición",
|
||||
"menu_action_email_link": "Unviar enllaz per corréu…",
|
||||
"menu_action_open_new_window": "Abrir nuna ventana nueva",
|
||||
"menu_action_open_private_window": "Abrir nuna ventana privada nueva",
|
||||
"menu_action_dismiss": "Escartar",
|
||||
"menu_action_delete": "Desaniciar del historial",
|
||||
"menu_action_pin": "Fixar",
|
||||
"menu_action_unpin": "Desfixar",
|
||||
"confirm_history_delete_p1": "¿De xuru que quies desaniciar cada instancia d'esta páxina del to historial?",
|
||||
"confirm_history_delete_notice_p2": "Esta aición nun pue desfacese.",
|
||||
"menu_action_save_to_pocket": "Guardar en Pocket",
|
||||
"search_for_something_with": "Guetar {search_term} con:",
|
||||
"search_button": "Guetar",
|
||||
"search_header": "Gueta en {search_engine_name}",
|
||||
"search_web_placeholder": "Guetar na web",
|
||||
"search_settings": "Camudar axustes de gueta",
|
||||
"section_info_option": "Información",
|
||||
"section_info_send_feedback": "Unviar comentarios",
|
||||
"section_info_privacy_notice": "Nota de privacidá",
|
||||
"welcome_title": "Afáyate na llingüeta nueva",
|
||||
"welcome_body": "Firefox usará esti espaciu p'amosate los marcadores, artículos, vídeos y páxines más relevantes que visitares apocayá, asina pues volver a ellos de mou cenciellu.",
|
||||
"time_label_less_than_minute": "<1m",
|
||||
"time_label_minute": "{number}m",
|
||||
"time_label_hour": "{number}h",
|
||||
"time_label_day": "{number}d",
|
||||
"settings_pane_done_button": "Fecho",
|
||||
"edit_topsites_showmore_button": "Amosar más",
|
||||
"edit_topsites_done_button": "Fecho",
|
||||
"topsites_form_add_button": "Amestar",
|
||||
"topsites_form_save_button": "Guardar",
|
||||
"topsites_form_cancel_button": "Encaboxar",
|
||||
"pocket_read_more": "Temes populares:",
|
||||
"pocket_read_even_more": "Ver más histories"
|
||||
},
|
||||
"az": {
|
||||
"newtab_page_title": "Yeni Vərəq",
|
||||
|
@ -999,7 +1055,7 @@
|
|||
"default_label_loading": "Indlæser…",
|
||||
"header_top_sites": "Mest besøgte websider",
|
||||
"header_stories": "Tophistorier",
|
||||
"header_highlights": "Højdepunkter",
|
||||
"header_highlights": "Fremhævede",
|
||||
"header_visit_again": "Besøg igen",
|
||||
"header_bookmarks": "Seneste bogmærker",
|
||||
"header_recommended_by": "Anbefalet af {provider}",
|
||||
|
@ -1032,15 +1088,17 @@
|
|||
"search_settings": "Skift søgeindstillinger",
|
||||
"section_info_option": "Info",
|
||||
"section_info_send_feedback": "Send feedback",
|
||||
"section_info_privacy_notice": "Privatlivspolitik",
|
||||
"welcome_title": "Velkommen til nyt faneblad",
|
||||
"welcome_body": "Firefox vil bruge denne plads til at vise dine mest relevante bogmærker, artikler, videoer og sider, du har besøgt for nylig - så kan du nemmere finde dem.",
|
||||
"welcome_label": "Finder dine højdepunkter",
|
||||
"welcome_label": "Finder dine vigtigste sider",
|
||||
"time_label_less_than_minute": "<1 m.",
|
||||
"time_label_minute": "{number} m.",
|
||||
"time_label_hour": "{number} t.",
|
||||
"time_label_day": "{number} d.",
|
||||
"settings_pane_button_label": "Tilpas siden Nyt faneblad",
|
||||
"settings_pane_header": "Indstillinger for Nyt faneblad",
|
||||
"settings_pane_body2": "Vælg, hvad du vil se på denne side.",
|
||||
"settings_pane_search_header": "Søgning",
|
||||
"settings_pane_search_body": "Søg på nettet fra Nyt faneblad.",
|
||||
"settings_pane_topsites_header": "Mest besøgte websider",
|
||||
|
@ -1050,6 +1108,12 @@
|
|||
"settings_pane_bookmarks_body": "Dine seneste bogmærker samlet ét sted.",
|
||||
"settings_pane_visit_again_header": "Besøg igen",
|
||||
"settings_pane_visit_again_body": "Firefox viser dig dele af din browserhistorik, som du måske vil huske på eller vende tilbage til.",
|
||||
"settings_pane_highlights_header": "Fremhævede",
|
||||
"settings_pane_highlights_body2": "Find tilbage til interessant indhold, du har besøgt eller gemt et bogmærke til for nylig.",
|
||||
"settings_pane_highlights_options_bookmarks": "Bogmærker",
|
||||
"settings_pane_highlights_options_visited": "Besøgte websider",
|
||||
"settings_pane_snippets_header": "Notitser",
|
||||
"settings_pane_snippets_body": "Læs korte opdateringer fra Mozilla om Firefox, internet-kultur og lidt underholdning fra tid til anden.",
|
||||
"settings_pane_done_button": "Færdig",
|
||||
"edit_topsites_button_text": "Rediger",
|
||||
"edit_topsites_button_label": "Tilpas afsnittet Mest besøgte websider",
|
||||
|
@ -1072,7 +1136,10 @@
|
|||
"pocket_read_more": "Populære emner:",
|
||||
"pocket_read_even_more": "Se flere historier",
|
||||
"pocket_feedback_header": "Det bedste fra nettet, udvalgt af mere end 25 millioner mennesker.",
|
||||
"pocket_description": "Opdag indhold af høj kvalitet, som du måske ellers ikke ville have opdaget. Indholdet kommer fra Pocket, der nu er en del af Mozilla.",
|
||||
"highlights_empty_state": "Gå i gang med at browse, så vil vi vise dig nogle af de artikler, videoer og andre sider, du har besøgt eller gemt et bogmærke til for nylig.",
|
||||
"topstories_empty_state": "Der er ikke flere nye historier. Kom tilbage senere for at se flere tophistorier fra {provider}. Kan du ikke vente? Vælg et populært emne og find flere spændende historier fra hele verden.",
|
||||
"manual_migration_explanation2": "Prøv Firefox med bogmærkerne, historikken og adgangskoderne fra en anden browser.",
|
||||
"manual_migration_cancel_button": "Nej tak",
|
||||
"manual_migration_import_button": "Importer nu"
|
||||
},
|
||||
|
@ -2404,7 +2471,7 @@
|
|||
"time_label_day": "{number} j",
|
||||
"settings_pane_button_label": "Personnaliser la page Nouvel onglet",
|
||||
"settings_pane_header": "Préférences Nouvel onglet",
|
||||
"settings_pane_body2": "Choisissez les éléments à afficher sur cette page.",
|
||||
"settings_pane_body2": "Choisissez les éléments à afficher sur la page.",
|
||||
"settings_pane_search_header": "Recherche",
|
||||
"settings_pane_search_body": "Effectuez une recherche sur le Web depuis le nouvel onglet.",
|
||||
"settings_pane_topsites_header": "Sites les plus visités",
|
||||
|
@ -3285,19 +3352,19 @@
|
|||
"header_stories": "Historias popular",
|
||||
"header_highlights": "In evidentia",
|
||||
"header_visit_again": "Visita de novo",
|
||||
"header_bookmarks": "Paginas marcate recentemente",
|
||||
"header_bookmarks": "Marcapaginas recente",
|
||||
"header_recommended_by": "Recommendate per {provider}",
|
||||
"header_bookmarks_placeholder": "Tu ha ancora nulle paginas marcate.",
|
||||
"header_bookmarks_placeholder": "Tu ha ancora nulle marcapaginas.",
|
||||
"header_stories_from": "de",
|
||||
"type_label_visited": "Visitate",
|
||||
"type_label_bookmarked": "Marcate",
|
||||
"type_label_bookmarked": "Marcapaginas addite",
|
||||
"type_label_synced": "Synchronisate de altere apparato",
|
||||
"type_label_recommended": "Tendentias",
|
||||
"type_label_open": "Aperite",
|
||||
"type_label_topic": "Subjecto",
|
||||
"type_label_now": "Ora",
|
||||
"menu_action_bookmark": "Marcar le pagina",
|
||||
"menu_action_remove_bookmark": "Dismarcar le pagina",
|
||||
"menu_action_bookmark": "Adder marcapaginas",
|
||||
"menu_action_remove_bookmark": "Remover le marcapaginas",
|
||||
"menu_action_copy_address": "Copiar le adresse",
|
||||
"menu_action_email_link": "Inviar le ligamine per email…",
|
||||
"menu_action_open_new_window": "Aperir in un nove fenestra",
|
||||
|
@ -3318,7 +3385,7 @@
|
|||
"section_info_send_feedback": "Inviar feedback",
|
||||
"section_info_privacy_notice": "Advertentia de privacitate",
|
||||
"welcome_title": "Benvenite al nove scheda",
|
||||
"welcome_body": "Firefox usara iste spatio pro monstrar tu paginas marcate le plus relevante, articulos, videos e paginas que tu ha visitate recentemente, de sorta que tu pote revider los facilemente.",
|
||||
"welcome_body": "Firefox usara iste spatio pro monstrar tu marcapaginas le plus relevante, articulos, videos e paginas que tu ha visitate recentemente, de sorta que tu pote revider los facilemente.",
|
||||
"welcome_label": "Identificante tu evidentias",
|
||||
"time_label_less_than_minute": "<1 min",
|
||||
"time_label_minute": "{number} min",
|
||||
|
@ -3332,13 +3399,13 @@
|
|||
"settings_pane_topsites_header": "Sitos popular",
|
||||
"settings_pane_topsites_body": "Acceder al sitos web que tu plus visita.",
|
||||
"settings_pane_topsites_options_showmore": "Monstrar duo lineas",
|
||||
"settings_pane_bookmarks_header": "Paginas marcate recentemente",
|
||||
"settings_pane_bookmarks_body": "Tu paginas marcate recentemente a un sol loco.",
|
||||
"settings_pane_bookmarks_header": "Marcapaginas recente",
|
||||
"settings_pane_bookmarks_body": "Tu marcapaginas le plus recente a un sol loco.",
|
||||
"settings_pane_visit_again_header": "Visitar de novo",
|
||||
"settings_pane_visit_again_body": "Firefox te monstrara partes de tu chronologia de navigation que tu pote voler rememorar o visitar novemente.",
|
||||
"settings_pane_highlights_header": "In evidentia",
|
||||
"settings_pane_highlights_body2": "Retrova cosas interessante que tu ha recentemente visitate o marcate.",
|
||||
"settings_pane_highlights_options_bookmarks": "Paginas marcate",
|
||||
"settings_pane_highlights_body2": "Retrova cosas interessante que tu ha recentemente visitate o addite marcapaginas.",
|
||||
"settings_pane_highlights_options_bookmarks": "Marcapaginas",
|
||||
"settings_pane_highlights_options_visited": "Sitos visitate",
|
||||
"settings_pane_snippets_header": "Breve novas",
|
||||
"settings_pane_snippets_body": "Lege breve e legier novas de Mozilla super Firefox, cultura internet e occasionalmente super alcun meme.",
|
||||
|
@ -3365,9 +3432,9 @@
|
|||
"pocket_read_even_more": "Vider plus historias",
|
||||
"pocket_feedback_header": "Le melior del web, selectionate per 25 milliones de personas.",
|
||||
"pocket_description": "Discoperir contento de alte qualitate que tu poterea alteremente non cognoscer, con le adjuta de Pocket, ora parte de Mozilla.",
|
||||
"highlights_empty_state": "Comencia navigar e nos te monstrara alcun del grande articulos, videos e altere paginas que tu ha recentemente visitate o marcate hic.",
|
||||
"highlights_empty_state": "Comencia navigar e nos te monstrara alcun del grande articulos, videos e altere paginas que tu ha recentemente visitate o addite marcapaginas hic.",
|
||||
"topstories_empty_state": "Tu ja es in die con toto. Reveni plus tarde pro plus historias popular de {provider}. Non vole attender? Selectiona un subjecto popular pro trovar plus altere historias interessante del web.",
|
||||
"manual_migration_explanation2": "Essaya Firefox con le paginas marcate, le chronologia e le contrasignos de altere navigator.",
|
||||
"manual_migration_explanation2": "Essaya Firefox con le marcapaginas, le chronologia e le contrasignos de un altere navigator.",
|
||||
"manual_migration_cancel_button": "No, gratias",
|
||||
"manual_migration_import_button": "Importar ora"
|
||||
},
|
||||
|
@ -3655,7 +3722,7 @@
|
|||
"default_label_loading": "იტვირთება…",
|
||||
"header_top_sites": "რჩეული საიტები",
|
||||
"header_stories": "რჩეული სტატიები",
|
||||
"header_highlights": "მნიშნველოვანი სიახლეები",
|
||||
"header_highlights": "მნიშვნელოვანი საიტები",
|
||||
"header_visit_again": "ხელახლა ნახვა",
|
||||
"header_bookmarks": "ბოლოს ჩანიშნულები",
|
||||
"header_recommended_by": "რეკომენდებულია {provider}-ის მიერ",
|
||||
|
@ -3691,7 +3758,7 @@
|
|||
"section_info_privacy_notice": "პირადი მონაცემების დაცვა",
|
||||
"welcome_title": "მოგესალმებით ახალ ჩანართზე",
|
||||
"welcome_body": "Firefox ამ სივრცეს გამოიყენებს თქვენთვის ყველაზე საჭირო სანიშნეების, სტატიების, ვიდეოებისა და ბოლოს მონახულებული გვერდებისთვის, რომ ადვილად შეძლოთ მათზე დაბრუნება.",
|
||||
"welcome_label": "რჩეული ვებგვერდების დადგენა",
|
||||
"welcome_label": "მნიშვნელოვანი საიტების დადგენა",
|
||||
"time_label_less_than_minute": "<1წთ",
|
||||
"time_label_minute": "{number}წთ",
|
||||
"time_label_hour": "{number}სთ",
|
||||
|
@ -3708,8 +3775,8 @@
|
|||
"settings_pane_bookmarks_body": "ახლად შექმნილი სანიშნეები, ერთი ხელის გაწვდენაზე.",
|
||||
"settings_pane_visit_again_header": "ხელახლა ნახვა",
|
||||
"settings_pane_visit_again_body": "Firefox გაჩვენებთ მონახულებული გვერდების ისტორიიდან იმას, რისი გახსენებაც ან რაზე დაბრუნებაც გენდომებათ.",
|
||||
"settings_pane_highlights_header": "მნიშნველოვანი სიახლეები",
|
||||
"settings_pane_highlights_body2": "მარტივად დაუბრუნდით ბოლოს მონახულებულ, ან ჩანიშნულ საიტებს.",
|
||||
"settings_pane_highlights_header": "მნიშვნელოვანი საიტები",
|
||||
"settings_pane_highlights_body2": "მარტივად დაუბრუნდით ბოლოს მონახულებულ, ან ჩანიშნულ გვერდებს.",
|
||||
"settings_pane_highlights_options_bookmarks": "სანიშნეები",
|
||||
"settings_pane_highlights_options_visited": "მონახულებული საიტები",
|
||||
"settings_pane_snippets_header": "ცნობები",
|
||||
|
@ -3717,8 +3784,8 @@
|
|||
"settings_pane_done_button": "მზადაა",
|
||||
"edit_topsites_button_text": "ჩასწორება",
|
||||
"edit_topsites_button_label": "მოირგეთ რჩეული საიტების განყოფილება",
|
||||
"edit_topsites_showmore_button": "მეტის ჩვენება",
|
||||
"edit_topsites_showless_button": "ნაკლების ჩვენება",
|
||||
"edit_topsites_showmore_button": "მეტის გამოჩენა",
|
||||
"edit_topsites_showless_button": "ნაკლების გამოჩენა",
|
||||
"edit_topsites_done_button": "მზადაა",
|
||||
"edit_topsites_pin_button": "საიტის მიმაგრება",
|
||||
"edit_topsites_unpin_button": "მიმაგრების მოხსნა",
|
||||
|
@ -4308,7 +4375,7 @@
|
|||
"settings_pane_body2": "Изберете што ќе гледате на оваа страница.",
|
||||
"settings_pane_search_header": "Пребарување",
|
||||
"settings_pane_search_body": "Пребарајте низ Интернет од вашето ново јазиче.",
|
||||
"settings_pane_topsites_header": "Врвни мрежни места",
|
||||
"settings_pane_topsites_header": "Популарни мрежни места",
|
||||
"settings_pane_topsites_body": "Пристапете до мрежните места што ги посетувате најмногу.",
|
||||
"settings_pane_topsites_options_showmore": "Прикажи два реда",
|
||||
"settings_pane_bookmarks_header": "Скорешни обележувачи",
|
||||
|
@ -4354,8 +4421,11 @@
|
|||
"newtab_page_title": "പുതിയ ടാബ്",
|
||||
"default_label_loading": "ലോഡ്ചെയ്യുന്നു…",
|
||||
"header_top_sites": "മികച്ച സൈറ്റുകൾ",
|
||||
"header_highlights": "ഹൈലൈറ്റുകൾ",
|
||||
"header_stories": "മികച്ച ലേഖനങ്ങൾ",
|
||||
"header_highlights": "ഹൈലൈറ്റുകൾ",
|
||||
"header_visit_again": "വീണ്ടും സന്ദർശിക്കുക",
|
||||
"header_bookmarks": "അടുത്തിടെയുള്ള ബുക്ക്മാർക്കുകൾ",
|
||||
"header_bookmarks_placeholder": "നിങ്ങൾക്ക് ഇതുവരെ ബുക്ക്മാർക്കുകൾ ഇല്ല.",
|
||||
"header_stories_from": "എവിടെ നിന്നും",
|
||||
"type_label_visited": "സന്ദർശിച്ചത്",
|
||||
"type_label_bookmarked": "അടയാളപ്പെടുത്തിയത്",
|
||||
|
@ -4363,6 +4433,7 @@
|
|||
"type_label_recommended": "ട്രെൻഡിംഗ്",
|
||||
"type_label_open": "തുറക്കുക",
|
||||
"type_label_topic": "വിഷയം",
|
||||
"type_label_now": "ഇപ്പോൾ",
|
||||
"menu_action_bookmark": "അടയാളം",
|
||||
"menu_action_remove_bookmark": "അടയാളം മാറ്റുക",
|
||||
"menu_action_copy_address": "വിലാസം പകർത്തുക",
|
||||
|
@ -4371,12 +4442,17 @@
|
|||
"menu_action_open_private_window": "പുതിയ രസഹ്യജാലകത്തിൽ തുറക്കുക",
|
||||
"menu_action_dismiss": "പുറത്താക്കുക",
|
||||
"menu_action_delete": "ചരിത്രത്തിൽ നിന്ന് ഒഴിവാക്കുക",
|
||||
"menu_action_pin": "പിൻ ചെയ്യുക",
|
||||
"menu_action_unpin": "അൺപിൻ ചെയ്യുക",
|
||||
"confirm_history_delete_p1": "നിങ്ങളുടെ ചരിത്രത്തിൽ നിന്ന് ഈ പേജിന്റെ എല്ലാ ഉദാഹരണങ്ങളും ഇല്ലാതാക്കാൻ നിങ്ങൾ താൽപ്പര്യപ്പെടുന്നുവെന്ന് തീർച്ചയാണോ?",
|
||||
"confirm_history_delete_notice_p2": "ഈ പ്രവർത്തനം പഴയപടിയാക്കാനാവില്ല.",
|
||||
"menu_action_save_to_pocket": "പോക്കറ്റിലേയ്ക്ക് സംരക്ഷിയ്ക്കുക",
|
||||
"search_for_something_with": "തിരയാൻ {search_term} : എന്നത് ഉപയോഗിയ്ക്കുക",
|
||||
"search_button": "തിരയുക",
|
||||
"search_header": "{search_engine_name} തിരയുക",
|
||||
"search_web_placeholder": "ഇൻറർനെറ്റിൽ തിരയുക",
|
||||
"search_settings": "തിരയാനുള്ള രീതികൾ മാറ്റുക",
|
||||
"section_info_option": "വിവരം",
|
||||
"welcome_title": "പുതിയ ജാലകത്തിലേക്കു സ്വാഗതം",
|
||||
"welcome_body": "നിങ്ങളുടെ ഏറ്റവും ശ്രദ്ധേയമായ അടയാളങ്ങൾ, ലേഖനങ്ങൾ, വീഡിയോകൾ, കൂടാതെ നിങ്ങൾ സമീപകാലത്ത് സന്ദർശിച്ച താളുകൾ എന്നിവ കാണിക്കുന്നതിനായി ഫയർഫോക്സ് ഈ ഇടം ഉപയോഗിക്കും, അതിനാൽ നിങ്ങൾക്ക് എളുപ്പത്തിൽ അവയിലേക്ക് തിരിച്ചു പോകാം.",
|
||||
"welcome_label": "താങ്കളുടെ ഹൈലൈറ്റ്സ് തിരിച്ചറിയുന്നു",
|
||||
|
@ -4386,16 +4462,15 @@
|
|||
"time_label_day": "{number} മിനിറ്റ്",
|
||||
"settings_pane_button_label": "നിങ്ങളുടെ പുതിയ ടാബ് താള് ഇഷ്ടാനുസൃതമാക്കുക",
|
||||
"settings_pane_header": "പുതിയ ടാബിന്റെ മുൻഗണനകൾ",
|
||||
"settings_pane_body": "പുതിയ ടാബ് തുറക്കുമ്പോൾ എന്ത് കാണണമെന്ന് തീരുമാനിക്കുക.",
|
||||
"settings_pane_search_header": "തിരയുക",
|
||||
"settings_pane_search_body": "പുതിയ ടാബിൽ നിന്ന് ഇന്റർനെറ്റിൽ തിരയുക.",
|
||||
"settings_pane_topsites_header": "മുന്നേറിയ സൈറ്റുകൾ",
|
||||
"settings_pane_topsites_body": "നിങ്ങൾ കൂടുതൽ സന്ദർശിക്കുന്ന വെബ്സൈറ്റുകളിൽ പ്രവേശിക്കുക.",
|
||||
"settings_pane_topsites_options_showmore": "രണ്ടു വരികൾ കാണിയ്ക്കുക",
|
||||
"settings_pane_bookmarks_header": "അടുത്തിടെയുള്ള ബുക്ക്മാർക്കുകൾ",
|
||||
"settings_pane_bookmarks_body": "നിങ്ങളുടെ പുതിയതായി സൃഷ്ടിച്ച ബുക്ക്മാർക്കുകൾ ഒരു സ്ഥലത്ത്.",
|
||||
"settings_pane_visit_again_header": "വീണ്ടും സന്ദർശിക്കുക",
|
||||
"settings_pane_highlights_header": "ഹൈലൈറ്റുകൾ",
|
||||
"settings_pane_highlights_body": "നിങ്ങളുടെ സമീപകാല ബ്രൗസിംഗ് ചരിത്രവും പുതുതായി സൃഷ്ടിച്ച അടയാളങ്ങളും കാണുക.",
|
||||
"settings_pane_pocketstories_header": "മികച്ച ലേഖനങ്ങൾ",
|
||||
"settings_pane_pocketstories_body": "മോസില്ല കുടുംബാംഗമായ പോക്കറ്റ്, വിട്ടുപോയേയ്ക്കാവുന്ന മികച്ച ലേഖനങ്ങൾ നിങ്ങളുടെ ശ്രദ്ധയിൽ എത്തിയ്ക്കുന്നു.",
|
||||
"settings_pane_done_button": "തീർന്നു",
|
||||
"edit_topsites_button_text": "തിരുത്തുക",
|
||||
"edit_topsites_button_label": "നിങ്ങളുടെ മുന്നേറിയ സൈറ്റുകളുടെ വിഭാഗം ഇഷ്ടാനുസൃതമാക്കുക",
|
||||
|
@ -4418,8 +4493,8 @@
|
|||
"pocket_read_more": "ജനപ്രിയ വിഷയങ്ങൾ:",
|
||||
"pocket_read_even_more": "കൂടുതൽ ലേഖനങ്ങൾ കാണുക",
|
||||
"pocket_feedback_header": "250 ലക്ഷം പേരാൽ തെരഞ്ഞെടുക്കപ്പെട്ട വെബിലെ ഏറ്റവും മികച്ചവയാണിവ.",
|
||||
"pocket_feedback_body": "മോസില്ല കുടുംബാംഗമായ പോക്കറ്റ്, വിട്ടുപോയേയ്ക്കാവുന്ന മികച്ച ലേഖനങ്ങൾ നിങ്ങളുടെ ശ്രദ്ധയിൽ എത്തിയ്ക്കുന്നു.",
|
||||
"pocket_send_feedback": "പ്രതികരണം അയയ്ക്കുക"
|
||||
"manual_migration_cancel_button": "വേണ്ട, നന്ദി",
|
||||
"manual_migration_import_button": "ഇപ്പോൾ ഇറക്കുമതി ചെയ്യുക"
|
||||
},
|
||||
"mr": {
|
||||
"newtab_page_title": "नवीन टॅब",
|
||||
|
@ -4742,6 +4817,7 @@
|
|||
"time_label_day": "{number} दिन",
|
||||
"settings_pane_button_label": "तपाईंको नयाँ ट्याब पृष्ठ अनुकूलन गर्नुहोस्",
|
||||
"settings_pane_header": "नयाँ ट्याब प्राथमिकताहरू",
|
||||
"settings_pane_body2": "तपाईँले यो पृष्ठमा के देख्नुभयो छनौट गर्नुहोस् ।",
|
||||
"settings_pane_search_header": "खोजी गर्नुहोस्",
|
||||
"settings_pane_search_body": "तपाईंको नयाँ ट्याबबाट वेबमा खोज्नुहोस् ।",
|
||||
"settings_pane_topsites_header": "शीर्ष साइटहरू",
|
||||
|
@ -6220,10 +6296,13 @@
|
|||
"settings_pane_bookmarks_header": "ที่คั่นหน้าล่าสุด",
|
||||
"settings_pane_bookmarks_body": "ที่คั่นหน้าที่สร้างใหม่ของคุณในตำแหน่งที่ตั้งเดียวที่สะดวก",
|
||||
"settings_pane_visit_again_header": "เยี่ยมชมอีกครั้ง",
|
||||
"settings_pane_visit_again_body": "Firefox จะแสดงประวัติการท่องเว็บที่คุณอาจต้องการให้จดจำหรือกลับไปเยี่ยมชมอีกครั้งที่นี่",
|
||||
"settings_pane_highlights_header": "รายการเด่น",
|
||||
"settings_pane_highlights_body2": "ค้นหาทางของคุณกลับไปยังสิ่งที่น่าสนใจที่คุณได้เยี่ยมชมหรือเพิ่มที่คั่นหน้าไว้เมื่อเร็ว ๆ นี้",
|
||||
"settings_pane_highlights_options_bookmarks": "ที่คั่นหน้า",
|
||||
"settings_pane_highlights_options_visited": "ไซต์ที่เยี่ยมชมแล้ว",
|
||||
"settings_pane_snippets_header": "ส่วนย่อย",
|
||||
"settings_pane_snippets_body": "อ่านบทความข่าวสารสั้น ๆ ที่น่าสนใจจาก Mozilla เกี่ยวกับ Firefox, วัฒนธรรมอินเทอร์เน็ต, และมีมแบบสุ่มในบางครั้ง",
|
||||
"settings_pane_done_button": "เสร็จสิ้น",
|
||||
"edit_topsites_button_text": "แก้ไข",
|
||||
"edit_topsites_button_label": "ปรับแต่งส่วนไซต์เด่นของคุณ",
|
||||
|
@ -6246,7 +6325,9 @@
|
|||
"pocket_read_more": "หัวข้อยอดนิยม:",
|
||||
"pocket_read_even_more": "ดูเรื่องราวเพิ่มเติม",
|
||||
"pocket_feedback_header": "ที่สุดของเว็บ จัดรายการโดยผู้คนกว่า 25 ล้านคน",
|
||||
"highlights_empty_state": "เริ่มการท่องเว็บและเราจะแสดงบทความ, วิดีโอ และหน้าอื่น ๆ บางส่วนที่ยอดเยี่ยมที่คุณได้เยี่ยมชมหรือเพิ่มที่คั่นหน้าไว้เมื่อเร็ว ๆ นี้ที่นี่",
|
||||
"pocket_description": "ค้นพบเนื้อหาคุณภาพสูงที่คุณอาจจะพลาดไปด้วยความช่วยเหลือจาก Pocket ซึ่งขณะนี้เป็นส่วนหนึ่งของ Mozilla",
|
||||
"highlights_empty_state": "เริ่มการท่องเว็บและเราจะแสดงบทความ, วิดีโอ และหน้าอื่น ๆ บางส่วนที่ยอดเยี่ยมที่คุณได้เยี่ยมชมหรือเพิ่มที่คั่นหน้าไว้ล่าสุดที่นี่",
|
||||
"topstories_empty_state": "คุณได้อ่านเรื่องราวครบทั้งหมดแล้ว คุณสามารถกลับมาตรวจดูเรื่องราวเด่นจาก {provider} ได้ภายหลัง อดใจรอไม่ได้งั้นหรือ? เลือกหัวข้อยอดนิยมเพื่อค้นหาเรื่องราวที่ยอดเยี่ยมจากเว็บต่าง ๆ",
|
||||
"manual_migration_explanation2": "ลอง Firefox ด้วยที่คั่นหน้า, ประวัติ และรหัสผ่านจากเบราว์เซอร์อื่น",
|
||||
"manual_migration_cancel_button": "ไม่ ขอบคุณ",
|
||||
"manual_migration_import_button": "นำเข้าตอนนี้"
|
||||
|
|
|
@ -8,7 +8,7 @@
|
|||
<em:type>2</em:type>
|
||||
<em:bootstrap>true</em:bootstrap>
|
||||
<em:unpack>false</em:unpack>
|
||||
<em:version>2017.10.05.1066-43726d35</em:version>
|
||||
<em:version>2017.10.05.1357-d2d5439b</em:version>
|
||||
<em:name>Activity Stream</em:name>
|
||||
<em:description>A rich visual history feed and a reimagined home page make it easier than ever to find exactly what you're looking for in Firefox.</em:description>
|
||||
<em:multiprocessCompatible>true</em:multiprocessCompatible>
|
||||
|
|
|
@ -21,11 +21,16 @@ XPCOMUtils.defineLazyModuleGetter(this, "NewTabUtils",
|
|||
"resource://gre/modules/NewTabUtils.jsm");
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "Screenshots",
|
||||
"resource://activity-stream/lib/Screenshots.jsm");
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "ProfileAge",
|
||||
"resource://gre/modules/ProfileAge.jsm");
|
||||
|
||||
const HIGHLIGHTS_MAX_LENGTH = 9;
|
||||
const HIGHLIGHTS_UPDATE_TIME = 15 * 60 * 1000; // 15 minutes
|
||||
const MANY_EXTRA_LENGTH = HIGHLIGHTS_MAX_LENGTH * 5 + TOP_SITES_SHOWMORE_LENGTH;
|
||||
const SECTION_ID = "highlights";
|
||||
const BOOKMARKS_THRESHOLD = 4000; // 3 seconds to milliseconds.
|
||||
// Some default seconds ago for Activity Stream recent requests
|
||||
const ACTIVITY_STREAM_DEFAULT_RECENT = 5 * 24 * 60 * 60;
|
||||
|
||||
this.HighlightsFeed = class HighlightsFeed {
|
||||
constructor() {
|
||||
|
@ -33,15 +38,8 @@ this.HighlightsFeed = class HighlightsFeed {
|
|||
this.highlightsLength = 0;
|
||||
this.dedupe = new Dedupe(this._dedupeKey);
|
||||
this.linksCache = new LinksCache(NewTabUtils.activityStreamLinks,
|
||||
"getHighlights", (oldLink, newLink) => {
|
||||
// Migrate any pending images or images to the new link
|
||||
for (const property of ["__fetchingScreenshot", "image"]) {
|
||||
const oldValue = oldLink[property];
|
||||
if (oldValue) {
|
||||
newLink[property] = oldValue;
|
||||
}
|
||||
}
|
||||
});
|
||||
"getHighlights", ["image"]);
|
||||
this._profileAge = 0;
|
||||
}
|
||||
|
||||
_dedupeKey(site) {
|
||||
|
@ -61,6 +59,20 @@ this.HighlightsFeed = class HighlightsFeed {
|
|||
SectionsManager.disableSection(SECTION_ID);
|
||||
}
|
||||
|
||||
/**
|
||||
* Timeframe used to select recent bookmarks, in seconds.
|
||||
* Looks back 5 days while also taking into account new profiles
|
||||
* not to include default bookmarks.
|
||||
*/
|
||||
async _getBookmarksThreshold() {
|
||||
if (this._profileAge === 0) {
|
||||
// Value in milliseconds.
|
||||
this._profileAge = await (new ProfileAge()).created;
|
||||
}
|
||||
const defaultsThreshold = Date.now() - this._profileAge - BOOKMARKS_THRESHOLD;
|
||||
return Math.min(ACTIVITY_STREAM_DEFAULT_RECENT, defaultsThreshold / 1000);
|
||||
}
|
||||
|
||||
async fetchHighlights(broadcast = false) {
|
||||
// We broadcast when we want to force an update, so get fresh links
|
||||
if (broadcast) {
|
||||
|
@ -81,7 +93,10 @@ this.HighlightsFeed = class HighlightsFeed {
|
|||
|
||||
// Request more than the expected length to allow for items being removed by
|
||||
// deduping against Top Sites or multiple history from the same domain, etc.
|
||||
const manyPages = await this.linksCache.request({numItems: MANY_EXTRA_LENGTH});
|
||||
const manyPages = await this.linksCache.request({
|
||||
numItems: MANY_EXTRA_LENGTH,
|
||||
bookmarkSecondsAgo: await this._getBookmarksThreshold()
|
||||
});
|
||||
|
||||
// Remove adult highlights if we need to
|
||||
const checkedAdult = this.store.getState().Prefs.values.filterAdult ?
|
||||
|
@ -117,9 +132,8 @@ this.HighlightsFeed = class HighlightsFeed {
|
|||
highlights.push(page);
|
||||
hosts.add(hostname);
|
||||
|
||||
// Remove any internal properties
|
||||
delete page.__fetchingScreenshot;
|
||||
delete page.__updateCache;
|
||||
// Remove internal properties that might be updated after dispatch
|
||||
delete page.__sharedCache;
|
||||
|
||||
// Skip the rest if we have enough items
|
||||
if (highlights.length === HIGHLIGHTS_MAX_LENGTH) {
|
||||
|
@ -139,7 +153,7 @@ this.HighlightsFeed = class HighlightsFeed {
|
|||
async fetchImage(page) {
|
||||
// Request a screenshot if we don't already have one pending
|
||||
const {preview_image_url: imageUrl, url} = page;
|
||||
Screenshots.maybeGetAndSetScreenshot(page, imageUrl || url, "image", image => {
|
||||
Screenshots.maybeCacheScreenshot(page, imageUrl || url, "image", image => {
|
||||
SectionsManager.updateSectionCard(SECTION_ID, url, {image}, true);
|
||||
});
|
||||
}
|
||||
|
@ -166,6 +180,9 @@ this.HighlightsFeed = class HighlightsFeed {
|
|||
break;
|
||||
case at.PLACES_BOOKMARK_ADDED:
|
||||
case at.PLACES_BOOKMARK_REMOVED:
|
||||
this.linksCache.expire();
|
||||
this.fetchHighlights(false);
|
||||
break;
|
||||
case at.TOP_SITES_UPDATED:
|
||||
this.fetchHighlights(false);
|
||||
break;
|
||||
|
|
|
@ -19,19 +19,21 @@ this.LinksCache = class LinksCache {
|
|||
*
|
||||
* @param {object} linkObject Object containing the link property
|
||||
* @param {string} linkProperty Name of property on object to access
|
||||
* @param {function} migrator Optional callback receiving the old and new link
|
||||
* to allow custom migrating data from old to new.
|
||||
* @param {array} properties Optional properties list to migrate to new links.
|
||||
* @param {function} shouldRefresh Optional callback receiving the old and new
|
||||
* options to refresh even when not expired.
|
||||
*/
|
||||
constructor(linkObject, linkProperty, migrator = () => {}, shouldRefresh = () => {}) {
|
||||
constructor(linkObject, linkProperty, properties = [], shouldRefresh = () => {}) {
|
||||
this.clear();
|
||||
|
||||
// Allow getting links from both methods and array properties
|
||||
this.linkGetter = options => {
|
||||
const ret = linkObject[linkProperty];
|
||||
return typeof ret === "function" ? ret.call(linkObject, options) : ret;
|
||||
};
|
||||
this.migrator = migrator;
|
||||
|
||||
// Always migrate the shared cache data in addition to any custom properties
|
||||
this.migrateProperties = ["__sharedCache", ...properties];
|
||||
this.shouldRefresh = shouldRefresh;
|
||||
}
|
||||
|
||||
|
@ -78,37 +80,38 @@ this.LinksCache = class LinksCache {
|
|||
}
|
||||
}
|
||||
|
||||
// Make a shallow copy of each resulting link to allow direct edits
|
||||
const copied = (await this.linkGetter(options)).map(link => link &&
|
||||
Object.assign({}, link));
|
||||
// Update the cache with migrated links without modifying source objects
|
||||
resolve((await this.linkGetter(options)).map(link => {
|
||||
// Keep original array hole positions
|
||||
if (!link) {
|
||||
return link;
|
||||
}
|
||||
|
||||
// Migrate data to the new link if we have an old link
|
||||
for (const newLink of copied) {
|
||||
if (newLink) {
|
||||
const oldLink = toMigrate.get(newLink.url);
|
||||
if (oldLink) {
|
||||
this.migrator(oldLink, newLink);
|
||||
// Migrate data to the new link copy if we have an old link
|
||||
const newLink = Object.assign({}, link);
|
||||
const oldLink = toMigrate.get(newLink.url);
|
||||
if (oldLink) {
|
||||
for (const property of this.migrateProperties) {
|
||||
const oldValue = oldLink[property];
|
||||
if (oldValue) {
|
||||
newLink[property] = oldValue;
|
||||
}
|
||||
}
|
||||
|
||||
// Add a method that can be copied to cloned links that will update
|
||||
// the original cached link's property with the current one
|
||||
newLink.__updateCache = function(prop) {
|
||||
const val = this[prop];
|
||||
if (val === undefined) {
|
||||
delete newLink[prop];
|
||||
} else {
|
||||
newLink[prop] = val;
|
||||
} else {
|
||||
// Share data among link copies and new links from future requests
|
||||
newLink.__sharedCache = {
|
||||
// Provide a helper to update the cached link
|
||||
updateLink(property, value) {
|
||||
newLink[property] = value;
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// Update cache with the copied links migrated
|
||||
resolve(copied);
|
||||
return newLink;
|
||||
}));
|
||||
});
|
||||
}
|
||||
|
||||
// Return the promise of the links array
|
||||
return this.cache;
|
||||
// Provide a shallow copy of the cached link objects for callers to modify
|
||||
return (await this.cache).map(link => link && Object.assign({}, link));
|
||||
}
|
||||
};
|
||||
|
|
|
@ -65,36 +65,31 @@ this.Screenshots = {
|
|||
|
||||
/**
|
||||
* Conditionally get a screenshot for a link if there's no existing pending
|
||||
* screenshot. Updates the link object's desired property with the result.
|
||||
* screenshot. Updates the cached link's desired property with the result.
|
||||
*
|
||||
* @param link {object} Link object to update
|
||||
* @param url {string} Url to get a screenshot of
|
||||
* @param property {string} Name of property on object to set
|
||||
@ @param onScreenshot {function} Callback for when the screenshot loads
|
||||
*/
|
||||
async maybeGetAndSetScreenshot(link, url, property, onScreenshot) {
|
||||
// Make a link copy so we can stash internal properties to cache
|
||||
const updateCache = link.__updateCache ? link.__updateCache.bind(link) :
|
||||
() => {};
|
||||
|
||||
// Request a screenshot if we don't already have one pending
|
||||
if (!link.__fetchingScreenshot) {
|
||||
link.__fetchingScreenshot = this.getScreenshotForURL(url);
|
||||
updateCache("__fetchingScreenshot");
|
||||
|
||||
// Trigger this callback only when first requesting
|
||||
link.__fetchingScreenshot.then(onScreenshot).catch();
|
||||
async maybeCacheScreenshot(link, url, property, onScreenshot) {
|
||||
// Nothing to do if we already have a pending screenshot
|
||||
const cache = link.__sharedCache;
|
||||
if (cache.fetchingScreenshot) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Clean up now that we got the screenshot
|
||||
const screenshot = await link.__fetchingScreenshot;
|
||||
delete link.__fetchingScreenshot;
|
||||
updateCache("__fetchingScreenshot");
|
||||
// Save the promise to the cache so other links get it immediately
|
||||
cache.fetchingScreenshot = this.getScreenshotForURL(url);
|
||||
|
||||
// Update the link so the screenshot is in the cache
|
||||
// Clean up now that we got the screenshot
|
||||
const screenshot = await cache.fetchingScreenshot;
|
||||
delete cache.fetchingScreenshot;
|
||||
|
||||
// Update the cache for future links and call back for existing content
|
||||
if (screenshot) {
|
||||
link[property] = screenshot;
|
||||
updateCache(property);
|
||||
cache.updateLink(property, screenshot);
|
||||
onScreenshot(screenshot);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
|
|
@ -6,8 +6,10 @@
|
|||
const {utils: Cu} = Components;
|
||||
Cu.import("resource://gre/modules/EventEmitter.jsm");
|
||||
Cu.import("resource://gre/modules/Services.jsm");
|
||||
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
|
||||
const {actionCreators: ac, actionTypes: at} = Cu.import("resource://activity-stream/common/Actions.jsm", {});
|
||||
const {Dedupe} = Cu.import("resource://activity-stream/common/Dedupe.jsm", {});
|
||||
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "PlacesUtils", "resource://gre/modules/PlacesUtils.jsm");
|
||||
|
||||
/*
|
||||
* Generators for built in sections, keyed by the pref name for their feed.
|
||||
|
@ -21,7 +23,7 @@ const BUILT_IN_SECTIONS = {
|
|||
titleString: {id: "header_recommended_by", values: {provider: options.provider_name}},
|
||||
descString: {id: options.provider_description || "pocket_feedback_body"}
|
||||
},
|
||||
shouldHidePref: options.hidden,
|
||||
shouldHidePref: options.hidden,
|
||||
eventSource: "TOP_STORIES",
|
||||
icon: options.provider_icon,
|
||||
title: {id: "header_recommended_by", values: {provider: options.provider_name}},
|
||||
|
@ -74,13 +76,21 @@ const SectionsManager = {
|
|||
for (const feedPrefName of Object.keys(BUILT_IN_SECTIONS)) {
|
||||
const optionsPrefName = `${feedPrefName}.options`;
|
||||
this.addBuiltInSection(feedPrefName, prefs[optionsPrefName]);
|
||||
|
||||
this._dedupeConfiguration = [];
|
||||
this.sections.forEach(section => {
|
||||
if (section.dedupeFrom) {
|
||||
this._dedupeConfiguration.push({
|
||||
id: section.id,
|
||||
dedupeFrom: section.dedupeFrom
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
Object.keys(this.CONTEXT_MENU_PREFS).forEach(k =>
|
||||
Services.prefs.addObserver(this.CONTEXT_MENU_PREFS[k], this));
|
||||
|
||||
this.dedupe = new Dedupe(site => site && site.url);
|
||||
|
||||
this.initialized = true;
|
||||
this.emit(this.INIT);
|
||||
},
|
||||
|
@ -129,33 +139,28 @@ const SectionsManager = {
|
|||
},
|
||||
updateSection(id, options, shouldBroadcast) {
|
||||
this.updateSectionContextMenuOptions(options);
|
||||
|
||||
if (this.sections.has(id)) {
|
||||
const dedupedOptions = this.dedupeRows(id, options);
|
||||
this.sections.set(id, Object.assign(this.sections.get(id), dedupedOptions));
|
||||
this.emit(this.UPDATE_SECTION, id, dedupedOptions, shouldBroadcast);
|
||||
|
||||
// Update any sections that dedupe from the updated section
|
||||
this.sections.forEach(section => {
|
||||
if (section.dedupeFrom && section.dedupeFrom.includes(id)) {
|
||||
this.updateSection(section.id, section, shouldBroadcast);
|
||||
}
|
||||
});
|
||||
const optionsWithDedupe = Object.assign({}, options, {dedupeConfigurations: this._dedupeConfiguration});
|
||||
this.sections.set(id, Object.assign(this.sections.get(id), options));
|
||||
this.emit(this.UPDATE_SECTION, id, optionsWithDedupe, shouldBroadcast);
|
||||
}
|
||||
},
|
||||
dedupeRows(id, options) {
|
||||
const newOptions = Object.assign({}, options);
|
||||
const dedupeFrom = this.sections.get(id).dedupeFrom;
|
||||
if (dedupeFrom && dedupeFrom.length > 0 && options.rows) {
|
||||
for (const sectionId of dedupeFrom) {
|
||||
const section = this.sections.get(sectionId);
|
||||
if (section && section.rows) {
|
||||
const [, newRows] = this.dedupe.group(section.rows, options.rows);
|
||||
newOptions.rows = newRows;
|
||||
}
|
||||
|
||||
updateBookmarkMetadata({url}) {
|
||||
this.sections.forEach(section => {
|
||||
if (section.rows) {
|
||||
section.rows.forEach(card => {
|
||||
if (card.url === url && card.description && card.title && card.image) {
|
||||
PlacesUtils.history.update({
|
||||
url: card.url,
|
||||
title: card.title,
|
||||
description: card.description,
|
||||
previewImageURL: card.image
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
return newOptions;
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -292,6 +297,9 @@ class SectionsFeed {
|
|||
}
|
||||
break;
|
||||
}
|
||||
case at.PLACES_BOOKMARK_ADDED:
|
||||
SectionsManager.updateBookmarkMetadata(action.data);
|
||||
break;
|
||||
case at.SECTION_DISABLE:
|
||||
SectionsManager.disableSection(action.data);
|
||||
break;
|
||||
|
|
|
@ -28,7 +28,9 @@ const ACTIVITY_STREAM_ENDPOINT_PREF = "browser.newtabpage.activity-stream.teleme
|
|||
const USER_PREFS_ENCODING = {
|
||||
"showSearch": 1 << 0,
|
||||
"showTopSites": 1 << 1,
|
||||
"feeds.section.topstories": 1 << 2
|
||||
"feeds.section.topstories": 1 << 2,
|
||||
"feeds.section.highlights": 1 << 3,
|
||||
"feeds.snippets": 1 << 4
|
||||
};
|
||||
|
||||
const IMPRESSION_STATS_RESET_TIME = 60 * 60 * 1000; // 60 minutes
|
||||
|
|
|
@ -33,11 +33,11 @@ this.TopSitesFeed = class TopSitesFeed {
|
|||
this._tippyTopProvider = new TippyTopProvider();
|
||||
this.dedupe = new Dedupe(this._dedupeKey);
|
||||
this.frecentCache = new LinksCache(NewTabUtils.activityStreamLinks,
|
||||
"getTopSites", this.getLinkMigrator(), (oldOptions, newOptions) =>
|
||||
"getTopSites", ["screenshot"], (oldOptions, newOptions) =>
|
||||
// Refresh if no old options or requesting more items
|
||||
!(oldOptions.numItems >= newOptions.numItems));
|
||||
this.pinnedCache = new LinksCache(NewTabUtils.pinnedLinks,
|
||||
"links", this.getLinkMigrator(["favicon", "faviconSize"]));
|
||||
this.pinnedCache = new LinksCache(NewTabUtils.pinnedLinks, "links",
|
||||
["favicon", "faviconSize", "screenshot"]);
|
||||
}
|
||||
_dedupeKey(site) {
|
||||
return site && site.hostname;
|
||||
|
@ -59,22 +59,6 @@ this.TopSitesFeed = class TopSitesFeed {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Make a cached link data migrator by copying over screenshots and others.
|
||||
*
|
||||
* @param others {array} Optional extra properties to copy
|
||||
*/
|
||||
getLinkMigrator(others = []) {
|
||||
const properties = ["__fetchingScreenshot", "screenshot", ...others];
|
||||
return (oldLink, newLink) => {
|
||||
for (const property of properties) {
|
||||
const oldValue = oldLink[property];
|
||||
if (oldValue) {
|
||||
newLink[property] = oldValue;
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
async getLinksWithDefaults(action) {
|
||||
// Get at least SHOWMORE amount so toggling between 1 and 2 rows has sites
|
||||
const numItems = Math.max(this.store.getState().Prefs.values.topSitesCount,
|
||||
|
@ -107,8 +91,8 @@ this.TopSitesFeed = class TopSitesFeed {
|
|||
try {
|
||||
NewTabUtils.activityStreamProvider._faviconBytesToDataURI(await
|
||||
NewTabUtils.activityStreamProvider._addFavicons([copy]));
|
||||
copy.__updateCache("favicon");
|
||||
copy.__updateCache("faviconSize");
|
||||
copy.__sharedCache.updateLink("favicon", copy.favicon);
|
||||
copy.__sharedCache.updateLink("faviconSize", copy.faviconSize);
|
||||
} catch (e) {
|
||||
// Some issue with favicon, so just continue without one
|
||||
}
|
||||
|
@ -134,9 +118,8 @@ this.TopSitesFeed = class TopSitesFeed {
|
|||
if (link) {
|
||||
this._fetchIcon(link);
|
||||
|
||||
// Remove any internal properties
|
||||
delete link.__fetchingScreenshot;
|
||||
delete link.__updateCache;
|
||||
// Remove internal properties that might be updated after dispatch
|
||||
delete link.__sharedCache;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -176,12 +159,11 @@ this.TopSitesFeed = class TopSitesFeed {
|
|||
(!link.favicon || link.faviconSize < MIN_FAVICON_SIZE) &&
|
||||
!link.screenshot) {
|
||||
const {url} = link;
|
||||
Screenshots.maybeGetAndSetScreenshot(link, url, "screenshot", screenshot => {
|
||||
this.store.dispatch(ac.BroadcastToContent({
|
||||
await Screenshots.maybeCacheScreenshot(link, url, "screenshot",
|
||||
screenshot => this.store.dispatch(ac.BroadcastToContent({
|
||||
data: {screenshot, url},
|
||||
type: at.SCREENSHOT_UPDATED
|
||||
}));
|
||||
});
|
||||
})));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -232,7 +232,8 @@ this.TopStoriesFeed = class TopStoriesFeed {
|
|||
|
||||
// Create a new array with a spoc inserted at index 2
|
||||
// For now we're using the top scored spoc until we can support viewability based rotation
|
||||
let rows = this.stories.slice(0, this.stories.length);
|
||||
const position = SectionsManager.sections.get(SECTION_ID).order;
|
||||
let rows = this.store.getState().Sections[position].rows.slice(0, this.stories.length);
|
||||
rows.splice(2, 0, this.spocs[0]);
|
||||
|
||||
// Send a content update to the target tab
|
||||
|
@ -282,14 +283,6 @@ this.TopStoriesFeed = class TopStoriesFeed {
|
|||
if (this.spocs) {
|
||||
this.spocs = this.spocs.filter(s => s.url !== action.data.url);
|
||||
}
|
||||
|
||||
if (this.stories) {
|
||||
const prevStoriesLength = this.stories.length;
|
||||
this.stories = this.stories.filter(s => s.url !== action.data.url);
|
||||
if (prevStoriesLength !== this.stories.length) {
|
||||
SectionsManager.updateSection(SECTION_ID, {rows: this.stories}, true);
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -356,6 +356,19 @@ describe("Reducers", () => {
|
|||
const updatedSection = newState.find(section => section.id === "foo_bar_2");
|
||||
assert.propertyVal(updatedSection, "initialized", false);
|
||||
});
|
||||
it("should dedupe based on dedupeConfigurations", () => {
|
||||
const site = {url: "foo.com"};
|
||||
const highlights = {rows: [site], id: "highlights"};
|
||||
const topstories = {rows: [site], id: "topstories"};
|
||||
const dedupeConfigurations = [{id: "topstories", dedupeFrom: ["highlights"]}];
|
||||
const action = {data: {dedupeConfigurations}, type: "SECTION_UPDATE"};
|
||||
const state = [highlights, topstories];
|
||||
|
||||
const nextState = Sections(state, action);
|
||||
|
||||
assert.equal(nextState.find(s => s.id === "highlights").rows.length, 1);
|
||||
assert.equal(nextState.find(s => s.id === "topstories").rows.length, 0);
|
||||
});
|
||||
it("should remove blocked and deleted urls from all rows in all sections", () => {
|
||||
const blockAction = {type: at.PLACES_LINK_BLOCKED, data: {url: "www.foo.bar"}};
|
||||
const deleteAction = {type: at.PLACES_LINKS_DELETED, data: ["www.foo.bar"]};
|
||||
|
|
|
@ -22,6 +22,7 @@ describe("Highlights Feed", () => {
|
|||
let filterAdultStub;
|
||||
let sectionsManagerStub;
|
||||
let shortURLStub;
|
||||
let profileAgeCreatedStub;
|
||||
|
||||
const fetchHighlights = async() => {
|
||||
await feed.fetchHighlights();
|
||||
|
@ -42,11 +43,18 @@ describe("Highlights Feed", () => {
|
|||
};
|
||||
fakeScreenshot = {
|
||||
getScreenshotForURL: sandbox.spy(() => Promise.resolve(FAKE_IMAGE)),
|
||||
maybeGetAndSetScreenshot: Screenshots.maybeGetAndSetScreenshot
|
||||
maybeCacheScreenshot: Screenshots.maybeCacheScreenshot
|
||||
};
|
||||
filterAdultStub = sinon.stub().returns([]);
|
||||
shortURLStub = sinon.stub().callsFake(site => site.url.match(/\/([^/]+)/)[1]);
|
||||
|
||||
const fakeProfileAgePromise = {};
|
||||
const fakeProfileAge = function() { return fakeProfileAgePromise; };
|
||||
profileAgeCreatedStub = sinon.stub().callsFake(() => Promise.resolve(42));
|
||||
sinon.stub(fakeProfileAgePromise, "created").get(profileAgeCreatedStub);
|
||||
|
||||
globals.set("NewTabUtils", fakeNewTabUtils);
|
||||
globals.set("ProfileAge", fakeProfileAge);
|
||||
({HighlightsFeed, HIGHLIGHTS_UPDATE_TIME, SECTION_ID} = injector({
|
||||
"lib/FilterAdult.jsm": {filterAdult: filterAdultStub},
|
||||
"lib/ShortURL.jsm": {shortURL: shortURLStub},
|
||||
|
@ -109,6 +117,7 @@ describe("Highlights Feed", () => {
|
|||
const subscribeCallback = feed.store.subscribe.firstCall.args[0];
|
||||
await subscribeCallback();
|
||||
await firstFetch;
|
||||
await feed._getBookmarksThreshold();
|
||||
assert.calledOnce(fakeNewTabUtils.activityStreamLinks.getHighlights);
|
||||
|
||||
// If TopSites is initialised in the first place it shouldn't wait
|
||||
|
@ -117,6 +126,7 @@ describe("Highlights Feed", () => {
|
|||
fakeNewTabUtils.activityStreamLinks.getHighlights.reset();
|
||||
await feed.fetchHighlights();
|
||||
assert.notCalled(feed.store.subscribe);
|
||||
await feed._getBookmarksThreshold();
|
||||
assert.calledOnce(fakeNewTabUtils.activityStreamLinks.getHighlights);
|
||||
});
|
||||
it("should add hostname and hasImage to each link", async () => {
|
||||
|
@ -215,8 +225,12 @@ describe("Highlights Feed", () => {
|
|||
describe("#fetchImage", () => {
|
||||
const FAKE_URL = "https://mozilla.org";
|
||||
const FAKE_IMAGE_URL = "https://mozilla.org/preview.jpg";
|
||||
function fetchImage(page) {
|
||||
return feed.fetchImage(Object.assign({__sharedCache: {updateLink() {}}},
|
||||
page));
|
||||
}
|
||||
it("should capture the image, if available", async () => {
|
||||
await feed.fetchImage({
|
||||
await fetchImage({
|
||||
preview_image_url: FAKE_IMAGE_URL,
|
||||
url: FAKE_URL
|
||||
});
|
||||
|
@ -225,13 +239,13 @@ describe("Highlights Feed", () => {
|
|||
assert.calledWith(fakeScreenshot.getScreenshotForURL, FAKE_IMAGE_URL);
|
||||
});
|
||||
it("should fall back to capturing a screenshot", async () => {
|
||||
await feed.fetchImage({url: FAKE_URL});
|
||||
await fetchImage({url: FAKE_URL});
|
||||
|
||||
assert.calledOnce(fakeScreenshot.getScreenshotForURL);
|
||||
assert.calledWith(fakeScreenshot.getScreenshotForURL, FAKE_URL);
|
||||
});
|
||||
it("should call SectionsManager.updateSectionCard with the right arguments", async () => {
|
||||
await feed.fetchImage({
|
||||
await fetchImage({
|
||||
preview_image_url: FAKE_IMAGE_URL,
|
||||
url: FAKE_URL
|
||||
});
|
||||
|
@ -239,7 +253,7 @@ describe("Highlights Feed", () => {
|
|||
assert.calledOnce(sectionsManagerStub.updateSectionCard);
|
||||
assert.calledWith(sectionsManagerStub.updateSectionCard, "highlights", FAKE_URL, {image: FAKE_IMAGE}, true);
|
||||
});
|
||||
it("should update the card with the image", async () => {
|
||||
it("should not update the card with the image", async () => {
|
||||
const card = {
|
||||
preview_image_url: FAKE_IMAGE_URL,
|
||||
url: FAKE_URL
|
||||
|
@ -247,7 +261,29 @@ describe("Highlights Feed", () => {
|
|||
|
||||
await feed.fetchImage(card);
|
||||
|
||||
assert.propertyVal(card, "image", FAKE_IMAGE);
|
||||
assert.notProperty(card, "image");
|
||||
});
|
||||
});
|
||||
describe("#_getBookmarksThreshold", () => {
|
||||
it("should have the correct default", () => {
|
||||
assert.equal(feed._profileAge, 0);
|
||||
});
|
||||
it("should not call ProfileAge if _profileAge is set", async () => {
|
||||
feed._profileAge = 10;
|
||||
|
||||
await feed._getBookmarksThreshold();
|
||||
|
||||
assert.notCalled(profileAgeCreatedStub);
|
||||
});
|
||||
it("should call ProfileAge if _profileAge is not set", async () => {
|
||||
await feed._getBookmarksThreshold();
|
||||
|
||||
assert.calledOnce(profileAgeCreatedStub);
|
||||
});
|
||||
it("should set _profileAge", async () => {
|
||||
await feed._getBookmarksThreshold();
|
||||
|
||||
assert.notEqual(feed._profileAge, 0);
|
||||
});
|
||||
});
|
||||
describe("#uninit", () => {
|
||||
|
|
|
@ -12,11 +12,14 @@ const FAKE_CARD_OPTIONS = {title: "Some fake title"};
|
|||
describe("SectionsManager", () => {
|
||||
let globals;
|
||||
let fakeServices;
|
||||
let fakePlacesUtils;
|
||||
|
||||
beforeEach(() => {
|
||||
globals = new GlobalOverrider();
|
||||
fakeServices = {prefs: {getBoolPref: sinon.spy(), addObserver: sinon.spy(), removeObserver: sinon.spy()}};
|
||||
fakePlacesUtils = {history: {update: sinon.stub()}};
|
||||
globals.set("Services", fakeServices);
|
||||
globals.set("PlacesUtils", fakePlacesUtils);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
|
@ -132,10 +135,11 @@ describe("SectionsManager", () => {
|
|||
it("should emit an UPDATE_SECTION event with correct arguments", () => {
|
||||
SectionsManager.addSection(FAKE_ID, FAKE_OPTIONS);
|
||||
const spy = sinon.spy();
|
||||
const dedupeConfigurations = [{id: "topstories", dedupeFrom: ["highlights"]}];
|
||||
SectionsManager.on(SectionsManager.UPDATE_SECTION, spy);
|
||||
SectionsManager.updateSection(FAKE_ID, {rows: FAKE_ROWS}, true);
|
||||
assert.calledOnce(spy);
|
||||
assert.calledWith(spy, SectionsManager.UPDATE_SECTION, FAKE_ID, {rows: FAKE_ROWS}, true);
|
||||
assert.calledWith(spy, SectionsManager.UPDATE_SECTION, FAKE_ID, {rows: FAKE_ROWS, dedupeConfigurations}, true);
|
||||
});
|
||||
it("should do nothing if the section doesn't exist", () => {
|
||||
SectionsManager.removeSection(FAKE_ID);
|
||||
|
@ -205,25 +209,33 @@ describe("SectionsManager", () => {
|
|||
assert.notCalled(spy);
|
||||
});
|
||||
});
|
||||
describe("#dedupe", () => {
|
||||
it("should dedupe stories from highlights", () => {
|
||||
SectionsManager.init();
|
||||
// Add some rows to highlights
|
||||
SectionsManager.updateSection("highlights", {rows: [{url: "https://highlight.com/abc"}, {url: "https://shared.com/def"}]});
|
||||
// Add some rows to top stories
|
||||
SectionsManager.updateSection("topstories", {rows: [{url: "https://topstory.com/ghi"}, {url: "https://shared.com/def"}]});
|
||||
// Verify deduping
|
||||
assert.deepEqual(SectionsManager.sections.get("topstories").rows, [{url: "https://topstory.com/ghi"}]);
|
||||
describe("#updateBookmarkMetadata", () => {
|
||||
let rows;
|
||||
beforeEach(() => {
|
||||
rows = [{
|
||||
url: "bar",
|
||||
title: "title",
|
||||
description: "description",
|
||||
image: "image"
|
||||
}];
|
||||
SectionsManager.addSection(FAKE_ID, {rows});
|
||||
});
|
||||
it("should dedupe stories from highlights when updating highlights", () => {
|
||||
SectionsManager.init();
|
||||
// Add some rows to top stories
|
||||
SectionsManager.updateSection("topstories", {rows: [{url: "https://topstory.com/ghi"}, {url: "https://shared.com/def"}]});
|
||||
assert.deepEqual(SectionsManager.sections.get("topstories").rows, [{url: "https://topstory.com/ghi"}, {url: "https://shared.com/def"}]);
|
||||
// Add some rows to highlights
|
||||
SectionsManager.updateSection("highlights", {rows: [{url: "https://highlight.com/abc"}, {url: "https://shared.com/def"}]});
|
||||
// Verify deduping
|
||||
assert.deepEqual(SectionsManager.sections.get("topstories").rows, [{url: "https://topstory.com/ghi"}]);
|
||||
it("shouldn't call PlacesUtils if no story", () => {
|
||||
SectionsManager.updateBookmarkMetadata({url: "foo"});
|
||||
|
||||
assert.notCalled(fakePlacesUtils.history.update);
|
||||
});
|
||||
|
||||
it("should call PlacesUtils", () => {
|
||||
SectionsManager.updateBookmarkMetadata({url: "bar"});
|
||||
|
||||
assert.calledOnce(fakePlacesUtils.history.update);
|
||||
assert.calledWithExactly(fakePlacesUtils.history.update, {
|
||||
url: "bar",
|
||||
title: "title",
|
||||
description: "description",
|
||||
previewImageURL: "image"
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -426,5 +438,12 @@ describe("SectionsFeed", () => {
|
|||
assert.neverCalledWith(spy, "ACTION_DISPATCHED", action);
|
||||
}
|
||||
});
|
||||
it("should call updateBookmarkMetadata on PLACES_BOOKMARK_ADDED", () => {
|
||||
const stub = sinon.stub(SectionsManager, "updateBookmarkMetadata");
|
||||
|
||||
feed.onAction({type: "PLACES_BOOKMARK_ADDED", data: {}});
|
||||
|
||||
assert.calledOnce(stub);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -61,7 +61,7 @@ describe("Top Sites Feed", () => {
|
|||
};
|
||||
fakeScreenshot = {
|
||||
getScreenshotForURL: sandbox.spy(() => Promise.resolve(FAKE_SCREENSHOT)),
|
||||
maybeGetAndSetScreenshot: Screenshots.maybeGetAndSetScreenshot
|
||||
maybeCacheScreenshot: Screenshots.maybeCacheScreenshot
|
||||
};
|
||||
filterAdultStub = sinon.stub().returns([]);
|
||||
shortURLStub = sinon.stub().callsFake(site => site.url);
|
||||
|
@ -95,6 +95,10 @@ describe("Top Sites Feed", () => {
|
|||
clock.restore();
|
||||
});
|
||||
|
||||
function stubFaviconsToUseScreenshots() {
|
||||
fakeNewTabUtils.activityStreamProvider._addFavicons = sandbox.stub();
|
||||
}
|
||||
|
||||
describe("#refreshDefaults", () => {
|
||||
it("should add defaults on PREFS_INITIAL_VALUES", () => {
|
||||
feed.onAction({type: at.PREFS_INITIAL_VALUES, data: {"default.sites": "https://foo.com"}});
|
||||
|
@ -136,7 +140,7 @@ describe("Top Sites Feed", () => {
|
|||
|
||||
describe("general", () => {
|
||||
beforeEach(() => {
|
||||
sandbox.stub(fakeScreenshot, "maybeGetAndSetScreenshot");
|
||||
sandbox.stub(fakeScreenshot, "maybeCacheScreenshot");
|
||||
});
|
||||
it("should get the links from NewTabUtils", async () => {
|
||||
const result = await feed.getLinksWithDefaults();
|
||||
|
@ -241,8 +245,7 @@ describe("Top Sites Feed", () => {
|
|||
assert.calledTwice(global.NewTabUtils.activityStreamLinks.getTopSites);
|
||||
});
|
||||
it("should migrate frecent screenshot data without getting screenshots again", async () => {
|
||||
// Don't add favicons so we fall back to screenshots
|
||||
fakeNewTabUtils.activityStreamProvider._addFavicons = sandbox.stub();
|
||||
stubFaviconsToUseScreenshots();
|
||||
await feed.getLinksWithDefaults();
|
||||
const {callCount} = fakeScreenshot.getScreenshotForURL;
|
||||
feed.frecentCache.expire();
|
||||
|
@ -271,6 +274,36 @@ describe("Top Sites Feed", () => {
|
|||
const internal = Object.keys(result[0]).filter(key => key.startsWith("__"));
|
||||
assert.equal(internal.join(""), "");
|
||||
});
|
||||
describe("concurrency", () => {
|
||||
let resolvers;
|
||||
beforeEach(() => {
|
||||
stubFaviconsToUseScreenshots();
|
||||
resolvers = [];
|
||||
fakeScreenshot.getScreenshotForURL = sandbox.spy(() => new Promise(
|
||||
resolve => resolvers.push(resolve)));
|
||||
});
|
||||
|
||||
const getTwice = () => Promise.all([feed.getLinksWithDefaults(), feed.getLinksWithDefaults()]);
|
||||
const resolveAll = () => resolvers.forEach(resolve => resolve(FAKE_SCREENSHOT));
|
||||
|
||||
it("should call the backing data once", async () => {
|
||||
await getTwice();
|
||||
|
||||
assert.calledOnce(global.NewTabUtils.activityStreamLinks.getTopSites);
|
||||
});
|
||||
it("should get screenshots once per link", async () => {
|
||||
await getTwice();
|
||||
|
||||
assert.callCount(fakeScreenshot.getScreenshotForURL, FAKE_LINKS.length);
|
||||
});
|
||||
it("should dispatch once per link screenshot fetched", async () => {
|
||||
await getTwice();
|
||||
|
||||
await resolveAll();
|
||||
|
||||
assert.callCount(feed.store.dispatch, FAKE_LINKS.length);
|
||||
});
|
||||
});
|
||||
});
|
||||
describe("deduping", () => {
|
||||
beforeEach(() => {
|
||||
|
@ -376,26 +409,26 @@ describe("Top Sites Feed", () => {
|
|||
assert.propertyVal(link, "screenshot", "reuse.png");
|
||||
});
|
||||
it("should reuse existing fetching screenshot on the link", async () => {
|
||||
const link = {__fetchingScreenshot: Promise.resolve("fetching.png")};
|
||||
const link = {__sharedCache: {fetchingScreenshot: Promise.resolve("fetching.png")}};
|
||||
|
||||
await feed._fetchIcon(link);
|
||||
|
||||
assert.notCalled(fakeScreenshot.getScreenshotForURL);
|
||||
assert.propertyVal(link, "screenshot", "fetching.png");
|
||||
});
|
||||
it("should get a screenshot if the link is missing it", () => {
|
||||
feed._fetchIcon(FAKE_LINKS[0]);
|
||||
feed._fetchIcon(Object.assign({__sharedCache: {}}, FAKE_LINKS[0]));
|
||||
|
||||
assert.calledOnce(fakeScreenshot.getScreenshotForURL);
|
||||
assert.calledWith(fakeScreenshot.getScreenshotForURL, FAKE_LINKS[0].url);
|
||||
});
|
||||
it("should update the link's cache if it can be updated", () => {
|
||||
const link = {__updateCache: sandbox.stub()};
|
||||
it("should update the link's cache with a screenshot", async () => {
|
||||
const updateLink = sandbox.stub();
|
||||
const link = {__sharedCache: {updateLink}};
|
||||
|
||||
feed._fetchIcon(link);
|
||||
await feed._fetchIcon(link);
|
||||
|
||||
assert.calledOnce(link.__updateCache);
|
||||
assert.calledWith(link.__updateCache, "__fetchingScreenshot");
|
||||
assert.calledOnce(updateLink);
|
||||
assert.calledWith(updateLink, "screenshot", FAKE_SCREENSHOT);
|
||||
});
|
||||
it("should skip getting a screenshot if there is a tippy top icon", () => {
|
||||
feed._tippyTopProvider.processSite = site => {
|
||||
|
|
|
@ -40,7 +40,7 @@ describe("Top Stories Feed", () => {
|
|||
enableSection: sinon.spy(),
|
||||
disableSection: sinon.spy(),
|
||||
updateSection: sinon.spy(),
|
||||
sections: new Map([["topstories", {options: FAKE_OPTIONS}]])
|
||||
sections: new Map([["topstories", {order: 0, options: FAKE_OPTIONS}]])
|
||||
};
|
||||
|
||||
class FakeUserDomainAffinityProvider {}
|
||||
|
@ -377,7 +377,7 @@ describe("Top Stories Feed", () => {
|
|||
|
||||
const response = {
|
||||
"settings": {"spocsPerNewTabs": 2},
|
||||
"recommendations": [{"id": "rec1"}, {"id": "rec2"}, {"id": "rec3"}],
|
||||
"recommendations": [{"guid": "rec1"}, {"guid": "rec2"}, {"guid": "rec3"}],
|
||||
"spocs": [{"id": "spoc1"}, {"id": "spoc2"}]
|
||||
};
|
||||
|
||||
|
@ -387,9 +387,12 @@ describe("Top Stories Feed", () => {
|
|||
fetchStub.resolves({ok: true, status: 200, json: () => Promise.resolve(response)});
|
||||
await instance.fetchStories();
|
||||
|
||||
instance.store.getState = () => ({Sections: [{rows: response.recommendations}]});
|
||||
|
||||
instance.onAction({type: at.NEW_TAB_REHYDRATED, meta: {fromTarget: {}}});
|
||||
assert.calledOnce(instance.store.dispatch);
|
||||
let action = instance.store.dispatch.firstCall.args[0];
|
||||
|
||||
assert.equal(at.SECTION_UPDATE, action.type);
|
||||
assert.equal(true, action.meta.skipMain);
|
||||
assert.equal(action.data.rows[0].guid, "rec1");
|
||||
|
@ -429,6 +432,8 @@ describe("Top Stories Feed", () => {
|
|||
assert.notCalled(instance.store.dispatch);
|
||||
assert.equal(instance.contentUpdateQueue.length, 1);
|
||||
|
||||
instance.store.getState = () => ({Sections: [{rows: response.recommendations}]});
|
||||
|
||||
await instance.fetchStories();
|
||||
assert.equal(instance.contentUpdateQueue.length, 0);
|
||||
assert.calledOnce(instance.store.dispatch);
|
||||
|
@ -538,15 +543,11 @@ describe("Top Stories Feed", () => {
|
|||
assert.equal(instance.topicsLastUpdated, 0);
|
||||
assert.equal(instance.affinityLastUpdated, 0);
|
||||
});
|
||||
it("should filter recs and spocs when link is blocked", () => {
|
||||
instance.stories = [{"url": "not_blocked"}, {"url": "blocked"}];
|
||||
it("should filter spocs when link is blocked", () => {
|
||||
instance.spocs = [{"url": "not_blocked"}, {"url": "blocked"}];
|
||||
instance.onAction({type: at.PLACES_LINK_BLOCKED, data: {url: "blocked"}});
|
||||
|
||||
assert.deepEqual(instance.stories, [{"url": "not_blocked"}]);
|
||||
assert.deepEqual(instance.spocs, [{"url": "not_blocked"}]);
|
||||
assert.calledOnce(sectionsManagerStub.updateSection);
|
||||
assert.calledWith(sectionsManagerStub.updateSection, SECTION_ID, {rows: instance.stories});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
Загрузка…
Ссылка в новой задаче