diff options
author | jochen@chromium.org <jochen@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2010-03-05 08:15:53 +0000 |
---|---|---|
committer | jochen@chromium.org <jochen@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2010-03-05 08:15:53 +0000 |
commit | 21f4d251210a4408da8a3279510ef7eb44cc1e1a (patch) | |
tree | a235ca4b807641d671b244fc78ed9bb64205b4ac | |
parent | 0c86dbf56c6f3e82ee748f34dca48aedf962dec2 (diff) | |
download | chromium_src-21f4d251210a4408da8a3279510ef7eb44cc1e1a.zip chromium_src-21f4d251210a4408da8a3279510ef7eb44cc1e1a.tar.gz chromium_src-21f4d251210a4408da8a3279510ef7eb44cc1e1a.tar.bz2 |
Implement edit mode for history page.
BUG=35338
TEST=none
Review URL: http://codereview.chromium.org/660283
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@40722 0039d316-1c4b-4281-b951-d872f2087c98
-rw-r--r-- | chrome/app/generated_resources.grd | 13 | ||||
-rw-r--r-- | chrome/browser/browsing_data_remover.cc | 6 | ||||
-rw-r--r-- | chrome/browser/dom_ui/history_ui.cc | 96 | ||||
-rw-r--r-- | chrome/browser/dom_ui/history_ui.h | 22 | ||||
-rw-r--r-- | chrome/browser/extensions/extension_history_api.cc | 6 | ||||
-rw-r--r-- | chrome/browser/history/expire_history_backend.cc | 28 | ||||
-rw-r--r-- | chrome/browser/history/expire_history_backend.h | 8 | ||||
-rw-r--r-- | chrome/browser/history/expire_history_backend_unittest.cc | 65 | ||||
-rw-r--r-- | chrome/browser/history/history.cc | 7 | ||||
-rw-r--r-- | chrome/browser/history/history.h | 7 | ||||
-rw-r--r-- | chrome/browser/history/history_backend.cc | 9 | ||||
-rw-r--r-- | chrome/browser/history/history_backend.h | 3 | ||||
-rw-r--r-- | chrome/browser/history/history_backend_unittest.cc | 6 | ||||
-rw-r--r-- | chrome/browser/history/text_database_manager.cc | 25 | ||||
-rw-r--r-- | chrome/browser/history/text_database_manager.h | 12 | ||||
-rw-r--r-- | chrome/browser/resources/history.html | 335 |
16 files changed, 496 insertions, 152 deletions
diff --git a/chrome/app/generated_resources.grd b/chrome/app/generated_resources.grd index 2768701..0b0de03 100644 --- a/chrome/app/generated_resources.grd +++ b/chrome/app/generated_resources.grd @@ -222,8 +222,17 @@ each locale. --> $1<ex>pie</ex> </ph>' </message> - <message name="IDS_HISTORY_DELETE_PRIOR_VISITS_LINK" desc="Title of the link that allows the user to delete visits prior to the specified point"> - Delete history for this day + <message name="IDS_HISTORY_START_EDITING_HISTORY" desc="Title of the link that allows the user to enable controls for deleting single history items"> + Edit items... + </message> + <message name="IDS_HISTORY_STOP_EDITING_HISTORY" desc="Title of the link that allows the user to disable controls for deleting single history items"> + Done removing items + </message> + <message name="IDS_HISTORY_REMOVE_SELECTED_ITEMS" desc="Title of the button that allows the user to remove the selected history items"> + Remove selected items + </message> + <message name="IDS_HISTORY_OPEN_CLEAR_BROWSING_DATA_DIALOG" desc="Title of the button that will open the clear browsing data dialog."> + Clear all browsing data... </message> <message name="IDS_HISTORY_DELETE_PRIOR_VISITS_WARNING" desc="Warning shown before deleting"> Are you sure you want to delete these pages from your history? diff --git a/chrome/browser/browsing_data_remover.cc b/chrome/browser/browsing_data_remover.cc index 435c3d7..b4468c7 100644 --- a/chrome/browser/browsing_data_remover.cc +++ b/chrome/browser/browsing_data_remover.cc @@ -1,4 +1,4 @@ -// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved. +// Copyright (c) 2010 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. @@ -78,9 +78,11 @@ void BrowsingDataRemover::Remove(int remove_mask) { HistoryService* history_service = profile_->GetHistoryService(Profile::EXPLICIT_ACCESS); if (history_service) { + std::set<GURL> restrict_urls; UserMetrics::RecordAction("ClearBrowsingData_History", profile_); waiting_for_clear_history_ = true; - history_service->ExpireHistoryBetween(delete_begin_, delete_end_, + history_service->ExpireHistoryBetween(restrict_urls, + delete_begin_, delete_end_, &request_consumer_, NewCallback(this, &BrowsingDataRemover::OnHistoryDeletionDone)); } diff --git a/chrome/browser/dom_ui/history_ui.cc b/chrome/browser/dom_ui/history_ui.cc index 0019059..d81bbc1 100644 --- a/chrome/browser/dom_ui/history_ui.cc +++ b/chrome/browser/dom_ui/history_ui.cc @@ -1,4 +1,4 @@ -// Copyright (c) 2009 The Chromium Authors. All rights reserved. +// Copyright (c) 2010 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. @@ -16,11 +16,14 @@ #include "base/time.h" #include "base/values.h" #include "chrome/browser/bookmarks/bookmark_model.h" +#include "chrome/browser/browser.h" #include "chrome/browser/chrome_thread.h" #include "chrome/browser/dom_ui/dom_ui_favicon_source.h" #include "chrome/browser/metrics/user_metrics.h" #include "chrome/browser/history/history_types.h" #include "chrome/browser/profile.h" +#include "chrome/browser/tab_contents/tab_contents.h" +#include "chrome/browser/tab_contents/tab_contents_delegate.h" #include "chrome/common/jstemplate_builder.h" #include "chrome/common/notification_service.h" #include "chrome/common/time_format.h" @@ -74,9 +77,15 @@ void HistoryUIHTMLSource::StartDataRequest(const std::string& path, l10n_util::GetString(IDS_HISTORY_NO_RESULTS)); localized_strings.SetString(L"noitems", l10n_util::GetString(IDS_HISTORY_NO_ITEMS)); - localized_strings.SetString(L"deleteday", - l10n_util::GetString(IDS_HISTORY_DELETE_PRIOR_VISITS_LINK)); - localized_strings.SetString(L"deletedaywarning", + localized_strings.SetString(L"edithistory", + l10n_util::GetString(IDS_HISTORY_START_EDITING_HISTORY)); + localized_strings.SetString(L"doneediting", + l10n_util::GetString(IDS_HISTORY_STOP_EDITING_HISTORY)); + localized_strings.SetString(L"removeselected", + l10n_util::GetString(IDS_HISTORY_REMOVE_SELECTED_ITEMS)); + localized_strings.SetString(L"clearallhistory", + l10n_util::GetString(IDS_HISTORY_OPEN_CLEAR_BROWSING_DATA_DIALOG)); + localized_strings.SetString(L"deletewarning", l10n_util::GetString(IDS_HISTORY_DELETE_PRIOR_VISITS_WARNING)); SetFontAndTextDirection(&localized_strings); @@ -100,14 +109,11 @@ void HistoryUIHTMLSource::StartDataRequest(const std::string& path, // //////////////////////////////////////////////////////////////////////////////// BrowsingHistoryHandler::BrowsingHistoryHandler() - : search_text_(), - remover_(NULL) { + : search_text_() { } BrowsingHistoryHandler::~BrowsingHistoryHandler() { cancelable_consumer_.CancelAllRequests(); - if (remover_) - remover_->RemoveObserver(this); } DOMMessageHandler* BrowsingHistoryHandler::Attach(DOMUI* dom_ui) { @@ -130,8 +136,10 @@ void BrowsingHistoryHandler::RegisterMessages() { NewCallback(this, &BrowsingHistoryHandler::HandleGetHistory)); dom_ui_->RegisterMessageCallback("searchHistory", NewCallback(this, &BrowsingHistoryHandler::HandleSearchHistory)); - dom_ui_->RegisterMessageCallback("deleteDay", - NewCallback(this, &BrowsingHistoryHandler::HandleDeleteDay)); + dom_ui_->RegisterMessageCallback("removeURLsOnOneDay", + NewCallback(this, &BrowsingHistoryHandler::HandleRemoveURLsOnOneDay)); + dom_ui_->RegisterMessageCallback("clearBrowsingData", + NewCallback(this, &BrowsingHistoryHandler::HandleClearBrowsingData)); } void BrowsingHistoryHandler::HandleGetHistory(const Value* value) { @@ -185,37 +193,50 @@ void BrowsingHistoryHandler::HandleSearchHistory(const Value* value) { NewCallback(this, &BrowsingHistoryHandler::QueryComplete)); } -void BrowsingHistoryHandler::HandleDeleteDay(const Value* value) { - if (BrowsingDataRemover::is_removing()) { +void BrowsingHistoryHandler::HandleRemoveURLsOnOneDay(const Value* value) { + if (cancelable_consumer_.HasPendingRequests()) { dom_ui_->CallJavascriptFunction(L"deleteFailed"); return; } - // Anything in-flight is invalid. - cancelable_consumer_.CancelAllRequests(); + DCHECK(value && value->GetType() == Value::TYPE_LIST); - // Get time. - base::Time time; - bool success = base::Time::FromString(ExtractStringValue(value).c_str(), - &time); - DCHECK(success); - - base::Time begin_time = time.LocalMidnight(); + // Get day to delete data from. + int visit_time = 0; + ExtractIntegerValue(value, &visit_time); + base::Time::Exploded exploded; + base::Time::FromTimeT( + static_cast<time_t>(visit_time)).LocalExplode(&exploded); + exploded.hour = exploded.minute = exploded.second = exploded.millisecond = 0; + base::Time begin_time = base::Time::FromLocalExploded(exploded); base::Time end_time = begin_time + base::TimeDelta::FromDays(1); - remover_ = new BrowsingDataRemover(dom_ui_->GetProfile(), - begin_time, - end_time); - remover_->AddObserver(this); - remover_->Remove(BrowsingDataRemover::REMOVE_HISTORY | - BrowsingDataRemover::REMOVE_CACHE); + // Get URLs. + std::set<GURL> urls; + const ListValue* list_value = static_cast<const ListValue*>(value); + for (ListValue::const_iterator v = list_value->begin() + 1; + v != list_value->end(); ++v) { + if ((*v)->GetType() != Value::TYPE_STRING) + continue; + const StringValue* string_value = static_cast<const StringValue*>(*v); + string16 string16_value; + if (!string_value->GetAsUTF16(&string16_value)) + continue; + urls.insert(GURL(string16_value)); + } + + HistoryService* hs = + dom_ui_->GetProfile()->GetHistoryService(Profile::EXPLICIT_ACCESS); + hs->ExpireHistoryBetween(urls, begin_time, end_time, &cancelable_consumer_, + NewCallback(this, &BrowsingHistoryHandler::RemoveComplete)); } -void BrowsingHistoryHandler::OnBrowsingDataRemoverDone() { - dom_ui_->CallJavascriptFunction(L"deleteComplete"); - // No need to remove ourselves as an observer as BrowsingDataRemover deletes - // itself after we return. - remover_ = NULL; +void BrowsingHistoryHandler::HandleClearBrowsingData(const Value* value) { + // Anything in-flight is invalid. + cancelable_consumer_.CancelAllRequests(); + + dom_ui_->tab_contents()->delegate()->GetBrowser()-> + OpenClearBrowsingDataDialog(); } void BrowsingHistoryHandler::QueryComplete( @@ -271,6 +292,11 @@ void BrowsingHistoryHandler::QueryComplete( dom_ui_->CallJavascriptFunction(L"historyResult", info_value, results_value); } +void BrowsingHistoryHandler::RemoveComplete() { + // Some Visits were deleted from history. Reload the list. + dom_ui_->CallJavascriptFunction(L"deleteComplete"); +} + void BrowsingHistoryHandler::ExtractSearchHistoryArguments(const Value* value, int* month, std::wstring* query) { *month = 0; @@ -292,9 +318,9 @@ void BrowsingHistoryHandler::ExtractSearchHistoryArguments(const Value* value, list_member->GetType() == Value::TYPE_STRING) { const StringValue* string_value = static_cast<const StringValue*>(list_member); - std::wstring wstring_value; - string_value->GetAsString(&wstring_value); - *month = StringToInt(WideToUTF16Hack(wstring_value)); + string16 string16_value; + string_value->GetAsUTF16(&string16_value); + *month = StringToInt(string16_value); } } } diff --git a/chrome/browser/dom_ui/history_ui.h b/chrome/browser/dom_ui/history_ui.h index c1d4262..a8fceb3 100644 --- a/chrome/browser/dom_ui/history_ui.h +++ b/chrome/browser/dom_ui/history_ui.h @@ -1,4 +1,4 @@ -// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved. +// Copyright (c) 2010 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. @@ -8,7 +8,6 @@ #include <string> #include <vector> -#include "chrome/browser/browsing_data_remover.h" #include "chrome/browser/dom_ui/chrome_url_data_manager.h" #include "chrome/browser/dom_ui/dom_ui.h" #include "chrome/browser/cancelable_request.h" @@ -38,8 +37,7 @@ class HistoryUIHTMLSource : public ChromeURLDataManager::DataSource { // The handler for Javascript messages related to the "history" view. class BrowsingHistoryHandler : public DOMMessageHandler, - public NotificationObserver, - public BrowsingDataRemover::Observer { + public NotificationObserver { public: BrowsingHistoryHandler(); virtual ~BrowsingHistoryHandler(); @@ -54,22 +52,25 @@ class BrowsingHistoryHandler : public DOMMessageHandler, // Callback for the "searchHistory" message. void HandleSearchHistory(const Value* value); - // Callback for the "deleteDay" message. - void HandleDeleteDay(const Value* value); + // Callback for the "removeURLsOnOneDay" message. + void HandleRemoveURLsOnOneDay(const Value* value); + + // Handle for "clearBrowsingData" message. + void HandleClearBrowsingData(const Value* value); // NotificationObserver implementation. virtual void Observe(NotificationType type, const NotificationSource& source, const NotificationDetails& details); - // BrowsingDataRemover observer implementation. - void OnBrowsingDataRemoverDone(); - private: // Callback from the history system when the history list is available. void QueryComplete(HistoryService::Handle request_handle, history::QueryResults* results); + // Callback from the history system when visits were deleted. + void RemoveComplete(); + // Extract the arguments from the call to HandleSearchHistory. void ExtractSearchHistoryArguments(const Value* value, int* month, @@ -83,9 +84,6 @@ class BrowsingHistoryHandler : public DOMMessageHandler, // Current search text. std::wstring search_text_; - // Browsing history remover - BrowsingDataRemover* remover_; - // Our consumer for the history service. CancelableRequestConsumerT<int, 0> cancelable_consumer_; diff --git a/chrome/browser/extensions/extension_history_api.cc b/chrome/browser/extensions/extension_history_api.cc index 75ed48a..af45f82 100644 --- a/chrome/browser/extensions/extension_history_api.cc +++ b/chrome/browser/extensions/extension_history_api.cc @@ -1,4 +1,4 @@ -// Copyright (c) 2009 The Chromium Authors. All rights reserved. +// Copyright (c) 2010 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. @@ -336,8 +336,10 @@ bool DeleteRangeHistoryFunction::RunAsyncImpl() { base::Time end_time; EXTENSION_FUNCTION_VALIDATE(GetTimeFromValue(value, &end_time)); + std::set<GURL> restrict_urls; HistoryService* hs = profile()->GetHistoryService(Profile::EXPLICIT_ACCESS); hs->ExpireHistoryBetween( + restrict_urls, begin_time, end_time, &cancelable_consumer_, @@ -351,8 +353,10 @@ void DeleteRangeHistoryFunction::DeleteComplete() { } bool DeleteAllHistoryFunction::RunAsyncImpl() { + std::set<GURL> restrict_urls; HistoryService* hs = profile()->GetHistoryService(Profile::EXPLICIT_ACCESS); hs->ExpireHistoryBetween( + restrict_urls, base::Time::FromDoubleT(0), // From the beginning of the epoch. base::Time::Now(), // To the current time. &cancelable_consumer_, diff --git a/chrome/browser/history/expire_history_backend.cc b/chrome/browser/history/expire_history_backend.cc index 882a53c..2a01212 100644 --- a/chrome/browser/history/expire_history_backend.cc +++ b/chrome/browser/history/expire_history_backend.cc @@ -1,4 +1,4 @@ -// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved. +// Copyright (c) 2010 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. @@ -212,19 +212,32 @@ void ExpireHistoryBackend::DeleteURL(const GURL& url) { BroadcastDeleteNotifications(&dependencies); } -void ExpireHistoryBackend::ExpireHistoryBetween(Time begin_time, - Time end_time) { +void ExpireHistoryBackend::ExpireHistoryBetween( + const std::set<GURL>& restrict_urls, Time begin_time, Time end_time) { if (!main_db_) return; // There may be stuff in the text database manager's temporary cache. if (text_db_) - text_db_->DeleteFromUncommitted(begin_time, end_time); + text_db_->DeleteFromUncommitted(restrict_urls, begin_time, end_time); // Find the affected visits and delete them. // TODO(brettw): bug 1171164: We should query the archived database here, too. VisitVector visits; main_db_->GetAllVisitsInRange(begin_time, end_time, 0, &visits); + if (!restrict_urls.empty()) { + std::set<URLID> url_ids; + for (std::set<GURL>::const_iterator url = restrict_urls.begin(); + url != restrict_urls.end(); ++url) + url_ids.insert(main_db_->GetRowForURL(*url, NULL)); + VisitVector all_visits; + all_visits.swap(visits); + for (VisitVector::iterator visit = all_visits.begin(); + visit != all_visits.end(); ++visit) { + if (url_ids.find(visit->url_id) != url_ids.end()) + visits.push_back(*visit); + } + } if (visits.empty()) return; @@ -375,8 +388,11 @@ void ExpireHistoryBackend::DeleteOneURL( main_db_->DeleteSegmentForURL(url_row.id()); // The URL may be in the text database manager's temporary cache. - if (text_db_) - text_db_->DeleteURLFromUncommitted(url_row.url()); + if (text_db_) { + std::set<GURL> restrict_urls; + restrict_urls.insert(url_row.url()); + text_db_->DeleteFromUncommitted(restrict_urls, base::Time(), base::Time()); + } if (!is_bookmarked) { dependencies->deleted_urls.push_back(url_row); diff --git a/chrome/browser/history/expire_history_backend.h b/chrome/browser/history/expire_history_backend.h index 5b7525c..11c0416 100644 --- a/chrome/browser/history/expire_history_backend.h +++ b/chrome/browser/history/expire_history_backend.h @@ -1,4 +1,4 @@ -// Copyright (c) 2009 The Chromium Authors. All rights reserved. +// Copyright (c) 2010 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. @@ -82,8 +82,10 @@ class ExpireHistoryBackend { // Deletes everything associated with a URL. void DeleteURL(const GURL& url); - // Removes all visits in the given time range, updating the URLs accordingly. - void ExpireHistoryBetween(base::Time begin_time, base::Time end_time); + // Removes all visits to restrict_urls (or all URLs if empty) in the given + // time range, updating the URLs accordingly, + void ExpireHistoryBetween(const std::set<GURL>& restrict_urls, + base::Time begin_time, base::Time end_time); // Archives all visits before and including the given time, updating the URLs // accordingly. This function is intended for migrating old databases diff --git a/chrome/browser/history/expire_history_backend_unittest.cc b/chrome/browser/history/expire_history_backend_unittest.cc index 03c4eef..2cdce64 100644 --- a/chrome/browser/history/expire_history_backend_unittest.cc +++ b/chrome/browser/history/expire_history_backend_unittest.cc @@ -1,4 +1,4 @@ -// Copyright (c) 2009 The Chromium Authors. All rights reserved. +// Copyright (c) 2010 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. @@ -529,7 +529,8 @@ TEST_F(ExpireHistoryTest, FlushRecentURLsUnstarred) { visits[0].visit_time); // This should delete the last two visits. - expirer_.ExpireHistoryBetween(visit_times[2], Time()); + std::set<GURL> restrict_urls; + expirer_.ExpireHistoryBetween(restrict_urls, visit_times[2], Time()); // Run the text database expirer. This will flush any pending entries so we // can check that nothing was committed. We use a time far in the future so @@ -562,6 +563,63 @@ TEST_F(ExpireHistoryTest, FlushRecentURLsUnstarred) { EXPECT_FALSE(HasFavIcon(url_row2.favicon_id())); } +// Expires only a specific URLs more recent than a given time, with no starred +// items. Our time threshold is such that the URL should be updated (we delete +// one of the two visits). +TEST_F(ExpireHistoryTest, FlushRecentURLsUnstarredRestricted) { + URLID url_ids[3]; + Time visit_times[4]; + AddExampleData(url_ids, visit_times); + + URLRow url_row1, url_row2; + ASSERT_TRUE(main_db_->GetURLRow(url_ids[1], &url_row1)); + ASSERT_TRUE(main_db_->GetURLRow(url_ids[2], &url_row2)); + + // In this test we also make sure that any pending entries in the text + // database manager are removed. + VisitVector visits; + main_db_->GetVisitsForURL(url_ids[2], &visits); + ASSERT_EQ(1U, visits.size()); + text_db_->AddPageURL(url_row2.url(), url_row2.id(), visits[0].visit_id, + visits[0].visit_time); + + // This should delete the last two visits. + std::set<GURL> restrict_urls; + restrict_urls.insert(url_row1.url()); + expirer_.ExpireHistoryBetween(restrict_urls, visit_times[2], Time()); + + // Run the text database expirer. This will flush any pending entries so we + // can check that nothing was committed. We use a time far in the future so + // that anything added recently will get flushed. + TimeTicks expiration_time = TimeTicks::Now() + TimeDelta::FromDays(1); + text_db_->FlushOldChangesForTime(expiration_time); + + // Verify that the middle URL had its last visit deleted only. + visits.clear(); + main_db_->GetVisitsForURL(url_ids[1], &visits); + EXPECT_EQ(1U, visits.size()); + EXPECT_EQ(0, CountTextMatchesForURL(url_row1.url())); + + // Verify that the middle URL visit time and visit counts were updated. + URLRow temp_row; + ASSERT_TRUE(main_db_->GetURLRow(url_ids[1], &temp_row)); + EXPECT_TRUE(visit_times[2] == url_row1.last_visit()); // Previous value. + EXPECT_TRUE(visit_times[1] == temp_row.last_visit()); // New value. + EXPECT_EQ(2, url_row1.visit_count()); + EXPECT_EQ(1, temp_row.visit_count()); + EXPECT_EQ(1, url_row1.typed_count()); + EXPECT_EQ(0, temp_row.typed_count()); + + // Verify that the middle URL's favicon and thumbnail is still there. + EXPECT_TRUE(HasFavIcon(url_row1.favicon_id())); + EXPECT_TRUE(HasThumbnail(url_row1.id())); + + // Verify that the last URL was not touched. + EXPECT_TRUE(main_db_->GetURLRow(url_ids[2], &temp_row)); + EXPECT_TRUE(HasFavIcon(url_row2.favicon_id())); + EXPECT_TRUE(HasThumbnail(url_row2.id())); +} + // Expire a starred URL, it shouldn't get deleted TEST_F(ExpireHistoryTest, FlushRecentURLsStarred) { URLID url_ids[3]; @@ -577,7 +635,8 @@ TEST_F(ExpireHistoryTest, FlushRecentURLsStarred) { StarURL(url_row2.url()); // This should delete the last two visits. - expirer_.ExpireHistoryBetween(visit_times[2], Time()); + std::set<GURL> restrict_urls; + expirer_.ExpireHistoryBetween(restrict_urls, visit_times[2], Time()); // The URL rows should still exist. URLRow new_url_row1, new_url_row2; diff --git a/chrome/browser/history/history.cc b/chrome/browser/history/history.cc index 71d48f0..53ad780 100644 --- a/chrome/browser/history/history.cc +++ b/chrome/browser/history/history.cc @@ -1,4 +1,4 @@ -// Copyright (c) 2009 The Chromium Authors. All rights reserved. +// Copyright (c) 2010 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. @@ -664,14 +664,15 @@ void HistoryService::DeleteURL(const GURL& url) { } void HistoryService::ExpireHistoryBetween( + const std::set<GURL>& restrict_urls, Time begin_time, Time end_time, CancelableRequestConsumerBase* consumer, ExpireHistoryCallback* callback) { // We will update the visited links when we observe the delete notifications. Schedule(PRIORITY_UI, &HistoryBackend::ExpireHistoryBetween, consumer, - new history::ExpireHistoryRequest(callback), - begin_time, end_time); + new history::ExpireHistoryRequest(callback), + restrict_urls, begin_time, end_time); } void HistoryService::BroadcastNotifications( diff --git a/chrome/browser/history/history.h b/chrome/browser/history/history.h index 5d251c0..8f7c68c 100644 --- a/chrome/browser/history/history.h +++ b/chrome/browser/history/history.h @@ -359,7 +359,7 @@ class HistoryService : public CancelableRequestProvider, // Delete all the information related to a single url. void DeleteURL(const GURL& url); - // Implemented by the caller of 'ExpireHistory(Since|Between)' below, and + // Implemented by the caller of ExpireHistoryBetween, and // is called when the history service has deleted the history. typedef Callback0::Type ExpireHistoryCallback; @@ -369,7 +369,10 @@ class HistoryService : public CancelableRequestProvider, // if they are no longer referenced. |callback| runs when the expiration is // complete. You may use null Time values to do an unbounded delete in // either direction. - void ExpireHistoryBetween(base::Time begin_time, base::Time end_time, + // If |restrict_urls| is not empty, only visits to the URLs in this set are + // removed. + void ExpireHistoryBetween(const std::set<GURL>& restrict_urls, + base::Time begin_time, base::Time end_time, CancelableRequestConsumerBase* consumer, ExpireHistoryCallback* callback); diff --git a/chrome/browser/history/history_backend.cc b/chrome/browser/history/history_backend.cc index 4fdcef8..33c079a 100644 --- a/chrome/browser/history/history_backend.cc +++ b/chrome/browser/history/history_backend.cc @@ -1,4 +1,4 @@ -// Copyright (c) 2009 The Chromium Authors. All rights reserved. +// Copyright (c) 2010 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. @@ -1740,19 +1740,20 @@ void HistoryBackend::DeleteURL(const GURL& url) { void HistoryBackend::ExpireHistoryBetween( scoped_refptr<ExpireHistoryRequest> request, + const std::set<GURL>& restrict_urls, Time begin_time, Time end_time) { if (request->canceled()) return; if (db_.get()) { - if (begin_time.is_null() && end_time.is_null()) { + if (begin_time.is_null() && end_time.is_null() && restrict_urls.empty()) { // Special case deleting all history so it can be faster and to reduce the // possibility of an information leak. DeleteAllHistory(); } else { // Clearing parts of history, have the expirer do the depend - expirer_.ExpireHistoryBetween(begin_time, end_time); + expirer_.ExpireHistoryBetween(restrict_urls, begin_time, end_time); // Force a commit, if the user is deleting something for privacy reasons, // we want to get it on disk ASAP. @@ -1765,7 +1766,7 @@ void HistoryBackend::ExpireHistoryBetween( request->ForwardResult(ExpireHistoryRequest::TupleType()); - if (history_publisher_.get()) + if (history_publisher_.get() && restrict_urls.empty()) history_publisher_->DeleteUserHistoryBetween(begin_time, end_time); } diff --git a/chrome/browser/history/history_backend.h b/chrome/browser/history/history_backend.h index 49569f5..54d8d63 100644 --- a/chrome/browser/history/history_backend.h +++ b/chrome/browser/history/history_backend.h @@ -1,4 +1,4 @@ -// Copyright (c) 2009 The Chromium Authors. All rights reserved. +// Copyright (c) 2010 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. @@ -245,6 +245,7 @@ class HistoryBackend : public base::RefCountedThreadSafe<HistoryBackend>, // Calls ExpireHistoryBackend::ExpireHistoryBetween and commits the change. void ExpireHistoryBetween(scoped_refptr<ExpireHistoryRequest> request, + const std::set<GURL>& restrict_urls, base::Time begin_time, base::Time end_time); diff --git a/chrome/browser/history/history_backend_unittest.cc b/chrome/browser/history/history_backend_unittest.cc index 84f1d95..d3d28a4 100644 --- a/chrome/browser/history/history_backend_unittest.cc +++ b/chrome/browser/history/history_backend_unittest.cc @@ -1,4 +1,4 @@ -// Copyright (c) 2009 The Chromium Authors. All rights reserved. +// Copyright (c) 2010 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. @@ -465,7 +465,9 @@ TEST_F(HistoryBackendTest, KeywordGenerated) { EXPECT_TRUE(visits.empty()); // Expire the visits. - backend_->expire_backend()->ExpireHistoryBetween(visit_time, Time::Now()); + std::set<GURL> restrict_urls; + backend_->expire_backend()->ExpireHistoryBetween(restrict_urls, + visit_time, Time::Now()); // The visit should have been nuked. visits.clear(); diff --git a/chrome/browser/history/text_database_manager.cc b/chrome/browser/history/text_database_manager.cc index 149cd72..0b27203 100644 --- a/chrome/browser/history/text_database_manager.cc +++ b/chrome/browser/history/text_database_manager.cc @@ -1,4 +1,4 @@ -// Copyright (c) 2009 The Chromium Authors. All rights reserved. +// Copyright (c) 2010 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. @@ -337,7 +337,8 @@ void TextDatabaseManager::DeletePageData(Time time, const GURL& url, db->DeletePageData(time, URLDatabase::GURLToDatabaseURL(url)); } -void TextDatabaseManager::DeleteFromUncommitted(Time begin, Time end) { +void TextDatabaseManager::DeleteFromUncommitted( + const std::set<GURL>& restrict_urls, Time begin, Time end) { // First find the beginning of the range to delete. Recall that the list // has the most recent item at the beginning. There won't normally be very // many items, so a brute-force search is fine. @@ -352,15 +353,17 @@ void TextDatabaseManager::DeleteFromUncommitted(Time begin, Time end) { // Now delete all visits up to the oldest one we were supposed to delete. // Note that if begin is_null, it will be less than or equal to any other // time. - while (cur != recent_changes_.end() && cur->second.visit_time() >= begin) - cur = recent_changes_.Erase(cur); -} - -void TextDatabaseManager::DeleteURLFromUncommitted(const GURL& url) { - RecentChangeList::iterator found = recent_changes_.Peek(url); - if (found == recent_changes_.end()) - return; // We don't know about this page, give up. - recent_changes_.Erase(found); + if (restrict_urls.empty()) { + while (cur != recent_changes_.end() && cur->second.visit_time() >= begin) + cur = recent_changes_.Erase(cur); + } else { + while (cur != recent_changes_.end() && cur->second.visit_time() >= begin) { + if (restrict_urls.find(cur->first) != restrict_urls.end()) + cur = recent_changes_.Erase(cur); + else + ++cur; + } + } } void TextDatabaseManager::DeleteAll() { diff --git a/chrome/browser/history/text_database_manager.h b/chrome/browser/history/text_database_manager.h index b27b9db..cec9dea 100644 --- a/chrome/browser/history/text_database_manager.h +++ b/chrome/browser/history/text_database_manager.h @@ -1,4 +1,4 @@ -// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved. +// Copyright (c) 2010 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. @@ -124,14 +124,13 @@ class TextDatabaseManager { // file AddPageURL/Title/Body that may not be committed to the database yet. // This function removes entires from this list happening between the given // time range. It is called when the user clears their history for a time - // range, and we don't want any of our data to "leak." + // range, and we don't want any of our data to "leak." If restrict_urls is + // not empty, only changes on those URLs are deleted. // // Either or both times my be is_null to be unbounded in that direction. When // non-null, the range is [begin, end). - void DeleteFromUncommitted(base::Time begin, base::Time end); - - // Same as DeleteFromUncommitted but for a single URL. - void DeleteURLFromUncommitted(const GURL& url); + void DeleteFromUncommitted(const std::set<GURL>& restrict_urls, + base::Time begin, base::Time end); // Deletes all full text search data by removing the files from the disk. // This must be called OUTSIDE of a transaction since it actually deletes the @@ -161,6 +160,7 @@ class TextDatabaseManager { FRIEND_TEST(TextDatabaseManagerTest, PartialComplete); FRIEND_TEST(ExpireHistoryTest, DISABLED_DeleteURLAndFavicon); FRIEND_TEST(ExpireHistoryTest, FlushRecentURLsUnstarred); + FRIEND_TEST(ExpireHistoryTest, FlushRecentURLsUnstarredRestricted); // Stores "recent stuff" that has happened with the page, since the page // visit, title, and body all come in at different times. diff --git a/chrome/browser/resources/history.html b/chrome/browser/resources/history.html index f32ef0b..39a23d8 100644 --- a/chrome/browser/resources/history.html +++ b/chrome/browser/resources/history.html @@ -37,6 +37,9 @@ var localStrings; var pageState; var deleteQueue = []; var deleteInFlight = false; +var selectionAnchor = -1; +var selectionEnd = -1; +var id2checkbox = []; /////////////////////////////////////////////////////////////////////////////// @@ -47,12 +50,13 @@ var deleteInFlight = false; * @param {boolean} continued Whether this page is on the same day as the * page before it */ -function Page(result, continued, model) { +function Page(result, continued, model, id) { this.model_ = model; this.title_ = result.title; this.url_ = result.url; this.starred_ = result.starred; this.snippet_ = result.snippet || ""; + this.id_ = id; this.changed = false; @@ -82,6 +86,15 @@ function Page(result, continued, model) { Page.prototype.getBrowseResultDOM = function() { var node = createElementWithClassName('div', 'entry'); var time = createElementWithClassName('div', 'time'); + if (this.model_.getEditMode()) { + var checkbox = document.createElement('input'); + checkbox.type = "checkbox"; + checkbox.name = this.id_; + checkbox.time = this.time.toString(); + checkbox.addEventListener("click", checkboxClicked, false); + id2checkbox[this.id_] = checkbox; + time.appendChild(checkbox); + } time.appendChild(document.createTextNode(this.dateTimeOfDay)); node.appendChild(time); node.appendChild(this.getTitleDOM_()); @@ -131,6 +144,7 @@ Page.prototype.getTitleDOM_ = function() { link.style.backgroundImage = 'url(chrome://favicon/' + encodeURIForCSS(this.url_) + ')'; link.appendChild(document.createTextNode(this.title_)); + link.id = "id-" + this.id_; this.highlightNodeContent_(link); node.appendChild(link); @@ -180,6 +194,7 @@ Page.pregQuote_ = function(str) { */ function HistoryModel() { this.clearModel_(); + this.setEditMode(false); this.view_; } @@ -266,7 +281,8 @@ HistoryModel.prototype.addResults = function(info, results) { if (!lastURL || lastURL != thisURL) { // Figure out if this page is in the same day as the previous page, // this is used to determine how day headers should be drawn. - this.pages_.push(new Page(thisResult, thisDay == lastDay, this)); + this.pages_.push(new Page(thisResult, thisDay == lastDay, this, + this.last_id_++)); lastDay = thisDay; lastURL = thisURL; } @@ -307,12 +323,31 @@ HistoryModel.prototype.getNumberedRange = function(start, end) { return this.pages_.slice(start, end); } +/** + * @return {boolean} Whether we are in edit mode where history items can be + * deleted + */ +HistoryModel.prototype.getEditMode = function() { + return this.editMode_; +} + +/** + * @param {boolean} edit_mode Control whether we are in edit mode. + */ +HistoryModel.prototype.setEditMode = function(edit_mode) { + this.editMode_ = edit_mode; +} + // HistoryModel, Private: ----------------------------------------------------- HistoryModel.prototype.clearModel_ = function() { this.inFlight_ = false; // Whether a query is inflight. this.searchText_ = ''; this.searchDepth_ = 0; this.pages_ = []; // Date-sorted list of pages. + this.last_id_ = 0; + selectionAnchor = -1; + selectionEnd = -1; + id2checkbox = []; // The page that the view wants to see - we only fetch slightly past this // point. If the view requests a page that we don't have data for, we try @@ -404,8 +439,10 @@ HistoryModel.prototype.canFillPage_ = function(page) { * @param {HistoryModel} model The model backing this view. */ function HistoryView(model) { - this.summaryDiv_ = $('results-summary'); - this.summaryDiv_.innerHTML = localStrings.getString('loading'); + this.summaryTd_ = $('results-summary'); + this.summaryTd_.innerHTML = localStrings.getString('loading'); + this.editButtonTd_ = $('edit-button'); + this.editingControlsDiv_ = $('editing-controls'); this.resultDiv_ = $('results-display'); this.pageDiv_ = $('results-pagination'); this.model_ = model @@ -420,6 +457,7 @@ function HistoryView(model) { window.onresize = function() { self.updateEntryAnchorWidth_(); }; + self.updateEditControls_(); } // HistoryView, public: ------------------------------------------------------- @@ -433,7 +471,30 @@ HistoryView.prototype.setSearch = function(term, opt_page) { this.pageIndex_ = parseInt(opt_page || 0, 10); window.scrollTo(0, 0); this.model_.setSearchText(term, this.pageIndex_); - pageState.setUIState(term, this.pageIndex_); + if (term) { + this.setEditMode(false); + } + this.updateEditControls_(); + pageState.setUIState(this.model_.getEditMode(), term, this.pageIndex_); +} + +/** + * Controls edit mode where history can be deleted. + * @param {boolean} edit_mode Whether to enable edit mode. + */ +HistoryView.prototype.setEditMode = function(edit_mode) { + this.model_.setEditMode(edit_mode); + pageState.setUIState(this.model_.getEditMode(), this.model_.getSearchText(), + this.pageIndex_); +} + +/** + * Toggles the edit mode and triggers UI update. + */ +HistoryView.prototype.toggleEditMode = function() { + var editMode = !this.model_.getEditMode(); + this.setEditMode(editMode); + this.updateEditControls_(); } /** @@ -452,7 +513,8 @@ HistoryView.prototype.setPage = function(page) { this.pageIndex_ = parseInt(page, 10); window.scrollTo(0, 0); this.model_.requestPage(page); - pageState.setUIState(this.model_.getSearchText(), this.pageIndex_); + pageState.setUIState(this.model_.getEditMode(), this.model_.getSearchText(), + this.pageIndex_); } /** @@ -528,13 +590,6 @@ HistoryView.prototype.displayResults_ = function() { localStrings.getString('cont'))); } - var link = createElementWithClassName('button', 'delete-day'); - link.time = page.time.toString(); - link.onclick = deleteDay; - link.appendChild( - document.createTextNode(localStrings.getString("deleteday"))); - - day.appendChild(link); this.resultDiv_.appendChild(day); } else if (lastTime - thisTime > BROWSING_GAP_TIME) { this.resultDiv_.appendChild(createElementWithClassName('div', 'gap')); @@ -558,10 +613,51 @@ HistoryView.prototype.displayResults_ = function() { HistoryView.prototype.displaySummaryBar_ = function() { var searchText = this.model_.getSearchText(); if (searchText != '') { - this.summaryDiv_.textContent = localStrings.formatString('searchresultsfor', + this.summaryTd_.textContent = localStrings.formatString('searchresultsfor', searchText); } else { - this.summaryDiv_.innerHTML = localStrings.getString('history'); + this.summaryTd_.innerHTML = localStrings.getString('history'); + } +} + +/** + * Update the widgets related to edit mode. + */ +HistoryView.prototype.updateEditControls_ = function() { + // Display a button (looking like a link) to enable/disable edit mode. + var oldButton = this.editButtonTd_.firstChild; + if (this.model_.getSearchText()) { + this.editButtonTd_.replaceChild(document.createElement('p'), oldButton); + while (this.editingControlsDiv_.hasChildNodes()) { + this.editingControlsDiv_.removeChild(this.editingControlsDiv_.firstChild); + } + return; + } + + var editMode = this.model_.getEditMode(); + var button = createElementWithClassName('button', 'edit-button'); + button.onclick = toggleEditMode; + button.appendChild(document.createTextNode(editMode ? + localStrings.getString("doneediting") : + localStrings.getString("edithistory"))); + this.editButtonTd_.replaceChild(button, oldButton); + if (editMode) { + // Button to delete the selected items. + button = document.createElement('button'); + button.onclick = removeItems; + button.appendChild(document.createTextNode( + localStrings.getString("removeselected"))); + this.editingControlsDiv_.appendChild(button); + // Button that opens up the clear browsing data dialog. + button = document.createElement('button'); + button.onclick = openClearBrowsingData; + button.appendChild(document.createTextNode( + localStrings.getString("clearallhistory"))); + this.editingControlsDiv_.appendChild(button); + } else { + while (this.editingControlsDiv_.hasChildNodes()) { + this.editingControlsDiv_.removeChild(this.editingControlsDiv_.firstChild); + } } } @@ -592,7 +688,8 @@ HistoryView.prototype.displayNavBar_ = function() { * @return {string} HTML representation of the pagination link */ HistoryView.prototype.createPageNavHTML_ = function(page, name) { - var hashString = PageState.getHashString(this.model_.getSearchText(), page); + var hashString = PageState.getHashString(this.model_.getEditMode(), + this.model_.getSearchText(), page); return '<a href="chrome://history/' + (hashString ? '#' + hashString : '') + '"' + @@ -613,23 +710,24 @@ HistoryView.prototype.updateEntryAnchorWidth_ = function() { return; // Create new CSS rules and add them last to the last stylesheet. - if (!this.entryAnchorRule_) { - var styleSheets = document.styleSheets; - var styleSheet = styleSheets[styleSheets.length - 1]; - var rules = styleSheet.cssRules; - var createRule = function(selector) { - styleSheet.insertRule(selector + '{}', rules.length); - return rules[rules.length - 1]; - }; - this.entryAnchorRule_ = createRule('.entry .title > a'); - // The following rule needs to be more specific to have higher priority. - this.entryAnchorStarredRule_ = createRule('.entry .title.starred > a'); - } - - var anchorMaxWith = titleElement.offsetWidth; - this.entryAnchorRule_.style.maxWidth = anchorMaxWith + 'px'; - // Adjust by the width of star plus its margin. - this.entryAnchorStarredRule_.style.maxWidth = anchorMaxWith - 23 + 'px'; + // TODO(jochen): The following code does not work due to WebKit bug #32309 + // if (!this.entryAnchorRule_) { + // var styleSheets = document.styleSheets; + // var styleSheet = styleSheets[styleSheets.length - 1]; + // var rules = styleSheet.cssRules; + // var createRule = function(selector) { + // styleSheet.insertRule(selector + '{}', rules.length); + // return rules[rules.length - 1]; + // }; + // this.entryAnchorRule_ = createRule('.entry .title > a'); + // // The following rule needs to be more specific to have higher priority. + // this.entryAnchorStarredRule_ = createRule('.entry .title.starred > a'); + // } + // + // var anchorMaxWith = titleElement.offsetWidth; + // this.entryAnchorRule_.style.maxWidth = anchorMaxWith + 'px'; + // // Adjust by the width of star plus its margin. + // this.entryAnchorStarredRule_.style.maxWidth = anchorMaxWith - 23 + 'px'; }; /////////////////////////////////////////////////////////////////////////////// @@ -672,6 +770,7 @@ PageState.instance = null; */ PageState.prototype.getHashData = function() { var result = { + e : 0, q : '', p : 0 }; @@ -698,12 +797,13 @@ PageState.prototype.getHashData = function() { * @param {string} term The current search string. * @param {string} page The page currently being viewed. */ -PageState.prototype.setUIState = function(term, page) { +PageState.prototype.setUIState = function(editMode, term, page) { // Make sure the form looks pretty. document.forms[0].term.value = term; var currentHash = this.getHashData(); - if (currentHash.q != term || currentHash.p != page) { - window.location.hash = PageState.getHashString(term, page); + if (Boolean(currentHash.e) != editMode || currentHash.q != term || + currentHash.p != page) { + window.location.hash = PageState.getHashString(editMode, term, page); } } @@ -713,8 +813,11 @@ PageState.prototype.setUIState = function(term, page) { * @param {string} page The page currently being viewed. * @return {string} The string to be used in a hash. */ -PageState.getHashString = function(term, page) { +PageState.getHashString = function(editMode, term, page) { var newHash = []; + if (editMode) { + newHash.push("e=1"); + } if (term) { newHash.push("q=" + encodeURIComponent(term)); } @@ -740,6 +843,9 @@ function load() { // Create default view. var hashData = pageState.getHashData(); + if (Boolean(hashData.e)) { + historyView.toggleEditMode(); + } historyView.setSearch(hashData.q, hashData.p); } @@ -766,25 +872,14 @@ function setPage(page) { } /** - * Delete a day from history. - * TODO: Add UI to indicate that something is happening. - * @param {number} time A time from the day we wish to delete. + * TODO(glen): Get rid of this function. + * Toggles edit mode. */ -function deleteDay() { - var time = this.time; - - // Check to see if item is already being deleted. - for (var i = 0, deleting; deleting = deleteQueue[i]; i++) { - if (deleting == time) - return false; - } - - if (confirm(localStrings.getString("deletedaywarning"))) { - deleteQueue.push(time); - deleteNextInQueue(); +function toggleEditMode() { + if (historyView) { + historyView.toggleEditMode(); + historyView.reload(); } - - return false; } /** @@ -793,7 +888,101 @@ function deleteDay() { function deleteNextInQueue() { if (!deleteInFlight && deleteQueue.length) { deleteInFlight = true; - chrome.send("deleteDay", [deleteQueue[0]]); + chrome.send("removeURLsOnOneDay", + [String(deleteQueue[0])].concat(deleteQueue[1])); + } +} + +/** + * Open the clear browsing data dialog. + */ +function openClearBrowsingData() { + chrome.send("clearBrowsingData", []); + return false; +} + +/** + * Collect IDs from checked checkboxes and send to Chrome for deletion. + */ +function removeItems() { + var checkboxes = document.getElementsByTagName('input'); + var ids = []; + var queue = []; + var date = new Date(); + for (var i = 0; i < checkboxes.length; i++) { + if (checkboxes[i].type == "checkbox" && checkboxes[i].checked && + !checkboxes[i].disabled) { + var cbDate = new Date(checkboxes[i].time); + if (date.getFullYear() != cbDate.getFullYear() || + date.getMonth() != cbDate.getMonth() || + date.getDate() != cbDate.getDate()) { + if (ids.length > 0) { + queue.push(date.valueOf() / 1000); + queue.push(ids); + } + ids = []; + date = cbDate; + } + var link = $("id-" + checkboxes[i].name); + checkboxes[i].disabled = true; + link.style.setProperty("text-decoration", "line-through"); + ids.push(link.href); + } + } + if (ids.length > 0) { + queue.push(date.valueOf() / 1000); + queue.push(ids); + } + if (queue.length > 0) { + if (confirm(localStrings.getString("deletewarning"))) { + deleteQueue = deleteQueue.concat(queue); + deleteNextInQueue(); + } + } + return false; +} + +/** + * Toggle state of checkbox and handle CTRL and Shift modifiers + */ +function checkboxClicked(event) { + if ((selectionAnchor == -1) || !event.shiftKey) { + selectionAnchor = this.name; + selectionEnd = this.name; + } + if (event.shiftKey) { + if (((selectionAnchor >= selectionEnd) && (selectionAnchor >= this.name)) || + ((selectionAnchor <= selectionEnd) && (selectionAnchor <= this.name))) { + // If the click was on the same side of the anchor as the last click, + // extend the current selection. + var begin = selectionEnd < this.name ? selectionEnd : this.name; + var end = selectionEnd < this.name ? this.name : selectionEnd; + var checked = true; + if (((selectionAnchor <= this.name) && (this.name <= selectionEnd)) || + ((selectionEnd <= this.name) && (this.name <= selectionAnchor))) { + checked = false; + } + for (var i = begin; i <= end; i++) { + if (!id2checkbox[i].disabled) { + id2checkbox[i].checked = checked; + } + } + this.checked = true; + selectionEnd = this.name; + } else { + // Otherwise, the last clicked checkbox becomes the new anchor, and + // everything between the last anchor and the newly clicked checkbox is + // enabled. + var begin = selectionAnchor < this.name ? selectionAnchor : this.name; + var end = selectionAnchor < this.name ? this.name : selectionAnchor; + for (var i = begin; i <= end; i++) { + if (!id2checkbox[i].disabled) { + id2checkbox[i].checked = true; + } + } + selectionAnchor = selectionEnd; + selectionEnd = this.name; + } } } @@ -813,8 +1002,11 @@ function deleteComplete() { window.console.log("Delete complete"); deleteInFlight = false; if (deleteQueue.length > 1) { - deleteQueue = deleteQueue.slice(1, deleteQueue.length); + deleteQueue = deleteQueue.slice(2); deleteNextInQueue(); + } else { + deleteQueue = []; + historyView.reload(); } } @@ -840,7 +1032,7 @@ function historyDeleted() { </script> <link rel="stylesheet" href="dom_ui.css"> <style> -#results-summary { +#results-separator { margin-top:12px; border-top:1px solid #9cc2ef; background-color:#ebeff9; @@ -848,6 +1040,26 @@ function historyDeleted() { padding:3px; margin-bottom:-8px; } +#results-separator table { + width: 100%; +} +#results-summary { + overflow: hidden; + white-space: nowrap; + text-overflow: ellipsis; + width: 50%; +} +#edit-button { + text-align: right; + overflow: hidden; + white-space: nowrap; + text-overflow: ellipsis; + width: 50%; +} +#editing-controls button { + margin-top: 18px; + margin-bottom: -8px; +} #results-display { max-width:740px; } @@ -856,7 +1068,7 @@ function historyDeleted() { padding:0px 3px; display:inline-block; } -.delete-day { +.edit-button { display: inline; -webkit-appearance: none; background: none; @@ -952,7 +1164,12 @@ html[dir='rtl'] .entry .title > a { </form> </div> <div class="main"> - <div id="results-summary"></div> + <div id="results-separator"> + <table border="0" cellPadding="0" cellSpacing="0"> + <tr><td id="results-summary"></td><td id="edit-button"><p></p></td></tr> + </table> + </div> + <div id="editing-controls"></div> <div id="results-display"></div> <div id="results-pagination"></div> </div> |