feat: add app.getRecentDocuments()
(#47924)
feat: add app.getRecentDocuments() Co-authored-by: trop[bot] <37223003+trop[bot]@users.noreply.github.com> Co-authored-by: Shelley Vohr <shelley.vohr@gmail.com>
This commit is contained in:
parent
eb02db5185
commit
33f4808182
8 changed files with 142 additions and 11 deletions
|
@ -776,6 +776,22 @@ bar, and on macOS, you can visit it from dock menu.
|
||||||
|
|
||||||
Clears the recent documents list.
|
Clears the recent documents list.
|
||||||
|
|
||||||
|
### `app.getRecentDocuments()` _macOS_ _Windows_
|
||||||
|
|
||||||
|
Returns `string[]` - An array containing documents in the most recent documents list.
|
||||||
|
|
||||||
|
```js
|
||||||
|
const { app } = require('electron')
|
||||||
|
|
||||||
|
const path = require('node:path')
|
||||||
|
|
||||||
|
const file = path.join(app.getPath('desktop'), 'foo.txt')
|
||||||
|
app.addRecentDocument(file)
|
||||||
|
|
||||||
|
const recents = app.getRecentDocuments()
|
||||||
|
console.log(recents) // ['/path/to/desktop/foo.txt'}
|
||||||
|
```
|
||||||
|
|
||||||
### `app.setAsDefaultProtocolClient(protocol[, path, args])`
|
### `app.setAsDefaultProtocolClient(protocol[, path, args])`
|
||||||
|
|
||||||
* `protocol` string - The name of your protocol, without `://`. For example,
|
* `protocol` string - The name of your protocol, without `://`. For example,
|
||||||
|
|
|
@ -77,6 +77,11 @@ To clear the list of recent documents, use the
|
||||||
In this guide, the list of documents is cleared once all windows have been
|
In this guide, the list of documents is cleared once all windows have been
|
||||||
closed.
|
closed.
|
||||||
|
|
||||||
|
#### Accessing the list of recent documents
|
||||||
|
|
||||||
|
To access the list of recent documents, use the
|
||||||
|
[app.getRecentDocuments][getrecentdocuments] API.
|
||||||
|
|
||||||
## Additional information
|
## Additional information
|
||||||
|
|
||||||
### Windows Notes
|
### Windows Notes
|
||||||
|
@ -138,5 +143,6 @@ of `app` module will be emitted for it.
|
||||||
[dock-menu-image]: https://cloud.githubusercontent.com/assets/639601/5069610/2aa80758-6e97-11e4-8cfb-c1a414a10774.png
|
[dock-menu-image]: https://cloud.githubusercontent.com/assets/639601/5069610/2aa80758-6e97-11e4-8cfb-c1a414a10774.png
|
||||||
[addrecentdocument]: ../api/app.md#appaddrecentdocumentpath-macos-windows
|
[addrecentdocument]: ../api/app.md#appaddrecentdocumentpath-macos-windows
|
||||||
[clearrecentdocuments]: ../api/app.md#appclearrecentdocuments-macos-windows
|
[clearrecentdocuments]: ../api/app.md#appclearrecentdocuments-macos-windows
|
||||||
|
[getrecentdocuments]: ../api/app.md#appgetrecentdocuments-macos-windows
|
||||||
[app-registration]: https://learn.microsoft.com/en-us/windows/win32/shell/app-registration
|
[app-registration]: https://learn.microsoft.com/en-us/windows/win32/shell/app-registration
|
||||||
[menu-item-image]: https://user-images.githubusercontent.com/3168941/33003655-ea601c3a-cd70-11e7-97fa-7c062149cfb1.png
|
[menu-item-image]: https://user-images.githubusercontent.com/3168941/33003655-ea601c3a-cd70-11e7-97fa-7c062149cfb1.png
|
||||||
|
|
|
@ -1720,6 +1720,8 @@ gin::ObjectTemplateBuilder App::GetObjectTemplateBuilder(v8::Isolate* isolate) {
|
||||||
base::BindRepeating(&Browser::AddRecentDocument, browser))
|
base::BindRepeating(&Browser::AddRecentDocument, browser))
|
||||||
.SetMethod("clearRecentDocuments",
|
.SetMethod("clearRecentDocuments",
|
||||||
base::BindRepeating(&Browser::ClearRecentDocuments, browser))
|
base::BindRepeating(&Browser::ClearRecentDocuments, browser))
|
||||||
|
.SetMethod("getRecentDocuments",
|
||||||
|
base::BindRepeating(&Browser::GetRecentDocuments, browser))
|
||||||
#if BUILDFLAG(IS_WIN)
|
#if BUILDFLAG(IS_WIN)
|
||||||
.SetMethod("setAppUserModelId",
|
.SetMethod("setAppUserModelId",
|
||||||
base::BindRepeating(&Browser::SetAppUserModelID, browser))
|
base::BindRepeating(&Browser::SetAppUserModelID, browser))
|
||||||
|
|
|
@ -125,6 +125,9 @@ class Browser : private WindowListObserver {
|
||||||
// Clear the recent documents list.
|
// Clear the recent documents list.
|
||||||
void ClearRecentDocuments();
|
void ClearRecentDocuments();
|
||||||
|
|
||||||
|
// Return the recent documents list.
|
||||||
|
std::vector<std::string> GetRecentDocuments();
|
||||||
|
|
||||||
#if BUILDFLAG(IS_WIN)
|
#if BUILDFLAG(IS_WIN)
|
||||||
// Set the application user model ID.
|
// Set the application user model ID.
|
||||||
void SetAppUserModelID(const std::wstring& name);
|
void SetAppUserModelID(const std::wstring& name);
|
||||||
|
|
|
@ -95,6 +95,10 @@ bool SetDefaultWebClient(const std::string& protocol) {
|
||||||
|
|
||||||
void Browser::AddRecentDocument(const base::FilePath& path) {}
|
void Browser::AddRecentDocument(const base::FilePath& path) {}
|
||||||
|
|
||||||
|
std::vector<std::string> Browser::GetRecentDocuments() {
|
||||||
|
return std::vector<std::string>();
|
||||||
|
}
|
||||||
|
|
||||||
void Browser::ClearRecentDocuments() {}
|
void Browser::ClearRecentDocuments() {}
|
||||||
|
|
||||||
bool Browser::SetAsDefaultProtocolClient(const std::string& protocol,
|
bool Browser::SetAsDefaultProtocolClient(const std::string& protocol,
|
||||||
|
|
|
@ -162,19 +162,31 @@ void Browser::Show() {
|
||||||
}
|
}
|
||||||
|
|
||||||
void Browser::AddRecentDocument(const base::FilePath& path) {
|
void Browser::AddRecentDocument(const base::FilePath& path) {
|
||||||
NSString* path_string = base::apple::FilePathToNSString(path);
|
NSURL* url = base::apple::FilePathToNSURL(path);
|
||||||
if (!path_string)
|
if (!url) {
|
||||||
|
LOG(WARNING) << "Failed to convert file path " << path.value()
|
||||||
|
<< " to NSURL";
|
||||||
return;
|
return;
|
||||||
NSURL* u = [NSURL fileURLWithPath:path_string];
|
}
|
||||||
if (!u)
|
|
||||||
return;
|
[[NSDocumentController sharedDocumentController]
|
||||||
[[NSDocumentController sharedDocumentController] noteNewRecentDocumentURL:u];
|
noteNewRecentDocumentURL:url];
|
||||||
}
|
}
|
||||||
|
|
||||||
void Browser::ClearRecentDocuments() {
|
void Browser::ClearRecentDocuments() {
|
||||||
[[NSDocumentController sharedDocumentController] clearRecentDocuments:nil];
|
[[NSDocumentController sharedDocumentController] clearRecentDocuments:nil];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::vector<std::string> Browser::GetRecentDocuments() {
|
||||||
|
NSArray<NSURL*>* recentURLs =
|
||||||
|
[[NSDocumentController sharedDocumentController] recentDocumentURLs];
|
||||||
|
std::vector<std::string> documents;
|
||||||
|
documents.reserve([recentURLs count]);
|
||||||
|
for (NSURL* url in recentURLs)
|
||||||
|
documents.push_back(std::string([url.path UTF8String]));
|
||||||
|
return documents;
|
||||||
|
}
|
||||||
|
|
||||||
bool Browser::RemoveAsDefaultProtocolClient(const std::string& protocol,
|
bool Browser::RemoveAsDefaultProtocolClient(const std::string& protocol,
|
||||||
gin::Arguments* args) {
|
gin::Arguments* args) {
|
||||||
NSString* identifier = [base::apple::MainBundle() bundleIdentifier];
|
NSString* identifier = [base::apple::MainBundle() bundleIdentifier];
|
||||||
|
|
|
@ -17,6 +17,7 @@
|
||||||
#include "base/base_paths.h"
|
#include "base/base_paths.h"
|
||||||
#include "base/command_line.h"
|
#include "base/command_line.h"
|
||||||
#include "base/file_version_info.h"
|
#include "base/file_version_info.h"
|
||||||
|
#include "base/files/file_enumerator.h"
|
||||||
#include "base/files/file_path.h"
|
#include "base/files/file_path.h"
|
||||||
#include "base/logging.h"
|
#include "base/logging.h"
|
||||||
#include "base/path_service.h"
|
#include "base/path_service.h"
|
||||||
|
@ -315,14 +316,33 @@ void GetApplicationInfoForProtocolUsingAssocQuery(
|
||||||
app_display_name, std::move(promise));
|
app_display_name, std::move(promise));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::string ResolveShortcut(const base::FilePath& lnk_path) {
|
||||||
|
std::string target_path;
|
||||||
|
|
||||||
|
CComPtr<IShellLink> shell_link;
|
||||||
|
if (SUCCEEDED(CoCreateInstance(CLSID_ShellLink, nullptr, CLSCTX_INPROC_SERVER,
|
||||||
|
IID_PPV_ARGS(&shell_link)))) {
|
||||||
|
CComPtr<IPersistFile> persist_file;
|
||||||
|
if (SUCCEEDED(shell_link->QueryInterface(IID_PPV_ARGS(&persist_file)))) {
|
||||||
|
if (SUCCEEDED(persist_file->Load(lnk_path.value().c_str(), STGM_READ))) {
|
||||||
|
WCHAR resolved_path[MAX_PATH];
|
||||||
|
if (SUCCEEDED(
|
||||||
|
shell_link->GetPath(resolved_path, MAX_PATH, nullptr, 0))) {
|
||||||
|
target_path = base::FilePath(resolved_path).MaybeAsASCII();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return target_path;
|
||||||
|
}
|
||||||
|
|
||||||
void Browser::AddRecentDocument(const base::FilePath& path) {
|
void Browser::AddRecentDocument(const base::FilePath& path) {
|
||||||
CComPtr<IShellItem> item;
|
CComPtr<IShellItem> item;
|
||||||
HRESULT hr = SHCreateItemFromParsingName(path.value().c_str(), nullptr,
|
HRESULT hr = SHCreateItemFromParsingName(path.value().c_str(), nullptr,
|
||||||
IID_PPV_ARGS(&item));
|
IID_PPV_ARGS(&item));
|
||||||
if (SUCCEEDED(hr)) {
|
if (SUCCEEDED(hr)) {
|
||||||
SHARDAPPIDINFO info;
|
SHARDAPPIDINFO info = {item, GetAppUserModelID()};
|
||||||
info.psi = item;
|
|
||||||
info.pszAppID = GetAppUserModelID();
|
|
||||||
SHAddToRecentDocs(SHARD_APPIDINFO, &info);
|
SHAddToRecentDocs(SHARD_APPIDINFO, &info);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -331,6 +351,33 @@ void Browser::ClearRecentDocuments() {
|
||||||
SHAddToRecentDocs(SHARD_APPIDINFO, nullptr);
|
SHAddToRecentDocs(SHARD_APPIDINFO, nullptr);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::vector<std::string> Browser::GetRecentDocuments() {
|
||||||
|
ScopedAllowBlockingForElectron allow_blocking;
|
||||||
|
std::vector<std::string> docs;
|
||||||
|
|
||||||
|
PWSTR recent_path_ptr = nullptr;
|
||||||
|
HRESULT hr =
|
||||||
|
SHGetKnownFolderPath(FOLDERID_Recent, 0, nullptr, &recent_path_ptr);
|
||||||
|
if (SUCCEEDED(hr) && recent_path_ptr) {
|
||||||
|
base::FilePath recent_folder(recent_path_ptr);
|
||||||
|
CoTaskMemFree(recent_path_ptr);
|
||||||
|
|
||||||
|
base::FileEnumerator enumerator(recent_folder, /*recursive=*/false,
|
||||||
|
base::FileEnumerator::FILES,
|
||||||
|
FILE_PATH_LITERAL("*.lnk"));
|
||||||
|
|
||||||
|
for (base::FilePath file = enumerator.Next(); !file.empty();
|
||||||
|
file = enumerator.Next()) {
|
||||||
|
std::string resolved_path = ResolveShortcut(file);
|
||||||
|
if (!resolved_path.empty()) {
|
||||||
|
docs.push_back(resolved_path);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return docs;
|
||||||
|
}
|
||||||
|
|
||||||
void Browser::SetAppUserModelID(const std::wstring& name) {
|
void Browser::SetAppUserModelID(const std::wstring& name) {
|
||||||
electron::SetAppUserModelID(name);
|
electron::SetAppUserModelID(name);
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,6 +11,7 @@ import * as http from 'node:http';
|
||||||
import * as https from 'node:https';
|
import * as https from 'node:https';
|
||||||
import * as net from 'node:net';
|
import * as net from 'node:net';
|
||||||
import * as path from 'node:path';
|
import * as path from 'node:path';
|
||||||
|
import { setTimeout } from 'node:timers/promises';
|
||||||
import { promisify } from 'node:util';
|
import { promisify } from 'node:util';
|
||||||
|
|
||||||
import { collectStreamBody, getResponse } from './lib/net-helpers';
|
import { collectStreamBody, getResponse } from './lib/net-helpers';
|
||||||
|
@ -19,6 +20,8 @@ import { closeWindow, closeAllWindows } from './lib/window-helpers';
|
||||||
|
|
||||||
const fixturesPath = path.resolve(__dirname, 'fixtures');
|
const fixturesPath = path.resolve(__dirname, 'fixtures');
|
||||||
|
|
||||||
|
const isMacOSx64 = process.platform === 'darwin' && process.arch === 'x64';
|
||||||
|
|
||||||
describe('electron module', () => {
|
describe('electron module', () => {
|
||||||
it('does not expose internal modules to require', () => {
|
it('does not expose internal modules to require', () => {
|
||||||
expect(() => {
|
expect(() => {
|
||||||
|
@ -356,6 +359,44 @@ describe('app module', () => {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// GitHub Actions macOS-13 runners used for x64 seem to have a problem with this test.
|
||||||
|
ifdescribe(process.platform !== 'linux' && !isMacOSx64)('app.{add|get|clear}RecentDocument(s)', () => {
|
||||||
|
const tempFiles = [
|
||||||
|
path.join(fixturesPath, 'foo.txt'),
|
||||||
|
path.join(fixturesPath, 'bar.txt'),
|
||||||
|
path.join(fixturesPath, 'baz.txt')
|
||||||
|
];
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
app.clearRecentDocuments();
|
||||||
|
for (const file of tempFiles) {
|
||||||
|
fs.unlinkSync(file);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
for (const file of tempFiles) {
|
||||||
|
fs.writeFileSync(file, 'Lorem Ipsum');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
it('can add a recent document', async () => {
|
||||||
|
app.addRecentDocument(tempFiles[0]);
|
||||||
|
await setTimeout(2000);
|
||||||
|
expect(app.getRecentDocuments()).to.include.members([tempFiles[0]]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('can clear recent documents', async () => {
|
||||||
|
app.addRecentDocument(tempFiles[1]);
|
||||||
|
app.addRecentDocument(tempFiles[2]);
|
||||||
|
await setTimeout(2000);
|
||||||
|
expect(app.getRecentDocuments()).to.include.members([tempFiles[1], tempFiles[2]]);
|
||||||
|
app.clearRecentDocuments();
|
||||||
|
await setTimeout(2000);
|
||||||
|
expect(app.getRecentDocuments()).to.deep.equal([]);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
describe('app.relaunch', () => {
|
describe('app.relaunch', () => {
|
||||||
let server: net.Server | null = null;
|
let server: net.Server | null = null;
|
||||||
const socketPath = process.platform === 'win32' ? '\\\\.\\pipe\\electron-app-relaunch' : '/tmp/electron-app-relaunch';
|
const socketPath = process.platform === 'win32' ? '\\\\.\\pipe\\electron-app-relaunch' : '/tmp/electron-app-relaunch';
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue