chore: release v2.4
- Add theme system (Dark/Light palettes, ModernTheme, ThemeManager) - Add InputDialog, Spinner common view - Add DuplicatesCsvExportService - Refresh views, dialogs, and view models across tabs - Update localization strings (en/fr) - Tweak services (transfer, permissions, search, user access, ownership elevation, bulk operations) Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -93,8 +93,7 @@ public class TemplateService : ITemplateService
|
||||
{
|
||||
ctx.Load(list.RootFolder, f => f.ServerRelativeUrl);
|
||||
await ExecuteQueryRetryHelper.ExecuteQueryRetryAsync(ctx, progress, ct);
|
||||
libInfo.Folders = await EnumerateFoldersRecursiveAsync(
|
||||
ctx, list.RootFolder, string.Empty, progress, ct);
|
||||
libInfo.Folders = await EnumerateLibraryFoldersAsync(ctx, list, ct);
|
||||
}
|
||||
|
||||
template.Libraries.Add(libInfo);
|
||||
@@ -293,39 +292,72 @@ public class TemplateService : ITemplateService
|
||||
return siteUrl;
|
||||
}
|
||||
|
||||
private async Task<List<TemplateFolderInfo>> EnumerateFoldersRecursiveAsync(
|
||||
/// <summary>
|
||||
/// Enumerates every folder in a library via one paginated CAML scan, then
|
||||
/// reconstructs the hierarchy from the server-relative paths. Replaces the
|
||||
/// former per-level Folder.Folders lazy loading, which hits the list-view
|
||||
/// threshold on libraries above 5,000 items.
|
||||
/// </summary>
|
||||
private static async Task<List<TemplateFolderInfo>> EnumerateLibraryFoldersAsync(
|
||||
ClientContext ctx,
|
||||
Folder parentFolder,
|
||||
string parentRelativePath,
|
||||
IProgress<OperationProgress> progress,
|
||||
List list,
|
||||
CancellationToken ct)
|
||||
{
|
||||
ct.ThrowIfCancellationRequested();
|
||||
var result = new List<TemplateFolderInfo>();
|
||||
|
||||
ctx.Load(parentFolder, f => f.Folders.Include(sf => sf.Name, sf => sf.ServerRelativeUrl));
|
||||
await ExecuteQueryRetryHelper.ExecuteQueryRetryAsync(ctx, progress, ct);
|
||||
var rootUrl = list.RootFolder.ServerRelativeUrl.TrimEnd('/');
|
||||
|
||||
foreach (var subFolder in parentFolder.Folders)
|
||||
// Collect all folders flat: (relativePath, parentRelativePath).
|
||||
var folders = new List<(string Relative, string Parent)>();
|
||||
|
||||
await foreach (var item in SharePointPaginationHelper.GetItemsInFolderAsync(
|
||||
ctx, list, rootUrl, recursive: true,
|
||||
viewFields: new[] { "FSObjType", "FileLeafRef", "FileRef", "FileDirRef" },
|
||||
ct: ct))
|
||||
{
|
||||
// Skip system folders
|
||||
if (subFolder.Name.StartsWith("_") || subFolder.Name == "Forms")
|
||||
if (item["FSObjType"]?.ToString() != "1") continue; // folders only
|
||||
|
||||
var name = item["FileLeafRef"]?.ToString() ?? string.Empty;
|
||||
var fileRef = (item["FileRef"]?.ToString() ?? string.Empty).TrimEnd('/');
|
||||
var dirRef = (item["FileDirRef"]?.ToString() ?? string.Empty).TrimEnd('/');
|
||||
|
||||
if (string.IsNullOrEmpty(name) || string.IsNullOrEmpty(fileRef)) continue;
|
||||
if (name.StartsWith("_", StringComparison.Ordinal) ||
|
||||
name.Equals("Forms", StringComparison.OrdinalIgnoreCase))
|
||||
continue;
|
||||
|
||||
var relativePath = string.IsNullOrEmpty(parentRelativePath)
|
||||
? subFolder.Name
|
||||
: $"{parentRelativePath}/{subFolder.Name}";
|
||||
// Paths relative to the library root.
|
||||
var rel = fileRef.StartsWith(rootUrl, StringComparison.OrdinalIgnoreCase)
|
||||
? fileRef.Substring(rootUrl.Length).TrimStart('/')
|
||||
: name;
|
||||
var parentRel = dirRef.StartsWith(rootUrl, StringComparison.OrdinalIgnoreCase)
|
||||
? dirRef.Substring(rootUrl.Length).TrimStart('/')
|
||||
: string.Empty;
|
||||
|
||||
var folderInfo = new TemplateFolderInfo
|
||||
{
|
||||
Name = subFolder.Name,
|
||||
RelativePath = relativePath,
|
||||
Children = await EnumerateFoldersRecursiveAsync(ctx, subFolder, relativePath, progress, ct),
|
||||
};
|
||||
result.Add(folderInfo);
|
||||
folders.Add((rel, parentRel));
|
||||
}
|
||||
|
||||
return result;
|
||||
// Build tree keyed by relative path.
|
||||
var nodes = folders.ToDictionary(
|
||||
f => f.Relative,
|
||||
f => new TemplateFolderInfo
|
||||
{
|
||||
Name = System.IO.Path.GetFileName(f.Relative),
|
||||
RelativePath = f.Relative,
|
||||
Children = new List<TemplateFolderInfo>(),
|
||||
},
|
||||
StringComparer.OrdinalIgnoreCase);
|
||||
|
||||
var roots = new List<TemplateFolderInfo>();
|
||||
foreach (var (rel, parent) in folders)
|
||||
{
|
||||
if (!nodes.TryGetValue(rel, out var node)) continue;
|
||||
if (!string.IsNullOrEmpty(parent) && nodes.TryGetValue(parent, out var p))
|
||||
p.Children.Add(node);
|
||||
else
|
||||
roots.Add(node);
|
||||
}
|
||||
return roots;
|
||||
}
|
||||
|
||||
private static async Task CreateFoldersFromTemplateAsync(
|
||||
|
||||
Reference in New Issue
Block a user