mirror of
https://github.com/electron/electron.git
synced 2026-03-19 03:02:02 -04:00
Compare commits
2 Commits
programmat
...
downloads-
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c203d4c070 | ||
|
|
74c1811b35 |
2
.github/actions/checkout/action.yml
vendored
2
.github/actions/checkout/action.yml
vendored
@@ -43,7 +43,7 @@ runs:
|
||||
curl --unix-socket /var/run/sas/sas.sock --fail "http://foo/$CACHE_FILE?platform=${{ inputs.target-platform }}&getAccountName=true" > sas-token
|
||||
- name: Save SAS Key
|
||||
if: ${{ inputs.generate-sas-token == 'true' }}
|
||||
uses: actions/cache/save@cdf6c1fa76f9f475f3d7449005a359c84ca0f306 # v5.0.3
|
||||
uses: actions/cache/save@5a3ec84eff668545956fd18022155c47e93e2684 # v4.2.3
|
||||
with:
|
||||
path: sas-token
|
||||
key: sas-key-${{ inputs.target-platform }}-${{ github.run_number }}-${{ github.run_attempt }}
|
||||
|
||||
@@ -7,7 +7,7 @@ runs:
|
||||
shell: bash
|
||||
id: yarn-cache-dir-path
|
||||
run: echo "dir=$(node src/electron/script/yarn.js config get cacheFolder)" >> $GITHUB_OUTPUT
|
||||
- uses: actions/cache@cdf6c1fa76f9f475f3d7449005a359c84ca0f306 # v5.0.3
|
||||
- uses: actions/cache@5a3ec84eff668545956fd18022155c47e93e2684 # v4.2.3
|
||||
id: yarn-cache
|
||||
with:
|
||||
path: ${{ steps.yarn-cache-dir-path.outputs.dir }}
|
||||
|
||||
7
.github/workflows/apply-patches.yml
vendored
7
.github/workflows/apply-patches.yml
vendored
@@ -71,10 +71,3 @@ jobs:
|
||||
uses: ./src/electron/.github/actions/checkout
|
||||
with:
|
||||
target-platform: linux
|
||||
- name: Upload Patch Conflict Fix
|
||||
if: ${{ failure() }}
|
||||
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
|
||||
with:
|
||||
name: update-patches
|
||||
path: patches/update-patches.patch
|
||||
if-no-files-found: ignore
|
||||
|
||||
27
.github/workflows/build.yml
vendored
27
.github/workflows/build.yml
vendored
@@ -434,30 +434,3 @@ jobs:
|
||||
- name: GitHub Actions Jobs Done
|
||||
run: |
|
||||
echo "All GitHub Actions Jobs are done"
|
||||
|
||||
check-signed-commits:
|
||||
name: Check signed commits in green PR
|
||||
needs: gha-done
|
||||
if: ${{ contains(github.event.pull_request.labels.*.name, 'needs-signed-commits')}}
|
||||
runs-on: ubuntu-slim
|
||||
permissions:
|
||||
contents: read
|
||||
pull-requests: write
|
||||
steps:
|
||||
- name: Check signed commits in PR
|
||||
uses: 1Password/check-signed-commits-action@ed2885f3ed2577a4f5d3c3fe895432a557d23d52 # v1
|
||||
with:
|
||||
comment: |
|
||||
⚠️ This PR contains unsigned commits. This repository enforces [commit signatures](https://docs.github.com/en/authentication/managing-commit-signature-verification)
|
||||
for all incoming PRs. To get your PR merged, please sign those commits
|
||||
(`git rebase --exec 'git commit -S --amend --no-edit -n' @{upstream}`) and force push them to this branch
|
||||
(`git push --force-with-lease`)
|
||||
|
||||
For more information on signing commits, see GitHub's documentation on [Telling Git about your signing key](https://docs.github.com/en/authentication/managing-commit-signature-verification/telling-git-about-your-signing-key).
|
||||
|
||||
- name: Remove needs-signed-commits label
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
PR_URL: ${{ github.event.pull_request.html_url }}
|
||||
run: |
|
||||
gh pr edit $PR_URL --remove-label needs-signed-commits
|
||||
|
||||
24
.github/workflows/issue-commented.yml
vendored
24
.github/workflows/issue-commented.yml
vendored
@@ -34,6 +34,30 @@ jobs:
|
||||
run: |
|
||||
gh issue edit $ISSUE_URL --remove-label 'blocked/need-repro','blocked/need-info ❌'
|
||||
|
||||
pr-needs-signed-commits-commented:
|
||||
name: Remove needs-signed-commits on comment
|
||||
if: ${{ github.event.issue.pull_request && (contains(github.event.issue.labels.*.name, 'needs-signed-commits')) && (github.event.comment.user.login == github.event.issue.user.login) }}
|
||||
runs-on: ubuntu-slim
|
||||
steps:
|
||||
- name: Get author association
|
||||
id: get-author-association
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
run: *get-author-association
|
||||
- name: Generate GitHub App token
|
||||
uses: electron/github-app-auth-action@e14e47722ed120360649d0789e25b9baece12725 # v2.0.0
|
||||
if: ${{ !contains(fromJSON('["MEMBER", "OWNER", "COLLABORATOR"]'), steps.get-author-association.outputs.author_association) }}
|
||||
id: generate-token
|
||||
with:
|
||||
creds: ${{ secrets.ISSUE_TRIAGE_GH_APP_CREDS }}
|
||||
- name: Remove label
|
||||
if: ${{ !contains(fromJSON('["MEMBER", "OWNER", "COLLABORATOR"]'), steps.get-author-association.outputs.author_association) }}
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ steps.generate-token.outputs.token }}
|
||||
ISSUE_URL: ${{ github.event.issue.html_url }}
|
||||
run: |
|
||||
gh issue edit $ISSUE_URL --remove-label 'needs-signed-commits'
|
||||
|
||||
pr-reviewer-requested:
|
||||
name: Maintainer requested reviewer on PR
|
||||
if: ${{ github.event.issue.pull_request && startsWith(github.event.comment.body, '/request-review') && github.event.comment.user.type != 'Bot' }}
|
||||
|
||||
@@ -1,35 +0,0 @@
|
||||
name: Pull Request Opened/Synchronized
|
||||
|
||||
on:
|
||||
pull_request_target:
|
||||
types: [opened, synchronize]
|
||||
|
||||
permissions: {}
|
||||
|
||||
jobs:
|
||||
check-signed-commits:
|
||||
name: Check signed commits in PR
|
||||
if: ${{ !contains(github.event.pull_request.labels.*.name, 'needs-signed-commits')}}
|
||||
runs-on: ubuntu-slim
|
||||
permissions:
|
||||
contents: read
|
||||
pull-requests: write
|
||||
steps:
|
||||
- name: Check signed commits in PR
|
||||
uses: 1Password/check-signed-commits-action@ed2885f3ed2577a4f5d3c3fe895432a557d23d52 # v1
|
||||
with:
|
||||
comment: |
|
||||
⚠️ This PR contains unsigned commits. This repository enforces [commit signatures](https://docs.github.com/en/authentication/managing-commit-signature-verification)
|
||||
for all incoming PRs. To get your PR merged, please sign those commits
|
||||
(`git rebase --exec 'git commit -S --amend --no-edit -n' @{upstream}`) and force push them to this branch
|
||||
(`git push --force-with-lease`)
|
||||
|
||||
For more information on signing commits, see GitHub's documentation on [Telling Git about your signing key](https://docs.github.com/en/authentication/managing-commit-signature-verification/telling-git-about-your-signing-key).
|
||||
|
||||
- name: Add needs-signed-commits label
|
||||
if: ${{ failure() }}
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
PR_URL: ${{ github.event.pull_request.html_url }}
|
||||
run: |
|
||||
gh pr edit $PR_URL --add-label needs-signed-commits
|
||||
16
CLAUDE.md
16
CLAUDE.md
@@ -127,22 +127,6 @@ patches/{target}/*.patch → [e sync --3] → target repo commits
|
||||
2. Create a git commit
|
||||
3. Run `e patches <target>` to export
|
||||
|
||||
**Fixing patch conflicts on an existing PR:**
|
||||
|
||||
If asked to fix a patch conflict on a branch that already has an open PR, check the PR's failed **Apply Patches** CI run for an `update-patches` artifact before running `e sync` locally. CI has already performed the 3-way merge and exported the resolved patch diff — applying it is much faster than a full local sync.
|
||||
|
||||
```bash
|
||||
# Find the failed Apply Patches run for the PR and download the artifact
|
||||
gh run list --repo electron/electron --branch <pr-branch> --workflow "Apply Patches" --limit 1
|
||||
gh run download <run-id> --repo electron/electron --name update-patches
|
||||
|
||||
# Apply the CI-generated fix, then push
|
||||
git am update-patches.patch
|
||||
git push
|
||||
```
|
||||
|
||||
If no artifact exists (e.g. the 3-way merge itself failed), fall back to `e sync --3` and resolve manually.
|
||||
|
||||
## Testing
|
||||
|
||||
**Test location:** `spec/` directory
|
||||
|
||||
@@ -122,8 +122,8 @@ if ((globalThis.process || binding.process).argv.includes("--profile-electron-in
|
||||
'electron/renderer$': electronAPIFile,
|
||||
'electron/common$': electronAPIFile,
|
||||
'electron/utility$': electronAPIFile,
|
||||
// Force timers to resolve to our own shim that doesn't use window.postMessage
|
||||
timers: path.resolve(electronRoot, 'lib', 'common', 'timers-shim.ts')
|
||||
// Force timers to resolve to our dependency that doesn't use window.postMessage
|
||||
timers: path.resolve(electronRoot, 'node_modules', 'timers-browserify', 'main.js')
|
||||
},
|
||||
extensions: ['.ts', '.js'],
|
||||
fallback: {
|
||||
|
||||
@@ -28,7 +28,10 @@ added:
|
||||
* `window` [BaseWindow](base-window.md) (optional)
|
||||
* `options` Object
|
||||
* `title` string (optional)
|
||||
* `defaultPath` string (optional)
|
||||
* `defaultPath` string (optional) - Absolute directory path, absolute file
|
||||
path, or file name to use by default. If not provided, the dialog will
|
||||
default to the user's Downloads folder, or their home directory if Downloads
|
||||
doesn't exist.
|
||||
* `buttonLabel` string (optional) - Custom label for the confirmation button, when
|
||||
left empty the default label will be used.
|
||||
* `filters` [FileFilter[]](structures/file-filter.md) (optional)
|
||||
@@ -109,7 +112,10 @@ changes:
|
||||
* `window` [BaseWindow](base-window.md) (optional)
|
||||
* `options` Object
|
||||
* `title` string (optional)
|
||||
* `defaultPath` string (optional)
|
||||
* `defaultPath` string (optional) - Absolute directory path, absolute file
|
||||
path, or file name to use by default. If not provided, the dialog will
|
||||
default to the user's Downloads folder, or their home directory if Downloads
|
||||
doesn't exist.
|
||||
* `buttonLabel` string (optional) - Custom label for the confirmation button, when
|
||||
left empty the default label will be used.
|
||||
* `filters` [FileFilter[]](structures/file-filter.md) (optional)
|
||||
@@ -198,7 +204,9 @@ added:
|
||||
* `options` Object
|
||||
* `title` string (optional) - The dialog title. Cannot be displayed on some _Linux_ desktop environments.
|
||||
* `defaultPath` string (optional) - Absolute directory path, absolute file
|
||||
path, or file name to use by default.
|
||||
path, or file name to use by default. If not provided, the dialog will
|
||||
default to the user's Downloads folder, or their home directory if Downloads
|
||||
doesn't exist.
|
||||
* `buttonLabel` string (optional) - Custom label for the confirmation button, when
|
||||
left empty the default label will be used.
|
||||
* `filters` [FileFilter[]](structures/file-filter.md) (optional)
|
||||
@@ -238,7 +246,9 @@ changes:
|
||||
* `options` Object
|
||||
* `title` string (optional) - The dialog title. Cannot be displayed on some _Linux_ desktop environments.
|
||||
* `defaultPath` string (optional) - Absolute directory path, absolute file
|
||||
path, or file name to use by default.
|
||||
path, or file name to use by default. If not provided, the dialog will
|
||||
default to the user's Downloads folder, or their home directory if Downloads
|
||||
doesn't exist.
|
||||
* `buttonLabel` string (optional) - Custom label for the confirmation button, when
|
||||
left empty the default label will be used.
|
||||
* `filters` [FileFilter[]](structures/file-filter.md) (optional)
|
||||
|
||||
@@ -12,6 +12,34 @@ This document uses the following convention to categorize breaking changes:
|
||||
* **Deprecated:** An API was marked as deprecated. The API will continue to function, but will emit a deprecation warning, and will be removed in a future release.
|
||||
* **Removed:** An API or feature was removed, and is no longer supported by Electron.
|
||||
|
||||
## Planned Breaking API Changes (43.0)
|
||||
|
||||
### Behavior Changed: Dialog methods default to Downloads directory
|
||||
|
||||
The `defaultPath` option for the following methods now defaults to the user's Downloads folder (or their home directory if Downloads doesn't exist) when not explicitly provided:
|
||||
|
||||
* `dialog.showOpenDialog`
|
||||
* `dialog.showOpenDialogSync`
|
||||
* `dialog.showSaveDialog`
|
||||
* `dialog.showSaveDialogSync`
|
||||
|
||||
Previously, when no `defaultPath` was provided, the underlying OS file dialog would determine the initial directory — typically remembering the last directory the user navigated to, or falling back to an OS-specific default. Now, Electron explicitly sets the initial directory to Downloads, which also means the OS will no longer track and restore the last-used directory between dialog invocations.
|
||||
|
||||
To preserve the old behavior, you can track the last-used directory yourself and pass it as `defaultPath`:
|
||||
|
||||
```js
|
||||
const path = require('node:path')
|
||||
|
||||
let lastUsedPath
|
||||
const result = await dialog.showOpenDialog({
|
||||
defaultPath: lastUsedPath
|
||||
})
|
||||
|
||||
if (!result.canceled && result.filePaths.length > 0) {
|
||||
lastUsedPath = path.dirname(result.filePaths[0])
|
||||
}
|
||||
```
|
||||
|
||||
## Planned Breaking API Changes (42.0)
|
||||
|
||||
### Behavior Changed: macOS notifications now use `UNNotification` API
|
||||
|
||||
@@ -1,102 +0,0 @@
|
||||
// Drop-in replacement for timers-browserify@1.4.2.
|
||||
// Provides the Node.js 'timers' API surface for renderer/web webpack bundles
|
||||
// without relying on window.postMessage (which the newer timers-browserify 2.x
|
||||
// polyfill uses and can interfere with Electron IPC).
|
||||
|
||||
const immediateIds: Record<number, boolean> = {};
|
||||
let nextImmediateId = 0;
|
||||
|
||||
// --- DOM timer wrappers ------------------------------------------------
|
||||
// Wrap the global setTimeout/setInterval so we return Timeout objects that
|
||||
// expose ref/unref/close matching the Node.js API shape.
|
||||
|
||||
class Timeout {
|
||||
_id: ReturnType<typeof globalThis.setTimeout>;
|
||||
_clearFn: (id: ReturnType<typeof globalThis.setTimeout>) => void;
|
||||
|
||||
constructor (id: ReturnType<typeof globalThis.setTimeout>, clearFn: (id: ReturnType<typeof globalThis.setTimeout>) => void) {
|
||||
this._id = id;
|
||||
this._clearFn = clearFn;
|
||||
}
|
||||
|
||||
unref () {}
|
||||
ref () {}
|
||||
|
||||
close () {
|
||||
this._clearFn.call(globalThis, this._id);
|
||||
}
|
||||
}
|
||||
|
||||
export const setTimeout = function (...args: Parameters<typeof globalThis.setTimeout>): Timeout {
|
||||
return new Timeout(globalThis.setTimeout(...args), globalThis.clearTimeout);
|
||||
};
|
||||
|
||||
export const setInterval = function (...args: Parameters<typeof globalThis.setInterval>): Timeout {
|
||||
return new Timeout(globalThis.setInterval(...args), globalThis.clearInterval);
|
||||
};
|
||||
|
||||
export const clearTimeout = function (timeout: Timeout | undefined) {
|
||||
if (timeout) timeout.close();
|
||||
};
|
||||
|
||||
export const clearInterval = clearTimeout;
|
||||
|
||||
// --- Legacy enroll/unenroll (Node < 11 API, preserved for compatibility) ------
|
||||
|
||||
interface EnrollableItem {
|
||||
_idleTimeoutId?: ReturnType<typeof setTimeout>;
|
||||
_idleTimeout?: number;
|
||||
_onTimeout?: () => void;
|
||||
}
|
||||
|
||||
export const enroll = function (item: EnrollableItem, msecs: number) {
|
||||
clearTimeout(item._idleTimeoutId);
|
||||
item._idleTimeout = msecs;
|
||||
};
|
||||
|
||||
export const unenroll = function (item: EnrollableItem) {
|
||||
clearTimeout(item._idleTimeoutId);
|
||||
item._idleTimeout = -1;
|
||||
};
|
||||
|
||||
export const active = function (item: EnrollableItem) {
|
||||
clearTimeout(item._idleTimeoutId);
|
||||
|
||||
const msecs = item._idleTimeout;
|
||||
if (msecs !== undefined && msecs >= 0) {
|
||||
item._idleTimeoutId = setTimeout(function onTimeout () {
|
||||
if (item._onTimeout) item._onTimeout();
|
||||
}, msecs);
|
||||
}
|
||||
};
|
||||
|
||||
export const _unrefActive = active;
|
||||
|
||||
// --- setImmediate / clearImmediate -------------------------------------
|
||||
// Prefer the native implementations when available. Fall back to a
|
||||
// nextTick-based shim that avoids window.postMessage.
|
||||
|
||||
const clearImmediateFallback = function (id: number) {
|
||||
delete immediateIds[id];
|
||||
};
|
||||
|
||||
export const setImmediate = typeof globalThis.setImmediate === 'function'
|
||||
? globalThis.setImmediate
|
||||
: function (fn: (...args: unknown[]) => void, ...rest: unknown[]) {
|
||||
const id = nextImmediateId++;
|
||||
|
||||
immediateIds[id] = true;
|
||||
|
||||
Promise.resolve().then(function onMicrotask () {
|
||||
if (immediateIds[id]) {
|
||||
fn(...rest);
|
||||
clearImmediateFallback(id);
|
||||
}
|
||||
});
|
||||
|
||||
return id;
|
||||
};
|
||||
|
||||
export const clearImmediate = typeof globalThis.clearImmediate === 'function'
|
||||
? globalThis.clearImmediate
|
||||
: clearImmediateFallback;
|
||||
@@ -49,6 +49,7 @@
|
||||
"stream-json": "^1.9.1",
|
||||
"tap-xunit": "^2.4.1",
|
||||
"temp": "^0.9.4",
|
||||
"timers-browserify": "1.4.2",
|
||||
"ts-loader": "^8.0.2",
|
||||
"ts-node": "6.2.0",
|
||||
"typescript": "^5.8.3",
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
#include "shell/browser/javascript_environment.h"
|
||||
#include "shell/browser/native_window_views.h"
|
||||
#include "shell/browser/ui/file_dialog.h"
|
||||
#include "shell/common/electron_paths.h"
|
||||
#include "shell/common/gin_converters/callback_converter.h"
|
||||
#include "shell/common/gin_converters/file_path_converter.h"
|
||||
#include "shell/common/gin_helper/dictionary.h"
|
||||
@@ -81,14 +82,18 @@ class FileChooserDialog : public ui::SelectFileDialog::Listener {
|
||||
ui::SelectFileDialog::FileTypeInfo file_info =
|
||||
GetFilterInfo(settings.filters);
|
||||
ApplySettings(settings);
|
||||
dialog_->SelectFile(
|
||||
ui::SelectFileDialog::SELECT_SAVEAS_FILE,
|
||||
base::UTF8ToUTF16(settings.title), settings.default_path,
|
||||
&file_info /* file_types */, 0 /* file_type_index */,
|
||||
base::FilePath::StringType() /* default_extension */,
|
||||
settings.parent_window ? settings.parent_window->GetNativeWindow()
|
||||
: nullptr,
|
||||
nullptr);
|
||||
base::FilePath default_path = settings.default_path.empty()
|
||||
? electron::GetDefaultPath()
|
||||
: settings.default_path;
|
||||
|
||||
dialog_->SelectFile(ui::SelectFileDialog::SELECT_SAVEAS_FILE,
|
||||
base::UTF8ToUTF16(settings.title), default_path,
|
||||
&file_info /* file_types */, 0 /* file_type_index */,
|
||||
base::FilePath::StringType() /* default_extension */,
|
||||
settings.parent_window
|
||||
? settings.parent_window->GetNativeWindow()
|
||||
: nullptr,
|
||||
nullptr);
|
||||
}
|
||||
|
||||
void RunSaveDialog(gin_helper::Promise<gin_helper::Dictionary> promise,
|
||||
@@ -108,9 +113,13 @@ class FileChooserDialog : public ui::SelectFileDialog::Listener {
|
||||
ui::SelectFileDialog::FileTypeInfo file_info =
|
||||
GetFilterInfo(settings.filters);
|
||||
ApplySettings(settings);
|
||||
base::FilePath default_path = settings.default_path.empty()
|
||||
? electron::GetDefaultPath()
|
||||
: settings.default_path;
|
||||
|
||||
dialog_->SelectFile(
|
||||
GetDialogType(settings.properties), base::UTF8ToUTF16(settings.title),
|
||||
settings.default_path, &file_info, 0 /* file_type_index */,
|
||||
default_path, &file_info, 0 /* file_type_index */,
|
||||
base::FilePath::StringType() /* default_extension */,
|
||||
settings.parent_window ? settings.parent_window->GetNativeWindow()
|
||||
: nullptr,
|
||||
|
||||
@@ -21,6 +21,7 @@
|
||||
#include "content/public/browser/browser_thread.h"
|
||||
#include "electron/mas.h"
|
||||
#include "shell/browser/native_window.h"
|
||||
#include "shell/common/electron_paths.h"
|
||||
#include "shell/common/gin_converters/file_path_converter.h"
|
||||
#include "shell/common/gin_helper/dictionary.h"
|
||||
#include "shell/common/gin_helper/promise.h"
|
||||
@@ -186,14 +187,18 @@ void SetupDialog(NSSavePanel* dialog, const DialogSettings& settings) {
|
||||
|
||||
[dialog setShowsTagField:settings.shows_tag_field];
|
||||
|
||||
base::FilePath default_path = settings.default_path.empty()
|
||||
? electron::GetDefaultPath()
|
||||
: settings.default_path;
|
||||
|
||||
NSString* default_dir = nil;
|
||||
NSString* default_filename = nil;
|
||||
if (!settings.default_path.empty()) {
|
||||
if (!default_path.empty()) {
|
||||
electron::ScopedAllowBlockingForElectron allow_blocking;
|
||||
if (base::DirectoryExists(settings.default_path)) {
|
||||
default_dir = base::SysUTF8ToNSString(settings.default_path.value());
|
||||
if (base::DirectoryExists(default_path)) {
|
||||
default_dir = base::SysUTF8ToNSString(default_path.value());
|
||||
} else {
|
||||
if (settings.default_path.IsAbsolute()) {
|
||||
if (default_path.IsAbsolute()) {
|
||||
default_dir =
|
||||
base::SysUTF8ToNSString(settings.default_path.DirName().value());
|
||||
}
|
||||
|
||||
@@ -21,6 +21,7 @@
|
||||
#include "base/win/registry.h"
|
||||
#include "shell/browser/native_window_views.h"
|
||||
#include "shell/browser/ui/win/dialog_thread.h"
|
||||
#include "shell/common/electron_paths.h"
|
||||
#include "shell/common/gin_converters/file_path_converter.h"
|
||||
#include "shell/common/gin_helper/dictionary.h"
|
||||
#include "shell/common/gin_helper/promise.h"
|
||||
@@ -106,8 +107,12 @@ static HRESULT ShowFileDialog(IFileDialog* dialog,
|
||||
static void ApplySettings(IFileDialog* dialog, const DialogSettings& settings) {
|
||||
std::wstring file_part;
|
||||
|
||||
if (!IsDirectory(settings.default_path))
|
||||
file_part = settings.default_path.BaseName().value();
|
||||
base::FilePath default_path = settings.default_path.empty()
|
||||
? electron::GetDefaultPath()
|
||||
: settings.default_path;
|
||||
|
||||
if (!IsDirectory(default_path))
|
||||
file_part = default_path.BaseName().value();
|
||||
|
||||
dialog->SetFileName(file_part.c_str());
|
||||
|
||||
@@ -149,8 +154,8 @@ static void ApplySettings(IFileDialog* dialog, const DialogSettings& settings) {
|
||||
}
|
||||
}
|
||||
|
||||
if (settings.default_path.IsAbsolute()) {
|
||||
SetDefaultFolder(dialog, settings.default_path);
|
||||
if (default_path.IsAbsolute()) {
|
||||
SetDefaultFolder(dialog, default_path);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -114,4 +114,18 @@ void RegisterPathProvider() {
|
||||
PATH_END);
|
||||
}
|
||||
|
||||
base::FilePath GetDefaultPath() {
|
||||
base::FilePath path;
|
||||
|
||||
ScopedAllowBlockingForElectron allow_blocking;
|
||||
if (base::PathService::Get(chrome::DIR_DEFAULT_DOWNLOADS, &path) &&
|
||||
base::DirectoryExists(path))
|
||||
return path;
|
||||
|
||||
if (base::PathService::Get(base::DIR_HOME, &path))
|
||||
return path;
|
||||
|
||||
return base::FilePath();
|
||||
}
|
||||
|
||||
} // namespace electron
|
||||
|
||||
@@ -51,6 +51,10 @@ static_assert(PATH_START < PATH_END, "invalid PATH boundaries");
|
||||
// Register the path provider with the base::PathService.
|
||||
void RegisterPathProvider();
|
||||
|
||||
// Returns a default directory for file dialogs when no default path is
|
||||
// provided.
|
||||
base::FilePath GetDefaultPath();
|
||||
|
||||
} // namespace electron
|
||||
|
||||
#endif // ELECTRON_SHELL_COMMON_ELECTRON_PATHS_H_
|
||||
|
||||
12
yarn.lock
12
yarn.lock
@@ -602,6 +602,7 @@ __metadata:
|
||||
stream-json: "npm:^1.9.1"
|
||||
tap-xunit: "npm:^2.4.1"
|
||||
temp: "npm:^0.9.4"
|
||||
timers-browserify: "npm:1.4.2"
|
||||
ts-loader: "npm:^8.0.2"
|
||||
ts-node: "npm:6.2.0"
|
||||
typescript: "npm:^5.8.3"
|
||||
@@ -10613,7 +10614,7 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"process@npm:^0.11.10":
|
||||
"process@npm:^0.11.10, process@npm:~0.11.0":
|
||||
version: 0.11.10
|
||||
resolution: "process@npm:0.11.10"
|
||||
checksum: 10c0/40c3ce4b7e6d4b8c3355479df77aeed46f81b279818ccdc500124e6a5ab882c0cc81ff7ea16384873a95a74c4570b01b120f287abbdd4c877931460eca6084b3
|
||||
@@ -12461,6 +12462,15 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"timers-browserify@npm:1.4.2":
|
||||
version: 1.4.2
|
||||
resolution: "timers-browserify@npm:1.4.2"
|
||||
dependencies:
|
||||
process: "npm:~0.11.0"
|
||||
checksum: 10c0/96e9b6d629bbb8bed7c55745112d065a2abdc33f3c29354c62de2fb02916893994b28678d675cdfceb12ca8e26f5e77e41fda9b2aa449f74ba0bbf191735a656
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"tiny-async-pool@npm:^2.1.0":
|
||||
version: 2.1.0
|
||||
resolution: "tiny-async-pool@npm:2.1.0"
|
||||
|
||||
Reference in New Issue
Block a user