diff --git a/src/SeoToolkit.Umbraco.Redirects.Core/Composers/RedirectsComposer.cs b/src/SeoToolkit.Umbraco.Redirects.Core/Composers/RedirectsComposer.cs index 58891e0d..de000372 100644 --- a/src/SeoToolkit.Umbraco.Redirects.Core/Composers/RedirectsComposer.cs +++ b/src/SeoToolkit.Umbraco.Redirects.Core/Composers/RedirectsComposer.cs @@ -8,12 +8,12 @@ using Umbraco.Cms.Web.Common.ApplicationBuilder; using Umbraco.Extensions; using SeoToolkit.Umbraco.Common.Core.Constants; -using SeoToolkit.Umbraco.Common.Core.Extensions; using SeoToolkit.Umbraco.Common.Core.Services.SettingsService; using SeoToolkit.Umbraco.Redirects.Core.Components; using SeoToolkit.Umbraco.Redirects.Core.Config; using SeoToolkit.Umbraco.Redirects.Core.Config.Models; using SeoToolkit.Umbraco.Redirects.Core.Controllers; +using SeoToolkit.Umbraco.Redirects.Core.Helpers; using SeoToolkit.Umbraco.Redirects.Core.Interfaces; using SeoToolkit.Umbraco.Redirects.Core.Middleware; using SeoToolkit.Umbraco.Redirects.Core.Repositories; @@ -42,11 +42,12 @@ public void Compose(IUmbracoBuilder builder) { builder.Trees().RemoveTreeController(); } - + builder.Components().Append(); builder.Services.AddUnique(); builder.Services.AddUnique(); + builder.Services.AddTransient(); if (!disabledModules.Contains(DisabledModuleConstant.Middleware)) { diff --git a/src/SeoToolkit.Umbraco.Redirects.Core/Constants/ImportConstants.cs b/src/SeoToolkit.Umbraco.Redirects.Core/Constants/ImportConstants.cs new file mode 100644 index 00000000..1a1e0bb0 --- /dev/null +++ b/src/SeoToolkit.Umbraco.Redirects.Core/Constants/ImportConstants.cs @@ -0,0 +1,8 @@ +namespace SeoToolkit.Umbraco.Redirects.Core.Constants; + +public class ImportConstants +{ + public const string SessionAlias = "uploadedImportRedirectsFile"; + public const string SessionFileTypeAlias = "uploadedFileType"; + public const string SessionDomainId = "selectedDomain"; +} \ No newline at end of file diff --git a/src/SeoToolkit.Umbraco.Redirects.Core/Controllers/RedirectsController.cs b/src/SeoToolkit.Umbraco.Redirects.Core/Controllers/RedirectsController.cs index 1595a2a5..7c56657c 100644 --- a/src/SeoToolkit.Umbraco.Redirects.Core/Controllers/RedirectsController.cs +++ b/src/SeoToolkit.Umbraco.Redirects.Core/Controllers/RedirectsController.cs @@ -1,6 +1,11 @@ -using System.Linq; +using System; +using System.IO; +using System.Linq; +using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; -using Microsoft.Extensions.Options; +using SeoToolkit.Umbraco.Redirects.Core.Constants; +using SeoToolkit.Umbraco.Redirects.Core.Enumerators; +using SeoToolkit.Umbraco.Redirects.Core.Helpers; using Umbraco.Cms.Core.Models; using Umbraco.Cms.Core.Services; using Umbraco.Cms.Core.Web; @@ -22,16 +27,19 @@ public class RedirectsController : UmbracoAuthorizedApiController private readonly IUmbracoContextFactory _umbracoContextFactory; private readonly ILocalizationService _localizationService; private readonly IBackOfficeSecurityAccessor _backOfficeSecurityAccessor; + private readonly RedirectsImportHelper _redirectsImportHelper; + public RedirectsController(IRedirectsService redirectsService, IUmbracoContextFactory umbracoContextFactory, ILocalizationService localizationService, - IBackOfficeSecurityAccessor backOfficeSecurityAccessor) + IBackOfficeSecurityAccessor backOfficeSecurityAccessor, RedirectsImportHelper redirectsImportHelper) { _redirectsService = redirectsService; _umbracoContextFactory = umbracoContextFactory; _localizationService = localizationService; _backOfficeSecurityAccessor = backOfficeSecurityAccessor; + _redirectsImportHelper = redirectsImportHelper; } [HttpPost] @@ -134,5 +142,63 @@ public IActionResult Delete(DeleteRedirectsPostModel postModel) _redirectsService.Delete(postModel.Ids); return GetAll(1, 20); } + + [HttpPost] + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status400BadRequest)] + [ProducesResponseType(StatusCodes.Status422UnprocessableEntity)] + public IActionResult Validate(ImportRedirectsFileExtension fileExtension, string domain, IFormFile file) + { + if (file.Length == 0) + { + return BadRequest("Please select a file"); + } + + using var memoryStream = new MemoryStream(); + file.CopyTo(memoryStream); + + var result = _redirectsImportHelper.Validate(fileExtension, memoryStream, domain); + if (result.Success) + { + + // Storing the file contents in session for later import + HttpContext.Session.Set(ImportConstants.SessionAlias, memoryStream.ToArray()); + HttpContext.Session.SetString(ImportConstants.SessionFileTypeAlias, fileExtension.ToString()); + HttpContext.Session.SetString(ImportConstants.SessionDomainId, domain); + + return Ok(); + } + + return UnprocessableEntity(!string.IsNullOrWhiteSpace(result.Status) ? result.Status : "Something went wrong during the validation"); + } + + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status400BadRequest)] + [ProducesResponseType(StatusCodes.Status422UnprocessableEntity)] + public IActionResult Import() + { + var fileContent = HttpContext.Session.Get(ImportConstants.SessionAlias); + var fileExtensionString = HttpContext.Session.GetString(ImportConstants.SessionFileTypeAlias); + var domain= HttpContext.Session.GetString(ImportConstants.SessionDomainId); + + if (fileContent == null || fileExtensionString == null || domain == null) + { + return BadRequest("Something went wrong during import, please try again"); + } + + if (!Enum.TryParse(fileExtensionString, out ImportRedirectsFileExtension fileExtension)) + { + return UnprocessableEntity("Invalid file extension."); + } + + using var memoryStream = new MemoryStream(fileContent); + var result = _redirectsImportHelper.Import(fileExtension, memoryStream, domain); + if (result.Success) + { + return Ok(); + } + + return UnprocessableEntity(!string.IsNullOrWhiteSpace(result.Status) ? result.Status : "Something went wrong during the import"); + } } } diff --git a/src/SeoToolkit.Umbraco.Redirects.Core/Enumerators/ImportRedirectsFileExtension.cs b/src/SeoToolkit.Umbraco.Redirects.Core/Enumerators/ImportRedirectsFileExtension.cs new file mode 100644 index 00000000..1eac99af --- /dev/null +++ b/src/SeoToolkit.Umbraco.Redirects.Core/Enumerators/ImportRedirectsFileExtension.cs @@ -0,0 +1,7 @@ +namespace SeoToolkit.Umbraco.Redirects.Core.Enumerators; + +public enum ImportRedirectsFileExtension +{ + Csv, + Excel +} diff --git a/src/SeoToolkit.Umbraco.Redirects.Core/Helpers/RedirectsImportHelper.cs b/src/SeoToolkit.Umbraco.Redirects.Core/Helpers/RedirectsImportHelper.cs new file mode 100644 index 00000000..e59b8c6f --- /dev/null +++ b/src/SeoToolkit.Umbraco.Redirects.Core/Helpers/RedirectsImportHelper.cs @@ -0,0 +1,260 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using ExcelDataReader; +using Microsoft.VisualBasic.FileIO; +using SeoToolkit.Umbraco.Redirects.Core.Enumerators; +using SeoToolkit.Umbraco.Redirects.Core.Interfaces; +using SeoToolkit.Umbraco.Redirects.Core.Models.Business; +using Umbraco.Cms.Core; +using Umbraco.Cms.Core.Routing; +using Umbraco.Cms.Core.Web; +using Umbraco.Extensions; + +namespace SeoToolkit.Umbraco.Redirects.Core.Helpers; + +public class RedirectsImportHelper +{ + private Domain _selectedDomain; + private readonly IRedirectsService _redirectsService; + private readonly IUmbracoContextFactory _umbracoContextFactory; + + public RedirectsImportHelper(IRedirectsService redirectsService, IUmbracoContextFactory umbracoContextFactory) + { + _redirectsService = redirectsService; + _umbracoContextFactory = umbracoContextFactory; + } + + public Attempt?, string> Validate(ImportRedirectsFileExtension fileExtension, MemoryStream memoryStream, string domain) + { + SetDomain(domain); + Attempt, string> validationResult; + switch (fileExtension) + { + case ImportRedirectsFileExtension.Csv: + validationResult = ValidateCsv(memoryStream); + break; + case ImportRedirectsFileExtension.Excel: + validationResult = ValidateExcel(memoryStream); + break; + default: + return Attempt?, string>.Fail("Invalid filetype, you may only use .csv or .xls", result: null); + } + + if (validationResult.Success) + { + return Attempt?, string>.Succeed(string.Empty, validationResult.Result); + } + + return validationResult; + } + + public Attempt?, string> Import(ImportRedirectsFileExtension fileExtension, MemoryStream memoryStream, string domain) + { + SetDomain(domain); + var validation = Validate(fileExtension, memoryStream, domain); + if (validation is { Success: true, Result: not null } && validation.Result.Count != 0) + { + foreach (var entry in validation.Result) + { + SaveRedirect(entry); + } + } + + return validation; + } + + private bool UrlExists(string oldUrl) + { + var existingRedirects = _redirectsService.GetAll(1, 10, null, null, oldUrl.TrimEnd('/')); + if (existingRedirects.TotalItems > 0 && existingRedirects.Items is not null) + { + if (existingRedirects.Items.All(x => x.OldUrl != oldUrl.TrimEnd('/'))) + { + //exact match not found + return false; + } + if (existingRedirects.Items.Count(x => x.Domain is null || x.Domain.Id == 0) > 0) + { + //url exists without any domain set + return true; + } + if (existingRedirects.Items.Count(x => x.Domain == _selectedDomain) > 0) + { + //url exists with specific domain set + return true; + } + } + return false; + } + + private Attempt?, string> ValidateCsv(Stream fileStream) + { + //This currently assumes no header, and only 2 columns, from and to url + fileStream.Position = 0; + using (var reader = new StreamReader(fileStream)) + using (var parser = new TextFieldParser(reader)) + { + parser.TextFieldType = FieldType.Delimited; + parser.SetDelimiters(","); + var parsedData = new Dictionary(); + + while (!parser.EndOfData) + { + var fields = parser.ReadFields(); + + if (fields?.Length != 2) + { + return Attempt?, string>.Fail($"Validation Fail: only 2 columns allowed on line {parser.LineNumber}", result: null); + } + var fromUrl = CleanFromUrl(fields[0]); + var toUrl = Uri.IsWellFormedUriString(fields[1], UriKind.Absolute) ? + fields[1] : + fields[1].EnsureEndsWith("/").ToLower(); + + if(!string.IsNullOrWhiteSpace(fromUrl) && !string.IsNullOrWhiteSpace(toUrl)){ + + if (!Uri.IsWellFormedUriString(fromUrl, UriKind.Relative)) + { + return Attempt?, string>.Fail($"line {parser.LineNumber}", result: null); + } + if(UrlExists(fromUrl)) + { + return Attempt?, string>.Fail($"Redirect already exists for 'from' URL: {fromUrl} validation aborted.", result: null); + } + if (parsedData.ContainsKey(fromUrl.TrimEnd("/"))) + { + return Attempt?, string>.Fail($"Url appears more then one time in import file: {fromUrl}", result: null); + } + parsedData.Add(fromUrl, toUrl); + + } + else + { + return Attempt?, string>.Fail($"line {parser.LineNumber}", result: null); + } + } + return Attempt?, string>.Succeed(string.Empty, parsedData); + } + + + } + private Attempt?, string> ValidateExcel(Stream fileStream) + { + fileStream.Position = 0; + Encoding.RegisterProvider(CodePagesEncodingProvider.Instance); + + try + { + using var reader = ExcelReaderFactory.CreateReader(fileStream); + var result = reader.AsDataSet(); + var dataTable = result.Tables[0]; + + var parsedData = new Dictionary(); + + for (var i = 0; i < dataTable.Rows.Count; i++) + { + var row = dataTable.Rows[i]; + if (row.ItemArray.Length != 2) + { + return Attempt?, string>.Fail($"only 2 columns allowed on row {i + 1}"); + } + + var fromUrl = CleanFromUrl(row[0].ToString()); + var toUrl = Uri.IsWellFormedUriString(row[1].ToString(), UriKind.Absolute) + ? row[1].ToString() + : row[1].ToString()?.EnsureEndsWith("/").ToLower(); + + if (!string.IsNullOrWhiteSpace(fromUrl) && !string.IsNullOrWhiteSpace(toUrl)) + { + if (!Uri.IsWellFormedUriString(fromUrl, UriKind.Relative)) + { + return Attempt?, string>.Fail($"row {i + 1}", result: null); + } + + if (UrlExists(fromUrl)) + { + return Attempt?, string>.Fail( + $"Redirect already exists for 'from' URL: {fromUrl} validation aborted."); + } + if (parsedData.ContainsKey(fromUrl.TrimEnd("/"))) + { + return Attempt?, string>.Fail($"Url appears more then one time in import file: {fromUrl}", result: null); + } + parsedData.Add(fromUrl, toUrl); + } + else + { + return Attempt?, string>.Fail($"row {i + 1}"); + } + } + + return Attempt?, string>.Succeed(string.Empty, parsedData); + } + catch + { + return Attempt?, string>.Fail("Invalid file type"); + } + } + + private void SetDomain(string domain) + { + var parseSuccess = int.TryParse(domain, out var domainId); + if (!parseSuccess) + { + domainId = 0; + } + + using var ctx = _umbracoContextFactory.EnsureUmbracoContext(); + var foundDomain = ctx.UmbracoContext.Domains?.GetAll(false).FirstOrDefault(it => it.Id == domainId); + if (foundDomain is null) + { + return; + } + + _selectedDomain = foundDomain; + } + + private static string CleanFromUrl(string? url) + { + if (string.IsNullOrWhiteSpace(url)) + { + return string.Empty; + } + var urlParts = url.ToLowerInvariant().Split('?'); + if (urlParts.Length == 0) + { + return string.Empty; + } + var fromUrl = urlParts[0].TrimEnd('/'); + if (urlParts.Length > 1) + { + fromUrl = $"{fromUrl}?{string.Join("?", urlParts.Skip(1))}"; + } + + fromUrl = fromUrl.EnsureStartsWith("/"); + + return fromUrl; + } + + private void SaveRedirect(KeyValuePair entry) + { + var redirect = new Redirect + { + Domain = _selectedDomain, + CustomDomain = null, + Id = 0, + IsEnabled = true, + IsRegex = false, + NewNodeCulture = null, + NewNode = null, + NewUrl = entry.Value, + OldUrl = entry.Key, + RedirectCode = 301 + }; + + _redirectsService.Save(redirect); + } +} diff --git a/src/SeoToolkit.Umbraco.Redirects.Core/SeoToolkit.Umbraco.Redirects.Core.csproj b/src/SeoToolkit.Umbraco.Redirects.Core/SeoToolkit.Umbraco.Redirects.Core.csproj index f89545a3..1ca227e8 100644 --- a/src/SeoToolkit.Umbraco.Redirects.Core/SeoToolkit.Umbraco.Redirects.Core.csproj +++ b/src/SeoToolkit.Umbraco.Redirects.Core/SeoToolkit.Umbraco.Redirects.Core.csproj @@ -15,6 +15,11 @@ 3.5.1 + + + + + diff --git a/src/SeoToolkit.Umbraco.Redirects/wwwroot/Redirects/Dialogs/import.controller.js b/src/SeoToolkit.Umbraco.Redirects/wwwroot/Redirects/Dialogs/import.controller.js new file mode 100644 index 00000000..f2a476c5 --- /dev/null +++ b/src/SeoToolkit.Umbraco.Redirects/wwwroot/Redirects/Dialogs/import.controller.js @@ -0,0 +1,132 @@ +(function () { + "use strict"; + + function importController($http, $scope, formHelper, localizationService, redirectsApiResource, notificationsService) { + + var vm = this; + + vm.submit = submit; + vm.close = close; + vm.handleFiles = handleFiles; + vm.submit = submit; + vm.import = importFile; + vm.validated = false; + vm.validationSuccess = ""; + vm.validationFailed = ""; + vm.importSuccess = ""; + vm.importFailed = ""; + vm.fileTypePropertyLabel = "Select the file type"; + vm.fileTypePropertyDescription = "Select the type of file you want to import the redirects from"; + vm.filePropertyLabel = "Select a file to import"; + vm.domainPropertyLabel = "Select the domain to import for"; + vm.domainPropertyDescription = "if nothing is selected, redirects will be imported for all sites"; + + var labelKeys = [ + "redirect_fileTypePropertyLabel", "redirect_fileTypePropertyDescription", + "redirect_filePropertyLabel", + "redirect_validationSuccess", "redirect_validationFailed", + "redirect_importSuccess", "redirect_importFailed", + "redirect_domainPropertyLabel","redirect_domainPropertyDescription", + ]; + localizationService.localizeMany(labelKeys).then(function (data) { + vm.fileTypeSelection.label = data[0]; + vm.fileTypeSelection.description = data[1]; + vm.fileSelection.label = data[2]; + vm.validationSuccess = data[3]; + vm.validationFailed = data[4]; + vm.importSuccess = data[5]; + vm.importFailed = data[6]; + vm.domainPropertyLabel = data[7]; + vm.domainPropertyDescription = data[8]; + }); + + vm.fileTypes = [ + { + label:"CSV", + extension:".csv" + }, + { + label:"Excel", + extension:".xls,.xlsx" + }, + ]; + + vm.fileTypeSelection = { + alias: "fileType", + label: vm.fileTypePropertyLabel, + description: vm.fileTypePropertyDescription, + value: 0, + validation: { + mandatory: true + } + } + + vm.domainSelection = { + alias: "domain", + label: vm.domainPropertyLabel, + description: vm.domainPropertyDescription, + value: "0", + validation: { + mandatory: true + } + } + + vm.fileSelection = { + alias: "file", + label: vm.filePropertyLabel, + value: 0, + validation: { + mandatory: true + } + } + + function handleFiles(files) { + if (files && files.length > 0) { + vm.fileSelection.value = files[0]; + } + } + + function submit() { + if (formHelper.submitForm({ scope: $scope, formCtrl: $scope.createRedirectForm })) { + redirectsApiResource.validateRedirects(vm.fileTypeSelection.value.label, vm.fileSelection.value, vm.domainSelection.value).then(function (response) { + vm.notification = vm.validationSuccess; + vm.validated = true; + }).catch(function (error) { + vm.notification = `${vm.validationFailed} ${error}`; + vm.validated = false; + }); + } + } + function importFile() { + if (formHelper.submitForm({ scope: $scope, formCtrl: $scope.createRedirectForm })) { + redirectsApiResource.importRedirects().then(function (response) { + notificationsService.success(`${vm.fileSelection.value.name} ${vm.importSuccess}`); + vm.validated = true; + $scope.model.close(); + }).catch(function (error) { + vm.notification = `${vm.validationFailed} ${error}`; + vm.validated = false; + }); + } + } + + function close() { + if ($scope.model.close) { + $scope.model.close(); + } + } + + init(); + + function init(){ + $http.get("backoffice/SeoToolkit/Redirects/GetDomains").then(function (response) { + vm.domains = response.data.map(function (item) { + return { id: item.Id, name: item.Name } + }); + vm.domains.splice(0, 0, { id: 0, name: "All Sites" }); + }); + } + } + + angular.module("umbraco").controller("SeoToolkit.Redirects.ImportController", importController); +})(); \ No newline at end of file diff --git a/src/SeoToolkit.Umbraco.Redirects/wwwroot/Redirects/Dialogs/import.html b/src/SeoToolkit.Umbraco.Redirects/wwwroot/Redirects/Dialogs/import.html new file mode 100644 index 00000000..cde91174 --- /dev/null +++ b/src/SeoToolkit.Umbraco.Redirects/wwwroot/Redirects/Dialogs/import.html @@ -0,0 +1,66 @@ +
+
+ + + + + + + +
+ +
+
+ +
+ +
+
+ + + + + +
+ {{vm.notification}} + + +
+
+
+
+ + + + + + + +
+
+
\ No newline at end of file diff --git a/src/SeoToolkit.Umbraco.Redirects/wwwroot/Redirects/Dialogs/redirectsapi.resource.js b/src/SeoToolkit.Umbraco.Redirects/wwwroot/Redirects/Dialogs/redirectsapi.resource.js new file mode 100644 index 00000000..ba60a71b --- /dev/null +++ b/src/SeoToolkit.Umbraco.Redirects/wwwroot/Redirects/Dialogs/redirectsapi.resource.js @@ -0,0 +1,33 @@ +angular.module('umbraco.resources').factory('redirectsApiResource', + function (umbRequestHelper, Upload) { + // the factory object returned + var baseUrl = "backoffice/SeoToolkit/Redirects/"; + + return { + validateRedirects: function (fileExtension, file, domainId) { + return umbRequestHelper.resourcePromise( + Upload.upload({ + url: baseUrl + 'Validate?fileExtension=' + fileExtension + '&domain=' + domainId, + file: file + }).then(function (response) { + return response; + }).catch(function (error) { + var errorMsg = error.data ? error.data : "Failed to validate redirects"; + return Promise.reject(errorMsg); + }), + ); + }, + importRedirects: function () { + return umbRequestHelper.resourcePromise( + Upload.upload({ + url: baseUrl + 'Import' + }).then(function (response) { + return response; + }).catch(function (error) { + var errorMsg = error.data ? error.data : "Failed to import redirects"; + return Promise.reject(errorMsg); + }), + ); + }, + }; + }); diff --git a/src/SeoToolkit.Umbraco.Redirects/wwwroot/Redirects/lang/en-us.xml b/src/SeoToolkit.Umbraco.Redirects/wwwroot/Redirects/lang/en-us.xml index ea783113..11fd2b37 100644 --- a/src/SeoToolkit.Umbraco.Redirects/wwwroot/Redirects/lang/en-us.xml +++ b/src/SeoToolkit.Umbraco.Redirects/wwwroot/Redirects/lang/en-us.xml @@ -2,5 +2,15 @@ Create Redirect + Import redirects + Select the file type + Select the type of file you want to import the redirects from + Select a file to import + Validated successfully + Something went wrong: + Imported successfully + Something went wrong: + Select the domain to import for + if nothing is selected, redirects will be active for all domains \ No newline at end of file diff --git a/src/SeoToolkit.Umbraco.Redirects/wwwroot/Redirects/lang/nl-nl.xml b/src/SeoToolkit.Umbraco.Redirects/wwwroot/Redirects/lang/nl-nl.xml index d680bdc6..e6224f1a 100644 --- a/src/SeoToolkit.Umbraco.Redirects/wwwroot/Redirects/lang/nl-nl.xml +++ b/src/SeoToolkit.Umbraco.Redirects/wwwroot/Redirects/lang/nl-nl.xml @@ -2,5 +2,15 @@ Redirect aanmaken + Redirects importeren + Selecteer het type bestand + Selecteer hier het type bestand dat geimporteerd moet worden + Selecteer het bestand + Gevalideerd + Er ging iets mis: + Succesvol geimporteerd + Er ging iets mis: + Selecteer hier het domein + Als niets is geselecteerd worden de redirects actief voor alle domeinen \ No newline at end of file diff --git a/src/SeoToolkit.Umbraco.Redirects/wwwroot/Redirects/package.manifest b/src/SeoToolkit.Umbraco.Redirects/wwwroot/Redirects/package.manifest index d5655b5b..9ab4e1e3 100644 --- a/src/SeoToolkit.Umbraco.Redirects/wwwroot/Redirects/package.manifest +++ b/src/SeoToolkit.Umbraco.Redirects/wwwroot/Redirects/package.manifest @@ -2,7 +2,9 @@ "javascript": [ "/App_Plugins/SeoToolkit/backoffice/Redirects/list.controller.js", "/App_Plugins/SeoToolkit/Redirects/Dialogs/createRedirect.controller.js", - "/App_Plugins/SeoToolkit/Redirects/Dialogs/linkPicker.controller.js" + "/App_Plugins/SeoToolkit/Redirects/Dialogs/linkPicker.controller.js", + "/App_Plugins/SeoToolkit/Redirects/Dialogs/redirectsapi.resource.js", + "/App_Plugins/SeoToolkit/Redirects/Dialogs/import.controller.js" ], "css": [ "/App_Plugins/SeoToolkit/Redirects/css/main.css" diff --git a/src/SeoToolkit.Umbraco.Redirects/wwwroot/backoffice/Redirects/list.controller.js b/src/SeoToolkit.Umbraco.Redirects/wwwroot/backoffice/Redirects/list.controller.js index 0a272445..572ffa4a 100644 --- a/src/SeoToolkit.Umbraco.Redirects/wwwroot/backoffice/Redirects/list.controller.js +++ b/src/SeoToolkit.Umbraco.Redirects/wwwroot/backoffice/Redirects/list.controller.js @@ -1,7 +1,7 @@ (function () { "use strict"; - function redirectListController($timeout, $http, listViewHelper, notificationsService, editorService) { + function redirectListController($timeout, $http, listViewHelper, notificationsService, editorService, localizationService) { var vm = this; vm.items = []; @@ -37,8 +37,16 @@ vm.search = search; vm.create = openRedirectDialog; + vm.import = openImportDialog; vm.deleteSelection = deleteSelection; + vm.dialogLabel = "import redirects" + + var labelKeys = ["redirect_importButton"]; + localizationService.localizeMany(labelKeys).then(function (data) { + vm.dialogLabel = data[0]; + }); + vm.pageNumber = 1; vm.pageSize = 20; vm.totalPages = 1; @@ -98,6 +106,23 @@ goToPage(1); } + function openImportDialog(model) { + var redirectDialogOptions = { + title: vm.dialogLabel, + view: "/App_Plugins/SeoToolkit/Redirects/Dialogs/import.html", + size: "small", + close: function () { + loadItems(); + editorService.close(); + } + }; + if (model) { + redirectDialogOptions.redirect = model; + } + + editorService.open(redirectDialogOptions); + } + function openRedirectDialog(model) { var redirectDialogOptions = { title: "Create redirect", diff --git a/src/SeoToolkit.Umbraco.Redirects/wwwroot/backoffice/Redirects/list.html b/src/SeoToolkit.Umbraco.Redirects/wwwroot/backoffice/Redirects/list.html index 8014ccfd..e9a26143 100644 --- a/src/SeoToolkit.Umbraco.Redirects/wwwroot/backoffice/Redirects/list.html +++ b/src/SeoToolkit.Umbraco.Redirects/wwwroot/backoffice/Redirects/list.html @@ -10,6 +10,11 @@ size="xs" label-key="redirect_create" action="vm.create()">Add Redirect + Import redirects @@ -19,7 +24,7 @@ label-key="buttons_clearSelection" action="vm.clearSelection()">Clear Selection - + @@ -34,9 +39,9 @@ action="vm.deleteSelection()">Delete - + - +