chore: complete v1.0 milestone
Archive 5 phases (36 plans) to milestones/v1.0-phases/. Archive roadmap, requirements, and audit to milestones/. Evolve PROJECT.md with shipped state and validated requirements. Collapse ROADMAP.md to one-line milestone summary. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,576 @@
|
||||
---
|
||||
phase: 04
|
||||
plan: 07
|
||||
title: Localization + Shared Dialogs + Example CSV Resources
|
||||
status: pending
|
||||
wave: 2
|
||||
depends_on:
|
||||
- 04-02
|
||||
- 04-03
|
||||
- 04-04
|
||||
- 04-05
|
||||
- 04-06
|
||||
files_modified:
|
||||
- SharepointToolbox/Localization/Strings.resx
|
||||
- SharepointToolbox/Localization/Strings.fr.resx
|
||||
- SharepointToolbox/Localization/Strings.Designer.cs
|
||||
- SharepointToolbox/Views/Dialogs/ConfirmBulkOperationDialog.xaml
|
||||
- SharepointToolbox/Views/Dialogs/ConfirmBulkOperationDialog.xaml.cs
|
||||
- SharepointToolbox/Views/Dialogs/FolderBrowserDialog.xaml
|
||||
- SharepointToolbox/Views/Dialogs/FolderBrowserDialog.xaml.cs
|
||||
- SharepointToolbox/Resources/bulk_add_members.csv
|
||||
- SharepointToolbox/Resources/bulk_create_sites.csv
|
||||
- SharepointToolbox/Resources/folder_structure.csv
|
||||
- SharepointToolbox/SharepointToolbox.csproj
|
||||
autonomous: true
|
||||
requirements:
|
||||
- FOLD-02
|
||||
|
||||
must_haves:
|
||||
truths:
|
||||
- "All Phase 4 EN/FR localization keys exist in Strings.resx and Strings.fr.resx"
|
||||
- "Strings.Designer.cs has ResourceManager accessor for new keys"
|
||||
- "ConfirmBulkOperationDialog shows operation summary and Proceed/Cancel buttons"
|
||||
- "FolderBrowserDialog shows a TreeView of SharePoint libraries and folders"
|
||||
- "Example CSV files are embedded resources accessible at runtime"
|
||||
artifacts:
|
||||
- path: "SharepointToolbox/Views/Dialogs/ConfirmBulkOperationDialog.xaml"
|
||||
provides: "Pre-write confirmation dialog"
|
||||
- path: "SharepointToolbox/Views/Dialogs/FolderBrowserDialog.xaml"
|
||||
provides: "Library/folder tree browser for file transfer"
|
||||
- path: "SharepointToolbox/Resources/bulk_add_members.csv"
|
||||
provides: "Example CSV for bulk member addition"
|
||||
key_links:
|
||||
- from: "ConfirmBulkOperationDialog.xaml.cs"
|
||||
to: "TranslationSource"
|
||||
via: "localized button text and labels"
|
||||
pattern: "TranslationSource.Instance"
|
||||
- from: "Strings.Designer.cs"
|
||||
to: "Strings.resx"
|
||||
via: "ResourceManager property accessor"
|
||||
pattern: "ResourceManager"
|
||||
---
|
||||
|
||||
# Plan 04-07: Localization + Shared Dialogs + Example CSV Resources
|
||||
|
||||
## Goal
|
||||
|
||||
Add all Phase 4 EN/FR localization keys, create the ConfirmBulkOperationDialog and FolderBrowserDialog XAML dialogs, and bundle example CSV files as embedded resources. This plan creates shared infrastructure needed by all 5 tab ViewModels/Views.
|
||||
|
||||
## Context
|
||||
|
||||
Localization follows the established pattern: keys in `Strings.resx` (EN) and `Strings.fr.resx` (FR), accessor methods in `Strings.Designer.cs` (maintained manually per Phase 1 decision). UI strings use `TranslationSource.Instance[key]` in XAML.
|
||||
|
||||
Existing dialogs: `ProfileManagementDialog` and `SitePickerDialog` in `Views/Dialogs/`.
|
||||
|
||||
Example CSVs exist in `/examples/` directory. Need to copy to `Resources/` and mark as EmbeddedResource in .csproj.
|
||||
|
||||
## Tasks
|
||||
|
||||
### Task 1: Add all Phase 4 localization keys + Strings.Designer.cs update
|
||||
|
||||
**Files:**
|
||||
- `SharepointToolbox/Localization/Strings.resx`
|
||||
- `SharepointToolbox/Localization/Strings.fr.resx`
|
||||
- `SharepointToolbox/Localization/Strings.Designer.cs`
|
||||
|
||||
**Action:**
|
||||
|
||||
Add the following keys to `Strings.resx` (EN values) and `Strings.fr.resx` (FR values). Do NOT remove existing keys — append only.
|
||||
|
||||
**New keys for Strings.resx (EN):**
|
||||
|
||||
```
|
||||
<!-- Phase 4: Tab headers -->
|
||||
tab.transfer = Transfer
|
||||
tab.bulkMembers = Bulk Members
|
||||
tab.bulkSites = Bulk Sites
|
||||
tab.folderStructure = Folder Structure
|
||||
|
||||
<!-- Phase 4: Transfer tab -->
|
||||
transfer.sourcesite = Source Site
|
||||
transfer.destsite = Destination Site
|
||||
transfer.sourcelibrary = Source Library
|
||||
transfer.destlibrary = Destination Library
|
||||
transfer.sourcefolder = Source Folder
|
||||
transfer.destfolder = Destination Folder
|
||||
transfer.mode = Transfer Mode
|
||||
transfer.mode.copy = Copy
|
||||
transfer.mode.move = Move
|
||||
transfer.conflict = Conflict Policy
|
||||
transfer.conflict.skip = Skip
|
||||
transfer.conflict.overwrite = Overwrite
|
||||
transfer.conflict.rename = Rename (append suffix)
|
||||
transfer.browse = Browse...
|
||||
transfer.start = Start Transfer
|
||||
transfer.nofiles = No files found to transfer.
|
||||
|
||||
<!-- Phase 4: Bulk Members tab -->
|
||||
bulkmembers.import = Import CSV
|
||||
bulkmembers.example = Load Example
|
||||
bulkmembers.execute = Add Members
|
||||
bulkmembers.preview = Preview ({0} rows, {1} valid, {2} invalid)
|
||||
bulkmembers.groupname = Group Name
|
||||
bulkmembers.groupurl = Group URL
|
||||
bulkmembers.email = Email
|
||||
bulkmembers.role = Role
|
||||
|
||||
<!-- Phase 4: Bulk Sites tab -->
|
||||
bulksites.import = Import CSV
|
||||
bulksites.example = Load Example
|
||||
bulksites.execute = Create Sites
|
||||
bulksites.preview = Preview ({0} rows, {1} valid, {2} invalid)
|
||||
bulksites.name = Name
|
||||
bulksites.alias = Alias
|
||||
bulksites.type = Type
|
||||
bulksites.owners = Owners
|
||||
bulksites.members = Members
|
||||
|
||||
<!-- Phase 4: Folder Structure tab -->
|
||||
folderstruct.import = Import CSV
|
||||
folderstruct.example = Load Example
|
||||
folderstruct.execute = Create Folders
|
||||
folderstruct.preview = Preview ({0} folders to create)
|
||||
folderstruct.library = Target Library
|
||||
folderstruct.siteurl = Site URL
|
||||
|
||||
<!-- Phase 4: Templates tab -->
|
||||
templates.list = Saved Templates
|
||||
templates.capture = Capture Template
|
||||
templates.apply = Apply Template
|
||||
templates.rename = Rename
|
||||
templates.delete = Delete
|
||||
templates.siteurl = Source Site URL
|
||||
templates.name = Template Name
|
||||
templates.newtitle = New Site Title
|
||||
templates.newalias = New Site Alias
|
||||
templates.options = Capture Options
|
||||
templates.opt.libraries = Libraries
|
||||
templates.opt.folders = Folders
|
||||
templates.opt.permissions = Permission Groups
|
||||
templates.opt.logo = Site Logo
|
||||
templates.opt.settings = Site Settings
|
||||
templates.empty = No templates saved yet.
|
||||
|
||||
<!-- Phase 4: Shared bulk operation strings -->
|
||||
bulk.confirm.title = Confirm Operation
|
||||
bulk.confirm.proceed = Proceed
|
||||
bulk.confirm.cancel = Cancel
|
||||
bulk.confirm.message = {0} — Proceed?
|
||||
bulk.result.success = Completed: {0} succeeded, {1} failed
|
||||
bulk.result.allfailed = All {0} items failed.
|
||||
bulk.result.allsuccess = All {0} items completed successfully.
|
||||
bulk.exportfailed = Export Failed Items
|
||||
bulk.retryfailed = Retry Failed
|
||||
bulk.validation.invalid = {0} rows have validation errors. Fix and re-import.
|
||||
bulk.csvimport.title = Select CSV File
|
||||
bulk.csvimport.filter = CSV Files (*.csv)|*.csv
|
||||
|
||||
<!-- Phase 4: Folder browser dialog -->
|
||||
folderbrowser.title = Select Folder
|
||||
folderbrowser.loading = Loading folder tree...
|
||||
folderbrowser.select = Select
|
||||
folderbrowser.cancel = Cancel
|
||||
```
|
||||
|
||||
**New keys for Strings.fr.resx (FR):**
|
||||
|
||||
```
|
||||
tab.transfer = Transfert
|
||||
tab.bulkMembers = Ajout en masse
|
||||
tab.bulkSites = Sites en masse
|
||||
tab.folderStructure = Structure de dossiers
|
||||
|
||||
transfer.sourcesite = Site source
|
||||
transfer.destsite = Site destination
|
||||
transfer.sourcelibrary = Bibliotheque source
|
||||
transfer.destlibrary = Bibliotheque destination
|
||||
transfer.sourcefolder = Dossier source
|
||||
transfer.destfolder = Dossier destination
|
||||
transfer.mode = Mode de transfert
|
||||
transfer.mode.copy = Copier
|
||||
transfer.mode.move = Deplacer
|
||||
transfer.conflict = Politique de conflit
|
||||
transfer.conflict.skip = Ignorer
|
||||
transfer.conflict.overwrite = Ecraser
|
||||
transfer.conflict.rename = Renommer (ajouter suffixe)
|
||||
transfer.browse = Parcourir...
|
||||
transfer.start = Demarrer le transfert
|
||||
transfer.nofiles = Aucun fichier a transferer.
|
||||
|
||||
bulkmembers.import = Importer CSV
|
||||
bulkmembers.example = Charger l'exemple
|
||||
bulkmembers.execute = Ajouter les membres
|
||||
bulkmembers.preview = Apercu ({0} lignes, {1} valides, {2} invalides)
|
||||
bulkmembers.groupname = Nom du groupe
|
||||
bulkmembers.groupurl = URL du groupe
|
||||
bulkmembers.email = Courriel
|
||||
bulkmembers.role = Role
|
||||
|
||||
bulksites.import = Importer CSV
|
||||
bulksites.example = Charger l'exemple
|
||||
bulksites.execute = Creer les sites
|
||||
bulksites.preview = Apercu ({0} lignes, {1} valides, {2} invalides)
|
||||
bulksites.name = Nom
|
||||
bulksites.alias = Alias
|
||||
bulksites.type = Type
|
||||
bulksites.owners = Proprietaires
|
||||
bulksites.members = Membres
|
||||
|
||||
folderstruct.import = Importer CSV
|
||||
folderstruct.example = Charger l'exemple
|
||||
folderstruct.execute = Creer les dossiers
|
||||
folderstruct.preview = Apercu ({0} dossiers a creer)
|
||||
folderstruct.library = Bibliotheque cible
|
||||
folderstruct.siteurl = URL du site
|
||||
|
||||
templates.list = Modeles enregistres
|
||||
templates.capture = Capturer un modele
|
||||
templates.apply = Appliquer le modele
|
||||
templates.rename = Renommer
|
||||
templates.delete = Supprimer
|
||||
templates.siteurl = URL du site source
|
||||
templates.name = Nom du modele
|
||||
templates.newtitle = Titre du nouveau site
|
||||
templates.newalias = Alias du nouveau site
|
||||
templates.options = Options de capture
|
||||
templates.opt.libraries = Bibliotheques
|
||||
templates.opt.folders = Dossiers
|
||||
templates.opt.permissions = Groupes de permissions
|
||||
templates.opt.logo = Logo du site
|
||||
templates.opt.settings = Parametres du site
|
||||
templates.empty = Aucun modele enregistre.
|
||||
|
||||
bulk.confirm.title = Confirmer l'operation
|
||||
bulk.confirm.proceed = Continuer
|
||||
bulk.confirm.cancel = Annuler
|
||||
bulk.confirm.message = {0} — Continuer ?
|
||||
bulk.result.success = Termine : {0} reussis, {1} echoues
|
||||
bulk.result.allfailed = Les {0} elements ont echoue.
|
||||
bulk.result.allsuccess = Les {0} elements ont ete traites avec succes.
|
||||
bulk.exportfailed = Exporter les elements echoues
|
||||
bulk.retryfailed = Reessayer les echecs
|
||||
bulk.validation.invalid = {0} lignes contiennent des erreurs. Corrigez et reimportez.
|
||||
bulk.csvimport.title = Selectionner un fichier CSV
|
||||
bulk.csvimport.filter = Fichiers CSV (*.csv)|*.csv
|
||||
|
||||
folderbrowser.title = Selectionner un dossier
|
||||
folderbrowser.loading = Chargement de l'arborescence...
|
||||
folderbrowser.select = Selectionner
|
||||
folderbrowser.cancel = Annuler
|
||||
```
|
||||
|
||||
Update `Strings.Designer.cs` — add ResourceManager property accessors for all new keys. Follow the exact pattern of existing entries (static property with `ResourceManager.GetString`). Since there are many keys, the executor should add all keys programmatically following the existing pattern in the file.
|
||||
|
||||
**Verify:**
|
||||
```bash
|
||||
dotnet build SharepointToolbox/SharepointToolbox.csproj --no-restore -q
|
||||
```
|
||||
|
||||
**Done:** All localization keys compile. EN and FR values present.
|
||||
|
||||
### Task 2: Create shared dialogs + bundle example CSVs
|
||||
|
||||
**Files:**
|
||||
- `SharepointToolbox/Views/Dialogs/ConfirmBulkOperationDialog.xaml`
|
||||
- `SharepointToolbox/Views/Dialogs/ConfirmBulkOperationDialog.xaml.cs`
|
||||
- `SharepointToolbox/Views/Dialogs/FolderBrowserDialog.xaml`
|
||||
- `SharepointToolbox/Views/Dialogs/FolderBrowserDialog.xaml.cs`
|
||||
- `SharepointToolbox/Resources/bulk_add_members.csv`
|
||||
- `SharepointToolbox/Resources/bulk_create_sites.csv`
|
||||
- `SharepointToolbox/Resources/folder_structure.csv`
|
||||
- `SharepointToolbox/SharepointToolbox.csproj`
|
||||
|
||||
**Action:**
|
||||
|
||||
1. Create `ConfirmBulkOperationDialog.xaml`:
|
||||
```xml
|
||||
<Window x:Class="SharepointToolbox.Views.Dialogs.ConfirmBulkOperationDialog"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:loc="clr-namespace:SharepointToolbox.Localization"
|
||||
Title="{Binding Source={x:Static loc:TranslationSource.Instance}, Path=[bulk.confirm.title]}"
|
||||
Width="450" Height="220" WindowStartupLocation="CenterOwner"
|
||||
ResizeMode="NoResize">
|
||||
<Grid Margin="20">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="*" />
|
||||
<RowDefinition Height="Auto" />
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<TextBlock x:Name="MessageText" Grid.Row="0"
|
||||
TextWrapping="Wrap" FontSize="14"
|
||||
VerticalAlignment="Center" />
|
||||
|
||||
<StackPanel Grid.Row="1" Orientation="Horizontal"
|
||||
HorizontalAlignment="Right" Margin="0,20,0,0">
|
||||
<Button Content="{Binding Source={x:Static loc:TranslationSource.Instance}, Path=[bulk.confirm.cancel]}"
|
||||
Width="100" Margin="0,0,10,0" IsCancel="True"
|
||||
Click="Cancel_Click" />
|
||||
<Button x:Name="ProceedButton"
|
||||
Content="{Binding Source={x:Static loc:TranslationSource.Instance}, Path=[bulk.confirm.proceed]}"
|
||||
Width="100" IsDefault="True"
|
||||
Click="Proceed_Click" />
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
</Window>
|
||||
```
|
||||
|
||||
2. Create `ConfirmBulkOperationDialog.xaml.cs`:
|
||||
```csharp
|
||||
using System.Windows;
|
||||
|
||||
namespace SharepointToolbox.Views.Dialogs;
|
||||
|
||||
public partial class ConfirmBulkOperationDialog : Window
|
||||
{
|
||||
public bool IsConfirmed { get; private set; }
|
||||
|
||||
public ConfirmBulkOperationDialog(string message)
|
||||
{
|
||||
InitializeComponent();
|
||||
MessageText.Text = message;
|
||||
}
|
||||
|
||||
private void Proceed_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
IsConfirmed = true;
|
||||
DialogResult = true;
|
||||
Close();
|
||||
}
|
||||
|
||||
private void Cancel_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
IsConfirmed = false;
|
||||
DialogResult = false;
|
||||
Close();
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
3. Create `FolderBrowserDialog.xaml`:
|
||||
```xml
|
||||
<Window x:Class="SharepointToolbox.Views.Dialogs.FolderBrowserDialog"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:loc="clr-namespace:SharepointToolbox.Localization"
|
||||
Title="{Binding Source={x:Static loc:TranslationSource.Instance}, Path=[folderbrowser.title]}"
|
||||
Width="400" Height="500" WindowStartupLocation="CenterOwner"
|
||||
ResizeMode="CanResizeWithGrip">
|
||||
<DockPanel Margin="10">
|
||||
<!-- Status -->
|
||||
<TextBlock x:Name="StatusText" DockPanel.Dock="Top" Margin="0,0,0,10"
|
||||
Text="{Binding Source={x:Static loc:TranslationSource.Instance}, Path=[folderbrowser.loading]}" />
|
||||
|
||||
<!-- Buttons -->
|
||||
<StackPanel DockPanel.Dock="Bottom" Orientation="Horizontal"
|
||||
HorizontalAlignment="Right" Margin="0,10,0,0">
|
||||
<Button Content="{Binding Source={x:Static loc:TranslationSource.Instance}, Path=[folderbrowser.cancel]}"
|
||||
Width="80" Margin="0,0,10,0" IsCancel="True"
|
||||
Click="Cancel_Click" />
|
||||
<Button x:Name="SelectButton"
|
||||
Content="{Binding Source={x:Static loc:TranslationSource.Instance}, Path=[folderbrowser.select]}"
|
||||
Width="80" IsDefault="True" IsEnabled="False"
|
||||
Click="Select_Click" />
|
||||
</StackPanel>
|
||||
|
||||
<!-- Tree -->
|
||||
<TreeView x:Name="FolderTree" SelectedItemChanged="FolderTree_SelectedItemChanged" />
|
||||
</DockPanel>
|
||||
</Window>
|
||||
```
|
||||
|
||||
4. Create `FolderBrowserDialog.xaml.cs`:
|
||||
```csharp
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using Microsoft.SharePoint.Client;
|
||||
using SharepointToolbox.Infrastructure.Auth;
|
||||
|
||||
namespace SharepointToolbox.Views.Dialogs;
|
||||
|
||||
public partial class FolderBrowserDialog : Window
|
||||
{
|
||||
private readonly ClientContext _ctx;
|
||||
public string SelectedLibrary { get; private set; } = string.Empty;
|
||||
public string SelectedFolderPath { get; private set; } = string.Empty;
|
||||
|
||||
public FolderBrowserDialog(ClientContext ctx)
|
||||
{
|
||||
InitializeComponent();
|
||||
_ctx = ctx;
|
||||
Loaded += OnLoaded;
|
||||
}
|
||||
|
||||
private async void OnLoaded(object sender, RoutedEventArgs e)
|
||||
{
|
||||
try
|
||||
{
|
||||
// Load libraries
|
||||
var web = _ctx.Web;
|
||||
var lists = _ctx.LoadQuery(web.Lists
|
||||
.Include(l => l.Title, l => l.Hidden, l => l.BaseType, l => l.RootFolder)
|
||||
.Where(l => !l.Hidden && l.BaseType == BaseType.DocumentLibrary));
|
||||
var progress = new Progress<Core.Models.OperationProgress>();
|
||||
await ExecuteQueryRetryHelper.ExecuteQueryRetryAsync(_ctx, progress, CancellationToken.None);
|
||||
|
||||
foreach (var list in lists)
|
||||
{
|
||||
var libNode = new TreeViewItem
|
||||
{
|
||||
Header = list.Title,
|
||||
Tag = new FolderNodeInfo(list.Title, string.Empty),
|
||||
};
|
||||
// Add dummy child for expand arrow
|
||||
libNode.Items.Add(new TreeViewItem { Header = "Loading..." });
|
||||
libNode.Expanded += LibNode_Expanded;
|
||||
FolderTree.Items.Add(libNode);
|
||||
}
|
||||
|
||||
StatusText.Text = $"{FolderTree.Items.Count} libraries loaded.";
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
StatusText.Text = $"Error: {ex.Message}";
|
||||
}
|
||||
}
|
||||
|
||||
private async void LibNode_Expanded(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (sender is not TreeViewItem node || node.Tag is not FolderNodeInfo info)
|
||||
return;
|
||||
|
||||
// Only load children once
|
||||
if (node.Items.Count == 1 && node.Items[0] is TreeViewItem dummy && dummy.Header?.ToString() == "Loading...")
|
||||
{
|
||||
node.Items.Clear();
|
||||
try
|
||||
{
|
||||
var folderUrl = string.IsNullOrEmpty(info.FolderPath)
|
||||
? GetLibraryRootUrl(info.LibraryTitle)
|
||||
: info.FolderPath;
|
||||
|
||||
var folder = _ctx.Web.GetFolderByServerRelativeUrl(folderUrl);
|
||||
_ctx.Load(folder, f => f.Folders.Include(sf => sf.Name, sf => sf.ServerRelativeUrl));
|
||||
var progress = new Progress<Core.Models.OperationProgress>();
|
||||
await ExecuteQueryRetryHelper.ExecuteQueryRetryAsync(_ctx, progress, CancellationToken.None);
|
||||
|
||||
foreach (var subFolder in folder.Folders)
|
||||
{
|
||||
if (subFolder.Name.StartsWith("_") || subFolder.Name == "Forms")
|
||||
continue;
|
||||
|
||||
var childNode = new TreeViewItem
|
||||
{
|
||||
Header = subFolder.Name,
|
||||
Tag = new FolderNodeInfo(info.LibraryTitle, subFolder.ServerRelativeUrl),
|
||||
};
|
||||
childNode.Items.Add(new TreeViewItem { Header = "Loading..." });
|
||||
childNode.Expanded += LibNode_Expanded;
|
||||
node.Items.Add(childNode);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
node.Items.Add(new TreeViewItem { Header = $"Error: {ex.Message}" });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private string GetLibraryRootUrl(string libraryTitle)
|
||||
{
|
||||
var uri = new Uri(_ctx.Url);
|
||||
return $"{uri.AbsolutePath.TrimEnd('/')}/{libraryTitle}";
|
||||
}
|
||||
|
||||
private void FolderTree_SelectedItemChanged(object sender, RoutedPropertyChangedEventArgs<object> e)
|
||||
{
|
||||
if (e.NewValue is TreeViewItem node && node.Tag is FolderNodeInfo info)
|
||||
{
|
||||
SelectedLibrary = info.LibraryTitle;
|
||||
SelectedFolderPath = info.FolderPath;
|
||||
SelectButton.IsEnabled = true;
|
||||
}
|
||||
}
|
||||
|
||||
private void Select_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
DialogResult = true;
|
||||
Close();
|
||||
}
|
||||
|
||||
private void Cancel_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
DialogResult = false;
|
||||
Close();
|
||||
}
|
||||
|
||||
private record FolderNodeInfo(string LibraryTitle, string FolderPath);
|
||||
}
|
||||
```
|
||||
|
||||
5. Bundle example CSVs as embedded resources. Create `SharepointToolbox/Resources/` directory and copy the example CSVs there with extended schemas.
|
||||
|
||||
Create `Resources/bulk_add_members.csv`:
|
||||
```
|
||||
GroupName,GroupUrl,Email,Role
|
||||
Marketing Team,https://contoso.sharepoint.com/sites/Marketing,user1@contoso.com,Member
|
||||
Marketing Team,https://contoso.sharepoint.com/sites/Marketing,manager@contoso.com,Owner
|
||||
HR Team,https://contoso.sharepoint.com/sites/HR,hr-admin@contoso.com,Owner
|
||||
HR Team,https://contoso.sharepoint.com/sites/HR,recruiter@contoso.com,Member
|
||||
HR Team,https://contoso.sharepoint.com/sites/HR,analyst@contoso.com,Member
|
||||
IT Support,https://contoso.sharepoint.com/sites/IT,sysadmin@contoso.com,Owner
|
||||
IT Support,https://contoso.sharepoint.com/sites/IT,helpdesk@contoso.com,Member
|
||||
```
|
||||
|
||||
Create `Resources/bulk_create_sites.csv` (keep semicolon delimiter matching existing example):
|
||||
```
|
||||
Name;Alias;Type;Template;Owners;Members
|
||||
Projet Alpha;projet-alpha;Team;;admin@contoso.com;user1@contoso.com, user2@contoso.com
|
||||
Projet Beta;projet-beta;Team;;admin@contoso.com;user3@contoso.com, user4@contoso.com
|
||||
Communication RH;comm-rh;Communication;;rh-admin@contoso.com;manager1@contoso.com, manager2@contoso.com
|
||||
Equipe Marketing;equipe-marketing;Team;;marketing-lead@contoso.com;designer@contoso.com, redacteur@contoso.com
|
||||
Portail Intranet;portail-intranet;Communication;;it-admin@contoso.com;
|
||||
```
|
||||
|
||||
Create `Resources/folder_structure.csv` (copy from existing example):
|
||||
```
|
||||
Level1;Level2;Level3;Level4
|
||||
Administration;;;
|
||||
Administration;Comptabilite;;
|
||||
Administration;Comptabilite;Factures;
|
||||
Administration;Comptabilite;Bilans;
|
||||
Administration;Ressources Humaines;;
|
||||
Administration;Ressources Humaines;Contrats;
|
||||
Administration;Ressources Humaines;Fiches de paie;
|
||||
Projets;;;
|
||||
Projets;Projet Alpha;;
|
||||
Projets;Projet Alpha;Documents;
|
||||
Projets;Projet Alpha;Livrables;
|
||||
Projets;Projet Beta;;
|
||||
Projets;Projet Beta;Documents;
|
||||
Communication;;;
|
||||
Communication;Interne;;
|
||||
Communication;Interne;Notes de service;
|
||||
Communication;Externe;;
|
||||
Communication;Externe;Communiques de presse;
|
||||
Communication;Externe;Newsletter;
|
||||
```
|
||||
|
||||
6. Add EmbeddedResource entries to `SharepointToolbox.csproj`:
|
||||
```xml
|
||||
<ItemGroup>
|
||||
<EmbeddedResource Include="Resources\bulk_add_members.csv" />
|
||||
<EmbeddedResource Include="Resources\bulk_create_sites.csv" />
|
||||
<EmbeddedResource Include="Resources\folder_structure.csv" />
|
||||
</ItemGroup>
|
||||
```
|
||||
|
||||
**Verify:**
|
||||
```bash
|
||||
dotnet build SharepointToolbox/SharepointToolbox.csproj --no-restore -q
|
||||
```
|
||||
|
||||
**Done:** All localization keys added (EN + FR). ConfirmBulkOperationDialog and FolderBrowserDialog compile. Example CSVs bundled as embedded resources. All new XAML dialogs compile.
|
||||
|
||||
**Commit:** `feat(04-07): add Phase 4 localization, shared dialogs, and example CSV resources`
|
||||
Reference in New Issue
Block a user