Bug 1426774 - Add drag-n-drop, 2-rows default and bug fixes to Activity Stream. r=dmose

MozReview-Commit-ID: CctWqWzJow

--HG--
extra : rebase_source : 39ec45dd214b6f308517a437380c477d297ec2ab
This commit is contained in:
Ed Lee 2017-12-21 16:56:22 -08:00
Родитель 36e8f8aeb2
Коммит 2240dd0135
106 изменённых файлов: 531 добавлений и 265 удалений

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

@ -78,9 +78,9 @@ for (const type of [
"TELEMETRY_PERFORMANCE_EVENT",
"TELEMETRY_UNDESIRED_EVENT",
"TELEMETRY_USER_EVENT",
"TOP_SITES_ADD",
"TOP_SITES_CANCEL_EDIT",
"TOP_SITES_EDIT",
"TOP_SITES_INSERT",
"TOP_SITES_PIN",
"TOP_SITES_UNPIN",
"TOP_SITES_UPDATED",

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

@ -51,7 +51,7 @@ this.PrerenderData = new _PrerenderData({
"migrationExpired": true,
"showTopSites": true,
"showSearch": true,
"topSitesCount": 6,
"topSitesCount": 12,
"collapseTopSites": false,
"section.highlights.collapsed": false,
"section.topstories.collapsed": false,
@ -67,6 +67,7 @@ this.PrerenderData = new _PrerenderData({
validation: [
"showTopSites",
"showSearch",
"topSitesCount",
"collapseTopSites",
"section.highlights.collapsed",
"section.topstories.collapsed",

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

@ -28,7 +28,7 @@ const INITIAL_STATE = {
// context menu to TopSitesEdit.
editForm: {
visible: false,
site: null
index: -1
}
},
Prefs: {
@ -95,7 +95,7 @@ function TopSites(prevState = INITIAL_STATE.TopSites, action) {
}
return Object.assign({}, prevState, {initialized: true, rows: action.data});
case at.TOP_SITES_EDIT:
return Object.assign({}, prevState, {editForm: {visible: true, site: action.data}});
return Object.assign({}, prevState, {editForm: {visible: true, index: action.data.index}});
case at.TOP_SITES_CANCEL_EDIT:
return Object.assign({}, prevState, {editForm: {visible: false}});
case at.SCREENSHOT_UPDATED:

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

@ -242,9 +242,8 @@ main {
.top-sites-list {
list-style: none;
margin: 0;
margin: 0 -16px;
margin-bottom: -18px;
margin-inline-end: -32px;
padding: 0; }
@media (max-width: 416px) {
.top-sites-list :nth-child(2n+1) .context-menu {
@ -290,17 +289,18 @@ main {
offset-inline-start: auto; } }
.top-sites-list li {
display: inline-block;
margin: 0 0 8px;
margin-inline-end: 32px; }
margin: 0 0 8px; }
.top-sites-list .top-site-outer {
position: relative; }
.top-sites-list .top-site-outer > a {
color: inherit;
display: block;
outline: none; }
.top-sites-list .top-site-outer > a:-moz-any(.active, :focus) .tile {
box-shadow: inset 0 0 0 1px rgba(0, 0, 0, 0.1), 0 0 0 5px #D7D7DB;
transition: box-shadow 150ms; }
padding: 0 16px; }
.top-sites-list .top-site-outer .top-site-inner {
position: relative; }
.top-sites-list .top-site-outer .top-site-inner > a {
color: inherit;
display: block;
outline: none; }
.top-sites-list .top-site-outer .top-site-inner > a:-moz-any(.active, :focus) .tile {
box-shadow: inset 0 0 0 1px rgba(0, 0, 0, 0.1), 0 0 0 5px #D7D7DB;
transition: box-shadow 150ms; }
.top-sites-list .top-site-outer .context-menu-button {
background-clip: padding-box;
background-color: #FFF;
@ -347,10 +347,6 @@ main {
text-transform: uppercase; }
.top-sites-list .top-site-outer .tile::before {
content: attr(data-fallback); }
.top-sites-list .top-site-outer.placeholder .tile {
box-shadow: inset 0 0 0 1px rgba(0, 0, 0, 0.1); }
.top-sites-list .top-site-outer.placeholder .screenshot {
display: none; }
.top-sites-list .top-site-outer .screenshot {
background-color: #FFF;
background-position: top left;
@ -446,6 +442,13 @@ main {
border-right: 0; }
.top-sites-list .top-site-outer .edit-menu button:first-child:dir(rtl) {
border-right: 0; }
.top-sites-list .top-site-outer.placeholder .tile {
box-shadow: inset 0 0 0 1px rgba(0, 0, 0, 0.1); }
.top-sites-list .top-site-outer.placeholder .screenshot {
display: none; }
.top-sites-list .top-site-outer.placeholder .edit-menu:last-child button {
background-size: 13px;
width: 23px; }
.edit-topsites-wrapper .edit-topsites-button {
border-right: 1px solid #D7D7DB;

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

@ -242,9 +242,8 @@ main {
.top-sites-list {
list-style: none;
margin: 0;
margin: 0 -16px;
margin-bottom: -18px;
margin-inline-end: -32px;
padding: 0; }
@media (max-width: 416px) {
.top-sites-list :nth-child(2n+1) .context-menu {
@ -290,17 +289,18 @@ main {
offset-inline-start: auto; } }
.top-sites-list li {
display: inline-block;
margin: 0 0 8px;
margin-inline-end: 32px; }
margin: 0 0 8px; }
.top-sites-list .top-site-outer {
position: relative; }
.top-sites-list .top-site-outer > a {
color: inherit;
display: block;
outline: none; }
.top-sites-list .top-site-outer > a:-moz-any(.active, :focus) .tile {
box-shadow: inset 0 0 0 1px rgba(0, 0, 0, 0.1), 0 0 0 5px #D7D7DB;
transition: box-shadow 150ms; }
padding: 0 16px; }
.top-sites-list .top-site-outer .top-site-inner {
position: relative; }
.top-sites-list .top-site-outer .top-site-inner > a {
color: inherit;
display: block;
outline: none; }
.top-sites-list .top-site-outer .top-site-inner > a:-moz-any(.active, :focus) .tile {
box-shadow: inset 0 0 0 1px rgba(0, 0, 0, 0.1), 0 0 0 5px #D7D7DB;
transition: box-shadow 150ms; }
.top-sites-list .top-site-outer .context-menu-button {
background-clip: padding-box;
background-color: #FFF;
@ -347,10 +347,6 @@ main {
text-transform: uppercase; }
.top-sites-list .top-site-outer .tile::before {
content: attr(data-fallback); }
.top-sites-list .top-site-outer.placeholder .tile {
box-shadow: inset 0 0 0 1px rgba(0, 0, 0, 0.1); }
.top-sites-list .top-site-outer.placeholder .screenshot {
display: none; }
.top-sites-list .top-site-outer .screenshot {
background-color: #FFF;
background-position: top left;
@ -446,6 +442,13 @@ main {
border-right: 0; }
.top-sites-list .top-site-outer .edit-menu button:first-child:dir(rtl) {
border-right: 0; }
.top-sites-list .top-site-outer.placeholder .tile {
box-shadow: inset 0 0 0 1px rgba(0, 0, 0, 0.1); }
.top-sites-list .top-site-outer.placeholder .screenshot {
display: none; }
.top-sites-list .top-site-outer.placeholder .edit-menu:last-child button {
background-size: 13px;
width: 23px; }
.edit-topsites-wrapper .edit-topsites-button {
border-right: 1px solid #D7D7DB;

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

@ -242,9 +242,8 @@ main {
.top-sites-list {
list-style: none;
margin: 0;
margin: 0 -16px;
margin-bottom: -18px;
margin-inline-end: -32px;
padding: 0; }
@media (max-width: 416px) {
.top-sites-list :nth-child(2n+1) .context-menu {
@ -290,17 +289,18 @@ main {
offset-inline-start: auto; } }
.top-sites-list li {
display: inline-block;
margin: 0 0 8px;
margin-inline-end: 32px; }
margin: 0 0 8px; }
.top-sites-list .top-site-outer {
position: relative; }
.top-sites-list .top-site-outer > a {
color: inherit;
display: block;
outline: none; }
.top-sites-list .top-site-outer > a:-moz-any(.active, :focus) .tile {
box-shadow: inset 0 0 0 1px rgba(0, 0, 0, 0.1), 0 0 0 5px #D7D7DB;
transition: box-shadow 150ms; }
padding: 0 16px; }
.top-sites-list .top-site-outer .top-site-inner {
position: relative; }
.top-sites-list .top-site-outer .top-site-inner > a {
color: inherit;
display: block;
outline: none; }
.top-sites-list .top-site-outer .top-site-inner > a:-moz-any(.active, :focus) .tile {
box-shadow: inset 0 0 0 1px rgba(0, 0, 0, 0.1), 0 0 0 5px #D7D7DB;
transition: box-shadow 150ms; }
.top-sites-list .top-site-outer .context-menu-button {
background-clip: padding-box;
background-color: #FFF;
@ -347,10 +347,6 @@ main {
text-transform: uppercase; }
.top-sites-list .top-site-outer .tile::before {
content: attr(data-fallback); }
.top-sites-list .top-site-outer.placeholder .tile {
box-shadow: inset 0 0 0 1px rgba(0, 0, 0, 0.1); }
.top-sites-list .top-site-outer.placeholder .screenshot {
display: none; }
.top-sites-list .top-site-outer .screenshot {
background-color: #FFF;
background-position: top left;
@ -446,6 +442,13 @@ main {
border-right: 0; }
.top-sites-list .top-site-outer .edit-menu button:first-child:dir(rtl) {
border-right: 0; }
.top-sites-list .top-site-outer.placeholder .tile {
box-shadow: inset 0 0 0 1px rgba(0, 0, 0, 0.1); }
.top-sites-list .top-site-outer.placeholder .screenshot {
display: none; }
.top-sites-list .top-site-outer.placeholder .edit-menu:last-child button {
background-size: 13px;
width: 23px; }
.edit-topsites-wrapper .edit-topsites-button {
border-right: 1px solid #D7D7DB;

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

@ -94,7 +94,7 @@ const globalImportContext = typeof Window === "undefined" ? BACKGROUND_PROCESS :
// UNINIT: "UNINIT"
// }
const actionTypes = {};
for (const type of ["BLOCK_URL", "BOOKMARK_URL", "DELETE_BOOKMARK_BY_ID", "DELETE_HISTORY_URL", "DELETE_HISTORY_URL_CONFIRM", "DIALOG_CANCEL", "DIALOG_OPEN", "DISABLE_ONBOARDING", "INIT", "MIGRATION_CANCEL", "MIGRATION_COMPLETED", "MIGRATION_START", "NEW_TAB_INIT", "NEW_TAB_INITIAL_STATE", "NEW_TAB_LOAD", "NEW_TAB_REHYDRATED", "NEW_TAB_STATE_REQUEST", "NEW_TAB_UNLOAD", "OPEN_LINK", "OPEN_NEW_WINDOW", "OPEN_PRIVATE_WINDOW", "PAGE_PRERENDERED", "PLACES_BOOKMARK_ADDED", "PLACES_BOOKMARK_CHANGED", "PLACES_BOOKMARK_REMOVED", "PLACES_HISTORY_CLEARED", "PLACES_LINKS_DELETED", "PLACES_LINK_BLOCKED", "PREFS_INITIAL_VALUES", "PREF_CHANGED", "RICH_ICON_MISSING", "SAVE_SESSION_PERF_DATA", "SAVE_TO_POCKET", "SCREENSHOT_UPDATED", "SECTION_DEREGISTER", "SECTION_DISABLE", "SECTION_ENABLE", "SECTION_OPTIONS_CHANGED", "SECTION_REGISTER", "SECTION_UPDATE", "SECTION_UPDATE_CARD", "SETTINGS_CLOSE", "SETTINGS_OPEN", "SET_PREF", "SHOW_FIREFOX_ACCOUNTS", "SNIPPETS_BLOCKLIST_UPDATED", "SNIPPETS_DATA", "SNIPPETS_RESET", "SNIPPET_BLOCKED", "SYSTEM_TICK", "TELEMETRY_IMPRESSION_STATS", "TELEMETRY_PERFORMANCE_EVENT", "TELEMETRY_UNDESIRED_EVENT", "TELEMETRY_USER_EVENT", "TOP_SITES_ADD", "TOP_SITES_CANCEL_EDIT", "TOP_SITES_EDIT", "TOP_SITES_PIN", "TOP_SITES_UNPIN", "TOP_SITES_UPDATED", "UNINIT"]) {
for (const type of ["BLOCK_URL", "BOOKMARK_URL", "DELETE_BOOKMARK_BY_ID", "DELETE_HISTORY_URL", "DELETE_HISTORY_URL_CONFIRM", "DIALOG_CANCEL", "DIALOG_OPEN", "DISABLE_ONBOARDING", "INIT", "MIGRATION_CANCEL", "MIGRATION_COMPLETED", "MIGRATION_START", "NEW_TAB_INIT", "NEW_TAB_INITIAL_STATE", "NEW_TAB_LOAD", "NEW_TAB_REHYDRATED", "NEW_TAB_STATE_REQUEST", "NEW_TAB_UNLOAD", "OPEN_LINK", "OPEN_NEW_WINDOW", "OPEN_PRIVATE_WINDOW", "PAGE_PRERENDERED", "PLACES_BOOKMARK_ADDED", "PLACES_BOOKMARK_CHANGED", "PLACES_BOOKMARK_REMOVED", "PLACES_HISTORY_CLEARED", "PLACES_LINKS_DELETED", "PLACES_LINK_BLOCKED", "PREFS_INITIAL_VALUES", "PREF_CHANGED", "RICH_ICON_MISSING", "SAVE_SESSION_PERF_DATA", "SAVE_TO_POCKET", "SCREENSHOT_UPDATED", "SECTION_DEREGISTER", "SECTION_DISABLE", "SECTION_ENABLE", "SECTION_OPTIONS_CHANGED", "SECTION_REGISTER", "SECTION_UPDATE", "SECTION_UPDATE_CARD", "SETTINGS_CLOSE", "SETTINGS_OPEN", "SET_PREF", "SHOW_FIREFOX_ACCOUNTS", "SNIPPETS_BLOCKLIST_UPDATED", "SNIPPETS_DATA", "SNIPPETS_RESET", "SNIPPET_BLOCKED", "SYSTEM_TICK", "TELEMETRY_IMPRESSION_STATS", "TELEMETRY_PERFORMANCE_EVENT", "TELEMETRY_UNDESIRED_EVENT", "TELEMETRY_USER_EVENT", "TOP_SITES_CANCEL_EDIT", "TOP_SITES_EDIT", "TOP_SITES_INSERT", "TOP_SITES_PIN", "TOP_SITES_UNPIN", "TOP_SITES_UPDATED", "UNINIT"]) {
actionTypes[type] = type;
}
@ -368,7 +368,7 @@ const INITIAL_STATE = {
// context menu to TopSitesEdit.
editForm: {
visible: false,
site: null
index: -1
}
},
Prefs: {
@ -437,7 +437,7 @@ function TopSites(prevState = INITIAL_STATE.TopSites, action) {
}
return Object.assign({}, prevState, { initialized: true, rows: action.data });
case at.TOP_SITES_EDIT:
return Object.assign({}, prevState, { editForm: { visible: true, site: action.data } });
return Object.assign({}, prevState, { editForm: { visible: true, index: action.data.index } });
case at.TOP_SITES_CANCEL_EDIT:
return Object.assign({}, prevState, { editForm: { visible: false } });
case at.SCREENSHOT_UPDATED:
@ -884,12 +884,12 @@ const LinkMenuOptions = {
}),
userEvent: "SAVE_TO_POCKET"
}),
EditTopSite: site => ({
EditTopSite: (site, index) => ({
id: "edit_topsites_button_text",
icon: "edit",
action: {
type: Actions["actionTypes"].TOP_SITES_EDIT,
data: { url: site.url, label: site.label }
data: { index }
}
}),
CheckBookmark: site => site.bookmarkGuid ? LinkMenuOptions.RemoveBookmark(site) : LinkMenuOptions.AddBookmark(site),
@ -902,7 +902,7 @@ const LinkMenuOptions = {
const DEFAULT_SITE_MENU_OPTIONS = ["CheckPinTopSite", "Separator", "OpenInNewWindow", "OpenInPrivateWindow", "Separator", "BlockUrl"];
const DEFAULT_SITE_MENU_OPTIONS = ["CheckPinTopSite", "EditTopSite", "Separator", "OpenInNewWindow", "OpenInPrivateWindow", "Separator", "BlockUrl"];
class LinkMenu__LinkMenu extends external__React__default.a.PureComponent {
getOptions() {
@ -2502,71 +2502,125 @@ var _extends = Object.assign || function (target) { for (var i = 1; i < argument
const TopSiteLink = props => {
const { link, title } = props;
const topSiteOuterClassName = `top-site-outer${props.className ? ` ${props.className}` : ""}`;
const { tippyTopIcon, faviconSize } = link;
const letterFallback = title[0];
let imageClassName;
let imageStyle;
let showSmallFavicon = false;
let smallFaviconStyle;
let smallFaviconFallback;
if (tippyTopIcon || faviconSize >= MIN_RICH_FAVICON_SIZE) {
// styles and class names for top sites with rich icons
imageClassName = "top-site-icon rich-icon";
imageStyle = {
backgroundColor: link.backgroundColor,
backgroundImage: `url(${tippyTopIcon || link.favicon})`
};
} else {
// styles and class names for top sites with screenshot + small icon in top left corner
imageClassName = `screenshot${link.screenshot ? " active" : ""}`;
imageStyle = { backgroundImage: link.screenshot ? `url(${link.screenshot})` : "none" };
// only show a favicon in top left if it's greater than 16x16
if (faviconSize >= MIN_CORNER_FAVICON_SIZE) {
showSmallFavicon = true;
smallFaviconStyle = { backgroundImage: `url(${link.favicon})` };
} else if (link.screenshot) {
// Don't show a small favicon if there is no screenshot, because that
// would result in two fallback icons
showSmallFavicon = true;
smallFaviconFallback = true;
class TopSite_TopSiteLink extends external__React__default.a.PureComponent {
constructor(props) {
super(props);
this.onDragEvent = this.onDragEvent.bind(this);
}
/*
* Helper to determine whether the drop zone should allow a drop. We only allow
* dropping top sites for now.
*/
_allowDrop(e, index) {
let draggedIndex = parseInt(e.dataTransfer.getData("text/topsite-index"), 10);
if (!isNaN(draggedIndex) && draggedIndex !== index) {
return true;
}
return false;
}
onDragEvent(event) {
switch (event.type) {
case "dragstart":
event.dataTransfer.effectAllowed = "move";
event.dataTransfer.setData("text/topsite-index", this.props.index);
event.target.blur();
this.props.onDragEvent(event, this.props.index, this.props.link, this.props.title);
break;
case "dragend":
this.props.onDragEvent(event);
break;
case "dragover":
case "dragenter":
case "dragleave":
case "drop":
if (this._allowDrop(event, this.props.index)) {
event.preventDefault();
this.props.onDragEvent(event, this.props.index);
}
break;
}
}
return external__React__default.a.createElement(
"li",
{ className: topSiteOuterClassName, key: link.guid || link.url },
external__React__default.a.createElement(
"a",
{ href: link.url, onClick: props.onClick },
render() {
const { children, className, isDraggable, link, onClick, title } = this.props;
const topSiteOuterClassName = `top-site-outer${className ? ` ${className}` : ""}`;
const { tippyTopIcon, faviconSize } = link;
const letterFallback = title[0];
let imageClassName;
let imageStyle;
let showSmallFavicon = false;
let smallFaviconStyle;
let smallFaviconFallback;
if (tippyTopIcon || faviconSize >= MIN_RICH_FAVICON_SIZE) {
// styles and class names for top sites with rich icons
imageClassName = "top-site-icon rich-icon";
imageStyle = {
backgroundColor: link.backgroundColor,
backgroundImage: `url(${tippyTopIcon || link.favicon})`
};
} else {
// styles and class names for top sites with screenshot + small icon in top left corner
imageClassName = `screenshot${link.screenshot ? " active" : ""}`;
imageStyle = { backgroundImage: link.screenshot ? `url(${link.screenshot})` : "none" };
// only show a favicon in top left if it's greater than 16x16
if (faviconSize >= MIN_CORNER_FAVICON_SIZE) {
showSmallFavicon = true;
smallFaviconStyle = { backgroundImage: `url(${link.favicon})` };
} else if (link.screenshot) {
// Don't show a small favicon if there is no screenshot, because that
// would result in two fallback icons
showSmallFavicon = true;
smallFaviconFallback = true;
}
}
let draggableProps = {};
if (isDraggable) {
draggableProps = {
draggable: true,
onDragStart: this.onDragEvent,
onDragEnd: this.onDragEvent
};
}
return external__React__default.a.createElement(
"li",
_extends({ className: topSiteOuterClassName, key: link.guid || link.url, onDrop: this.onDragEvent, onDragOver: this.onDragEvent, onDragEnter: this.onDragEvent, onDragLeave: this.onDragEvent }, draggableProps),
external__React__default.a.createElement(
"div",
{ className: "tile", "aria-hidden": true, "data-fallback": letterFallback },
external__React__default.a.createElement("div", { className: imageClassName, style: imageStyle }),
showSmallFavicon && external__React__default.a.createElement("div", {
className: "top-site-icon default-icon",
"data-fallback": smallFaviconFallback && letterFallback,
style: smallFaviconStyle })
),
external__React__default.a.createElement(
"div",
{ className: `title ${link.isPinned ? "pinned" : ""}` },
link.isPinned && external__React__default.a.createElement("div", { className: "icon icon-pin-small" }),
{ className: "top-site-inner" },
external__React__default.a.createElement(
"span",
{ dir: "auto" },
title
)
"a",
{ href: link.url, onClick: onClick },
external__React__default.a.createElement(
"div",
{ className: "tile", "aria-hidden": true, "data-fallback": letterFallback },
external__React__default.a.createElement("div", { className: imageClassName, style: imageStyle }),
showSmallFavicon && external__React__default.a.createElement("div", {
className: "top-site-icon default-icon",
"data-fallback": smallFaviconFallback && letterFallback,
style: smallFaviconStyle })
),
external__React__default.a.createElement(
"div",
{ className: `title ${link.isPinned ? "pinned" : ""}` },
link.isPinned && external__React__default.a.createElement("div", { className: "icon icon-pin-small" }),
external__React__default.a.createElement(
"span",
{ dir: "auto" },
title
)
)
),
children
)
),
props.children
);
};
TopSiteLink.defaultProps = {
);
}
}
TopSite_TopSiteLink.defaultProps = {
title: "",
link: {}
link: {},
isDraggable: true
};
class TopSite_TopSite extends external__React__default.a.PureComponent {
@ -2579,6 +2633,7 @@ class TopSite_TopSite extends external__React__default.a.PureComponent {
this.onDismissButtonClick = this.onDismissButtonClick.bind(this);
this.onPinButtonClick = this.onPinButtonClick.bind(this);
this.onEditButtonClick = this.onEditButtonClick.bind(this);
this.onDragEvent = this.onDragEvent.bind(this);
}
toggleContextMenu(event, index) {
this.setState({
@ -2641,14 +2696,23 @@ class TopSite_TopSite extends external__React__default.a.PureComponent {
onEditButtonClick() {
this.props.onEdit(this.props.index);
}
onDragEvent(event, index, link, title) {
if (event.type === "dragstart") {
this.setState({
activeTile: null,
showContextMenu: false
});
}
this.props.onDragEvent(event, index, link, title);
}
render() {
const { props } = this;
const { link } = props;
const isContextMenuOpen = this.state.showContextMenu && this.state.activeTile === props.index;
const title = link.label || link.hostname;
return external__React__default.a.createElement(
TopSiteLink,
_extends({}, props, { onClick: this.onLinkClick, className: isContextMenuOpen ? "active" : "", title: title }),
TopSite_TopSiteLink,
_extends({}, props, { onClick: this.onLinkClick, onDragEvent: this.onDragEvent, className: isContextMenuOpen ? "active" : "", title: title }),
!props.onEdit && external__React__default.a.createElement(
"div",
null,
@ -2689,29 +2753,180 @@ class TopSite_TopSite extends external__React__default.a.PureComponent {
);
}
}
TopSite_TopSite.defaultProps = { link: {} };
const TopSitePlaceholder = () => external__React__default.a.createElement(TopSiteLink, { className: "placeholder" });
const TopSiteList = props => {
const topSites = props.TopSites.rows.slice(0, props.TopSitesCount);
const topSitesUI = [];
for (let i = 0, l = props.TopSitesCount; i < l; i++) {
const link = topSites[i];
topSitesUI.push(!link ? external__React__default.a.createElement(TopSitePlaceholder, { key: i }) : external__React__default.a.createElement(TopSite_TopSite, {
key: link.guid || link.url,
dispatch: props.dispatch,
link: link,
index: i,
intl: props.intl,
onEdit: props.onEdit }));
}
return external__React__default.a.createElement(
"ul",
{ className: "top-sites-list" },
topSitesUI
);
TopSite_TopSite.defaultProps = {
link: {},
onDragStart() {}
};
class TopSite_TopSitePlaceholder extends external__React__default.a.PureComponent {
constructor(props) {
super(props);
this.onEditButtonClick = this.onEditButtonClick.bind(this);
}
onEditButtonClick() {
this.props.dispatch({
type: Actions["actionTypes"].TOP_SITES_EDIT,
data: { index: this.props.index }
});
}
render() {
return external__React__default.a.createElement(
TopSite_TopSiteLink,
_extends({ className: "placeholder", isDraggable: false }, this.props),
external__React__default.a.createElement(
"div",
{ className: "edit-menu" },
external__React__default.a.createElement("button", {
className: "icon icon-edit",
title: this.props.intl.formatMessage({ id: "edit_topsites_edit_button" }),
onClick: this.onEditButtonClick })
)
);
}
}
class TopSite__TopSiteList extends external__React__default.a.PureComponent {
constructor(props) {
super(props);
this.state = this.DEFAULT_STATE = {
draggedIndex: null,
draggedSite: null,
draggedTitle: null,
topSitesPreview: null
};
this.onDragEvent = this.onDragEvent.bind(this);
}
componentWillUpdate(nextProps) {
if (this.state.draggedSite) {
const prevTopSites = this.props.TopSites && this.props.TopSites.rows;
const newTopSites = nextProps.TopSites && nextProps.TopSites.rows;
if (prevTopSites && prevTopSites[this.state.draggedIndex] && prevTopSites[this.state.draggedIndex].url === this.state.draggedSite.url && (!newTopSites[this.state.draggedIndex] || newTopSites[this.state.draggedIndex].url !== this.state.draggedSite.url)) {
// We got the new order from the redux store via props. We can clear state now.
this.setState(this.DEFAULT_STATE);
}
}
}
userEvent(event, index) {
this.props.dispatch(Actions["actionCreators"].UserEvent({
event,
source: TOP_SITES_SOURCE,
action_position: index
}));
}
onDragEvent(event, index, link, title) {
switch (event.type) {
case "dragstart":
this.setState({
draggedIndex: index,
draggedSite: link,
draggedTitle: title
});
this.userEvent("DRAG", index);
break;
case "dragend":
this.setState(this.DEFAULT_STATE);
break;
case "dragenter":
this.setState({ topSitesPreview: this._makeTopSitesPreview(index) });
break;
case "dragleave":
this.setState({ topSitesPreview: null });
break;
case "drop":
this.props.dispatch(Actions["actionCreators"].SendToMain({
type: Actions["actionTypes"].TOP_SITES_INSERT,
data: { site: { url: this.state.draggedSite.url, label: this.state.draggedTitle }, index }
}));
this.userEvent("DROP", index);
break;
default:
break;
}
}
_getTopSites() {
return this.props.TopSites.rows.slice(0, this.props.TopSitesCount);
}
/**
* Make a preview of the topsites that will be the result of dropping the currently
* dragged site at the specified index.
*/
_makeTopSitesPreview(index) {
const preview = this._getTopSites();
this._fillOrLeaveHole(preview, this.state.draggedIndex);
this._insertSite(preview, Object.assign({}, this.state.draggedSite, { isPinned: true }), index);
return preview;
}
/**
* Fill in the slot at the specified index with a non pinned site further down the
* list, if any. Otherwise leave an empty slot.
*/
_fillOrLeaveHole(sites, index) {
let slotIndex = index;
sites[slotIndex] = null;
for (let i = slotIndex + 1; i < sites.length; i++) {
const site = sites[i];
if (site && !site.isPinned) {
sites[i] = null;
sites[slotIndex] = site;
// Update the index to fill to be the spot we just grabbed a site from
slotIndex = i;
}
}
}
/**
* Insert the given site in the slot at the specified index. If the slot is occupied,
* move it appropriately.
*/
_insertSite(sites, site, index) {
const replacedSite = sites[index];
if (replacedSite && index < this.props.TopSitesCount - 1) {
if (replacedSite.isPinned) {
// If the replaced site is pinned, it goes into the next slot no matter what.
this._insertSite(sites, replacedSite, index + 1);
} else {
// If the replaced site isn't pinned, it goes into the next slot that doesn't havea pinned site;
for (let i = index + 1, l = sites.length; i < l; i++) {
if (!sites[i] || !sites[i].isPinned) {
this._insertSite(sites, replacedSite, i);
break;
}
}
}
}
sites[index] = site;
}
render() {
const { props } = this;
const topSites = this.state.topSitesPreview || this._getTopSites();
const topSitesUI = [];
const commonProps = {
onDragEvent: this.onDragEvent,
dispatch: props.dispatch,
intl: props.intl
};
for (let i = 0, l = props.TopSitesCount; i < l; i++) {
const link = topSites[i];
const slotProps = {
key: i,
index: i
};
topSitesUI.push(!link ? external__React__default.a.createElement(TopSite_TopSitePlaceholder, _extends({}, slotProps, commonProps)) : external__React__default.a.createElement(TopSite_TopSite, _extends({
link: link,
onEdit: props.onEdit
}, slotProps, commonProps)));
}
return external__React__default.a.createElement(
"ul",
{ className: "top-sites-list" },
topSitesUI
);
}
}
const TopSiteList = Object(external__ReactIntl_["injectIntl"])(TopSite__TopSiteList);
// CONCATENATED MODULE: ./system-addon/content-src/components/TopSites/TopSiteForm.jsx
@ -2753,7 +2968,7 @@ class TopSiteForm_TopSiteForm extends external__React__default.a.PureComponent {
site.label = this.state.label;
}
this.props.dispatch(Actions["actionCreators"].SendToMain({
type: Actions["actionTypes"].TOP_SITES_ADD,
type: Actions["actionTypes"].TOP_SITES_INSERT,
data: { site }
}));
this.props.dispatch(Actions["actionCreators"].UserEvent({
@ -2956,12 +3171,13 @@ class TopSitesEdit__TopSitesEdit extends external__React__default.a.PureComponen
}));
}
render() {
const showEditForm = this.props.TopSites.editForm && this.props.TopSites.editForm.visible || this.state.showEditModal && this.state.showEditForm;
const { editForm } = this.props.TopSites;
const showEditForm = editForm && editForm.visible || this.state.showEditModal && this.state.showEditForm;
let editIndex = this.state.editIndex;
if (showEditForm && this.props.TopSites.editForm.visible) {
const targetURL = this.props.TopSites.editForm.site.url;
editIndex = this.props.TopSites.rows.findIndex(s => s.url === targetURL);
if (editIndex < 0 && editForm) {
editIndex = editForm.index;
}
const editSite = this.props.TopSites.rows[editIndex] || {};
return external__React__default.a.createElement(
"div",
{ className: "edit-topsites-wrapper" },
@ -3038,8 +3254,8 @@ class TopSitesEdit__TopSitesEdit extends external__React__default.a.PureComponen
"div",
{ className: "modal" },
external__React__default.a.createElement(TopSiteForm_TopSiteForm, {
label: this.props.TopSites.rows[editIndex].label || this.props.TopSites.rows[editIndex].hostname,
url: this.props.TopSites.rows[editIndex].url,
label: editSite.label || editSite.hostname || "",
url: editSite.url || "",
index: editIndex,
editMode: true,
onClose: this.onFormClose,
@ -3341,7 +3557,7 @@ var PrerenderData = new _PrerenderData({
"migrationExpired": true,
"showTopSites": true,
"showSearch": true,
"topSitesCount": 6,
"topSitesCount": 12,
"collapseTopSites": false,
"section.highlights.collapsed": false,
"section.topstories.collapsed": false,
@ -3354,7 +3570,7 @@ var PrerenderData = new _PrerenderData({
// too different for the prerendered version to be used. Unfortunately, this
// will result in users who have modified some of their preferences not being
// able to get the benefits of prerendering.
validation: ["showTopSites", "showSearch", "collapseTopSites", "section.highlights.collapsed", "section.topstories.collapsed",
validation: ["showTopSites", "showSearch", "topSitesCount", "collapseTopSites", "section.highlights.collapsed", "section.topstories.collapsed",
// This means if either of these are set to their default values,
// prerendering can be used.
{ oneOf: ["feeds.section.topstories", "feeds.section.highlights"] }],

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

@ -8,7 +8,7 @@
<em:type>2</em:type>
<em:bootstrap>true</em:bootstrap>
<em:unpack>false</em:unpack>
<em:version>2017.12.20.1328-00d79b97</em:version>
<em:version>2017.12.22.0055-8fe1055e</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>

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

@ -108,7 +108,7 @@ const PREFS_CONFIG = new Map([
}],
["topSitesCount", {
title: "Number of Top Sites to display",
value: 6
value: 12
}],
["telemetry", {
title: "Enable system error and usage data collection",

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

@ -241,6 +241,13 @@ this.TopSitesFeed = class TopSitesFeed {
* Insert a site to pin at a position shifting over any other pinned sites.
*/
_insertPin(site, index) {
// Don't insert any pins past the end of the visible top sites. Otherwise,
// we can end up with a bunch of pinned sites that can never be unpinned again
// from the UI.
if (index >= this.store.getState().Prefs.values.topSitesCount) {
return;
}
// For existing sites, recursively push it and others to the next positions
let pinned = NewTabUtils.pinnedLinks.links;
if (pinned.length > index && pinned[index]) {
@ -250,12 +257,12 @@ this.TopSitesFeed = class TopSitesFeed {
}
/**
* Handle an add action of a site.
* Handle an insert (drop/add) action of a site.
*/
add(action) {
// Adding a top site pins it in the first slot, pushing over any link already
// pinned in the slot.
this._insertPin(action.data.site, 0);
insert(action) {
// Inserting a top site pins it in the specified slot, pushing over any link already
// pinned in the slot (unless it's the last slot, then it replaces).
this._insertPin(action.data.site, action.data.index || 0);
this._broadcastPinnedSitesUpdated();
}
@ -293,8 +300,8 @@ this.TopSitesFeed = class TopSitesFeed {
case at.TOP_SITES_UNPIN:
this.unpin(action);
break;
case at.TOP_SITES_ADD:
this.add(action);
case at.TOP_SITES_INSERT:
this.insert(action);
break;
case at.UNINIT:
this.uninit();

Различия файлов скрыты, потому что одна или несколько строк слишком длинны

Различия файлов скрыты, потому что одна или несколько строк слишком длинны

Различия файлов скрыты, потому что одна или несколько строк слишком длинны

Различия файлов скрыты, потому что одна или несколько строк слишком длинны

Различия файлов скрыты, потому что одна или несколько строк слишком длинны

Различия файлов скрыты, потому что одна или несколько строк слишком длинны

Различия файлов скрыты, потому что одна или несколько строк слишком длинны

Различия файлов скрыты, потому что одна или несколько строк слишком длинны

Различия файлов скрыты, потому что одна или несколько строк слишком длинны

Различия файлов скрыты, потому что одна или несколько строк слишком длинны

Различия файлов скрыты, потому что одна или несколько строк слишком длинны

Различия файлов скрыты, потому что одна или несколько строк слишком длинны

Различия файлов скрыты, потому что одна или несколько строк слишком длинны

Различия файлов скрыты, потому что одна или несколько строк слишком длинны

Различия файлов скрыты, потому что одна или несколько строк слишком длинны

Различия файлов скрыты, потому что одна или несколько строк слишком длинны

Различия файлов скрыты, потому что одна или несколько строк слишком длинны

Различия файлов скрыты, потому что одна или несколько строк слишком длинны

Различия файлов скрыты, потому что одна или несколько строк слишком длинны

Различия файлов скрыты, потому что одна или несколько строк слишком длинны

Различия файлов скрыты, потому что одна или несколько строк слишком длинны

Различия файлов скрыты, потому что одна или несколько строк слишком длинны

Различия файлов скрыты, потому что одна или несколько строк слишком длинны

Различия файлов скрыты, потому что одна или несколько строк слишком длинны

Различия файлов скрыты, потому что одна или несколько строк слишком длинны

Различия файлов скрыты, потому что одна или несколько строк слишком длинны

Различия файлов скрыты, потому что одна или несколько строк слишком длинны

Различия файлов скрыты, потому что одна или несколько строк слишком длинны

Различия файлов скрыты, потому что одна или несколько строк слишком длинны

Различия файлов скрыты, потому что одна или несколько строк слишком длинны

Различия файлов скрыты, потому что одна или несколько строк слишком длинны

Различия файлов скрыты, потому что одна или несколько строк слишком длинны

Различия файлов скрыты, потому что одна или несколько строк слишком длинны

Различия файлов скрыты, потому что одна или несколько строк слишком длинны

Различия файлов скрыты, потому что одна или несколько строк слишком длинны

Различия файлов скрыты, потому что одна или несколько строк слишком длинны

Различия файлов скрыты, потому что одна или несколько строк слишком длинны

Различия файлов скрыты, потому что одна или несколько строк слишком длинны

Различия файлов скрыты, потому что одна или несколько строк слишком длинны

Различия файлов скрыты, потому что одна или несколько строк слишком длинны

Различия файлов скрыты, потому что одна или несколько строк слишком длинны

Различия файлов скрыты, потому что одна или несколько строк слишком длинны

Различия файлов скрыты, потому что одна или несколько строк слишком длинны

Различия файлов скрыты, потому что одна или несколько строк слишком длинны

Различия файлов скрыты, потому что одна или несколько строк слишком длинны

Различия файлов скрыты, потому что одна или несколько строк слишком длинны

Различия файлов скрыты, потому что одна или несколько строк слишком длинны

Различия файлов скрыты, потому что одна или несколько строк слишком длинны

Различия файлов скрыты, потому что одна или несколько строк слишком длинны

Различия файлов скрыты, потому что одна или несколько строк слишком длинны

Различия файлов скрыты, потому что одна или несколько строк слишком длинны

Различия файлов скрыты, потому что одна или несколько строк слишком длинны

Различия файлов скрыты, потому что одна или несколько строк слишком длинны

Различия файлов скрыты, потому что одна или несколько строк слишком длинны

Различия файлов скрыты, потому что одна или несколько строк слишком длинны

Различия файлов скрыты, потому что одна или несколько строк слишком длинны

Различия файлов скрыты, потому что одна или несколько строк слишком длинны

Различия файлов скрыты, потому что одна или несколько строк слишком длинны

Различия файлов скрыты, потому что одна или несколько строк слишком длинны

Различия файлов скрыты, потому что одна или несколько строк слишком длинны

Различия файлов скрыты, потому что одна или несколько строк слишком длинны

Различия файлов скрыты, потому что одна или несколько строк слишком длинны

Различия файлов скрыты, потому что одна или несколько строк слишком длинны

Различия файлов скрыты, потому что одна или несколько строк слишком длинны

Различия файлов скрыты, потому что одна или несколько строк слишком длинны

Различия файлов скрыты, потому что одна или несколько строк слишком длинны

Различия файлов скрыты, потому что одна или несколько строк слишком длинны

Различия файлов скрыты, потому что одна или несколько строк слишком длинны

Различия файлов скрыты, потому что одна или несколько строк слишком длинны

Различия файлов скрыты, потому что одна или несколько строк слишком длинны

Различия файлов скрыты, потому что одна или несколько строк слишком длинны

Различия файлов скрыты, потому что одна или несколько строк слишком длинны

Различия файлов скрыты, потому что одна или несколько строк слишком длинны

Различия файлов скрыты, потому что одна или несколько строк слишком длинны

Различия файлов скрыты, потому что одна или несколько строк слишком длинны

Различия файлов скрыты, потому что одна или несколько строк слишком длинны

Различия файлов скрыты, потому что одна или несколько строк слишком длинны

Различия файлов скрыты, потому что одна или несколько строк слишком длинны

Различия файлов скрыты, потому что одна или несколько строк слишком длинны

Различия файлов скрыты, потому что одна или несколько строк слишком длинны

Различия файлов скрыты, потому что одна или несколько строк слишком длинны

Различия файлов скрыты, потому что одна или несколько строк слишком длинны

Различия файлов скрыты, потому что одна или несколько строк слишком длинны

Различия файлов скрыты, потому что одна или несколько строк слишком длинны

Различия файлов скрыты, потому что одна или несколько строк слишком длинны

Различия файлов скрыты, потому что одна или несколько строк слишком длинны

Различия файлов скрыты, потому что одна или несколько строк слишком длинны

Различия файлов скрыты, потому что одна или несколько строк слишком длинны

Различия файлов скрыты, потому что одна или несколько строк слишком длинны

Различия файлов скрыты, потому что одна или несколько строк слишком длинны

Некоторые файлы не были показаны из-за слишком большого количества измененных файлов Показать больше